From 2a4ea2a231f1ed86c78746658312418ed403c2f9 Mon Sep 17 00:00:00 2001 From: John Votta Date: Fri, 29 May 2026 09:16:50 -0700 Subject: [PATCH 01/28] Fix task-queue config set help: use real fairness weight flag names (#1056) The `temporal task-queue config set` help text referenced three flags that don't exist: - `--fairness-key-weight-set =` - `--fairness-key-weight-unset ` - `--fairness-key-weight-unset-all` The real flags, defined a few lines below in the same YAML block, are: - `--fairness-key-weight =` (with `=default` to unset a single key) - `--fairness-key-weight-clear-all` This PR updates the example block and the unset instructions in `commands.yaml` to match, and regenerates `commands.gen.go`. Verified locally with `go run ./cmd/temporal task-queue config set --help`. This bug also surfaces in the auto-generated docs site at [docs/cli/task-queue.mdx](https://docs.temporal.io/cli/task-queue#set), which a customer hit in [this Slack thread](https://temporaltechnologies.slack.com/archives/C0748KDH2DD/p1779899719317389?thread_ts=1779898503.202859&cid=C0748KDH2DD). The docs PR over in `temporalio/documentation#4625` works around it by referencing the real flag names directly. Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.gen.go | 4 ++-- internal/temporalcli/commands.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 03424647a..468900910 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -2548,9 +2548,9 @@ func NewTemporalTaskQueueConfigSetCommand(cctx *CommandContext, parent *Temporal s.Command.Use = "set [flags]" s.Command.Short = "Set Task Queue configuration" if hasHighlighting { - s.Command.Long = "Update configuration settings for a Task Queue.\n\n\x1b[1mtemporal task-queue config set \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity \\\n --namespace YourNamespace \\\n --queue-rps-limit \\\n --queue-rps-limit-reason \\\n --fairness-key-rps-limit-default \\\n --fairness-key-rps-limit-reason \\\n --fairness-key-weight-set HighPriority=2.0 \\\n --fairness-key-weight-set LowPriority=0.5\x1b[0m\n\nThis command supports updating:\n- Queue rate limits: Controls the overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Sets default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key.\n- Fairness key weight overrides: Set custom weights for specific fairness keys.\n Weights control the relative share of capacity each key receives.\n\nTo unset a rate limit, pass in 'default', for example: --queue-rps-limit default\nTo unset specific fairness weights, use --fairness-key-weight-unset \nTo unset all fairness weights, use --fairness-key-weight-unset-all" + s.Command.Long = "Update configuration settings for a Task Queue.\n\n\x1b[1mtemporal task-queue config set \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity \\\n --namespace YourNamespace \\\n --queue-rps-limit \\\n --queue-rps-limit-reason \\\n --fairness-key-rps-limit-default \\\n --fairness-key-rps-limit-reason \\\n --fairness-key-weight HighPriority=2.0 \\\n --fairness-key-weight LowPriority=0.5\x1b[0m\n\nThis command supports updating:\n- Queue rate limits: Controls the overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Sets default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key.\n- Fairness key weight overrides: Set custom weights for specific fairness keys.\n Weights control the relative share of capacity each key receives.\n\nTo unset a rate limit, pass in 'default', for example: --queue-rps-limit default\nTo unset a specific fairness weight, use --fairness-key-weight =default\nTo unset all fairness weights, use --fairness-key-weight-clear-all" } else { - s.Command.Long = "Update configuration settings for a Task Queue.\n\n```\ntemporal task-queue config set \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity \\\n --namespace YourNamespace \\\n --queue-rps-limit \\\n --queue-rps-limit-reason \\\n --fairness-key-rps-limit-default \\\n --fairness-key-rps-limit-reason \\\n --fairness-key-weight-set HighPriority=2.0 \\\n --fairness-key-weight-set LowPriority=0.5\n```\n\nThis command supports updating:\n- Queue rate limits: Controls the overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Sets default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key.\n- Fairness key weight overrides: Set custom weights for specific fairness keys.\n Weights control the relative share of capacity each key receives.\n\nTo unset a rate limit, pass in 'default', for example: --queue-rps-limit default\nTo unset specific fairness weights, use --fairness-key-weight-unset \nTo unset all fairness weights, use --fairness-key-weight-unset-all" + s.Command.Long = "Update configuration settings for a Task Queue.\n\n```\ntemporal task-queue config set \\\n --task-queue YourTaskQueue \\\n --task-queue-type activity \\\n --namespace YourNamespace \\\n --queue-rps-limit \\\n --queue-rps-limit-reason \\\n --fairness-key-rps-limit-default \\\n --fairness-key-rps-limit-reason \\\n --fairness-key-weight HighPriority=2.0 \\\n --fairness-key-weight LowPriority=0.5\n```\n\nThis command supports updating:\n- Queue rate limits: Controls the overall rate limit of the task queue.\n This setting overrides the worker rate limit if set.\n Unless modified, this is the system-defined rate limit.\n- Fairness key rate limit defaults: Sets default rate limits for fairness keys.\n If set, each individual fairness key will be limited to this rate,\n scaled by the weight of the fairness key.\n- Fairness key weight overrides: Set custom weights for specific fairness keys.\n Weights control the relative share of capacity each key receives.\n\nTo unset a rate limit, pass in 'default', for example: --queue-rps-limit default\nTo unset a specific fairness weight, use --fairness-key-weight =default\nTo unset all fairness weights, use --fairness-key-weight-clear-all" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 78bdf3513..262cc55dd 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -3492,8 +3492,8 @@ commands: --queue-rps-limit-reason \ --fairness-key-rps-limit-default \ --fairness-key-rps-limit-reason \ - --fairness-key-weight-set HighPriority=2.0 \ - --fairness-key-weight-set LowPriority=0.5 + --fairness-key-weight HighPriority=2.0 \ + --fairness-key-weight LowPriority=0.5 ``` This command supports updating: @@ -3507,8 +3507,8 @@ commands: Weights control the relative share of capacity each key receives. To unset a rate limit, pass in 'default', for example: --queue-rps-limit default - To unset specific fairness weights, use --fairness-key-weight-unset - To unset all fairness weights, use --fairness-key-weight-unset-all + To unset a specific fairness weight, use --fairness-key-weight =default + To unset all fairness weights, use --fairness-key-weight-clear-all options: - name: task-queue type: string From 12655a439044ff2e05af85ed59364d30a95895c4 Mon Sep 17 00:00:00 2001 From: Kent Gruber Date: Fri, 29 May 2026 15:49:21 -0400 Subject: [PATCH 02/28] VLN-1354: pin and bump GitHub Actions to latest versions (#1054) VLN-1354: pin and bump GitHub Actions to latest versions Pin all GitHub Actions to full 40-character commit SHAs and bump to latest versions across all workflows: - actions/checkout v4.3.1 -> v6.0.2 - actions/setup-go -> v6.4.0 - actions/upload-artifact (already pinned, kept current) - actions/create-github-app-token v2.2.2 -> v3.1.1 (#1037) - actions/github-script v8.0.0 -> v9.0.0 (#1036) - docker/setup-qemu-action v3.2.0 -> v4.1.0 - docker/setup-buildx-action v3.10.0 -> v4.1.0 (#1035) - docker/login-action v3.5.0 -> v4.2.0 - goreleaser/goreleaser-action v6.4.0 -> v7.2.1 (#1039) Resolves open Dependabot PRs #1035, #1036, #1037, #1038, #1039. Left temporalio/public-actions refs unchanged per exception policy. --------- Co-authored-by: picatz <14850816+picatz@users.noreply.github.com> Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- .github/workflows/build-and-publish-docker.yml | 10 +++++----- .github/workflows/ci.yaml | 10 +++++----- .github/workflows/govulncheck.yml | 4 ++-- .github/workflows/release.yml | 6 +++--- .github/workflows/trigger-docs.yml | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-and-publish-docker.yml b/.github/workflows/build-and-publish-docker.yml index f6bc513ab..502c103d1 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@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -36,7 +36,7 @@ jobs: env: INPUT_VERSION: ${{ inputs.version }} INPUT_TAG_LATEST: ${{ inputs.tag_latest }} - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const inputVersion = process.env.INPUT_VERSION; @@ -82,14 +82,14 @@ jobs: ls -lh dist/arm64/temporal - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log in to Docker Hub if: inputs.publish - uses: docker/login-action@v3 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3a9ee87a3..2314b3ef3 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@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.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@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod @@ -72,7 +72,7 @@ jobs: run: gotestsum --junitfile junit-xml/${{matrix.os}}.xml -- ./... - name: Upload junit-xml artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() with: name: junit-xml--${{github.run_id}}--${{github.run_attempt}}--${{matrix.os}} diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index b8b6cbe97..7c49fd9df 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@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.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 0bcd5d666..24ed5570e 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@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: "go.mod" check-latest: true @@ -47,7 +47,7 @@ jobs: run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1 with: version: v2.12.7 args: release diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml index cfa6349a8..40b4a2de4 100644 --- a/.github/workflows/trigger-docs.yml +++ b/.github/workflows/trigger-docs.yml @@ -39,7 +39,7 @@ jobs: - name: Generate token id: generate_token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 with: app-id: ${{ secrets.TEMPORAL_CICD_APP_ID }} private-key: ${{ secrets.TEMPORAL_CICD_PRIVATE_KEY }} From 6692b61645e844949ac9576d6f261e3246f3255c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 05:21:12 -0500 Subject: [PATCH 03/28] Bump golang.org/x/mod from 0.35.0 to 0.36.0 (#1052) Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.35.0 to 0.36.0.
Commits
  • 643da9b go.mod: update golang.org/x dependencies
  • ccc3cdf zip: include 'but content has correct sum' note in TestVCS
  • ab30318 zip: update zip hashes for new flate compression
  • See full diff in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 39af1d4ff..d99d153ec 100644 --- a/go.mod +++ b/go.mod @@ -22,9 +22,9 @@ require ( go.temporal.io/sdk/contrib/envconfig v1.0.0 go.temporal.io/server v1.31.0 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 - golang.org/x/mod v0.35.0 - golang.org/x/term v0.41.0 - golang.org/x/tools v0.43.0 + golang.org/x/mod v0.36.0 + golang.org/x/term v0.42.0 + golang.org/x/tools v0.44.0 google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 @@ -190,12 +190,12 @@ 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.49.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.256.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect diff --git a/go.sum b/go.sum index cc18ea5d4..1af7e88ea 100644 --- a/go.sum +++ b/go.sum @@ -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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= 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.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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,8 +526,8 @@ 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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= 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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -556,15 +556,15 @@ 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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.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.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 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,8 +572,8 @@ 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.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -590,8 +590,8 @@ 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.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= 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= From 5380e71e9bf51bc047f9203d04666128dca7a99f Mon Sep 17 00:00:00 2001 From: Nasit Sarwar Sony Date: Sat, 30 May 2026 04:08:40 -0700 Subject: [PATCH 04/28] =?UTF-8?q?=20Add=20temporal=20schedule=20list-match?= =?UTF-8?q?ing-times=20command=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=E2=80=A6=20(#1047)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related issues Closes #1030 ## What changed? Adds `temporal schedule list-matching-times` command implementing the `ListScheduleMatchingTimes` RPC. Allows users to preview when a schedule's spec would match within a given time range (past or future) without taking any actions. ## Checklist **Stability** - [x] Breaking changes are marked with :boom: in the PR title and release notes - [x] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [x] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [x] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [x] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [x] New flags don't duplicate an existing flag that serves the same purpose - [x] New flags do not have short aliases without strong justification - [x] Experimental features are marked with `(Experimental feature)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [x] All flags shown in help text and examples are implemented and functional - [x] Summaries use sentence case and have no trailing period - [x] Long descriptions end with a period and include at least one example invocation - [x] Examples use long flags (`--namespace`, not `-n`), one flag per line - [x] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [x] Results go to stdout; errors and warnings go to stderr - [x] Error messages are lowercase with no trailing punctuation **Tests** - [x] Added functional test(s) (`SharedServerSuite`) - [ ] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** ``` temporal server start-dev --headless temporal schedule create \ --schedule-id YourScheduleId \ --interval 1h \ --workflow-id YourWorkflowId \ --task-queue YourTaskQueue \ --workflow-type YourWorkflowType ``` **Happy path** ``` $ temporal schedule list-matching-times \ --schedule-id YourScheduleId \ --start-time 2025-01-01T00:00:00Z \ --end-time 2025-01-01T23:59:59Z Time 2025-01-01T00:00:00Z 2025-01-01T01:00:00Z ... $ temporal schedule list-matching-times \ --schedule-id YourScheduleId \ --start-time 2025-01-01T00:00:00Z \ --end-time 2025-01-01T23:59:59Z \ -o json {"startTime":["2025-01-01T00:00:00Z","2025-01-01T01:00:00Z",...]} ``` **Error case** ``` $ temporal schedule list-matching-times \ --schedule-id nonexistent-id \ --start-time 2025-01-01T00:00:00Z \ --end-time 2025-01-01T23:59:59Z Error: ... $ echo $? 1 ``` **Composition** ``` $ temporal schedule list-matching-times \ --schedule-id YourScheduleId \ --start-time 2025-06-01T00:00:00Z \ --end-time 2025-06-07T23:59:59Z \ -o json | jq '.startTime | length' 168 ``` --- internal/temporalcli/commands.gen.go | 34 ++++++++++++ internal/temporalcli/commands.schedule.go | 34 ++++++++++++ .../temporalcli/commands.schedule_test.go | 53 +++++++++++++++++++ internal/temporalcli/commands.yaml | 31 +++++++++++ 4 files changed, 152 insertions(+) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 468900910..5186d70e3 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -2103,6 +2103,7 @@ func NewTemporalScheduleCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalScheduleDeleteCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleDescribeCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleListCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalScheduleListMatchingTimesCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleToggleCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleTriggerCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleUpdateCommand(cctx, &s).Command) @@ -2270,6 +2271,39 @@ func NewTemporalScheduleListCommand(cctx *CommandContext, parent *TemporalSchedu return &s } +type TemporalScheduleListMatchingTimesCommand struct { + Parent *TemporalScheduleCommand + Command cobra.Command + ScheduleIdOptions + StartTime cliext.FlagTimestamp + EndTime cliext.FlagTimestamp +} + +func NewTemporalScheduleListMatchingTimesCommand(cctx *CommandContext, parent *TemporalScheduleCommand) *TemporalScheduleListMatchingTimesCommand { + var s TemporalScheduleListMatchingTimesCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "list-matching-times [flags]" + s.Command.Short = "List matching times for a Schedule (Experimental feature)" + if hasHighlighting { + s.Command.Long = "\nNote: This is an experimental feature and may change in the future.\n\nList the times a Schedule's spec would match within a given time\nrange. The time range may be in the past or future. Use this\ncommand to preview when a Schedule will take actions without\nactually running them.\n\nFor example:\n\n\x1b[1m temporal schedule list-matching-times \\\n --schedule-id \"YourScheduleId\" \\\n --start-time \"2024-01-01T00:00:00Z\" \\\n --end-time \"2024-01-31T23:59:59Z\"\x1b[0m" + } else { + s.Command.Long = "\nNote: This is an experimental feature and may change in the future.\n\nList the times a Schedule's spec would match within a given time\nrange. The time range may be in the past or future. Use this\ncommand to preview when a Schedule will take actions without\nactually running them.\n\nFor example:\n\n```\n temporal schedule list-matching-times \\\n --schedule-id \"YourScheduleId\" \\\n --start-time \"2024-01-01T00:00:00Z\" \\\n --end-time \"2024-01-31T23:59:59Z\"\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().Var(&s.StartTime, "start-time", "Start of time range to list matching times. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "start-time") + s.Command.Flags().Var(&s.EndTime, "end-time", "End of time range to list matching times. Required.") + _ = cobra.MarkFlagRequired(s.Command.Flags(), "end-time") + s.ScheduleIdOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalScheduleToggleCommand struct { Parent *TemporalScheduleCommand Command cobra.Command diff --git a/internal/temporalcli/commands.schedule.go b/internal/temporalcli/commands.schedule.go index 5deadb8ec..3a18271bd 100644 --- a/internal/temporalcli/commands.schedule.go +++ b/internal/temporalcli/commands.schedule.go @@ -11,6 +11,7 @@ import ( "github.com/temporalio/cli/cliext" "github.com/temporalio/cli/internal/printer" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/timestamppb" commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" @@ -606,3 +607,36 @@ func formatDuration(d time.Duration) string { s = strings.TrimSpace(s) return s } + +func (c *TemporalScheduleListMatchingTimesCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + res, err := cl.WorkflowService().ListScheduleMatchingTimes(cctx, &workflowservice.ListScheduleMatchingTimesRequest{ + Namespace: c.Parent.Namespace, + ScheduleId: c.ScheduleId, + StartTime: timestamppb.New(c.StartTime.Time()), + EndTime: timestamppb.New(c.EndTime.Time()), + }) + if err != nil { + return err + } + + if cctx.JSONOutput { + return cctx.Printer.PrintStructured(res, printer.StructuredOptions{}) + } + + type matchingTime struct { + Time string `json:"time"` + } + var rows []matchingTime + for _, t := range res.StartTime { + rows = append(rows, matchingTime{Time: t.AsTime().Format(time.RFC3339)}) + } + return cctx.Printer.PrintStructured(rows, printer.StructuredOptions{ + Table: &printer.TableOptions{}, + }) +} diff --git a/internal/temporalcli/commands.schedule_test.go b/internal/temporalcli/commands.schedule_test.go index d828e55ea..d4d089bf8 100644 --- a/internal/temporalcli/commands.schedule_test.go +++ b/internal/temporalcli/commands.schedule_test.go @@ -9,6 +9,7 @@ import ( "io" "math/rand" "regexp" + "strings" "time" "github.com/stretchr/testify/assert" @@ -553,3 +554,55 @@ func (s *SharedServerSuite) TestSchedule_Memo_Update() { return j.Schedule.Action.StartWorkflow.Memo.Fields.Bar.Data == "Mg==" }, 10*time.Second, 100*time.Millisecond) } + +func (s *SharedServerSuite) TestSchedule_ListMatchingTimes() { + // use a calendar spec with known hours so results are deterministic + schedId, _, res := s.createSchedule("--calendar", `{"hour":"3,6,9"}`) + s.NoError(res.Err) + + // query a full day - should match exactly 3 times + res = s.Execute( + "schedule", "list-matching-times", + "--address", s.Address(), + "-s", schedId, + "--start-time", "2025-01-01T00:00:00Z", + "--end-time", "2025-01-01T23:59:59Z", + ) + s.NoError(res.Err) + // text output should contain parseable RFC3339 timestamps + for _, line := range strings.Split(res.Stdout.String(), "\n") { + line = strings.TrimSpace(line) + if line == "" || line == "Time" { + continue + } + _, err := time.Parse(time.RFC3339, line) + s.NoError(err, "should parse text line as time: %q", line) + } + + // json output + res = s.Execute( + "schedule", "list-matching-times", + "--address", s.Address(), + "-s", schedId, + "--start-time", "2025-01-01T00:00:00Z", + "--end-time", "2025-01-01T23:59:59Z", + "-o", "json", + ) + s.NoError(res.Err) + var resp struct { + StartTime []string `json:"startTime"` + } + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &resp)) + s.Equal(3, len(resp.StartTime)) +} + +func (s *SharedServerSuite) TestSchedule_ListMatchingTimes_NotFound() { + res := s.Execute( + "schedule", "list-matching-times", + "--address", s.Address(), + "-s", "nonexistent-schedule-id", + "--start-time", "2025-01-01T00:00:00Z", + "--end-time", "2025-01-01T23:59:59Z", + ) + s.Error(res.Err) +} diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 262cc55dd..1d383d179 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -2320,6 +2320,37 @@ commands: - overlap-policy - schedule-id + - name: temporal schedule list-matching-times + summary: List matching times for a Schedule (Experimental feature) + description: | + + Note: This is an experimental feature and may change in the future. + + List the times a Schedule's spec would match within a given time + range. The time range may be in the past or future. Use this + command to preview when a Schedule will take actions without + actually running them. + + For example: + + ``` + temporal schedule list-matching-times \ + --schedule-id "YourScheduleId" \ + --start-time "2024-01-01T00:00:00Z" \ + --end-time "2024-01-31T23:59:59Z" + ``` + options: + - name: start-time + type: timestamp + description: Start of time range to list matching times. + required: true + - name: end-time + type: timestamp + description: End of time range to list matching times. + required: true + option-sets: + - schedule-id + - name: temporal schedule create summary: Create a new Schedule description: | From 90f1364fab59b9dea05648eaaaf522cbbc067b66 Mon Sep 17 00:00:00 2001 From: hussam-salah <156124396+hussam-salah@users.noreply.github.com> Date: Sat, 30 May 2026 13:35:43 +0200 Subject: [PATCH 05/28] Remove time.Sleep() in commands.taskqueue_test.go (#1020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove time.Sleep() needed before v1.26.2 because of server cache bug - update test assertions of "2 seconds ago" to "now" since we won't wait ## What was changed test `TestTaskQueue_Describe_Simple` at `commands.taskqueue_test.go` was updated to remove unnecessary `time.Sleep(1 * time.Second) ` ## Why? The waiting was a necessary workaround of server cache bug, bug was fixed on v1.26.2 ## Checklist 1. Closes #741 3. How was this tested: Running the unit tests 4. Any docs updates needed? Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- .../temporalcli/commands.taskqueue_test.go | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/internal/temporalcli/commands.taskqueue_test.go b/internal/temporalcli/commands.taskqueue_test.go index 6c5e3ba1a..7ca102c00 100644 --- a/internal/temporalcli/commands.taskqueue_test.go +++ b/internal/temporalcli/commands.taskqueue_test.go @@ -262,10 +262,6 @@ func (s *SharedServerSuite) TestTaskQueue_Describe_Simple() { ) s.NoError(res.Err) - // TODO(antlai-temporal): Delete when a server caching bug in 1.26.2 is fixed, - // see https://github.com/temporalio/temporal/pull/6978 - time.Sleep(1 * time.Second) - // Text res = s.Execute( "task-queue", "describe", @@ -279,10 +275,6 @@ func (s *SharedServerSuite) TestTaskQueue_Describe_Simple() { // No pollers on id1 s.NotContains(res.Stdout.String(), "now") - // TODO(antlai-temporal): Delete when a server caching bug in 1.26.2 is fixed, - // see https://github.com/temporalio/temporal/pull/6978 - time.Sleep(1 * time.Second) - res = s.Execute( "task-queue", "describe", "--select-unversioned", @@ -294,12 +286,8 @@ func (s *SharedServerSuite) TestTaskQueue_Describe_Simple() { s.NoError(res.Err) s.ContainsOnSameLine(res.Stdout.String(), "UNVERSIONED", "unreachable") - s.ContainsOnSameLine(res.Stdout.String(), "UNVERSIONED", "workflow", s.DevServer.Options.ClientOptions.Identity, "2 seconds ago", "100000") - s.ContainsOnSameLine(res.Stdout.String(), "UNVERSIONED", "activity", s.DevServer.Options.ClientOptions.Identity, "2 seconds ago", "100000") - - // TODO(antlai-temporal): Delete when a server caching bug in 1.26.2 is fixed, - // see https://github.com/temporalio/temporal/pull/6978 - time.Sleep(1 * time.Second) + s.ContainsOnSameLine(res.Stdout.String(), "UNVERSIONED", "workflow", s.DevServer.Options.ClientOptions.Identity, "now", "100000") + s.ContainsOnSameLine(res.Stdout.String(), "UNVERSIONED", "activity", s.DevServer.Options.ClientOptions.Identity, "now", "100000") res = s.Execute( "task-queue", "describe", @@ -315,10 +303,6 @@ func (s *SharedServerSuite) TestTaskQueue_Describe_Simple() { // No pollers on id2 s.NotContains(res.Stdout.String(), "now") - // TODO(antlai-temporal): Delete when a server caching bug in 1.26.2 is fixed, - // see https://github.com/temporalio/temporal/pull/6978 - time.Sleep(1 * time.Second) - res = s.Execute( "task-queue", "describe", "--select-all-active", From f015ce2ac01ec26b97fa51cf5ce29ab047c03e07 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Sat, 30 May 2026 04:56:54 -0700 Subject: [PATCH 06/28] Add persistence info to start-dev banner (#1033) ## What was changed `temporal server start-dev` now prints a `Persistence:` line at the end of the startup banner. In-memory (no `--db-filename`): ``` Persistence: in-memory (Workflow Executions are lost when the server process exits) ``` File-backed: ``` Persistence: file (/path/to/dev.sqlite) ``` ## Why? Per #634, the start-dev banner did not surface what form of persistence the dev server uses, so users could not tell at a glance whether Workflow Executions would survive a restart. ## Checklist 1. Closes #634 2. How was this tested: Two new tests in `internal/temporalcli/commands.server_test.go` start the dev server, capture stdout, and assert that the banner contains `Persistence:` plus the expected backend description. Both pass locally and fail when the banner change is reverted. ``` $ go test -run TestServer_StartDev_BannerPersistence ./internal/temporalcli/ ok github.com/temporalio/cli/internal/temporalcli 10.318s ``` --------- Signed-off-by: Sai Asish Y Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.server.go | 13 ++- internal/temporalcli/commands.server_test.go | 87 ++++++++++++++++++++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/internal/temporalcli/commands.server.go b/internal/temporalcli/commands.server.go index 90c5b2fe3..bd6bed6af 100644 --- a/internal/temporalcli/commands.server.go +++ b/internal/temporalcli/commands.server.go @@ -160,15 +160,20 @@ func (t *TemporalServerStartDevCommand) run(cctx *CommandContext, args []string) defer s.Stop() cctx.Printer.Printlnf("Temporal CLI %v\n", VersionString()) - cctx.Printer.Printlnf("%-17s %v:%v", "Temporal Server:", toFriendlyIp(opts.FrontendIP), opts.FrontendPort) + cctx.Printer.Printlnf("%-21s %v:%v", "Temporal Server:", toFriendlyIp(opts.FrontendIP), opts.FrontendPort) + if t.DbFilename == "" { + cctx.Printer.Printlnf("%-21s %v", "Temporal Persistence:", "in-memory") + } else { + cctx.Printer.Printlnf("%-21s %v", "Temporal Persistence:", t.DbFilename) + } // Only print HTTP port if explicitly provided to avoid promoting the unstable HTTP API. if opts.FrontendHTTPPort > 0 { - cctx.Printer.Printlnf("%-17s %v:%v", "Temporal HTTP:", toFriendlyIp(opts.FrontendIP), opts.FrontendHTTPPort) + cctx.Printer.Printlnf("%-21s %v:%v", "Temporal HTTP:", toFriendlyIp(opts.FrontendIP), opts.FrontendHTTPPort) } if !t.Headless { - cctx.Printer.Printlnf("%-17s http://%v:%v%v", "Temporal UI:", toFriendlyIp(opts.UIIP), opts.UIPort, opts.PublicPath) + cctx.Printer.Printlnf("%-21s http://%v:%v%v", "Temporal UI:", toFriendlyIp(opts.UIIP), opts.UIPort, opts.PublicPath) } - cctx.Printer.Printlnf("%-17s http://%v:%v/metrics", "Temporal Metrics:", toFriendlyIp(opts.FrontendIP), opts.MetricsPort) + cctx.Printer.Printlnf("%-21s http://%v:%v/metrics", "Temporal Metrics:", toFriendlyIp(opts.FrontendIP), opts.MetricsPort) <-cctx.Done() if !t.Parent.Parent.LogLevel.ChangedFromDefault { // The server routinely emits various warnings on shutdown. diff --git a/internal/temporalcli/commands.server_test.go b/internal/temporalcli/commands.server_test.go index 8c25558d9..26b92cf60 100644 --- a/internal/temporalcli/commands.server_test.go +++ b/internal/temporalcli/commands.server_test.go @@ -271,6 +271,93 @@ func TestServer_StartDev_WithSearchAttributes(t *testing.T) { } } +func TestServer_StartDev_BannerPersistenceInMemory(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + + 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") + }() + + // Wait until the server is dial-able, then cancel + var cl client.Client + h.EventuallyWithT(func(t *assert.CollectT) { + select { + case res := <-resCh: + require.NoError(t, res.Err) + require.Fail(t, "got early server result") + default: + } + var err error + cl, err = client.Dial(client.Options{HostPort: "127.0.0.1:" + port}) + assert.NoError(t, err) + }, 3*time.Second, 200*time.Millisecond) + defer cl.Close() + + h.CancelContext() + var res *CommandResult + select { + case <-time.After(20 * time.Second): + h.Fail("didn't cleanup after 20 seconds") + case res = <-resCh: + h.NoError(res.Err) + } + out := res.Stdout.String() + h.Contains(out, "Temporal Persistence:") + h.Contains(out, "in-memory") +} + +func TestServer_StartDev_BannerPersistenceFile(t *testing.T) { + h := NewCommandHarness(t) + defer h.Close() + + port := strconv.Itoa(devserver.MustGetFreePort("127.0.0.1")) + httpPort := strconv.Itoa(devserver.MustGetFreePort("127.0.0.1")) + // Use os.TempDir with an explicit defer-remove so Windows file-lock cleanup + // (os.RemoveAll in t.TempDir) does not race with a still-open SQLite handle. + dbFilename := filepath.Join(os.TempDir(), "devserver-banner-"+t.Name()+".sqlite") + t.Cleanup(func() { + _ = os.Remove(dbFilename) + _ = os.Remove(dbFilename + "-shm") + _ = os.Remove(dbFilename + "-wal") + }) + resCh := make(chan *CommandResult, 1) + go func() { + resCh <- h.Execute("server", "start-dev", "-p", port, "--http-port", httpPort, + "--headless", "--db-filename", dbFilename) + }() + + var cl client.Client + // File-backed server takes longer to start due to SQLite initialization. + h.EventuallyWithT(func(t *assert.CollectT) { + select { + case res := <-resCh: + require.NoError(t, res.Err) + require.Fail(t, "got early server result") + default: + } + var err error + cl, err = client.Dial(client.Options{HostPort: "127.0.0.1:" + port}) + assert.NoError(t, err) + }, 15*time.Second, 200*time.Millisecond) + defer cl.Close() + + h.CancelContext() + var res *CommandResult + select { + case <-time.After(20 * time.Second): + h.Fail("didn't cleanup after 20 seconds") + case res = <-resCh: + h.NoError(res.Err) + } + out := res.Stdout.String() + h.Contains(out, "Temporal Persistence:") + h.Contains(out, dbFilename) +} + type testLogger struct { t *testing.T } From 7e5ba380c9a4a117333045a3dace75555b906aa1 Mon Sep 17 00:00:00 2001 From: Bitalizer <23104115+bitalizer@users.noreply.github.com> Date: Sun, 31 May 2026 03:42:01 +0300 Subject: [PATCH 07/28] fix: skip CountWorkflow in batch operations when --yes is set (#1012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #838. Implements the design @cretz proposed in the issue thread: when `--yes` bypasses the confirmation prompt, skip the `CountWorkflowExecutions` request entirely. ## What was changed - [internal/temporalcli/commands.workflow.go](internal/temporalcli/commands.workflow.go) — `SingleWorkflowOrBatchOptions.workflowExecOrBatch`: `CountWorkflow` only runs in the `!s.Yes` branch. - [internal/temporalcli/commands.workflow_reset.go](internal/temporalcli/commands.workflow_reset.go) — same pattern in `TemporalWorkflowResetCommand`'s batch path. When `--yes` skips the count, the prompt text changes from `Start batch against approximately N workflow(s)? y/N` to `Start batch against workflows matching query ""? y/N`, so the output (which `promptYes` still prints in the auto-confirm path) stays informative. The non-`--yes` interactive flow is untouched: it still counts, still prompts, still prints the same `approximately N workflow(s)` message. ## Why `workflowExecOrBatch` (terminate / signal / cancel) and the reset command both unconditionally call `CountWorkflowExecutions` before starting a batch. The result is only used to fill in the `approximately N workflow(s)` confirmation. When `--yes` is set, the prompt is skipped — but the count call still runs, and on clusters where the visibility API is timing out it fails the entire batch start. The original report is from a Postgres-backed cluster where the batch query itself works but the count times out; users can't start batch jobs they otherwise have permission to run. ## How was this tested Added `TestWorkflow_Terminate_BatchWorkflow_SkipsCountWhenYes` that: 1. Installs a unary gRPC interceptor that counts `CountWorkflowExecutionsRequest` and `StartBatchOperationRequest` calls. 2. Starts one workflow, then runs `workflow terminate --query ... --yes`. 3. Asserts: 0 `CountWorkflow` calls, 1 `StartBatchOperation` call, prompt text contains `matching query` and not `approximately`. The interceptor pattern matches the existing `testTerminateBatchWorkflow` helper. Ran the new test plus a sample of the existing batch tests locally; they pass. (`TestWorkflow_Terminate_BatchWorkflowSuccess` flakes locally on the unrelated `Completed` assertion both with and without my changes — pre-existing timing issue, not caused by this PR.) --------- Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.workflow.go | 18 ++++--- .../temporalcli/commands.workflow_reset.go | 17 +++++-- .../commands.workflow_reset_test.go | 39 +++++++++++++++ .../temporalcli/commands.workflow_test.go | 48 ++++++++++++++++++- 4 files changed, 110 insertions(+), 12 deletions(-) diff --git a/internal/temporalcli/commands.workflow.go b/internal/temporalcli/commands.workflow.go index 20ebec8d1..e03047714 100644 --- a/internal/temporalcli/commands.workflow.go +++ b/internal/temporalcli/commands.workflow.go @@ -569,13 +569,19 @@ func (s *SingleWorkflowOrBatchOptions) workflowExecOrBatch( return nil, nil, fmt.Errorf("cannot set run ID when query is set") } - // Count the workflows that will be affected - count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: s.Query}) - if err != nil { - return nil, nil, fmt.Errorf("failed counting workflows from query: %w", err) + // The count is only used in the confirmation prompt; skip the request when --yes + // bypasses it, so batch jobs can still proceed if the visibility API is timing out. + var promptMessage string + if s.Yes { + promptMessage = fmt.Sprintf("Start batch against workflows matching query %q? y/N", s.Query) + } else { + count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: s.Query}) + if err != nil { + return nil, nil, fmt.Errorf("failed counting workflows from query: %w", err) + } + promptMessage = fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count) } - yes, err := cctx.promptYes( - fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count), s.Yes) + yes, err := cctx.promptYes(promptMessage, s.Yes) if err != nil { return nil, nil, err } else if !yes { diff --git a/internal/temporalcli/commands.workflow_reset.go b/internal/temporalcli/commands.workflow_reset.go index 4c33065ce..aee402891 100644 --- a/internal/temporalcli/commands.workflow_reset.go +++ b/internal/temporalcli/commands.workflow_reset.go @@ -134,12 +134,19 @@ func (c *TemporalWorkflowResetCommand) runBatchResetWithPostOps(cctx *CommandCon PostResetOperations: postOps, }, } - count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: c.Query}) - if err != nil { - return fmt.Errorf("failed counting workflows from query: %w", err) + // The count is only used in the confirmation prompt; skip the request when --yes + // bypasses it, so batch jobs can still proceed if the visibility API is timing out. + var promptMessage string + if c.Yes { + promptMessage = fmt.Sprintf("Start batch against workflows matching query %q? y/N", c.Query) + } else { + count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: c.Query}) + if err != nil { + return fmt.Errorf("failed counting workflows from query: %w", err) + } + promptMessage = fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count) } - yes, err := cctx.promptYes( - fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count), c.Yes) + yes, err := cctx.promptYes(promptMessage, c.Yes) if err != nil { return err } diff --git a/internal/temporalcli/commands.workflow_reset_test.go b/internal/temporalcli/commands.workflow_reset_test.go index 33b3cc525..5dc87604c 100644 --- a/internal/temporalcli/commands.workflow_reset_test.go +++ b/internal/temporalcli/commands.workflow_reset_test.go @@ -927,3 +927,42 @@ func (sut *batchResetTestData) getWorkflowHistory() ([]*history.HistoryEvent, er return events, nil } + +func (s *SharedServerSuite) TestWorkflow_ResetBatch_SkipsCountWhenYes() { + s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { + ctx.Done().Receive(ctx, nil) + return nil, ctx.Err() + }) + + searchAttr := "keyword-" + uuid.NewString() + _, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{ + TaskQueue: s.Worker().Options.TaskQueue, + SearchAttributes: map[string]any{"CustomKeywordField": searchAttr}, + }, + DevWorkflow, + "ignored", + ) + s.NoError(err) + s.Eventually(func() bool { + resp, err := s.Client.ListWorkflow(s.Context, &workflowservice.ListWorkflowExecutionsRequest{ + Query: "CustomKeywordField = '" + searchAttr + "'", + }) + s.NoError(err) + return len(resp.Executions) == 1 + }, 3*time.Second, 100*time.Millisecond) + + res := s.Execute( + "workflow", "reset", + "--address", s.Address(), + "--query", "CustomKeywordField = '"+searchAttr+"'", + "--type", "FirstWorkflowTask", + "--reason", "skip-count-test", + "--yes", + ) + s.NoError(res.Err) + + s.NotContains(res.Stdout.String(), "approximately") + s.Contains(res.Stdout.String(), "matching query") +} diff --git a/internal/temporalcli/commands.workflow_test.go b/internal/temporalcli/commands.workflow_test.go index 20d6d256b..aedf331f7 100644 --- a/internal/temporalcli/commands.workflow_test.go +++ b/internal/temporalcli/commands.workflow_test.go @@ -7,6 +7,7 @@ import ( "math/rand" "regexp" "strconv" + "strings" "sync" "time" @@ -250,7 +251,7 @@ func (s *SharedServerSuite) TestWorkflow_Delete_BatchWorkflowSuccess() { "-y", ) s.NoError(res.Err) - s.Contains(res.Stdout.String(), "Start batch against approximately 2 workflow(s)") + s.Contains(res.Stdout.String(), "Start batch against workflows matching query") s.NotContains(res.Stdout.String(), "Deleting Workflow Executions in a global Namespace") s.NotContains(res.Stderr.String(), "Deleting Workflow Executions in a global Namespace") @@ -387,6 +388,51 @@ func (s *SharedServerSuite) TestWorkflow_Terminate_BatchWorkflowSuccess_JSON() { s.NotEmpty(jsonRes["batchJobId"]) } +func (s *SharedServerSuite) TestWorkflow_Terminate_BatchWorkflow_SkipsCountWhenYes() { + s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { + ctx.Done().Receive(ctx, nil) + return nil, ctx.Err() + }) + + searchAttr := "keyword-" + uuid.NewString() + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{ + TaskQueue: s.Worker().Options.TaskQueue, + SearchAttributes: map[string]any{"CustomKeywordField": searchAttr}, + }, + DevWorkflow, + "ignored", + ) + s.NoError(err) + s.Eventually(func() bool { + resp, err := s.Client.ListWorkflow(s.Context, &workflowservice.ListWorkflowExecutionsRequest{ + Query: "CustomKeywordField = '" + searchAttr + "'", + }) + s.NoError(err) + return len(resp.Executions) == 1 + }, 3*time.Second, 100*time.Millisecond) + + res := s.Execute( + "workflow", "terminate", + "--address", s.Address(), + "--query", "CustomKeywordField = '"+searchAttr+"'", + "--reason", "skip-count-test", + "--yes", + ) + s.NoError(res.Err) + + // Prompt text should show the query, not the count + s.NotContains(res.Stdout.String(), "approximately") + s.Contains(res.Stdout.String(), "matching query") + + // Confirm workflow was terminated + s.Eventually(func() bool { + err := run.Get(s.Context, nil) + return err != nil && strings.Contains(err.Error(), "terminated") + }, 5*time.Second, 100*time.Millisecond) +} + func (s *SharedServerSuite) testTerminateBatchWorkflow( total int, rps float32, From a10f9ae8221a0ec5e9d750ccbdafbd54a5d7827e Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Tue, 2 Jun 2026 12:26:29 -0600 Subject: [PATCH 08/28] Unwrap System Nexus Operations in event history (#1017) ## What was changed - `workflow show` (table and detailed modes, not `-o json`) now displays the actual operation name instead of the generic `NexusOperationScheduled`/`NexusOperationCompleted` event type when the Nexus endpoint is `__temporal_system`. - `workflow describe` no longer includes pending Nexus operations on the `__temporal_system` endpoint in the "Pending Nexus Operations" list or count. ## Why? System Nexus operations (endpoint `__temporal_system`) are implementation details of high-level SDK operations like `SignalWithStartWorkflowExecution`. Surfacing them as `NexusOperationScheduled`/`NexusOperationCompleted` events is confusing. By unwrapping the operation name, the history and describe output reflect what the workflow actually did at a semantic level. Similarly, pending system operations in describe are noise the user has no actionable interest in. ## Checklist 1. Closes NA 2. How was this tested: - Unit tests added in `commands.workflow_show_test.go` covering all six NexusOperation event types, the non-system-endpoint no-op path, and the missing-scheduled-event (reverse traversal / orphaned completion) path. - Functional tests added to `commands.workflow_view_test.go` to show system operations are unwrapped but user defined are not - Manual validation: ran `temporal workflow show -w ` against a live workflow that executes a `SignalWithStartWorkflowExecution` system nexus operation and confirmed events 5 and 6 now show as `SignalWithStartWorkflowExecutionScheduled` and `SignalWithStartWorkflowExecutionCompleted`. 4. Any docs updates needed? No --- cliext/client.go | 28 +- go.mod | 195 ++++---- go.sum | 461 +++++++++--------- internal/temporalcli/client.go | 17 +- .../temporalcli/commands.schedule_test.go | 2 +- internal/temporalcli/commands.system_nexus.go | 54 ++ .../temporalcli/commands.system_nexus_test.go | 332 +++++++++++++ .../temporalcli/commands.workflow_exec.go | 188 ++++++- .../commands.workflow_show_test.go | 173 +++++++ .../temporalcli/commands.workflow_view.go | 17 +- .../commands.workflow_view_test.go | 264 ++++++++++ internal/temporalcli/commands_test.go | 8 +- 12 files changed, 1398 insertions(+), 341 deletions(-) create mode 100644 internal/temporalcli/commands.system_nexus.go create mode 100644 internal/temporalcli/commands.system_nexus_test.go create mode 100644 internal/temporalcli/commands.workflow_show_test.go diff --git a/cliext/client.go b/cliext/client.go index db6506078..8512f34ba 100644 --- a/cliext/client.go +++ b/cliext/client.go @@ -29,6 +29,11 @@ type ClientOptionsBuilder struct { // Logger is the slog logger to use for the client. If set, it will be // wrapped with the SDK's structured logger adapter. Logger *slog.Logger + + // PayloadCodec is populated by Build when a remote payload codec is + // configured. Callers can use it to decode payloads outside the gRPC + // interceptor chain (e.g. payloads nested inside opaque proto bytes). + PayloadCodec converter.PayloadCodec } type oauthCredentials struct { @@ -248,13 +253,18 @@ func (b *ClientOptionsBuilder) Build(ctx context.Context) (client.Options, error if err != nil { return client.Options{}, fmt.Errorf("invalid codec headers: %w", err) } - interceptor, err := newPayloadCodecInterceptor( + payloadCodec := newRemotePayloadCodec( profile.Namespace, profile.Codec.Endpoint, profile.Codec.Auth, codecHeaders) + interceptor, err := converter.NewPayloadCodecGRPCClientInterceptor( + converter.PayloadCodecGRPCClientInterceptorOptions{ + Codecs: []converter.PayloadCodec{payloadCodec}, + }) if err != nil { return client.Options{}, fmt.Errorf("failed creating payload codec interceptor: %w", err) } clientOpts.ConnectionOptions.DialOptions = append( clientOpts.ConnectionOptions.DialOptions, grpc.WithChainUnaryInterceptor(interceptor)) + b.PayloadCodec = payloadCodec } // Set connect timeout for GetSystemInfo if provided. @@ -278,16 +288,17 @@ func parseKeyValuePairs(pairs []string) (map[string]string, error) { return result, nil } -// newPayloadCodecInterceptor creates a gRPC interceptor for remote payload codec. -func newPayloadCodecInterceptor( +// newRemotePayloadCodec constructs a remote payload codec from the configured endpoint, +// auth, and headers. The returned codec can be used both inside a gRPC interceptor and +// to decode payloads nested inside opaque proto bytes (e.g. system Nexus operation inputs). +func newRemotePayloadCodec( namespace string, codecEndpoint string, codecAuth string, codecHeaders map[string]string, -) (grpc.UnaryClientInterceptor, error) { +) converter.PayloadCodec { codecEndpoint = strings.ReplaceAll(codecEndpoint, "{namespace}", namespace) - - payloadCodec := converter.NewRemotePayloadCodec( + return converter.NewRemotePayloadCodec( converter.RemotePayloadCodecOptions{ Endpoint: codecEndpoint, ModifyRequest: func(req *http.Request) error { @@ -302,11 +313,6 @@ func newPayloadCodecInterceptor( }, }, ) - return converter.NewPayloadCodecGRPCClientInterceptor( - converter.PayloadCodecGRPCClientInterceptorOptions{ - Codecs: []converter.PayloadCodec{payloadCodec}, - }, - ) } func (c *oauthCredentials) getToken(ctx context.Context) (string, error) { diff --git a/go.mod b/go.mod index d99d153ec..847f9817a 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/temporalio/cli -go 1.26.2 +go 1.26.3 require ( github.com/BurntSushi/toml v1.4.0 github.com/alitto/pond v1.9.2 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/dustin/go-humanize v1.0.1 - github.com/fatih/color v1.18.0 + github.com/fatih/color v1.19.0 github.com/google/uuid v1.6.0 - github.com/mattn/go-isatty v0.0.20 + github.com/mattn/go-isatty v0.0.21 github.com/nexus-rpc/sdk-go v0.6.0 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.10.2 @@ -17,101 +17,112 @@ require ( github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0 github.com/temporalio/ui-server/v2 v2.49.1 - go.temporal.io/api v1.62.8 + go.temporal.io/api v1.62.13 go.temporal.io/sdk v1.41.1 go.temporal.io/sdk/contrib/envconfig v1.0.0 - go.temporal.io/server v1.31.0 - golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 - golang.org/x/mod v0.36.0 + go.temporal.io/server v1.32.0-157.0 + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f + golang.org/x/mod v0.35.0 golang.org/x/term v0.42.0 golang.org/x/tools v0.44.0 - google.golang.org/grpc v1.79.3 - google.golang.org/protobuf v1.36.10 + google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.44.3 + modernc.org/sqlite v1.51.0 ) replace github.com/temporalio/cli/cliext => ./cliext require ( cel.dev/expr v0.25.1 // indirect - cloud.google.com/go v0.121.6 // indirect - cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/longrunning v0.7.0 // indirect - cloud.google.com/go/monitoring v1.24.2 // indirect - cloud.google.com/go/run v1.15.0 // indirect - 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/exporter/metric v0.53.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + cloud.google.com/go/iam v1.9.0 // indirect + cloud.google.com/go/longrunning v0.11.0 // indirect + cloud.google.com/go/monitoring v1.27.0 // indirect + cloud.google.com/go/run v1.19.0 // indirect + cloud.google.com/go/storage v1.62.1 // indirect + dario.cat/mergo v1.0.2 // indirect + filippo.io/edwards25519 v1.2.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.56.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/apache/thrift v0.21.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.13 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.13 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect - github.com/aws/aws-sdk-go-v2/service/ecs v1.72.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect - github.com/aws/aws-sdk-go-v2/service/lambda v1.88.0 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.98.0 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.14 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect - github.com/aws/smithy-go v1.24.2 // indirect + github.com/apache/thrift v0.23.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.6 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.16 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.15 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/service/ecs v1.78.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22 // indirect + github.com/aws/aws-sdk-go-v2/service/lambda v1.89.1 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.99.1 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect + github.com/aws/smithy-go v1.25.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c // indirect github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // 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/emicklei/go-restful/v3 v3.13.0 // 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 + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-sql-driver/mysql v1.9.1 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.5 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/gocql/gocql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect - github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect + github.com/googleapis/gax-go/v2 v2.22.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 @@ -123,13 +134,13 @@ require ( github.com/jmoiron/sqlx v1.4.0 // indirect 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/klauspost/compress v1.18.5 // indirect github.com/labstack/echo/v4 v4.13.4 // indirect github.com/labstack/gommon v0.4.2 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/lib/pq v1.12.3 // indirect + github.com/mailru/easyjson v0.9.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -137,6 +148,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/nexus-rpc/nexus-proto-annotations v0.1.0 // indirect github.com/olivere/elastic/v7 v7.0.32 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -145,21 +157,20 @@ require ( 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 - github.com/prometheus/procfs v0.16.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/sony/gobreaker v1.0.0 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cast v1.10.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 + github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61 // indirect github.com/tidwall/btree v1.8.1 // indirect github.com/twmb/murmur3 v1.1.8 // indirect github.com/uber-common/bark v1.3.0 // indirect @@ -168,19 +179,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/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/contrib/detectors/gcp v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.43.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/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.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/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/proto/otlp v1.10.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 @@ -188,33 +199,33 @@ require ( go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/net v0.53.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.14.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 + golang.org/x/time v0.15.0 // indirect + google.golang.org/api v0.276.0 // indirect + google.golang.org/genproto v0.0.0-20260420184626-e10c466a9529 // 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 - k8s.io/api v0.35.1 // indirect - k8s.io/apimachinery v0.35.1 // indirect - k8s.io/client-go v0.35.1 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect - modernc.org/libc v1.67.6 // indirect + k8s.io/api v0.35.4 // indirect + k8s.io/apimachinery v0.35.4 // indirect + k8s.io/client-go v0.35.4 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f // indirect + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect + modernc.org/libc v1.72.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 1af7e88ea..5d98f630c 100644 --- a/go.sum +++ b/go.sum @@ -1,45 +1,45 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= -cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= -cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= -cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= -cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= -cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= -cloud.google.com/go/run v1.15.0 h1:4cwyNv9SUQEsQOf5/DfPKyMWYSA52p38/o119BgMhO4= -cloud.google.com/go/run v1.15.0/go.mod h1:rgFHMdAopLl++57vzeqA+a1o2x0/ILZnEacRD6nC0EA= -cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= -cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= -cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= -cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +cloud.google.com/go/iam v1.9.0 h1:89wyjxT6DL4b5rk/Nk8eBC9DHqf+JiMstrn5IEYxFw4= +cloud.google.com/go/iam v1.9.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= +cloud.google.com/go/logging v1.16.0 h1:MMNgYRvZ/pEwiNSkcoJTKWfAbAJDqCqAMJiarZx+/CI= +cloud.google.com/go/logging v1.16.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI= +cloud.google.com/go/longrunning v0.11.0 h1:fE4XVLJQj+gRnw1HrbDyQXXgC0aiqY3wxP7DDU4cWk0= +cloud.google.com/go/longrunning v0.11.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= +cloud.google.com/go/monitoring v1.27.0 h1:BhYwMqao+e5Nn7JtWMM9m6zRtKtVUK6kJWMizXChkLU= +cloud.google.com/go/monitoring v1.27.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM= +cloud.google.com/go/run v1.19.0 h1:kjXZKDwrUOeUYDd7/0TZ/iKsG3rJ3Lq3cyksTspcNSU= +cloud.google.com/go/run v1.19.0/go.mod h1:Z5wHbyFirI8XU48EPs5XJf/qmVm1SXZEhuS8EvZOuQU= +cloud.google.com/go/storage v1.62.1 h1:Os0G3XbUbjZumkpDUf2Y0rLoXJTCF1kU2kWUujKYXD8= +cloud.google.com/go/storage v1.62.1/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA= +cloud.google.com/go/trace v1.14.0 h1:jUtnmOrNcu5XJNk4Gz0fv+v5sM0weaOa3z5MPQUjRXs= +cloud.google.com/go/trace v1.14.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= -filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/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= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +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.56.0 h1:O2sXMyJh8b7devAGdE+163xtRurt0RVpB6DIzX5vGfg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.56.0/go.mod h1:hEpiGU18xf70qb3jbTcIggWAiEfX/cOIVc2OTe4OegA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.56.0 h1:ZIT85vKP7LBS84XJ0WdJ3dPOX3iz4j3c0+lpajGQMyo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.56.0/go.mod h1:rqP9UEhOXv9WhQ7Gjz+G5y/pf8+BJZW5/Ts0AhE0PwE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0 h1:0YP0+/ixwu+Uqeu/FGiBZNQ19huiUxxiPXIc9WsLKuQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0/go.mod h1:6ZZMQhZKDvUvkJw2rc+oDP90tMMzuU/J+5HG1ZmPOmE= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -51,50 +51,48 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3 github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= -github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= -github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= -github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= -github.com/aws/aws-sdk-go-v2/config v1.32.13 h1:5KgbxMaS2coSWRrx9TX/QtWbqzgQkOdEa3sZPhBhCSg= -github.com/aws/aws-sdk-go-v2/config v1.32.13/go.mod h1:8zz7wedqtCbw5e9Mi2doEwDyEgHcEE9YOJp6a8jdSMY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.13 h1:mA59E3fokBvyEGHKFdnpNNrvaR351cqiHgRg+JzOSRI= -github.com/aws/aws-sdk-go-v2/credentials v1.19.13/go.mod h1:yoTXOQKea18nrM69wGF9jBdG4WocSZA1h38A+t/MAsk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= -github.com/aws/aws-sdk-go-v2/service/ecs v1.72.1 h1:Pciw9l/TbLpOjvTT9vm4IzHAyl2xQMBGlS44d0TvXXE= -github.com/aws/aws-sdk-go-v2/service/ecs v1.72.1/go.mod h1:DdtkqcURi9GM8f9HVLzJLTvS0h0k1qYg39vKQFmeR/k= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= -github.com/aws/aws-sdk-go-v2/service/lambda v1.88.0 h1:u66DMbJWDFXs9458RAHNtq2d0gyqcZFV4mzRwfjM358= -github.com/aws/aws-sdk-go-v2/service/lambda v1.88.0/go.mod h1:ogjbkxFgFOjG3dYFQ8irC92gQfpfMDcy1RDKNSZWXNU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.98.0 h1:foqo/ocQ7WqKwy3FojGtZQJo0FR4vto9qnz9VaumbCo= -github.com/aws/aws-sdk-go-v2/service/s3 v1.98.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.14 h1:GcLE9ba5ehAQma6wlopUesYg/hbcOhFNWTjELkiWkh4= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.14/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18 h1:mP49nTpfKtpXLt5SLn8Uv8z6W+03jYVoOSAl/c02nog= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/apache/thrift v0.23.0 h1:wKR6YnefQSEnxpEfmgTPuJibNG4bF0p2TK34tHLWi3s= +github.com/apache/thrift v0.23.0/go.mod h1:zPt6WxgvTOM6hF92y8C+MkEM5LMxZuk4JcQOiU4Esvs= +github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= +github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 h1:adBsCIIpLbLmYnkQU+nAChU5yhVTvu5PerROm+/Kq2A= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9/go.mod h1:uOYhgfgThm/ZyAuJGNQ5YgNyOlYfqnGpTHXvk3cpykg= +github.com/aws/aws-sdk-go-v2/config v1.32.16 h1:Q0iQ7quUgJP0F/SCRTieScnaMdXr9h/2+wze1u3cNeM= +github.com/aws/aws-sdk-go-v2/config v1.32.16/go.mod h1:duCCnJEFqpt2RC6no1iK6q+8HpwOAkiUua0pY507dQc= +github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.15/go.mod h1:gJiYyMOjNg8OEdRWOf3CrFQxM2a98qmrtjx1zuiQfB8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 h1:IOGsJ1xVWhsi+ZO7/NW8OuZZBtMJLZbk4P5HDjJO0jQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22/go.mod h1:b+hYdbU+jGKfXE8kKM6g1+h+L/Go3vMvzlxBsiuGsxg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 h1:FPXsW9+gMuIeKmz7j6ENWcWtBGTe1kH8r9thNt5Uxx4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23/go.mod h1:7J8iGMdRKk6lw2C+cMIphgAnT8uTwBwNOsGkyOCm80U= +github.com/aws/aws-sdk-go-v2/service/ecs v1.78.1 h1:9zSVr4X6X8JNTxSMip2RORaBB+Mu0/IfzNu3iRWZE9c= +github.com/aws/aws-sdk-go-v2/service/ecs v1.78.1/go.mod h1:1DlTqkp+8uc5At3UXyJAvJXFaWoMmxSHcp2Zdor0qGw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 h1:HtOTYcbVcGABLOVuPYaIihj6IlkqubBwFj10K5fxRek= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8/go.mod h1:VsK9abqQeGlzPgUr+isNWzPlK2vKe9INMLWnY65f5Xs= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14 h1:xnvDEnw+pnj5mctWiYuFbigrEzSm35x7k4KS/ZkCANg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14/go.mod h1:yS5rNogD8e0Wu9+l3MUwr6eENBzEeGejvINpN5PAYfY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 h1:PUmZeJU6Y1Lbvt9WFuJ0ugUK2xn6hIWUBBbKuOWF30s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22/go.mod h1:nO6egFBoAaoXze24a2C0NjQCvdpk8OueRoYimvEB9jo= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22 h1:SE+aQ4DEqG53RRCAIHlCf//B2ycxGH7jFkpnAh/kKPM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22/go.mod h1:ES3ynECd7fYeJIL6+oax+uIEljmfps0S70BaQzbMd/o= +github.com/aws/aws-sdk-go-v2/service/lambda v1.89.1 h1:JxHLwNK5mIKsh2Q0APTSijdzkk5ccI4gyvYdar1JU/0= +github.com/aws/aws-sdk-go-v2/service/lambda v1.89.1/go.mod h1:7qoh/MlWG5QCnZwq9bvdXomEAkmumayXcjEjIemIV7U= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.1 h1:kU/eBN5+MWNo/LcbNa4hWDdN76hdcd7hocU5kvu7IsU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.1/go.mod h1:Fw9aqhJicIVee1VytBBjH+l+5ov6/PhbtIK/u3rt/ls= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.10/go.mod h1:p6+MXNxW7IA6dMgHfTAzljuwSKD0NCm/4lbS4t6+7vI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 h1:x6bKbmDhsgSZwv6q19wY/u3rLk/3FGjJWyqKcIRufpE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.16/go.mod h1:CudnEVKRtLn0+3uMV0yEXZ+YZOKnAtUJ5DmDhilVnIw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 h1:oK/njaL8GtyEihkWMD4k3VgHCT64RQKkZwh0DG5j8ak= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20/go.mod h1:JHs8/y1f3zY7U5WcuzoJ/yAYGYtNIVPKLIbp61euvmg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcuxk8l+PG4+A0exds= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo= +github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U= +github.com/aws/smithy-go v1.25.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/benbjohnson/clock v0.0.0-20160125162948-a620c1cc9866/go.mod h1:UMqtWQTnOe4byzwe7Zhwh8f8s+36uszN51sJrSIZlTE= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -114,12 +112,14 @@ github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go. github.com/cactus/go-statsd-client/v4 v4.0.0/go.mod h1:m73kwJp6TN0Ja9P6ycdZhWM1MlfxY/95WZ//IptPQ+Y= github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4= github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +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= @@ -134,22 +134,22 @@ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa5 github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -157,10 +157,10 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs= -github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-faker/faker/v4 v4.7.0 h1:VboC02cXHl/NuQh5lM2W8b87yp4iFXIu59x4w0RZi4E= +github.com/go-faker/faker/v4 v4.7.0/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= @@ -169,19 +169,43 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= -github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus= github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -199,8 +223,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -216,20 +240,22 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= +github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= 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/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= @@ -257,12 +283,11 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -275,18 +300,18 @@ github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcX 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/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= -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/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= +github.com/mailru/easyjson v0.9.2/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.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= 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= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -305,6 +330,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0 h1:2fELd+9sqUtNu6Fg//pw8YFsxOvp8vZ8hfP0nHhNI80= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -312,10 +339,6 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= -github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= -github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -335,16 +358,13 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM= -github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rcrowley/go-metrics v0.0.0-20141108142129-dee209f2455f/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -358,12 +378,12 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.0.2-0.20170726183946-abee6f9b0679/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -373,18 +393,13 @@ github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMps github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7 h1:lEebX/hZss+TSH3EBwhztnBavJVj7pWGJOH8UgKHS0w= @@ -392,8 +407,8 @@ github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7/go.mod h1:RE github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb h1:YzHH/U/dN7vMP+glybzcXRTczTrgfdRisNTzAj7La04= github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb/go.mod h1:143qKdh3G45IgV9p+gbAwp3ikRDI8mxsijFiXDfuxsw= github.com/temporalio/tchannel-go v1.22.1-0.20220818200552-1be8d8cffa5b/go.mod h1:c+V9Z/ZgkzAdyGvHrvC5AsXgN+M9Qwey04cBdKYzV7U= -github.com/temporalio/tchannel-go v1.22.1-0.20240528171429-1db37fdea938 h1:sEJGhmDo+0FaPWM6f0v8Tjia0H5pR6/Baj6+kS78B+M= -github.com/temporalio/tchannel-go v1.22.1-0.20240528171429-1db37fdea938/go.mod h1:ezRQRwu9KQXy8Wuuv1aaFFxoCNz5CeNbVOOkh3xctbY= +github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61 h1:v9EBEMJggmXGbVcIAjGQpKgEB+a9E/Q0brJ6fGWJvhQ= +github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61/go.mod h1:ezRQRwu9KQXy8Wuuv1aaFFxoCNz5CeNbVOOkh3xctbY= github.com/temporalio/ui-server/v2 v2.49.1 h1:OFbEpfCdSEEFYe7YdVZPTk3l4idT8ReStB6F788iiP0= github.com/temporalio/ui-server/v2 v2.49.1/go.mod h1:BgMLfNqd11tohA1gjgEeZLlVvRSnV/PAKiMQYTij6IE= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= @@ -422,26 +437,28 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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/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/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/collector/featuregate v1.56.0 h1:NjcbOZkdCSXddAJmFLdO+pv1gmAgrU6sC5PBga2KlKI= +go.opentelemetry.io/collector/featuregate v1.56.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/pdata v1.56.0 h1:W+QAfN2Iz8SNss1T5JNzRWFnw+7oP1vXBQH9ZuOJkXY= +go.opentelemetry.io/collector/pdata v1.56.0/go.mod h1:usR9utboXufbD1rp1oJy+3smQXXpZ+CsI3WN7QsiOs0= +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.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= 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/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= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= 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/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= 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/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= @@ -450,18 +467,18 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC 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/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.temporal.io/api v1.62.13 h1:xMa8Nt5oAMX+LvlCJA44wjTCc1H09i2rG9poB1/xvH4= +go.temporal.io/api v1.62.13/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= 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= 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= -go.temporal.io/server v1.31.0 h1:FKLodreaMXUxYc3zr6xxwxtpGz1WH/t7O0IWxV1d1x0= -go.temporal.io/server v1.31.0/go.mod h1:MTQAw8uMU3ooSHyg/62JsNu/j8lK34SfKMTXkexYcw8= +go.temporal.io/server v1.32.0-157.0 h1:nzFqNwx+5lXsT0/DSiFyR5vHMnDcT3PVAvmRDqCUn38= +go.temporal.io/server v1.32.0-157.0/go.mod h1:a76wf30/s28JXh+3nDQtQi8KzOfRQEddpebvmr/oQL4= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -482,8 +499,8 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -499,8 +516,8 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= -golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -513,8 +530,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.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +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/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= @@ -528,8 +545,8 @@ 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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -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/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= @@ -550,10 +567,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= @@ -574,8 +589,8 @@ 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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -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/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= @@ -598,22 +613,22 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T 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/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY= +google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= +google.golang.org/genproto v0.0.0-20260420184626-e10c466a9529 h1:QoMBg0moLIlB/eucPzc+ID5SgPZWuirtjAn3l8nW2Dg= +google.golang.org/genproto v0.0.0-20260420184626-e10c466a9529/go.mod h1:EjLmDZ8liSLBrCTK5vP+bGIxRQHE3ovGvOI0CzGk1PI= +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.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +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= @@ -633,42 +648,42 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= -k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= -k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= -k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= -k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= -modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= -modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= +k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= +k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= +k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= +k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= +k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f h1:4Qiq0YAoQATdgmHALJWz9rJ4fj20pB3xebpB4CFNhYM= +k8s.io/kube-openapi v0.0.0-20260414162039-ec9c827d403f/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= +modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= +modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= -modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= -modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= +modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY= -modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/sqlite v1.51.0 h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U= +modernc.org/sqlite v1.51.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= @@ -678,7 +693,7 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/temporalcli/client.go b/internal/temporalcli/client.go index 751c61c06..230ff5a35 100644 --- a/internal/temporalcli/client.go +++ b/internal/temporalcli/client.go @@ -21,8 +21,17 @@ import ( // so often used by callers after this call to know the currently configured // namespace. func dialClient(cctx *CommandContext, c *cliext.ClientOptions) (client.Client, error) { + cl, _, err := dialClientWithCodec(cctx, c) + return cl, err +} + +// dialClientWithCodec is like [dialClient] but also returns the configured remote +// payload codec, or nil if no codec is configured. The codec is the same instance +// used by the gRPC interceptor; callers can use it to decode payloads nested inside +// opaque proto bytes (e.g. the request/response of a system Nexus operation). +func dialClientWithCodec(cctx *CommandContext, c *cliext.ClientOptions) (client.Client, converter.PayloadCodec, error) { if cctx.RootCommand == nil { - return nil, fmt.Errorf("root command unexpectedly missing when dialing client") + return nil, nil, fmt.Errorf("root command unexpectedly missing when dialing client") } // Set default identity if not provided @@ -47,7 +56,7 @@ func dialClient(cctx *CommandContext, c *cliext.ClientOptions) (client.Client, e } clientOpts, err := builder.Build(cctx) if err != nil { - return nil, err + return nil, nil, err } // We do not put codec on data converter here, it is applied via @@ -78,14 +87,14 @@ func dialClient(cctx *CommandContext, c *cliext.ClientOptions) (client.Client, e cl, err := client.DialContext(dialCtx, clientOpts) if err != nil { - return nil, err + return nil, nil, err } // Since this namespace value is used by many commands after this call, // we are mutating it to be the derived one c.Namespace = clientOpts.Namespace - return cl, nil + return cl, builder.PayloadCodec, nil } func fixedHeaderOverrideInterceptor( diff --git a/internal/temporalcli/commands.schedule_test.go b/internal/temporalcli/commands.schedule_test.go index d4d089bf8..d3bf978d9 100644 --- a/internal/temporalcli/commands.schedule_test.go +++ b/internal/temporalcli/commands.schedule_test.go @@ -236,7 +236,7 @@ func (s *SharedServerSuite) TestSchedule_List() { out = res.Stdout.String() assert.Contains(t, out, schedId) }, 10*time.Second, time.Second) - s.ContainsOnSameLine(out, schedId, "DevWorkflow", "0s" /*jitter*/, "false", "nil" /*memo*/) + s.ContainsOnSameLine(out, schedId, "DevWorkflow", "0s" /*jitter*/, "false", "{}" /*memo*/) s.ContainsOnSameLine(out, "TestSchedule_List") // table diff --git a/internal/temporalcli/commands.system_nexus.go b/internal/temporalcli/commands.system_nexus.go new file mode 100644 index 000000000..da352b3c0 --- /dev/null +++ b/internal/temporalcli/commands.system_nexus.go @@ -0,0 +1,54 @@ +package temporalcli + +import ( + "context" + + commonpb "go.temporal.io/api/common/v1" + "go.temporal.io/api/proxy" + "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/api/workflowservice/v1/workflowservicenexus" + "go.temporal.io/sdk/converter" + "google.golang.org/protobuf/proto" +) + +// systemNexusOpKey identifies a system Nexus operation by its (endpoint, operation) pair. +type systemNexusOpKey struct { + Endpoint string + Operation string +} + +// systemNexusOpTypes maps a system Nexus operation to the proto request and response types +// whose bytes are serialized in NexusOperationScheduled.Input and NexusOperationCompleted.Result. +type systemNexusOpTypes struct { + // NewRequest returns a fresh, zero-valued instance of the request proto. + NewRequest func() proto.Message + // NewResponse returns a fresh, zero-valued instance of the response proto. + NewResponse func() proto.Message +} + +// systemNexusOps is the global registry of known system Nexus operations on the +// __temporal_system endpoint. Add new entries here as the server adds support for more +// system operations. The keys' Operation values must match what the server records in +// NexusOperationScheduledEventAttributes.Operation. +// NOTE seankane: Part 2 of the System Operations work is to code generate this map from the +// go.temporal.io/api/workflowservice/v1/workflowservicenexus package. +var systemNexusOps = map[systemNexusOpKey]systemNexusOpTypes{ + { + Endpoint: temporalSystemNexusEndpoint, + Operation: workflowservicenexus.TemporalAPIWorkflowserviceV1WorkflowService.SignalWithStartWorkflowExecution.Name(), + }: { + NewRequest: func() proto.Message { return &workflowservice.SignalWithStartWorkflowExecutionRequest{} }, + NewResponse: func() proto.Message { return &workflowservice.SignalWithStartWorkflowExecutionResponse{} }, + }, +} + +// decodePayloadsInProto walks a proto message and applies codec.Decode to every Payload +// found inside it (including nested messages). The message is mutated in place. +func decodePayloadsInProto(ctx context.Context, msg proto.Message, codec converter.PayloadCodec) error { + return proxy.VisitPayloads(ctx, msg, proxy.VisitPayloadsOptions{ + SkipSearchAttributes: true, + Visitor: func(_ *proxy.VisitPayloadsContext, payloads []*commonpb.Payload) ([]*commonpb.Payload, error) { + return codec.Decode(payloads) + }, + }) +} diff --git a/internal/temporalcli/commands.system_nexus_test.go b/internal/temporalcli/commands.system_nexus_test.go new file mode 100644 index 000000000..ad160df2e --- /dev/null +++ b/internal/temporalcli/commands.system_nexus_test.go @@ -0,0 +1,332 @@ +package temporalcli + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + commonpb "go.temporal.io/api/common/v1" + enumspb "go.temporal.io/api/enums/v1" + historypb "go.temporal.io/api/history/v1" + "go.temporal.io/api/temporalproto" + "go.temporal.io/api/workflowservice/v1" + "google.golang.org/protobuf/proto" +) + +// markingCodec is a test [converter.PayloadCodec] that prefixes every payload's +// data with "decoded:" on Decode and tracks how many payloads it saw. It is used +// to verify that the codec is actually invoked on payloads nested inside opaque +// system Nexus operation bytes. +type markingCodec struct { + decodeCalls int +} + +func (c *markingCodec) Encode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error) { + return payloads, nil +} + +func (c *markingCodec) Decode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error) { + out := make([]*commonpb.Payload, len(payloads)) + for i, p := range payloads { + c.decodeCalls++ + out[i] = &commonpb.Payload{ + Metadata: p.Metadata, + Data: append([]byte("decoded:"), p.Data...), + } + } + return out, nil +} + +// failingCodec always returns an error from Decode; used to verify error propagation. +type failingCodec struct{} + +func (failingCodec) Encode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error) { + return payloads, nil +} + +func (failingCodec) Decode(_ []*commonpb.Payload) ([]*commonpb.Payload, error) { + return nil, fmt.Errorf("codec decode failure for testing") +} + +func signalWithStartRequestPayload(t *testing.T, req *workflowservice.SignalWithStartWorkflowExecutionRequest) *commonpb.Payload { + t.Helper() + data, err := proto.Marshal(req) + require.NoError(t, err) + return &commonpb.Payload{ + Metadata: map[string][]byte{"encoding": []byte("binary/protobuf")}, + Data: data, + } +} + +func signalWithStartResponsePayload(t *testing.T, resp *workflowservice.SignalWithStartWorkflowExecutionResponse) *commonpb.Payload { + t.Helper() + data, err := proto.Marshal(resp) + require.NoError(t, err) + return &commonpb.Payload{ + Metadata: map[string][]byte{"encoding": []byte("binary/protobuf")}, + Data: data, + } +} + +func TestUnwrapAndInjectRequest_NilPayloadIsNoOp(t *testing.T) { + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.unwrapAndInjectRequest( + temporalSystemNexusEndpoint, "SignalWithStartWorkflowExecution", + nil, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Empty(t, fields, "nil payload should not inject anything") +} + +func TestUnwrapAndInjectRequest_UnknownOperationIsNoOp(t *testing.T) { + p := &commonpb.Payload{Data: []byte("ignored")} + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.unwrapAndInjectRequest( + temporalSystemNexusEndpoint, "NotARealOperation", + p, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Empty(t, fields, "unknown operation should be a no-op") +} + +func TestUnwrapAndInjectRequest_UnknownEndpointIsNoOp(t *testing.T) { + p := &commonpb.Payload{Data: []byte("ignored")} + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.unwrapAndInjectRequest( + "some-user-endpoint", "SignalWithStartWorkflowExecution", + p, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Empty(t, fields, "non-system endpoint should be a no-op even if operation name matches") +} + +func TestUnwrapAndInjectRequest_BadProtoBytesReturnsError(t *testing.T) { + p := &commonpb.Payload{Data: []byte{0xff, 0xff, 0xff}} + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + err := iter.unwrapAndInjectRequest( + temporalSystemNexusEndpoint, "SignalWithStartWorkflowExecution", + p, fields, temporalproto.CustomJSONMarshalOptions{}) + require.Error(t, err) + require.ErrorContains(t, err, "failed unmarshaling system nexus payload") +} + +func TestUnwrapAndInjectResponse_NilPayloadIsNoOp(t *testing.T) { + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.unwrapAndInjectResponse( + temporalSystemNexusEndpoint, "SignalWithStartWorkflowExecution", + nil, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Empty(t, fields) +} + +func TestUnwrapAndInjectResponse_UnknownOperationIsNoOp(t *testing.T) { + p := &commonpb.Payload{Data: []byte("ignored")} + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.unwrapAndInjectResponse( + temporalSystemNexusEndpoint, "NotARealOperation", + p, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Empty(t, fields) +} + +func TestUnwrapAndInjectResponse_BadProtoBytesReturnsError(t *testing.T) { + p := &commonpb.Payload{Data: []byte{0xff, 0xff, 0xff}} + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + err := iter.unwrapAndInjectResponse( + temporalSystemNexusEndpoint, "SignalWithStartWorkflowExecution", + p, fields, temporalproto.CustomJSONMarshalOptions{}) + require.Error(t, err) + require.ErrorContains(t, err, "failed unmarshaling system nexus payload") +} + +func TestUnwrapAndInjectRequest_DecodesAllNestedPayloads(t *testing.T) { + // The Input/SignalInput fields hold the user-supplied payloads, which the codec + // should be applied to. The outer payload bytes are raw proto and are not codec-encoded. + inner1 := &commonpb.Payload{Metadata: map[string][]byte{"encoding": []byte("binary/plain")}, Data: []byte("hello")} + inner2 := &commonpb.Payload{Metadata: map[string][]byte{"encoding": []byte("binary/plain")}, Data: []byte("world")} + signalInner := &commonpb.Payload{Metadata: map[string][]byte{"encoding": []byte("binary/plain")}, Data: []byte("signal-arg")} + req := &workflowservice.SignalWithStartWorkflowExecutionRequest{ + Namespace: "ns", + WorkflowId: "wf", + Input: &commonpb.Payloads{Payloads: []*commonpb.Payload{inner1, inner2}}, + SignalInput: &commonpb.Payloads{Payloads: []*commonpb.Payload{signalInner}}, + } + p := signalWithStartRequestPayload(t, req) + + codec := &markingCodec{} + iter := &structuredHistoryIter{ctx: context.Background(), codec: codec} + fields := map[string]any{} + require.NoError(t, iter.unwrapAndInjectRequest( + temporalSystemNexusEndpoint, "SignalWithStartWorkflowExecution", + p, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Equal(t, 3, codec.decodeCalls, "codec should have been invoked once per nested payload") + + unwrapped, ok := fields["unwrappedInput"].(map[string]any) + require.True(t, ok) + require.Equal(t, "ns", unwrapped["namespace"]) + require.Equal(t, "wf", unwrapped["workflowId"]) +} + +func TestUnwrapAndInjectRequest_CodecErrorPropagates(t *testing.T) { + req := &workflowservice.SignalWithStartWorkflowExecutionRequest{ + Input: &commonpb.Payloads{Payloads: []*commonpb.Payload{{Data: []byte("x")}}}, + } + p := signalWithStartRequestPayload(t, req) + iter := &structuredHistoryIter{ctx: context.Background(), codec: failingCodec{}} + fields := map[string]any{} + err := iter.unwrapAndInjectRequest( + temporalSystemNexusEndpoint, "SignalWithStartWorkflowExecution", + p, fields, temporalproto.CustomJSONMarshalOptions{}) + require.Error(t, err) + require.Equal(t, "failed decoding payloads in system nexus payload: codec decode failure for testing", err.Error()) +} + +func TestDecodePayloadsInProto_VisitsAllPayloads(t *testing.T) { + req := &workflowservice.SignalWithStartWorkflowExecutionRequest{ + Input: &commonpb.Payloads{Payloads: []*commonpb.Payload{ + {Data: []byte("a")}, + {Data: []byte("b")}, + }}, + SignalInput: &commonpb.Payloads{Payloads: []*commonpb.Payload{ + {Data: []byte("c")}, + }}, + } + codec := &markingCodec{} + require.NoError(t, decodePayloadsInProto(context.Background(), req, codec)) + require.Equal(t, 3, codec.decodeCalls) + require.Equal(t, []byte("decoded:a"), req.Input.Payloads[0].Data) + require.Equal(t, []byte("decoded:b"), req.Input.Payloads[1].Data) + require.Equal(t, []byte("decoded:c"), req.SignalInput.Payloads[0].Data) +} + +func TestInjectSystemNexusUnwrapped_ScheduledKnownOp(t *testing.T) { + req := &workflowservice.SignalWithStartWorkflowExecutionRequest{ + Namespace: "ns", + WorkflowId: "wf-xyz", + SignalName: "ping", + } + event := &historypb.HistoryEvent{ + EventId: 5, + EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED, + Attributes: &historypb.HistoryEvent_NexusOperationScheduledEventAttributes{ + NexusOperationScheduledEventAttributes: &historypb.NexusOperationScheduledEventAttributes{ + Endpoint: temporalSystemNexusEndpoint, + Operation: "SignalWithStartWorkflowExecution", + Input: signalWithStartRequestPayload(t, req), + }, + }, + } + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.injectSystemNexusUnwrapped(event, fields, temporalproto.CustomJSONMarshalOptions{})) + + unwrapped, ok := fields["unwrappedInput"].(map[string]any) + require.True(t, ok, "expected unwrappedInput map to be set") + require.Equal(t, "ns", unwrapped["namespace"]) + require.Equal(t, "wf-xyz", unwrapped["workflowId"]) + require.Equal(t, "ping", unwrapped["signalName"]) +} + +func TestInjectSystemNexusUnwrapped_ScheduledUnknownEndpointSkipped(t *testing.T) { + req := &workflowservice.SignalWithStartWorkflowExecutionRequest{Namespace: "ns"} + event := &historypb.HistoryEvent{ + EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED, + Attributes: &historypb.HistoryEvent_NexusOperationScheduledEventAttributes{ + NexusOperationScheduledEventAttributes: &historypb.NexusOperationScheduledEventAttributes{ + Endpoint: "user-endpoint", + Operation: "SignalWithStartWorkflowExecution", + Input: signalWithStartRequestPayload(t, req), + }, + }, + } + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.injectSystemNexusUnwrapped(event, fields, temporalproto.CustomJSONMarshalOptions{})) + _, ok := fields["unwrappedInput"] + require.False(t, ok, "non-system endpoint must not produce unwrappedInput") +} + +func TestInjectSystemNexusUnwrapped_CompletedUsesPriorScheduled(t *testing.T) { + resp := &workflowservice.SignalWithStartWorkflowExecutionResponse{ + RunId: "run-abc", + Started: true, + } + completed := &historypb.HistoryEvent{ + EventId: 6, + EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_COMPLETED, + Attributes: &historypb.HistoryEvent_NexusOperationCompletedEventAttributes{ + NexusOperationCompletedEventAttributes: &historypb.NexusOperationCompletedEventAttributes{ + ScheduledEventId: 5, + Result: signalWithStartResponsePayload(t, resp), + }, + }, + } + iter := &structuredHistoryIter{ + ctx: context.Background(), + systemNexusOps: map[int64]string{5: "SignalWithStartWorkflowExecution"}, + } + fields := map[string]any{} + require.NoError(t, iter.injectSystemNexusUnwrapped(completed, fields, temporalproto.CustomJSONMarshalOptions{})) + + unwrapped, ok := fields["unwrappedResult"].(map[string]any) + require.True(t, ok, "expected unwrappedResult map to be set") + require.Equal(t, "run-abc", unwrapped["runId"]) + require.Equal(t, true, unwrapped["started"]) +} + +func TestInjectSystemNexusUnwrapped_CompletedWithoutPriorScheduledSkipped(t *testing.T) { + completed := &historypb.HistoryEvent{ + EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_COMPLETED, + Attributes: &historypb.HistoryEvent_NexusOperationCompletedEventAttributes{ + NexusOperationCompletedEventAttributes: &historypb.NexusOperationCompletedEventAttributes{ + ScheduledEventId: 5, + Result: &commonpb.Payload{Data: []byte("garbage")}, + }, + }, + } + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.injectSystemNexusUnwrapped(completed, fields, temporalproto.CustomJSONMarshalOptions{})) + _, ok := fields["unwrappedResult"] + require.False(t, ok, "no prior scheduled means we don't know the op, so no unwrap") +} + +func TestInjectSystemNexusUnwrapped_NonNexusEventNoOp(t *testing.T) { + event := &historypb.HistoryEvent{ + EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, + Attributes: &historypb.HistoryEvent_WorkflowExecutionStartedEventAttributes{ + WorkflowExecutionStartedEventAttributes: &historypb.WorkflowExecutionStartedEventAttributes{}, + }, + } + iter := &structuredHistoryIter{ctx: context.Background()} + fields := map[string]any{} + require.NoError(t, iter.injectSystemNexusUnwrapped(event, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Empty(t, fields) +} + +func TestInjectSystemNexusUnwrapped_AppliesCodecToScheduledInput(t *testing.T) { + req := &workflowservice.SignalWithStartWorkflowExecutionRequest{ + WorkflowId: "wf", + Input: &commonpb.Payloads{Payloads: []*commonpb.Payload{ + {Data: []byte("inner-input")}, + }}, + SignalInput: &commonpb.Payloads{Payloads: []*commonpb.Payload{ + {Data: []byte("inner-signal")}, + }}, + } + event := &historypb.HistoryEvent{ + EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED, + Attributes: &historypb.HistoryEvent_NexusOperationScheduledEventAttributes{ + NexusOperationScheduledEventAttributes: &historypb.NexusOperationScheduledEventAttributes{ + Endpoint: temporalSystemNexusEndpoint, + Operation: "SignalWithStartWorkflowExecution", + Input: signalWithStartRequestPayload(t, req), + }, + }, + } + codec := &markingCodec{} + iter := &structuredHistoryIter{ctx: context.Background(), codec: codec} + fields := map[string]any{} + require.NoError(t, iter.injectSystemNexusUnwrapped(event, fields, temporalproto.CustomJSONMarshalOptions{})) + require.Equal(t, 2, codec.decodeCalls, "codec should run on both nested payloads") +} diff --git a/internal/temporalcli/commands.workflow_exec.go b/internal/temporalcli/commands.workflow_exec.go index e115a5303..cef46fdf2 100644 --- a/internal/temporalcli/commands.workflow_exec.go +++ b/internal/temporalcli/commands.workflow_exec.go @@ -25,7 +25,9 @@ import ( "go.temporal.io/api/temporalproto" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/client" + "go.temporal.io/sdk/converter" "go.temporal.io/sdk/temporal" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" ) @@ -40,7 +42,7 @@ func (c *TemporalWorkflowStartCommand) run(cctx *CommandContext, args []string) } func (c *TemporalWorkflowExecuteCommand) run(cctx *CommandContext, args []string) error { - cl, err := dialClient(cctx, &c.Parent.ClientOptions) + cl, codec, err := dialClientWithCodec(cctx, &c.Parent.ClientOptions) if err != nil { return err } @@ -63,6 +65,7 @@ func (c *TemporalWorkflowExecuteCommand) run(cctx *CommandContext, args []string runID: run.GetRunID(), includeDetails: c.Detailed, follow: true, + codec: codec, } if err := iter.print(cctx); err != nil && cctx.Err() == nil { return fmt.Errorf("displaying history failed: %w", err) @@ -704,6 +707,68 @@ func coloredEventType(e enums.EventType) string { return fn(e.String()) } +const temporalSystemNexusEndpoint = "__temporal_system" + +// systemNexusOpDisplayName returns a display name for Nexus operation events on the +// "__temporal_system" endpoint, replacing the generic NexusOperation* name with the +// actual operation name + event-type suffix. Returns "" for all other events. +// Populates s.systemNexusOps when it encounters a qualifying Scheduled event. +func (s *structuredHistoryIter) systemNexusOpDisplayName(event *history.HistoryEvent) string { + if s.systemNexusOps == nil { + s.systemNexusOps = make(map[int64]string) + } + switch event.EventType { + case enums.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED: + attr := event.GetNexusOperationScheduledEventAttributes() + if attr == nil || attr.Endpoint != temporalSystemNexusEndpoint { + return "" + } + s.systemNexusOps[event.EventId] = attr.Operation + return attr.Operation + "Scheduled" + case enums.EVENT_TYPE_NEXUS_OPERATION_STARTED: + attr := event.GetNexusOperationStartedEventAttributes() + if attr == nil { + return "" + } + if op, ok := s.systemNexusOps[attr.ScheduledEventId]; ok { + return op + "Started" + } + case enums.EVENT_TYPE_NEXUS_OPERATION_COMPLETED: + attr := event.GetNexusOperationCompletedEventAttributes() + if attr == nil { + return "" + } + if op, ok := s.systemNexusOps[attr.ScheduledEventId]; ok { + return op + "Completed" + } + case enums.EVENT_TYPE_NEXUS_OPERATION_FAILED: + attr := event.GetNexusOperationFailedEventAttributes() + if attr == nil { + return "" + } + if op, ok := s.systemNexusOps[attr.ScheduledEventId]; ok { + return op + "Failed" + } + case enums.EVENT_TYPE_NEXUS_OPERATION_TIMED_OUT: + attr := event.GetNexusOperationTimedOutEventAttributes() + if attr == nil { + return "" + } + if op, ok := s.systemNexusOps[attr.ScheduledEventId]; ok { + return op + "TimedOut" + } + case enums.EVENT_TYPE_NEXUS_OPERATION_CANCELED: + attr := event.GetNexusOperationCanceledEventAttributes() + if attr == nil { + return "" + } + if op, ok := s.systemNexusOps[attr.ScheduledEventId]; ok { + return op + "Canceled" + } + } + return "" +} + type structuredHistoryIter struct { ctx context.Context client client.Client @@ -725,6 +790,14 @@ type structuredHistoryIter struct { reverseBuf []*history.HistoryEvent reverseNextToken []byte reverseStarted bool + + // maps NexusOperationScheduled eventId → operation name for __temporal_system endpoint events + systemNexusOps map[int64]string + + // codec is the remote payload codec configured for this client, or nil if none. Used to + // decode payloads nested inside system Nexus operation request/response bytes so they + // can be rendered alongside the rest of the event fields. + codec converter.PayloadCodec } func (s *structuredHistoryIter) print(cctx *CommandContext) error { @@ -751,7 +824,11 @@ func (s *structuredHistoryIter) print(cctx *CommandContext) error { first = false // Print section heading - cctx.Printer.Printlnf("--------------- [%v] %v ---------------", event.EventId, event.EventType) + eventTypeName := event.EventType.String() + if name := s.systemNexusOpDisplayName(event); name != "" { + eventTypeName = name + } + cctx.Printer.Printlnf("--------------- [%v] %v ---------------", event.EventId, eventTypeName) // Convert the event to dot-delimited-field/value and print one per line fields, err := s.flattenFields(cctx, event) if err != nil { @@ -786,10 +863,14 @@ func (s *structuredHistoryIter) Next() (any, error) { return nil, nil } // Build data + typeName := s.systemNexusOpDisplayName(event) + if typeName == "" { + typeName = coloredEventType(event.EventType) + } data := structuredHistoryEvent{ ID: event.EventId, Time: event.EventTime.AsTime().Format(time.RFC3339), - Type: coloredEventType(event.EventType), + Type: typeName, } // Follow continue as new (forward only; reverse traversal stays within the requested run) @@ -900,6 +981,12 @@ func (s *structuredHistoryIter) flattenFields( delete(fieldsMap, k) } } + // For system Nexus operation events, deserialize the request/response payload bytes + // into the typed proto, decode any payloads nested inside via the codec, and merge + // the decoded view into the output under "unwrappedInput" / "unwrappedResult". + if err := s.injectSystemNexusUnwrapped(event, fieldsMap, opts); err != nil { + return nil, err + } // Flatten JSON map and sort fields, err := s.flattenJSONValue(nil, "", fieldsMap) if err != nil { @@ -909,6 +996,101 @@ func (s *structuredHistoryIter) flattenFields( return fields, nil } +// injectSystemNexusUnwrapped, if the given event is a known system Nexus operation, +// deserializes the underlying request (on Scheduled) or response (on Completed) proto, +// decodes any payloads nested inside via the codec, and inserts the resulting JSON +// representation into fieldsMap under "unwrappedInput" / "unwrappedResult". +func (s *structuredHistoryIter) injectSystemNexusUnwrapped( + event *history.HistoryEvent, + fieldsMap map[string]any, + opts temporalproto.CustomJSONMarshalOptions, +) error { + switch event.EventType { + case enums.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED: + attr := event.GetNexusOperationScheduledEventAttributes() + if attr == nil { + return nil + } + return s.unwrapAndInjectRequest(attr.GetEndpoint(), attr.GetOperation(), attr.GetInput(), fieldsMap, opts) + case enums.EVENT_TYPE_NEXUS_OPERATION_COMPLETED: + attr := event.GetNexusOperationCompletedEventAttributes() + if attr == nil { + return nil + } + op, ok := s.systemNexusOps[attr.GetScheduledEventId()] + if !ok { + return nil + } + return s.unwrapAndInjectResponse(temporalSystemNexusEndpoint, op, attr.GetResult(), fieldsMap, opts) + } + return nil +} + +// unwrapAndInjectRequest looks up the registered request proto for (endpoint, operation), +// then injects the decoded view under "unwrappedInput". No-op for unregistered ops. +func (s *structuredHistoryIter) unwrapAndInjectRequest( + endpoint, operation string, + payload *commonpb.Payload, + fieldsMap map[string]any, + opts temporalproto.CustomJSONMarshalOptions, +) error { + types, ok := systemNexusOps[systemNexusOpKey{Endpoint: endpoint, Operation: operation}] + if !ok { + return nil + } + return s.unwrapAndInject(types.NewRequest(), payload, fieldsMap, "unwrappedInput", opts) +} + +// unwrapAndInjectResponse looks up the registered response proto for (endpoint, operation), +// then injects the decoded view under "unwrappedResult". No-op for unregistered ops. +func (s *structuredHistoryIter) unwrapAndInjectResponse( + endpoint, operation string, + payload *commonpb.Payload, + fieldsMap map[string]any, + opts temporalproto.CustomJSONMarshalOptions, +) error { + types, ok := systemNexusOps[systemNexusOpKey{Endpoint: endpoint, Operation: operation}] + if !ok { + return nil + } + return s.unwrapAndInject(types.NewResponse(), payload, fieldsMap, "unwrappedResult", opts) +} + +// unwrapAndInject is the shared body: unmarshal payload bytes into the supplied proto, +// decode any payloads nested inside via the codec, marshal back to JSON, and inject the +// resulting map into fieldsMap[key]. A nil payload is a no-op. +func (s *structuredHistoryIter) unwrapAndInject( + msg proto.Message, + payload *commonpb.Payload, + fieldsMap map[string]any, + key string, + opts temporalproto.CustomJSONMarshalOptions, +) error { + if payload == nil { + return nil + } + if err := proto.Unmarshal(payload.Data, msg); err != nil { + return fmt.Errorf("failed unmarshaling system nexus payload: %w", err) + } + if s.codec != nil { + if err := decodePayloadsInProto(s.ctx, msg, s.codec); err != nil { + return fmt.Errorf("failed decoding payloads in system nexus payload: %w", err) + } + } + unwrappedJSON, err := opts.Marshal(msg) + if err != nil { + return fmt.Errorf("failed marshaling unwrapped system nexus payload: %w", err) + } + dec := json.NewDecoder(bytes.NewReader(unwrappedJSON)) + dec.UseNumber() + var unwrappedMap map[string]any + if err := dec.Decode(&unwrappedMap); err != nil { + return fmt.Errorf("failed unmarshaling unwrapped JSON for system nexus payload: %w", err) + } + fieldsMap[key] = unwrappedMap + return nil +} + func (s *structuredHistoryIter) flattenJSONValue( to []eventFieldValue, field string, diff --git a/internal/temporalcli/commands.workflow_show_test.go b/internal/temporalcli/commands.workflow_show_test.go new file mode 100644 index 000000000..0f70449a5 --- /dev/null +++ b/internal/temporalcli/commands.workflow_show_test.go @@ -0,0 +1,173 @@ +package temporalcli + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.temporal.io/api/enums/v1" + historypb "go.temporal.io/api/history/v1" +) + +func TestSystemNexusOpDisplayName(t *testing.T) { + const op = "SignalWithStartWorkflowExecution" // present in the global systemNexusOps registry + const futureOp = "FutureUnregisteredOperation" // intentionally NOT in the global registry + + newScheduled := func(eventID int64, endpoint, operation string) *historypb.HistoryEvent { + return &historypb.HistoryEvent{ + EventId: eventID, + EventType: enums.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED, + Attributes: &historypb.HistoryEvent_NexusOperationScheduledEventAttributes{ + NexusOperationScheduledEventAttributes: &historypb.NexusOperationScheduledEventAttributes{ + Endpoint: endpoint, Operation: operation, + }, + }, + } + } + // terminalEvent returns a NexusOperation* event of the given type referencing scheduledEventID. + // Returns nil for unsupported event types. + terminalEvent := func(eventType enums.EventType, scheduledEventID int64) *historypb.HistoryEvent { + e := &historypb.HistoryEvent{EventId: scheduledEventID + 1, EventType: eventType} + switch eventType { + case enums.EVENT_TYPE_NEXUS_OPERATION_STARTED: + e.Attributes = &historypb.HistoryEvent_NexusOperationStartedEventAttributes{ + NexusOperationStartedEventAttributes: &historypb.NexusOperationStartedEventAttributes{ScheduledEventId: scheduledEventID}, + } + case enums.EVENT_TYPE_NEXUS_OPERATION_COMPLETED: + e.Attributes = &historypb.HistoryEvent_NexusOperationCompletedEventAttributes{ + NexusOperationCompletedEventAttributes: &historypb.NexusOperationCompletedEventAttributes{ScheduledEventId: scheduledEventID}, + } + case enums.EVENT_TYPE_NEXUS_OPERATION_FAILED: + e.Attributes = &historypb.HistoryEvent_NexusOperationFailedEventAttributes{ + NexusOperationFailedEventAttributes: &historypb.NexusOperationFailedEventAttributes{ScheduledEventId: scheduledEventID}, + } + case enums.EVENT_TYPE_NEXUS_OPERATION_TIMED_OUT: + e.Attributes = &historypb.HistoryEvent_NexusOperationTimedOutEventAttributes{ + NexusOperationTimedOutEventAttributes: &historypb.NexusOperationTimedOutEventAttributes{ScheduledEventId: scheduledEventID}, + } + case enums.EVENT_TYPE_NEXUS_OPERATION_CANCELED: + e.Attributes = &historypb.HistoryEvent_NexusOperationCanceledEventAttributes{ + NexusOperationCanceledEventAttributes: &historypb.NexusOperationCanceledEventAttributes{ScheduledEventId: scheduledEventID}, + } + } + return e + } + + // priorScheduled returns a setup that creates an iter and pre-processes the given Scheduled + // event. In forward traversal this matches the natural event order; in reverse traversal it + // simulates the iter having pre-scanned the buffered history. + priorScheduled := func(scheduled *historypb.HistoryEvent) func(reverse bool) *structuredHistoryIter { + return func(reverse bool) *structuredHistoryIter { + s := &structuredHistoryIter{reverse: reverse} + s.systemNexusOpDisplayName(scheduled) + return s + } + } + empty := func(reverse bool) *structuredHistoryIter { + return &structuredHistoryIter{reverse: reverse} + } + + schedSystem := newScheduled(5, temporalSystemNexusEndpoint, op) + schedSystemFutureOp := newScheduled(5, temporalSystemNexusEndpoint, futureOp) + schedNonSystem := newScheduled(7, "some-other-endpoint", op) + + tests := []struct { + name string + setup func(reverse bool) *structuredHistoryIter + event *historypb.HistoryEvent + wantName string + wantEmpty bool + }{ + // Scheduled cases + { + name: "scheduled system endpoint records op and returns name", + setup: empty, + event: schedSystem, + wantName: op + "Scheduled", + }, + { + name: "scheduled non-system endpoint returns empty", + setup: empty, + event: schedNonSystem, + wantEmpty: true, + }, + { + // Display name does not consult the global systemNexusOps registry; any op on the + // __temporal_system endpoint produces a display name. (Payload unwrap-and-inject is + // the layer that requires registry membership; see TestUnwrapAndInjectRequest_*.) + name: "scheduled system endpoint with op not in registry still returns name", + setup: empty, + event: schedSystemFutureOp, + wantName: futureOp + "Scheduled", + }, + // Terminal cases with a prior Scheduled in the instance map + { + name: "started after system scheduled", + setup: priorScheduled(schedSystem), + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_STARTED, 5), + wantName: op + "Started", + }, + { + // Confirms terminal events use the iter's instance map (which captured the unregistered + // op from its Scheduled event), regardless of global-registry membership. + name: "started after system scheduled with op not in registry", + setup: priorScheduled(schedSystemFutureOp), + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_STARTED, 5), + wantName: futureOp + "Started", + }, + { + name: "completed after system scheduled", + setup: priorScheduled(schedSystem), + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_COMPLETED, 5), + wantName: op + "Completed", + }, + { + name: "completed with no prior scheduled returns empty", + setup: empty, + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_COMPLETED, 5), + wantEmpty: true, + }, + { + name: "failed after system scheduled", + setup: priorScheduled(schedSystem), + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_FAILED, 5), + wantName: op + "Failed", + }, + { + name: "timed out after system scheduled", + setup: priorScheduled(schedSystem), + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_TIMED_OUT, 5), + wantName: op + "TimedOut", + }, + { + name: "canceled after system scheduled", + setup: priorScheduled(schedSystem), + event: terminalEvent(enums.EVENT_TYPE_NEXUS_OPERATION_CANCELED, 5), + wantName: op + "Canceled", + }, + } + + // The function is direction-agnostic: given the same prior state, it returns the same + // display name whether iter.reverse is true or false. Running each case in both modes + // pins that contract so a future change can't silently introduce direction-sensitive + // branching without breaking these tests. + for _, reverse := range []bool{false, true} { + modeName := "forward" + if reverse { + modeName = "reverse" + } + t.Run(modeName, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + iter := tc.setup(reverse) + require.Equal(t, reverse, iter.reverse, "test setup must mark iter reverse=%v", reverse) + name := iter.systemNexusOpDisplayName(tc.event) + if tc.wantEmpty { + require.Empty(t, name) + } else { + require.Equal(t, tc.wantName, name) + } + }) + } + }) + } +} diff --git a/internal/temporalcli/commands.workflow_view.go b/internal/temporalcli/commands.workflow_view.go index 9d142c3c8..a1487d906 100644 --- a/internal/temporalcli/commands.workflow_view.go +++ b/internal/temporalcli/commands.workflow_view.go @@ -324,8 +324,14 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin _ = cctx.Printer.PrintStructured(resp.PendingChildren, printer.StructuredOptions{}) } - cctx.Printer.Println(color.MagentaString("Pending Nexus Operations: %v", len(resp.PendingNexusOperations))) - if len(resp.PendingNexusOperations) > 0 { + var pendingNexusOps []*workflow.PendingNexusOperationInfo + for _, op := range resp.PendingNexusOperations { + if op.GetEndpoint() != temporalSystemNexusEndpoint { + pendingNexusOps = append(pendingNexusOps, op) + } + } + cctx.Printer.Println(color.MagentaString("Pending Nexus Operations: %v", len(pendingNexusOps))) + if len(pendingNexusOps) > 0 { cctx.Printer.Println() ops := make([]struct { Endpoint string @@ -348,8 +354,8 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin CancelationLastAttemptCompleteTime time.Time `cli:",cardOmitEmpty"` CancelationLastAttemptFailure *failure.Failure `cli:",cardOmitEmpty"` CancelationBlockedReason string `cli:",cardOmitEmpty"` - }, len(resp.PendingNexusOperations)) - for i, op := range resp.PendingNexusOperations { + }, len(pendingNexusOps)) + for i, op := range pendingNexusOps { ops[i].Endpoint = op.GetEndpoint() ops[i].Service = op.GetService() ops[i].Operation = op.GetOperation() @@ -558,7 +564,7 @@ func (c *TemporalWorkflowShowCommand) run(cctx *CommandContext, _ []string) erro } // Call describe - cl, err := dialClient(cctx, &c.Parent.ClientOptions) + cl, codec, err := dialClientWithCodec(cctx, &c.Parent.ClientOptions) if err != nil { return err } @@ -574,6 +580,7 @@ func (c *TemporalWorkflowShowCommand) run(cctx *CommandContext, _ []string) erro includeDetails: c.Detailed, follow: c.Follow, reverse: c.Reverse, + codec: codec, } if !cctx.JSONOutput { cctx.Printer.Println(color.MagentaString("Progress:")) diff --git a/internal/temporalcli/commands.workflow_view_test.go b/internal/temporalcli/commands.workflow_view_test.go index 31cf22b33..d3d13851e 100644 --- a/internal/temporalcli/commands.workflow_view_test.go +++ b/internal/temporalcli/commands.workflow_view_test.go @@ -13,16 +13,21 @@ import ( "github.com/nexus-rpc/sdk-go/nexus" "github.com/stretchr/testify/assert" "github.com/temporalio/cli/internal/temporalcli" + commandpb "go.temporal.io/api/command/v1" "go.temporal.io/api/common/v1" "go.temporal.io/api/enums/v1" + historypb "go.temporal.io/api/history/v1" nexuspb "go.temporal.io/api/nexus/v1" "go.temporal.io/api/operatorservice/v1" + taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/api/workflowservice/v1/workflowservicenexus" "go.temporal.io/sdk/client" "go.temporal.io/sdk/temporal" "go.temporal.io/sdk/temporalnexus" "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" + "go.temporal.io/server/common/payloads" ) func (s *SharedServerSuite) TestWorkflow_Describe_ActivityFailing() { @@ -1148,3 +1153,262 @@ func (s *SharedServerSuite) TestWorkflow_Describe_RootWorkflow() { s.Equal(run.GetID(), jsonOut.WorkflowExecutionInfo.RootExecution.GetWorkflowId()) s.Equal(run.GetRunID(), jsonOut.WorkflowExecutionInfo.RootExecution.GetRunId()) } + +// temporalSystemNexusEndpointName is the reserved endpoint name for Temporal system +// Nexus operations (mirrors the unexported temporalSystemNexusEndpoint constant in the +// production code; reproduced here as a literal because the constant is not exported). +const temporalSystemNexusEndpointName = "__temporal_system" + +// runSystemNexusSWSWorkflow drives a SignalWithStartWorkflowExecution Nexus operation +// against the __temporal_system endpoint from a raw-polled caller workflow, waits for it +// to complete, then completes the caller. Returns the caller workflow ID. The target +// workflow is registered for cleanup via s.T().Cleanup. The SDK cannot be used here +// because it refuses endpoints with the reserved "__temporal_" prefix. +func (s *SharedServerSuite) runSystemNexusSWSWorkflow(ctx context.Context) string { + callerTaskQueue := "cli-sys-nexus-caller-" + uuid.NewString() + targetTaskQueue := "cli-sys-nexus-target-" + uuid.NewString() + targetWorkflowID := "cli-sys-nexus-target-" + uuid.NewString() + callerWorkflowID := "cli-sys-nexus-caller-" + uuid.NewString() + + startResp, err := s.Client.WorkflowService().StartWorkflowExecution(ctx, &workflowservice.StartWorkflowExecutionRequest{ + Namespace: s.Namespace(), + WorkflowId: callerWorkflowID, + WorkflowType: &common.WorkflowType{Name: "caller-workflow"}, + TaskQueue: &taskqueuepb.TaskQueue{Name: callerTaskQueue, Kind: enums.TASK_QUEUE_KIND_NORMAL}, + RequestId: uuid.NewString(), + }) + s.NoError(err) + + pollResp, err := s.Client.WorkflowService().PollWorkflowTaskQueue(ctx, &workflowservice.PollWorkflowTaskQueueRequest{ + Namespace: s.Namespace(), + TaskQueue: &taskqueuepb.TaskQueue{Name: callerTaskQueue, Kind: enums.TASK_QUEUE_KIND_NORMAL}, + Identity: "cli-test", + }) + s.NoError(err) + s.Equal(callerWorkflowID, pollResp.WorkflowExecution.WorkflowId) + s.Equal(startResp.RunId, pollResp.WorkflowExecution.RunId) + + _, err = s.Client.WorkflowService().RespondWorkflowTaskCompleted(ctx, &workflowservice.RespondWorkflowTaskCompletedRequest{ + Identity: "cli-test", + TaskToken: pollResp.TaskToken, + Commands: []*commandpb.Command{ + { + CommandType: enums.COMMAND_TYPE_SCHEDULE_NEXUS_OPERATION, + Attributes: &commandpb.Command_ScheduleNexusOperationCommandAttributes{ + ScheduleNexusOperationCommandAttributes: &commandpb.ScheduleNexusOperationCommandAttributes{ + Endpoint: temporalSystemNexusEndpointName, + Service: workflowservicenexus.TemporalAPIWorkflowserviceV1WorkflowService.ServiceName, + Operation: workflowservicenexus.TemporalAPIWorkflowserviceV1WorkflowService.SignalWithStartWorkflowExecution.Name(), + Input: payloads.MustEncodeSingle(&workflowservice.SignalWithStartWorkflowExecutionRequest{ + WorkflowId: targetWorkflowID, + SignalName: "cli-test-signal", + WorkflowType: &common.WorkflowType{Name: "target-workflow"}, + TaskQueue: &taskqueuepb.TaskQueue{Name: targetTaskQueue}, + }), + }, + }, + }, + }, + }) + s.NoError(err) + s.T().Cleanup(func() { + _ = s.Client.TerminateWorkflow(context.Background(), targetWorkflowID, "", "test cleanup") + }) + + pollResp, err = s.Client.WorkflowService().PollWorkflowTaskQueue(ctx, &workflowservice.PollWorkflowTaskQueueRequest{ + Namespace: s.Namespace(), + TaskQueue: &taskqueuepb.TaskQueue{Name: callerTaskQueue, Kind: enums.TASK_QUEUE_KIND_NORMAL}, + Identity: "cli-test", + }) + s.NoError(err) + + var sawCompleted bool + for _, e := range pollResp.History.Events { + if e.GetNexusOperationCompletedEventAttributes() != nil { + sawCompleted = true + break + } + if attrs := e.GetNexusOperationFailedEventAttributes(); attrs != nil { + s.FailNow("SignalWithStartWorkflowExecution Nexus operation failed", "failure: %v", attrs.Failure) + } + } + s.True(sawCompleted, "expected a NexusOperationCompleted event in caller history before completing the caller workflow") + + _, err = s.Client.WorkflowService().RespondWorkflowTaskCompleted(ctx, &workflowservice.RespondWorkflowTaskCompletedRequest{ + Identity: "cli-test", + TaskToken: pollResp.TaskToken, + Commands: []*commandpb.Command{{ + CommandType: enums.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION, + Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{ + CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{}, + }, + }}, + }) + s.NoError(err) + return callerWorkflowID +} + +// TestWorkflow_Show_SystemNexusOperationTransformsTypeNames drives a SignalWithStart +// Nexus operation against the __temporal_system endpoint from inside a workflow, then +// verifies that `workflow show` (default table mode) renders the resulting history +// events using the operation-prefixed names (e.g. SignalWithStartWorkflowExecutionScheduled) +// instead of the generic NexusOperation* names. +func (s *SharedServerSuite) TestWorkflow_Show_SystemNexusOperationTransformsTypeNames() { + ctx, cancel := context.WithTimeout(s.Context, 60*time.Second) + defer cancel() + + callerWorkflowID := s.runSystemNexusSWSWorkflow(ctx) + + res := s.Execute( + "workflow", "show", + "--address", s.Address(), + "-w", callerWorkflowID, + ) + s.NoError(res.Err) + out := res.Stdout.String() + s.Contains(out, "SignalWithStartWorkflowExecutionScheduled", "expected transformed Scheduled name in show output") + s.Contains(out, "SignalWithStartWorkflowExecutionCompleted", "expected transformed Completed name in show output") + s.NotContains(out, "NexusOperationScheduled", "raw event type name should be replaced by the unwrapped form") + s.NotContains(out, "NexusOperationCompleted", "raw event type name should be replaced by the unwrapped form") +} + +// TestWorkflow_Show_JSONOutputDoesNotUnwrapSystemNexus pins down that `workflow show -o json` +// emits the raw history.History proto for system Nexus operations — no event-type name +// rewriting and no "unwrappedInput"/"unwrappedResult" injection. JSON output must stay +// compatible with SDK replayers, which need the exact serialized history the server stored. +func (s *SharedServerSuite) TestWorkflow_Show_JSONOutputDoesNotUnwrapSystemNexus() { + ctx, cancel := context.WithTimeout(s.Context, 60*time.Second) + defer cancel() + + callerWorkflowID := s.runSystemNexusSWSWorkflow(ctx) + + res := s.Execute( + "workflow", "show", + "--address", s.Address(), + "-w", callerWorkflowID, + "-o", "json", + ) + s.NoError(res.Err) + raw := res.Stdout.String() + + // Negative assertions: none of the text-mode transformations may bleed into JSON. + s.NotContains(raw, "SignalWithStartWorkflowExecutionScheduled", + "JSON output must not rewrite NEXUS_OPERATION_SCHEDULED to the operation-prefixed name") + s.NotContains(raw, "SignalWithStartWorkflowExecutionCompleted", + "JSON output must not rewrite NEXUS_OPERATION_COMPLETED to the operation-prefixed name") + s.NotContains(raw, "unwrappedInput", + "JSON output must not inject the unwrappedInput field used by text-mode rendering") + s.NotContains(raw, "unwrappedResult", + "JSON output must not inject the unwrappedResult field used by text-mode rendering") + + // Positive assertion: the raw event types still appear, and the JSON parses cleanly as + // a history.History proto with the original Scheduled+Completed events for the system + // Nexus operation. + var hist historypb.History + s.NoError(temporalcli.UnmarshalProtoJSONWithOptions([]byte(raw), &hist, false)) + var schedAttrs *historypb.NexusOperationScheduledEventAttributes + var sawCompleted bool + for _, ev := range hist.Events { + if a := ev.GetNexusOperationScheduledEventAttributes(); a != nil { + schedAttrs = a + } + if ev.GetNexusOperationCompletedEventAttributes() != nil { + sawCompleted = true + } + } + if schedAttrs == nil { + s.FailNow("expected a NexusOperationScheduled event in JSON output") + } + s.True(sawCompleted, "expected a NexusOperationCompleted event in JSON output") + // The raw scheduled attributes must still carry the system endpoint and operation name — + // that's what replayers need to reconstruct the original command. + s.Equal(temporalSystemNexusEndpointName, schedAttrs.Endpoint) + s.Equal(workflowservicenexus.TemporalAPIWorkflowserviceV1WorkflowService.SignalWithStartWorkflowExecution.Name(), schedAttrs.Operation) + s.Equal(workflowservicenexus.TemporalAPIWorkflowserviceV1WorkflowService.ServiceName, schedAttrs.Service) + s.NotNil(schedAttrs.Input, "scheduled Input payload must survive untouched in JSON output") +} + +// TestWorkflow_Show_UserEndpointNexusOperationDoesNotTransformTypeNames verifies the +// negative case: when a Nexus operation runs against a non-system, user-defined endpoint, +// `workflow show` must leave the event type names untransformed because the +// (endpoint, operation) pair is not registered in the global systemNexusOps map. +func (s *SharedServerSuite) TestWorkflow_Show_UserEndpointNexusOperationDoesNotTransformTypeNames() { + handlerWorkflowID := uuid.NewString() + endpointName := validEndpointName(s.T()) + + // Workflow that waits to be canceled. Reused from the existing Nexus describe test. + handlerWorkflow := func(ctx workflow.Context, input nexus.NoValue) (nexus.NoValue, error) { + ctx.Done().Receive(ctx, nil) + return nil, ctx.Err() + } + + op := temporalnexus.NewWorkflowRunOperation("test-op", handlerWorkflow, func(ctx context.Context, _ nexus.NoValue, opts nexus.StartOperationOptions) (client.StartWorkflowOptions, error) { + return client.StartWorkflowOptions{ID: handlerWorkflowID}, nil + }) + service := nexus.NewService("test-service") + s.NoError(service.Register(op)) + + callerWorkflow := func(ctx workflow.Context) error { + nc := workflow.NewNexusClient(endpointName, service.Name) + fut := nc.ExecuteOperation(ctx, op, nil, workflow.NexusOperationOptions{}) + var exec workflow.NexusOperationExecution + if err := fut.GetNexusOperationExecution().Get(ctx, &exec); err != nil { + return err + } + // Block forever; the test only needs the Scheduled event to be present. + ctx.Done().Receive(ctx, nil) + return ctx.Err() + } + + w := s.DevServer.StartDevWorker(s.Suite.T(), DevWorkerOptions{ + Workflows: []any{handlerWorkflow, callerWorkflow}, + NexusServices: []*nexus.Service{service}, + }) + defer w.Stop() + + _, err := s.Client.OperatorService().CreateNexusEndpoint(s.Context, &operatorservice.CreateNexusEndpointRequest{ + Spec: &nexuspb.EndpointSpec{ + Name: endpointName, + Target: &nexuspb.EndpointTarget{ + Variant: &nexuspb.EndpointTarget_Worker_{ + Worker: &nexuspb.EndpointTarget_Worker{ + Namespace: s.Namespace(), + TaskQueue: w.Options.TaskQueue, + }, + }, + }, + }, + }) + s.NoError(err) + + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{TaskQueue: w.Options.TaskQueue}, + callerWorkflow, + ) + s.NoError(err) + s.T().Cleanup(func() { + _ = s.Client.TerminateWorkflow(context.Background(), run.GetID(), run.GetRunID(), "test cleanup") + _ = s.Client.TerminateWorkflow(context.Background(), handlerWorkflowID, "", "test cleanup") + }) + + // Wait until the operation has been scheduled — we don't need it to complete. + s.Eventually(func() bool { + resp, derr := s.Client.DescribeWorkflowExecution(s.Context, run.GetID(), run.GetRunID()) + if derr != nil { + return false + } + return len(resp.PendingNexusOperations) > 0 + }, 30*time.Second, 100*time.Millisecond) + + // `workflow show` over a non-system Nexus operation must NOT transform names. + res := s.Execute( + "workflow", "show", + "--address", s.Address(), + "-w", run.GetID(), + ) + s.NoError(res.Err) + out := res.Stdout.String() + s.Contains(out, "NexusOperationScheduled", "user-endpoint event type name should be preserved") + s.NotContains(out, "SignalWithStartWorkflowExecutionScheduled", "user-endpoint event must not pick up a system-op unwrapped name") +} diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index b77732bcb..c3efa843e 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -234,8 +234,12 @@ func (s *SharedServerSuite) SetupSuite() { // Disable DescribeTaskQueue cache. "frontend.activityAPIsEnabled": true, "history.enableChasm": true, - "activity.enableStandalone": true, - "activity.longPollTimeout": 2 * time.Second, + // Required by TestWorkflow_Show_SystemNexusOperationTransformsTypeNames + // to schedule a SignalWithStartWorkflowExecution Nexus operation against + // the __temporal_system endpoint from inside a workflow. + "history.enableSignalWithStartFromWorkflow": true, + "activity.enableStandalone": true, + "activity.longPollTimeout": 2 * time.Second, // this is overridden since we don't want caching to be enabled // while testing DescribeTaskQueue behaviour related to versioning "matching.TaskQueueInfoByBuildIdTTL": 0 * time.Second, From 8bb57b77ad55235f9b0f6d9f09e350057f53cdb8 Mon Sep 17 00:00:00 2001 From: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:07:03 -0500 Subject: [PATCH 09/28] fix: use allow instead of ignore for dependency-type in dependabot config (#1057) ## Summary - `dependency-type` is only valid inside `allow`, not `ignore` in dependabot.yml - Replaces the invalid `ignore` entry with `allow: [{dependency-type: direct}]` to achieve the same goal of skipping indirect/transitive dependency updates - Adds a CI workflow using `check-jsonschema` to validate `dependabot.yml` on PRs and pushes to main - Fixes the Dependabot config validation failure introduced in #1044 ## Test plan - [ ] Verify the `.github/dependabot.yml` validation check passes on this PR - [ ] Verify `check-jsonschema --builtin-schema vendor.dependabot .github/dependabot.yml` passes locally --- .github/dependabot.yml | 4 ++-- .github/workflows/validate-dependabot.yml | 29 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/validate-dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4ebbf0d4e..7226d8733 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,9 +6,9 @@ updates: interval: weekly cooldown: default-days: 14 + allow: + - dependency-type: direct ignore: - - dependency-name: "*" - dependency-type: indirect # Temporal dependencies are managed manually to ensure coordinated upgrades across all temporal packages - dependency-name: "go.temporal.io/*" diff --git a/.github/workflows/validate-dependabot.yml b/.github/workflows/validate-dependabot.yml new file mode 100644 index 000000000..d9010aa50 --- /dev/null +++ b/.github/workflows/validate-dependabot.yml @@ -0,0 +1,29 @@ +name: Validate Dependabot Config +on: + pull_request: + paths: + - '.github/dependabot.yml' + push: + branches: + - main + paths: + - '.github/dependabot.yml' + +permissions: + contents: read + +jobs: + validate: + name: Validate Dependabot Config + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: '3.12' + + - name: Validate dependabot.yml + run: | + pip install check-jsonschema==0.37.2 + check-jsonschema --builtin-schema vendor.dependabot .github/dependabot.yml From dd0524a8e1f04e9cd6ddff0f1b2b0dc211d5025c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:57:27 -0500 Subject: [PATCH 10/28] Bump the github-actions group with 2 updates (#1080) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 2 updates: [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) and [actions/create-github-app-token](https://github.com/actions/create-github-app-token). Updates `goreleaser/goreleaser-action` from 7.2.1 to 7.2.2
Release notes

Sourced from goreleaser/goreleaser-action's releases.

v7.2.2

What's Changed

New Contributors

Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v7...v7.2.2

Commits

Updates `actions/create-github-app-token` from 3.1.1 to 3.2.0
Release notes

Sourced from actions/create-github-app-token's releases.

v3.2.0

3.2.0 (2026-05-12)

Features

  • add support for enterprise-level GitHub Apps (#263) (952a2a7)
  • support full repository names in repositories input (#372) (85eb8dd)

Bug Fixes

  • deps: bump @​actions/core from 3.0.0 to 3.0.1 in the production-dependencies group (#364) (43e5c34)
  • validate private-key input (#376) (f24bbd8)
Changelog

Sourced from actions/create-github-app-token's changelog.

Changelog

3.2.0 (2026-05-12)

Features

  • add support for enterprise-level GitHub Apps (#263) (952a2a7)
  • support full repository names in repositories input (#372) (85eb8dd)

Bug Fixes

  • deps: bump @​actions/core from 3.0.0 to 3.0.1 in the production-dependencies group (#364) (43e5c34)
  • validate private-key input (#376) (f24bbd8)
Commits
  • bcd2ba4 chore(main): release 3.2.0 (#370)
  • f24bbd8 fix: validate private-key input (#376)
  • 363531b docs: capitalize Git as a proper noun in README (#374)
  • fd28011 docs: update procedure to configure Git (#287)
  • 85eb8dd feat: support full repository names in repositories input (#372)
  • c9aabb8 build(deps-dev): bump yaml from 2.8.3 to 2.8.4 in the development-dependencie...
  • e02e816 build(deps-dev): bump undici from 7.24.6 to 8.2.0 (#366)
  • 8d835bf build(deps-dev): bump esbuild from 0.27.4 to 0.28.0 in the development-depend...
  • 952a2a7 feat: add support for enterprise-level GitHub Apps (#263)
  • 43e5c34 fix(deps): bump @​actions/core from 3.0.0 to 3.0.1 in the production-dependenc...
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- .github/workflows/trigger-docs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24ed5570e..ec10831b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1 + uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2 with: version: v2.12.7 args: release diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml index 40b4a2de4..a6124050c 100644 --- a/.github/workflows/trigger-docs.yml +++ b/.github/workflows/trigger-docs.yml @@ -39,7 +39,7 @@ jobs: - name: Generate token id: generate_token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: ${{ secrets.TEMPORAL_CICD_APP_ID }} private-key: ${{ secrets.TEMPORAL_CICD_PRIVATE_KEY }} From 2ae37a368a8c9f15c78e2563a8961901af1a826b Mon Sep 17 00:00:00 2001 From: Nasit Sarwar Sony Date: Wed, 3 Jun 2026 17:16:59 -0700 Subject: [PATCH 11/28] Prefix dev server cluster ID with 'dev-server-' (#1059) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related issues Closes #609 ## What changed? Prefix generated dev server cluster IDs with `dev-server-` for better identification. Previously cluster IDs were plain UUIDs with no indication they came from a dev server. ## Checklist **Stability** - [x] Breaking changes are marked with 💥 in the PR title and release notes **Design** - [x] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) **Tests** - [x] Added functional test(s) — existing tests cover this change ## Manual tests **Setup** ``` temporal server start-dev --headless ``` **Happy path** ``` $ temporal operator cluster describe ClusterId dev-server- ``` **Error case** N/A Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/temporalcli/commands.server.go b/internal/temporalcli/commands.server.go index bd6bed6af..bd45f911a 100644 --- a/internal/temporalcli/commands.server.go +++ b/internal/temporalcli/commands.server.go @@ -196,7 +196,7 @@ func persistentClusterID() string { file := defaultDeprecatedEnvConfigFile("temporalio", "version-info") if file == "" { // No file, can do nothing here - return uuid.NewString() + return "dev-server-" + uuid.NewString() } // Try to get existing first env, _ := readDeprecatedEnvConfigFile(file) @@ -204,7 +204,7 @@ func persistentClusterID() string { return id } // Create and try to write - id := uuid.NewString() + id := "dev-server-" + uuid.NewString() _ = writeDeprecatedEnvConfigFile(file, map[string]map[string]string{"default": {"cluster-id": id}}) return id } From e20ca61959d938196e0c5bc7a44da13905e9025d Mon Sep 17 00:00:00 2001 From: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:38:12 -0500 Subject: [PATCH 12/28] feat: auto-generate deprecation warnings from YAML config (#941) ## Related issues Closes #673 ## What changed? Auto-generate deprecation warnings from a `deprecated: true` YAML flag instead of manually writing CAUTION boxes in command descriptions. Deprecation warnings are also printed to stderr at runtime so users see them when invoking deprecated commands, without breaking JSON output. ### Before Deprecation required manually adding a CAUTION box to the description and `(Deprecated)` to the summary: ```yaml - name: temporal task-queue get-build-id-reachability summary: Show Build ID availability (Deprecated) description: | ` `` +-----------------------------------------------------------------------------+ | CAUTION: This command is deprecated and will be removed in a later release. | +-----------------------------------------------------------------------------+ ` `` Show if a given Build ID can be used for new, existing, or closed Workflows... ``` No runtime warning was shown when invoking the command. ### After Set `deprecated: true` and optionally `deprecation-message`. The CAUTION box, `(Deprecated)` summary suffix, and stderr runtime warning are all auto-generated: ```yaml - name: temporal task-queue get-build-id-reachability deprecated: true description: | Show if a given Build ID can be used for new, existing, or closed Workflows... - name: temporal task-queue versioning deprecated: true deprecation-message: This API has been deprecated by Worker Deployment. description: | Provides commands to add, list, remove, or replace... ``` Runtime stderr output: ``` $ temporal task-queue get-build-id-reachability --task-queue foo --build-id bar warning: this command is deprecated and will be removed in a later release ... ``` JSON output is not affected (warning goes to stderr only): ``` $ temporal task-queue get-build-ids -o json --task-queue foo 2>/dev/null [{"buildIds":["1.0"],"defaultForSet":"1.0","isDefaultSet":true}] ``` ## Checklist **Stability** - [x] Breaking changes are marked with :boom: in the PR title and release notes - [x] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [x] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) **Help text** (see style guide at the top of `commands.yaml`) - [x] Summaries use sentence case and have no trailing period - [x] Long descriptions end with a period and include at least one example invocation **Behavior** - [x] Results go to stdout; errors and warnings go to stderr - [x] Error messages are lowercase with no trailing punctuation **Tests** - [x] Added functional test(s) (`SharedServerSuite`) - [x] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** ``` temporal server start-dev --headless ``` **Happy path -- stderr warning on deprecated command** ``` $ temporal task-queue get-build-ids \ --task-queue YourTaskQueue warning: this command is deprecated and will be removed in a later release ... ``` **Happy path -- JSON output not polluted** ``` $ temporal task-queue get-build-ids \ -o json \ --task-queue YourTaskQueue 2>/dev/null [...] ``` **Happy path -- help text shows CAUTION box** ``` $ temporal task-queue get-build-id-reachability --help +-----------------------------------------------------------------------------+ | CAUTION: This command is deprecated and will be removed in a later release. | +-----------------------------------------------------------------------------+ Show if a given Build ID can be used for new, existing, or closed Workflows... ``` **Happy path -- custom deprecation message** ``` $ temporal task-queue versioning --help +-------------------------------------------------------------+ | CAUTION: This API has been deprecated by Worker Deployment. | +-------------------------------------------------------------+ ... ``` --- internal/commandsgen/code.go | 18 ++- internal/commandsgen/parse.go | 28 +++- internal/commandsgen/parse_test.go | 48 ++++++ internal/temporalcli/commands.gen.go | 66 +++++--- internal/temporalcli/commands.go | 5 + .../commands.taskqueue_build_id_test.go | 42 +++++ internal/temporalcli/commands.yaml | 143 +++++------------- 7 files changed, 222 insertions(+), 128 deletions(-) create mode 100644 internal/commandsgen/parse_test.go diff --git a/internal/commandsgen/code.go b/internal/commandsgen/code.go index fb500751b..84cff3bbc 100644 --- a/internal/commandsgen/code.go +++ b/internal/commandsgen/code.go @@ -233,12 +233,22 @@ func (c *Command) writeCode(w *codeWriter) error { } else { w.writeLinef("s.Command.Args = %v.NoArgs", w.importCobra()) } - if c.IgnoreMissingEnv { + if c.IgnoreMissingEnv || c.Deprecated { w.writeLinef("s.Command.Annotations = make(map[string]string)") - w.writeLinef("s.Command.Annotations[\"ignoresMissingEnv\"] = \"true\"") + if c.IgnoreMissingEnv { + w.writeLinef("s.Command.Annotations[\"ignoresMissingEnv\"] = \"true\"") + } } - if c.Deprecated != "" { - w.writeLinef("s.Command.Deprecated = %q", c.Deprecated) + // Note: We intentionally don't set s.Command.Deprecated here because Cobra + // prints deprecation warnings to stdout, which breaks JSON output. Instead, + // the deprecation warning is prepended to the description/help text and + // printed to stderr via the annotation below. + if c.Deprecated { + msg := c.DeprecationMessage + if msg == "" { + msg = defaultDeprecationMessage + } + w.writeLinef("s.Command.Annotations[\"deprecationWarning\"] = %q", msg) } // Add subcommands for _, subCommand := range subCommands { diff --git a/internal/commandsgen/parse.go b/internal/commandsgen/parse.go index 2083a033d..831b86902 100644 --- a/internal/commandsgen/parse.go +++ b/internal/commandsgen/parse.go @@ -39,8 +39,9 @@ type ( Description string `yaml:"description"` DescriptionPlain string DescriptionHighlighted string - Deprecated string `yaml:"deprecated"` - HasInit bool `yaml:"has-init"` + Deprecated bool `yaml:"deprecated"` + DeprecationMessage string `yaml:"deprecation-message"` + HasInit bool `yaml:"has-init"` ExactArgs int `yaml:"exact-args"` MaximumArgs int `yaml:"maximum-args"` IgnoreMissingEnv bool `yaml:"ignores-missing-env"` @@ -137,6 +138,22 @@ var markdownInlineCodeRegex = regexp.MustCompile("`([^`]+)`") const ansiReset = "\033[0m" const ansiBold = "\033[1m" +const defaultDeprecationMessage = "This command is deprecated and will be removed in a later release." + +// generateDeprecationBox creates a formatted CAUTION box for deprecated commands. +// If message is empty, uses the default deprecation message. +func generateDeprecationBox(message string) string { + if message == "" { + message = defaultDeprecationMessage + } + content := "CAUTION: " + message + // Calculate box width (content + 2 spaces padding + 2 border chars) + boxWidth := len(content) + 4 + border := "+" + strings.Repeat("-", boxWidth-2) + "+" + middle := "| " + content + " |" + return "```\n" + border + "\n" + middle + "\n" + border + "\n```\n\n" +} + func (o OptionSets) processSection() error { if o.Name == "" { return fmt.Errorf("missing option set name") @@ -172,6 +189,13 @@ func (c *Command) processSection() error { return fmt.Errorf("missing description for command: %s", c.FullName) } + // Auto-handle deprecation: prepend warning box to description and append + // "(Deprecated)" to summary. + if c.Deprecated { + c.Description = generateDeprecationBox(c.DeprecationMessage) + c.Description + c.Summary += " (Deprecated)" + } + if len(c.NamePath) == 2 { if c.Docs.Keywords == nil { return fmt.Errorf("missing keywords for root command: %s", c.FullName) diff --git a/internal/commandsgen/parse_test.go b/internal/commandsgen/parse_test.go new file mode 100644 index 000000000..eb77a83e1 --- /dev/null +++ b/internal/commandsgen/parse_test.go @@ -0,0 +1,48 @@ +package commandsgen + +import "testing" + +func TestGenerateDeprecationBox(t *testing.T) { + tests := []struct { + name string + message string + expected string + }{ + { + name: "default message when empty", + message: "", + expected: "```\n" + + "+-----------------------------------------------------------------------------+\n" + + "| CAUTION: This command is deprecated and will be removed in a later release. |\n" + + "+-----------------------------------------------------------------------------+\n" + + "```\n\n", + }, + { + name: "custom message", + message: "Use the new API instead.", + expected: "```\n" + + "+-----------------------------------+\n" + + "| CAUTION: Use the new API instead. |\n" + + "+-----------------------------------+\n" + + "```\n\n", + }, + { + name: "short custom message", + message: "Removed.", + expected: "```\n" + + "+-------------------+\n" + + "| CAUTION: Removed. |\n" + + "+-------------------+\n" + + "```\n\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := generateDeprecationBox(tt.message) + if got != tt.expected { + t.Errorf("generateDeprecationBox(%q) =\n%q\nwant:\n%q", tt.message, got, tt.expected) + } + }) + } +} diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 5186d70e3..4985840e1 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -2678,6 +2678,8 @@ func NewTemporalTaskQueueGetBuildIdReachabilityCommand(cctx *CommandContext, par s.Command.Long = "```\n+-----------------------------------------------------------------------------+\n| CAUTION: This command is deprecated and will be removed in a later release. |\n+-----------------------------------------------------------------------------+\n```\n\nShow if a given Build ID can be used for new, existing, or closed Workflows\nin Namespaces that support Worker versioning:\n\n```\ntemporal task-queue get-build-id-reachability \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\n```\n\nYou can specify the `--build-id` and `--task-queue` flags multiple times. If\n`--task-queue` is omitted, the command checks Build ID reachability against\nall Task Queues." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This command is deprecated and will be removed in a later release." s.Command.Flags().StringArrayVar(&s.BuildId, "build-id", nil, "One or more Build ID strings. Can be passed multiple times.") s.ReachabilityType = cliext.NewFlagStringEnum([]string{"open", "closed", "existing"}, "existing") s.Command.Flags().Var(&s.ReachabilityType, "reachability-type", "Reachability filter. `open`: reachable by one or more open workflows. `closed`: reachable by one or more closed workflows. `existing`: reachable by either. New Workflow Executions reachable by a Build ID are always reported. Accepted values: open, closed, existing.") @@ -2709,6 +2711,8 @@ func NewTemporalTaskQueueGetBuildIdsCommand(cctx *CommandContext, parent *Tempor s.Command.Long = "```\n+-----------------------------------------------------------------------------+\n| CAUTION: This command is deprecated and will be removed in a later release. |\n+-----------------------------------------------------------------------------+\n```\n\nFetch sets of compatible Build IDs for specified Task Queues and display their\ninformation:\n\n```\ntemporal task-queue get-build-ids \\\n --task-queue YourTaskQueue\n```\n\nThis command is limited to Namespaces that support Worker versioning." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This command is deprecated and will be removed in a later release." s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "task-queue") s.Command.Flags().IntVar(&s.MaxSets, "max-sets", 0, "Max return count. Use 1 for default major version. Use 0 for all sets.") @@ -2764,6 +2768,8 @@ func NewTemporalTaskQueueUpdateBuildIdsCommand(cctx *CommandContext, parent *Tem s.Command.Long = "```\n+-----------------------------------------------------------------------------+\n| CAUTION: This command is deprecated and will be removed in a later release. |\n+-----------------------------------------------------------------------------+\n```\n\nAdd or change a Task Queue's compatible Build IDs for Namespaces using Worker\nversioning:\n\n```\ntemporal task-queue update-build-ids [subcommands] [options] \\\n --task-queue YourTaskQueue\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This command is deprecated and will be removed in a later release." s.Command.AddCommand(&NewTemporalTaskQueueUpdateBuildIdsAddNewCompatibleCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueUpdateBuildIdsAddNewDefaultCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueUpdateBuildIdsPromoteIdInSetCommand(cctx, &s).Command) @@ -2826,6 +2832,8 @@ func NewTemporalTaskQueueUpdateBuildIdsAddNewDefaultCommand(cctx *CommandContext s.Command.Long = "```\n+-----------------------------------------------------------------------------+\n| CAUTION: This command is deprecated and will be removed in a later release. |\n+-----------------------------------------------------------------------------+\n```\n\nCreate a new Task Queue Build ID set, add a Build ID to it, and make it the\noverall Task Queue default. The new set will be incompatible with previous\nsets and versions.\n\n```\ntemporal task-queue update-build-ids add-new-default \\\n --task-queue YourTaskQueue \\\n --build-id \"YourNewBuildId\"\n```\n\n```\n+------------------------------------------------------------------------+\n| NOTICE: This command is limited to Namespaces that support Worker |\n| versioning. Worker versioning is experimental. Versioning commands are |\n| subject to change. |\n+------------------------------------------------------------------------+\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This command is deprecated and will be removed in a later release." s.Command.Flags().StringVar(&s.BuildId, "build-id", "", "Build ID to be added. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "build-id") s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") @@ -2857,6 +2865,8 @@ func NewTemporalTaskQueueUpdateBuildIdsPromoteIdInSetCommand(cctx *CommandContex s.Command.Long = "```\n+-----------------------------------------------------------------------------+\n| CAUTION: This command is deprecated and will be removed in a later release. |\n+-----------------------------------------------------------------------------+\n```\n\nEstablish an existing Build ID as the default in its Task Queue set. New tasks\ncompatible with this set will now be dispatched to this ID:\n\n```\ntemporal task-queue update-build-ids promote-id-in-set \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\n```\n\n```\n+------------------------------------------------------------------------+\n| NOTICE: This command is limited to Namespaces that support Worker |\n| versioning. Worker versioning is experimental. Versioning commands are |\n| subject to change. |\n+------------------------------------------------------------------------+\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This command is deprecated and will be removed in a later release." s.Command.Flags().StringVar(&s.BuildId, "build-id", "", "Build ID to set as default. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "build-id") s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") @@ -2888,6 +2898,8 @@ func NewTemporalTaskQueueUpdateBuildIdsPromoteSetCommand(cctx *CommandContext, p s.Command.Long = "```\n+-----------------------------------------------------------------------------+\n| CAUTION: This command is deprecated and will be removed in a later release. |\n+-----------------------------------------------------------------------------+\n```\n\nPromote a Build ID set to be the default on a Task Queue. Identify the set by\nproviding a Build ID within it. If the set is already the default, this\ncommand has no effect:\n\n```\ntemporal task-queue update-build-ids promote-set \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\n```\n\n```\n+------------------------------------------------------------------------+\n| NOTICE: This command is limited to Namespaces that support Worker |\n| versioning. Worker versioning is experimental. Versioning commands are |\n| subject to change. |\n+------------------------------------------------------------------------+\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This command is deprecated and will be removed in a later release." s.Command.Flags().StringVar(&s.BuildId, "build-id", "", "Build ID within the promoted set. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "build-id") s.Command.Flags().StringVarP(&s.TaskQueue, "task-queue", "t", "", "Task Queue name. Required.") @@ -2912,11 +2924,13 @@ func NewTemporalTaskQueueVersioningCommand(cctx *CommandContext, parent *Tempora s.Command.Use = "versioning" s.Command.Short = "Manage Task Queue Build ID handling (Deprecated)" if hasHighlighting { - s.Command.Long = "\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m\n\nProvides commands to add, list, remove, or replace Worker Build ID assignment\nand redirect rules associated with Task Queues:\n\n\x1b[1mtemporal task-queue versioning [subcommands] [options] \\\n --task-queue YourTaskQueue\x1b[0m\n\nTask Queues support the following versioning rules and policies:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID." + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nProvides commands to add, list, remove, or replace Worker Build ID assignment\nand redirect rules associated with Task Queues:\n\n\x1b[1mtemporal task-queue versioning [subcommands] [options] \\\n --task-queue YourTaskQueue\x1b[0m\n\nTask Queues support the following versioning rules and policies:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID." } else { - s.Command.Long = "```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```\n\nProvides commands to add, list, remove, or replace Worker Build ID assignment\nand redirect rules associated with Task Queues:\n\n```\ntemporal task-queue versioning [subcommands] [options] \\\n --task-queue YourTaskQueue\n```\n\nTask Queues support the following versioning rules and policies:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID." + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nProvides commands to add, list, remove, or replace Worker Build ID assignment\nand redirect rules associated with Task Queues:\n\n```\ntemporal task-queue versioning [subcommands] [options] \\\n --task-queue YourTaskQueue\n```\n\nTask Queues support the following versioning rules and policies:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.AddCommand(&NewTemporalTaskQueueVersioningAddRedirectRuleCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueVersioningCommitBuildIdCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueVersioningDeleteAssignmentRuleCommand(cctx, &s).Command) @@ -2945,11 +2959,13 @@ func NewTemporalTaskQueueVersioningAddRedirectRuleCommand(cctx *CommandContext, s.Command.Use = "add-redirect-rule [flags]" s.Command.Short = "Add Task Queue redirect rules (Deprecated)" if hasHighlighting { - s.Command.Long = "Add a new redirect rule for a given Task Queue. You may add at most one\nredirect rule for each distinct source build ID:\n\n\x1b[1mtemporal task-queue versioning add-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourSourceBuildID\" \\\n --target-build-id \"YourTargetBuildID\"\x1b[0m\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nAdd a new redirect rule for a given Task Queue. You may add at most one\nredirect rule for each distinct source build ID:\n\n\x1b[1mtemporal task-queue versioning add-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourSourceBuildID\" \\\n --target-build-id \"YourTargetBuildID\"\x1b[0m" } else { - s.Command.Long = "Add a new redirect rule for a given Task Queue. You may add at most one\nredirect rule for each distinct source build ID:\n\n```\ntemporal task-queue versioning add-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourSourceBuildID\" \\\n --target-build-id \"YourTargetBuildID\"\n```\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nAdd a new redirect rule for a given Task Queue. You may add at most one\nredirect rule for each distinct source build ID:\n\n```\ntemporal task-queue versioning add-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourSourceBuildID\" \\\n --target-build-id \"YourTargetBuildID\"\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().StringVar(&s.SourceBuildId, "source-build-id", "", "Source build ID. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "source-build-id") s.Command.Flags().StringVar(&s.TargetBuildId, "target-build-id", "", "Target build ID. Required.") @@ -2978,11 +2994,13 @@ func NewTemporalTaskQueueVersioningCommitBuildIdCommand(cctx *CommandContext, pa s.Command.Use = "commit-build-id [flags]" s.Command.Short = "Complete Build ID rollout (Deprecated)" if hasHighlighting { - s.Command.Long = "Complete a Build ID's rollout and clean up unnecessary rules that might have\nbeen created during a gradual rollout:\n\n\x1b[1mtemporal task-queue versioning commit-build-id \\\n --task-queue YourTaskQueue\n --build-id \"YourBuildId\"\x1b[0m\n\nThis command automatically applies the following atomic changes:\n\n- Adds an unconditional assignment rule for the target Build ID at the\n end of the list.\n- Removes all previously added assignment rules to the given target\n Build ID.\n- Removes any unconditional assignment rules for other Build IDs.\n\nRejects requests when there have been no recent pollers for this Build ID.\nThis prevents committing invalid Build IDs. Use the \x1b[1m--force\x1b[0m option to\noverride this validation.\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nComplete a Build ID's rollout and clean up unnecessary rules that might have\nbeen created during a gradual rollout:\n\n\x1b[1mtemporal task-queue versioning commit-build-id \\\n --task-queue YourTaskQueue\n --build-id \"YourBuildId\"\x1b[0m\n\nThis command automatically applies the following atomic changes:\n\n- Adds an unconditional assignment rule for the target Build ID at the\n end of the list.\n- Removes all previously added assignment rules to the given target\n Build ID.\n- Removes any unconditional assignment rules for other Build IDs.\n\nRejects requests when there have been no recent pollers for this Build ID.\nThis prevents committing invalid Build IDs. Use the \x1b[1m--force\x1b[0m option to\noverride this validation." } else { - s.Command.Long = "Complete a Build ID's rollout and clean up unnecessary rules that might have\nbeen created during a gradual rollout:\n\n```\ntemporal task-queue versioning commit-build-id \\\n --task-queue YourTaskQueue\n --build-id \"YourBuildId\"\n```\n\nThis command automatically applies the following atomic changes:\n\n- Adds an unconditional assignment rule for the target Build ID at the\n end of the list.\n- Removes all previously added assignment rules to the given target\n Build ID.\n- Removes any unconditional assignment rules for other Build IDs.\n\nRejects requests when there have been no recent pollers for this Build ID.\nThis prevents committing invalid Build IDs. Use the `--force` option to\noverride this validation.\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nComplete a Build ID's rollout and clean up unnecessary rules that might have\nbeen created during a gradual rollout:\n\n```\ntemporal task-queue versioning commit-build-id \\\n --task-queue YourTaskQueue\n --build-id \"YourBuildId\"\n```\n\nThis command automatically applies the following atomic changes:\n\n- Adds an unconditional assignment rule for the target Build ID at the\n end of the list.\n- Removes all previously added assignment rules to the given target\n Build ID.\n- Removes any unconditional assignment rules for other Build IDs.\n\nRejects requests when there have been no recent pollers for this Build ID.\nThis prevents committing invalid Build IDs. Use the `--force` option to\noverride this validation." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().StringVar(&s.BuildId, "build-id", "", "Target build ID. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "build-id") s.Command.Flags().BoolVar(&s.Force, "force", false, "Bypass recent-poller validation.") @@ -3010,11 +3028,13 @@ func NewTemporalTaskQueueVersioningDeleteAssignmentRuleCommand(cctx *CommandCont s.Command.Use = "delete-assignment-rule [flags]" s.Command.Short = "Removes a Task Queue assignment rule (Deprecated)" if hasHighlighting { - s.Command.Long = "Deletes a rule identified by its index in the Task Queue's list of assignment\nrules.\n\n\x1b[1mtemporal task-queue versioning delete-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index YourIntegerRuleIndex\x1b[0m\n\nBy default, the Task Queue must retain one unconditional rule, such as \"no\nhint filter\" or \"percentage\". Otherwise, the delete operation is rejected.\nUse the \x1b[1m--force\x1b[0m option to override this validation.\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nDeletes a rule identified by its index in the Task Queue's list of assignment\nrules.\n\n\x1b[1mtemporal task-queue versioning delete-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index YourIntegerRuleIndex\x1b[0m\n\nBy default, the Task Queue must retain one unconditional rule, such as \"no\nhint filter\" or \"percentage\". Otherwise, the delete operation is rejected.\nUse the \x1b[1m--force\x1b[0m option to override this validation." } else { - s.Command.Long = "Deletes a rule identified by its index in the Task Queue's list of assignment\nrules.\n\n```\ntemporal task-queue versioning delete-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index YourIntegerRuleIndex\n```\n\nBy default, the Task Queue must retain one unconditional rule, such as \"no\nhint filter\" or \"percentage\". Otherwise, the delete operation is rejected.\nUse the `--force` option to override this validation.\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nDeletes a rule identified by its index in the Task Queue's list of assignment\nrules.\n\n```\ntemporal task-queue versioning delete-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index YourIntegerRuleIndex\n```\n\nBy default, the Task Queue must retain one unconditional rule, such as \"no\nhint filter\" or \"percentage\". Otherwise, the delete operation is rejected.\nUse the `--force` option to override this validation." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().IntVarP(&s.RuleIndex, "rule-index", "i", 0, "Position of the assignment rule to be replaced. Requests for invalid indices will fail. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "rule-index") s.Command.Flags().BoolVar(&s.Force, "force", false, "Bypass one-unconditional-rule validation.") @@ -3041,11 +3061,13 @@ func NewTemporalTaskQueueVersioningDeleteRedirectRuleCommand(cctx *CommandContex s.Command.Use = "delete-redirect-rule [flags]" s.Command.Short = "Removes Build-ID routing rule (Deprecated)" if hasHighlighting { - s.Command.Long = "Deletes the routing rule for the given source Build ID.\n\n\x1b[1mtemporal task-queue versioning delete-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourBuildId\"\x1b[0m\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nDeletes the routing rule for the given source Build ID.\n\n\x1b[1mtemporal task-queue versioning delete-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourBuildId\"\x1b[0m" } else { - s.Command.Long = "Deletes the routing rule for the given source Build ID.\n\n```\ntemporal task-queue versioning delete-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourBuildId\"\n```\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nDeletes the routing rule for the given source Build ID.\n\n```\ntemporal task-queue versioning delete-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id \"YourBuildId\"\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().StringVar(&s.SourceBuildId, "source-build-id", "", "Source Build ID. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "source-build-id") s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm.") @@ -3069,11 +3091,13 @@ func NewTemporalTaskQueueVersioningGetRulesCommand(cctx *CommandContext, parent s.Command.Use = "get-rules [flags]" s.Command.Short = "Fetch Worker Build ID assignments and redirect rules (Deprecated)" if hasHighlighting { - s.Command.Long = "Retrieve all the Worker Build ID assignments and redirect rules associated\nwith a Task Queue:\n\n\x1b[1mtemporal task-queue versioning get-rules \\\n --task-queue YourTaskQueue\x1b[0m\n\nTask Queues support the following versioning rules:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID.\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nRetrieve all the Worker Build ID assignments and redirect rules associated\nwith a Task Queue:\n\n\x1b[1mtemporal task-queue versioning get-rules \\\n --task-queue YourTaskQueue\x1b[0m\n\nTask Queues support the following versioning rules:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID." } else { - s.Command.Long = "Retrieve all the Worker Build ID assignments and redirect rules associated\nwith a Task Queue:\n\n```\ntemporal task-queue versioning get-rules \\\n --task-queue YourTaskQueue\n```\n\nTask Queues support the following versioning rules:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID.\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nRetrieve all the Worker Build ID assignments and redirect rules associated\nwith a Task Queue:\n\n```\ntemporal task-queue versioning get-rules \\\n --task-queue YourTaskQueue\n```\n\nTask Queues support the following versioning rules:\n\n- Assignment Rules: manage how new executions are assigned to run on specific\n Worker Build IDs. Each Task Queue stores a list of ordered Assignment Rules,\n which are evaluated from first to last. Assignment Rules also allow for\n gradual rollout of new Build IDs by setting ramp percentage.\n- Redirect Rules: automatically assign work for a source Build ID to a target\n Build ID. You may add at most one redirect rule for each source Build ID.\n Redirect rules require that a target Build ID is fully compatible with\n the source Build ID." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -3098,11 +3122,13 @@ func NewTemporalTaskQueueVersioningInsertAssignmentRuleCommand(cctx *CommandCont s.Command.Use = "insert-assignment-rule [flags]" s.Command.Short = "Add an assignment rule at a index (Deprecated)" if hasHighlighting { - s.Command.Long = "Inserts a new assignment rule for this Task Queue. Rules are evaluated in\norder, starting from index 0. The first applicable rule is applied, and the\nrest ignored:\n\n\x1b[1mtemporal task-queue versioning insert-assignment-rule \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\x1b[0m\n\nIf you do not specify a \x1b[1m--rule-index\x1b[0m, this command inserts at index 0.\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nInserts a new assignment rule for this Task Queue. Rules are evaluated in\norder, starting from index 0. The first applicable rule is applied, and the\nrest ignored:\n\n\x1b[1mtemporal task-queue versioning insert-assignment-rule \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\x1b[0m\n\nIf you do not specify a \x1b[1m--rule-index\x1b[0m, this command inserts at index 0." } else { - s.Command.Long = "Inserts a new assignment rule for this Task Queue. Rules are evaluated in\norder, starting from index 0. The first applicable rule is applied, and the\nrest ignored:\n\n```\ntemporal task-queue versioning insert-assignment-rule \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\n```\n\nIf you do not specify a `--rule-index`, this command inserts at index 0.\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nInserts a new assignment rule for this Task Queue. Rules are evaluated in\norder, starting from index 0. The first applicable rule is applied, and the\nrest ignored:\n\n```\ntemporal task-queue versioning insert-assignment-rule \\\n --task-queue YourTaskQueue \\\n --build-id \"YourBuildId\"\n```\n\nIf you do not specify a `--rule-index`, this command inserts at index 0." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().StringVar(&s.BuildId, "build-id", "", "Target Build ID. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "build-id") s.Command.Flags().IntVarP(&s.RuleIndex, "rule-index", "i", 0, "Insertion position. Ranges from 0 (insert at start) to count (append). Any number greater than the count is treated as \"append\".") @@ -3133,11 +3159,13 @@ func NewTemporalTaskQueueVersioningReplaceAssignmentRuleCommand(cctx *CommandCon s.Command.Use = "replace-assignment-rule [flags]" s.Command.Short = "Update assignment rule at index (Deprecated)" if hasHighlighting { - s.Command.Long = "Change an assignment rule for this Task Queue. By default, this enforces one\nunconditional rule (no hint filter or percentage). Otherwise, the operation\nwill be rejected. Set \x1b[1mforce\x1b[0m to true to bypass this validation.\n\n\x1b[1mtemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\"\x1b[0m\n\nTo assign multiple assignment rules to a single Build ID, use\n'insert-assignment-rule'.\n\nTo update the percent:\n\n\x1b[1mtemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\" \\\n --percentage AnIntegerPercent\x1b[0m\n\nPercent may vary between 0 and 100 (default).\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nChange an assignment rule for this Task Queue. By default, this enforces one\nunconditional rule (no hint filter or percentage). Otherwise, the operation\nwill be rejected. Set \x1b[1mforce\x1b[0m to true to bypass this validation.\n\n\x1b[1mtemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\"\x1b[0m\n\nTo assign multiple assignment rules to a single Build ID, use\n'insert-assignment-rule'.\n\nTo update the percent:\n\n\x1b[1mtemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\" \\\n --percentage AnIntegerPercent\x1b[0m\n\nPercent may vary between 0 and 100 (default)." } else { - s.Command.Long = "Change an assignment rule for this Task Queue. By default, this enforces one\nunconditional rule (no hint filter or percentage). Otherwise, the operation\nwill be rejected. Set `force` to true to bypass this validation.\n\n```\ntemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\"\n```\n\nTo assign multiple assignment rules to a single Build ID, use\n'insert-assignment-rule'.\n\nTo update the percent:\n\n```\ntemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\" \\\n --percentage AnIntegerPercent\n```\n\nPercent may vary between 0 and 100 (default).\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nChange an assignment rule for this Task Queue. By default, this enforces one\nunconditional rule (no hint filter or percentage). Otherwise, the operation\nwill be rejected. Set `force` to true to bypass this validation.\n\n```\ntemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\"\n```\n\nTo assign multiple assignment rules to a single Build ID, use\n'insert-assignment-rule'.\n\nTo update the percent:\n\n```\ntemporal task-queue versioning replace-assignment-rule \\\n --task-queue YourTaskQueue \\\n --rule-index AnIntegerIndex \\\n --build-id \"YourBuildId\" \\\n --percentage AnIntegerPercent\n```\n\nPercent may vary between 0 and 100 (default)." } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().StringVar(&s.BuildId, "build-id", "", "Target Build ID. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "build-id") s.Command.Flags().IntVarP(&s.RuleIndex, "rule-index", "i", 0, "Position of the assignment rule to be replaced. Requests for invalid indices will fail. Required.") @@ -3168,11 +3196,13 @@ func NewTemporalTaskQueueVersioningReplaceRedirectRuleCommand(cctx *CommandConte s.Command.Use = "replace-redirect-rule [flags]" s.Command.Short = "Change the target for a Build ID's redirect (Deprecated)" if hasHighlighting { - s.Command.Long = "Updates a Build ID's redirect rule on a Task Queue by replacing its target\nBuild ID:\n\n\x1b[1mtemporal task-queue versioning replace-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id YourSourceBuildId \\\n --target-build-id YourNewTargetBuildId\x1b[0m\n\n\x1b[1m+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\x1b[0m" + s.Command.Long = "\x1b[1m+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\x1b[0m\n\nUpdates a Build ID's redirect rule on a Task Queue by replacing its target\nBuild ID:\n\n\x1b[1mtemporal task-queue versioning replace-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id YourSourceBuildId \\\n --target-build-id YourNewTargetBuildId\x1b[0m" } else { - s.Command.Long = "Updates a Build ID's redirect rule on a Task Queue by replacing its target\nBuild ID:\n\n```\ntemporal task-queue versioning replace-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id YourSourceBuildId \\\n --target-build-id YourNewTargetBuildId\n```\n\n```\n+---------------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+---------------------------------------------------------------------+\n```" + s.Command.Long = "```\n+-------------------------------------------------------------+\n| CAUTION: This API has been deprecated by Worker Deployment. |\n+-------------------------------------------------------------+\n```\n\nUpdates a Build ID's redirect rule on a Task Queue by replacing its target\nBuild ID:\n\n```\ntemporal task-queue versioning replace-redirect-rule \\\n --task-queue YourTaskQueue \\\n --source-build-id YourSourceBuildId \\\n --target-build-id YourNewTargetBuildId\n```" } s.Command.Args = cobra.NoArgs + s.Command.Annotations = make(map[string]string) + s.Command.Annotations["deprecationWarning"] = "This API has been deprecated by Worker Deployment." s.Command.Flags().StringVar(&s.SourceBuildId, "source-build-id", "", "Source Build ID. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "source-build-id") s.Command.Flags().StringVar(&s.TargetBuildId, "target-build-id", "", "Target Build ID. Required.") diff --git a/internal/temporalcli/commands.go b/internal/temporalcli/commands.go index 116de4800..ecb52515d 100644 --- a/internal/temporalcli/commands.go +++ b/internal/temporalcli/commands.go @@ -497,6 +497,11 @@ func (c *TemporalCommand) initCommand(cctx *CommandContext) { } cctx.ActuallyRanCommand = true + // Print deprecation warning to stderr so it doesn't break JSON output + if msg, ok := cmd.Annotations["deprecationWarning"]; ok { + fmt.Fprintf(cctx.Options.Stderr, "warning: %s\n", strings.ToLower(strings.TrimRight(msg, "."))) + } + if cctx.Options.DeprecatedEnvConfig.EnvConfigName != "default" { if _, ok := cctx.DeprecatedEnvConfigValues[cctx.Options.DeprecatedEnvConfig.EnvConfigName]; !ok { if _, ok := cmd.Annotations["ignoresMissingEnv"]; !ok { diff --git a/internal/temporalcli/commands.taskqueue_build_id_test.go b/internal/temporalcli/commands.taskqueue_build_id_test.go index 365bf9e1b..fb137ac28 100644 --- a/internal/temporalcli/commands.taskqueue_build_id_test.go +++ b/internal/temporalcli/commands.taskqueue_build_id_test.go @@ -180,3 +180,45 @@ func (s *SharedServerSuite) TestTaskQueue_BuildId() { }, }, jsonReachOut) } + +func (s *SharedServerSuite) TestDeprecatedCommand_WarnsOnStderr() { + taskQueue := uuid.NewString() + + // Run a deprecated command + res := s.Execute( + "task-queue", "update-build-ids", "add-new-default", + "--address", s.Address(), + "--task-queue", taskQueue, + "--build-id", "1.0", + ) + s.NoError(res.Err) + + // Deprecation warning must appear on stderr + stderr := res.Stderr.String() + s.Contains(stderr, "warning:") + s.Contains(stderr, "deprecated") + + // Warning must not appear on stdout (would break JSON consumers) + s.NotContains(res.Stdout.String(), "warning:") +} + +func (s *SharedServerSuite) TestDeprecatedCommand_WarnsOnStderr_JSON() { + taskQueue := uuid.NewString() + + // Run a deprecated command with JSON output + res := s.Execute( + "task-queue", "get-build-ids", + "-o", "json", + "--address", s.Address(), + "--task-queue", taskQueue, + ) + s.NoError(res.Err) + + // Deprecation warning must appear on stderr even with JSON output + stderr := res.Stderr.String() + s.Contains(stderr, "warning:") + s.Contains(stderr, "deprecated") + + // Stdout must be valid JSON, not polluted by warnings + s.NotContains(res.Stdout.String(), "warning:") +} diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 1d383d179..7d67822a2 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -2870,14 +2870,9 @@ commands: configuration for the task queue. - name: temporal task-queue get-build-id-reachability - summary: Show Build ID availability (Deprecated) + summary: Show Build ID availability + deprecated: true description: | - ``` - +-----------------------------------------------------------------------------+ - | CAUTION: This command is deprecated and will be removed in a later release. | - +-----------------------------------------------------------------------------+ - ``` - Show if a given Build ID can be used for new, existing, or closed Workflows in Namespaces that support Worker versioning: @@ -2917,14 +2912,9 @@ commands: Can be passed multiple times. - name: temporal task-queue get-build-ids - summary: Fetch Build ID versions (Deprecated) + summary: Fetch Build ID versions + deprecated: true description: | - ``` - +-----------------------------------------------------------------------------+ - | CAUTION: This command is deprecated and will be removed in a later release. | - +-----------------------------------------------------------------------------+ - ``` - Fetch sets of compatible Build IDs for specified Task Queues and display their information: @@ -2966,14 +2956,9 @@ commands: short: t - name: temporal task-queue update-build-ids - summary: Manage Build IDs (Deprecated) + summary: Manage Build IDs + deprecated: true description: | - ``` - +-----------------------------------------------------------------------------+ - | CAUTION: This command is deprecated and will be removed in a later release. | - +-----------------------------------------------------------------------------+ - ``` - Add or change a Task Queue's compatible Build IDs for Namespaces using Worker versioning: @@ -3018,14 +3003,9 @@ commands: description: Set the expanded Build ID set as the Task Queue default. - name: temporal task-queue update-build-ids add-new-default - summary: Set new default Build ID set (Deprecated) + summary: Set new default Build ID set + deprecated: true description: | - ``` - +-----------------------------------------------------------------------------+ - | CAUTION: This command is deprecated and will be removed in a later release. | - +-----------------------------------------------------------------------------+ - ``` - Create a new Task Queue Build ID set, add a Build ID to it, and make it the overall Task Queue default. The new set will be incompatible with previous sets and versions. @@ -3055,14 +3035,9 @@ commands: short: t - name: temporal task-queue update-build-ids promote-id-in-set - summary: Set Build ID as set default (Deprecated) + summary: Set Build ID as set default + deprecated: true description: | - ``` - +-----------------------------------------------------------------------------+ - | CAUTION: This command is deprecated and will be removed in a later release. | - +-----------------------------------------------------------------------------+ - ``` - Establish an existing Build ID as the default in its Task Queue set. New tasks compatible with this set will now be dispatched to this ID: @@ -3091,14 +3066,9 @@ commands: short: t - name: temporal task-queue update-build-ids promote-set - summary: Promote Build ID set (Deprecated) + summary: Promote Build ID set + deprecated: true description: | - ``` - +-----------------------------------------------------------------------------+ - | CAUTION: This command is deprecated and will be removed in a later release. | - +-----------------------------------------------------------------------------+ - ``` - Promote a Build ID set to be the default on a Task Queue. Identify the set by providing a Build ID within it. If the set is already the default, this command has no effect: @@ -3128,14 +3098,10 @@ commands: short: t - name: temporal task-queue versioning - summary: Manage Task Queue Build ID handling (Deprecated) + summary: Manage Task Queue Build ID handling + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` - Provides commands to add, list, remove, or replace Worker Build ID assignment and redirect rules associated with Task Queues: @@ -3162,7 +3128,9 @@ commands: required: true - name: temporal task-queue versioning add-redirect-rule - summary: Add Task Queue redirect rules (Deprecated) + summary: Add Task Queue redirect rules + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Add a new redirect rule for a given Task Queue. You may add at most one redirect rule for each distinct source build ID: @@ -3173,12 +3141,6 @@ commands: --source-build-id "YourSourceBuildID" \ --target-build-id "YourTargetBuildID" ``` - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: source-build-id type: string @@ -3194,7 +3156,9 @@ commands: description: Don't prompt to confirm. - name: temporal task-queue versioning commit-build-id - summary: Complete Build ID rollout (Deprecated) + summary: Complete Build ID rollout + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Complete a Build ID's rollout and clean up unnecessary rules that might have been created during a gradual rollout: @@ -3216,12 +3180,6 @@ commands: Rejects requests when there have been no recent pollers for this Build ID. This prevents committing invalid Build IDs. Use the `--force` option to override this validation. - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: build-id type: string @@ -3236,7 +3194,9 @@ commands: description: Don't prompt to confirm. - name: temporal task-queue versioning delete-assignment-rule - summary: Removes a Task Queue assignment rule (Deprecated) + summary: Removes a Task Queue assignment rule + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Deletes a rule identified by its index in the Task Queue's list of assignment rules. @@ -3250,12 +3210,6 @@ commands: By default, the Task Queue must retain one unconditional rule, such as "no hint filter" or "percentage". Otherwise, the delete operation is rejected. Use the `--force` option to override this validation. - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: rule-index type: int @@ -3273,7 +3227,9 @@ commands: description: Don't prompt to confirm. - name: temporal task-queue versioning delete-redirect-rule - summary: Removes Build-ID routing rule (Deprecated) + summary: Removes Build-ID routing rule + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Deletes the routing rule for the given source Build ID. @@ -3282,12 +3238,6 @@ commands: --task-queue YourTaskQueue \ --source-build-id "YourBuildId" ``` - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: source-build-id type: string @@ -3299,7 +3249,9 @@ commands: description: Don't prompt to confirm. - name: temporal task-queue versioning get-rules - summary: Fetch Worker Build ID assignments and redirect rules (Deprecated) + summary: Fetch Worker Build ID assignments and redirect rules + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Retrieve all the Worker Build ID assignments and redirect rules associated with a Task Queue: @@ -3319,14 +3271,11 @@ commands: Build ID. You may add at most one redirect rule for each source Build ID. Redirect rules require that a target Build ID is fully compatible with the source Build ID. - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` - name: temporal task-queue versioning insert-assignment-rule - summary: Add an assignment rule at a index (Deprecated) + summary: Add an assignment rule at a index + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Inserts a new assignment rule for this Task Queue. Rules are evaluated in order, starting from index 0. The first applicable rule is applied, and the @@ -3339,12 +3288,6 @@ commands: ``` If you do not specify a `--rule-index`, this command inserts at index 0. - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: build-id type: string @@ -3369,7 +3312,9 @@ commands: description: Don't prompt to confirm. - name: temporal task-queue versioning replace-assignment-rule - summary: Update assignment rule at index (Deprecated) + summary: Update assignment rule at index + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Change an assignment rule for this Task Queue. By default, this enforces one unconditional rule (no hint filter or percentage). Otherwise, the operation @@ -3396,12 +3341,6 @@ commands: ``` Percent may vary between 0 and 100 (default). - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: build-id type: string @@ -3428,7 +3367,9 @@ commands: description: Bypass the validation that one unconditional rule remains. - name: temporal task-queue versioning replace-redirect-rule - summary: Change the target for a Build ID's redirect (Deprecated) + summary: Change the target for a Build ID's redirect + deprecated: true + deprecation-message: This API has been deprecated by Worker Deployment. description: | Updates a Build ID's redirect rule on a Task Queue by replacing its target Build ID: @@ -3439,12 +3380,6 @@ commands: --source-build-id YourSourceBuildId \ --target-build-id YourNewTargetBuildId ``` - - ``` - +---------------------------------------------------------------------+ - | CAUTION: This API has been deprecated by Worker Deployment. | - +---------------------------------------------------------------------+ - ``` options: - name: source-build-id type: string From 67af3a2b4f0dc452a6c37fe28745b1c1876415ca Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Fri, 5 Jun 2026 09:27:44 -0700 Subject: [PATCH 13/28] Add support for Standalone Nexus Operations (#1046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## NOTE: REQUIRES OSS SERVER VERSION v1.32.0 ## What changed? Add support for stand alone nexus operations to the CLI. ## Checklist **Stability** - [ ] Breaking changes are marked with 💥 in the PR title and release notes - [ ] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [x] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [x] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [x] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [x] New flags don't duplicate an existing flag that serves the same purpose - [ ] New flags do not have short aliases without strong justification - [x] Experimental features are marked with `(Experimental)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [x] All flags shown in help text and examples are implemented and functional - [x] Summaries use sentence case and have no trailing period - [x] Long descriptions end with a period and include at least one example invocation - [ ] Examples use long flags (`--namespace`, not `-n`), one flag per line - [x] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [x] Results go to stdout; errors and warnings go to stderr - [x] Error messages are lowercase with no trailing punctuation **Tests** - [x] Added functional test(s) (`SharedServerSuite`) - [x] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** ``` temporal operator nexus endpoint create --name my-endpoint --target-namespace default --target-task-queue my-tq ``` **Happy path** ``` $ temporal nexus operation start --endpoint my-endpoint --service my-service --operation my-op --operation-id my-op-1 --input '"hello"' Started Nexus Operation: Endpoint my-endpoint Service my-service Operation my-op OperationId my-op-1 RunId 7e7c8b2a-...-... Namespace default ``` **Error case** ``` $ temporal nexus operation start --service my-service --operation my-op --operation-id my-op-2 Error: required flag(s) "endpoint" not set ``` **Composition** — start an operation, then fetch its result, then list/describe: ``` $ temporal nexus operation start --endpoint my-endpoint --service my-service --operation my-op --operation-id my-op-3 --input '"world"' $ temporal nexus operation result --operation-id my-op-3 Results: Status COMPLETED Result "got: world" $ temporal nexus operation describe --operation-id my-op-3 OperationId my-op-3 RunId 7e7c8b2a-...-... Endpoint my-endpoint Service my-service Operation my-op Status Succeeded ... ``` --- go.mod | 2 +- go.sum | 4 +- internal/temporalcli/commands.gen.go | 321 ++++++++ .../temporalcli/commands.nexus_operation.go | 501 ++++++++++++ .../commands.nexus_operation_test.go | 728 ++++++++++++++++++ internal/temporalcli/commands.yaml | 256 ++++++ internal/temporalcli/commands_test.go | 2 + 7 files changed, 1811 insertions(+), 3 deletions(-) create mode 100644 internal/temporalcli/commands.nexus_operation.go create mode 100644 internal/temporalcli/commands.nexus_operation_test.go diff --git a/go.mod b/go.mod index 847f9817a..6921fd3ca 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/temporalio/cli/cliext v0.0.0 github.com/temporalio/ui-server/v2 v2.49.1 go.temporal.io/api v1.62.13 - go.temporal.io/sdk v1.41.1 + go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/envconfig v1.0.0 go.temporal.io/server v1.32.0-157.0 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f diff --git a/go.sum b/go.sum index 5d98f630c..31903bc40 100644 --- a/go.sum +++ b/go.sum @@ -473,8 +473,8 @@ go.temporal.io/api v1.62.13 h1:xMa8Nt5oAMX+LvlCJA44wjTCc1H09i2rG9poB1/xvH4= go.temporal.io/api v1.62.13/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= 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= -go.temporal.io/sdk v1.41.1/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc= +go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= +go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= 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= go.temporal.io/server v1.32.0-157.0 h1:nzFqNwx+5lXsT0/DSiFyR5vHMnDcT3PVAvmRDqCUn38= diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 4985840e1..1d0a2a9a8 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -341,6 +341,47 @@ func (v *NexusEndpointConfigOptions) BuildFlags(f *pflag.FlagSet) { f.StringVar(&v.TargetUrl, "target-url", "", "An external Nexus Endpoint that receives forwarded Nexus requests. May be used as an alternative to `--target-namespace` and `--target-task-queue`. EXPERIMENTAL.") } +type NexusOperationReferenceOptions struct { + OperationId string + RunId string + FlagSet *pflag.FlagSet +} + +func (v *NexusOperationReferenceOptions) BuildFlags(f *pflag.FlagSet) { + v.FlagSet = f + f.StringVar(&v.OperationId, "operation-id", "", "Nexus Operation ID. Required.") + _ = cobra.MarkFlagRequired(f, "operation-id") + f.StringVarP(&v.RunId, "run-id", "r", "", "Run ID of the Nexus Operation.") +} + +type NexusOperationStartOptions struct { + Endpoint string + Service string + Operation string + OperationId string + ScheduleToCloseTimeout cliext.FlagDuration + IdConflictPolicy cliext.FlagStringEnum + IdReusePolicy cliext.FlagStringEnum + FlagSet *pflag.FlagSet +} + +func (v *NexusOperationStartOptions) BuildFlags(f *pflag.FlagSet) { + v.FlagSet = f + f.StringVar(&v.Endpoint, "endpoint", "", "Nexus Endpoint name. Required.") + _ = cobra.MarkFlagRequired(f, "endpoint") + f.StringVar(&v.Service, "service", "", "Nexus Service name. Required.") + _ = cobra.MarkFlagRequired(f, "service") + f.StringVar(&v.Operation, "operation", "", "Nexus Operation name. Required.") + _ = cobra.MarkFlagRequired(f, "operation") + f.StringVar(&v.OperationId, "operation-id", "", "Nexus Operation ID. If not supplied, a unique ID is generated.") + v.ScheduleToCloseTimeout = 0 + f.Var(&v.ScheduleToCloseTimeout, "schedule-to-close-timeout", "Total time the operation is allowed to run.") + v.IdConflictPolicy = cliext.NewFlagStringEnum([]string{"Fail", "UseExisting", "TerminateExisting"}, "") + f.Var(&v.IdConflictPolicy, "id-conflict-policy", "Policy for handling an Operation ID conflict with a running operation. Accepted values: Fail, UseExisting, TerminateExisting.") + v.IdReusePolicy = cliext.NewFlagStringEnum([]string{"AllowDuplicate", "RejectDuplicate"}, "") + f.Var(&v.IdReusePolicy, "id-reuse-policy", "Policy for re-using an Operation ID from a previously closed operation. Accepted values: AllowDuplicate, RejectDuplicate.") +} + type QueryModifiersOptions struct { RejectCondition cliext.FlagStringEnum Headers []string @@ -461,6 +502,7 @@ func NewTemporalCommand(cctx *CommandContext) *TemporalCommand { s.Command.AddCommand(&NewTemporalBatchCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalConfigCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalEnvCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalOperatorCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalServerCommand(cctx, &s).Command) @@ -1369,6 +1411,285 @@ func NewTemporalEnvSetCommand(cctx *CommandContext, parent *TemporalEnvCommand) return &s } +type TemporalNexusCommand struct { + Parent *TemporalCommand + Command cobra.Command + cliext.ClientOptions +} + +func NewTemporalNexusCommand(cctx *CommandContext, parent *TemporalCommand) *TemporalNexusCommand { + var s TemporalNexusCommand + s.Parent = parent + s.Command.Use = "nexus" + s.Command.Short = "Start, list, and operate on Nexus Operations" + if hasHighlighting { + s.Command.Long = "Nexus Operation commands perform operations on Nexus\nOperation Executions:\n\n\x1b[1mtemporal nexus [command] [options]\x1b[0m\n\nFor example:\n\n\x1b[1mtemporal nexus operation list\x1b[0m" + } else { + s.Command.Long = "Nexus Operation commands perform operations on Nexus\nOperation Executions:\n\n```\ntemporal nexus [command] [options]\n```\n\nFor example:\n\n```\ntemporal nexus operation list\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewTemporalNexusOperationCommand(cctx, &s).Command) + s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + return &s +} + +type TemporalNexusOperationCommand struct { + Parent *TemporalNexusCommand + Command cobra.Command +} + +func NewTemporalNexusOperationCommand(cctx *CommandContext, parent *TemporalNexusCommand) *TemporalNexusOperationCommand { + var s TemporalNexusOperationCommand + s.Parent = parent + s.Command.Use = "operation" + s.Command.Short = "Commands for managing Nexus Operations" + if hasHighlighting { + s.Command.Long = "These commands manage Nexus Operation Executions.\n\nNexus Operation commands follow this syntax:\n\n\x1b[1mtemporal nexus operation [command] [options]\x1b[0m" + } else { + s.Command.Long = "These commands manage Nexus Operation Executions.\n\nNexus Operation commands follow this syntax:\n\n```\ntemporal nexus operation [command] [options]\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.AddCommand(&NewTemporalNexusOperationCancelCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationCountCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationDescribeCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationExecuteCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationListCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationResultCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationStartCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalNexusOperationTerminateCommand(cctx, &s).Command) + return &s +} + +type TemporalNexusOperationCancelCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + NexusOperationReferenceOptions + Reason string +} + +func NewTemporalNexusOperationCancelCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationCancelCommand { + var s TemporalNexusOperationCancelCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "cancel [flags]" + s.Command.Short = "Request cancellation of a Nexus Operation (Experimental)" + if hasHighlighting { + s.Command.Long = "Request cancellation of a Nexus Operation.\n\n\x1b[1mtemporal nexus operation cancel \\\n --operation-id YourOperationId\x1b[0m\n\nThe Operation handler determines how to handle the\ncancellation request." + } else { + s.Command.Long = "Request cancellation of a Nexus Operation.\n\n```\ntemporal nexus operation cancel \\\n --operation-id YourOperationId\n```\n\nThe Operation handler determines how to handle the\ncancellation request." + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for cancellation.") + s.NexusOperationReferenceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationCountCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + Query string +} + +func NewTemporalNexusOperationCountCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationCountCommand { + var s TemporalNexusOperationCountCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "count [flags]" + s.Command.Short = "Count Nexus Operations matching a query (Experimental)" + if hasHighlighting { + s.Command.Long = "Return a count of Nexus Operations. Use \x1b[1m--query\x1b[0m\nto filter the operations to be counted.\n\n\x1b[1mtemporal nexus operation count \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + } else { + s.Command.Long = "Return a count of Nexus Operations. Use `--query`\nto filter the operations to be counted.\n\n```\ntemporal nexus operation count \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\n```\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.Query, "query", "q", "", "Query to filter Nexus Operation Executions to count.") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationDescribeCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + NexusOperationReferenceOptions + Raw bool +} + +func NewTemporalNexusOperationDescribeCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationDescribeCommand { + var s TemporalNexusOperationDescribeCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "describe [flags]" + s.Command.Short = "Show detailed info for a Nexus Operation (Experimental)" + if hasHighlighting { + s.Command.Long = "Display detailed information about a specific Nexus\nOperation Execution.\n\n\x1b[1mtemporal nexus operation describe \\\n --operation-id YourOperationId\x1b[0m" + } else { + s.Command.Long = "Display detailed information about a specific Nexus\nOperation Execution.\n\n```\ntemporal nexus operation describe \\\n --operation-id YourOperationId\n```" + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().BoolVar(&s.Raw, "raw", false, "Print properties without changing their format.") + s.NexusOperationReferenceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationExecuteCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + NexusOperationStartOptions + PayloadInputOptions +} + +func NewTemporalNexusOperationExecuteCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationExecuteCommand { + var s TemporalNexusOperationExecuteCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "execute [flags]" + s.Command.Short = "Start a new Nexus Operation and wait for its result (Experimental)" + if hasHighlighting { + s.Command.Long = "Start a new Nexus Operation Execution and block until\nit completes. The result is output to stdout.\n\n\x1b[1mtemporal nexus operation execute \\\n --endpoint YourEndpoint \\\n --service YourService \\\n --operation YourOperation \\\n --operation-id YourOperationId \\\n --input '{\"some-key\": \"some-value\"}'\x1b[0m" + } else { + s.Command.Long = "Start a new Nexus Operation Execution and block until\nit completes. The result is output to stdout.\n\n```\ntemporal nexus operation execute \\\n --endpoint YourEndpoint \\\n --service YourService \\\n --operation YourOperation \\\n --operation-id YourOperationId \\\n --input '{\"some-key\": \"some-value\"}'\n```" + } + s.Command.Args = cobra.NoArgs + s.NexusOperationStartOptions.BuildFlags(s.Command.Flags()) + s.PayloadInputOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationListCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + Query string + Limit int + PageSize int +} + +func NewTemporalNexusOperationListCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationListCommand { + var s TemporalNexusOperationListCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "list [flags]" + s.Command.Short = "List Nexus Operations matching a query (Experimental)" + if hasHighlighting { + s.Command.Long = "List Nexus Operations. Use \x1b[1m--query\x1b[0m to filter results.\n\n\x1b[1mtemporal nexus operation list \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + } else { + s.Command.Long = "List Nexus Operations. Use `--query` to filter results.\n\n```\ntemporal nexus operation list \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\n```\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVarP(&s.Query, "query", "q", "", "Query to filter the Nexus Operation Executions to list.") + s.Command.Flags().IntVar(&s.Limit, "limit", 0, "Maximum number of Nexus Operation Executions to display.") + s.Command.Flags().IntVar(&s.PageSize, "page-size", 0, "Maximum number of Nexus Operation Executions to fetch at a time from the server.") + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationResultCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + NexusOperationReferenceOptions +} + +func NewTemporalNexusOperationResultCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationResultCommand { + var s TemporalNexusOperationResultCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "result [flags]" + s.Command.Short = "Wait for and output the result of a Nexus Operation (Experimental)" + if hasHighlighting { + s.Command.Long = "Wait for a Nexus Operation to complete and output\nthe result.\n\n\x1b[1mtemporal nexus operation result \\\n --operation-id YourOperationId\x1b[0m" + } else { + s.Command.Long = "Wait for a Nexus Operation to complete and output\nthe result.\n\n```\ntemporal nexus operation result \\\n --operation-id YourOperationId\n```" + } + s.Command.Args = cobra.NoArgs + s.NexusOperationReferenceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationStartCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + NexusOperationStartOptions + PayloadInputOptions +} + +func NewTemporalNexusOperationStartCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationStartCommand { + var s TemporalNexusOperationStartCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "start [flags]" + s.Command.Short = "Start a new Nexus Operation (Experimental)" + if hasHighlighting { + s.Command.Long = "Start a new Nexus Operation. Outputs the\nOperation ID and Run ID.\n\n\x1b[1mtemporal nexus operation start \\\n --endpoint YourEndpoint \\\n --service YourService \\\n --operation YourOperation \\\n --operation-id YourOperationId \\\n --input '{\"some-key\": \"some-value\"}'\x1b[0m" + } else { + s.Command.Long = "Start a new Nexus Operation. Outputs the\nOperation ID and Run ID.\n\n```\ntemporal nexus operation start \\\n --endpoint YourEndpoint \\\n --service YourService \\\n --operation YourOperation \\\n --operation-id YourOperationId \\\n --input '{\"some-key\": \"some-value\"}'\n```" + } + s.Command.Args = cobra.NoArgs + s.NexusOperationStartOptions.BuildFlags(s.Command.Flags()) + s.PayloadInputOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + +type TemporalNexusOperationTerminateCommand struct { + Parent *TemporalNexusOperationCommand + Command cobra.Command + NexusOperationReferenceOptions + Reason string +} + +func NewTemporalNexusOperationTerminateCommand(cctx *CommandContext, parent *TemporalNexusOperationCommand) *TemporalNexusOperationTerminateCommand { + var s TemporalNexusOperationTerminateCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "terminate [flags]" + s.Command.Short = "Forcefully end a Nexus Operation (Experimental)" + if hasHighlighting { + s.Command.Long = "Terminate a Nexus Operation.\n\n\x1b[1mtemporal nexus operation terminate \\\n --operation-id YourOperationId \\\n --reason YourReason\x1b[0m\n\nOperation handlers cannot see or respond to terminations." + } else { + s.Command.Long = "Terminate a Nexus Operation.\n\n```\ntemporal nexus operation terminate \\\n --operation-id YourOperationId \\\n --reason YourReason\n```\n\nOperation handlers cannot see or respond to terminations." + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for termination. Defaults to a message with the current user's name.") + s.NexusOperationReferenceOptions.BuildFlags(s.Command.Flags()) + s.Command.Run = func(c *cobra.Command, args []string) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalOperatorCommand struct { Parent *TemporalCommand Command cobra.Command diff --git a/internal/temporalcli/commands.nexus_operation.go b/internal/temporalcli/commands.nexus_operation.go new file mode 100644 index 000000000..4e58c11bf --- /dev/null +++ b/internal/temporalcli/commands.nexus_operation.go @@ -0,0 +1,501 @@ +package temporalcli + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/fatih/color" + "github.com/google/uuid" + "github.com/temporalio/cli/internal/printer" + "go.temporal.io/api/common/v1" + enumspb "go.temporal.io/api/enums/v1" + "go.temporal.io/api/failure/v1" + "go.temporal.io/api/serviceerror" + "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/converter" +) + +func (c *TemporalNexusOperationStartCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + handle, err := startNexusOperation(cctx, cl, &c.NexusOperationStartOptions, &c.PayloadInputOptions) + if err != nil { + return err + } + return printNexusOperationExecution(cctx, &c.NexusOperationStartOptions, handle.GetID(), handle.GetRunID(), c.Parent.Parent.Namespace) +} + +func (c *TemporalNexusOperationExecuteCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + handle, err := startNexusOperation(cctx, cl, &c.NexusOperationStartOptions, &c.PayloadInputOptions) + if err != nil { + return err + } + if !cctx.JSONOutput { + if err := printNexusOperationExecution(cctx, &c.NexusOperationStartOptions, handle.GetID(), handle.GetRunID(), c.Parent.Parent.Namespace); err != nil { + cctx.Logger.Error("Failed printing execution info", "error", err) + } + } + return getNexusOperationResult(cctx, cl, c.Parent.Parent.Namespace, handle.GetID(), handle.GetRunID()) +} + +func (c *TemporalNexusOperationResultCommand) run(cctx *CommandContext, _ []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + return getNexusOperationResult(cctx, cl, c.Parent.Parent.Namespace, c.OperationId, c.RunId) +} + +func startNexusOperation( + cctx *CommandContext, + cl client.Client, + opts *NexusOperationStartOptions, + inputOpts *PayloadInputOptions, +) (client.NexusOperationHandle, error) { + nexusCl, startOpts, err := buildNexusStartOptions(opts, inputOpts) + if err != nil { + return nil, err + } + nCl, err := cl.NewNexusClient(nexusCl) + if err != nil { + return nil, err + } + handle, err := nCl.ExecuteOperation(cctx, startOpts.operation, startOpts.input, startOpts.options) + if err != nil { + return nil, fmt.Errorf("failed starting nexus operation: %w", err) + } + return handle, nil +} + +func printNexusOperationExecution(cctx *CommandContext, opts *NexusOperationStartOptions, operationID, runID, namespace string) error { + if !cctx.JSONOutput { + cctx.Printer.Println(color.MagentaString("Started Nexus Operation:")) + } + return cctx.Printer.PrintStructured(struct { + Endpoint string `json:"endpoint"` + Service string `json:"service"` + Operation string `json:"operation"` + OperationId string `json:"operationId"` + RunId string `json:"runId"` + Namespace string `json:"namespace"` + }{ + Endpoint: opts.Endpoint, + Service: opts.Service, + Operation: opts.Operation, + OperationId: operationID, + RunId: runID, + Namespace: namespace, + }, printer.StructuredOptions{}) +} + +func getNexusOperationResult(cctx *CommandContext, cl client.Client, namespace, operationID, runID string) error { + resp, err := pollNexusOperationOutcome(cctx, cl, namespace, operationID, runID) + if err != nil { + var notFound *serviceerror.NotFound + if errors.As(err, ¬Found) { + return fmt.Errorf("nexus operation not found: %s", operationID) + } + return fmt.Errorf("failed polling nexus operation result: %w", err) + } + + // Use the run ID from the response if the caller didn't supply one. + if runID == "" { + runID = resp.GetRunId() + } + + switch v := resp.GetOutcome().(type) { + case *workflowservice.PollNexusOperationExecutionResponse_Result: + return printNexusOperationResult(cctx, operationID, runID, v.Result) + case *workflowservice.PollNexusOperationExecutionResponse_Failure: + if err := printNexusOperationFailure(cctx, operationID, runID, v.Failure); err != nil { + cctx.Logger.Error("Nexus operation failed, and printing the output also failed", "error", err) + } + return fmt.Errorf("nexus operation failed") + default: + return fmt.Errorf("unexpected nexus operation outcome type: %T", v) + } +} + +// Matches the SDK's pollNexusOperationTimeout in internal_nexus_client.go. +const pollNexusOperationTimeout = 60 * time.Second + +// pollNexusOperationOutcome polls for a nexus operation result using a +// hand-rolled loop rather than handle.Get() because handle.Get() deserializes +// the result into a Go value and converts failures to Go errors, losing the +// raw proto payloads. +func pollNexusOperationOutcome(cctx *CommandContext, cl client.Client, namespace, operationID, runID string) (*workflowservice.PollNexusOperationExecutionResponse, error) { + for { + pollCtx, cancel := context.WithTimeout(cctx, pollNexusOperationTimeout) + resp, err := cl.WorkflowService().PollNexusOperationExecution(pollCtx, &workflowservice.PollNexusOperationExecutionRequest{ + Namespace: namespace, + OperationId: operationID, + RunId: runID, + WaitStage: enumspb.NEXUS_OPERATION_WAIT_STAGE_CLOSED, + }) + if err != nil { + // check pollCtx.Err() first because it is set by cancel() + pollTimedOut := pollCtx.Err() != nil + cancel() + if cctx.Err() != nil { + return nil, cctx.Err() + } + if pollTimedOut { + continue + } + return nil, err + } + cancel() + if resp.GetOutcome() != nil { + return resp, nil + } + } +} + +func printNexusOperationResult(cctx *CommandContext, operationID, runID string, result *common.Payload) error { + if cctx.JSONOutput { + var resultJSON json.RawMessage + var err error + if cctx.JSONShorthandPayloads { + var valuePtr any + if err = converter.GetDefaultDataConverter().FromPayload(result, &valuePtr); err != nil { + return fmt.Errorf("nexus operation completed, but failed decoding result for json output: %w", err) + } + resultJSON, err = json.Marshal(valuePtr) + } else { + resultJSON, err = cctx.MarshalProtoJSON(result) + } + if err != nil { + return fmt.Errorf("nexus operation completed, but failed marshaling result for json output: %w", err) + } + return cctx.Printer.PrintStructured(struct { + OperationId string `json:"operationId"` + RunId string `json:"runId"` + Status string `json:"status"` + Result json.RawMessage `json:"result"` + }{ + OperationId: operationID, + RunId: runID, + Status: "COMPLETED", + Result: resultJSON, + }, printer.StructuredOptions{}) + } + + cctx.Printer.Println(color.MagentaString("Results:")) + var valuePtr any + if err := converter.GetDefaultDataConverter().FromPayload(result, &valuePtr); err != nil { + return fmt.Errorf("nexus operation completed, but failed decoding result: %w", err) + } + resultJSON, err := json.Marshal(valuePtr) + if err != nil { + return fmt.Errorf("nexus operation completed, but failed marshaling result: %w", err) + } + return cctx.Printer.PrintStructured(struct { + Status string + Result json.RawMessage `cli:",cardOmitEmpty"` + }{ + Status: color.GreenString("COMPLETED"), + Result: resultJSON, + }, printer.StructuredOptions{}) +} + +func printNexusOperationFailure(cctx *CommandContext, operationID, runID string, f *failure.Failure) error { + if cctx.JSONOutput { + failureJSON, err := cctx.MarshalProtoJSON(f) + if err != nil { + return fmt.Errorf("nexus operation failed, but failed marshaling failure for json output: %w", err) + } + return cctx.Printer.PrintStructured(struct { + OperationId string `json:"operationId"` + RunId string `json:"runId"` + Status string `json:"status"` + Failure json.RawMessage `json:"failure"` + }{ + OperationId: operationID, + RunId: runID, + Status: "FAILED", + Failure: failureJSON, + }, printer.StructuredOptions{}) + } + + cctx.Printer.Println(color.MagentaString("Results:")) + return cctx.Printer.PrintStructured(struct { + Status string + Failure string `cli:",cardOmitEmpty"` + }{ + Status: color.RedString("FAILED"), + Failure: cctx.MarshalFriendlyFailureBodyText(f, " "), + }, printer.StructuredOptions{}) +} + +func (c *TemporalNexusOperationDescribeCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + handle := cl.GetNexusOperationHandle(client.GetNexusOperationHandleOptions{ + OperationID: c.OperationId, + RunID: c.RunId, + }) + + desc, err := handle.Describe(cctx, client.DescribeNexusOperationOptions{}) + if err != nil { + return fmt.Errorf("failed describing nexus operation: %w", err) + } + + if c.Raw || cctx.JSONOutput { + return cctx.Printer.PrintStructured(desc.RawInfo, printer.StructuredOptions{}) + } + return printNexusOperationDescription(cctx, desc) +} + +func printNexusOperationDescription(cctx *CommandContext, desc *client.NexusOperationExecutionDescription) error { + summary, _ := desc.GetSummary() + d := struct { + OperationId string + RunId string + Endpoint string + Service string + Operation string + Status string + State string `cli:",cardOmitEmpty"` + Attempt int32 + ScheduleToCloseTimeout time.Duration `cli:",cardOmitEmpty"` + ScheduledTime time.Time `cli:",cardOmitEmpty"` + CloseTime time.Time `cli:",cardOmitEmpty"` + ExpirationTime time.Time `cli:",cardOmitEmpty"` + BlockedReason string `cli:",cardOmitEmpty"` + OperationToken string `cli:",cardOmitEmpty"` + Identity string `cli:",cardOmitEmpty"` + Summary string `cli:",cardOmitEmpty"` + }{ + OperationId: desc.OperationID, + RunId: desc.OperationRunID, + Endpoint: desc.Endpoint, + Service: desc.Service, + Operation: desc.Operation, + Status: desc.Status.String(), + State: desc.State.String(), + Attempt: desc.Attempt, + ScheduleToCloseTimeout: desc.ScheduleToCloseTimeout, + ScheduledTime: desc.ScheduledTime, + CloseTime: desc.CloseTime, + ExpirationTime: desc.ExpirationTime, + BlockedReason: desc.BlockedReason, + OperationToken: desc.OperationToken, + Identity: desc.Identity, + Summary: summary, + } + return cctx.Printer.PrintStructured(d, printer.StructuredOptions{}) +} + +func (c *TemporalNexusOperationCancelCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + handle := cl.GetNexusOperationHandle(client.GetNexusOperationHandleOptions{ + OperationID: c.OperationId, + RunID: c.RunId, + }) + + err = handle.Cancel(cctx, client.CancelNexusOperationOptions{ + Reason: c.Reason, + }) + if err != nil { + return fmt.Errorf("failed to request nexus operation cancellation: %w", err) + } + cctx.Printer.Println("Cancellation requested") + return nil +} + +func (c *TemporalNexusOperationTerminateCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + reason := c.Reason + if reason == "" { + reason = defaultReason() + } + handle := cl.GetNexusOperationHandle(client.GetNexusOperationHandleOptions{ + OperationID: c.OperationId, + RunID: c.RunId, + }) + if err := handle.Terminate(cctx, client.TerminateNexusOperationOptions{Reason: reason}); err != nil { + return fmt.Errorf("failed to terminate nexus operation: %w", err) + } + cctx.Printer.Println("Nexus Operation terminated") + return nil +} + +func (c *TemporalNexusOperationListCommand) run(cctx *CommandContext, _ []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + if c.Limit > 0 && c.Limit < c.PageSize { + c.PageSize = c.Limit + } + + cctx.Printer.StartList() + defer cctx.Printer.EndList() + + var nextPageToken []byte + var opsProcessed int + for pageIndex := 0; ; pageIndex++ { + resp, err := cl.WorkflowService().ListNexusOperationExecutions(cctx, &workflowservice.ListNexusOperationExecutionsRequest{ + Namespace: c.Parent.Parent.Namespace, + PageSize: int32(c.PageSize), + NextPageToken: nextPageToken, + Query: c.Query, + }) + if err != nil { + return fmt.Errorf("failed listing nexus operations: %w", err) + } + var textTable []map[string]any + for _, op := range resp.GetOperations() { + if c.Limit > 0 && opsProcessed >= c.Limit { + break + } + opsProcessed++ + if cctx.JSONOutput { + _ = cctx.Printer.PrintStructured(op, printer.StructuredOptions{}) + } else { + textTable = append(textTable, map[string]any{ + "Status": op.Status, + "OperationId": op.OperationId, + "Endpoint": op.Endpoint, + "Service": op.Service, + "Operation": op.Operation, + "StartTime": op.ScheduleTime.AsTime(), + }) + } + } + if len(textTable) > 0 { + _ = cctx.Printer.PrintStructured(textTable, printer.StructuredOptions{ + Fields: []string{"Status", "OperationId", "Endpoint", "Service", "Operation", "StartTime"}, + Table: &printer.TableOptions{NoHeader: pageIndex > 0}, + }) + } + nextPageToken = resp.GetNextPageToken() + if len(nextPageToken) == 0 || (c.Limit > 0 && opsProcessed >= c.Limit) { + return nil + } + } +} + +func (c *TemporalNexusOperationCountCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + resp, err := cl.WorkflowService().CountNexusOperationExecutions(cctx, &workflowservice.CountNexusOperationExecutionsRequest{ + Namespace: c.Parent.Parent.Namespace, + Query: c.Query, + }) + if err != nil { + return fmt.Errorf("failed counting nexus operations: %w", err) + } + groups := make([]countGroup, len(resp.Groups)) + for i, g := range resp.Groups { + groups[i] = g + } + if cctx.JSONOutput { + stripCountGroupMetadataType(groups) + return cctx.Printer.PrintStructured(resp, printer.StructuredOptions{}) + } + cctx.Printer.Printlnf("Total: %v", resp.Count) + printCountGroupsText(cctx, groups) + return nil +} + +// nexusStartInput holds the parsed inputs for starting a Nexus operation. +type nexusStartInput struct { + operation string + input any + options client.StartNexusOperationOptions +} + +func buildNexusStartOptions(s *NexusOperationStartOptions, p *PayloadInputOptions) (client.NexusClientOptions, *nexusStartInput, error) { + nexusCl := client.NexusClientOptions{ + Endpoint: s.Endpoint, + Service: s.Service, + } + + operationID := s.OperationId + if operationID == "" { + operationID = uuid.NewString() + } + + opts := client.StartNexusOperationOptions{ + ID: operationID, + ScheduleToCloseTimeout: s.ScheduleToCloseTimeout.Duration(), + } + + if s.IdConflictPolicy.Value != "" { + v, err := stringToProtoEnum[enumspb.NexusOperationIdConflictPolicy]( + s.IdConflictPolicy.Value, + enumspb.NexusOperationIdConflictPolicy_shorthandValue, + enumspb.NexusOperationIdConflictPolicy_value) + if err != nil { + return nexusCl, nil, fmt.Errorf("invalid id-conflict-policy: %w", err) + } + opts.IDConflictPolicy = v + } + + if s.IdReusePolicy.Value != "" { + v, err := stringToProtoEnum[enumspb.NexusOperationIdReusePolicy]( + s.IdReusePolicy.Value, + enumspb.NexusOperationIdReusePolicy_shorthandValue, + enumspb.NexusOperationIdReusePolicy_value) + if err != nil { + return nexusCl, nil, fmt.Errorf("invalid id-reuse-policy: %w", err) + } + opts.IDReusePolicy = v + } + + // Build input payload + var input any + rawInput, err := p.buildRawInput() + if err != nil { + return nexusCl, nil, err + } + if len(rawInput) > 1 { + return nexusCl, nil, fmt.Errorf("nexus operations accept at most one input argument, got %d", len(rawInput)) + } + if len(rawInput) == 1 { + input = rawInput[0] + } + + return nexusCl, &nexusStartInput{ + operation: s.Operation, + input: input, + options: opts, + }, nil +} diff --git a/internal/temporalcli/commands.nexus_operation_test.go b/internal/temporalcli/commands.nexus_operation_test.go new file mode 100644 index 000000000..277786955 --- /dev/null +++ b/internal/temporalcli/commands.nexus_operation_test.go @@ -0,0 +1,728 @@ +package temporalcli_test + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "testing" + "time" + + "github.com/google/uuid" + nexus "github.com/nexus-rpc/sdk-go/nexus" + "github.com/stretchr/testify/require" + nexuspb "go.temporal.io/api/nexus/v1" + "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/temporalnexus" + "go.temporal.io/sdk/workflow" +) + +func (s *SharedServerSuite) setupNexusEndpointAndWorker(t *testing.T) (string, *DevWorker) { + handlerWorkflow := func(ctx workflow.Context, input string) (string, error) { + return "got: " + input, nil + } + return s.setupNexusEndpointWithWorkflow(t, handlerWorkflow, "handler-") +} + +func (s *SharedServerSuite) setupNexusEndpointWithWorkflow( + t *testing.T, + handlerWorkflow func(workflow.Context, string) (string, error), + workflowIDPrefix string, +) (string, *DevWorker) { + endpointName := "test-ep-" + uuid.NewString()[:8] + + op := temporalnexus.NewWorkflowRunOperation( + "test-op", + handlerWorkflow, + func(ctx context.Context, input string, opts nexus.StartOperationOptions) (client.StartWorkflowOptions, error) { + return client.StartWorkflowOptions{ + ID: workflowIDPrefix + opts.RequestID, + }, nil + }, + ) + svc := nexus.NewService("test-service") + require.NoError(t, svc.Register(op)) + + w := s.DevServer.StartDevWorker(t, DevWorkerOptions{ + Workflows: []any{handlerWorkflow}, + NexusServices: []*nexus.Service{svc}, + }) + + _, err := s.Client.OperatorService().CreateNexusEndpoint(s.Context, &operatorservice.CreateNexusEndpointRequest{ + Spec: &nexuspb.EndpointSpec{ + Name: endpointName, + Target: &nexuspb.EndpointTarget{ + Variant: &nexuspb.EndpointTarget_Worker_{ + Worker: &nexuspb.EndpointTarget_Worker{ + Namespace: s.Namespace(), + TaskQueue: w.Options.TaskQueue, + }, + }, + }, + }, + }) + require.NoError(t, err) + + return endpointName, w +} + +func (s *SharedServerSuite) TestNexusOperationStart() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "start-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Started Nexus Operation") + s.Contains(res.Stdout.String(), opID) +} + +func (s *SharedServerSuite) TestNexusOperationExecute() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "exec-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "execute", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "got: hello") +} + +func (s *SharedServerSuite) TestNexusOperationExecute_JSON() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "exec-json-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "execute", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + "--output", "json", + ) + s.NoError(res.Err) + + var result struct { + OperationId string `json:"operationId"` + RunId string `json:"runId"` + Result json.RawMessage `json:"result"` + } + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) + s.Equal(opID, result.OperationId) + s.Contains(string(result.Result), "got: hello") +} + +func (s *SharedServerSuite) TestNexusOperationDescribe() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "desc-op-" + uuid.NewString()[:8] + + // Start an operation first. + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Describe it. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "describe", + "--address", s.Address(), + "--operation-id", opID, + ) + return res.Err == nil + }, 30*time.Second, 500*time.Millisecond) + + s.Contains(res.Stdout.String(), opID) + s.Contains(res.Stdout.String(), endpointName) + s.Contains(res.Stdout.String(), "test-service") +} + +func (s *SharedServerSuite) TestNexusOperationDescribe_JSON() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "desc-json-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "describe", + "--address", s.Address(), + "--operation-id", opID, + "--output", "json", + ) + return res.Err == nil + }, 30*time.Second, 500*time.Millisecond) + + s.NoError(res.Err) + s.Contains(res.Stdout.String(), opID) +} + +func (s *SharedServerSuite) TestNexusOperationCancel() { + blockingHandler := func(ctx workflow.Context, input string) (string, error) { + ctx.Done().Receive(ctx, nil) + return "", ctx.Err() + } + endpointName, w := s.setupNexusEndpointWithWorkflow(s.T(), blockingHandler, "cancel-handler-") + defer w.Stop() + + opID := "cancel-op-" + uuid.NewString()[:8] + + // Start operation. + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Cancel it. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "cancel", + "--address", s.Address(), + "--operation-id", opID, + "--reason", "testing cancellation", + ) + return res.Err == nil + }, 30*time.Second, 500*time.Millisecond) + + s.Contains(res.Stdout.String(), "Cancellation requested") +} + +func (s *SharedServerSuite) TestNexusOperationTerminate() { + blockingHandler := func(ctx workflow.Context, input string) (string, error) { + ctx.Done().Receive(ctx, nil) + return "", ctx.Err() + } + endpointName, w := s.setupNexusEndpointWithWorkflow(s.T(), blockingHandler, "term-handler-") + defer w.Stop() + + opID := "term-op-" + uuid.NewString()[:8] + + // Start operation. + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Terminate it. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "terminate", + "--address", s.Address(), + "--operation-id", opID, + "--reason", "testing termination", + ) + return res.Err == nil + }, 30*time.Second, 500*time.Millisecond) + + s.Contains(res.Stdout.String(), "Nexus Operation terminated") +} + +func (s *SharedServerSuite) TestNexusOperationList() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + // Start a couple of operations. + for i := 0; i < 2; i++ { + opID := fmt.Sprintf("list-op-%d-%s", i, uuid.NewString()[:8]) + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + } + + // List operations — wait until both are visible. + var res *CommandResult + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "list", + "--address", s.Address(), + ) + out := res.Stdout.String() + return res.Err == nil && + strings.Contains(out, "list-op-0") && + strings.Contains(out, "list-op-1") + }, 30*time.Second, 500*time.Millisecond) +} + +func (s *SharedServerSuite) TestNexusOperationCount() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "count-op-" + uuid.NewString()[:8] + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Count operations. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "count", + "--address", s.Address(), + ) + return res.Err == nil && res.Stdout.String() != "" + }, 30*time.Second, 500*time.Millisecond) + + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Total:") +} + +func (s *SharedServerSuite) TestNexusOperationResult() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "result-op-" + uuid.NewString()[:8] + + // Start an operation. + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Get result (blocks until completed). + res = s.Execute( + "nexus", "operation", "result", + "--address", s.Address(), + "--operation-id", opID, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "got: hello") +} + +func (s *SharedServerSuite) TestNexusOperationResult_JSON() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "result-json-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + res = s.Execute( + "nexus", "operation", "result", + "--address", s.Address(), + "--operation-id", opID, + "--output", "json", + ) + s.NoError(res.Err) + + var result struct { + OperationId string `json:"operationId"` + Result json.RawMessage `json:"result"` + } + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) + s.Equal(opID, result.OperationId) + s.Contains(string(result.Result), "got: hello") +} + +func (s *SharedServerSuite) TestNexusOperationStart_JSON() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "start-json-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + "--output", "json", + ) + s.NoError(res.Err) + + var result struct { + Endpoint string `json:"endpoint"` + Service string `json:"service"` + Operation string `json:"operation"` + OperationId string `json:"operationId"` + RunId string `json:"runId"` + } + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) + s.Equal(endpointName, result.Endpoint) + s.Equal("test-service", result.Service) + s.Equal("test-op", result.Operation) + s.Equal(opID, result.OperationId) + s.NotEmpty(result.RunId) +} + +func (s *SharedServerSuite) TestNexusOperationStart_ServerGeneratedID() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--input", `"hello"`, + "--output", "json", + ) + s.NoError(res.Err) + + var result struct { + OperationId string `json:"operationId"` + } + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) + s.NotEmpty(result.OperationId, "server should generate an operation ID") +} + +func (s *SharedServerSuite) TestNexusOperationStart_ScheduleToCloseTimeout() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "timeout-op-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--schedule-to-close-timeout", "30m", + "--input", `"hello"`, + "--output", "json", + ) + s.NoError(res.Err) + + var result struct { + OperationId string `json:"operationId"` + } + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) + s.Equal(opID, result.OperationId) + + // Verify the timeout is reflected in describe. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "describe", + "--address", s.Address(), + "--operation-id", opID, + "--output", "json", + ) + return res.Err == nil + }, 30*time.Second, 500*time.Millisecond) + s.Contains(res.Stdout.String(), opID) +} + +func (s *SharedServerSuite) TestNexusOperationStart_IdConflictPolicy() { + // Use a blocking handler so the operation stays running across the three + // start calls — IDConflictPolicy only triggers against an open operation. + blockingHandler := func(ctx workflow.Context, input string) (string, error) { + ctx.Done().Receive(ctx, nil) + return "", ctx.Err() + } + endpointName, w := s.setupNexusEndpointWithWorkflow(s.T(), blockingHandler, "conflict-handler-") + defer w.Stop() + + opID := "conflict-op-" + uuid.NewString()[:8] + + // Start first operation. + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Start again with same ID and UseExisting policy — should succeed. + res = s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--id-conflict-policy", "UseExisting", + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Start again with same ID and Fail policy — should fail. + res = s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--id-conflict-policy", "Fail", + "--input", `"hello"`, + ) + s.Error(res.Err) +} + +func (s *SharedServerSuite) TestNexusOperationList_JSON() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "list-json-op-" + uuid.NewString()[:8] + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "list", + "--address", s.Address(), + "--output", "json", + ) + return res.Err == nil && strings.Contains(res.Stdout.String(), opID) + }, 30*time.Second, 500*time.Millisecond) +} + +func (s *SharedServerSuite) TestNexusOperationList_Limit() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + // Start 3 operations. + for i := 0; i < 3; i++ { + opID := fmt.Sprintf("limit-op-%d-%s", i, uuid.NewString()[:8]) + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + } + + // List with limit=1, JSONL output so we can count entries. + var res *CommandResult + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "list", + "--address", s.Address(), + "--limit", "1", + "--output", "jsonl", + ) + return res.Err == nil && len(res.Stdout.String()) > 0 + }, 30*time.Second, 500*time.Millisecond) + + // Count JSON objects — each on its own line. + lines := 0 + for _, line := range strings.Split(strings.TrimSpace(res.Stdout.String()), "\n") { + if len(strings.TrimSpace(line)) > 0 { + lines++ + } + } + s.Equal(1, lines, "limit=1 should return exactly 1 operation") +} + +func (s *SharedServerSuite) TestNexusOperationCount_JSON() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "count-json-op-" + uuid.NewString()[:8] + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "count", + "--address", s.Address(), + "--output", "json", + ) + if res.Err != nil { + return false + } + // Proto JSON encodes int64 as a string; accept either form. + var result struct { + Count json.Number `json:"count"` + } + if err := json.Unmarshal(res.Stdout.Bytes(), &result); err != nil { + return false + } + n, err := result.Count.Int64() + return err == nil && n > 0 + }, 30*time.Second, 500*time.Millisecond) +} + +func (s *SharedServerSuite) TestNexusOperationCount_Query() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "count-query-op-" + uuid.NewString()[:8] + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // Count with a query that matches this specific operation. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "count", + "--address", s.Address(), + "--query", fmt.Sprintf(`OperationId = "%s"`, opID), + ) + return res.Err == nil && res.Stdout.String() != "" + }, 30*time.Second, 500*time.Millisecond) + + s.Contains(res.Stdout.String(), "Total:") +} + +func (s *SharedServerSuite) TestNexusOperationList_Query() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "query-op-" + uuid.NewString()[:8] + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + ) + s.NoError(res.Err) + + // List with query filter — wait until the operation appears. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "list", + "--address", s.Address(), + "--query", fmt.Sprintf(`OperationId = "%s"`, opID), + ) + return res.Err == nil && strings.Contains(res.Stdout.String(), opID) + }, 30*time.Second, 500*time.Millisecond) +} + +func (s *SharedServerSuite) TestNexusOperationStart_MissingRequiredFlags() { + // Missing --endpoint + res := s.Execute( + "nexus", "operation", "start", + "--service", "test-service", + "--operation", "test-op", + "--operation-id", "some-id", + ) + s.Error(res.Err) + + // Missing --service + res = s.Execute( + "nexus", "operation", "start", + "--endpoint", "test-ep", + "--operation", "test-op", + "--operation-id", "some-id", + ) + s.Error(res.Err) + + // Missing --operation + res = s.Execute( + "nexus", "operation", "start", + "--endpoint", "test-ep", + "--service", "test-service", + "--operation-id", "some-id", + ) + s.Error(res.Err) +} diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 7d67822a2..33523ffa0 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -1619,6 +1619,210 @@ commands: description: Property value (required). # required: true + - name: temporal nexus + summary: Start, list, and operate on Nexus Operations + description: | + Nexus Operation commands perform operations on Nexus + Operation Executions: + + ``` + temporal nexus [command] [options] + ``` + + For example: + + ``` + temporal nexus operation list + ``` + option-sets: + - client + docs: + description-header: >- + Learn how to use Temporal Nexus commands for starting, listing, + and managing Nexus Operation Executions. + keywords: + - nexus + - nexus operation + - nexus operation start + - nexus operation execute + - nexus operation describe + - nexus operation cancel + - nexus operation terminate + - nexus operation list + - nexus operation count + - cli reference + - cli-feature + - command-line-interface-cli + - temporal cli + tags: + - Nexus + - Temporal CLI + + - name: temporal nexus operation + summary: Commands for managing Nexus Operations + description: | + These commands manage Nexus Operation Executions. + + Nexus Operation commands follow this syntax: + + ``` + temporal nexus operation [command] [options] + ``` + + - name: temporal nexus operation start + summary: Start a new Nexus Operation (Experimental) + description: | + Start a new Nexus Operation. Outputs the + Operation ID and Run ID. + + ``` + temporal nexus operation start \ + --endpoint YourEndpoint \ + --service YourService \ + --operation YourOperation \ + --operation-id YourOperationId \ + --input '{"some-key": "some-value"}' + ``` + option-sets: + - nexus-operation-start + - payload-input + + - name: temporal nexus operation execute + summary: Start a new Nexus Operation and wait for its result (Experimental) + description: | + Start a new Nexus Operation Execution and block until + it completes. The result is output to stdout. + + ``` + temporal nexus operation execute \ + --endpoint YourEndpoint \ + --service YourService \ + --operation YourOperation \ + --operation-id YourOperationId \ + --input '{"some-key": "some-value"}' + ``` + option-sets: + - nexus-operation-start + - payload-input + + - name: temporal nexus operation describe + summary: Show detailed info for a Nexus Operation (Experimental) + description: | + Display detailed information about a specific Nexus + Operation Execution. + + ``` + temporal nexus operation describe \ + --operation-id YourOperationId + ``` + option-sets: + - nexus-operation-reference + options: + - name: raw + type: bool + description: Print properties without changing their format. + + - name: temporal nexus operation cancel + summary: Request cancellation of a Nexus Operation (Experimental) + description: | + Request cancellation of a Nexus Operation. + + ``` + temporal nexus operation cancel \ + --operation-id YourOperationId + ``` + + The Operation handler determines how to handle the + cancellation request. + option-sets: + - nexus-operation-reference + options: + - name: reason + type: string + description: Reason for cancellation. + + - name: temporal nexus operation terminate + summary: Forcefully end a Nexus Operation (Experimental) + description: | + Terminate a Nexus Operation. + + ``` + temporal nexus operation terminate \ + --operation-id YourOperationId \ + --reason YourReason + ``` + + Operation handlers cannot see or respond to terminations. + option-sets: + - nexus-operation-reference + options: + - name: reason + type: string + description: | + Reason for termination. + Defaults to a message with the current user's name. + + - name: temporal nexus operation list + summary: List Nexus Operations matching a query (Experimental) + description: | + List Nexus Operations. Use `--query` to filter results. + + ``` + temporal nexus operation list \ + --query 'NexusEndpoint="YourEndpoint"' + ``` + + Visit https://docs.temporal.io/visibility to read more about + Search Attributes and queries. + options: + - name: query + short: q + type: string + description: | + Query to filter the Nexus Operation Executions to list. + - name: limit + type: int + description: | + Maximum number of Nexus Operation Executions to display. + - name: page-size + type: int + description: | + Maximum number of Nexus Operation Executions to fetch + at a time from the server. + + - name: temporal nexus operation count + summary: Count Nexus Operations matching a query (Experimental) + description: | + Return a count of Nexus Operations. Use `--query` + to filter the operations to be counted. + + ``` + temporal nexus operation count \ + --query 'NexusEndpoint="YourEndpoint"' + ``` + + Visit https://docs.temporal.io/visibility to read more about + Search Attributes and queries. + options: + - name: query + short: q + type: string + description: | + Query to filter Nexus Operation Executions to count. + + - name: temporal nexus operation result + summary: Wait for and output the result of a Nexus Operation (Experimental) + description: | + Wait for a Nexus Operation to complete and output + the result. + + ``` + temporal nexus operation result \ + --operation-id YourOperationId + ``` + option-sets: + - nexus-operation-reference + - name: temporal operator summary: Manage Temporal deployments description: | @@ -4991,6 +5195,58 @@ option-sets: May be used as an alternative to `--target-namespace` and `--target-task-queue`. + - name: nexus-operation-reference + options: + - name: operation-id + type: string + description: Nexus Operation ID. + required: true + - name: run-id + type: string + short: r + description: Run ID of the Nexus Operation. + + - name: nexus-operation-start + options: + - name: endpoint + type: string + description: Nexus Endpoint name. + required: true + - name: service + type: string + description: Nexus Service name. + required: true + - name: operation + type: string + description: Nexus Operation name. + required: true + - name: operation-id + type: string + description: | + Nexus Operation ID. + If not supplied, a unique ID is generated. + - name: schedule-to-close-timeout + type: duration + description: | + Total time the operation is allowed to run. + - name: id-conflict-policy + type: string-enum + description: | + Policy for handling an Operation ID conflict with a + running operation. + enum-values: + - Fail + - UseExisting + - TerminateExisting + - name: id-reuse-policy + type: string-enum + description: | + Policy for re-using an Operation ID from a previously + closed operation. + enum-values: + - AllowDuplicate + - RejectDuplicate + - name: query-modifiers options: - name: reject-condition diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index c3efa843e..1b1a18e51 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -240,6 +240,8 @@ func (s *SharedServerSuite) SetupSuite() { "history.enableSignalWithStartFromWorkflow": true, "activity.enableStandalone": true, "activity.longPollTimeout": 2 * time.Second, + "nexusoperation.enableStandalone": true, + "history.enableChasmCallbacks": true, // this is overridden since we don't want caching to be enabled // while testing DescribeTaskQueue behaviour related to versioning "matching.TaskQueueInfoByBuildIdTTL": 0 * time.Second, From 700a87dd6d024d54ed8c35d69feb6b3a73601a04 Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Wed, 10 Jun 2026 11:34:42 -0400 Subject: [PATCH 14/28] worker deployment update-version-compute-config (#1001) Adds implementation of the `UpdateWorkerDeploymentVersionComputeConfig` gRPC API call with a `temporal worker deployment update-version-compute-config` CLI command. There is a `--remove` CLI flag that allows the user to explicitly delete the compute config from a Worker Deployment Version. --------- Signed-off-by: Jay Pipes --- internal/temporalcli/commands.gen.go | 36 +++++++ .../temporalcli/commands.worker.deployment.go | 99 +++++++++++++++++-- .../commands.worker.deployment_test.go | 52 ++++++++++ internal/temporalcli/commands.yaml | 67 +++++++++++++ 4 files changed, 247 insertions(+), 7 deletions(-) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 1d0a2a9a8..cfaa5e4d4 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -3587,6 +3587,7 @@ func NewTemporalWorkerDeploymentCommand(cctx *CommandContext, parent *TemporalWo s.Command.AddCommand(&NewTemporalWorkerDeploymentManagerIdentityCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkerDeploymentSetCurrentVersionCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkerDeploymentSetRampingVersionCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkerDeploymentUpdateVersionMetadataCommand(cctx, &s).Command) return &s } @@ -3941,6 +3942,41 @@ func NewTemporalWorkerDeploymentSetRampingVersionCommand(cctx *CommandContext, p return &s } +type TemporalWorkerDeploymentUpdateVersionComputeConfigCommand struct { + Parent *TemporalWorkerDeploymentCommand + Command cobra.Command + DeploymentVersionOptions + AwsLambdaFunctionArn string + AwsLambdaAssumeRoleArn string + AwsLambdaAssumeRoleExternalId string + Remove bool +} + +func NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx *CommandContext, parent *TemporalWorkerDeploymentCommand) *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand { + var s TemporalWorkerDeploymentUpdateVersionComputeConfigCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + 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\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\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().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) { + if err := s.run(cctx, args); err != nil { + cctx.Options.Fail(err) + } + } + return &s +} + type TemporalWorkerDeploymentUpdateVersionMetadataCommand struct { Parent *TemporalWorkerDeploymentCommand Command cobra.Command diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index a13d7dfc9..f82db4a6c 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -22,6 +22,7 @@ import ( "go.temporal.io/sdk/client" "go.temporal.io/sdk/converter" "go.temporal.io/sdk/worker" + "google.golang.org/protobuf/types/known/fieldmaskpb" ) type versionSummariesRowType struct { @@ -894,17 +895,21 @@ func validateAWSLambdaProviderDetails(details map[string]any) error { // awsLambdaProviderDetailsPayload returns the encoded Payload representing AWS // Lambda compute provider details. -func (c *TemporalWorkerDeploymentCreateVersionCommand) awsLambdaProviderDetailsPayload() (*commonpb.Payload, error) { +func awsLambdaProviderDetailsPayload( + functionARN string, + assumeRoleARN string, + assumeRoleExternalID string, +) (*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": c.AwsLambdaFunctionArn, + "arn": functionARN, } - if c.AwsLambdaAssumeRoleArn != "" { - providerDetails["role"] = c.AwsLambdaAssumeRoleArn + if assumeRoleARN != "" { + providerDetails["role"] = assumeRoleARN } - if c.AwsLambdaAssumeRoleExternalId != "" { - providerDetails["role_external_id"] = c.AwsLambdaAssumeRoleExternalId + if assumeRoleExternalID != "" { + providerDetails["role_external_id"] = assumeRoleExternalID } err := validateAWSLambdaProviderDetails(providerDetails) if err != nil { @@ -929,7 +934,11 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, var cc *computepb.ComputeConfig if c.AwsLambdaFunctionArn != "" { - detailsPayload, err := c.awsLambdaProviderDetailsPayload() + detailsPayload, err := awsLambdaProviderDetailsPayload( + c.AwsLambdaFunctionArn, + c.AwsLambdaAssumeRoleArn, + c.AwsLambdaAssumeRoleExternalId, + ) if err != nil { return err } @@ -969,6 +978,82 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, return nil } +func (c *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + ns := c.Parent.Parent.Namespace + buildID := c.BuildId + identity := c.Parent.Parent.Identity + deploymentName := c.DeploymentName + requestID := uuid.NewString() + + request := &workflowservice.UpdateWorkerDeploymentVersionComputeConfigRequest{ + Namespace: ns, + DeploymentVersion: &deployment.WorkerDeploymentVersion{ + DeploymentName: deploymentName, + BuildId: buildID, + }, + Identity: identity, + RequestId: requestID, + } + + if c.Remove { + if c.AwsLambdaFunctionArn != "" || c.AwsLambdaAssumeRoleArn != "" || c.AwsLambdaAssumeRoleExternalId != "" { + return fmt.Errorf("--remove cannot be combined with --aws-lambda-function-arn, --aws-lambda-assume-role-arn, or --aws-lambda-assume-role-external-id") + } + request.RemoveComputeConfigScalingGroups = []string{"default"} + } else { + detailsPayload, err := awsLambdaProviderDetailsPayload( + c.AwsLambdaFunctionArn, + c.AwsLambdaAssumeRoleArn, + c.AwsLambdaAssumeRoleExternalId, + ) + if err != nil { + return err + } + sg := &computepb.ComputeConfigScalingGroup{ + Provider: &computepb.ComputeProvider{ + Type: "aws-lambda", + Details: detailsPayload, + }, + Scaler: &computepb.ComputeScaler{ + // Hard-coded: no-sync is the only supported algorithm + // in temporal-auto-scaled-workers as of 2026-04-01. + Type: "no-sync", + }, + } + updatePaths := []string{ + "provider.details", + } + ccScalingGroups := map[string]*computepb.ComputeConfigScalingGroupUpdate{ + "default": &computepb.ComputeConfigScalingGroupUpdate{ + ScalingGroup: sg, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: updatePaths, + }, + }, + } + request.ComputeConfigScalingGroups = ccScalingGroups + + } + + _, err = cl.WorkflowService().UpdateWorkerDeploymentVersionComputeConfig(cctx, request) + if err != nil { + return fmt.Errorf("error updating worker deployment version compute config: %w", err) + } + + if c.Remove { + cctx.Printer.Println("Successfully removed worker deployment version compute config") + } else { + cctx.Printer.Println("Successfully updated worker deployment version compute config") + } + return nil +} + func (c *TemporalWorkerDeploymentDeleteVersionCommand) run(cctx *CommandContext, args []string) error { cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions) if err != nil { diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index 8b97854e3..7c6400179 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -1292,6 +1292,32 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { ) s.Error(res.Err) s.ErrorContains(res.Err, "missing required AWS Lambda provider detail: role") + + // Attempting to update the compute config for a non-existent WDV + // should fail. + nonExistingBuildID := "non-existing" + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", nonExistingBuildID, + "--aws-lambda-function-arn", invokeARN, + "--aws-lambda-assume-role-arn", assumeRoleARN, + "--aws-lambda-assume-role-external-id", assumeRoleExternalID, + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "build ID 'non-existing' not found") + + // Same for attempting to remove the compute config for a non-existent WDV. + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", nonExistingBuildID, + "--remove", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "build ID 'non-existing' not found") } // TODO(jaypipes): Enable this test when we have a way of ensuring AWS resource @@ -1384,4 +1410,30 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_LambdaComputeConfi 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. + invokeARN2 := "arn:aws:lambda:us-east-1:123456789012:function:MyExampleFunction:2" + 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-lambda-function-arn", invokeARN2, + "--aws-lambda-assume-role-arn", assumeRoleARN2, + "--aws-lambda-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") } diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 33523ffa0..3d60e1c56 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -979,6 +979,7 @@ commands: - worker deployment set-ramping-version - worker deployment delete-version - worker deployment update-version-metadata + - worker deployment update-version-compute-config - worker deployment manager-identity - name: temporal worker deployment create @@ -1319,6 +1320,72 @@ commands: Keys of entries to be deleted from metadata. Can be passed multiple times. + - name: temporal worker deployment update-version-compute-config + summary: Update compute configuration for a Version + description: | + Update compute configuration associated with a Worker Deployment + Version. + + For example, to update the AWS Lambda function ARN associated with an + existing Worker Deployment Version: + + ``` + temporal worker deployment update-version-compute-config \ + --deployment-name YourDeploymentName --build-id YourBuildID \ + --aws-lambda-function-arn UpdatedLambdaFunctionARN + ``` + + To update the AWS IAM role ARN that is assumed by the serverless worker + manager associated with an existing Worker Deployment Version: + + ``` + temporal worker deployment update-version-compute-config \ + --deployment-name YourDeploymentName --build-id YourBuildID \ + --aws-lambda-assume-role-arn UpdatedRoleARN + ``` + + If --remove is specified, the compute configuration for the Worker + Deployment Version will be removed: + + ``` + temporal worker deployment update-version-compute-config \ + --deployment-name YourDeploymentName --build-id YourBuildID \ + --remove + ``` + + If a Worker Deployment Version with the supplied BuildID does not exist, + this command will return an error. + + Note: This is an experimental feature and may change in the future. + option-sets: + - deployment-version + options: + - name: aws-lambda-function-arn + type: string + description: | + 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. + - name: aws-lambda-assume-role-arn + type: string + description: | + 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. + - 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. + - name: remove + type: bool + description: | + Removes any compute configuration associated with this Worker + Deployment Version. + - name: temporal worker deployment manager-identity summary: Manager Identity commands change the `ManagerIdentity` of a Worker Deployment description: | From bc187720391b3d10f9cc3237891b87a57641a17b Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Thu, 11 Jun 2026 14:58:00 -0700 Subject: [PATCH 15/28] Address Nexus Operation command issues (#1087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What changed? Address some issues found in the Nexus Operation command like: * Operation ID should be required * Some missing fields on start ## Checklist **Stability** - [ ] Breaking changes are marked with 💥 in the PR title and release notes - [ ] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [x] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [x] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [x] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [x] New flags don't duplicate an existing flag that serves the same purpose - [x] New flags do not have short aliases without strong justification - [x] Experimental features are marked with `(Experimental)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [x] All flags shown in help text and examples are implemented and functional - [x] Summaries use sentence case and have no trailing period - [x] Long descriptions end with a period and include at least one example invocation - [ ] Examples use long flags (`--namespace`, not `-n`), one flag per line - [x] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [x] Results go to stdout; errors and warnings go to stderr - [ ] Error messages are lowercase with no trailing punctuation **Tests** - [x] Added functional test(s) (`SharedServerSuite`) - [ ] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** ``` temporal operator nexus endpoint create --name my-endpoint --target-namespace default --target-task-queue my-tq ``` **Happy path** ``` $ temporal nexus operation start --endpoint my-endpoint --service my-service --operation my-op --operation-id my-op-1 --input '"hello"' Started Nexus Operation: Endpoint my-endpoint Service my-service Operation my-op OperationId my-op-1 RunId 7e7c8b2a-...-... Namespace default ``` **Error case** ``` $ temporal nexus operation start --service my-service --operation my-op --operation-id my-op-2 Error: required flag(s) "endpoint" not set ``` **Composition** — start an operation, then fetch its result, then list/describe: ``` $ temporal nexus operation start --endpoint my-endpoint --service my-service --operation my-op --operation-id my-op-3 --input '"world"' $ temporal nexus operation result --operation-id my-op-3 Results: Status COMPLETED Result "got: world" $ temporal nexus operation describe --operation-id my-op-3 OperationId my-op-3 RunId 7e7c8b2a-...-... Endpoint my-endpoint Service my-service Operation my-op Status Succeeded ... ``` --- .github/workflows/ci.yaml | 4 +- internal/temporalcli/commands.gen.go | 13 ++- .../temporalcli/commands.nexus_operation.go | 21 ++-- .../commands.nexus_operation_test.go | 100 +++++++++++++++--- internal/temporalcli/commands.yaml | 28 ++++- 5 files changed, 137 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2314b3ef3..6a5df116d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -108,7 +108,7 @@ jobs: - name: Test cloud API key env var if: ${{ matrix.cloudTestTarget && env.HAS_SECRETS == 'true' }} env: - TEMPORAL_ADDRESS: us-east-1.aws.api.temporal.io:7233 + TEMPORAL_ADDRESS: ca-central-1.aws.api.temporal.io:7233 TEMPORAL_NAMESPACE: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }} TEMPORAL_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} shell: bash @@ -117,7 +117,7 @@ jobs: - name: Test cloud API key arg if: ${{ matrix.cloudTestTarget && env.HAS_SECRETS == 'true' }} env: - TEMPORAL_ADDRESS: us-east-1.aws.api.temporal.io:7233 + TEMPORAL_ADDRESS: ca-central-1.aws.api.temporal.io:7233 TEMPORAL_NAMESPACE: ${{ vars.TEMPORAL_CLIENT_NAMESPACE }} shell: bash run: go run ./cmd/temporal workflow list --limit 2 --api-key ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index cfaa5e4d4..b416faced 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -360,8 +360,12 @@ type NexusOperationStartOptions struct { Operation string OperationId string ScheduleToCloseTimeout cliext.FlagDuration + ScheduleToStartTimeout cliext.FlagDuration + StartToCloseTimeout cliext.FlagDuration IdConflictPolicy cliext.FlagStringEnum IdReusePolicy cliext.FlagStringEnum + SearchAttribute []string + StaticSummary string FlagSet *pflag.FlagSet } @@ -373,13 +377,20 @@ func (v *NexusOperationStartOptions) BuildFlags(f *pflag.FlagSet) { _ = cobra.MarkFlagRequired(f, "service") f.StringVar(&v.Operation, "operation", "", "Nexus Operation name. Required.") _ = cobra.MarkFlagRequired(f, "operation") - f.StringVar(&v.OperationId, "operation-id", "", "Nexus Operation ID. If not supplied, a unique ID is generated.") + f.StringVar(&v.OperationId, "operation-id", "", "Nexus Operation ID. Required.") + _ = cobra.MarkFlagRequired(f, "operation-id") v.ScheduleToCloseTimeout = 0 f.Var(&v.ScheduleToCloseTimeout, "schedule-to-close-timeout", "Total time the operation is allowed to run.") + v.ScheduleToStartTimeout = 0 + f.Var(&v.ScheduleToStartTimeout, "schedule-to-start-timeout", "Maximum time to wait for an operation to be started (or completed synchronously) by a handler.") + v.StartToCloseTimeout = 0 + f.Var(&v.StartToCloseTimeout, "start-to-close-timeout", "Maximum time to wait for an asynchronous operation to complete after it has been started.") v.IdConflictPolicy = cliext.NewFlagStringEnum([]string{"Fail", "UseExisting", "TerminateExisting"}, "") f.Var(&v.IdConflictPolicy, "id-conflict-policy", "Policy for handling an Operation ID conflict with a running operation. Accepted values: Fail, UseExisting, TerminateExisting.") v.IdReusePolicy = cliext.NewFlagStringEnum([]string{"AllowDuplicate", "RejectDuplicate"}, "") f.Var(&v.IdReusePolicy, "id-reuse-policy", "Policy for re-using an Operation ID from a previously closed operation. Accepted values: AllowDuplicate, RejectDuplicate.") + f.StringArrayVar(&v.SearchAttribute, "search-attribute", nil, "Search Attribute in `KEY=VALUE` format. Keys must be identifiers, and values must be JSON values. For example: 'YourKey={\"your\": \"value\"}'. Can be passed multiple times.") + f.StringVar(&v.StaticSummary, "static-summary", "", "Static summary for the Nexus Operation for human consumption in UIs. Uses Temporal Markdown formatting, should be a single line. EXPERIMENTAL.") } type QueryModifiersOptions struct { diff --git a/internal/temporalcli/commands.nexus_operation.go b/internal/temporalcli/commands.nexus_operation.go index 4e58c11bf..6c0fcada3 100644 --- a/internal/temporalcli/commands.nexus_operation.go +++ b/internal/temporalcli/commands.nexus_operation.go @@ -8,7 +8,6 @@ import ( "time" "github.com/fatih/color" - "github.com/google/uuid" "github.com/temporalio/cli/internal/printer" "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" @@ -448,14 +447,22 @@ func buildNexusStartOptions(s *NexusOperationStartOptions, p *PayloadInputOption Service: s.Service, } - operationID := s.OperationId - if operationID == "" { - operationID = uuid.NewString() - } - opts := client.StartNexusOperationOptions{ - ID: operationID, + ID: s.OperationId, ScheduleToCloseTimeout: s.ScheduleToCloseTimeout.Duration(), + ScheduleToStartTimeout: s.ScheduleToStartTimeout.Duration(), + StartToCloseTimeout: s.StartToCloseTimeout.Duration(), + Summary: s.StaticSummary, + } + + if len(s.SearchAttribute) > 0 { + saMap, err := stringKeysJSONValues(s.SearchAttribute, false) + if err != nil { + return nexusCl, nil, fmt.Errorf("invalid search attribute values: %w", err) + } + if opts.SearchAttributes, err = mapToSearchAttributes(saMap); err != nil { + return nexusCl, nil, err + } } if s.IdConflictPolicy.Value != "" { diff --git a/internal/temporalcli/commands.nexus_operation_test.go b/internal/temporalcli/commands.nexus_operation_test.go index 277786955..0418c22f4 100644 --- a/internal/temporalcli/commands.nexus_operation_test.go +++ b/internal/temporalcli/commands.nexus_operation_test.go @@ -426,27 +426,15 @@ func (s *SharedServerSuite) TestNexusOperationStart_JSON() { s.Equal(opID, result.OperationId) s.NotEmpty(result.RunId) } - -func (s *SharedServerSuite) TestNexusOperationStart_ServerGeneratedID() { - endpointName, w := s.setupNexusEndpointAndWorker(s.T()) - defer w.Stop() - +func (s *SharedServerSuite) TestNexusOperationStart_OperationIDRequired() { res := s.Execute( "nexus", "operation", "start", - "--address", s.Address(), - "--endpoint", endpointName, + "--endpoint", "test-ep", "--service", "test-service", "--operation", "test-op", - "--input", `"hello"`, - "--output", "json", ) - s.NoError(res.Err) - - var result struct { - OperationId string `json:"operationId"` - } - s.NoError(json.Unmarshal(res.Stdout.Bytes(), &result)) - s.NotEmpty(result.OperationId, "server should generate an operation ID") + s.Error(res.Err) + s.ErrorContains(res.Err, "operation-id") } func (s *SharedServerSuite) TestNexusOperationStart_ScheduleToCloseTimeout() { @@ -725,4 +713,84 @@ func (s *SharedServerSuite) TestNexusOperationStart_MissingRequiredFlags() { "--operation-id", "some-id", ) s.Error(res.Err) + + // Missing --operation-id + res = s.Execute( + "nexus", "operation", "start", + "--endpoint", "test-ep", + "--service", "test-service", + "--operation", "test-op", + ) + s.Error(res.Err) +} + +func (s *SharedServerSuite) TestNexusOperationExecute_MissingOperationID() { + res := s.Execute( + "nexus", "operation", "execute", + "--endpoint", "test-ep", + "--service", "test-service", + "--operation", "test-op", + ) + s.Error(res.Err) +} + +func (s *SharedServerSuite) TestNexusOperationStart_OptionalStartFlags() { + endpointName, w := s.setupNexusEndpointAndWorker(s.T()) + defer w.Stop() + + opID := "optional-flags-op-" + uuid.NewString()[:8] + summary := "this is the operation summary" + uniqueKW := "nexus-sa-" + uuid.NewString()[:8] + + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", endpointName, + "--service", "test-service", + "--operation", "test-op", + "--operation-id", opID, + "--input", `"hello"`, + "--static-summary", summary, + "--search-attribute", fmt.Sprintf(`CustomKeywordField="%s"`, uniqueKW), + "--schedule-to-close-timeout", "1m", + "--schedule-to-start-timeout", "30s", + "--start-to-close-timeout", "30s", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), opID) + + // Describe and verify summary is reported back. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "describe", + "--address", s.Address(), + "--operation-id", opID, + ) + return res.Err == nil && strings.Contains(res.Stdout.String(), summary) + }, 30*time.Second, 500*time.Millisecond) + + // List with a query filter on the search attribute — confirms the SA was attached and indexed. + s.Eventually(func() bool { + res = s.Execute( + "nexus", "operation", "list", + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + ) + return res.Err == nil && strings.Contains(res.Stdout.String(), opID) + }, 30*time.Second, 500*time.Millisecond) +} + +func (s *SharedServerSuite) TestNexusOperationStart_InvalidSearchAttribute() { + res := s.Execute( + "nexus", "operation", "start", + "--address", s.Address(), + "--endpoint", "test-ep", + "--service", "test-service", + "--operation", "test-op", + "--operation-id", "some-id", + "--search-attribute", "CustomKeywordField=not-valid-json", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "invalid search attribute") } + diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 3d60e1c56..26630838a 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -5289,13 +5289,22 @@ option-sets: required: true - name: operation-id type: string - description: | - Nexus Operation ID. - If not supplied, a unique ID is generated. + description: Nexus Operation ID. + required: true - name: schedule-to-close-timeout type: duration description: | Total time the operation is allowed to run. + - name: schedule-to-start-timeout + type: duration + description: | + Maximum time to wait for an operation to be started (or completed + synchronously) by a handler. + - name: start-to-close-timeout + type: duration + description: | + Maximum time to wait for an asynchronous operation to complete + after it has been started. - name: id-conflict-policy type: string-enum description: | @@ -5313,6 +5322,19 @@ option-sets: enum-values: - AllowDuplicate - RejectDuplicate + - name: search-attribute + type: string[] + description: | + Search Attribute in `KEY=VALUE` format. + Keys must be identifiers, and values must be JSON values. + For example: 'YourKey={"your": "value"}'. + Can be passed multiple times. + - name: static-summary + type: string + experimental: true + description: | + Static summary for the Nexus Operation for human consumption in UIs. + Uses Temporal Markdown formatting, should be a single line. - name: query-modifiers options: From ad24393f346ced5104493d1dc0fe72e428d546ff Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Mon, 15 Jun 2026 10:10:49 -0400 Subject: [PATCH 16/28] prevent create of "empty" WorkerDeploymentVersions (#1091) Some customers were confused with the ability to create an "empty" WorkerDeploymentVersion with the `temporal worker deployment create-version` CLI command. This PR removes the ability to create a WDV that does not have any compute configuration associated with it. As we add support for non-Lambda compute providers like Google Cloud Run, we will update the conditional check in the CLI command implementation to verify that at least one "set" of compute provider configuration CLI options has been specified. Signed-off-by: Jay Pipes Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.gen.go | 4 +- .../temporalcli/commands.worker.deployment.go | 3 + .../commands.worker.deployment_test.go | 76 ++++++++++++------- internal/temporalcli/commands.yaml | 2 + 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index b416faced..42c17e7d4 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -3646,9 +3646,9 @@ 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\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\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\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\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\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\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.") diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index f82db4a6c..3dc25be79 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -957,6 +957,9 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, }, }, } + } else { + // We do not allow creation of an "empty" WDV. + return fmt.Errorf("missing configuration for compute provider") } request := &workflowservice.CreateWorkerDeploymentVersionRequest{ Namespace: ns, diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index 7c6400179..b1323532f 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -1153,49 +1153,67 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_EmptyComputeConfig // worker deployment create-version` CLI command. noComputeConfigBuildID := uuid.NewString() + // Attempting to create a WDV with no compute provider configuration should + // fail. res := s.Execute( "worker", "deployment", "create-version", "--address", s.Address(), "--deployment-name", deploymentName, "--build-id", noComputeConfigBuildID, ) - s.NoError(res.Err) - s.Contains(res.Stdout.String(), "Successfully created worker deployment version") + s.Error(res.Err) + s.ErrorContains(res.Err, "missing configuration for compute provider") - // Wait for the deployment version to appear - s.EventuallyWithT(func(t *assert.CollectT) { + // TODO(jaypipes): Uncomment the test block below once support for Cloud + // Run is added. When we add another compute provider, we can test for the + // return of a duplicate WDV. Until that point, we cannot create an "empty" + // WDV to test the build ID existence. + + /* res := s.Execute( - "worker", "deployment", "describe-version", + "worker", "deployment", "create-version", "--address", s.Address(), "--deployment-name", deploymentName, "--build-id", noComputeConfigBuildID, ) - assert.NoError(t, res.Err) - }, 30*time.Second, 100*time.Millisecond) + 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", noComputeConfigBuildID, + ) + assert.NoError(t, res.Err) + }, 30*time.Second, 100*time.Millisecond) - // Check that there is no compute config returned for this WDV - res = s.Execute( - "worker", "deployment", "describe-version", - "--address", s.Address(), - "--deployment-name", deploymentName, - "--build-id", noComputeConfigBuildID, - "--output", "json", - ) - s.NoError(res.Err) - var jsonOut jsonDeploymentVersionInfoType - s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) - s.Nil(jsonOut.ComputeConfig, "ComputeConfig should be nil.") + // Check that there is no compute config returned for this WDV + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", noComputeConfigBuildID, + "--output", "json", + ) + s.NoError(res.Err) + var jsonOut jsonDeploymentVersionInfoType + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.Nil(jsonOut.ComputeConfig, "ComputeConfig should be nil.") - // Attempting to create a WDV with the same BuildID should fail with a - // conflict error. - res = s.Execute( - "worker", "deployment", "create-version", - "--address", s.Address(), - "--deployment-name", deploymentName, - "--build-id", noComputeConfigBuildID, - ) - s.Error(res.Err) - s.ErrorContains(res.Err, "already exists") + // Attempting to create a WDV with the same BuildID should fail with a + // conflict error. + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", noComputeConfigBuildID, + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "already exists") + */ } func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 26630838a..bc655419a 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -1092,6 +1092,8 @@ commands: If a Worker Deployment Version with the supplied BuildID already exists, this command will return an error. + Returns an error if all compute configuration fields are empty. + Note: This is an experimental feature and may change in the future. option-sets: - deployment-version From 79e9f8f23cd63a244c2606fa04ad4b6ce7327b4c Mon Sep 17 00:00:00 2001 From: Sean Kane Date: Mon, 15 Jun 2026 08:49:27 -0600 Subject: [PATCH 17/28] fix: tls is not added for profiles without tls (#1089) ## Related issues Closes #1077 ## What changed? The helper to iterate over `envConfigPropsToFieldNames` has a side effect if the parent is nil and `failIfParentNotFound=true` it will set `confProfile.TLS = &envconvfig.ClientConfigTLS{}`. When the `k=="tls"` check happens, `confProfile.TLS != nil` and it will emit `tls: true`. Map iteration is non-deterministic and there are 9 `tls.*` keys vs 1 `tls` key so the mutation is more likely to happen before checking the `tls` key. ## Checklist **Tests** - [X] Added unit test Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.config.go | 8 ++++++- internal/temporalcli/commands.config_test.go | 24 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/internal/temporalcli/commands.config.go b/internal/temporalcli/commands.config.go index e8a423dd0..0b88bfaac 100644 --- a/internal/temporalcli/commands.config.go +++ b/internal/temporalcli/commands.config.go @@ -111,13 +111,19 @@ func (c *TemporalConfigGetCommand) run(cctx *CommandContext, _ []string) error { } return cctx.Printer.PrintStructured(tomlConf.Profiles[profileName], printer.StructuredOptions{}) } else { + // Capture whether TLS is configured before the loop below. Looking up + // any "tls.*" property via reflectEnvConfigProp lazily initializes + // confProfile.TLS to a non-nil empty struct, which would otherwise make + // TLS appear configured when it is not (#1077). + tlsConfigured := confProfile.TLS != nil + // Get every property individually as a property-value pair except zero // vals var props []prop for k := range envConfigPropsToFieldNames { // TLS is a special case if k == "tls" { - if confProfile.TLS != nil { + if tlsConfigured { props = append(props, prop{Property: "tls", Value: true}) } continue diff --git a/internal/temporalcli/commands.config_test.go b/internal/temporalcli/commands.config_test.go index 5cc89ec86..9375402bf 100644 --- a/internal/temporalcli/commands.config_test.go +++ b/internal/temporalcli/commands.config_test.go @@ -151,6 +151,30 @@ disable_host_verification = true`)) } } +func TestConfig_Get_NoTLSWhenUnconfigured(t *testing.T) { + // Regression test for #1077: `config get` (table output) must not display + // "tls true" for a profile that has no TLS configuration. + h := NewCommandHarness(t) + defer h.Close() + + f, err := os.CreateTemp("", "") + h.NoError(err) + defer os.Remove(f.Name()) + _, err = f.Write([]byte(` +[profile.devtest] +address = "localhost:17233" +namespace = "default"`)) + f.Close() + h.NoError(err) + h.Options.EnvLookup = EnvLookupMap{"TEMPORAL_CONFIG_FILE": f.Name(), "TEMPORAL_PROFILE": "devtest"} + + res := h.Execute("config", "get") + h.NoError(res.Err) + h.Contains(res.Stdout.String(), "localhost:17233") + // No TLS section was configured, so "tls" should not appear at all. + h.NotContains(res.Stdout.String(), "tls") +} + func TestConfig_TLS_Boolean(t *testing.T) { h := NewCommandHarness(t) defer h.Close() From 3d715e2cbf39db881673d3954003e48586104522 Mon Sep 17 00:00:00 2001 From: Nasit Sarwar Sony Date: Tue, 16 Jun 2026 13:56:30 -0700 Subject: [PATCH 18/28] Add client-authority support to config profile settings (#1084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related issues Closes #1013 ## What changed? - Added `authority` to `envConfigPropsToFieldNames` map so `client-authority` can be get/set via config commands - Wired `cfg.ClientAuthority` to `profile.Authority` in `client.go` - Updated `DefaultConfigFilePath()` call sites for envconfig v1.0.2 signature change (returns `string` instead of `(string, error)`) - Bumped `go.temporal.io/sdk/contrib/envconfig` to v1.0.2 - Added `authority` field to config tests ## Checklist **Stability** - ✅ No breaking changes - ✅ Works against OSS server - ✅ Flag named after API concept (client-authority) - ✅ No duplicate flags - ✅ No short aliases - ✅ Added test coverage in commands.config_test.go **Design** - ✅ Works against OSS server - ✅ Flag named after API concept (client-authority) - ✅ No duplicate flags - ✅ No short aliases **Tests** - [ ] Added functional test (SharedServerSuite) — config set/get test needed ## Manual tests **Setup** ``` temporal server start-dev --headless ``` **Happy path** ``` $ temporal config set --prop authority --value my-authority $ temporal config get --prop authority Property Value authority my-authority ``` **Error case** ``` $ temporal config set --prop authority Error: required flag --value not set ``` --- cliext/client.go | 5 +++++ cliext/config.oauth.go | 6 +----- go.mod | 2 +- go.sum | 4 ++-- internal/temporalcli/commands.config.go | 7 +++---- internal/temporalcli/commands.config_test.go | 5 +++++ 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cliext/client.go b/cliext/client.go index 8512f34ba..48eaa7508 100644 --- a/cliext/client.go +++ b/cliext/client.go @@ -132,6 +132,11 @@ func (b *ClientOptionsBuilder) Build(ctx context.Context) (client.Options, error profile.APIKey = cfg.ApiKey } + // Set client authority on profile if provided + if cfg.ClientAuthority != "" { + profile.Authority = cfg.ClientAuthority + } + // Handle gRPC metadata from flags. if len(cfg.GrpcMeta) > 0 { grpcMetaFromArg, err := parseKeyValuePairs(cfg.GrpcMeta) diff --git a/cliext/config.oauth.go b/cliext/config.oauth.go index 9593bdde0..d98b9011a 100644 --- a/cliext/config.oauth.go +++ b/cliext/config.oauth.go @@ -141,11 +141,7 @@ func resolveConfigAndProfile(configFilePath, profileName string, envLookup envco configFilePath, _ = envLookup.LookupEnv("TEMPORAL_CONFIG_FILE") } if configFilePath == "" { - var err error - configFilePath, err = envconfig.DefaultConfigFilePath() - if err != nil { - return "", "", fmt.Errorf("failed to get default config path: %w", err) - } + configFilePath = envconfig.DefaultConfigFilePath() } // Resolve profile name. diff --git a/go.mod b/go.mod index 6921fd3ca..90fe02df5 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/temporalio/ui-server/v2 v2.49.1 go.temporal.io/api v1.62.13 go.temporal.io/sdk v1.44.1 - go.temporal.io/sdk/contrib/envconfig v1.0.0 + go.temporal.io/sdk/contrib/envconfig v1.0.2 go.temporal.io/server v1.32.0-157.0 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f golang.org/x/mod v0.35.0 diff --git a/go.sum b/go.sum index 31903bc40..01c1676b9 100644 --- a/go.sum +++ b/go.sum @@ -475,8 +475,8 @@ go.temporal.io/auto-scaled-workers v0.0.0-20260407181057-edd947d743d2 h1:1hKeH3G go.temporal.io/auto-scaled-workers v0.0.0-20260407181057-edd947d743d2/go.mod h1:T8dnzVPeO+gaUTj9eDgm/lT2lZH4+JXNvrGaQGyVi50= go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= -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= +go.temporal.io/sdk/contrib/envconfig v1.0.2 h1:MGHfsuPUtsf7X9M6WYn3zYJj/mWsuYHnA1uuiL0KEuE= +go.temporal.io/sdk/contrib/envconfig v1.0.2/go.mod h1:MuMiH7hksps2uXnmKuAWaP9P6WbkSDy62kl64t1VJVg= go.temporal.io/server v1.32.0-157.0 h1:nzFqNwx+5lXsT0/DSiFyR5vHMnDcT3PVAvmRDqCUn38= go.temporal.io/server v1.32.0-157.0/go.mod h1:a76wf30/s28JXh+3nDQtQi8KzOfRQEddpebvmr/oQL4= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/internal/temporalcli/commands.config.go b/internal/temporalcli/commands.config.go index 0b88bfaac..fb4dc1d81 100644 --- a/internal/temporalcli/commands.config.go +++ b/internal/temporalcli/commands.config.go @@ -259,6 +259,7 @@ var envConfigPropsToFieldNames = map[string]string{ "address": "Address", "namespace": "Namespace", "api_key": "APIKey", + "authority": "Authority", "tls": "TLS", "tls.disabled": "Disabled", "tls.client_cert_path": "ClientCertPath", @@ -317,10 +318,8 @@ func writeEnvConfigFile(cctx *CommandContext, conf *envconfig.ClientConfig) erro if configFile == "" { configFile, _ = cctx.Options.EnvLookup.LookupEnv("TEMPORAL_CONFIG_FILE") if configFile == "" { - var err error - if configFile, err = envconfig.DefaultConfigFilePath(); err != nil { - return err - } + configFile = envconfig.DefaultConfigFilePath() + } } diff --git a/internal/temporalcli/commands.config_test.go b/internal/temporalcli/commands.config_test.go index 9375402bf..ee6e484b7 100644 --- a/internal/temporalcli/commands.config_test.go +++ b/internal/temporalcli/commands.config_test.go @@ -25,6 +25,7 @@ func TestConfig_Get(t *testing.T) { address = "my-address" namespace = "my-namespace" api_key = "my-api-key" +authority = "my-authority" codec = { endpoint = "my-endpoint", auth = "my-auth" } grpc_meta = { some-heAder1 = "some-value1", some-header2 = "some-value2", some_heaDer3 = "some-value3" } some_future_key = "some future value not handled" @@ -74,6 +75,7 @@ disable_host_verification = true`)) "address": "my-address", "namespace": "my-namespace", "api_key": "my-api-key", + "authority": "my-authority", "codec.endpoint": "my-endpoint", "codec.auth": "my-auth", "grpc_meta.some-header1": "some-value1", @@ -117,6 +119,7 @@ disable_host_verification = true`)) h.JSONEq(`{ "address": "my-address", "api_key": "my-api-key", + "authority": "my-authority", "codec": { "auth": "my-auth", "endpoint": "my-endpoint" @@ -344,6 +347,7 @@ func TestConfig_Set(t *testing.T) { "address": "my-address", "namespace": "my-namespace", "api_key": "my-api-key", + "authority": "my-authority", "codec.endpoint": "my-endpoint", "codec.auth": "my-auth", "grpc_meta.sOme_header1": "some-value1", @@ -374,6 +378,7 @@ func TestConfig_Set(t *testing.T) { "address": "my-address", "namespace": "my-namespace", "api_key": "my-api-key", + "authority": "my-authority", "tls": map[string]any{ "disabled": true, "client_cert_path": "my-client-cert-path", From 4f5bb7ca8f95632d8a0b6662eadb15420ba27a89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:49:40 -0500 Subject: [PATCH 19/28] Bump google.golang.org/grpc from 1.80.0 to 1.81.1 (#1063) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.80.0 to 1.81.1.
Release notes

Sourced from google.golang.org/grpc's releases.

Release 1.81.1

Security

  • xds/rbac: Fix a potential authorization bypass caused by incorrectly falling through URI/DNS SANs to Subject Distinguished Name (DN) when matching the authenticated principal name. With this fix, only the first non-empty identity source will be used, as per gRFC A41. (#9111)

Bug Fixes

  • otel: Segregate client and server RPC information used for metrics and traces, to avoid one overwriting the other. (#9081)

Release 1.81.0

Behavior Changes

  • balancer/rls: Switch gauge metrics to asynchronous emission (once per collection cycle) to reduce telemetry noise and align with other gRPC language implementations. (#8808)

Dependencies

  • Minimum supported Go version is now 1.25. (#8969)

Bug Fixes

  • xds: Use the leaf cluster's security config for the TLS handshake instead of the aggregate cluster's config. (#8956)
  • transport: Send a RST_STREAM when receiving an END_STREAM when the stream is not already half-closed. (#8832)
  • xds: Fix ADS resource name validation to prevent a panic. (#8970)

New Features

  • grpc/stats: Add support for custom labels in per-call metrics (gRFC A108). (#9008)
  • xds: Add support for Server Name Indication (SNI) and SAN validation (gRFC A101). Disabled by default. To enable, set GRPC_EXPERIMENTAL_XDS_SNI=true environment variable. (#9016)
  • xds: Add support to control which fields get propagated from ORCA backend metric reports to LRS load reports (gRFC A85). Disabled by default. To enable, set GRPC_EXPERIMENTAL_XDS_ORCA_LRS_PROPAGATION=true. (#9005)
  • xds: Add metrics to track xDS client connectivity and cached resource state (gRFC A78). (#8807)
  • stats/otel: Enhance grpc.subchannel.disconnections metric by adding disconnection reason to the grpc.disconnect_error label (gRFC A94). This provides granular insights into why subchannels are closing. (#8973)
  • mem: Add mem.Buffer.Slice() API to slice the buffer like a slice. (#8977)

Performance Improvements

  • alts: Pool read buffers to lower memory utilization when sockets are unreadable. (#8964)
  • transport: Pool HTTP/2 framer read buffers to reduce idle memory consumption. Currently limited to Linux for ALTS and non-encrypted transports (TCP, Unix). To disable, set GRPC_GO_EXPERIMENTAL_HTTP_FRAMER_READ_BUFFER_POOLING=false and report any issues. (#9032)
Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 90fe02df5..75eb2b27f 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( golang.org/x/mod v0.35.0 golang.org/x/term v0.42.0 golang.org/x/tools v0.44.0 - google.golang.org/grpc v1.80.0 + google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.51.0 diff --git a/go.sum b/go.sum index 01c1676b9..545168789 100644 --- a/go.sum +++ b/go.sum @@ -625,8 +625,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 h1: 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.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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= From 8c3e3b2e07b5e0faa590ef73ea6d9ab1a07561f7 Mon Sep 17 00:00:00 2001 From: Jessica Laughlin Date: Wed, 24 Jun 2026 20:42:18 -0400 Subject: [PATCH 20/28] Clarify activity pause timeout behavior (#1099) ## What changed? Clarifies `temporal activity pause --help` to state that pausing an Activity does not stop or extend the Activity's Schedule-To-Close Timeout. Regenerated `internal/temporalcli/commands.gen.go` from `internal/temporalcli/commands.yaml`. ## Why? The existing help explains that Pause prevents future retries and does not interrupt some in-flight attempts, but it does not mention that Schedule-To-Close continues running while paused. That can surprise operators because "pause" can imply the Activity is safe from timing out. This adds the timeout caveat and points users to `temporal activity update-options` before a long pause. ## Testing ```bash go run ./cmd/gen-commands \ -input internal/temporalcli/commands.yaml \ -pkg temporalcli \ -context "*CommandContext" \ > internal/temporalcli/commands.gen.go go run ./cmd/temporal activity pause --help go test ./cmd/gen-commands ``` Verified the help output includes the Schedule-To-Close warning. --- internal/temporalcli/commands.gen.go | 4 ++-- internal/temporalcli/commands.yaml | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 42c17e7d4..7ccb14e3d 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -788,9 +788,9 @@ func NewTemporalActivityPauseCommand(cctx *CommandContext, parent *TemporalActiv s.Command.Use = "pause [flags]" s.Command.Short = "Pause an Activity" if hasHighlighting { - s.Command.Long = "Pause an Activity. Not supported for Standalone Activities.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run until the next\ntime it fails, completes, or times out, at which point the pause will kick in.\n\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nSpecify the Activity and Workflow IDs:\n\n\x1b[1mtemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\x1b[0m\n\nTo later unpause the activity, see unpause. You may also want to\nreset the activity to unpause it while also starting it from the beginning." + s.Command.Long = "Pause an Activity. Not supported for Standalone Activities.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run until the next\ntime it fails, completes, or times out, at which point the pause will kick in.\n\nPause does not stop or extend the Activity's Schedule-To-Close Timeout.\nA paused Activity can still time out. Use \x1b[1mtemporal activity update-options\x1b[0m\nto extend timeout settings before a long pause.\n\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nSpecify the Activity and Workflow IDs:\n\n\x1b[1mtemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\x1b[0m\n\nTo later unpause the activity, see unpause. You may also want to\nreset the activity to unpause it while also starting it from the beginning." } else { - s.Command.Long = "Pause an Activity. Not supported for Standalone Activities.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run until the next\ntime it fails, completes, or times out, at which point the pause will kick in.\n\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nSpecify the Activity and Workflow IDs:\n\n```\ntemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n```\n\nTo later unpause the activity, see unpause. You may also want to\nreset the activity to unpause it while also starting it from the beginning." + s.Command.Long = "Pause an Activity. Not supported for Standalone Activities.\n\nIf the Activity is not currently running (e.g. because it previously\nfailed), it will not be run again until it is unpaused.\n\nHowever, if the Activity is currently running, it will run until the next\ntime it fails, completes, or times out, at which point the pause will kick in.\n\nPause does not stop or extend the Activity's Schedule-To-Close Timeout.\nA paused Activity can still time out. Use `temporal activity update-options`\nto extend timeout settings before a long pause.\n\nIf the Activity is on its last retry attempt and fails, the failure will\nbe returned to the caller, just as if the Activity had not been paused.\n\nSpecify the Activity and Workflow IDs:\n\n```\ntemporal activity pause \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n```\n\nTo later unpause the activity, see unpause. You may also want to\nreset the activity to unpause it while also starting it from the beginning." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to pause. Required.") diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index bc655419a..5ec1b9d66 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -471,6 +471,10 @@ commands: However, if the Activity is currently running, it will run until the next time it fails, completes, or times out, at which point the pause will kick in. + Pause does not stop or extend the Activity's Schedule-To-Close Timeout. + A paused Activity can still time out. Use `temporal activity update-options` + to extend timeout settings before a long pause. + If the Activity is on its last retry attempt and fails, the failure will be returned to the caller, just as if the Activity had not been paused. From b2ee8a2aad71749d831fb7645577dbea8347dc6a Mon Sep 17 00:00:00 2001 From: Shivam <57200924+Shivs11@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:44:43 -0400 Subject: [PATCH 21/28] Add one-time move versioning override CLI support (#1093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related issues ## What changed? - WISOTT - For more information, you can also view the relevant PR's: 1. Server PR: https://github.com/temporalio/temporal/pull/10763 2. API PR: https://github.com/temporalio/api/pull/808 ## Checklist **Stability** - [ ] Breaking changes are marked with 💥 in the PR title and release notes - [ ] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [ ] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [x] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [x] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [x] New flags don't duplicate an existing flag that serves the same purpose - [x] New flags do not have short aliases without strong justification - [ ] Experimental features are marked with `(Experimental)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [ ] All flags shown in help text and examples are implemented and functional - [ ] Summaries use sentence case and have no trailing period - [ ] Long descriptions end with a period and include at least one example invocation - [ ] Examples use long flags (`--namespace`, not `-n`), one flag per line - [ ] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [x] Results go to stdout; errors and warnings go to stderr - [x] Error messages are lowercase with no trailing punctuation **Tests** - [x] Added functional test(s) (`SharedServerSuite`) - [ ] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** Requires a server that includes temporalio/temporal#10763 and a workflow whose Task Queue is present in the target Worker Deployment Version. ```bash temporal server start-dev --headless temporal workflow start \ --type YourWorkflowType \ --task-queue YourTaskQueue \ --workflow-id YourWorkflowId ``` Use an existing Worker Deployment Version for the target: ```text Deployment name: YourDeploymentName Build ID: YourBuildId ``` **Happy path** ```bash temporal workflow update-options \ --workflow-id YourWorkflowId \ --versioning-override-behavior one_time \ --versioning-override-deployment-name YourDeploymentName \ --versioning-override-build-id YourBuildId ``` Expected output: ```text Update workflow options succeeded ``` Verify table output: ```bash temporal workflow describe \ --workflow-id YourWorkflowId ``` Expected: `OverrideBehavior` is `OneTime`, with `OverrideTargetVersionDeploymentName` and `OverrideTargetVersionBuildId` set. Verify JSON output: ```bash temporal workflow describe \ --workflow-id YourWorkflowId \ --output json ``` Expected: `versioningInfo.versioningOverride.oneTime.targetDeploymentVersion` contains the deployment name and build ID. **Error case** ```bash temporal workflow update-options \ --workflow-id YourWorkflowId \ --versioning-override-behavior one_time ``` Expected: ```text Error: missing deployment name and/or build id with 'one_time' behavior ``` ```bash echo $? ``` Expected: ```text 1 ``` **Composition** ```bash temporal workflow update-options \ --query 'WorkflowId = "YourWorkflowId"' \ --yes \ --versioning-override-behavior one_time \ --versioning-override-deployment-name YourDeploymentName \ --versioning-override-build-id YourBuildId ``` Expected: a batch update-options job is started. Use `temporal batch describe` to inspect the batch, then `temporal workflow describe` on an affected Workflow Execution to verify the one-time override. --- go.mod | 18 +-- go.sum | 32 ++--- internal/temporalcli/commands.gen.go | 20 ++-- internal/temporalcli/commands.workflow.go | 108 +++++++++++++---- .../commands.workflow_reset_update_options.go | 42 +++---- ...ands.workflow_reset_update_options_test.go | 112 ++++++++++++++++++ .../temporalcli/commands.workflow_test.go | 107 ++++++++++++++++- .../temporalcli/commands.workflow_view.go | 14 ++- .../commands.workflow_view_test.go | 107 +++++++++++++++++ internal/temporalcli/commands.yaml | 29 +++-- 10 files changed, 495 insertions(+), 94 deletions(-) diff --git a/go.mod b/go.mod index 75eb2b27f..8c5fa38e8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/temporalio/cli -go 1.26.3 +go 1.26.4 require ( github.com/BurntSushi/toml v1.4.0 @@ -17,13 +17,13 @@ require ( github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0 github.com/temporalio/ui-server/v2 v2.49.1 - go.temporal.io/api v1.62.13 + go.temporal.io/api v1.63.0 go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/envconfig v1.0.2 - go.temporal.io/server v1.32.0-157.0 + go.temporal.io/server v1.32.0-158.0 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f golang.org/x/mod v0.35.0 - golang.org/x/term v0.42.0 + golang.org/x/term v0.43.0 golang.org/x/tools v0.44.0 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 @@ -192,7 +192,7 @@ require ( 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.10.0 // indirect - go.temporal.io/auto-scaled-workers v0.0.0-20260407181057-edd947d743d2 // indirect + go.temporal.io/auto-scaled-workers v0.0.0-20260622220320-9b1e3849116d // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect @@ -201,12 +201,12 @@ require ( go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.276.0 // indirect google.golang.org/genproto v0.0.0-20260420184626-e10c466a9529 // indirect diff --git a/go.sum b/go.sum index 545168789..30a9ab4b0 100644 --- a/go.sum +++ b/go.sum @@ -469,16 +469,16 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.temporal.io/api v1.62.13 h1:xMa8Nt5oAMX+LvlCJA44wjTCc1H09i2rG9poB1/xvH4= -go.temporal.io/api v1.62.13/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= -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/api v1.63.0 h1:YZFOTA0/thRUIUC4qunAWdHhPh/IG4vy/+WjfEvT+ZE= +go.temporal.io/api v1.63.0/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= +go.temporal.io/auto-scaled-workers v0.0.0-20260622220320-9b1e3849116d h1:f7+FCJHSrYWz9zvJp2OxKo8Fu/dsBUdnZZA+m5CEOS0= +go.temporal.io/auto-scaled-workers v0.0.0-20260622220320-9b1e3849116d/go.mod h1:HOnbQTZCW18EPcutFHTkZrDcGO4tUjQ8N2pjDyrGhrY= go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= go.temporal.io/sdk/contrib/envconfig v1.0.2 h1:MGHfsuPUtsf7X9M6WYn3zYJj/mWsuYHnA1uuiL0KEuE= go.temporal.io/sdk/contrib/envconfig v1.0.2/go.mod h1:MuMiH7hksps2uXnmKuAWaP9P6WbkSDy62kl64t1VJVg= -go.temporal.io/server v1.32.0-157.0 h1:nzFqNwx+5lXsT0/DSiFyR5vHMnDcT3PVAvmRDqCUn38= -go.temporal.io/server v1.32.0-157.0/go.mod h1:a76wf30/s28JXh+3nDQtQi8KzOfRQEddpebvmr/oQL4= +go.temporal.io/server v1.32.0-158.0 h1:M+rXAmFztzBrIXNYPZyUPJFZO6yHPKP1lRTrQNUKvbs= +go.temporal.io/server v1.32.0-158.0/go.mod h1:5HHOkpEzSikoCT1CL54dtJC+qpyAwHKz47bHZHJAIp4= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -509,8 +509,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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +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/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= @@ -543,8 +543,8 @@ 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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +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.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= @@ -571,15 +571,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +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/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.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +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/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= @@ -587,8 +587,8 @@ 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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +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.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= diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 7ccb14e3d..ce21f9d26 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -415,11 +415,11 @@ type WorkflowUpdateOptionsOptions struct { func (v *WorkflowUpdateOptionsOptions) BuildFlags(f *pflag.FlagSet) { v.FlagSet = f - v.VersioningOverrideBehavior = cliext.NewFlagStringEnum([]string{"pinned", "auto_upgrade"}, "") - f.Var(&v.VersioningOverrideBehavior, "versioning-override-behavior", "Override the versioning behavior of a Workflow. Accepted values: pinned, auto_upgrade. Required.") + v.VersioningOverrideBehavior = cliext.NewFlagStringEnum([]string{"pinned", "one_time", "auto_upgrade"}, "") + f.Var(&v.VersioningOverrideBehavior, "versioning-override-behavior", "Override the versioning behavior of a Workflow. Accepted values: pinned, one_time, auto_upgrade. Required.") _ = cobra.MarkFlagRequired(f, "versioning-override-behavior") - f.StringVar(&v.VersioningOverrideDeploymentName, "versioning-override-deployment-name", "", "When overriding to a `pinned` behavior, specifies the Deployment Name of the version to target.") - f.StringVar(&v.VersioningOverrideBuildId, "versioning-override-build-id", "", "When overriding to a `pinned` behavior, specifies the Build ID of the version to target.") + f.StringVar(&v.VersioningOverrideDeploymentName, "versioning-override-deployment-name", "", "When overriding to a `pinned` or `one_time` behavior, specifies the Deployment Name of the version to target.") + f.StringVar(&v.VersioningOverrideBuildId, "versioning-override-build-id", "", "When overriding to a `pinned` or `one_time` behavior, specifies the Build ID of the version to target.") } type ActivityReferenceOptions struct { @@ -5063,16 +5063,16 @@ func NewTemporalWorkflowUpdateOptionsCommand(cctx *CommandContext, parent *Tempo s.Command.Use = "update-options [flags]" s.Command.Short = "Change Workflow Execution Options" if hasHighlighting { - s.Command.Long = "Modify properties of Workflow Executions:\n\n\x1b[1mtemporal workflow update-options [options]\x1b[0m\n\nIt can override the Worker Deployment configuration of a\nWorkflow Execution, which controls Worker Versioning.\n\nFor example, to force Workers in the current Deployment execute the\nnext Workflow Task change behavior to \x1b[1mauto_upgrade\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior auto_upgrade\x1b[0m\n\nor to pin the workflow execution to a Worker Deployment, set behavior\nto \x1b[1mpinned\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior pinned \\\n --versioning-override-deployment-name YourDeploymentName \\\n --versioning-override-build-id YourDeploymentBuildId\x1b[0m\n\nTo remove any previous overrides, set the behavior to\n\x1b[1munspecified\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior unspecified\x1b[0m\n\nTo see the current override use \x1b[1mtemporal workflow describe\x1b[0m" + s.Command.Long = "Modify properties of Workflow Executions:\n\n\x1b[1mtemporal workflow update-options [options]\x1b[0m\n\nIt can override the Worker Deployment configuration of a\nWorkflow Execution, which controls Worker Versioning.\n\nFor example, to force Workers in the current Deployment execute the\nnext Workflow Task change behavior to \x1b[1mauto_upgrade\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior auto_upgrade\x1b[0m\n\nor to pin the workflow execution to a Worker Deployment, set behavior\nto \x1b[1mpinned\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior pinned \\\n --versioning-override-deployment-name YourDeploymentName \\\n --versioning-override-build-id YourDeploymentBuildId\x1b[0m\n\nor to move the workflow execution to a Worker Deployment Version until\nthe next Workflow Task completes there, set behavior to \x1b[1mone_time\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior one_time \\\n --versioning-override-deployment-name YourDeploymentName \\\n --versioning-override-build-id YourDeploymentBuildId\x1b[0m\n\nTo remove any previous overrides, set the behavior to\n\x1b[1munspecified\x1b[0m:\n\n\x1b[1mtemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior unspecified\x1b[0m\n\nTo see the current override use \x1b[1mtemporal workflow describe\x1b[0m" } else { - s.Command.Long = "Modify properties of Workflow Executions:\n\n```\ntemporal workflow update-options [options]\n```\n\nIt can override the Worker Deployment configuration of a\nWorkflow Execution, which controls Worker Versioning.\n\nFor example, to force Workers in the current Deployment execute the\nnext Workflow Task change behavior to `auto_upgrade`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior auto_upgrade\n```\n\nor to pin the workflow execution to a Worker Deployment, set behavior\nto `pinned`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior pinned \\\n --versioning-override-deployment-name YourDeploymentName \\\n --versioning-override-build-id YourDeploymentBuildId\n```\n\nTo remove any previous overrides, set the behavior to\n`unspecified`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior unspecified\n```\n\nTo see the current override use `temporal workflow describe`" + s.Command.Long = "Modify properties of Workflow Executions:\n\n```\ntemporal workflow update-options [options]\n```\n\nIt can override the Worker Deployment configuration of a\nWorkflow Execution, which controls Worker Versioning.\n\nFor example, to force Workers in the current Deployment execute the\nnext Workflow Task change behavior to `auto_upgrade`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior auto_upgrade\n```\n\nor to pin the workflow execution to a Worker Deployment, set behavior\nto `pinned`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior pinned \\\n --versioning-override-deployment-name YourDeploymentName \\\n --versioning-override-build-id YourDeploymentBuildId\n```\n\nor to move the workflow execution to a Worker Deployment Version until\nthe next Workflow Task completes there, set behavior to `one_time`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior one_time \\\n --versioning-override-deployment-name YourDeploymentName \\\n --versioning-override-build-id YourDeploymentBuildId\n```\n\nTo remove any previous overrides, set the behavior to\n`unspecified`:\n\n```\ntemporal workflow update-options \\\n --workflow-id YourWorkflowId \\\n --versioning-override-behavior unspecified\n```\n\nTo see the current override use `temporal workflow describe`" } s.Command.Args = cobra.NoArgs - s.VersioningOverrideBehavior = cliext.NewFlagStringEnum([]string{"unspecified", "pinned", "auto_upgrade"}, "") - s.Command.Flags().Var(&s.VersioningOverrideBehavior, "versioning-override-behavior", "Override the versioning behavior of a Workflow. Accepted values: unspecified, pinned, auto_upgrade. Required.") + s.VersioningOverrideBehavior = cliext.NewFlagStringEnum([]string{"unspecified", "pinned", "one_time", "auto_upgrade"}, "") + s.Command.Flags().Var(&s.VersioningOverrideBehavior, "versioning-override-behavior", "Override the versioning behavior of a Workflow. Accepted values: unspecified, pinned, one_time, auto_upgrade. Required.") _ = cobra.MarkFlagRequired(s.Command.Flags(), "versioning-override-behavior") - s.Command.Flags().StringVar(&s.VersioningOverrideDeploymentName, "versioning-override-deployment-name", "", "When overriding to a `pinned` behavior, specifies the Deployment Name of the version to target.") - s.Command.Flags().StringVar(&s.VersioningOverrideBuildId, "versioning-override-build-id", "", "When overriding to a `pinned` behavior, specifies the Build ID of the version to target.") + s.Command.Flags().StringVar(&s.VersioningOverrideDeploymentName, "versioning-override-deployment-name", "", "When overriding to a `pinned` or `one_time` behavior, specifies the Deployment Name of the version to target.") + s.Command.Flags().StringVar(&s.VersioningOverrideBuildId, "versioning-override-build-id", "", "When overriding to a `pinned` or `one_time` behavior, specifies the Build ID of the version to target.") s.SingleWorkflowOrBatchOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { diff --git a/internal/temporalcli/commands.workflow.go b/internal/temporalcli/commands.workflow.go index e03047714..b6736c4f3 100644 --- a/internal/temporalcli/commands.workflow.go +++ b/internal/temporalcli/commands.workflow.go @@ -137,9 +137,10 @@ func (c *TemporalWorkflowUpdateOptionsCommand) run(cctx *CommandContext, args [] } } - if c.VersioningOverrideBehavior.Value == "pinned" { + if c.VersioningOverrideBehavior.Value == "pinned" || + c.VersioningOverrideBehavior.Value == "one_time" { if c.VersioningOverrideDeploymentName == "" || c.VersioningOverrideBuildId == "" { - return fmt.Errorf("missing deployment name and/or build id with 'pinned' behavior") + return fmt.Errorf("missing deployment name and/or build id with '%s' behavior", c.VersioningOverrideBehavior.Value) } } @@ -164,9 +165,10 @@ func (c *TemporalWorkflowUpdateOptionsCommand) run(cctx *CommandContext, args [] overrideChange = &client.VersioningOverrideChange{ Value: &client.AutoUpgradeVersioningOverride{}, } + case "one_time": default: return fmt.Errorf( - "invalid deployment behavior: %v, valid values are: 'unspecified', 'pinned', and 'auto_upgrade'", + "invalid deployment behavior: %v, valid values are: 'unspecified', 'pinned', 'auto_upgrade', and 'one_time'", c.VersioningOverrideBehavior, ) } @@ -176,36 +178,39 @@ func (c *TemporalWorkflowUpdateOptionsCommand) run(cctx *CommandContext, args [] return err } else if exec != nil { - _, err := cl.UpdateWorkflowExecutionOptions(cctx, client.UpdateWorkflowExecutionOptionsRequest{ - WorkflowId: exec.WorkflowId, - RunId: exec.RunId, - WorkflowExecutionOptionsChanges: client.WorkflowExecutionOptionsChanges{ - VersioningOverride: overrideChange, - }, - }) + if c.VersioningOverrideBehavior.Value == "one_time" { + err = c.updateWorkflowExecutionOptionsWithProto(cctx, cl, exec, oneTimeVersioningOverrideToProto(c.VersioningOverrideDeploymentName, c.VersioningOverrideBuildId)) + } else { + _, err = cl.UpdateWorkflowExecutionOptions(cctx, client.UpdateWorkflowExecutionOptionsRequest{ + WorkflowId: exec.WorkflowId, + RunId: exec.RunId, + WorkflowExecutionOptionsChanges: client.WorkflowExecutionOptionsChanges{ + VersioningOverride: overrideChange, + }, + }) + } if err != nil { return fmt.Errorf("failed to update workflow options: %w", err) } cctx.Printer.Println("Update workflow options succeeded") } else { // Run batch - var workflowExecutionOptions *workflowpb.WorkflowExecutionOptions - protoMask, err := fieldmaskpb.New(workflowExecutionOptions, "versioning_override") - if err != nil { - return fmt.Errorf("invalid field mask: %w", err) - } - var protoVerOverride *workflowpb.VersioningOverride - if overrideChange != nil { + if c.VersioningOverrideBehavior.Value == "one_time" { + protoVerOverride = oneTimeVersioningOverrideToProto(c.VersioningOverrideDeploymentName, c.VersioningOverrideBuildId) + } else if overrideChange != nil { protoVerOverride = versioningOverrideToProto(overrideChange.Value) } + workflowExecutionOptions, protoMask, err := workflowExecutionOptionsForVersioningOverride(protoVerOverride) + if err != nil { + return fmt.Errorf("invalid field mask: %w", err) + } + batchReq.Operation = &workflowservice.StartBatchOperationRequest_UpdateWorkflowOptionsOperation{ UpdateWorkflowOptionsOperation: &batch.BatchOperationUpdateWorkflowExecutionOptions{ - Identity: c.Parent.Identity, - WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{ - VersioningOverride: protoVerOverride, - }, - UpdateMask: protoMask, + Identity: c.Parent.Identity, + WorkflowExecutionOptions: workflowExecutionOptions, + UpdateMask: protoMask, }, } if err := startBatchJob(cctx, cl, batchReq); err != nil { @@ -215,6 +220,43 @@ func (c *TemporalWorkflowUpdateOptionsCommand) run(cctx *CommandContext, args [] return nil } +func (c *TemporalWorkflowUpdateOptionsCommand) updateWorkflowExecutionOptionsWithProto( + ctx context.Context, + cl client.Client, + exec *common.WorkflowExecution, + override *workflowpb.VersioningOverride, +) error { + workflowExecutionOptions, protoMask, err := workflowExecutionOptionsForVersioningOverride(override) + if err != nil { + return fmt.Errorf("invalid field mask: %w", err) + } + + _, err = cl.WorkflowService().UpdateWorkflowExecutionOptions(ctx, &workflowservice.UpdateWorkflowExecutionOptionsRequest{ + Namespace: c.Parent.Namespace, + WorkflowExecution: &common.WorkflowExecution{ + WorkflowId: exec.GetWorkflowId(), + RunId: exec.GetRunId(), + }, + WorkflowExecutionOptions: workflowExecutionOptions, + UpdateMask: protoMask, + Identity: c.Parent.Identity, + }) + return err +} + +func workflowExecutionOptionsForVersioningOverride( + override *workflowpb.VersioningOverride, +) (*workflowpb.WorkflowExecutionOptions, *fieldmaskpb.FieldMask, error) { + workflowExecutionOptions := &workflowpb.WorkflowExecutionOptions{ + VersioningOverride: override, + } + protoMask, err := fieldmaskpb.New(workflowExecutionOptions, "versioning_override") + if err != nil { + return nil, nil, err + } + return workflowExecutionOptions, protoMask, nil +} + func (c *TemporalWorkflowMetadataCommand) run(cctx *CommandContext, _ []string) error { return queryHelper(cctx, c.Parent, PayloadInputOptions{}, metadataQueryName, nil, c.RejectCondition, c.WorkflowReferenceOptions) @@ -756,10 +798,7 @@ func versioningOverrideToProto(versioningOverride client.VersioningOverride) *wo Override: &workflowpb.VersioningOverride_Pinned{ Pinned: &workflowpb.VersioningOverride_PinnedOverride{ Behavior: workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED, - Version: &deploymentpb.WorkerDeploymentVersion{ - DeploymentName: v.Version.DeploymentName, - BuildId: v.Version.BuildID, - }, + Version: workerDeploymentVersionToProto(v.Version.DeploymentName, v.Version.BuildID), }, }, } @@ -772,3 +811,20 @@ func versioningOverrideToProto(versioningOverride client.VersioningOverride) *wo return nil } } + +func workerDeploymentVersionToProto(deploymentName, buildID string) *deploymentpb.WorkerDeploymentVersion { + return &deploymentpb.WorkerDeploymentVersion{ + DeploymentName: deploymentName, + BuildId: buildID, + } +} + +func oneTimeVersioningOverrideToProto(deploymentName, buildID string) *workflowpb.VersioningOverride { + return &workflowpb.VersioningOverride{ + Override: &workflowpb.VersioningOverride_OneTime{ + OneTime: &workflowpb.VersioningOverride_OneTimeOverride{ + TargetDeploymentVersion: workerDeploymentVersionToProto(deploymentName, buildID), + }, + }, + } +} diff --git a/internal/temporalcli/commands.workflow_reset_update_options.go b/internal/temporalcli/commands.workflow_reset_update_options.go index ad6644604..53399578e 100644 --- a/internal/temporalcli/commands.workflow_reset_update_options.go +++ b/internal/temporalcli/commands.workflow_reset_update_options.go @@ -3,9 +3,7 @@ package temporalcli import ( "fmt" - deploymentpb "go.temporal.io/api/deployment/v1" workflowpb "go.temporal.io/api/workflow/v1" - "google.golang.org/protobuf/types/known/fieldmaskpb" ) func (c *TemporalWorkflowResetWithWorkflowUpdateOptionsCommand) run(cctx *CommandContext, args []string) error { @@ -14,12 +12,14 @@ func (c *TemporalWorkflowResetWithWorkflowUpdateOptionsCommand) run(cctx *Comman return err } - if c.VersioningOverrideBehavior.Value == "pinned" { + if c.VersioningOverrideBehavior.Value == "pinned" || + c.VersioningOverrideBehavior.Value == "one_time" { if c.VersioningOverrideDeploymentName == "" || c.VersioningOverrideBuildId == "" { - return fmt.Errorf("deployment name and build id are required with 'pinned' behavior") + return fmt.Errorf("deployment name and build id are required with '%s' behavior", c.VersioningOverrideBehavior.Value) } } - if c.VersioningOverrideBehavior.Value != "pinned" { + if c.VersioningOverrideBehavior.Value != "pinned" && + c.VersioningOverrideBehavior.Value != "one_time" { if c.VersioningOverrideDeploymentName != "" || c.VersioningOverrideBuildId != "" { return fmt.Errorf("cannot set deployment name or build id with %v behavior", c.VersioningOverrideBehavior.Value) } @@ -31,28 +31,30 @@ func (c *TemporalWorkflowResetWithWorkflowUpdateOptionsCommand) run(cctx *Comman } defer cl.Close() - VersioningOverride := &workflowpb.VersioningOverride{} + var versioningOverride *workflowpb.VersioningOverride switch c.VersioningOverrideBehavior.Value { case "pinned": - VersioningOverride.Override = &workflowpb.VersioningOverride_Pinned{ - Pinned: &workflowpb.VersioningOverride_PinnedOverride{ - Behavior: workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED, - Version: &deploymentpb.WorkerDeploymentVersion{ - DeploymentName: c.VersioningOverrideDeploymentName, - BuildId: c.VersioningOverrideBuildId, + versioningOverride = &workflowpb.VersioningOverride{ + Override: &workflowpb.VersioningOverride_Pinned{ + Pinned: &workflowpb.VersioningOverride_PinnedOverride{ + Behavior: workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED, + Version: workerDeploymentVersionToProto(c.VersioningOverrideDeploymentName, c.VersioningOverrideBuildId), }, }, } + case "one_time": + versioningOverride = oneTimeVersioningOverrideToProto(c.VersioningOverrideDeploymentName, c.VersioningOverrideBuildId) case "auto_upgrade": - VersioningOverride.Override = &workflowpb.VersioningOverride_AutoUpgrade{ - AutoUpgrade: true, + versioningOverride = &workflowpb.VersioningOverride{ + Override: &workflowpb.VersioningOverride_AutoUpgrade{ + AutoUpgrade: true, + }, } default: - return fmt.Errorf("invalid deployment behavior: %v, valid values are: 'pinned', and 'auto_upgrade'", c.VersioningOverrideBehavior.Value) + return fmt.Errorf("invalid deployment behavior: %v, valid values are: 'pinned', 'auto_upgrade', and 'one_time'", c.VersioningOverrideBehavior.Value) } - var workflowExecutionOptions *workflowpb.WorkflowExecutionOptions - protoMask, err := fieldmaskpb.New(workflowExecutionOptions, "versioning_override") + workflowExecutionOptions, protoMask, err := workflowExecutionOptionsForVersioningOverride(versioningOverride) if err != nil { return fmt.Errorf("invalid field mask: %w", err) } @@ -60,10 +62,8 @@ func (c *TemporalWorkflowResetWithWorkflowUpdateOptionsCommand) run(cctx *Comman postOp := &workflowpb.PostResetOperation{ Variant: &workflowpb.PostResetOperation_UpdateWorkflowOptions_{ UpdateWorkflowOptions: &workflowpb.PostResetOperation_UpdateWorkflowOptions{ - WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{ - VersioningOverride: VersioningOverride, - }, - UpdateMask: protoMask, + WorkflowExecutionOptions: workflowExecutionOptions, + UpdateMask: protoMask, }, }, } diff --git a/internal/temporalcli/commands.workflow_reset_update_options_test.go b/internal/temporalcli/commands.workflow_reset_update_options_test.go index 8e2bf9a19..f6148e89c 100644 --- a/internal/temporalcli/commands.workflow_reset_update_options_test.go +++ b/internal/temporalcli/commands.workflow_reset_update_options_test.go @@ -3,16 +3,19 @@ package temporalcli_test import ( "context" "fmt" + "sync" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.temporal.io/api/enums/v1" + workflowpb "go.temporal.io/api/workflow/v1" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" + "google.golang.org/grpc" ) func (s *SharedServerSuite) TestWorkflow_ResetWithWorkflowUpdateOptions_ValidatesArguments_MissingRequiredFlag() { @@ -54,6 +57,115 @@ func (s *SharedServerSuite) TestWorkflow_ResetWithWorkflowUpdateOptions_Validate require.Contains(s.T(), res.Err.Error(), "cannot set deployment name or build id with auto_upgrade behavior") } +func (s *SharedServerSuite) TestWorkflow_ResetWithWorkflowUpdateOptions_Single_OneTimeOverrideRequest() { + workflowID := "workflow-" + uuid.NewString() + deploymentName := "deployment-" + uuid.NewString() + buildID := "build-" + uuid.NewString() + + var lastRequestLock sync.Mutex + var resetRequest *workflowservice.ResetWorkflowExecutionRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + if r, ok := req.(*workflowservice.ResetWorkflowExecutionRequest); ok { + lastRequestLock.Lock() + resetRequest = r + lastRequestLock.Unlock() + return nil + } + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + res := s.Execute( + "workflow", "reset", "with-workflow-update-options", + "--address", s.Address(), + "-w", workflowID, + "-e", "3", + "--reason", "test-reset-one-time", + "--versioning-override-behavior", "one_time", + "--versioning-override-deployment-name", deploymentName, + "--versioning-override-build-id", buildID, + ) + require.NoError(s.T(), res.Err) + + lastRequestLock.Lock() + req := resetRequest + lastRequestLock.Unlock() + + require.NotNil(s.T(), req) + require.Equal(s.T(), s.Namespace(), req.GetNamespace()) + require.Equal(s.T(), workflowID, req.GetWorkflowExecution().GetWorkflowId()) + require.Equal(s.T(), int64(3), req.GetWorkflowTaskFinishEventId()) + s.requireOneTimePostResetOverride(req.GetPostResetOperations(), deploymentName, buildID) +} + +func (s *SharedServerSuite) TestWorkflow_ResetBatchWithWorkflowUpdateOptions_OneTimeOverrideRequest() { + query := "WorkflowType = 'YourWorkflowType'" + deploymentName := "deployment-" + uuid.NewString() + buildID := "build-" + uuid.NewString() + + var lastRequestLock sync.Mutex + var startBatchRequest *workflowservice.StartBatchOperationRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + if r, ok := req.(*workflowservice.StartBatchOperationRequest); ok { + lastRequestLock.Lock() + startBatchRequest = r + lastRequestLock.Unlock() + return nil + } + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + res := s.Execute( + "workflow", "reset", "with-workflow-update-options", + "--address", s.Address(), + "--query", query, + "--yes", + "-t", "FirstWorkflowTask", + "--reason", "test-batch-reset-one-time", + "--versioning-override-behavior", "one_time", + "--versioning-override-deployment-name", deploymentName, + "--versioning-override-build-id", buildID, + ) + require.NoError(s.T(), res.Err) + + lastRequestLock.Lock() + req := startBatchRequest + lastRequestLock.Unlock() + + require.NotNil(s.T(), req) + require.Equal(s.T(), s.Namespace(), req.GetNamespace()) + require.Equal(s.T(), query, req.GetVisibilityQuery()) + resetOperation := req.GetResetOperation() + require.NotNil(s.T(), resetOperation) + s.requireOneTimePostResetOverride(resetOperation.GetPostResetOperations(), deploymentName, buildID) +} + +func (s *SharedServerSuite) requireOneTimePostResetOverride(postOps []*workflowpb.PostResetOperation, deploymentName, buildID string) { + require.Len(s.T(), postOps, 1) + updateOptions := postOps[0].GetUpdateWorkflowOptions() + require.NotNil(s.T(), updateOptions) + require.Equal(s.T(), []string{"versioning_override"}, updateOptions.GetUpdateMask().GetPaths()) + override := updateOptions.GetWorkflowExecutionOptions().GetVersioningOverride() + require.NotNil(s.T(), override) + oneTime := override.GetOneTime() + require.NotNil(s.T(), oneTime) + require.Equal(s.T(), deploymentName, oneTime.GetTargetDeploymentVersion().GetDeploymentName()) + require.Equal(s.T(), buildID, oneTime.GetTargetDeploymentVersion().GetBuildId()) +} + func (s *SharedServerSuite) TestWorkflow_ResetWithWorkflowUpdateOptions_Single_AutoUpgradeBehavior() { var wfExecutions int s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { diff --git a/internal/temporalcli/commands.workflow_test.go b/internal/temporalcli/commands.workflow_test.go index aedf331f7..d1d728853 100644 --- a/internal/temporalcli/commands.workflow_test.go +++ b/internal/temporalcli/commands.workflow_test.go @@ -159,7 +159,7 @@ func (s *SharedServerSuite) testSignalBatchWorkflow(json bool) *CommandResult { s.CommandHarness.Stdin.WriteString("y\n") } res := s.Execute(args...) - s.NoError(res.Err) + require.NoError(s.T(), res.Err) // Confirm that all workflows complete with the signal value for _, run := range runs { @@ -202,7 +202,7 @@ func (s *SharedServerSuite) TestWorkflow_Delete_SingleWorkflowSuccess() { "--workflow-id", run.GetID(), "--yes", ) - s.NoError(res.Err) + require.NoError(s.T(), res.Err) s.NotContains(res.Stdout.String(), "Deleting Workflow Executions in a global Namespace") s.NotContains(res.Stderr.String(), "Deleting Workflow Executions in a global Namespace") s.Contains(res.Stdout.String(), "Delete workflow succeeded") @@ -720,6 +720,59 @@ func (s *SharedServerSuite) TestWorkflow_Batch_Update_Options_Versioning_Overrid }, 10*time.Second, 100*time.Millisecond) } +func (s *SharedServerSuite) TestWorkflow_Batch_Update_Options_OneTimeOverride() { + query := "WorkflowType = 'YourWorkflowType'" + deploymentName := "deployment-" + uuid.NewString() + buildID := "build-" + uuid.NewString() + + var lastRequestLock sync.Mutex + var startBatchRequest *workflowservice.StartBatchOperationRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + if r, ok := req.(*workflowservice.StartBatchOperationRequest); ok { + lastRequestLock.Lock() + startBatchRequest = r + lastRequestLock.Unlock() + return nil + } + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + res := s.Execute( + "workflow", "update-options", + "--address", s.Address(), + "--query", query, + "--yes", + "--versioning-override-behavior", "one_time", + "--versioning-override-deployment-name", deploymentName, + "--versioning-override-build-id", buildID, + ) + s.NoError(res.Err) + + lastRequestLock.Lock() + req := startBatchRequest + lastRequestLock.Unlock() + + require.NotNil(s.T(), req) + s.Equal(s.Namespace(), req.GetNamespace()) + s.Equal(query, req.GetVisibilityQuery()) + updateOptionsOperation := req.GetUpdateWorkflowOptionsOperation() + require.NotNil(s.T(), updateOptionsOperation) + s.Equal([]string{"versioning_override"}, updateOptionsOperation.GetUpdateMask().GetPaths()) + override := updateOptionsOperation.GetWorkflowExecutionOptions().GetVersioningOverride() + require.NotNil(s.T(), override) + oneTime := override.GetOneTime() + require.NotNil(s.T(), oneTime) + s.Equal(deploymentName, oneTime.GetTargetDeploymentVersion().GetDeploymentName()) + s.Equal(buildID, oneTime.GetTargetDeploymentVersion().GetBuildId()) +} + func (s *SharedServerSuite) TestWorkflow_Update_Options_Versioning_Override() { buildId1 := "id1-" + uuid.NewString() buildId2 := "id2-" + uuid.NewString() @@ -889,6 +942,56 @@ func (s *SharedServerSuite) TestWorkflow_Update_Options_Versioning_Override() { s.Nil(versioningInfo.VersioningOverride) } +func (s *SharedServerSuite) TestWorkflow_Update_Options_OneTimeOverride() { + workflowID := "workflow-" + uuid.NewString() + deploymentName := "deployment-" + uuid.NewString() + buildID := "build-" + uuid.NewString() + + var lastRequestLock sync.Mutex + var updateOptionsRequest *workflowservice.UpdateWorkflowExecutionOptionsRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + if r, ok := req.(*workflowservice.UpdateWorkflowExecutionOptionsRequest); ok { + lastRequestLock.Lock() + updateOptionsRequest = r + lastRequestLock.Unlock() + return nil + } + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + res := s.Execute( + "workflow", "update-options", + "--address", s.Address(), + "--workflow-id", workflowID, + "--versioning-override-behavior", "one_time", + "--versioning-override-deployment-name", deploymentName, + "--versioning-override-build-id", buildID, + ) + s.NoError(res.Err) + + lastRequestLock.Lock() + req := updateOptionsRequest + lastRequestLock.Unlock() + + require.NotNil(s.T(), req) + s.Equal(s.Namespace(), req.GetNamespace()) + s.Equal(workflowID, req.GetWorkflowExecution().GetWorkflowId()) + s.Equal([]string{"versioning_override"}, req.GetUpdateMask().GetPaths()) + override := req.GetWorkflowExecutionOptions().GetVersioningOverride() + require.NotNil(s.T(), override) + oneTime := override.GetOneTime() + require.NotNil(s.T(), oneTime) + s.Equal(deploymentName, oneTime.GetTargetDeploymentVersion().GetDeploymentName()) + s.Equal(buildID, oneTime.GetTargetDeploymentVersion().GetBuildId()) +} + func (s *SharedServerSuite) TestWorkflow_Update_Execute() { workflowUpdateTest{ s: s, diff --git a/internal/temporalcli/commands.workflow_view.go b/internal/temporalcli/commands.workflow_view.go index a1487d906..7becc5af3 100644 --- a/internal/temporalcli/commands.workflow_view.go +++ b/internal/temporalcli/commands.workflow_view.go @@ -210,14 +210,20 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin overrideBehavior := "" overridePinnedVersionDeploymentName := "" overridePinnedVersionBuildId := "" + overrideTargetVersionDeploymentName := "" + overrideTargetVersionBuildId := "" if vInfo.VersioningOverride != nil { switch vInfo.VersioningOverride.GetOverride().(type) { case *workflow.VersioningOverride_Pinned: - overridePinnedVersionDeploymentName = vInfo.GetVersioningOverride().GetPinned().Version.DeploymentName - overridePinnedVersionBuildId = vInfo.GetVersioningOverride().GetPinned().Version.BuildId + overridePinnedVersionDeploymentName = vInfo.GetVersioningOverride().GetPinned().GetVersion().GetDeploymentName() + overridePinnedVersionBuildId = vInfo.GetVersioningOverride().GetPinned().GetVersion().GetBuildId() overrideBehavior = enums.VERSIONING_BEHAVIOR_PINNED.String() case *workflow.VersioningOverride_AutoUpgrade: overrideBehavior = enums.VERSIONING_BEHAVIOR_AUTO_UPGRADE.String() + case *workflow.VersioningOverride_OneTime: + overrideTargetVersionDeploymentName = vInfo.GetVersioningOverride().GetOneTime().GetTargetDeploymentVersion().GetDeploymentName() + overrideTargetVersionBuildId = vInfo.GetVersioningOverride().GetOneTime().GetTargetDeploymentVersion().GetBuildId() + overrideBehavior = "OneTime" } } _ = cctx.Printer.PrintStructured(struct { @@ -227,6 +233,8 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin OverrideBehavior string `cli:",cardOmitEmpty"` OverridePinnedVersionDeploymentName string `cli:",cardOmitEmpty"` OverridePinnedVersionBuildId string `cli:",cardOmitEmpty"` + OverrideTargetVersionDeploymentName string `cli:",cardOmitEmpty"` + OverrideTargetVersionBuildId string `cli:",cardOmitEmpty"` TransitionVersionDeploymentName string `cli:",cardOmitEmpty"` TransitionVersionBuildId string `cli:",cardOmitEmpty"` }{ @@ -236,6 +244,8 @@ func (c *TemporalWorkflowDescribeCommand) run(cctx *CommandContext, args []strin OverrideBehavior: overrideBehavior, OverridePinnedVersionDeploymentName: overridePinnedVersionDeploymentName, OverridePinnedVersionBuildId: overridePinnedVersionBuildId, + OverrideTargetVersionDeploymentName: overrideTargetVersionDeploymentName, + OverrideTargetVersionBuildId: overrideTargetVersionBuildId, TransitionVersionDeploymentName: vInfo.VersionTransition.GetDeploymentVersion().GetDeploymentName(), TransitionVersionBuildId: vInfo.VersionTransition.GetDeploymentVersion().GetBuildId(), }, printer.StructuredOptions{}) diff --git a/internal/temporalcli/commands.workflow_view_test.go b/internal/temporalcli/commands.workflow_view_test.go index d3d13851e..0c3929305 100644 --- a/internal/temporalcli/commands.workflow_view_test.go +++ b/internal/temporalcli/commands.workflow_view_test.go @@ -7,19 +7,23 @@ import ( "fmt" "strconv" "strings" + "sync" "time" "github.com/google/uuid" "github.com/nexus-rpc/sdk-go/nexus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/temporalio/cli/internal/temporalcli" commandpb "go.temporal.io/api/command/v1" "go.temporal.io/api/common/v1" + deploymentpb "go.temporal.io/api/deployment/v1" "go.temporal.io/api/enums/v1" historypb "go.temporal.io/api/history/v1" nexuspb "go.temporal.io/api/nexus/v1" "go.temporal.io/api/operatorservice/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" + workflowpb "go.temporal.io/api/workflow/v1" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/api/workflowservice/v1/workflowservicenexus" "go.temporal.io/sdk/client" @@ -28,6 +32,7 @@ import ( "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" "go.temporal.io/server/common/payloads" + "google.golang.org/grpc" ) func (s *SharedServerSuite) TestWorkflow_Describe_ActivityFailing() { @@ -736,6 +741,108 @@ func (s *SharedServerSuite) TestWorkflow_Describe_Deployment() { s.Nil(versioningInfo.VersioningOverride) } +func (s *SharedServerSuite) TestWorkflow_Describe_OneTimeOverride() { + workflowID := "workflow-" + uuid.NewString() + runID := "run-" + uuid.NewString() + taskQueue := "task-queue-" + uuid.NewString() + baseDeploymentName := "base-deployment-" + uuid.NewString() + baseBuildID := "base-build-" + uuid.NewString() + targetDeploymentName := "target-deployment-" + uuid.NewString() + targetBuildID := "target-build-" + uuid.NewString() + + var lastRequestLock sync.Mutex + var describeRequest *workflowservice.DescribeWorkflowExecutionRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + if r, ok := req.(*workflowservice.DescribeWorkflowExecutionRequest); ok { + lastRequestLock.Lock() + describeRequest = r + lastRequestLock.Unlock() + + resp := reply.(*workflowservice.DescribeWorkflowExecutionResponse) + resp.WorkflowExecutionInfo = &workflowpb.WorkflowExecutionInfo{ + Execution: &common.WorkflowExecution{ + WorkflowId: workflowID, + RunId: runID, + }, + Type: &common.WorkflowType{Name: "DevWorkflow"}, + Status: enums.WORKFLOW_EXECUTION_STATUS_RUNNING, + TaskQueue: taskQueue, + VersioningInfo: &workflowpb.WorkflowExecutionVersioningInfo{ + Behavior: enums.VERSIONING_BEHAVIOR_AUTO_UPGRADE, + DeploymentVersion: &deploymentpb.WorkerDeploymentVersion{ + DeploymentName: baseDeploymentName, + BuildId: baseBuildID, + }, + VersioningOverride: &workflowpb.VersioningOverride{ + Override: &workflowpb.VersioningOverride_OneTime{ + OneTime: &workflowpb.VersioningOverride_OneTimeOverride{ + TargetDeploymentVersion: &deploymentpb.WorkerDeploymentVersion{ + DeploymentName: targetDeploymentName, + BuildId: targetBuildID, + }, + }, + }, + }, + }, + } + return nil + } + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + res := s.Execute( + "workflow", "describe", + "--address", s.Address(), + "-w", workflowID, + "-r", runID, + ) + require.NoError(s.T(), res.Err) + + lastRequestLock.Lock() + req := describeRequest + lastRequestLock.Unlock() + + require.NotNil(s.T(), req) + s.Equal(workflowID, req.GetExecution().GetWorkflowId()) + s.Equal(runID, req.GetExecution().GetRunId()) + + out := res.Stdout.String() + s.ContainsOnSameLine(out, "Behavior", "AutoUpgrade") + s.ContainsOnSameLine(out, "DeploymentName", baseDeploymentName) + s.ContainsOnSameLine(out, "BuildId", baseBuildID) + s.ContainsOnSameLine(out, "OverrideBehavior", "OneTime") + s.ContainsOnSameLine(out, "OverrideTargetVersionDeploymentName", targetDeploymentName) + s.ContainsOnSameLine(out, "OverrideTargetVersionBuildId", targetBuildID) + + res = s.Execute( + "workflow", "describe", + "--address", s.Address(), + "-w", workflowID, + "-r", runID, + "--output", "json", + ) + require.NoError(s.T(), res.Err) + + var jsonResp workflowservice.DescribeWorkflowExecutionResponse + require.NoError(s.T(), temporalcli.UnmarshalProtoJSONWithOptions(res.Stdout.Bytes(), &jsonResp, true)) + versioningInfo := jsonResp.GetWorkflowExecutionInfo().GetVersioningInfo() + require.NotNil(s.T(), versioningInfo) + s.Equal(enums.VERSIONING_BEHAVIOR_AUTO_UPGRADE, versioningInfo.GetBehavior()) + s.Equal(baseDeploymentName, versioningInfo.GetDeploymentVersion().GetDeploymentName()) + s.Equal(baseBuildID, versioningInfo.GetDeploymentVersion().GetBuildId()) + oneTime := versioningInfo.GetVersioningOverride().GetOneTime() + require.NotNil(s.T(), oneTime) + s.Equal(targetDeploymentName, oneTime.GetTargetDeploymentVersion().GetDeploymentName()) + s.Equal(targetBuildID, oneTime.GetTargetDeploymentVersion().GetBuildId()) +} + func (s *SharedServerSuite) TestWorkflow_Describe_NexusOperationAndCallback() { handlerWorkflowID := uuid.NewString() endpointName := validEndpointName(s.T()) diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 5ec1b9d66..3b444da5a 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -4089,6 +4089,17 @@ commands: --versioning-override-build-id YourDeploymentBuildId ``` + or to move the workflow execution to a Worker Deployment Version until + the next Workflow Task completes there, set behavior to `one_time`: + + ``` + temporal workflow update-options \ + --workflow-id YourWorkflowId \ + --versioning-override-behavior one_time \ + --versioning-override-deployment-name YourDeploymentName \ + --versioning-override-build-id YourDeploymentBuildId + ``` + To remove any previous overrides, set the behavior to `unspecified`: @@ -4111,17 +4122,18 @@ commands: enum-values: - unspecified - pinned + - one_time - auto_upgrade - name: versioning-override-deployment-name type: string description: - When overriding to a `pinned` behavior, specifies the Deployment Name of the - version to target. + When overriding to a `pinned` or `one_time` behavior, specifies the + Deployment Name of the version to target. - name: versioning-override-build-id type: string description: - When overriding to a `pinned` behavior, specifies the Build ID of the - version to target. + When overriding to a `pinned` or `one_time` behavior, specifies the + Build ID of the version to target. - name: temporal workflow query summary: Retrieve Workflow Execution state description: | @@ -5368,17 +5380,18 @@ option-sets: required: true enum-values: - pinned + - one_time - auto_upgrade - name: versioning-override-deployment-name type: string description: - When overriding to a `pinned` behavior, specifies the Deployment Name of the - version to target. + When overriding to a `pinned` or `one_time` behavior, specifies the + Deployment Name of the version to target. - name: versioning-override-build-id type: string description: - When overriding to a `pinned` behavior, specifies the Build ID of the - version to target. + When overriding to a `pinned` or `one_time` behavior, specifies the + Build ID of the version to target. - name: activity-reference options: From 655910e08c8e8223d44cb666a6483d5cdd224d6e Mon Sep 17 00:00:00 2001 From: Alex Tideman Date: Mon, 6 Jul 2026 13:13:02 -0500 Subject: [PATCH 22/28] Bump ui-server to v2.50.1 (#1109) ## Related issues ## What changed? Bump ui-server to v2.50.1 for News Feed feature --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8c5fa38e8..24563543c 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0 - github.com/temporalio/ui-server/v2 v2.49.1 + github.com/temporalio/ui-server/v2 v2.50.1 go.temporal.io/api v1.63.0 go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/envconfig v1.0.2 @@ -114,7 +114,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect + github.com/gomarkdown/markdown v0.0.0-20260411013819-759bbc3e3207 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect diff --git a/go.sum b/go.sum index 30a9ab4b0..86786fd75 100644 --- a/go.sum +++ b/go.sum @@ -221,8 +221,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= -github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/gomarkdown/markdown v0.0.0-20260411013819-759bbc3e3207 h1:p7t34F7K4OCRQblcDhNJnP46Uaarz3z2cLcvOZYxWn8= +github.com/gomarkdown/markdown v0.0.0-20260411013819-759bbc3e3207/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -409,8 +409,8 @@ github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb/go.mod h1:143 github.com/temporalio/tchannel-go v1.22.1-0.20220818200552-1be8d8cffa5b/go.mod h1:c+V9Z/ZgkzAdyGvHrvC5AsXgN+M9Qwey04cBdKYzV7U= github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61 h1:v9EBEMJggmXGbVcIAjGQpKgEB+a9E/Q0brJ6fGWJvhQ= github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61/go.mod h1:ezRQRwu9KQXy8Wuuv1aaFFxoCNz5CeNbVOOkh3xctbY= -github.com/temporalio/ui-server/v2 v2.49.1 h1:OFbEpfCdSEEFYe7YdVZPTk3l4idT8ReStB6F788iiP0= -github.com/temporalio/ui-server/v2 v2.49.1/go.mod h1:BgMLfNqd11tohA1gjgEeZLlVvRSnV/PAKiMQYTij6IE= +github.com/temporalio/ui-server/v2 v2.50.1 h1:dlyg96lFwOIaiXywDzPnSXVf8GPULLfrXXVXGqRsvJY= +github.com/temporalio/ui-server/v2 v2.50.1/go.mod h1:jiWHDxRe4RDXLxGwenfV+FSgjXat81okVTTZPQOlc3c= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= From f2a7159b370a84dbc64a22713eeec949e9fff55a Mon Sep 17 00:00:00 2001 From: Tom Wheeler Date: Tue, 7 Jul 2026 17:15:47 +0200 Subject: [PATCH 23/28] Add option to disable newsfeed in UI (#1110) ## Related issues Related to #[1104](https://github.com/temporalio/cli/pull/1104) ## What changed? This adds a command-line option that configures the embedded UI server to disable the news feed feature. ## Checklist ## Manual tests **Setup #1 (default case)** ``` temporal server start-dev ``` **Happy path #1 (default case)** Open the Web UI and observe that the news fetch feature is enabled, as it is by default. This is visible by way of a megaphone icon in the top navigation. **Setup two (feature disabled)** ``` temporal server start-dev --ui-disable-news-fetch ``` **Happy path two (feature disabled)** Open the Web UI and observe that the news fetch feature is disabled, since the environment variable is set to disable it, as described in the documentation. This is evident by the megaphone icon being omitted from the top navigation and the Web UI not making a request for the feed. --------- Co-authored-by: alex.stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/devserver/server.go | 2 ++ internal/temporalcli/commands.gen.go | 2 ++ internal/temporalcli/commands.server.go | 1 + internal/temporalcli/commands.yaml | 5 +++++ 4 files changed, 10 insertions(+) diff --git a/internal/devserver/server.go b/internal/devserver/server.go index 610bd628a..e6f6e7081 100644 --- a/internal/devserver/server.go +++ b/internal/devserver/server.go @@ -76,6 +76,7 @@ type StartOptions struct { UIPort int // Required if UIIP is non-empty UIAssetPath string UICodecEndpoint string + UIDisableNewsFetch bool PublicPath string DatabaseFile string MetricsPort int @@ -176,6 +177,7 @@ func (s *StartOptions) buildUIServer() *uiserver.Server { Codec: uiconfig.Codec{Endpoint: s.UICodecEndpoint}, CORS: uiconfig.CORS{CookieInsecure: true}, HideLogs: true, + DisableNewsFetch: s.UIDisableNewsFetch, })) } diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index ce21f9d26..6fcf8bb1a 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -2771,6 +2771,7 @@ type TemporalServerStartDevCommand struct { UiPublicPath string UiAssetPath string UiCodecEndpoint string + UiDisableNewsFetch bool SqlitePragma []string DynamicConfigValue []string LogConfig bool @@ -2801,6 +2802,7 @@ func NewTemporalServerStartDevCommand(cctx *CommandContext, parent *TemporalServ s.Command.Flags().StringVar(&s.UiPublicPath, "ui-public-path", "", "The public base path for the Web UI. Defaults to `/`.") s.Command.Flags().StringVar(&s.UiAssetPath, "ui-asset-path", "", "UI custom assets path.") s.Command.Flags().StringVar(&s.UiCodecEndpoint, "ui-codec-endpoint", "", "UI remote codec HTTP endpoint.") + s.Command.Flags().BoolVar(&s.UiDisableNewsFetch, "ui-disable-news-fetch", false, "Disable the Web UI newsfeed. When set, the UI will not request the newsfeed and the button to open the newsfeed panel is hidden.") s.Command.Flags().StringArrayVar(&s.SqlitePragma, "sqlite-pragma", nil, "SQLite pragma statements in \"PRAGMA=VALUE\" format.") s.Command.Flags().StringArrayVar(&s.DynamicConfigValue, "dynamic-config-value", nil, "Dynamic configuration value using `KEY=VALUE` pairs. Keys must be identifiers, and values must be JSON values. For example: `YourKey=\"YourString\"` Can be passed multiple times.") s.Command.Flags().BoolVar(&s.LogConfig, "log-config", false, "Print the server config to stderr.") diff --git a/internal/temporalcli/commands.server.go b/internal/temporalcli/commands.server.go index bd45f911a..d0aa7d97a 100644 --- a/internal/temporalcli/commands.server.go +++ b/internal/temporalcli/commands.server.go @@ -92,6 +92,7 @@ func (t *TemporalServerStartDevCommand) run(cctx *CommandContext, args []string) } } opts.UIAssetPath, opts.UICodecEndpoint, opts.PublicPath = t.UiAssetPath, t.UiCodecEndpoint, t.UiPublicPath + opts.UIDisableNewsFetch = t.UiDisableNewsFetch } // Pragmas and dyn config var err error diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 3b444da5a..ac166de20 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -2950,6 +2950,11 @@ commands: - name: ui-codec-endpoint type: string description: UI remote codec HTTP endpoint. + - name: ui-disable-news-fetch + type: bool + description: | + Disable the Web UI newsfeed. When set, the UI will not request the + newsfeed and the button to open the newsfeed panel is hidden. - name: sqlite-pragma type: string[] description: SQLite pragma statements in "PRAGMA=VALUE" format. From a38333dbb7952173a9d30668c45b92031dccb0eb Mon Sep 17 00:00:00 2001 From: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> Date: Wed, 8 Jul 2026 09:40:39 -0500 Subject: [PATCH 24/28] ci/docs: escape MDX-incompatible patterns and add -subdir to gen-docs (#1112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This consolidates the excellent groundwork in #980, #1076, and #981 into a single change, and verifies the result against a real Docusaurus 3.10.1 build. Huge thanks to @lennessyy — the approach here is entirely built on those PRs; this just stitches them together and irons out a few interactions between them. gen-docs now escapes the patterns that break Docusaurus MDX (JSX) compilation, on **every** path that writes a command description (including the new split paths): - bare angle-bracket placeholders in prose (e.g. ``, ``) → `\<...\>` - single-quoted JSON examples (e.g. `'{"a":"b"}'`, `'Key={"a":"b"}'`) → braces escaped in body text, backticked in option tables - custom heading IDs (e.g. `## Heading {#id}`) → `{/* #id */}`, the form that compiles under Docusaurus 3.10 and still produces the custom anchor Fenced code blocks and inline code spans are left untouched. It also adds the `-subdir` flag (subcommands of the named command are written to a subdirectory, e.g. `-subdir cloud` → `cloud/*.mdx`, with deeper subcommands nested as headings), and a generic auto-generated notice. ## What changed relative to the existing PRs All three PRs were on the right track. The differences here are about how they interact: - **#1076 (MDX escaping):** kept as the core of this change, including the `{#id}` → `{/* #id */}` heading conversion — which I confirmed is exactly right for the 3.10 upgrade (see verification below). Escaping is now also applied to the `-subdir` split output, so cloud docs get the same treatment. Re-added unit tests for the escaping logic. - **#980 (`-subdir`):** kept the split mechanism. Unified the `encodeJSONExample` regex so it matches both `'{...}'` and `'Key={...}'` (the standalone-vs-key-value cases the two PRs handled separately). Intentionally did **not** carry over #980's index-page generation: `command-reference/index.mdx` and `cloud/index.mdx` are hand-maintained on the docs site (custom ordering, the `ReleaseNoteHeader` component), so gen-docs deliberately does not emit them — generating them would overwrite that curated content. The companion docs PR restores those files after each regeneration. - **#981 (generic notice):** folded in — the previous notice pointed at paths that no longer exist and don't hold for cloud-cli inputs. ## Companion PR https://github.com/temporalio/documentation/pull/4836 --- cmd/gen-docs/main.go | 12 +- internal/commandsgen/docs.go | 273 ++++++++++++++++++++++++++---- internal/commandsgen/docs_test.go | 184 ++++++++++++++++++++ 3 files changed, 431 insertions(+), 38 deletions(-) create mode 100644 internal/commandsgen/docs_test.go diff --git a/cmd/gen-docs/main.go b/cmd/gen-docs/main.go index 11ea430e0..f6825f328 100644 --- a/cmd/gen-docs/main.go +++ b/cmd/gen-docs/main.go @@ -10,7 +10,8 @@ import ( "github.com/temporalio/cli/internal/commandsgen" ) -// stringSlice implements flag.Value to support multiple -input flags +// stringSlice implements flag.Value to support flags that may be specified +// multiple times (e.g. -input, -subdir). type stringSlice []string func (s *stringSlice) String() string { @@ -32,10 +33,12 @@ func run() error { var ( outputDir string inputFiles stringSlice + subdirs stringSlice ) flag.Var(&inputFiles, "input", "Input YAML file (can be specified multiple times)") flag.StringVar(&outputDir, "output", ".", "Output directory for docs") + flag.Var(&subdirs, "subdir", "Write the subcommands of this command into a subdirectory of separate files instead of a single file (can be specified multiple times)") flag.Parse() if len(inputFiles) == 0 { @@ -60,13 +63,18 @@ func run() error { return fmt.Errorf("failed parsing YAML: %w", err) } - docs, err := commandsgen.GenerateDocsFiles(cmds) + docs, err := commandsgen.GenerateDocsFiles(cmds, subdirs) if err != nil { return fmt.Errorf("failed generating docs: %w", err) } for filename, content := range docs { filePath := filepath.Join(outputDir, filename+".mdx") + // Filenames may contain a path separator (e.g. "cloud/namespace") when + // -subdir is used, so ensure the parent directory exists. + if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { + return fmt.Errorf("failed creating directory for %s: %w", filePath, err) + } if err := os.WriteFile(filePath, content, 0644); err != nil { return fmt.Errorf("failed writing %s: %w", filePath, err) } diff --git a/internal/commandsgen/docs.go b/internal/commandsgen/docs.go index b3857503e..d699c23f6 100644 --- a/internal/commandsgen/docs.go +++ b/internal/commandsgen/docs.go @@ -8,17 +8,33 @@ import ( "strings" ) -func GenerateDocsFiles(commands Commands) (map[string][]byte, error) { +// GenerateDocsFiles generates the MDX documentation files from parsed commands. +// +// subdirNames lists command names whose subcommands should each be written to +// their own file within a subdirectory instead of being combined into a single +// file. For example, passing "cloud" produces cloud/namespace.mdx, cloud/user.mdx, +// etc. rather than a single, unmanageably large cloud.mdx. +// +// Index/landing pages are intentionally not generated: on the docs site they are +// hand-maintained (custom ordering, front matter, embedded components), so +// generating them would overwrite that curated content. +func GenerateDocsFiles(commands Commands, subdirNames []string) (map[string][]byte, error) { optionSetMap := make(map[string]OptionSets) for i, optionSet := range commands.OptionSets { optionSetMap[optionSet.Name] = commands.OptionSets[i] } + splitParents := make(map[string]bool) + for _, name := range subdirNames { + splitParents[name] = true + } + w := &docWriter{ fileMap: make(map[string]*bytes.Buffer), optionSetMap: optionSetMap, allCommands: commands.CommandList, globalFlagsMap: make(map[string]map[string]Option), + splitParents: splitParents, } // sorted ascending by full name of command (activity complete, batch list, etc) @@ -45,13 +61,33 @@ type docWriter struct { optionSetMap map[string]OptionSets optionsStack [][]Option globalFlagsMap map[string]map[string]Option // fileName -> optionName -> Option + splitParents map[string]bool // command names whose subcommands are split into a subdirectory } func (c *Command) writeDoc(w *docWriter) error { w.processOptions(c) - // If this is a root command, write a new file depth := c.depth() + + // A split parent (e.g. "cloud") has no standalone file; its children are + // each written into a subdirectory instead. + if w.splitParents[c.FullName] { + return nil + } + // If any ancestor is a split parent, route this command into that subdirectory. + // FullName segments are separated by single spaces (never multiple), so + // splitting on " " yields the exact command path. + if depth >= 1 { + parts := strings.Split(c.FullName, " ") + for i := len(parts) - 1; i >= 1; i-- { + ancestor := strings.Join(parts[:i], " ") + if w.splitParents[ancestor] { + c.writeSplitDoc(w, ancestor) + return nil + } + } + } + if depth == 1 { w.writeCommand(c) } else if depth > 1 { @@ -80,8 +116,7 @@ func (w *docWriter) writeCommand(c *Command) { } w.fileMap[fileName].WriteString("---") w.fileMap[fileName].WriteString("\n\n") - w.fileMap[fileName].WriteString("{/* NOTE: This is an auto-generated file. Any edit to this file will be overwritten.\n") - w.fileMap[fileName].WriteString("This file is generated from https://github.com/temporalio/cli/blob/main/internal/commandsgen/commands.yml via internal/cmd/gen-docs */}\n\n") + w.fileMap[fileName].WriteString(autoGeneratedNotice) // Add introductory paragraph w.fileMap[fileName].WriteString(fmt.Sprintf("This page provides a reference for the `temporal` CLI `%s` command. ", fileName)) w.fileMap[fileName].WriteString("The flags applicable to each subcommand are presented in a table within the heading for the subcommand. ") @@ -92,45 +127,146 @@ func (w *docWriter) writeSubcommand(c *Command) { fileName := c.fileName() prefix := strings.Repeat("#", c.depth()) w.fileMap[fileName].WriteString(prefix + " " + c.leafName() + "\n\n") - w.fileMap[fileName].WriteString(c.Description + "\n\n") + w.fileMap[fileName].WriteString(escapeMDXDescription(c.Description) + "\n\n") if w.isLeafCommand(c) { - // gather options from command and all options available from parent commands - var options = make([]Option, 0) - var globalOptions = make([]Option, 0) - for i, o := range w.optionsStack { - if i == len(w.optionsStack)-1 { - options = append(options, o...) - } else { - globalOptions = append(globalOptions, o...) - } - } - - // alphabetize options - sort.Slice(options, func(i, j int) bool { - return options[i].Name < options[j].Name - }) + w.writeLeafOptions(fileName) + } +} - // Write command-specific flags or global flags message - if len(options) > 0 { - w.fileMap[fileName].WriteString("Use the following options to change the behavior of this command. ") - w.fileMap[fileName].WriteString("You can also use any of the [global flags](#global-flags) that apply to all subcommands.\n\n") - w.writeOptionsTable(options, c) +// writeLeafOptions writes the option table (or the global-flags fallback message) +// for a leaf command, and records its inherited options for the file's Global +// Flags section. The command's own options are the top frame of the options +// stack; everything below it is inherited from parent commands. +// +// The reference to the "#global-flags" anchor is only emitted when there are +// inherited options, since that is what causes a Global Flags section (and thus +// the anchor) to be generated for the file. A command with no inherited options +// (e.g. a top-level command in a split subdirectory) would otherwise link to an +// anchor that never exists. +func (w *docWriter) writeLeafOptions(fileName string) { + var options, globalOptions []Option + for i, o := range w.optionsStack { + if i == len(w.optionsStack)-1 { + options = append(options, o...) } else { - w.fileMap[fileName].WriteString("Use [global flags](#global-flags) to customize the connection to the Temporal Service for this command.\n\n") + globalOptions = append(globalOptions, o...) } + } + + sort.Slice(options, func(i, j int) bool { + return options[i].Name < options[j].Name + }) + + buf := w.fileMap[fileName] + hasGlobal := len(globalOptions) > 0 + switch { + case len(options) > 0 && hasGlobal: + buf.WriteString("Use the following options to change the behavior of this command. ") + buf.WriteString("You can also use any of the [global flags](#global-flags) that apply to all subcommands.\n\n") + w.writeOptionsTable(options, fileName) + case len(options) > 0: + buf.WriteString("Use the following options to change the behavior of this command.\n\n") + w.writeOptionsTable(options, fileName) + case hasGlobal: + buf.WriteString("Use [global flags](#global-flags) to customize the connection to the Temporal Service for this command.\n\n") + } + + w.collectGlobalFlags(fileName, globalOptions) +} + +// writeSplitDoc routes a command that lives under a split parent to the right +// writer: direct children get their own file, deeper subcommands are appended +// as headings within their parent's file. +func (c *Command) writeSplitDoc(w *docWriter, splitRoot string) { + splitDepth := len(strings.Split(splitRoot, " ")) + relativeDepth := len(strings.Split(c.FullName, " ")) - splitDepth + + switch { + case relativeDepth == 1: + w.writeSplitCommand(c, splitRoot) + case relativeDepth > 1: + w.writeSplitSubcommand(c, splitRoot) + } +} + +// splitFileName returns the file path for a command within a split parent. +// For example, with splitRoot "cloud" and command "cloud namespace", it returns +// "cloud/namespace". A multi-word split root is hyphenated into a single +// directory: splitRoot "one two" with command "one two three" returns +// "one-two/three" (not "one/two/three"). +func splitFileName(c *Command, splitRoot string) string { + splitParts := strings.Split(splitRoot, " ") + cmdParts := strings.Split(c.FullName, " ") + if len(cmdParts) <= len(splitParts) { + return "" + } + return strings.Join(splitParts, "-") + "/" + cmdParts[len(splitParts)] +} + +func (w *docWriter) writeSplitCommand(c *Command, splitRoot string) { + fileName := splitFileName(c, splitRoot) + splitParts := strings.Split(splitRoot, " ") + cmdParts := strings.Split(c.FullName, " ") + leafName := cmdParts[len(splitParts)] + + buf := &bytes.Buffer{} + w.fileMap[fileName] = buf + buf.WriteString("---\n") + buf.WriteString("id: " + leafName + "\n") + buf.WriteString("title: Temporal CLI " + c.FullName + " command reference\n") + buf.WriteString("sidebar_label: " + leafName + "\n") + buf.WriteString("description: " + c.Docs.DescriptionHeader + "\n") + buf.WriteString("toc_max_heading_level: 4\n") + buf.WriteString("keywords:\n") + for _, keyword := range c.Docs.Keywords { + buf.WriteString(" - " + keyword + "\n") + } + buf.WriteString("tags:\n") + for _, tag := range c.Docs.Tags { + buf.WriteString(" - " + tag + "\n") + } + buf.WriteString("---") + buf.WriteString("\n\n") + buf.WriteString(autoGeneratedNotice) + + buf.WriteString(escapeMDXDescription(c.Description) + "\n\n") + + if w.isLeafCommand(c) { + w.writeLeafOptions(fileName) + } else { + buf.WriteString(fmt.Sprintf("This page provides a reference for the `temporal %s` commands. ", c.FullName)) + buf.WriteString("The flags applicable to each subcommand are presented in a table within the heading for the subcommand. ") + buf.WriteString("Refer to [Global Flags](#global-flags) for flags that you can use with every subcommand.\n\n") + } +} + +func (w *docWriter) writeSplitSubcommand(c *Command, splitRoot string) { + fileName := splitFileName(c, splitRoot) + splitParts := strings.Split(splitRoot, " ") + cmdParts := strings.Split(c.FullName, " ") + relativeDepth := len(cmdParts) - len(splitParts) + prefix := strings.Repeat("#", relativeDepth) + // Use the path relative to the split root (e.g. "ha update", "cert-ca create") + // rather than just the final segment. Within one aggregated file this keeps + // each heading's generated anchor unique and stable, which other docs link to + // (e.g. /cloud/high-availability/enable -> cloud/namespace#ha-update). + leafName := strings.Join(cmdParts[len(splitParts)+1:], " ") + + buf := w.fileMap[fileName] + buf.WriteString(prefix + " " + leafName + "\n\n") + buf.WriteString(escapeMDXDescription(c.Description) + "\n\n") - // Collect global flags for later (deduplicated) - w.collectGlobalFlags(fileName, globalOptions) + if w.isLeafCommand(c) { + w.writeLeafOptions(fileName) } } -func (w *docWriter) writeOptionsTable(options []Option, c *Command) { +func (w *docWriter) writeOptionsTable(options []Option, fileName string) { if len(options) == 0 { return } - fileName := c.fileName() buf := w.fileMap[fileName] // Command-specific flags: 3 columns (no Default) @@ -254,11 +390,76 @@ func (w *docWriter) isLeafCommand(c *Command) bool { return true } +const autoGeneratedNotice = "{/* NOTE: This is an auto-generated file. Any edit to this file will be overwritten.\n" + + "This file is generated from the CLI command definitions via cmd/gen-docs. */}\n\n" + +// jsonInSingleQuotes matches a single-quoted string containing curly braces, +// e.g. 'YourKey={"a":"b"}' or '{"a":"b"}'. MDX interprets the braces as a JSX +// expression, so such examples must be wrapped in backticks (option table cells) +// or backslash-escaped (description body text). +var jsonInSingleQuotes = regexp.MustCompile(`'[^']*\{[^']*\}[^']*'`) + +// angleBracketPlaceholder matches lowercase angle-bracket placeholders such as +// , , or . Restricting the +// match to lowercase, multi-character names avoids escaping legitimate inline +// HTML that authors may intentionally include. +var angleBracketPlaceholder = regexp.MustCompile(`<([a-z][a-z0-9_:.-]+)>`) + +// headingID matches a Markdown heading line ending with a custom ID, e.g. +// "### Some heading {#custom-id}". The Docusaurus MDX parser (3.10+) treats the +// bare {#custom-id} as a JSX expression and fails to compile it. +var headingID = regexp.MustCompile(`^(#{1,6}\s+.+?)\s+\{#([\w-]+)\}\s*$`) + +// encodeJSONExample wraps single-quoted JSON examples in backticks so MDX renders +// them as inline code rather than parsing the curly braces as a JSX expression. +// This handles option description table cells; escapeMDXDescription handles the +// same class of issue for command description body text. func encodeJSONExample(v string) string { - // example: 'YourKey={"your": "value"}' - // results in an mdx acorn rendering error - // and wrapping in backticks lets it render - re := regexp.MustCompile(`('[a-zA-Z0-9]*={.*}')`) - v = re.ReplaceAllString(v, "`$1`") - return v + return jsonInSingleQuotes.ReplaceAllString(v, "`$0`") +} + +// escapeMDXDescription escapes patterns in a command description that are valid +// CommonMark but break MDX (JSX) compilation under Docusaurus 3.10+: +// +// - bare angle-bracket placeholders like , which MDX +// parses as an unclosed JSX/HTML tag; +// - curly braces in single-quoted JSON examples like '{"a":"b"}', which MDX +// parses as a JSX expression; and +// - custom heading IDs like "## Heading {#id}", where MDX parses the {#id} as +// a JSX expression and fails on the '#'. These are converted to Docusaurus's +// MDX-compatible {/* #id */} comment form, which preserves the custom anchor. +// +// Content inside fenced code blocks (```) and inline code spans (backticks) is +// left untouched, since MDX does not interpret those as JSX. Fence markers are +// assumed to be on their own line (the standard CommonMark form the command +// descriptions use): a ``` opening or closing a block must start the line. +func escapeMDXDescription(desc string) string { + lines := strings.Split(desc, "\n") + inCodeBlock := false + for i, line := range lines { + if strings.HasPrefix(strings.TrimSpace(line), "```") { + inCodeBlock = !inCodeBlock + continue + } + if inCodeBlock { + continue + } + + line = headingID.ReplaceAllString(line, "$1 {/* #$2 */}") + + // Escape only outside inline code spans. Splitting on backticks yields + // alternating outside/inside segments (assuming balanced backticks). + segments := strings.Split(line, "`") + for j := range segments { + if j%2 != 0 { + continue // inside an inline code span + } + segments[j] = angleBracketPlaceholder.ReplaceAllString(segments[j], `\<$1\>`) + segments[j] = jsonInSingleQuotes.ReplaceAllStringFunc(segments[j], func(m string) string { + return strings.NewReplacer("{", `\{`, "}", `\}`).Replace(m) + }) + } + lines[i] = strings.Join(segments, "`") + } + return strings.Join(lines, "\n") } diff --git a/internal/commandsgen/docs_test.go b/internal/commandsgen/docs_test.go new file mode 100644 index 000000000..8e9391b79 --- /dev/null +++ b/internal/commandsgen/docs_test.go @@ -0,0 +1,184 @@ +package commandsgen + +import ( + "strings" + "testing" +) + +func TestEscapeMDXDescription(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + { + name: "bare angle-bracket placeholder is escaped", + in: "Pass the cert via --ca-certificate to authenticate.", + want: `Pass the cert via --ca-certificate \ to authenticate.`, + }, + { + name: "placeholder with separators and colon is escaped", + in: "Set --limit and --reason .", + want: `Set --limit \ and --reason \.`, + }, + { + name: "angle brackets inside a fenced code block are left untouched", + in: "Example:\n\n```\ntemporal x --cert \n```\n", + want: "Example:\n\n```\ntemporal x --cert \n```\n", + }, + { + name: "angle brackets inside an inline code span are left untouched", + in: "Use `--cert ` here but escape there.", + want: "Use `--cert ` here but escape \\ there.", + }, + { + name: "custom heading id is converted to MDX comment form", + in: "### Resetting activities that heartbeat {#reset-heartbeats}\n\nSee [details](#reset-heartbeats).", + want: "### Resetting activities that heartbeat {/* #reset-heartbeats */}\n\nSee [details](#reset-heartbeats).", + }, + { + name: "non-heading line with {#id}-like text is not treated as a heading", + in: "See [details](#reset-heartbeats).", + want: "See [details](#reset-heartbeats).", + }, + { + name: "bare single-quoted JSON has its braces escaped", + in: `Provide default '{"some-key": "some-value"}' inline.`, + want: `Provide default '\{"some-key": "some-value"\}' inline.`, + }, + { + name: "single-quoted JSON inside a fence is left untouched", + in: "```\ntemporal x --input '{\"some-key\": \"some-value\"}'\n```", + want: "```\ntemporal x --input '{\"some-key\": \"some-value\"}'\n```", + }, + { + name: "plain prose is unchanged", + in: "This command does something useful with no special characters.", + want: "This command does something useful with no special characters.", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := escapeMDXDescription(tc.in); got != tc.want { + t.Errorf("escapeMDXDescription()\n got: %q\nwant: %q", got, tc.want) + } + }) + } +} + +func TestEncodeJSONExample(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + { + name: "key=json example is wrapped", + in: `Example: 'YourKey={"your": "value"}'.`, + want: "Example: `'YourKey={\"your\": \"value\"}'`.", + }, + { + name: "standalone json example is wrapped", + in: `Example: '{"some-key": "some-value"}'.`, + want: "Example: `'{\"some-key\": \"some-value\"}'`.", + }, + { + name: "nested json example is wrapped", + in: `Example: '{"a": {"b": "c"}}'.`, + want: "Example: `'{\"a\": {\"b\": \"c\"}}'`.", + }, + { + name: "no json is unchanged", + in: "A plain description without JSON.", + want: "A plain description without JSON.", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := encodeJSONExample(tc.in); got != tc.want { + t.Errorf("encodeJSONExample()\n got: %q\nwant: %q", got, tc.want) + } + }) + } +} + +// splitFixture is a minimal command tree with a "cloud" root, mirroring how the +// cloud CLI extension is structured, used to exercise -subdir splitting. +const splitFixture = ` +commands: + - name: cloud + summary: Cloud + description: Manage Temporal Cloud. + - name: cloud thing + summary: Thing + description: | + Manage things. + docs: + keywords: + - thing + description-header: Manage things + tags: + - Cloud + - name: cloud thing sub + summary: Sub + description: | + Do a thing with a bare outside code. + options: + - name: flag + type: string + description: A flag. +` + +func generateSplitFixture(t *testing.T, subdirs []string) map[string][]byte { + t.Helper() + cmds, err := ParseCommands([]byte(splitFixture)) + if err != nil { + t.Fatalf("ParseCommands: %v", err) + } + docs, err := GenerateDocsFiles(cmds, subdirs) + if err != nil { + t.Fatalf("GenerateDocsFiles: %v", err) + } + return docs +} + +func TestGenerateDocsFilesSubdir(t *testing.T) { + docs := generateSplitFixture(t, []string{"cloud"}) + + if _, ok := docs["cloud/thing"]; !ok { + t.Errorf("expected split file cloud/thing, got keys: %v", keys(docs)) + } + if _, ok := docs["thing"]; ok { + t.Errorf("did not expect a flat 'thing' file when splitting cloud") + } + if _, ok := docs["cloud"]; ok { + t.Errorf("split parent 'cloud' should not produce a standalone file") + } + // Index pages are hand-maintained on the docs site, so gen-docs never emits them. + if _, ok := docs["cloud/index"]; ok { + t.Errorf("gen-docs should not emit a cloud/index page") + } + if _, ok := docs["index"]; ok { + t.Errorf("gen-docs should not emit a top-level index page") + } + + // The deeper subcommand's description is appended to its parent file and + // must be MDX-escaped there. + thing := string(docs["cloud/thing"]) + if !strings.Contains(thing, `\`) { + t.Errorf("expected escaped placeholder in cloud/thing, got:\n%s", thing) + } + if !strings.Contains(thing, "## sub") { + t.Errorf("expected 'sub' heading in cloud/thing, got:\n%s", thing) + } +} + +func keys(m map[string][]byte) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + return out +} From 4262ffb9b6a1558939e5330f6169cbefaad173c7 Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Thu, 9 Jul 2026 07:16:20 -0700 Subject: [PATCH 25/28] Bump UI server v2.52.0 (#1117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related issues ## What changed? Bump UI server v2.52.0 ## Checklist **Stability** - [ ] Breaking changes are marked with 💥 in the PR title and release notes - [ ] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [ ] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [ ] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [ ] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [ ] New flags don't duplicate an existing flag that serves the same purpose - [ ] New flags do not have short aliases without strong justification - [ ] Experimental features are marked with `(Experimental)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [ ] All flags shown in help text and examples are implemented and functional - [ ] Summaries use sentence case and have no trailing period - [ ] Long descriptions end with a period and include at least one example invocation - [ ] Examples use long flags (`--namespace`, not `-n`), one flag per line - [ ] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [ ] Results go to stdout; errors and warnings go to stderr - [ ] Error messages are lowercase with no trailing punctuation **Tests** - [ ] Added functional test(s) (`SharedServerSuite`) - [ ] Added unit test(s) (`func TestXxx`) where applicable --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 24563543c..02ef784dd 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0 - github.com/temporalio/ui-server/v2 v2.50.1 + github.com/temporalio/ui-server/v2 v2.52.0 go.temporal.io/api v1.63.0 go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/envconfig v1.0.2 diff --git a/go.sum b/go.sum index 86786fd75..5f544e484 100644 --- a/go.sum +++ b/go.sum @@ -411,6 +411,8 @@ github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61 h1:v9EBE github.com/temporalio/tchannel-go v1.22.1-0.20260129151045-8706a1ab5f61/go.mod h1:ezRQRwu9KQXy8Wuuv1aaFFxoCNz5CeNbVOOkh3xctbY= github.com/temporalio/ui-server/v2 v2.50.1 h1:dlyg96lFwOIaiXywDzPnSXVf8GPULLfrXXVXGqRsvJY= github.com/temporalio/ui-server/v2 v2.50.1/go.mod h1:jiWHDxRe4RDXLxGwenfV+FSgjXat81okVTTZPQOlc3c= +github.com/temporalio/ui-server/v2 v2.52.0 h1:BJ5e1teLOITbn4G9zZTuDOsROonI0uRThGlzaQ1aMxs= +github.com/temporalio/ui-server/v2 v2.52.0/go.mod h1:HKjuLT3J/hM1nRN6Vge2Z63tPWPNUVR/2+5CMbkBQns= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= From 99eb0500f6bbab923db1944c9baa7f3934b12fda Mon Sep 17 00:00:00 2001 From: Jeri Lane Date: Thu, 9 Jul 2026 07:48:30 -0700 Subject: [PATCH 26/28] When delegating to an extension, exit with the same code the extension used (#1116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Related issues CLDDX-141 ## What changed? When delegating to an extension (e.g. `temporal-cloud`, non-zero exit codes don't make it back to the user, so there's no programmatic way to know if the command succeeded. This change uses the exit code of the extension as the exit command for the `temporal` command that wrapped it. ## Checklist **Stability** - [ ] Breaking changes are marked with 💥 in the PR title and release notes - [ ] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [ ] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [ ] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [ ] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [ ] New flags don't duplicate an existing flag that serves the same purpose - [ ] New flags do not have short aliases without strong justification - [ ] Experimental features are marked with `(Experimental)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [ ] All flags shown in help text and examples are implemented and functional - [ ] Summaries use sentence case and have no trailing period - [ ] Long descriptions end with a period and include at least one example invocation - [ ] Examples use long flags (`--namespace`, not `-n`), one flag per line - [ ] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [ ] Results go to stdout; errors and warnings go to stderr - [ ] Error messages are lowercase with no trailing punctuation **Tests** - [ ] Added functional test(s) (`SharedServerSuite`) - [ ] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** Create an executable file named `temporal-exit` somewhere on your PATH ``` #!/bin/bash exit $1 ``` **Happy path** ``` $ temporal exit 0; echo $? 0 ``` **Error case** ``` $ temporal exit 1; echo $? 1 ``` Co-authored-by: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> --- internal/temporalcli/commands.extension.go | 12 ++++++++++-- internal/temporalcli/commands.extension_test.go | 10 +++++++--- internal/temporalcli/commands.go | 7 ++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/internal/temporalcli/commands.extension.go b/internal/temporalcli/commands.extension.go index 794242263..b00900810 100644 --- a/internal/temporalcli/commands.extension.go +++ b/internal/temporalcli/commands.extension.go @@ -25,6 +25,14 @@ var cliArgsToParseForExtension = map[string]bool{ "command-timeout": true, } +type ExtensionNonZeroExit struct { + *exec.ExitError +} + +func (err ExtensionNonZeroExit) Unwrap() error { + return err.ExitError +} + // 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) { @@ -77,8 +85,8 @@ func tryExecuteExtension(cctx *CommandContext, tcmd *TemporalCommand) (error, bo if ctx.Err() != nil { return fmt.Errorf("program interrupted"), true } - if _, ok := err.(*exec.ExitError); ok { - return nil, true + if exitError, ok := err.(*exec.ExitError); ok { + return ExtensionNonZeroExit{exitError}, true } return fmt.Errorf("extension %s failed: %w", extPath, err), true } diff --git a/internal/temporalcli/commands.extension_test.go b/internal/temporalcli/commands.extension_test.go index 266a8fcd1..2590be0cc 100644 --- a/internal/temporalcli/commands.extension_test.go +++ b/internal/temporalcli/commands.extension_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/temporalio/cli/internal/temporalcli" "golang.org/x/tools/imports" ) @@ -232,7 +233,7 @@ func TestExtension_FailsOnNonExecutableCommand(t *testing.T) { h := newExtensionHarness(t) // Create file without execute permission. path := filepath.Join(h.binDir, "temporal-foo") - err := os.WriteFile(path, []byte("a text file"), 0644) + err := os.WriteFile(path, []byte("a text file"), 0o644) require.NoError(t, err) res := h.Execute("foo") @@ -248,7 +249,10 @@ func TestExtension_PassesThroughNonZeroExit(t *testing.T) { res := h.Execute("foo") assert.Equal(t, "Args: temporal-foo \n", res.Stdout.String()) - assert.NoError(t, res.Err) + var exitError temporalcli.ExtensionNonZeroExit + if assert.ErrorAs(t, res.Err, &exitError) { + assert.Equal(t, 42, exitError.ExitCode()) + } } func TestExtension_FailsOnCommandTimeout(t *testing.T) { @@ -306,7 +310,7 @@ func (h *extensionHarness) createExtension(name string, code ...string) string { // Write source file. srcPath := filepath.Join(h.binDir, name+".go") - require.NoError(h.t, os.WriteFile(srcPath, formatted, 0644)) + require.NoError(h.t, os.WriteFile(srcPath, formatted, 0o644)) // Build executable. binPath := filepath.Join(h.binDir, name) diff --git a/internal/temporalcli/commands.go b/internal/temporalcli/commands.go index ecb52515d..eaf08dbdf 100644 --- a/internal/temporalcli/commands.go +++ b/internal/temporalcli/commands.go @@ -145,6 +145,11 @@ func (c *CommandContext) preprocessOptions() error { if c.Err() != nil { err = fmt.Errorf("program interrupted") } + if exitError, ok := errors.AsType[ExtensionNonZeroExit](err); ok { + // An extension failed after being found and successfully started. Here we defer + // to its own error handling logic, and just copy the exit code through. + os.Exit(exitError.ExitCode()) + } fmt.Fprintf(c.Options.Stderr, "Error: %v\n", err) os.Exit(1) } @@ -524,7 +529,7 @@ var buildInfo string func VersionString() string { // To add build-time information to the version string, use // go build -ldflags "-X github.com/temporalio/cli/internal.buildInfo=" - var bi = buildInfo + bi := buildInfo if bi != "" { bi = fmt.Sprintf(", %s", bi) } From 8270cfc450030c3747aac9615d1334c12a8e2f9c Mon Sep 17 00:00:00 2001 From: Quinn Klassen Date: Fri, 10 Jul 2026 08:01:50 -0700 Subject: [PATCH 27/28] Fix SANO operation list description (#1119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the proper name for the SA NexusEndpoint -> Endpoint. ## Related issues ## What changed? Fix the description for list. ## Checklist **Stability** - [ ] Breaking changes are marked with 💥 in the PR title and release notes - [ ] Changes to JSON output (`-o json` / `-o jsonl`) are treated as breaking changes **Design** - [ ] This feature does not depend on Cloud-only APIs or behavior (it works against an OSS server) - [ ] New commands follow `temporal ` structure (e.g. `temporal workflow start`) - [ ] New flags are named after the API concept, not the implementation mechanism (good: `--search-attribute`, bad: `--index-field`) - [ ] New flags don't duplicate an existing flag that serves the same purpose - [ ] New flags do not have short aliases without strong justification - [ ] Experimental features are marked with `(Experimental)` in `commands.yaml` **Help text** (see style guide at the top of `commands.yaml`) - [ ] All flags shown in help text and examples are implemented and functional - [ ] Summaries use sentence case and have no trailing period - [ ] Long descriptions end with a period and include at least one example invocation - [ ] Examples use long flags (`--namespace`, not `-n`), one flag per line - [ ] Placeholder values use `YourXxx` form (`YourWorkflowId`, `YourNamespace`) **Behavior** - [ ] Results go to stdout; errors and warnings go to stderr - [ ] Error messages are lowercase with no trailing punctuation **Tests** - [ ] Added functional test(s) (`SharedServerSuite`) - [ ] Added unit test(s) (`func TestXxx`) where applicable ## Manual tests **Setup** ``` temporal server start-dev --headless temporal workflow start \ --type YourWorkflowType \ --task-queue YourTaskQueue \ --workflow-id YourWorkflowId ``` **Happy path** ``` $ temporal \ --flag value ``` **Error case** ``` $ temporal \ --invalid-combination Error: $ echo $? 1 ``` **Composition** ``` $ temporal ... $ temporal --flag ``` --- internal/temporalcli/commands.gen.go | 8 ++++---- internal/temporalcli/commands.yaml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index 6fcf8bb1a..16e075dc6 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -1513,9 +1513,9 @@ func NewTemporalNexusOperationCountCommand(cctx *CommandContext, parent *Tempora s.Command.Use = "count [flags]" s.Command.Short = "Count Nexus Operations matching a query (Experimental)" if hasHighlighting { - s.Command.Long = "Return a count of Nexus Operations. Use \x1b[1m--query\x1b[0m\nto filter the operations to be counted.\n\n\x1b[1mtemporal nexus operation count \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + s.Command.Long = "Return a count of Nexus Operations. Use \x1b[1m--query\x1b[0m\nto filter the operations to be counted.\n\n\x1b[1mtemporal nexus operation count \\\n --query 'Endpoint=\"YourEndpoint\"'\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." } else { - s.Command.Long = "Return a count of Nexus Operations. Use `--query`\nto filter the operations to be counted.\n\n```\ntemporal nexus operation count \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\n```\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + s.Command.Long = "Return a count of Nexus Operations. Use `--query`\nto filter the operations to be counted.\n\n```\ntemporal nexus operation count \\\n --query 'Endpoint=\"YourEndpoint\"'\n```\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.Query, "query", "q", "", "Query to filter Nexus Operation Executions to count.") @@ -1600,9 +1600,9 @@ func NewTemporalNexusOperationListCommand(cctx *CommandContext, parent *Temporal s.Command.Use = "list [flags]" s.Command.Short = "List Nexus Operations matching a query (Experimental)" if hasHighlighting { - s.Command.Long = "List Nexus Operations. Use \x1b[1m--query\x1b[0m to filter results.\n\n\x1b[1mtemporal nexus operation list \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + s.Command.Long = "List Nexus Operations. Use \x1b[1m--query\x1b[0m to filter results.\n\n\x1b[1mtemporal nexus operation list \\\n --query 'Endpoint=\"YourEndpoint\"'\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." } else { - s.Command.Long = "List Nexus Operations. Use `--query` to filter results.\n\n```\ntemporal nexus operation list \\\n --query 'NexusEndpoint=\"YourEndpoint\"'\n```\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." + s.Command.Long = "List Nexus Operations. Use `--query` to filter results.\n\n```\ntemporal nexus operation list \\\n --query 'Endpoint=\"YourEndpoint\"'\n```\n\nVisit https://docs.temporal.io/visibility to read more about\nSearch Attributes and queries." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.Query, "query", "q", "", "Query to filter the Nexus Operation Executions to list.") diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index ac166de20..9127c5f92 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -1842,7 +1842,7 @@ commands: ``` temporal nexus operation list \ - --query 'NexusEndpoint="YourEndpoint"' + --query 'Endpoint="YourEndpoint"' ``` Visit https://docs.temporal.io/visibility to read more about @@ -1871,7 +1871,7 @@ commands: ``` temporal nexus operation count \ - --query 'NexusEndpoint="YourEndpoint"' + --query 'Endpoint="YourEndpoint"' ``` Visit https://docs.temporal.io/visibility to read more about From 9bcd5dc6631effbe85e337843a06998924a3e51b Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:38:02 -0500 Subject: [PATCH 28/28] ci: run required checks for merge groups --- .github/workflows/ci.yaml | 4 +++- .github/workflows/govulncheck.yml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6a5df116d..e6728fd07 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,8 @@ name: Continuous Integration on: pull_request: + merge_group: + types: [checks_requested] push: branches: - main @@ -12,7 +14,7 @@ permissions: jobs: validate-server-version: name: Validate Server Version - if: github.event_name == 'push' || github.base_ref == 'main' + if: github.event_name == 'push' || github.event_name == 'merge_group' || github.base_ref == 'main' runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 7c49fd9df..118c44fb1 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -2,6 +2,8 @@ name: Govulncheck on: pull_request: + merge_group: + types: [checks_requested] permissions: contents: read