From d418c689cbfc516170ab2f6c91b16cf6e1af378c Mon Sep 17 00:00:00 2001 From: Alan Clucas Date: Mon, 2 Dec 2024 14:05:37 +0000 Subject: [PATCH 01/23] fix: archive fallback tests Signed-off-by: Alan Clucas (cherry picked from commit d2859eb255b234b2c7c944a478db2d468cb2f264) --- test/e2e/argo_server_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/argo_server_test.go b/test/e2e/argo_server_test.go index 0159cf5177da..3000261ad47e 100644 --- a/test/e2e/argo_server_test.go +++ b/test/e2e/argo_server_test.go @@ -611,7 +611,7 @@ func (s *ArgoServerSuite) TestPermission() { // Test get wf w/ archive fallback with good token s.bearerToken = goodToken s.Run("GetWFsFallbackArchivedGoodToken", func() { - s.e().GET("/api/v1/workflows/"+uid). + s.e().GET("/api/v1/workflows/"+nsName). WithQuery("listOptions.labelSelector", "workflows.argoproj.io/test"). Expect(). Status(200) @@ -620,7 +620,7 @@ func (s *ArgoServerSuite) TestPermission() { // Test get wf w/ archive fallback with bad token s.bearerToken = badToken s.Run("GetWFsFallbackArchivedBadToken", func() { - s.e().GET("/api/v1/workflows/" + uid). + s.e().GET("/api/v1/workflows/" + nsName). Expect(). Status(403) }) @@ -628,9 +628,9 @@ func (s *ArgoServerSuite) TestPermission() { // Test get wf w/ archive fallback with fake token s.bearerToken = fakeToken s.Run("GetWFsFallbackArchivedFakeToken", func() { - s.e().GET("/api/v1/workflows/" + uid). + s.e().GET("/api/v1/workflows/" + nsName). Expect(). - Status(403) + Status(401) }) // Test deleting archived wf with bad token From 5bf74c3f8f945ec397747e26007f2e64c33e2838 Mon Sep 17 00:00:00 2001 From: Alan Clucas Date: Thu, 6 Feb 2025 03:11:02 +0000 Subject: [PATCH 02/23] feat: enable cherry-pick bot (#14151) (cherry picked from commit e4f51c23af08ce2e0cd449b1e454b2d9103e3980) --- .github/cherry-pick-bot.yml | 2 ++ .github/workflows/ci-build.yaml | 12 ++++++++++++ .github/workflows/docs.yaml | 12 ++++++++++++ docs/CONTRIBUTING.md | 10 ++++++++++ docs/releasing.md | 10 +--------- 5 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 .github/cherry-pick-bot.yml diff --git a/.github/cherry-pick-bot.yml b/.github/cherry-pick-bot.yml new file mode 100644 index 000000000000..1f62315d79dc --- /dev/null +++ b/.github/cherry-pick-bot.yml @@ -0,0 +1,2 @@ +enabled: true +preservePullRequestTitle: true diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 28857b7d35ee..3240aa861915 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -8,6 +8,9 @@ on: pull_request: branches: - "main" + - "release-*" + issue_comment: + types: [created] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,6 +22,15 @@ permissions: jobs: changed-files: name: Get changed files + if: > + ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request != null && + github.event.comment.author_association == 'MEMBER' && + github.event.comment.body == '/test' + ) || ( + github.event_name != 'issue_comment' + ) outputs: # reference: https://github.com/tj-actions/changed-files#outputs- tests: ${{ steps.changed-files.outputs.tests_any_modified == 'true' }} diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 7df11be0449a..33b4adb43c50 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -7,6 +7,9 @@ on: pull_request: branches: - main + - release/* + issue_comment: + types: [created] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -18,6 +21,15 @@ permissions: jobs: docs: runs-on: ubuntu-24.04 + if: > + ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request != null && + github.event.comment.author_association == 'MEMBER' && + github.event.comment.body == '/test' + ) || ( + github.event_name != 'issue_comment' + ) steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4afbdacbaf96..997d14f2506d 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -134,6 +134,16 @@ Subsequently, if there is still no response, it will be automatically closed as See the [Stale Action configuration](https://github.com/argoproj/argo-workflows/blob/main/.github/workflows/stale.yaml) for more details. +## Automated actions + +As a member (see [roles](https://github.com/argoproj/argoproj/blob/main/community/membership.md)) of the argo-project you can use the following comments on PRs to trigger actions: + +* `/retest` - re-run any failing test cases +* `/test` - trigger the full test suite. +Only use this for PRs where the test suite has not automatically triggered - this is almost always wasteful and will not make things pass that `/retest` doesn't pass. +* `/cherry-pick ` - will [attempt to cherry-pick](https://github.com/googleapis/repo-automation-bots/tree/main/packages/cherry-pick-bot) this commit after it has been merged to the target branch. +This can be used prior to merging and the PR will be created after the merge, or commented after merging for an immediate attempt. + ## Sustainability Effort Argo Workflows is seeking more [Reviewers and Approvers](https://github.com/argoproj/argoproj/blob/main/community/membership.md) to help keep it viable. diff --git a/docs/releasing.md b/docs/releasing.md index 8293f62226f2..9982542a0e0c 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -56,15 +56,7 @@ this was successful. ## Update Changelog Once the tag is published, GitHub Actions will automatically open a PR to update the changelog. Once the PR is ready, -you can approve it, enable auto-merge, and then run the following to force trigger the CI build: - -```bash -git branch -D create-pull-request/changelog -git fetch upstream -git checkout --track upstream/create-pull-request/changelog -git commit -s --allow-empty -m "chore: Force trigger CI" -git push upstream create-pull-request/changelog -``` +you can approve it, enable auto-merge, and comment `/test` to run CI on it. ## Announce on Slack From 69b07e89bad42aa63d0dce93d3299df6d16e8dc8 Mon Sep 17 00:00:00 2001 From: Alan Clucas Date: Fri, 10 Jan 2025 10:28:07 +0000 Subject: [PATCH 03/23] fix: update upload-artifact and download-artifact (#14070) Signed-off-by: Alan Clucas (cherry picked from commit a7a72e7ca3c2bbf127b65fdcf1b5b85fbd2aad19) --- .github/dependabot.yml | 4 ---- .github/workflows/ci-build.yaml | 4 ++-- .github/workflows/docs.yaml | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1b590db438fd..40a43b0f02da 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -64,10 +64,6 @@ updates: schedule: interval: "weekly" day: "saturday" - ignore: - # temporarily ignore until https://github.com/actions/download-artifact/issues/249 is resolved - - dependency-name: "actions/download-artifact" - - dependency-name: "actions/upload-artifact" # ignore all non-security updates: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit open-pull-requests-limit: 0 labels: diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 3240aa861915..4b1968c75db7 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -190,7 +190,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - name: Upload - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{matrix.image}}_image.tar path: /tmp/${{matrix.image}}_image.tar @@ -301,7 +301,7 @@ jobs: echo " token: xxxxxx" >> $KUBECONFIG until kubectl cluster-info ; do sleep 10s ; done - name: Download images - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: '*_image.tar' path: /tmp diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 33b4adb43c50..b233813e2452 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -49,7 +49,7 @@ jobs: run: git diff --exit-code # Upload the site so reviewers see it. - name: Upload Docs Site - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: docs path: site From 88d799974ef727ab2bdac7bae83d91f1516f6caf Mon Sep 17 00:00:00 2001 From: "gcp-cherry-pick-bot[bot]" <98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:31:11 +0000 Subject: [PATCH 04/23] ci: Update sematic PR check to v5.5.3 (cherry-pick #14152) (#14154) Signed-off-by: Yuan Tang Co-authored-by: Yuan Tang Co-authored-by: Alan Clucas --- .github/workflows/pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 760977757677..aa5d13a02421 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -16,6 +16,6 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check PR Title's semantic conformance - uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f # v5.4.0 + uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2439becb969b17fcf1d3ae18dd34be300663d7d1 Mon Sep 17 00:00:00 2001 From: chengjoey <30427474+chengjoey@users.noreply.github.com> Date: Thu, 6 Feb 2025 00:40:56 +0800 Subject: [PATCH 05/23] fix(controller): task progress with mutexes not updated. Fixes #14148 (#14149) Signed-off-by: joey (cherry picked from commit abeffc7fa306c7dc08738afd7e1db78f201ffb03) --- workflow/controller/operator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index 43fda8c0acb8..b8402e911b72 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -256,7 +256,7 @@ func (woc *wfOperationCtx) operate(ctx context.Context) { woc.markWorkflowFailed(ctx, fmt.Sprintf("Failed to acquire the synchronization lock. %s", err.Error())) return } - woc.updated = wfUpdate + woc.updated = woc.updated || wfUpdate if !acquired { if !woc.releaseLocksForPendingShuttingdownWfs(ctx) { woc.log.Warn("Workflow processing has been postponed due to concurrency limit") From 731c1206adb6cce3fcd9195cf9b8631fc982c7ab Mon Sep 17 00:00:00 2001 From: Tim Collins <45351296+tico24@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:43:27 +0000 Subject: [PATCH 06/23] docs: explicitly reference mkdocs.yml in readthedocs config (#14016) Signed-off-by: Tim Collins (cherry picked from commit 7d7ebb1740f9a7e84d99aabe76497f8eec7de463) --- .readthedocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 71bd86159aeb..3090548be7e5 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,6 +2,7 @@ version: 2 formats: all mkdocs: fail_on_warning: false + configuration: mkdocs.yml python: install: - requirements: docs/requirements.txt From 6605c8c3d7c878648b6837fc1d717e11d874ad41 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Mon, 27 Jan 2025 18:02:34 -0800 Subject: [PATCH 07/23] build: upgrade `kit` (#14105) (cherry picked from commit bc19d5ec3353398b785889a1f39315c9097dd5d2) Signed-off-by: Alan Clucas --- .devcontainer/pre-build.sh | 4 +- Makefile | 10 +- hack/port-forward.sh | 65 +--------- tasks.yaml | 235 +++++++++++++++++++++---------------- 4 files changed, 142 insertions(+), 172 deletions(-) diff --git a/.devcontainer/pre-build.sh b/.devcontainer/pre-build.sh index 16727647fa36..a74aaae14402 100755 --- a/.devcontainer/pre-build.sh +++ b/.devcontainer/pre-build.sh @@ -13,7 +13,7 @@ sudo mv ./kubectl /usr/local/bin/kubectl kubectl cluster-info # install kit -curl -q https://raw.githubusercontent.com/kitproj/kit/main/install.sh | sh +make kit # install protocol buffer compiler (protoc) sudo apt update @@ -25,7 +25,7 @@ sudo chown vscode:vscode /home/vscode/go/src || true sudo chown vscode:vscode /home/vscode/go/src/github.com || true # download dependencies and do first-pass compile -CI=1 kit pre-up +kit build # Patch CoreDNS to have host.docker.internal inside the cluster available kubectl get cm coredns -n kube-system -o yaml | sed "s/ NodeHosts: |/ NodeHosts: |\n `grep host.docker.internal /etc/hosts`/" | kubectl apply -f - diff --git a/Makefile b/Makefile index b324d746cc53..b0a6923806c2 100644 --- a/Makefile +++ b/Makefile @@ -532,15 +532,7 @@ dist/argosay: .PHONY: kit kit: Makefile -ifeq ($(shell command -v kit),) -ifeq ($(shell uname),Darwin) - brew tap kitproj/kit --custom-remote https://github.com/kitproj/kit - brew install kit -else - curl -q https://raw.githubusercontent.com/kitproj/kit/main/install.sh | tag=v0.1.8 sh -endif -endif - + go install github.com/kitproj/kit@v0.1.79 .PHONY: start ifeq ($(RUN_MODE),local) diff --git a/hack/port-forward.sh b/hack/port-forward.sh index e2e7cce7e1e7..b07bf522983f 100755 --- a/hack/port-forward.sh +++ b/hack/port-forward.sh @@ -1,67 +1,6 @@ #!/usr/bin/env bash set -eu -o pipefail -pf() { - set -eu -o pipefail - resource=$1 - port=$2 - dest_port=${3:-"$port"} - ./hack/free-port.sh $port - echo "port-forward $resource $port" - kubectl -n argo port-forward "svc/$resource" "$port:$dest_port" & - until lsof -i ":$port" > /dev/null ; do sleep 1 ; done -} +go install github.com/kitproj/kubeauto@v0.0.7 -wait-for() { - set -eu -o pipefail - echo "wait-for $1" - kubectl -n argo wait --timeout 2m --for=condition=Available deploy/$1 -} - - -dex=$(kubectl -n argo get pod -l app=dex -o name) -if [[ "$dex" != "" ]]; then - wait-for dex - pf dex 5556 -fi - -postgres=$(kubectl -n argo get pod -l app=postgres -o name) -if [[ "$postgres" != "" ]]; then - wait-for postgres - pf postgres 5432 -fi - -mysql=$(kubectl -n argo get pod -l app=mysql -o name) -if [[ "$mysql" != "" ]]; then - wait-for mysql - pf mysql 3306 -fi - -if [[ "$(kubectl -n argo get pod -l app=argo-server -o name)" != "" ]]; then - wait-for argo-server - pf argo-server 2746 -fi - -if [[ "$(kubectl -n argo get pod -l app=workflow-controller -o name)" != "" ]]; then - wait-for workflow-controller - pf workflow-controller-metrics 9090 - if [[ "$(kubectl -n argo get svc workflow-controller-pprof -o name)" != "" ]]; then - pf workflow-controller-pprof 6060 - fi -fi - -if [[ "$(kubectl -n argo get pod -l app=prometheus -o name)" != "" ]]; then - wait-for prometheus - pf prometheus 9091 9090 -fi - -azurite=$(kubectl -n argo get pod -l app=azurite -o name) -if [[ "$azurite" != "" ]]; then - wait-for azurite - pf azurite 10000 -fi - -# forward MinIO last, so we can just wait for port 9000, and know that all ports are ready -wait-for minio -pf minio 9000 -pf minio 9001 \ No newline at end of file +kubeauto -p 0 diff --git a/tasks.yaml b/tasks.yaml index 246f1993ebe5..6c59bca5add2 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -1,98 +1,137 @@ -apiVersion: kit/v1 -kind: Tasks -metadata: - annotations: - help: | - Install `kit` by following https://github.com/kitproj/kit#install. - - Run `kit up` to start argo. - - - `env PROFILE=mysql kit up` to start with MySQL. - - `env PROFILE=plugins ARGO_EXECUTOR_PLUGINS=true kit up` to start with plugins. - - `env PROFILE=sso ARGO_AUTH_MODE=sso kit up` to start with SSO. - - The app will be up-and-running between 15s and 1m later (if hot compiled or cold). - Any changes made to the source code will be automatically recompiled and the app restarted, typically within a few seconds. - name: argo-workflows -spec: - tasks: - - name: go-deps - command: go mod download - - name: install - command: sh -c "make install PROFILE=$PROFILE" - env: - - PROFILE=minimal - dependencies: go-deps - watch: manifests - mutex: docker - - name: build-controller - command: make ./dist/workflow-controller - watch: cmd/workflow-controller config errors persist pkg util workflow - dependencies: go-deps - mutex: build - - name: port-forward - command: ./hack/port-forward.sh - ports: 9000 - dependencies: install - - name: controller - command: ./dist/workflow-controller - dependencies: install build-controller port-forward - env: - - ARGO_EXECUTOR_PLUGINS=false - - ARGO_NAMESPACE=argo - - ARGO_NAMESPACED=true - - ARGO_MANAGED_NAMESPACE=argo - - ARGO_LOGLEVEL=info - - ARGO_REMOVE_PVC_PROTECTION_FINALIZER=true - - ARGO_PROGRESS_PATCH_TICK_DURATION=7s - - DEFAULT_REQUEUE_TIME=1s - - LEADER_ELECTION_IDENTITY=local - - ALWAYS_OFFLOAD_NODE_STATUS=false - - OFFLOAD_NODE_STATUS_TTL=30s - - WORKFLOW_GC_PERIOD=30s - - UPPERIO_DB_DEBUG=1 - - ARCHIVED_WORKFLOW_GC_PERIOD=30s - ports: "9090" - - name: build-argo - command: make ./dist/argo - dependencies: go-deps - env: - - STATIC_FILES=false - watch: cmd/argo config errors persist pkg util server workflow - mutex: build - - name: server - command: ./dist/argo server - dependencies: build-argo port-forward - env: - - ARGO_X_FRAME_OPTIONS=SAMEORIGIN - - ARGO_SECURE=false - - ARGO_NAMESPACE=argo - - ARGO_NAMESPACED=true - - ARGO_LOGLEVEL=info - - ARGO_AUTH_MODE=hybrid - - ARGO_MANAGED_NAMESPACE=argo - - UPPERIO_DB_DEBUG=1 - ports: "2746" - - name: ui-deps - command: yarn install - workingDir: ui - watch: ui/package.json ui/yarn.lock - - name: ui - command: yarn start - workingDir: ui - dependencies: ui-deps - ports: "8080" - - name: executor - command: make argoexec-image - watch: cmd/argoexec config errors pkg util workflow - mutex: docker - - name: example - command: kubectl create -f examples/hello-world.yaml - dependencies: install - mutex: docker - - name: build - dependencies: build-controller build-argo - - name: pre-up - dependencies: build install executor example - - name: up - dependencies: pre-up controller server ui +tasks: + build: + dependencies: + - build-controller + - build-cli + - build-executor + build-cli: + command: + - make + - ./dist/argo + env: + STATIC_FILES: "false" + mutex: build + watch: + - cmd/argo + - config + - errors + - persist + - pkg + - util + - server + - workflow + build-controller: + command: + - make + - ./dist/workflow-controller + mutex: build + watch: + - cmd/workflow-controller + - config + - errors + - persist + - pkg + - util + - workflow + build-executor: + command: + - make + - argoexec-image + mutex: docker + watch: + - cmd/argoexec + - config + - errors + - pkg + - util + - workflow + controller: + command: + - ./dist/workflow-controller + dependencies: + - build-controller + - port-forward + env: + ALWAYS_OFFLOAD_NODE_STATUS: "false" + ARCHIVED_WORKFLOW_GC_PERIOD: 30s + ARGO_EXECUTOR_PLUGINS: "false" + ARGO_LOGLEVEL: info + ARGO_MANAGED_NAMESPACE: argo + ARGO_NAMESPACE: argo + ARGO_NAMESPACED: "true" + ARGO_PROGRESS_PATCH_TICK_DURATION: 7s + ARGO_REMOVE_PVC_PROTECTION_FINALIZER: "true" + DEFAULT_REQUEUE_TIME: 1s + LEADER_ELECTION_IDENTITY: local + OFFLOAD_NODE_STATUS_TTL: 30s + UPPERIO_DB_DEBUG: "1" + WORKFLOW_GC_PERIOD: 30s + ports: + - "9090" + example: + command: + - kubectl + - create + - -f + - examples/hello-world.yaml + dependencies: + - install + mutex: docker + install: + env: + PROFILE: minimal + mutex: docker + sh: make install PROFILE=$PROFILE + watch: + - manifests + port-forward: + command: + - ./hack/port-forward.sh + dependencies: + - install + ports: + - "9000" + server: + command: + - ./dist/argo + - server + dependencies: + - build-cli + - port-forward + env: + ARGO_AUTH_MODE: hybrid + ARGO_LOGLEVEL: info + ARGO_MANAGED_NAMESPACE: argo + ARGO_NAMESPACE: argo + ARGO_NAMESPACED: "true" + ARGO_SECURE: "false" + ARGO_X_FRAME_OPTIONS: SAMEORIGIN + UPPERIO_DB_DEBUG: "1" + ports: + - "2746" + ui: + command: + - yarn + - start + dependencies: + - ui-deps + ports: + - "8080" + watch: + - package.json + workingDir: ui + ui-deps: + command: + - yarn + - install + workingDir: ui + up: + command: + - sleep + - "999999" + dependencies: + - example + - controller + - server + - ui + - build-executor From c96c5dc7e6e3c8d6698c80c48ea3ad7aef2660ca Mon Sep 17 00:00:00 2001 From: Alan Clucas Date: Thu, 6 Feb 2025 18:39:47 +0000 Subject: [PATCH 08/23] fix: locking in metrics (backport release-3.6) (#14156) --- .../cronworkflow-deprecated-schedule.yaml | 3 +- util/telemetry/instrument.go | 8 +- util/telemetry/metrics.go | 40 ++++++-- util/telemetry/operators.go | 4 +- workflow/metrics/counter_log.go | 2 +- workflow/metrics/gauge_pod_phase.go | 4 +- workflow/metrics/gauge_workflow_condition.go | 5 +- workflow/metrics/gauge_workflow_phase.go | 5 +- workflow/metrics/leader.go | 4 +- workflow/metrics/metrics.go | 2 + workflow/metrics/metrics_custom.go | 97 ++++++++++++------- workflow/metrics/metrics_test.go | 25 ++--- workflow/metrics/work_queue.go | 28 +++--- 13 files changed, 138 insertions(+), 89 deletions(-) diff --git a/test/e2e/testdata/cronworkflow-deprecated-schedule.yaml b/test/e2e/testdata/cronworkflow-deprecated-schedule.yaml index abfda0b51fde..298e2e8547e4 100644 --- a/test/e2e/testdata/cronworkflow-deprecated-schedule.yaml +++ b/test/e2e/testdata/cronworkflow-deprecated-schedule.yaml @@ -3,8 +3,7 @@ kind: CronWorkflow metadata: name: test-cron-deprecated-schedule spec: - schedules: - - "* * * * *" + schedule: "* * * * *" concurrencyPolicy: "Forbid" startingDeadlineSeconds: 0 workflowSpec: diff --git a/util/telemetry/instrument.go b/util/telemetry/instrument.go index 6831dd5be804..ed12b748873f 100644 --- a/util/telemetry/instrument.go +++ b/util/telemetry/instrument.go @@ -17,7 +17,7 @@ type Instrument struct { } func (m *Metrics) preCreateCheck(name string) error { - if _, exists := m.AllInstruments[name]; exists { + if inst := m.GetInstrument(name); inst != nil { return fmt.Errorf("Instrument called %s already exists", name) } return nil @@ -69,8 +69,6 @@ func collectOptions(options ...instrumentOption) instrumentOptions { func (m *Metrics) CreateInstrument(instType instrumentType, name, desc, unit string, options ...instrumentOption) error { opts := collectOptions(options...) - m.Mutex.Lock() - defer m.Mutex.Unlock() err := m.preCreateCheck(name) if err != nil { return err @@ -137,11 +135,11 @@ func (m *Metrics) CreateInstrument(instType instrumentType, name, desc, unit str if err != nil { return err } - m.AllInstruments[name] = &Instrument{ + m.AddInstrument(name, &Instrument{ name: name, description: desc, otel: instPtr, - } + }) return nil } diff --git a/util/telemetry/metrics.go b/util/telemetry/metrics.go index a4583fe92aed..e6094588c867 100644 --- a/util/telemetry/metrics.go +++ b/util/telemetry/metrics.go @@ -29,15 +29,39 @@ type Config struct { } type Metrics struct { - // Ensures mutual exclusion in workflows map - Mutex sync.RWMutex - // Evil context for compatibility with legacy context free interfaces Ctx context.Context otelMeter *metric.Meter config *Config - AllInstruments map[string]*Instrument + // Ensures mutual exclusion in instruments + mutex sync.RWMutex + instruments map[string]*Instrument +} + +func (m *Metrics) AddInstrument(name string, inst *Instrument) { + m.mutex.Lock() + defer m.mutex.Unlock() + m.instruments[name] = inst +} + +func (m *Metrics) GetInstrument(name string) *Instrument { + m.mutex.RLock() + defer m.mutex.RUnlock() + inst, ok := m.instruments[name] + if !ok { + return nil + } + return inst +} + +// IterateROInstruments iterates over every instrument for Read-Only purposes +func (m *Metrics) IterateROInstruments(fn func(i *Instrument)) { + m.mutex.RLock() + defer m.mutex.RUnlock() + for _, i := range m.instruments { + fn(i) + } } func NewMetrics(ctx context.Context, serviceName, prometheusName string, config *Config, extraOpts ...metricsdk.Option) (*Metrics, error) { @@ -81,10 +105,10 @@ func NewMetrics(ctx context.Context, serviceName, prometheusName string, config meter := provider.Meter(serviceName) metrics := &Metrics{ - Ctx: ctx, - otelMeter: &meter, - config: config, - AllInstruments: make(map[string]*Instrument), + Ctx: ctx, + otelMeter: &meter, + config: config, + instruments: make(map[string]*Instrument), } return metrics, nil diff --git a/util/telemetry/operators.go b/util/telemetry/operators.go index f99f7426d5d6..3e87b33f69cf 100644 --- a/util/telemetry/operators.go +++ b/util/telemetry/operators.go @@ -10,7 +10,7 @@ import ( ) func (m *Metrics) AddInt(ctx context.Context, name string, val int64, attribs InstAttribs) { - if instrument, ok := m.AllInstruments[name]; ok { + if instrument := m.GetInstrument(name); instrument != nil { instrument.AddInt(ctx, val, attribs) } else { log.Errorf("Metrics addInt() to non-existent metric %s", name) @@ -29,7 +29,7 @@ func (i *Instrument) AddInt(ctx context.Context, val int64, attribs InstAttribs) } func (m *Metrics) Record(ctx context.Context, name string, val float64, attribs InstAttribs) { - if instrument, ok := m.AllInstruments[name]; ok { + if instrument := m.GetInstrument(name); instrument != nil { instrument.Record(ctx, val, attribs) } else { log.Errorf("Metrics record() to non-existent metric %s", name) diff --git a/workflow/metrics/counter_log.go b/workflow/metrics/counter_log.go index b9cea55952ab..bcacfc5eac18 100644 --- a/workflow/metrics/counter_log.go +++ b/workflow/metrics/counter_log.go @@ -21,7 +21,7 @@ func addLogCounter(ctx context.Context, m *Metrics) error { telemetry.WithAsBuiltIn(), ) lm := logMetric{ - counter: m.AllInstruments[nameLogMessages], + counter: m.GetInstrument(nameLogMessages), } log.AddHook(lm) for _, level := range lm.Levels() { diff --git a/workflow/metrics/gauge_pod_phase.go b/workflow/metrics/gauge_pod_phase.go index b93e9c33729b..6a1b1b2de94d 100644 --- a/workflow/metrics/gauge_pod_phase.go +++ b/workflow/metrics/gauge_pod_phase.go @@ -31,9 +31,9 @@ func addPodPhaseGauge(ctx context.Context, m *Metrics) error { if m.callbacks.PodPhase != nil { ppGauge := podPhaseGauge{ callback: m.callbacks.PodPhase, - gauge: m.AllInstruments[namePodsPhase], + gauge: m.GetInstrument(namePodsPhase), } - return m.AllInstruments[namePodsPhase].RegisterCallback(m.Metrics, ppGauge.update) + return ppGauge.gauge.RegisterCallback(m.Metrics, ppGauge.update) } return nil } diff --git a/workflow/metrics/gauge_workflow_condition.go b/workflow/metrics/gauge_workflow_condition.go index 3709cd07d9d1..a876e358b72e 100644 --- a/workflow/metrics/gauge_workflow_condition.go +++ b/workflow/metrics/gauge_workflow_condition.go @@ -32,9 +32,10 @@ func addWorkflowConditionGauge(_ context.Context, m *Metrics) error { if m.callbacks.WorkflowCondition != nil { wfcGauge := workflowConditionGauge{ callback: m.callbacks.WorkflowCondition, - gauge: m.AllInstruments[nameWorkflowCondition], + gauge: m.GetInstrument(nameWorkflowCondition), } - return m.AllInstruments[nameWorkflowCondition].RegisterCallback(m.Metrics, wfcGauge.update) + return wfcGauge.gauge.RegisterCallback(m.Metrics, wfcGauge.update) + } return nil // TODO init all phases? diff --git a/workflow/metrics/gauge_workflow_phase.go b/workflow/metrics/gauge_workflow_phase.go index 59a6e670e413..9de744c492ba 100644 --- a/workflow/metrics/gauge_workflow_phase.go +++ b/workflow/metrics/gauge_workflow_phase.go @@ -31,9 +31,10 @@ func addWorkflowPhaseGauge(_ context.Context, m *Metrics) error { if m.callbacks.WorkflowPhase != nil { wfpGauge := workflowPhaseGauge{ callback: m.callbacks.WorkflowPhase, - gauge: m.AllInstruments[nameWorkflowPhaseGauge], + gauge: m.GetInstrument(nameWorkflowPhaseGauge), } - return m.AllInstruments[nameWorkflowPhaseGauge].RegisterCallback(m.Metrics, wfpGauge.update) + return wfpGauge.gauge.RegisterCallback(m.Metrics, wfpGauge.update) + } return nil // TODO init all phases? diff --git a/workflow/metrics/leader.go b/workflow/metrics/leader.go index ff3562b66b52..a902ee6f62c5 100644 --- a/workflow/metrics/leader.go +++ b/workflow/metrics/leader.go @@ -31,9 +31,9 @@ func addIsLeader(ctx context.Context, m *Metrics) error { } lGauge := leaderGauge{ callback: m.callbacks.IsLeader, - gauge: m.AllInstruments[nameLeader], + gauge: m.GetInstrument(nameLeader), } - return m.AllInstruments[nameLeader].RegisterCallback(m.Metrics, lGauge.update) + return lGauge.gauge.RegisterCallback(m.Metrics, lGauge.update) } func (l *leaderGauge) update(_ context.Context, o metric.Observer) error { diff --git a/workflow/metrics/metrics.go b/workflow/metrics/metrics.go index 19243036be6e..73e5d22f57c6 100644 --- a/workflow/metrics/metrics.go +++ b/workflow/metrics/metrics.go @@ -2,6 +2,7 @@ package metrics import ( "context" + "sync" "github.com/argoproj/argo-workflows/v3/util/telemetry" @@ -12,6 +13,7 @@ type Metrics struct { *telemetry.Metrics callbacks Callbacks + realtimeMutex sync.Mutex realtimeWorkflows map[string][]realtimeTracker } diff --git a/workflow/metrics/metrics_custom.go b/workflow/metrics/metrics_custom.go index 77897a5d417c..65e5df1567ed 100644 --- a/workflow/metrics/metrics_custom.go +++ b/workflow/metrics/metrics_custom.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "strconv" + "sync" "time" "go.opentelemetry.io/otel/metric" @@ -28,6 +29,27 @@ type customMetricValue struct { key string } +type customMetricUserData struct { + mutex sync.RWMutex + values map[string]*customMetricValue +} + +func newUserData() *customMetricUserData { + return &customMetricUserData{ + values: make(map[string]*customMetricValue), + } +} + +func (ud *customMetricUserData) GetValue(key string) *customMetricValue { + ud.mutex.RLock() + defer ud.mutex.RUnlock() + val, ok := ud.values[key] + if !ok { + return nil + } + return val +} + type realtimeTracker struct { inst *telemetry.Instrument key string @@ -41,27 +63,30 @@ func (cmv *customMetricValue) getLabels() telemetry.InstAttribs { return labels } -func customUserdata(i *telemetry.Instrument, requireSuccess bool) map[string]*customMetricValue { +func customUserData(i *telemetry.Instrument, requireSuccess bool) *customMetricUserData { switch val := i.GetUserdata().(type) { - case map[string]*customMetricValue: + case *customMetricUserData: return val default: if requireSuccess { panic(fmt.Errorf("internal error: unexpected userdata on custom metric %s", i.GetName())) } - return make(map[string]*customMetricValue) + return nil } } func getOrCreateValue(i *telemetry.Instrument, key string, labels []*wfv1.MetricLabel) *customMetricValue { - if value, ok := customUserdata(i, true)[key]; ok { + ud := customUserData(i, true) + ud.mutex.Lock() + defer ud.mutex.Unlock() + if value, ok := ud.values[key]; ok { return value } newValue := customMetricValue{ key: key, labels: labels, } - customUserdata(i, true)[key] = &newValue + ud.values[key] = &newValue return &newValue } @@ -74,7 +99,10 @@ type customInstrument struct { // For non-realtime we have to fake observability as prometheus provides // up/down and set on the same gauge type, which otel forbids. func (i *customInstrument) customCallback(_ context.Context, o metric.Observer) error { - for _, value := range customUserdata(i.Instrument, true) { + ud := customUserData(i.Instrument, true) + ud.mutex.RLock() + defer ud.mutex.RUnlock() + for _, value := range ud.values { if value.rtValueFunc != nil { i.ObserveFloat(o, value.rtValueFunc(), value.getLabels()) } else { @@ -84,35 +112,23 @@ func (i *customInstrument) customCallback(_ context.Context, o metric.Observer) return nil } -// func addCustomMetrics(_ context.Context, m *Metrics) error { -// m.customMetrics = make(map[string]*customMetric, 0) -// return nil -// } - // GetCustomMetric returns a custom (or any) metric from it's key // This is exported for legacy testing only func (m *Metrics) GetCustomMetric(key string) *telemetry.Instrument { - m.Mutex.RLock() - defer m.Mutex.RUnlock() - // It's okay to return nil metrics in this function - return m.AllInstruments[key] + return m.GetInstrument(key) } // CustomMetricExists returns if metric exists from its key // This is exported for testing only func (m *Metrics) CustomMetricExists(key string) bool { - m.Mutex.RLock() - defer m.Mutex.RUnlock() - - // It's okay to return nil metrics in this function - return m.AllInstruments[key] != nil + return m.GetCustomMetric(key) != nil } // TODO labels on custom metrics func (m *Metrics) matchExistingMetric(metricSpec *wfv1.Prometheus) (*telemetry.Instrument, error) { key := metricSpec.Name - if inst, ok := m.AllInstruments[key]; ok { + if inst := m.GetInstrument(key); inst != nil { if inst.GetDescription() != metricSpec.Help { return nil, fmt.Errorf("Help for metric %s is already set to %s, it cannot be changed", metricSpec.Name, inst.GetDescription()) } @@ -152,11 +168,11 @@ func (m *Metrics) ensureBaseMetric(metricSpec *wfv1.Prometheus, ownerKey string) return nil, err } m.attachCustomMetricToWorkflow(metricSpec, ownerKey) - inst := m.AllInstruments[metricSpec.Name] + inst := m.GetInstrument(metricSpec.Name) if inst == nil { return nil, fmt.Errorf("Failed to create new metric %s", metricSpec.Name) } - inst.SetUserdata(make(map[string]*customMetricValue)) + inst.SetUserdata(newUserData()) return inst, nil } @@ -211,6 +227,8 @@ func (m *Metrics) UpsertCustomMetric(ctx context.Context, metricSpec *wfv1.Prome func (m *Metrics) attachCustomMetricToWorkflow(metricSpec *wfv1.Prometheus, ownerKey string) { if metricSpec.IsRealtime() { + m.realtimeMutex.Lock() + defer m.realtimeMutex.Unlock() // Must move to run each workflowkey for key := range m.realtimeWorkflows { if key == ownerKey { @@ -218,7 +236,7 @@ func (m *Metrics) attachCustomMetricToWorkflow(metricSpec *wfv1.Prometheus, owne } } m.realtimeWorkflows[ownerKey] = append(m.realtimeWorkflows[ownerKey], realtimeTracker{ - inst: m.AllInstruments[metricSpec.Name], + inst: m.GetInstrument(metricSpec.Name), key: metricSpec.GetKey(), }) } @@ -242,7 +260,7 @@ func (m *Metrics) createCustomMetric(metricSpec *wfv1.Prometheus) error { if err != nil { return err } - inst := m.AllInstruments[metricSpec.Name] + inst := m.GetInstrument(metricSpec.Name) customInst := customInstrument{Instrument: inst} return inst.RegisterCallback(m.Metrics, customInst.customCallback) default: @@ -255,22 +273,25 @@ func (m *Metrics) createCustomGauge(metricSpec *wfv1.Prometheus) error { if err != nil { return err } - inst := m.AllInstruments[metricSpec.Name] + inst := m.GetInstrument(metricSpec.Name) customInst := customInstrument{Instrument: inst} return inst.RegisterCallback(m.Metrics, customInst.customCallback) } func (m *Metrics) runCustomGC(ttl time.Duration) { - m.Mutex.Lock() - defer m.Mutex.Unlock() - for _, baseMetric := range m.AllInstruments { - custom := customUserdata(baseMetric, false) - for key, value := range custom { + m.IterateROInstruments(func(baseMetric *telemetry.Instrument) { + ud := customUserData(baseMetric, false) + if ud == nil { + return + } + ud.mutex.Lock() + for key, value := range ud.values { if time.Since(value.lastUpdated) > ttl { - delete(custom, key) + delete(ud.values, key) } } - } + ud.mutex.Unlock() + }) } func (m *Metrics) customMetricsGC(ctx context.Context, ttl time.Duration) { @@ -291,16 +312,18 @@ func (m *Metrics) customMetricsGC(ctx context.Context, ttl time.Duration) { } func (m *Metrics) StopRealtimeMetricsForWfUID(key string) { - m.Mutex.Lock() - defer m.Mutex.Unlock() - + m.realtimeMutex.Lock() + defer m.realtimeMutex.Unlock() if _, exists := m.realtimeWorkflows[key]; !exists { return } realtimeMetrics := m.realtimeWorkflows[key] for _, metric := range realtimeMetrics { - delete(customUserdata(metric.inst, true), metric.key) + ud := customUserData(metric.inst, true) + ud.mutex.Lock() + delete(ud.values, metric.key) + ud.mutex.Unlock() } delete(m.realtimeWorkflows, key) diff --git a/workflow/metrics/metrics_test.go b/workflow/metrics/metrics_test.go index c8fbda52ab6b..e24ed2c99206 100644 --- a/workflow/metrics/metrics_test.go +++ b/workflow/metrics/metrics_test.go @@ -74,22 +74,22 @@ func TestMetricGC(t *testing.T) { baseCm := m.GetCustomMetric(key) assert.NotNil(t, baseCm) - cm := customUserdata(baseCm, true) - assert.Len(t, cm, 1) + cm := customUserData(baseCm, true) + assert.NotNil(t, cm) + assert.Len(t, cm.values, 1) // Ensure we get at least one TTL run timeoutTime := time.Now().Add(time.Second * 2) for time.Now().Before(timeoutTime) { // Break if we know our test will pass. - if len(cm) == 0 { + if len(cm.values) == 0 { break } // Sleep to prevent overloading test worker CPU. time.Sleep(100 * time.Millisecond) } - assert.Empty(t, cm) - + assert.Empty(t, cm.values) } func TestRealtimeMetricGC(t *testing.T) { @@ -151,12 +151,12 @@ func TestWorkflowQueueMetrics(t *testing.T) { wfQueue := m.RateLimiterWithBusyWorkers(m.Ctx, workqueue.DefaultControllerRateLimiter(), "workflow_queue") defer wfQueue.ShutDown() - assert.NotNil(t, m.AllInstruments[nameWorkersQueueDepth]) - assert.NotNil(t, m.AllInstruments[nameWorkersQueueLatency]) + assert.NotNil(t, m.GetInstrument(nameWorkersQueueDepth)) + assert.NotNil(t, m.GetInstrument(nameWorkersQueueLatency)) wfQueue.Add("hello") - require.NotNil(t, m.AllInstruments[nameWorkersQueueAdds]) + require.NotNil(t, m.GetInstrument(nameWorkersQueueAdds)) val, err := te.GetInt64CounterValue(nameWorkersQueueAdds, &attribs) require.NoError(t, err) assert.Equal(t, int64(1), val) @@ -201,13 +201,14 @@ func TestRealTimeMetricDeletion(t *testing.T) { m.StopRealtimeMetricsForWfUID("456") assert.Empty(t, m.realtimeWorkflows["456"]) - cm := customUserdata(baseCm, true) - assert.Len(t, cm, 1) + cm := customUserData(baseCm, true) + assert.NotNil(t, cm) + assert.Len(t, cm.values, 1) assert.Len(t, m.realtimeWorkflows["123"], 1) m.StopRealtimeMetricsForWfUID("123") assert.Empty(t, m.realtimeWorkflows["123"]) - assert.Empty(t, cm) + assert.Empty(t, cm.values) err = m.UpsertCustomMetric(ctx, &wfv1.Prometheus{ Name: key, @@ -221,6 +222,6 @@ func TestRealTimeMetricDeletion(t *testing.T) { }, "456", nil) require.NoError(t, err) - assert.Len(t, cm, 1) + assert.Len(t, cm.values, 1) assert.Len(t, m.realtimeWorkflows["456"], 1) } diff --git a/workflow/metrics/work_queue.go b/workflow/metrics/work_queue.go index 537643e719cc..46679d77a3a0 100644 --- a/workflow/metrics/work_queue.go +++ b/workflow/metrics/work_queue.go @@ -100,10 +100,10 @@ func addWorkQueueMetrics(_ context.Context, m *Metrics) error { return err } unfinishedCallback := queueUserdata{ - gauge: m.AllInstruments[nameWorkersUnfinishedWork], + gauge: m.GetInstrument(nameWorkersUnfinishedWork), } - m.AllInstruments[nameWorkersUnfinishedWork].SetUserdata(&unfinishedCallback) - err = m.AllInstruments[nameWorkersUnfinishedWork].RegisterCallback(m.Metrics, unfinishedCallback.update) + unfinishedCallback.gauge.SetUserdata(&unfinishedCallback) + err = unfinishedCallback.gauge.RegisterCallback(m.Metrics, unfinishedCallback.update) if err != nil { return err } @@ -118,10 +118,10 @@ func addWorkQueueMetrics(_ context.Context, m *Metrics) error { return err } longestRunningCallback := queueUserdata{ - gauge: m.AllInstruments[nameWorkersLongestRunning], + gauge: m.GetInstrument(nameWorkersLongestRunning), } - m.AllInstruments[nameWorkersLongestRunning].SetUserdata(&longestRunningCallback) - err = m.AllInstruments[nameWorkersLongestRunning].RegisterCallback(m.Metrics, longestRunningCallback.update) + longestRunningCallback.gauge.SetUserdata(&longestRunningCallback) + err = longestRunningCallback.gauge.RegisterCallback(m.Metrics, longestRunningCallback.update) if err != nil { return err } @@ -132,7 +132,7 @@ func (m *Metrics) RateLimiterWithBusyWorkers(ctx context.Context, workQueue work queue := workersBusyRateLimiterWorkQueue{ RateLimitingInterface: workqueue.NewNamedRateLimitingQueue(workQueue, queueName), workerType: queueName, - busyGauge: m.AllInstruments[nameWorkersBusy], + busyGauge: m.GetInstrument(nameWorkersBusy), ctx: ctx, } queue.newWorker(ctx) @@ -221,7 +221,7 @@ func (m *Metrics) NewDepthMetric(name string) workqueue.GaugeMetric { return queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersQueueDepth], + inst: m.GetInstrument(nameWorkersQueueDepth), } } @@ -229,7 +229,7 @@ func (m *Metrics) NewAddsMetric(name string) workqueue.CounterMetric { return queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersQueueAdds], + inst: m.GetInstrument(nameWorkersQueueAdds), } } @@ -237,7 +237,7 @@ func (m *Metrics) NewLatencyMetric(name string) workqueue.HistogramMetric { return queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersQueueLatency], + inst: m.GetInstrument(nameWorkersQueueLatency), } } @@ -245,7 +245,7 @@ func (m *Metrics) NewWorkDurationMetric(name string) workqueue.HistogramMetric { return queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersQueueDuration], + inst: m.GetInstrument(nameWorkersQueueDuration), } } @@ -253,7 +253,7 @@ func (m *Metrics) NewRetriesMetric(name string) workqueue.CounterMetric { return queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersRetries], + inst: m.GetInstrument(nameWorkersRetries), } } @@ -261,7 +261,7 @@ func (m *Metrics) NewUnfinishedWorkSecondsMetric(name string) workqueue.Settable metric := queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersUnfinishedWork], + inst: m.GetInstrument(nameWorkersUnfinishedWork), value: ptr.To(float64(0.0)), } ud := getQueueUserdata(metric.inst) @@ -273,7 +273,7 @@ func (m *Metrics) NewLongestRunningProcessorSecondsMetric(name string) workqueue metric := queueMetric{ ctx: m.Ctx, name: name, - inst: m.AllInstruments[nameWorkersLongestRunning], + inst: m.GetInstrument(nameWorkersLongestRunning), value: ptr.To(float64(0.0)), } ud := getQueueUserdata(metric.inst) From cf56b182104d548e7037fbaa981c5c02290a4813 Mon Sep 17 00:00:00 2001 From: Mason Malone <651224+MasonM@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:30:17 -0800 Subject: [PATCH 09/23] fix: consistent variable substitution for `configMapKeyRef`. Fixes #13890 (#13921) Signed-off-by: Mason Malone <651224+MasonM@users.noreply.github.com> (cherry picked from commit f2159dcd841c8168df6acaf7afa0c54d24293c84) --- test/e2e/hooks_test.go | 3 +- workflow/common/util.go | 33 ++++---- workflow/common/util_test.go | 141 +++++++++++++++-------------------- 3 files changed, 78 insertions(+), 99 deletions(-) diff --git a/test/e2e/hooks_test.go b/test/e2e/hooks_test.go index d10e06cb5491..23cb235ce161 100644 --- a/test/e2e/hooks_test.go +++ b/test/e2e/hooks_test.go @@ -5,6 +5,7 @@ package e2e import ( "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -805,7 +806,7 @@ spec: args: ["sleep", "5"] `).When(). SubmitWorkflow(). - WaitForWorkflow(fixtures.ToBeCompleted). + WaitForWorkflow(fixtures.ToBeCompleted, 2*time.Minute). WaitForWorkflow(fixtures.Condition(func(wf *v1alpha1.Workflow) (bool, string) { onExitNodeName = common.GenerateOnExitNodeName(wf.ObjectMeta.Name) onExitNode := wf.Status.Nodes.FindByDisplayName(onExitNodeName) diff --git a/workflow/common/util.go b/workflow/common/util.go index 4b5aa0278225..b7ffbe291494 100644 --- a/workflow/common/util.go +++ b/workflow/common/util.go @@ -133,14 +133,19 @@ func overwriteWithArguments(argParam, inParam *wfv1.Parameter) { func substituteAndGetConfigMapValue(inParam *wfv1.Parameter, globalParams Parameters, namespace string, configMapStore ConfigMapStore) error { if inParam.ValueFrom != nil && inParam.ValueFrom.ConfigMapKeyRef != nil { if configMapStore != nil { + replaceMap := make(map[string]interface{}) + for k, v := range globalParams { + replaceMap[k] = v + } + // SubstituteParams is called only at the end of this method. To support parametrization of the configmap // we need to perform a substitution here over the name and the key of the ConfigMapKeyRef. - cmName, err := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Name, globalParams) + cmName, err := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Name, replaceMap) if err != nil { log.WithError(err).Error("unable to substitute name for ConfigMapKeyRef") return err } - cmKey, err := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Key, globalParams) + cmKey, err := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Key, replaceMap) if err != nil { log.WithError(err).Error("unable to substitute key for ConfigMapKeyRef") return err @@ -219,21 +224,17 @@ func ProcessArgs(tmpl *wfv1.Template, args wfv1.ArgumentsProvider, globalParams, return SubstituteParams(newTmpl, globalParams, localParams) } -// substituteConfigMapKeyRefParam check if ConfigMapKeyRef's key is a param and perform the substitution. -func substituteConfigMapKeyRefParam(in string, globalParams Parameters) (string, error) { - if strings.HasPrefix(in, "{{") && strings.HasSuffix(in, "}}") { - k := strings.TrimSuffix(strings.TrimPrefix(in, "{{"), "}}") - k = strings.Trim(k, " ") - - v, ok := globalParams[k] - if !ok { - err := errors.InternalError(fmt.Sprintf("parameter %s not found", k)) - log.WithError(err).Error() - return "", err - } - return v, nil +// substituteConfigMapKeyRefParam performs template substitution for ConfigMapKeyRef +func substituteConfigMapKeyRefParam(in string, replaceMap map[string]interface{}) (string, error) { + tmpl, err := template.NewTemplate(in) + if err != nil { + return "", err + } + replacedString, err := tmpl.Replace(replaceMap, false) + if err != nil { + return "", fmt.Errorf("failed to substitute configMapKeyRef: %w", err) } - return in, nil + return replacedString, nil } // SubstituteParams returns a new copy of the template with global, pod, and input parameters substituted diff --git a/workflow/common/util_test.go b/workflow/common/util_test.go index a5b02c882d8d..529da00690ce 100644 --- a/workflow/common/util_test.go +++ b/workflow/common/util_test.go @@ -48,54 +48,6 @@ spec: command: [cowsay] args: ["hello world"] -` - validConfigMapRefWf = `apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - name: test-template-configmapkeyselector-substitution -spec: - entrypoint: whalesay - arguments: - parameters: - - name: name - value: simple-parameters - - name: key - value: msg - templates: - - name: whalesay - inputs: - parameters: - - name: message - valueFrom: - configMapKeyRef: - name: "{{ workflow.parameters.name }}" - key: "{{ workflow.parameters.key }}" - container: - image: argoproj/argosay:v2 - args: - - echo - - "{{inputs.parameters.message}}" -` - invalidConfigMapRefWf = `apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - name: test-template-configmapkeyselector-substitution -spec: - entrypoint: whalesay - templates: - - name: whalesay - inputs: - parameters: - - name: message - valueFrom: - configMapKeyRef: - name: "{{ workflow.parameters.name }}" - key: "{{ workflow.parameters.key }}" - container: - image: argoproj/argosay:v2 - args: - - echo - - "{{inputs.parameters.message}}" ` ) @@ -194,45 +146,70 @@ func TestIsDone(t *testing.T) { } func TestSubstituteConfigMapKeyRefParam(t *testing.T) { - res := ParseObjects([]byte(validConfigMapRefWf), false) - assert.Len(t, res, 1) - - obj, ok := res[0].Object.(*wfv1.Workflow) - assert.True(t, ok) - assert.NotNil(t, obj) - - globalParams := Parameters{ + globalParams := map[string]interface{}{ "workflow.parameters.name": "simple-parameters", "workflow.parameters.key": "msg", } - - for _, inParam := range obj.GetTemplateByName("whalesay").Inputs.Parameters { - cmName, _ := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Name, globalParams) - assert.Equal(t, "simple-parameters", cmName, "it should be equal") - - cmKey, _ := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Key, globalParams) - assert.Equal(t, "msg", cmKey, "it should be equal") + tests := []struct { + name string + configMapKeyRefParam string + expected string + expectedErr string + }{ + { + name: "No string templating", + configMapKeyRefParam: "simple-parameters", + expected: "simple-parameters", + expectedErr: "", + }, + { + name: "Simple template", + configMapKeyRefParam: "{{ workflow.parameters.name }}", + expected: "simple-parameters", + expectedErr: "", + }, + { + name: "Simple template with prefix and suffix", + configMapKeyRefParam: "prefix-{{ workflow.parameters.name }}-suffix", + expected: "prefix-simple-parameters-suffix", + expectedErr: "", + }, + { + name: "Expression template", + configMapKeyRefParam: "{{=upper(workflow.parameters.key)}}", + expected: "MSG", + expectedErr: "", + }, + { + name: "Simple template referencing nonexistent param", + configMapKeyRefParam: "prefix-{{ workflow.parameters.bad }}", + expected: "", + expectedErr: "failed to substitute configMapKeyRef: failed to resolve {{ workflow.parameters.bad }}", + }, + { + name: "Expression template with invalid expression", + configMapKeyRefParam: "{{=!}}", + expected: "", + expectedErr: "failed to substitute configMapKeyRef: failed to evaluate expression: unexpected token EOF (1:1)\n | !\n | ^", + }, + { + name: "Malformed template", + configMapKeyRefParam: "{{ workflow.parameters.bad", + expected: "", + expectedErr: "Cannot find end tag=\"}}\" in the template=\"{{ workflow.parameters.bad\" starting from \" workflow.parameters.bad\"", + }, } -} - -func TestSubstituteConfigMapKeyRefParamWithNoParamsDefined(t *testing.T) { - res := ParseObjects([]byte(invalidConfigMapRefWf), false) - assert.Len(t, res, 1) - - obj, ok := res[0].Object.(*wfv1.Workflow) - assert.True(t, ok) - assert.NotNil(t, obj) - - globalParams := Parameters{} - - for _, inParam := range obj.GetTemplateByName("whalesay").Inputs.Parameters { - cmName, err := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Name, globalParams) - require.EqualError(t, err, "parameter workflow.parameters.name not found") - assert.Equal(t, "", cmName) - cmKey, err := substituteConfigMapKeyRefParam(inParam.ValueFrom.ConfigMapKeyRef.Key, globalParams) - require.EqualError(t, err, "parameter workflow.parameters.key not found") - assert.Equal(t, "", cmKey) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := substituteConfigMapKeyRefParam(tt.configMapKeyRefParam, globalParams) + assert.Equal(t, tt.expected, result) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + } else { + require.NoError(t, err) + } + }) } } From bd9b124878934e937d082441bd5db024b5fb07e3 Mon Sep 17 00:00:00 2001 From: Mason Malone <651224+MasonM@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:34:20 -0800 Subject: [PATCH 10/23] fix: don't log non-errors as "Non-transient error: ". Fixes #13881 (#13917) Signed-off-by: Mason Malone <651224+MasonM@users.noreply.github.com> (cherry picked from commit 6b221f460a5823cd0ada098f21ca5eb37797f2df) --- test/e2e/signals_test.go | 2 +- util/errors/errors.go | 2 +- util/errors/errors_test.go | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test/e2e/signals_test.go b/test/e2e/signals_test.go index 132e99a60c49..13a10020b91c 100644 --- a/test/e2e/signals_test.go +++ b/test/e2e/signals_test.go @@ -150,7 +150,7 @@ func (s *SignalsSuite) TestSignaledContainerSet() { Workflow("@testdata/signaled-container-set-workflow.yaml"). When(). SubmitWorkflow(). - WaitForWorkflow(). + WaitForWorkflow(killDuration). Then(). ExpectWorkflow(func(t *testing.T, metadata *metav1.ObjectMeta, status *wfv1.WorkflowStatus) { assert.Equal(t, wfv1.WorkflowFailed, status.Phase) diff --git a/util/errors/errors.go b/util/errors/errors.go index 19addc48312c..6d41bf612213 100644 --- a/util/errors/errors.go +++ b/util/errors/errors.go @@ -26,7 +26,7 @@ func IgnoreContainerNotFoundErr(err error) error { // IsTransientErr reports whether the error is transient and logs it. func IsTransientErr(err error) bool { isTransient := IsTransientErrQuiet(err) - if !isTransient { + if err != nil && !isTransient { log.Warnf("Non-transient error: %v", err) } return isTransient diff --git a/util/errors/errors_test.go b/util/errors/errors_test.go index 8090072c1ae1..702c3d594251 100644 --- a/util/errors/errors_test.go +++ b/util/errors/errors_test.go @@ -8,6 +8,8 @@ import ( "os/exec" "testing" + log "github.com/sirupsen/logrus" + logtest "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" apierr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" @@ -50,12 +52,19 @@ var ( const transientEnvVarKey = "TRANSIENT_ERROR_PATTERN" func TestIsTransientErr(t *testing.T) { + hook := &logtest.Hook{} + log.AddHook(hook) + defer log.StandardLogger().ReplaceHooks(nil) + t.Run("Nil", func(t *testing.T) { assert.False(t, IsTransientErr(nil)) + assert.Nil(t, hook.LastEntry()) }) t.Run("ResourceQuotaConflictErr", func(t *testing.T) { assert.False(t, IsTransientErr(apierr.NewConflict(schema.GroupResource{}, "", nil))) + assert.Contains(t, hook.LastEntry().Message, "Non-transient error:") assert.True(t, IsTransientErr(apierr.NewConflict(schema.GroupResource{Group: "v1", Resource: "resourcequotas"}, "", nil))) + assert.Contains(t, hook.LastEntry().Message, "Transient error:") }) t.Run("ResourceQuotaTimeoutErr", func(t *testing.T) { assert.False(t, IsTransientErr(apierr.NewInternalError(errors.New("")))) From c0ea4b0c4ad2897d3203418313d4860684c81e5f Mon Sep 17 00:00:00 2001 From: jswxstw Date: Thu, 28 Nov 2024 17:00:40 +0800 Subject: [PATCH 11/23] fix: Skip execution control for agent pod during pod reconciliation. Fixes #12726 (#12732) Signed-off-by: jswxstw (cherry picked from commit 7d6d8f310ce57ae515b1580aa40cd8399eb37746) --- workflow/controller/exec_control.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/controller/exec_control.go b/workflow/controller/exec_control.go index b341fa343e2e..9866aedd6bbe 100644 --- a/workflow/controller/exec_control.go +++ b/workflow/controller/exec_control.go @@ -13,9 +13,9 @@ import ( ) // applyExecutionControl will ensure a pod's execution control annotation is up-to-date -// kills any pending and running pods when workflow has reached it's deadline +// kills any pending and running pods (except agent pod) when workflow has reached its deadline func (woc *wfOperationCtx) applyExecutionControl(pod *apiv1.Pod, wfNodesLock *sync.RWMutex) { - if pod == nil { + if pod == nil || woc.isAgentPod(pod) { return } From f8012d4ec36413115a344068e4aebd5f64b8b409 Mon Sep 17 00:00:00 2001 From: Tianchu Zhao Date: Fri, 6 Dec 2024 14:36:28 +1100 Subject: [PATCH 12/23] fix: cronOperator/serverResubmitWf retry create workflow on transient error. Fixes #13970 (#13971) Signed-off-by: Tianchu Zhao (cherry picked from commit 9d1d2cd518628b2d4298b435386e82c7e7960f0d) --- workflow/util/util.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/workflow/util/util.go b/workflow/util/util.go index b56e9429a35d..f70b72d283a8 100644 --- a/workflow/util/util.go +++ b/workflow/util/util.go @@ -193,7 +193,13 @@ func SubmitWorkflow(ctx context.Context, wfIf v1alpha1.WorkflowInterface, wfClie } return wf, err } else { - return wfIf.Create(ctx, wf, metav1.CreateOptions{}) + var runWf *wfv1.Workflow + err = waitutil.Backoff(retry.DefaultRetry, func() (bool, error) { + var err error + runWf, err = wfIf.Create(ctx, wf, metav1.CreateOptions{}) + return !errorsutil.IsTransientErr(err), err + }) + return runWf, err } } From bb3c0832bcbe10bd1d8ac2b67074c71e0a8958a6 Mon Sep 17 00:00:00 2001 From: jswxstw Date: Thu, 12 Dec 2024 23:40:17 +0800 Subject: [PATCH 13/23] fix: mark all its children(container) as deleted if pod deleted. Fixes #13951 (#13978) (cherry picked from commit 43c6abd1fe009f36c290545655080eb9c9c197fd) --- workflow/controller/operator.go | 35 ++++++--- workflow/controller/operator_test.go | 108 +++++++++++++++++++++++---- 2 files changed, 117 insertions(+), 26 deletions(-) diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index b8402e911b72..3a5a52f06880 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -1234,17 +1234,8 @@ func (woc *wfOperationCtx) podReconciliation(ctx context.Context) (error, bool) woc.updated = true } woc.markNodeError(node.Name, errors.New("", "pod deleted")) - // Set pod's child(container) error if pod deleted - for _, childNodeID := range node.Children { - childNode, err := woc.wf.Status.Nodes.Get(childNodeID) - if err != nil { - woc.log.Errorf("was unable to obtain node for %s", childNodeID) - continue - } - if childNode.Type == wfv1.NodeTypeContainer { - woc.markNodeError(childNode.Name, errors.New("", "container deleted")) - } - } + // Mark all its children(container) as deleted if pod deleted + woc.markAllContainersDeleted(node.ID) } } return nil, !taskResultIncomplete @@ -1262,6 +1253,28 @@ func recentlyStarted(node wfv1.NodeStatus) bool { return time.Since(node.StartedAt.Time) <= envutil.LookupEnvDurationOr("RECENTLY_STARTED_POD_DURATION", 10*time.Second) } +// markAllContainersDeleted mark all its children(container) as deleted +func (woc *wfOperationCtx) markAllContainersDeleted(nodeID string) { + node, err := woc.wf.Status.Nodes.Get(nodeID) + if err != nil { + woc.log.Errorf("was unable to obtain node for %s", nodeID) + return + } + + for _, childNodeID := range node.Children { + childNode, err := woc.wf.Status.Nodes.Get(childNodeID) + if err != nil { + woc.log.Errorf("was unable to obtain node for %s", childNodeID) + continue + } + if childNode.Type == wfv1.NodeTypeContainer { + woc.markNodeError(childNode.Name, errors.New("", "container deleted")) + // Recursively mark successor node(container) as deleted + woc.markAllContainersDeleted(childNodeID) + } + } +} + // shouldPrintPodSpec return eligible to print to the pod spec func (woc *wfOperationCtx) shouldPrintPodSpec(node *wfv1.NodeStatus) bool { return woc.controller.Config.PodSpecLogStrategy.AllPods || diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go index 5b772e2f33f6..2e368dbbf767 100644 --- a/workflow/controller/operator_test.go +++ b/workflow/controller/operator_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "regexp" "strconv" "strings" @@ -11165,15 +11166,15 @@ var wfHasContainerSet = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: - name: lovely-rhino + name: wf-has-containerSet spec: + entrypoint: init templates: - name: init dag: tasks: - name: A template: run - arguments: {} - name: run containerSet: containers: @@ -11184,24 +11185,19 @@ spec: args: - '-c' - sleep 9000 - resources: {} - name: main2 image: alpine:latest command: - /bin/sh args: - '-c' - - sleep 9000 - resources: {} - entrypoint: init - arguments: {} - ttlStrategy: - secondsAfterCompletion: 300 - podGC: - strategy: OnPodCompletion` + - sleep 9000` -// TestContainerSetDeleteContainerWhenPodDeleted test whether a workflow has ContainerSet error when pod deleted. -func TestContainerSetDeleteContainerWhenPodDeleted(t *testing.T) { +// TestContainerSetWhenPodDeleted tests whether all its children(container) deleted when pod deleted if containerSet is used. +func TestContainerSetWhenPodDeleted(t *testing.T) { + // use local-scoped env vars in test to avoid long waits + _ = os.Setenv("RECENTLY_STARTED_POD_DURATION", "0") + defer os.Setenv("RECENTLY_STARTED_POD_DURATION", "") cancel, controller := newController() defer cancel() ctx := context.Background() @@ -11227,9 +11223,91 @@ func TestContainerSetDeleteContainerWhenPodDeleted(t *testing.T) { } } - // TODO: Refactor to use local-scoped env vars in test to avoid long wait. See https://github.com/argoproj/argo-workflows/pull/12756#discussion_r1530245007 // delete pod - time.Sleep(10 * time.Second) + deletePods(ctx, woc) + pods, err = listPods(woc) + require.NoError(t, err) + assert.Empty(t, pods.Items) + + // reconcile + woc = newWorkflowOperationCtx(woc.wf, controller) + woc.operate(ctx) + assert.Equal(t, wfv1.WorkflowError, woc.wf.Status.Phase) + for _, node := range woc.wf.Status.Nodes { + assert.Equal(t, wfv1.NodeError, node.Phase) + if node.Type == wfv1.NodeTypePod { + assert.Equal(t, "pod deleted", node.Message) + } + if node.Type == wfv1.NodeTypeContainer { + assert.Equal(t, "container deleted", node.Message) + } + } +} + +var wfHasContainerSetWithDependencies = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: wf-has-containerSet-with-dependencies +spec: + entrypoint: init + templates: + - name: init + dag: + tasks: + - name: A + template: run + - name: run + containerSet: + containers: + - name: main + image: alpine:latest + command: + - /bin/sh + args: + - '-c' + - sleep 9000 + - name: main2 + image: alpine:latest + command: + - /bin/sh + args: + - '-c' + - sleep 9000 + dependencies: + - main` + +// TestContainerSetWithDependenciesWhenPodDeleted tests whether all its children(container) deleted when pod deleted if containerSet with dependencies is used. +func TestContainerSetWithDependenciesWhenPodDeleted(t *testing.T) { + // use local-scoped env vars in test to avoid long waits + _ = os.Setenv("RECENTLY_STARTED_POD_DURATION", "0") + defer os.Setenv("RECENTLY_STARTED_POD_DURATION", "") + cancel, controller := newController() + defer cancel() + ctx := context.Background() + wfcset := controller.wfclientset.ArgoprojV1alpha1().Workflows("") + wf := wfv1.MustUnmarshalWorkflow(wfHasContainerSetWithDependencies) + wf, err := wfcset.Create(ctx, wf, metav1.CreateOptions{}) + require.NoError(t, err) + wf, err = wfcset.Get(ctx, wf.ObjectMeta.Name, metav1.GetOptions{}) + require.NoError(t, err) + woc := newWorkflowOperationCtx(wf, controller) + woc.operate(ctx) + pods, err := listPods(woc) + require.NoError(t, err) + assert.Len(t, pods.Items, 1) + + // mark pod Running + makePodsPhase(ctx, woc, apiv1.PodRunning) + woc = newWorkflowOperationCtx(woc.wf, controller) + woc.operate(ctx) + for _, node := range woc.wf.Status.Nodes { + if node.Type == wfv1.NodeTypePod { + assert.Equal(t, wfv1.NodeRunning, node.Phase) + } + } + + // delete pod deletePods(ctx, woc) pods, err = listPods(woc) require.NoError(t, err) From 4729176e82aa430f218e9f2867a71e4192a13b53 Mon Sep 17 00:00:00 2001 From: jswxstw Date: Mon, 23 Dec 2024 00:44:20 +0800 Subject: [PATCH 14/23] fix: ensure that nodes complete when workflow fails with `parallelism` and `failFast`. Fixes #13806 (#13827) (cherry picked from commit 1d85e685d41d25524fcc99a9fbccbe1bfe3fe2a7) --- .../parallelism-dag-fail-fast.yaml | 26 ++++++++++++ .../parallelism-dag-failure.yaml | 38 ------------------ .../parallelism-step-fail-fast.yaml | 25 ++++++++++++ .../parallelism-step-failure.yaml | 28 ------------- test/e2e/functional_test.go | 31 ++++++++++++++ workflow/controller/operator.go | 40 +++++++++++++++++-- 6 files changed, 119 insertions(+), 69 deletions(-) create mode 100644 test/e2e/expectedfailures/parallelism-dag-fail-fast.yaml delete mode 100644 test/e2e/expectedfailures/parallelism-dag-failure.yaml create mode 100644 test/e2e/expectedfailures/parallelism-step-fail-fast.yaml delete mode 100644 test/e2e/expectedfailures/parallelism-step-failure.yaml diff --git a/test/e2e/expectedfailures/parallelism-dag-fail-fast.yaml b/test/e2e/expectedfailures/parallelism-dag-fail-fast.yaml new file mode 100644 index 000000000000..fa52fc621913 --- /dev/null +++ b/test/e2e/expectedfailures/parallelism-dag-fail-fast.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: parallelism-dag-fail-fast +spec: + entrypoint: main + templates: + - name: main + parallelism: 2 + failFast: true + dag: + tasks: + - name: task1 + template: fail + - name: task2 + template: sleep + - name: fail + container: + image: alpine:latest + command: [ sh, -c ] + args: ["exit 1"] + - name: sleep + container: + image: alpine:latest + command: [sh, -c] + args: ["sleep 5"] \ No newline at end of file diff --git a/test/e2e/expectedfailures/parallelism-dag-failure.yaml b/test/e2e/expectedfailures/parallelism-dag-failure.yaml deleted file mode 100644 index 478d3f1724c0..000000000000 --- a/test/e2e/expectedfailures/parallelism-dag-failure.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - generateName: parallelism-failed-dag- -spec: - entrypoint: parallelism-failed-dag - templates: - - name: parallelism-failed-dag - parallelism: 2 - dag: - tasks: - - name: A - template: pass - - name: B - dependencies: [A] - template: pass - - name: C - dependencies: [A] - template: pass - - name: D - dependencies: [A] - template: fail - - name: E - dependencies: [A] - template: pass - - name: F - dependencies: [B, C, D, E] - template: pass - - - name: pass - container: - image: alpine:3.7 - command: [sh, -c, exit 0] - - - name: fail - container: - image: alpine:3.7 - command: [sh, -c, exit 1] diff --git a/test/e2e/expectedfailures/parallelism-step-fail-fast.yaml b/test/e2e/expectedfailures/parallelism-step-fail-fast.yaml new file mode 100644 index 000000000000..04c7798eb20f --- /dev/null +++ b/test/e2e/expectedfailures/parallelism-step-fail-fast.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: parallelism-step-fail-fast +spec: + entrypoint: main + templates: + - name: main + parallelism: 2 + failFast: true + steps: + - - name: step1 + template: fail + - name: step2 + template: sleep + - name: fail + container: + image: alpine:latest + command: [sh, -c] + args: ["exit 1"] + - name: sleep + container: + image: alpine:latest + command: [ sh, -c ] + args: [ "sleep 5" ] \ No newline at end of file diff --git a/test/e2e/expectedfailures/parallelism-step-failure.yaml b/test/e2e/expectedfailures/parallelism-step-failure.yaml deleted file mode 100644 index 37682259d852..000000000000 --- a/test/e2e/expectedfailures/parallelism-step-failure.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - generateName: parallelism-failed-step- -spec: - entrypoint: parallelism-failed-step - templates: - - name: parallelism-failed-step - parallelism: 2 - steps: - - - name: sleep - template: sleep - arguments: - parameters: - - name: exit-code - value: "{{item}}" - withItems: - - 0 - - 1 - - 0 - - - name: sleep - inputs: - parameters: - - name: exit-code - container: - image: alpine:latest - command: [sh, -c, "exit {{inputs.parameters.exit-code}}"] diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index 906eeac31770..44c51a3227c1 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -1415,3 +1415,34 @@ func (s *FunctionalSuite) TestWorkflowExitHandlerCrashEnsureNodeIsPresent() { assert.NotNil(t, hookNode.Inputs.Parameters[0].Value) }) } + +func (s *FunctionalSuite) TestWorkflowParallelismStepFailFast() { + s.Given(). + Workflow("@expectedfailures/parallelism-step-fail-fast.yaml"). + When(). + SubmitWorkflow(). + WaitForWorkflow(fixtures.ToBeRunning). + WaitForWorkflow(fixtures.ToBeFailed). + Then(). + ExpectWorkflow(func(t *testing.T, metadata *v1.ObjectMeta, status *wfv1.WorkflowStatus) { + assert.Equal(t, "template has failed or errored children and failFast enabled", status.Message) + assert.Equal(t, wfv1.NodeFailed, status.Nodes.FindByDisplayName("[0]").Phase) + assert.Equal(t, wfv1.NodeFailed, status.Nodes.FindByDisplayName("step1").Phase) + assert.Equal(t, wfv1.NodeSucceeded, status.Nodes.FindByDisplayName("step2").Phase) + }) +} + +func (s *FunctionalSuite) TestWorkflowParallelismDAGFailFast() { + s.Given(). + Workflow("@expectedfailures/parallelism-dag-fail-fast.yaml"). + When(). + SubmitWorkflow(). + WaitForWorkflow(fixtures.ToBeRunning). + WaitForWorkflow(fixtures.ToBeFailed). + Then(). + ExpectWorkflow(func(t *testing.T, metadata *v1.ObjectMeta, status *wfv1.WorkflowStatus) { + assert.Equal(t, "template has failed or errored children and failFast enabled", status.Message) + assert.Equal(t, wfv1.NodeFailed, status.Nodes.FindByDisplayName("task1").Phase) + assert.Equal(t, wfv1.NodeSucceeded, status.Nodes.FindByDisplayName("task2").Phase) + }) +} diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index 3a5a52f06880..8712ac984ea3 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -2783,6 +2783,26 @@ func (woc *wfOperationCtx) markNodeWaitingForLock(nodeName string, lockName stri return node, nil } +func (woc *wfOperationCtx) findLeafNodeWithType(boundaryID string, nodeType wfv1.NodeType) *wfv1.NodeStatus { + var leafNode *wfv1.NodeStatus + var dfs func(nodeID string) + dfs = func(nodeID string) { + node, err := woc.wf.Status.Nodes.Get(nodeID) + if err != nil { + woc.log.Errorf("was unable to obtain node for %s", nodeID) + return + } + if node.Type == nodeType { + leafNode = node + } + for _, childID := range node.Children { + dfs(childID) + } + } + dfs(boundaryID) + return leafNode +} + // checkParallelism checks if the given template is able to be executed, considering the current active pods and workflow/template parallelism func (woc *wfOperationCtx) checkParallelism(tmpl *wfv1.Template, node *wfv1.NodeStatus, boundaryID string) error { if woc.execWf.Spec.Parallelism != nil && woc.activePods >= *woc.execWf.Spec.Parallelism { @@ -2794,7 +2814,14 @@ func (woc *wfOperationCtx) checkParallelism(tmpl *wfv1.Template, node *wfv1.Node if node != nil && (tmpl.GetType() == wfv1.TemplateTypeDAG || tmpl.GetType() == wfv1.TemplateTypeSteps) { // Check failFast if tmpl.IsFailFast() && woc.getUnsuccessfulChildren(node.ID) > 0 { - woc.markNodePhase(node.Name, wfv1.NodeFailed, "template has failed or errored children and failFast enabled") + if woc.getActivePods(node.ID) == 0 { + if tmpl.GetType() == wfv1.TemplateTypeSteps { + if leafStepGroupNode := woc.findLeafNodeWithType(node.ID, wfv1.NodeTypeStepGroup); leafStepGroupNode != nil { + woc.markNodePhase(leafStepGroupNode.Name, wfv1.NodeFailed, "template has failed or errored children and failFast enabled") + } + } + woc.markNodePhase(node.Name, wfv1.NodeFailed, "template has failed or errored children and failFast enabled") + } return ErrParallelismReached } @@ -2805,7 +2832,7 @@ func (woc *wfOperationCtx) checkParallelism(tmpl *wfv1.Template, node *wfv1.Node } } - // if we are about to execute a pod, make sure our parent hasn't reached it's limit + // if we are about to execute a pod, make sure our parent hasn't reached its limit if boundaryID != "" && (node == nil || (node.Phase != wfv1.NodePending && node.Phase != wfv1.NodeRunning)) { boundaryNode, err := woc.wf.Status.Nodes.Get(boundaryID) if err != nil { @@ -2823,7 +2850,14 @@ func (woc *wfOperationCtx) checkParallelism(tmpl *wfv1.Template, node *wfv1.Node // Check failFast if boundaryTemplate.IsFailFast() && woc.getUnsuccessfulChildren(boundaryID) > 0 { - woc.markNodePhase(boundaryNode.Name, wfv1.NodeFailed, "template has failed or errored children and failFast enabled") + if woc.getActivePods(boundaryID) == 0 { + if boundaryTemplate.GetType() == wfv1.TemplateTypeSteps { + if leafStepGroupNode := woc.findLeafNodeWithType(boundaryID, wfv1.NodeTypeStepGroup); leafStepGroupNode != nil { + woc.markNodePhase(leafStepGroupNode.Name, wfv1.NodeFailed, "template has failed or errored children and failFast enabled") + } + } + woc.markNodePhase(boundaryNode.Name, wfv1.NodeFailed, "template has failed or errored children and failFast enabled") + } return ErrParallelismReached } From 7a2986698361c38beaac91e3cfbdf05b4f6e4879 Mon Sep 17 00:00:00 2001 From: chengjoey <30427474+chengjoey@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:52:05 +0800 Subject: [PATCH 15/23] fix(controller): step group stuck on running when exit hook has illegal expression (#14032) Signed-off-by: joey (cherry picked from commit 1d36956408aa5652959a4c58a7743f89a8f9e0a4) --- util/expr/argoexpr/eval_test.go | 11 +++++++++++ workflow/controller/steps.go | 10 +++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/util/expr/argoexpr/eval_test.go b/util/expr/argoexpr/eval_test.go index 28a59f5b7608..5862e39a6011 100644 --- a/util/expr/argoexpr/eval_test.go +++ b/util/expr/argoexpr/eval_test.go @@ -58,6 +58,17 @@ func TestEvalBool(t *testing.T) { want: true, wantErr: false, }, + { + name: "test null expression", + args: args{ + input: "steps[\"prepare\"].outputs != null", + env: map[string]interface{}{"steps": map[string]interface{}{ + "prepare": map[string]interface{}{"outputs": "msg"}, + }}, + }, + want: false, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/workflow/controller/steps.go b/workflow/controller/steps.go index d7e2860f7fd9..7926dce77abc 100644 --- a/workflow/controller/steps.go +++ b/workflow/controller/steps.go @@ -109,7 +109,7 @@ func (woc *wfOperationCtx) executeSteps(ctx context.Context, nodeName string, tm sgNode, err := woc.executeStepGroup(ctx, stepGroup.Steps, sgNodeName, &stepsCtx) if err != nil { - return nil, err + return woc.markNodeError(sgNodeName, err), nil } if !sgNode.Fulfilled() { woc.log.Infof("Workflow step group node %s not yet completed", sgNode.ID) @@ -330,8 +330,12 @@ func (woc *wfOperationCtx) executeStepGroup(ctx context.Context, stepGroup []wfv completed = false } else if childNode.Completed() { hasOnExitNode, onExitNode, err := woc.runOnExitNode(ctx, step.GetExitHook(woc.execWf.Spec.Arguments), childNode, stepsCtx.boundaryID, stepsCtx.tmplCtx, "steps."+step.Name, stepsCtx.scope) - if hasOnExitNode && (onExitNode == nil || !onExitNode.Fulfilled() || err != nil) { - // The onExit node is either not complete or has errored out, return. + // see https://github.com/argoproj/argo-workflows/issues/14031, + // we should return error otherwise the node will get stuck + if err != nil { + return node, err + } + if hasOnExitNode && (onExitNode == nil || !onExitNode.Fulfilled()) { completed = false } } From 59026f507bbb4499b62755d287341ae81c3e20bb Mon Sep 17 00:00:00 2001 From: chengjoey <30427474+chengjoey@users.noreply.github.com> Date: Tue, 7 Jan 2025 06:13:50 +0800 Subject: [PATCH 16/23] fix(controller): validation failed when dynamic templateRef is used in nested template (#14053) Signed-off-by: joey (cherry picked from commit 82537a7737028a0a2ee579f7d8d1e54dabcf6481) --- workflow/validate/validate.go | 15 ++++++- workflow/validate/validate_test.go | 69 ++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index f27375a7b3a9..65f7b004527a 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -958,7 +958,12 @@ func (ctx *templateValidationCtx) validateSteps(scope map[string]interface{}, tm if err != nil { return err } - resolvedTmpl, err := ctx.validateTemplateHolder(&step, tmplCtx, &FakeArguments{}, workflowTemplateValidation) + var args wfv1.ArgumentsProvider + args = &FakeArguments{} + if step.TemplateRef != nil { + args = &step.Arguments + } + resolvedTmpl, err := ctx.validateTemplateHolder(&step, tmplCtx, args, workflowTemplateValidation) if err != nil { return errors.Errorf(errors.CodeBadRequest, "templates.%s.steps[%d].%s %s", tmpl.Name, i, step.Name, err.Error()) } @@ -1351,7 +1356,13 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl return errors.Errorf(errors.CodeBadRequest, "templates.%s cannot use 'continueOn' when using 'depends'. Instead use 'dep-task.Failed'/'dep-task.Errored'", tmpl.Name) } - resolvedTmpl, err := ctx.validateTemplateHolder(&task, tmplCtx, &FakeArguments{}, workflowTemplateValidation) + var args wfv1.ArgumentsProvider + args = &FakeArguments{} + if task.TemplateRef != nil { + args = &task.Arguments + } + + resolvedTmpl, err := ctx.validateTemplateHolder(&task, tmplCtx, args, workflowTemplateValidation) if err != nil { return errors.Errorf(errors.CodeBadRequest, "templates.%s.tasks.%s %s", tmpl.Name, task.Name, err.Error()) diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index 3f541cfb5cac..f3d1abe8eca0 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -3294,3 +3294,72 @@ func TestShouldCheckValidationToSpacedParameters(t *testing.T) { // Do not allow leading or trailing spaces in parameters require.ErrorContains(t, err, "failed to resolve {{ workflow.thisdoesnotexist }}") } + +var dynamicWorkflowTemplateARefB = ` +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: workflow-template-a +spec: + templates: + - name: template-a + inputs: + parameters: + - name: message + steps: + - - name: step-a + templateRef: + name: workflow-template-b + template: "{{ inputs.parameters.message }}" +` + +var dynamicWorkflowTemplateRefB = ` +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: workflow-template-b +spec: + templates: + - name: template-b + container: + image: docker/whalesay + command: [cowsay] + args: ["hello from template"] +` + +var dynamicTemplateRefWorkflow = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: dynamic-workflow- +spec: + entrypoint: whalesay + templates: + - name: whalesay + steps: + - - name: whalesay + templateRef: + name: workflow-template-a + template: template-a + arguments: + parameters: + - name: message + value: "template-b" +` + +func TestDynamicWorkflowTemplateRef(t *testing.T) { + wf := wfv1.MustUnmarshalWorkflow(dynamicTemplateRefWorkflow) + wftmplA := wfv1.MustUnmarshalWorkflowTemplate(dynamicWorkflowTemplateARefB) + wftmplB := wfv1.MustUnmarshalWorkflowTemplate(dynamicWorkflowTemplateRefB) + + err := createWorkflowTemplate(wftmplA) + require.NoError(t, err) + err = createWorkflowTemplate(wftmplB) + require.NoError(t, err) + + err = ValidateWorkflow(wftmplGetter, cwftmplGetter, wf, ValidateOpts{}) + require.NoError(t, err) + + _ = deleteWorkflowTemplate(wftmplA.Name) + _ = deleteWorkflowTemplate(wftmplB.Name) +} From 939cefc197bea17d0e4fce8122e974809b526713 Mon Sep 17 00:00:00 2001 From: Tianchu Zhao Date: Tue, 7 Jan 2025 15:50:31 +1100 Subject: [PATCH 17/23] fix: validate template of the same name. Fixes #13763 (#14043) Signed-off-by: Tianchu Zhao (cherry picked from commit 2ece85c23646574434518a3ffb2236f2162f58b1) --- workflow/validate/validate.go | 5 +-- workflow/validate/validate_test.go | 50 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index 65f7b004527a..ad046fae7ee4 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -475,13 +475,14 @@ func (ctx *templateValidationCtx) validateTemplate(tmpl *wfv1.Template, tmplCtx } + templateScope := tmplCtx.GetTemplateScope() tmplID := getTemplateID(tmpl) - _, ok := ctx.results[tmplID] + _, ok := ctx.results[templateScope+tmplID] if ok { // we can skip the rest since it has been validated. return nil } - ctx.results[tmplID] = true + ctx.results[templateScope+tmplID] = true for globalVar, val := range ctx.globalParams { scope[globalVar] = val diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index f3d1abe8eca0..3c7d29e4a6cc 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -1538,6 +1538,56 @@ func TestNestedTemplateRef(t *testing.T) { require.NoError(t, err) } +var templateRefTargetWithInput = ` +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: template-ref-target-with-input +spec: + templates: + - name: A + inputs: + parameters: + - name: message + container: + image: alpine:3.11 + command: [sh, -c] + args: ["echo {{inputs.parameters.message}}"] +` + +var nestedTemplateRefWithError = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: template-ref- +spec: + entrypoint: main + templates: + - name: main + steps: + - - name: call-A + templateRef: + name: template-ref-target + template: A + - - name: call-A-input + template: A + - name: A + steps: + - - name: call-B + templateRef: + name: template-ref-target-with-input + template: A +` + +func TestNestedTemplateRefWithError(t *testing.T) { + err := createWorkflowTemplateFromSpec(templateRefTarget) + require.NoError(t, err) + err = createWorkflowTemplateFromSpec(templateRefTargetWithInput) + require.NoError(t, err) + err = validate(nestedTemplateRefWithError) + require.EqualError(t, err, "templates.main.steps[1].call-A-input templates.A.steps[0].call-B templates.A inputs.parameters.message was not supplied") +} + var undefinedTemplateRef = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow From 293b4a5281fad022c282b1b3094a108fb1a5b84e Mon Sep 17 00:00:00 2001 From: Isitha Subasinghe Date: Fri, 10 Jan 2025 10:26:50 +1100 Subject: [PATCH 18/23] fix: ensure namespace parallelism and parallelism work together. Fixes #10985 (#14039) Signed-off-by: isubasinghe (cherry picked from commit 09d5ee75ed1cf480e4a8c2663256a4b12db720da) --- docs/parallelism.md | 2 + workflow/controller/controller.go | 8 +- workflow/controller/controller_test.go | 1 + workflow/sync/chain_throttler.go | 41 ----- workflow/sync/chain_throttler_test.go | 24 --- workflow/sync/multi_throttler.go | 241 +++++++++++++++++++++++++ workflow/sync/multi_throttler_test.go | 204 +++++++++++++++++++++ workflow/sync/throttler.go | 211 ---------------------- workflow/sync/throttler_test.go | 153 ---------------- 9 files changed, 451 insertions(+), 434 deletions(-) delete mode 100644 workflow/sync/chain_throttler.go delete mode 100644 workflow/sync/chain_throttler_test.go create mode 100644 workflow/sync/multi_throttler.go create mode 100644 workflow/sync/multi_throttler_test.go delete mode 100644 workflow/sync/throttler.go delete mode 100644 workflow/sync/throttler_test.go diff --git a/docs/parallelism.md b/docs/parallelism.md index bae14e3c45fe..f94b68fccf14 100644 --- a/docs/parallelism.md +++ b/docs/parallelism.md @@ -18,6 +18,8 @@ data: namespaceParallelism: "4" ``` +When namespace parallelism is enabled, it is plausible for a workflow with a lower priority to be run first if a namespace is at its namespace parallelism limits. + !!! Note Workflows that are executing but restricted from running more nodes due to other mechanisms will still count toward parallelism limits. diff --git a/workflow/controller/controller.go b/workflow/controller/controller.go index ccce9cd43107..04ea4c30b934 100644 --- a/workflow/controller/controller.go +++ b/workflow/controller/controller.go @@ -242,11 +242,8 @@ func NewWorkflowController(ctx context.Context, restConfig *rest.Config, kubecli } func (wfc *WorkflowController) newThrottler() sync.Throttler { - f := func(key string) { wfc.wfQueue.AddRateLimited(key) } - return sync.ChainThrottler{ - sync.NewThrottler(wfc.Config.Parallelism, sync.SingleBucket, f), - sync.NewThrottler(wfc.Config.NamespaceParallelism, sync.NamespaceBucket, f), - } + f := func(key string) { wfc.wfQueue.Add(key) } + return sync.NewMultiThrottler(wfc.Config.Parallelism, wfc.Config.NamespaceParallelism, f) } // runGCcontroller runs the workflow garbage collector controller @@ -483,6 +480,7 @@ func (wfc *WorkflowController) notifySemaphoreConfigUpdate(cm *apiv1.ConfigMap) log.Warnf("received object from indexer %s is not an unstructured", indexes.SemaphoreConfigIndexName) continue } + log.Infof("Adding workflow %s/%s", un.GetNamespace(), un.GetName()) wfc.wfQueue.AddRateLimited(fmt.Sprintf("%s/%s", un.GetNamespace(), un.GetName())) } } diff --git a/workflow/controller/controller_test.go b/workflow/controller/controller_test.go index c7746f4b15fe..aba088fce33a 100644 --- a/workflow/controller/controller_test.go +++ b/workflow/controller/controller_test.go @@ -953,6 +953,7 @@ func TestNotifySemaphoreConfigUpdate(t *testing.T) { for i := 0; i < 3; i++ { key, _ := controller.wfQueue.Get() controller.wfQueue.Done(key) + controller.wfQueue.Forget(key) } assert.Equal(0, controller.wfQueue.Len()) diff --git a/workflow/sync/chain_throttler.go b/workflow/sync/chain_throttler.go deleted file mode 100644 index 3ea22759e238..000000000000 --- a/workflow/sync/chain_throttler.go +++ /dev/null @@ -1,41 +0,0 @@ -package sync - -import ( - "time" - - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" -) - -type ChainThrottler []Throttler - -func (c ChainThrottler) Init(wfs []wfv1.Workflow) error { - for _, t := range c { - if err := t.Init(wfs); err != nil { - return err - } - } - return nil -} - -func (c ChainThrottler) Add(key Key, priority int32, creationTime time.Time) { - for _, t := range c { - t.Add(key, priority, creationTime) - } -} - -func (c ChainThrottler) Admit(key Key) bool { - for _, t := range c { - if !t.Admit(key) { - return false - } - } - return true -} - -func (c ChainThrottler) Remove(key Key) { - for _, t := range c { - t.Remove(key) - } -} - -var _ Throttler = ChainThrottler{} diff --git a/workflow/sync/chain_throttler_test.go b/workflow/sync/chain_throttler_test.go deleted file mode 100644 index c4ed1868ec26..000000000000 --- a/workflow/sync/chain_throttler_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package sync - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/argoproj/argo-workflows/v3/workflow/sync/mocks" -) - -func TestChainThrottler(t *testing.T) { - m := &mocks.Throttler{} - m.On("Add", "foo", int32(1), time.Time{}).Return() - m.On("Admit", "foo").Return(false) - m.On("Remove", "foo").Return() - - c := ChainThrottler{m} - c.Add("foo", 1, time.Time{}) - assert.False(t, c.Admit("foo")) - c.Remove("foo") - - assert.True(t, ChainThrottler{}.Admit("foo")) -} diff --git a/workflow/sync/multi_throttler.go b/workflow/sync/multi_throttler.go new file mode 100644 index 000000000000..434201e148e8 --- /dev/null +++ b/workflow/sync/multi_throttler.go @@ -0,0 +1,241 @@ +package sync + +import ( + "container/heap" + "sync" + "time" + + "k8s.io/client-go/tools/cache" + + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" +) + +//go:generate mockery --name=Throttler + +// Throttler allows the controller to limit number of items it is processing in parallel. +// Items are processed in priority order, and one processing starts, other items (including higher-priority items) +// will be kept pending until the processing is complete. +// Implementations should be idempotent. +type Throttler interface { + Init(wfs []wfv1.Workflow) error + Add(key Key, priority int32, creationTime time.Time) + // Admit returns if the item should be processed. + Admit(key Key) bool + // Remove notifies throttler that item processing is no longer needed + Remove(key Key) +} + +type Key = string +type QueueFunc func(Key) + +// NewMultiThrottler creates a new multi throttler for throttling both namespace and global parallelism, a parallelism value of zero disables throttling +func NewMultiThrottler(parallelism int, namespaceParallelismLimit int, queue QueueFunc) Throttler { + namespaceParallelism := make(map[string]int) + return &multiThrottler{ + queue: queue, + namespaceParallelism: namespaceParallelism, + namespaceParallelismDefault: namespaceParallelismLimit, + totalParallelism: parallelism, + running: make(map[Key]bool), + pending: make(map[string]*priorityQueue), + lock: &sync.Mutex{}, + } +} + +type multiThrottler struct { + queue QueueFunc + namespaceParallelism map[string]int + namespaceParallelismDefault int + totalParallelism int + running map[Key]bool + pending map[string]*priorityQueue + lock *sync.Mutex +} + +func (m *multiThrottler) Init(wfs []wfv1.Workflow) error { + m.lock.Lock() + defer m.lock.Unlock() + + keys := []Key{} + for _, wf := range wfs { + if wf.Status.Phase != wfv1.WorkflowRunning { + continue + } + key, err := cache.MetaNamespaceKeyFunc(&wf) + if err != nil { + return err + } + keys = append(keys, key) + } + + for _, key := range keys { + m.running[key] = true + } + return nil +} + +func (m *multiThrottler) namespaceCount(namespace string) (int, int) { + setLimit, has := m.namespaceParallelism[namespace] + if !has { + m.namespaceParallelism[namespace] = m.namespaceParallelismDefault + setLimit = m.namespaceParallelismDefault + } + if setLimit == 0 { + // return count is no longer accurate, but preserves behaviour + return 0, 0 + } + count := 0 + for key := range m.running { + ns, _, _ := cache.SplitMetaNamespaceKey(key) + if ns == namespace { + count++ + } + } + return count, setLimit +} + +func (m *multiThrottler) namespaceAllows(namespace string) bool { + count, limit := m.namespaceCount(namespace) + return count < limit || limit == 0 +} + +func (m *multiThrottler) Add(key Key, priority int32, creationTime time.Time) { + m.lock.Lock() + defer m.lock.Unlock() + namespace, _, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return + } + _, ok := m.pending[namespace] + if !ok { + m.pending[namespace] = &priorityQueue{itemByKey: make(map[string]*item)} + } + + m.pending[namespace].add(key, priority, creationTime) + m.queueThrottled() +} + +func (m *multiThrottler) Admit(key Key) bool { + m.lock.Lock() + defer m.lock.Unlock() + + _, ok := m.running[key] + if ok { + return true + } + m.queueThrottled() + return false +} + +func (m *multiThrottler) Remove(key Key) { + m.lock.Lock() + defer m.lock.Unlock() + + namespace, _, _ := cache.SplitMetaNamespaceKey(key) + delete(m.running, key) + m.pending[namespace].remove(key) + m.queueThrottled() +} + +func (m *multiThrottler) queueThrottled() { + if m.totalParallelism != 0 && len(m.running) >= m.totalParallelism { + return + } + + minPq := &priorityQueue{itemByKey: make(map[string]*item)} + + for _, pq := range m.pending { + if len(pq.items) == 0 { + continue + } + currItem := pq.peek() + + namespace, _, err := cache.SplitMetaNamespaceKey(currItem.key) + if err != nil { + return + } + if !m.namespaceAllows(namespace) { + continue + } + + minPq.add(currItem.key, currItem.priority, currItem.creationTime) + } + if len(minPq.items) > 0 { + bestItem := minPq.pop() + bestNamespace, _, _ := cache.SplitMetaNamespaceKey(bestItem.key) + m.pending[bestNamespace].pop() + m.running[bestItem.key] = true + m.queue(bestItem.key) + } +} + +type item struct { + key string + creationTime time.Time + priority int32 + index int +} + +type priorityQueue struct { + items []*item + itemByKey map[string]*item +} + +func (pq *priorityQueue) pop() *item { + return heap.Pop(pq).(*item) +} + +func (pq *priorityQueue) peek() *item { + return pq.items[0] +} + +func (pq *priorityQueue) add(key Key, priority int32, creationTime time.Time) { + if res, ok := pq.itemByKey[key]; ok { + if res.priority != priority { + res.priority = priority + heap.Fix(pq, res.index) + } + } else { + heap.Push(pq, &item{key: key, priority: priority, creationTime: creationTime}) + } +} + +func (pq *priorityQueue) remove(key Key) { + if item, ok := pq.itemByKey[key]; ok { + heap.Remove(pq, item.index) + delete(pq.itemByKey, key) + } +} + +func (pq priorityQueue) Len() int { return len(pq.items) } + +func (pq priorityQueue) Less(i, j int) bool { + if pq.items[i].priority == pq.items[j].priority { + return pq.items[i].creationTime.Before(pq.items[j].creationTime) + } + return pq.items[i].priority > pq.items[j].priority +} + +func (pq priorityQueue) Swap(i, j int) { + pq.items[i], pq.items[j] = pq.items[j], pq.items[i] + pq.items[i].index = i + pq.items[j].index = j +} + +func (pq *priorityQueue) Push(x interface{}) { + n := len(pq.items) + item := x.(*item) + item.index = n + pq.items = append(pq.items, item) + pq.itemByKey[item.key] = item +} + +func (pq *priorityQueue) Pop() interface{} { + old := pq.items + n := len(old) + item := old[n-1] + item.index = -1 + pq.items = old[0 : n-1] + delete(pq.itemByKey, item.key) + return item +} diff --git a/workflow/sync/multi_throttler_test.go b/workflow/sync/multi_throttler_test.go new file mode 100644 index 000000000000..d321c31c0766 --- /dev/null +++ b/workflow/sync/multi_throttler_test.go @@ -0,0 +1,204 @@ +package sync + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + fakewfclientset "github.com/argoproj/argo-workflows/v3/pkg/client/clientset/versioned/fake" +) + +func TestMultiNoParallelismSamePriority(t *testing.T) { + throttler := NewMultiThrottler(0, 0, func(Key) {}) + + throttler.Add("default/c", 0, time.Now().Add(2*time.Hour)) + throttler.Add("default/b", 0, time.Now().Add(1*time.Hour)) + throttler.Add("default/a", 0, time.Now()) + + assert.True(t, throttler.Admit("default/a")) + assert.True(t, throttler.Admit("default/b")) + assert.True(t, throttler.Admit("default/c")) +} + +func TestMultiNoParallelismMultipleBuckets(t *testing.T) { + throttler := NewMultiThrottler(1, 1, func(Key) {}) + throttler.Add("a/0", 0, time.Now()) + throttler.Add("a/1", 0, time.Now().Add(-1*time.Second)) + throttler.Add("b/0", 0, time.Now().Add(-2*time.Second)) + throttler.Add("b/1", 0, time.Now().Add(-3*time.Second)) + + assert.True(t, throttler.Admit("a/0")) + assert.False(t, throttler.Admit("a/1")) + assert.False(t, throttler.Admit("b/0")) + assert.False(t, throttler.Admit("b/1")) + throttler.Remove("a/0") + assert.True(t, throttler.Admit("b/1")) +} + +func TestMultiWithParallelismLimitAndPriority(t *testing.T) { + queuedKey := "" + throttler := NewMultiThrottler(2, 0, func(key string) { queuedKey = key }) + + throttler.Add("default/a", 1, time.Now()) + throttler.Add("default/b", 2, time.Now()) + throttler.Add("default/c", 3, time.Now()) + throttler.Add("default/d", 4, time.Now()) + + assert.True(t, throttler.Admit("default/a"), "is started, even though low priority") + assert.True(t, throttler.Admit("default/b"), "is started, even though low priority") + assert.False(t, throttler.Admit("default/c"), "cannot start") + assert.False(t, throttler.Admit("default/d"), "cannot start") + assert.Equal(t, "default/b", queuedKey) + queuedKey = "" + + throttler.Remove("default/a") + assert.True(t, throttler.Admit("default/b"), "stays running") + assert.True(t, throttler.Admit("default/d"), "top priority") + assert.False(t, throttler.Admit("default/c")) + assert.Equal(t, "default/d", queuedKey) + queuedKey = "" + + throttler.Remove("default/b") + assert.True(t, throttler.Admit("default/d"), "top priority") + assert.True(t, throttler.Admit("default/c"), "now running too") + assert.Equal(t, "default/c", queuedKey) +} + +func TestMultiInitWithWorkflows(t *testing.T) { + queuedKey := "" + throttler := NewMultiThrottler(1, 1, func(key string) { queuedKey = key }) + ctx := context.Background() + + wfclientset := fakewfclientset.NewSimpleClientset( + wfv1.MustUnmarshalWorkflow(` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + labels: + workflows.argoproj.io/phase: Running + name: a + namespace: default +spec: + entrypoint: whalesay + templates: + - name: whalesay + container: + image: docker/whalesay:latest + command: [cowsay] + args: ["hello world"] +status: + phase: Running + startedAt: "2020-06-19T17:37:05Z" +`), + wfv1.MustUnmarshalWorkflow(` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + labels: + workflows.argoproj.io/phase: Running + name: b + namespace: default +spec: + entrypoint: whalesay + templates: + - name: whalesay + container: + image: docker/whalesay:latest + command: [cowsay] + args: ["hello world"] +status: + phase: Running + startedAt: "2020-06-19T17:37:05Z" +`)) + wfList, err := wfclientset.ArgoprojV1alpha1().Workflows("default").List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + err = throttler.Init(wfList.Items) + require.NoError(t, err) + assert.True(t, throttler.Admit("default/a")) + assert.True(t, throttler.Admit("default/b")) + + throttler.Add("default/c", 0, time.Now()) + throttler.Add("default/d", 0, time.Now()) + assert.False(t, throttler.Admit("default/c")) + assert.False(t, throttler.Admit("default/d")) + + throttler.Remove("default/a") + assert.Equal(t, "", queuedKey) + assert.False(t, throttler.Admit("default/c")) + assert.False(t, throttler.Admit("default/d")) + + queuedKey = "" + throttler.Remove("default/b") + assert.Equal(t, "default/c", queuedKey) + assert.True(t, throttler.Admit("default/c")) + assert.False(t, throttler.Admit("default/d")) + + queuedKey = "" + throttler.Remove("default/c") + assert.Equal(t, "default/d", queuedKey) + assert.True(t, throttler.Admit("default/d")) +} + +func TestTotalAllowNamespaceLimit(t *testing.T) { + namespaceLimits := make(map[string]int) + namespaceLimits["a"] = 2 + namespaceLimits["b"] = 1 + throttler := &multiThrottler{ + queue: func(key Key) {}, + namespaceParallelism: namespaceLimits, + namespaceParallelismDefault: 6, + totalParallelism: 4, + running: make(map[Key]bool), + pending: make(map[string]*priorityQueue), + lock: &sync.Mutex{}, + } + throttler.Add("a/0", 1, time.Now()) + throttler.Add("b/0", 2, time.Now()) + throttler.Add("a/1", 3, time.Now()) + throttler.Add("a/2", 4, time.Now()) + throttler.Add("a/3", 5, time.Now()) + throttler.Add("a/4", 6, time.Now()) + throttler.Add("b/1", 7, time.Now()) + + assert.True(t, throttler.Admit("a/0")) + assert.True(t, throttler.Admit("b/0")) + assert.True(t, throttler.Admit("a/1")) + + assert.False(t, throttler.Admit("a/2")) + assert.False(t, throttler.Admit("a/3")) + assert.False(t, throttler.Admit("a/4")) + assert.False(t, throttler.Admit("b/1")) + + throttler.Add("c/0", 8, time.Now()) + assert.True(t, throttler.Admit("c/0")) +} + +func TestPriorityAcrossNamespaces(t *testing.T) { + throttler := NewMultiThrottler(3, 1, func(Key) {}) + throttler.Add("a/0", 0, time.Now()) + throttler.Add("a/1", 0, time.Now()) + throttler.Add("a/2", 0, time.Now()) + throttler.Add("b/0", 1, time.Now()) + throttler.Add("b/1", 1, time.Now()) + throttler.Add("b/2", 1, time.Now()) + + assert.True(t, throttler.Admit("a/0")) + assert.True(t, throttler.Admit("b/0")) + assert.False(t, throttler.Admit("a/1")) + assert.False(t, throttler.Admit("a/2")) + assert.True(t, throttler.Admit("b/0")) + assert.False(t, throttler.Admit("b/1")) + assert.False(t, throttler.Admit("b/2")) + throttler.Remove("a/0") + assert.False(t, throttler.Admit("b/1")) + assert.True(t, throttler.Admit("a/1")) + throttler.Remove("b/0") + assert.True(t, throttler.Admit("b/1")) + assert.False(t, throttler.Admit("a/2")) +} diff --git a/workflow/sync/throttler.go b/workflow/sync/throttler.go deleted file mode 100644 index 674cff254908..000000000000 --- a/workflow/sync/throttler.go +++ /dev/null @@ -1,211 +0,0 @@ -package sync - -import ( - "container/heap" - "sync" - "time" - - "k8s.io/client-go/tools/cache" - - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" -) - -//go:generate mockery --name=Throttler - -// Throttler allows the controller to limit number of items it is processing in parallel. -// Items are processed in priority order, and one processing starts, other items (including higher-priority items) -// will be kept pending until the processing is complete. -// Implementations should be idempotent. -type Throttler interface { - Init(wfs []wfv1.Workflow) error - Add(key Key, priority int32, creationTime time.Time) - // Admit returns if the item should be processed. - Admit(key Key) bool - // Remove notifies throttler that item processing is no longer needed - Remove(key Key) -} - -type Key = string -type QueueFunc func(Key) - -type BucketKey = string -type BucketFunc func(Key) BucketKey - -var SingleBucket BucketFunc = func(key Key) BucketKey { return "" } -var NamespaceBucket BucketFunc = func(key Key) BucketKey { - namespace, _, _ := cache.SplitMetaNamespaceKey(key) - return namespace -} - -type throttler struct { - queue QueueFunc - bucketFunc BucketFunc - inProgress buckets - pending map[BucketKey]*priorityQueue - lock *sync.Mutex - parallelism int -} - -type bucket map[Key]bool -type buckets map[BucketKey]bucket - -// NewThrottler returns a throttle that only runs `parallelism` items at once. When an item may need processing, -// `queue` is invoked. -func NewThrottler(parallelism int, bucketFunc BucketFunc, queue QueueFunc) Throttler { - return &throttler{ - queue: queue, - bucketFunc: bucketFunc, - inProgress: make(buckets), - pending: make(map[BucketKey]*priorityQueue), - lock: &sync.Mutex{}, - parallelism: parallelism, - } -} - -func (t *throttler) Init(wfs []wfv1.Workflow) error { - t.lock.Lock() - defer t.lock.Unlock() - if t.parallelism == 0 { - return nil - } - - for _, wf := range wfs { - key, err := cache.MetaNamespaceKeyFunc(&wf) - if err != nil { - return err - } - if wf.Status.Phase == wfv1.WorkflowRunning { - bucketKey := t.bucketFunc(key) - if _, ok := t.inProgress[bucketKey]; !ok { - t.inProgress[bucketKey] = make(bucket) - } - t.inProgress[bucketKey][key] = true - } - } - return nil -} - -func (t *throttler) Add(key Key, priority int32, creationTime time.Time) { - t.lock.Lock() - defer t.lock.Unlock() - if t.parallelism == 0 { - return - } - bucketKey := t.bucketFunc(key) - if _, ok := t.pending[bucketKey]; !ok { - t.pending[bucketKey] = &priorityQueue{itemByKey: make(map[string]*item)} - } - t.pending[bucketKey].add(key, priority, creationTime) - t.queueThrottled(bucketKey) -} - -func (t *throttler) Admit(key Key) bool { - t.lock.Lock() - defer t.lock.Unlock() - if t.parallelism == 0 { - return true - } - bucketKey := t.bucketFunc(key) - if x, ok := t.inProgress[bucketKey]; ok && x[key] { - return true - } - t.queueThrottled(bucketKey) - return false -} - -func (t *throttler) Remove(key Key) { - t.lock.Lock() - defer t.lock.Unlock() - bucketKey := t.bucketFunc(key) - if x, ok := t.inProgress[bucketKey]; ok { - delete(x, key) - } - if x, ok := t.pending[bucketKey]; ok { - x.remove(key) - } - t.queueThrottled(bucketKey) -} - -func (t *throttler) queueThrottled(bucketKey BucketKey) { - if _, ok := t.inProgress[bucketKey]; !ok { - t.inProgress[bucketKey] = make(bucket) - } - inProgress := t.inProgress[bucketKey] - pending, ok := t.pending[bucketKey] - for ok && pending.Len() > 0 && t.parallelism > len(inProgress) { - key := pending.pop().key - inProgress[key] = true - t.queue(key) - } -} - -type item struct { - key string - creationTime time.Time - priority int32 - index int -} - -type priorityQueue struct { - items []*item - itemByKey map[string]*item -} - -func (pq *priorityQueue) pop() *item { - return heap.Pop(pq).(*item) -} - -func (pq *priorityQueue) peek() *item { - return pq.items[0] -} - -func (pq *priorityQueue) add(key Key, priority int32, creationTime time.Time) { - if res, ok := pq.itemByKey[key]; ok { - if res.priority != priority { - res.priority = priority - heap.Fix(pq, res.index) - } - } else { - heap.Push(pq, &item{key: key, priority: priority, creationTime: creationTime}) - } -} - -func (pq *priorityQueue) remove(key Key) { - if item, ok := pq.itemByKey[key]; ok { - heap.Remove(pq, item.index) - delete(pq.itemByKey, key) - } -} - -func (pq priorityQueue) Len() int { return len(pq.items) } - -func (pq priorityQueue) Less(i, j int) bool { - if pq.items[i].priority == pq.items[j].priority { - return pq.items[i].creationTime.Before(pq.items[j].creationTime) - } - return pq.items[i].priority > pq.items[j].priority -} - -func (pq priorityQueue) Swap(i, j int) { - pq.items[i], pq.items[j] = pq.items[j], pq.items[i] - pq.items[i].index = i - pq.items[j].index = j -} - -func (pq *priorityQueue) Push(x interface{}) { - n := len(pq.items) - item := x.(*item) - item.index = n - pq.items = append(pq.items, item) - pq.itemByKey[item.key] = item -} - -func (pq *priorityQueue) Pop() interface{} { - old := pq.items - n := len(old) - item := old[n-1] - item.index = -1 - pq.items = old[0 : n-1] - delete(pq.itemByKey, item.key) - return item -} diff --git a/workflow/sync/throttler_test.go b/workflow/sync/throttler_test.go deleted file mode 100644 index 4337eb8bdf7c..000000000000 --- a/workflow/sync/throttler_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package sync - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/cache" - - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" - fakewfclientset "github.com/argoproj/argo-workflows/v3/pkg/client/clientset/versioned/fake" -) - -func Test_NamespaceBucket(t *testing.T) { - assert.Equal(t, "a", NamespaceBucket("a/b")) -} - -func TestNoParallelismSamePriority(t *testing.T) { - throttler := NewThrottler(0, SingleBucket, nil) - - throttler.Add("c", 0, time.Now().Add(2*time.Hour)) - throttler.Add("b", 0, time.Now().Add(1*time.Hour)) - throttler.Add("a", 0, time.Now()) - - assert.True(t, throttler.Admit("a")) - assert.True(t, throttler.Admit("b")) - assert.True(t, throttler.Admit("c")) -} - -func TestNoParallelismMultipleBuckets(t *testing.T) { - throttler := NewThrottler(1, func(key Key) BucketKey { - namespace, _, _ := cache.SplitMetaNamespaceKey(key) - return namespace - }, func(key string) {}) - - throttler.Add("a/0", 0, time.Now()) - throttler.Add("a/1", 0, time.Now()) - throttler.Add("b/0", 0, time.Now()) - throttler.Add("b/1", 0, time.Now()) - - assert.True(t, throttler.Admit("a/0")) - assert.False(t, throttler.Admit("a/1")) - assert.True(t, throttler.Admit("b/0")) - throttler.Remove("a/0") - assert.True(t, throttler.Admit("a/1")) -} - -func TestWithParallelismLimitAndPriority(t *testing.T) { - queuedKey := "" - throttler := NewThrottler(2, SingleBucket, func(key string) { queuedKey = key }) - - throttler.Add("a", 1, time.Now()) - throttler.Add("b", 2, time.Now()) - throttler.Add("c", 3, time.Now()) - throttler.Add("d", 4, time.Now()) - - assert.True(t, throttler.Admit("a"), "is started, even though low priority") - assert.True(t, throttler.Admit("b"), "is started, even though low priority") - assert.False(t, throttler.Admit("c"), "cannot start") - assert.False(t, throttler.Admit("d"), "cannot start") - assert.Equal(t, "b", queuedKey) - queuedKey = "" - - throttler.Remove("a") - assert.True(t, throttler.Admit("b"), "stays running") - assert.True(t, throttler.Admit("d"), "top priority") - assert.False(t, throttler.Admit("c")) - assert.Equal(t, "d", queuedKey) - queuedKey = "" - - throttler.Remove("b") - assert.True(t, throttler.Admit("d"), "top priority") - assert.True(t, throttler.Admit("c"), "now running too") - assert.Equal(t, "c", queuedKey) -} - -func TestInitWithWorkflows(t *testing.T) { - queuedKey := "" - throttler := NewThrottler(1, SingleBucket, func(key string) { queuedKey = key }) - ctx := context.Background() - - wfclientset := fakewfclientset.NewSimpleClientset( - wfv1.MustUnmarshalWorkflow(` -apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - labels: - workflows.argoproj.io/phase: Running - name: a - namespace: default -spec: - entrypoint: whalesay - templates: - - name: whalesay - container: - image: docker/whalesay:latest - command: [cowsay] - args: ["hello world"] -status: - phase: Running - startedAt: "2020-06-19T17:37:05Z" -`), - wfv1.MustUnmarshalWorkflow(` -apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - labels: - workflows.argoproj.io/phase: Running - name: b - namespace: default -spec: - entrypoint: whalesay - templates: - - name: whalesay - container: - image: docker/whalesay:latest - command: [cowsay] - args: ["hello world"] -status: - phase: Running - startedAt: "2020-06-19T17:37:05Z" -`)) - wfList, err := wfclientset.ArgoprojV1alpha1().Workflows("default").List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - err = throttler.Init(wfList.Items) - require.NoError(t, err) - assert.True(t, throttler.Admit("default/a")) - assert.True(t, throttler.Admit("default/b")) - - throttler.Add("default/c", 0, time.Now()) - throttler.Add("default/d", 0, time.Now()) - assert.False(t, throttler.Admit("default/c")) - assert.False(t, throttler.Admit("default/d")) - - throttler.Remove("default/a") - assert.Equal(t, "", queuedKey) - assert.False(t, throttler.Admit("default/c")) - assert.False(t, throttler.Admit("default/d")) - - queuedKey = "" - throttler.Remove("default/b") - assert.Equal(t, "default/c", queuedKey) - assert.True(t, throttler.Admit("default/c")) - assert.False(t, throttler.Admit("default/d")) - - queuedKey = "" - throttler.Remove("default/c") - assert.Equal(t, "default/d", queuedKey) - assert.True(t, throttler.Admit("default/d")) -} From 3732ce6315983c4b498ffef08ffcc542393c59c1 Mon Sep 17 00:00:00 2001 From: shuangkun tian <72060326+shuangkun@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:56:01 +0800 Subject: [PATCH 19/23] fix: get logs from artifact when workflow deleted instead of archived. Fixes: #14083 (#14087) Signed-off-by: shuangkun (cherry picked from commit b1a65e79fe583134117c33814b54dcaba40c4238) --- ui/src/shared/services/workflows-service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/services/workflows-service.ts b/ui/src/shared/services/workflows-service.ts index e78df30f2f72..2a2a86e4a870 100644 --- a/ui/src/shared/services/workflows-service.ts +++ b/ui/src/shared/services/workflows-service.ts @@ -4,7 +4,7 @@ import {catchError, filter, map, mergeMap, switchMap} from 'rxjs/operators'; import {NameFilterKeys} from '../../workflows/components/workflow-filters/workflow-filters'; import {uiUrl} from '../base'; import * as models from '../models'; -import {Event, LogEntry, NodeStatus, Workflow, WorkflowList, WorkflowPhase} from '../models'; +import {Event, isWorkflowInCluster, LogEntry, NodeStatus, Workflow, WorkflowList, WorkflowPhase} from '../models'; import {ResubmitOpts, RetryOpts} from '../models'; import {SubmitOpts} from '../models/submit-opts'; import {Pagination} from '../pagination'; @@ -253,9 +253,8 @@ export const WorkflowsService = { const getLogsFromArtifact = () => this.getContainerLogsFromArtifact(workflow, nodeId, container, grep, archived); const getLogsFromCluster = () => this.getContainerLogsFromCluster(workflow, podName, container, grep); - // If our workflow is archived, don't even bother inspecting the cluster for logs since it's likely - // that the Workflow and associated pods have been deleted - if (archived) { + // If our workflow was deleted, try to get logs from artifacts. + if (!isWorkflowInCluster(workflow)) { return getLogsFromArtifact(); } From d60d7dd84ee04123fdf29c595551c0c336f577d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:47:39 +0000 Subject: [PATCH 20/23] chore(deps): bump github.com/go-git/go-git/v5 from 5.11.0 to 5.13.1 in the go_modules group (#14055) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit a91bd843b54c7511121da3561776e41c46f28809) --- go.mod | 36 ++++++++++++++++---------------- go.sum | 66 +++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 30719b0457ff..5f3dc0670436 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/evanphx/json-patch v5.8.0+incompatible github.com/expr-lang/expr v1.16.9 github.com/gavv/httpexpect/v2 v2.16.0 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.13.1 github.com/go-jose/go-jose/v3 v3.0.3 github.com/go-openapi/jsonreference v0.20.4 github.com/go-sql-driver/mysql v1.7.1 @@ -48,7 +48,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tidwall/gjson v1.17.0 github.com/upper/db/v4 v4.7.0 github.com/valyala/fasttemplate v1.2.2 @@ -60,10 +60,10 @@ require ( go.opentelemetry.io/otel/metric v1.23.0 go.opentelemetry.io/otel/sdk v1.23.0 go.opentelemetry.io/otel/sdk/metric v1.23.0 - golang.org/x/crypto v0.26.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/oauth2 v0.16.0 - golang.org/x/sync v0.8.0 + golang.org/x/crypto v0.31.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/oauth2 v0.21.0 + golang.org/x/sync v0.10.0 golang.org/x/time v0.5.0 google.golang.org/api v0.163.0 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe @@ -91,7 +91,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/evilmonkeyinc/jsonpath v0.8.1 // indirect github.com/fatih/color v1.15.0 // indirect @@ -125,7 +125,7 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/fasthash v1.0.3 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/vbatts/tar-split v0.11.3 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect @@ -133,8 +133,8 @@ require ( go.opentelemetry.io/otel/trace v1.23.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/tools v0.23.0 // indirect google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect modernc.org/libc v1.41.0 // indirect @@ -147,7 +147,7 @@ require ( require ( cloud.google.com/go v0.112.0 // indirect cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.5 // indirect github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 @@ -166,7 +166,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/awalterschulze/gographviz v0.0.0-20200901124122-0eecad45bd71 // indirect @@ -208,7 +208,7 @@ require ( github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-billy/v5 v5.6.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/swag v0.22.6 // indirect @@ -271,7 +271,7 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect - github.com/sergi/go-diff v1.1.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -290,10 +290,10 @@ require ( github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 - golang.org/x/term v0.23.0 - golang.org/x/text v0.17.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 + golang.org/x/term v0.27.0 + golang.org/x/text v0.21.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 25d52e0ee925..27c1b5698b3a 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiV cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= @@ -79,8 +81,8 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= @@ -176,7 +178,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -198,8 +199,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -232,8 +233,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= +github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -275,18 +276,18 @@ github.com/gavv/httpexpect/v2 v2.16.0/go.mod h1:uJLaO+hQ25ukBJtQi750PsztObHybNll github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= @@ -739,6 +740,8 @@ github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KR github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-limiter v0.7.2 h1:FgC4N7RMpV5gMrUdda15FaFTkQ/L4fEqM7seXMs4oO8= github.com/sethvargo/go-limiter v0.7.2/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -751,8 +754,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -794,8 +797,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= @@ -901,9 +905,7 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= @@ -914,12 +916,14 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -939,8 +943,9 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -969,7 +974,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= @@ -980,12 +984,16 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1001,6 +1009,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1043,7 +1053,6 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1057,6 +1066,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1064,7 +1075,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= @@ -1076,6 +1086,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1087,7 +1099,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= @@ -1097,6 +1108,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -1127,8 +1140,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 98eee45f321440b1e0c892a8f85af39e175447ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:24:54 +0000 Subject: [PATCH 21/23] chore(deps): bump golang.org/x/crypto from 0.26.0 to 0.31.0 in the go_modules group (#13992) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 53eaef83f4518e7ac8fc1c8ca500f31ed727e1d3) --- go.mod | 10 ++++------ go.sum | 37 ++++--------------------------------- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 5f3dc0670436..855e9a89b4da 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,8 @@ require ( golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/oauth2 v0.21.0 golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 + golang.org/x/term v0.27.0 golang.org/x/time v0.5.0 google.golang.org/api v0.163.0 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe @@ -134,6 +136,7 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.19.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.23.0 // indirect google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect @@ -146,7 +149,6 @@ require ( require ( cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.5 // indirect github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect @@ -291,11 +293,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 - golang.org/x/term v0.27.0 - golang.org/x/text v0.21.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 27c1b5698b3a..480e62ebde02 100644 --- a/go.sum +++ b/go.sum @@ -3,10 +3,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= @@ -164,7 +160,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/blushft/go-diagrams v0.0.0-20201006005127-c78c821223d9 h1:mV+hh0rMjzrhg7Jc/GKwpa+y/0BMHGOHdM9yY1GYyFI= github.com/blushft/go-diagrams v0.0.0-20201006005127-c78c821223d9/go.mod h1:nDeXEIaeDV+mAK1gBD3/RJH67DYPC0GdaznWN7sB07s= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -361,8 +356,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -385,7 +378,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -676,8 +668,8 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= -github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= @@ -738,8 +730,6 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-limiter v0.7.2 h1:FgC4N7RMpV5gMrUdda15FaFTkQ/L4fEqM7seXMs4oO8= @@ -914,8 +904,6 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -982,16 +970,12 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1007,8 +991,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1064,8 +1046,6 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= @@ -1084,8 +1064,6 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1096,7 +1074,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -1106,8 +1083,6 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1157,8 +1132,6 @@ google.golang.org/api v0.163.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYl google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1190,10 +1163,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 77552d852da5c53a1d8c9918b3474d8bff06260a Mon Sep 17 00:00:00 2001 From: Alan Clucas Date: Mon, 10 Feb 2025 11:09:05 +0000 Subject: [PATCH 22/23] Revert "fix(controller): validation failed when dynamic templateRef is used in nested template (#14053)" This reverts commit 59026f507bbb4499b62755d287341ae81c3e20bb. --- workflow/validate/validate.go | 15 +------ workflow/validate/validate_test.go | 69 ------------------------------ 2 files changed, 2 insertions(+), 82 deletions(-) diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index ad046fae7ee4..30574f2b0508 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -959,12 +959,7 @@ func (ctx *templateValidationCtx) validateSteps(scope map[string]interface{}, tm if err != nil { return err } - var args wfv1.ArgumentsProvider - args = &FakeArguments{} - if step.TemplateRef != nil { - args = &step.Arguments - } - resolvedTmpl, err := ctx.validateTemplateHolder(&step, tmplCtx, args, workflowTemplateValidation) + resolvedTmpl, err := ctx.validateTemplateHolder(&step, tmplCtx, &FakeArguments{}, workflowTemplateValidation) if err != nil { return errors.Errorf(errors.CodeBadRequest, "templates.%s.steps[%d].%s %s", tmpl.Name, i, step.Name, err.Error()) } @@ -1357,13 +1352,7 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl return errors.Errorf(errors.CodeBadRequest, "templates.%s cannot use 'continueOn' when using 'depends'. Instead use 'dep-task.Failed'/'dep-task.Errored'", tmpl.Name) } - var args wfv1.ArgumentsProvider - args = &FakeArguments{} - if task.TemplateRef != nil { - args = &task.Arguments - } - - resolvedTmpl, err := ctx.validateTemplateHolder(&task, tmplCtx, args, workflowTemplateValidation) + resolvedTmpl, err := ctx.validateTemplateHolder(&task, tmplCtx, &FakeArguments{}, workflowTemplateValidation) if err != nil { return errors.Errorf(errors.CodeBadRequest, "templates.%s.tasks.%s %s", tmpl.Name, task.Name, err.Error()) diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index 3c7d29e4a6cc..ebeaf55e49ef 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -3344,72 +3344,3 @@ func TestShouldCheckValidationToSpacedParameters(t *testing.T) { // Do not allow leading or trailing spaces in parameters require.ErrorContains(t, err, "failed to resolve {{ workflow.thisdoesnotexist }}") } - -var dynamicWorkflowTemplateARefB = ` -apiVersion: argoproj.io/v1alpha1 -kind: WorkflowTemplate -metadata: - name: workflow-template-a -spec: - templates: - - name: template-a - inputs: - parameters: - - name: message - steps: - - - name: step-a - templateRef: - name: workflow-template-b - template: "{{ inputs.parameters.message }}" -` - -var dynamicWorkflowTemplateRefB = ` -apiVersion: argoproj.io/v1alpha1 -kind: WorkflowTemplate -metadata: - name: workflow-template-b -spec: - templates: - - name: template-b - container: - image: docker/whalesay - command: [cowsay] - args: ["hello from template"] -` - -var dynamicTemplateRefWorkflow = ` -apiVersion: argoproj.io/v1alpha1 -kind: Workflow -metadata: - generateName: dynamic-workflow- -spec: - entrypoint: whalesay - templates: - - name: whalesay - steps: - - - name: whalesay - templateRef: - name: workflow-template-a - template: template-a - arguments: - parameters: - - name: message - value: "template-b" -` - -func TestDynamicWorkflowTemplateRef(t *testing.T) { - wf := wfv1.MustUnmarshalWorkflow(dynamicTemplateRefWorkflow) - wftmplA := wfv1.MustUnmarshalWorkflowTemplate(dynamicWorkflowTemplateARefB) - wftmplB := wfv1.MustUnmarshalWorkflowTemplate(dynamicWorkflowTemplateRefB) - - err := createWorkflowTemplate(wftmplA) - require.NoError(t, err) - err = createWorkflowTemplate(wftmplB) - require.NoError(t, err) - - err = ValidateWorkflow(wftmplGetter, cwftmplGetter, wf, ValidateOpts{}) - require.NoError(t, err) - - _ = deleteWorkflowTemplate(wftmplA.Name) - _ = deleteWorkflowTemplate(wftmplB.Name) -} From fe79073164b43160805610ad51430e5094c28f1c Mon Sep 17 00:00:00 2001 From: "gcp-cherry-pick-bot[bot]" <98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:02:30 +0000 Subject: [PATCH 23/23] chore: upgrade actions/cache to v4.2.0 (Fixes #14169) (cherry-pick #14170) (#14171) Signed-off-by: Rohan K Co-authored-by: Rohan K --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index da5e8fbb97c1..956e95ecd56d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,7 +40,7 @@ jobs: version: v0.10.4 - name: Cache Docker layers - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: /tmp/.buildx-cache @@ -293,7 +293,7 @@ jobs: with: go-version: "1.23" - name: Restore node packages cache - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ui/node_modules key: ${{ runner.os }}-node-dep-v1-${{ hashFiles('**/yarn.lock') }}