diff --git a/.github/actions/e2e-cluster-up/action.yml b/.github/actions/e2e-cluster-up/action.yml index c690b44708a9..fc37d8d74592 100644 --- a/.github/actions/e2e-cluster-up/action.yml +++ b/.github/actions/e2e-cluster-up/action.yml @@ -1,9 +1,9 @@ name: E2E cluster up description: >- - Shared E2E setup: free disk, install k3d and Tilt from the e2e-tools artifact, - create the k3d cluster, and set up /etc/hosts. Used by the e2e-tests and e2e-ui - jobs so their setup can't drift. The caller must run actions/checkout and - actions/setup-go first (this action references the repo and `go env GOPATH`). + Shared E2E setup: free disk, create the k3d cluster, and set up /etc/hosts. + Used by the e2e-tests and e2e-ui jobs so their setup can't drift. The caller + must run actions/checkout and .github/actions/setup-nix first: this action + references the repo, and k3d and kubectl come from the development shell. inputs: k8s-version: @@ -30,29 +30,20 @@ runs: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/.ghcup /opt/hostedtoolcache/CodeQL printf "==> Available space after cleanup\n" df -h - - name: Download k3d and Tilt - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: e2e-tools - path: tools-bin - - name: Install k3d and Tilt onto PATH - # built once by the e2e-tools job; artifacts don't preserve the +x bit - shell: bash - run: install -m 0755 tools-bin/k3d tools-bin/tilt "$(go env GOPATH)/bin/" - name: Install socat # socat supports "kubectl port-forward": cri-dockerd needed it (https://github.com/k3s-io/cri-dockerd/blob/4995f339edcffdf890406b3f1477d34e38477f18/streaming/streaming_others.go#L46); # k3d's k3s uses embedded containerd, which doesn't, so this is defensive and harmless. shell: bash run: sudo apt-get -y install socat - name: Create k3d cluster - shell: bash + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} env: KUBECONFIG: ${{ inputs.kubeconfig }} # hack/tilt/k3d-up.sh maps this key via hack/k8s-versions.sh to a k3s image K8S_VERSION: ${{ inputs.k8s-version }} CLI_AUTH_KUBECONFIG: ${{ inputs.cli-auth-kubeconfig }} run: | - # k3d and Tilt come from the e2e-tools artifact (installed above). + # k3d and kubectl come from the development shell. # k3d runs k3s with its embedded containerd (2.x in these k3s # versions), which supports the Kubernetes image volumes the # init-less pod layout uses to deliver argoexec. diff --git a/.github/actions/e2e-failure-debug/action.yml b/.github/actions/e2e-failure-debug/action.yml index 41d72222c0d0..e6878f6a715c 100644 --- a/.github/actions/e2e-failure-debug/action.yml +++ b/.github/actions/e2e-failure-debug/action.yml @@ -5,25 +5,28 @@ description: >- guarded with `|| true` so one failing diagnostic doesn't skip the rest — a composite step otherwise aborts the whole action on the first failure. + Each step runs in the development shell, which is where `kubectl` comes from, + so the caller must have run .github/actions/setup-nix. + runs: using: composite steps: - name: Failure debug - k3d server logs - shell: bash + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} run: docker logs k3d-k3s-default-server-0 || true - name: Failure debug - Controller/API logs - shell: bash + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} run: | kubectl -n argo logs deploy/workflow-controller --prefix --tail=-1 || true kubectl -n argo logs deploy/argo-server --prefix --tail=-1 || true - name: Failure debug - Tilt logs - shell: bash + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} run: '[ -e /tmp/tilt.log ] && cat /tmp/tilt.log || true' - name: Failure debug - describe Workflows - shell: bash + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} run: | kubectl get wf || true kubectl describe wf || true - name: Failure debug - Workflow Pod logs - shell: bash + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} run: kubectl logs --all-containers -l workflows.argoproj.io/workflow --prefix || true diff --git a/.github/actions/yarn-cache/action.yml b/.github/actions/yarn-cache/action.yml new file mode 100644 index 000000000000..3bdd242055f9 --- /dev/null +++ b/.github/actions/yarn-cache/action.yml @@ -0,0 +1,19 @@ +name: Cache the Yarn download cache +description: >- + Restore and save Yarn's global download cache. Jobs that get Node and Yarn + from the development shell rather than `actions/setup-node` lose the caching + that action did as a side effect; this puts it back, on its own, so that + fetching the same packages again is not the price of moving to Nix. + + Only the download cache is shared: `yarn install` still links `ui/node_modules` + in each job, exactly as it did before. + +runs: + using: composite + steps: + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles('ui/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 3114306430f2..69f6533d631a 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -134,11 +134,11 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version: "1.26" - cache: true - - run: make test STATIC_FILES=false GOTEST='go test -p 20 -covermode=atomic -coverprofile=coverage.out' + # Go and gotestsum come from the development shell, so the tests run on + # the toolchain go.mod asks for and report through the pinned runner. + - uses: ./.github/actions/setup-nix + - uses: ./.github/actions/go-cache + - run: nix develop --command make test STATIC_FILES=false GOTEST='go test -p 20 -covermode=atomic -coverprofile=coverage.out' - name: Upload test results if: ${{ always() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -164,6 +164,8 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # The development shell is Linux and macOS only, so Windows keeps its own + # Go install; the version below has to be kept in step with go.mod by hand. - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.26" @@ -181,40 +183,9 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - e2e-tools: - name: E2E Tools - needs: [ changed-files ] - # also required by e2e-ui (runs on ui-only changes); keep this condition a - # superset of e2e-ui's, else a skipped dependency cascades and e2e-ui is - # silently skipped on ui-only PRs - if: ${{ needs.changed-files.outputs.e2e-tests == 'true' || needs.changed-files.outputs.ui == 'true' }} - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version: "1.26" - cache: true - - name: Build k3d and Tilt once - # The whole e2e matrix shares these binaries via the artifact below, so - # we hit the k3d module proxy and the Tilt release CDN once each — not in - # ~14 parallel jobs, which throttles the CDN. This is why neither install - # needs download retries. - run: | - make k3d tilt - mkdir -p tools-bin - cp "$(go env GOPATH)/bin/k3d" "$(go env GOPATH)/bin/tilt" tools-bin/ - - name: Upload tools - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: e2e-tools - path: tools-bin - if-no-files-found: error - e2e-tests: name: E2E Tests - needs: [ changed-files, e2e-tools ] + needs: [ changed-files ] if: ${{ needs.changed-files.outputs.e2e-tests == 'true' }} runs-on: ubuntu-24.04 # These tests usually finish in ~25m, but occasionally they take much longer due to resource @@ -223,6 +194,13 @@ jobs: env: KUBECONFIG: /home/runner/.kube/config E2E_ENV_FACTOR: 2 + defaults: + run: + # Every step below wants something from the development shell — Tilt, + # k3d, kubectl, Go, gotestsum — so the job runs in it rather than + # prefixing each `run` with `nix develop --command`. Everything the + # runner image provides (Docker above all) is still on PATH behind it. + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} strategy: fail-fast: false matrix: @@ -331,17 +309,23 @@ jobs: use-api: false steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version: "1.26" - cache: true - - name: Install Java for the SDK - if: ${{matrix.test == 'test-java-sdk'}} - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + # The JDK the Java SDK suite builds with comes from the shell too — it is + # pinned there alongside the OpenAPI Generator that produced the client. + # Maven itself still comes from the runner image, as it always did: + # `actions/setup-java` installed a JDK, not Maven. + - uses: ./.github/actions/setup-nix + - uses: ./.github/actions/go-cache + - name: Cache the Maven repository + # `actions/setup-java` cached this as a side effect; only the Java SDK + # leg builds anything with Maven, so restore it only there. + if: ${{ matrix.test == 'test-java-sdk' }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - java-version: '8' - distribution: adopt - cache: maven + path: ~/.m2/repository + # the client pom is generated, so key on what generates it + key: ${{ runner.os }}-maven-${{ hashFiles('sdks/java/tests/pom.xml', 'sdks/java/Makefile') }} + restore-keys: | + ${{ runner.os }}-maven- - name: Set up k3d cluster and E2E tooling uses: ./.github/actions/e2e-cluster-up with: @@ -421,18 +405,13 @@ jobs: # see https://github.com/orgs/community/discussions/9141#discussioncomment-2296809 and https://github.com/orgs/community/discussions/26822#discussioncomment-3305794 e2e-tests-composite-result: name: E2E Tests - Composite result - needs: [ e2e-tools, e2e-tests, e2e-ui ] + needs: [ e2e-tests, e2e-ui ] if: ${{ always() }} runs-on: ubuntu-24.04 steps: - run: | - tools="${{ needs.e2e-tools.result }}" result="${{ needs.e2e-tests.result }}" ui="${{ needs.e2e-ui.result }}" - # a tools failure skips the dependent jobs, which must not count as a pass - if [[ $tools == "failure" ]]; then - exit 1 - fi # mark as successful even if skipped (e.g. a PR that touches neither) if [[ ( $result == "success" || $result == "skipped" ) && ( $ui == "success" || $ui == "skipped" ) ]]; then exit 0 @@ -496,13 +475,15 @@ jobs: timeout-minutes: 6 env: NODE_OPTIONS: --max-old-space-size=4096 + defaults: + run: + # Node and Yarn come from the development shell, so the bundle CI builds + # is the one built locally; every step here needs them. + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "24" # change in all GH Workflows - cache: yarn - cache-dependency-path: ui/yarn.lock + - uses: ./.github/actions/setup-nix + - uses: ./.github/actions/yarn-cache - run: yarn --cwd ui install - run: yarn --cwd ui build - run: yarn --cwd ui test @@ -518,7 +499,7 @@ jobs: e2e-ui: name: E2E UI - needs: [ changed-files, e2e-tools ] + needs: [ changed-files ] if: ${{ needs.changed-files.outputs.ui == 'true' || needs.changed-files.outputs.e2e-tests == 'true' }} runs-on: ubuntu-24.04 timeout-minutes: 30 @@ -530,17 +511,15 @@ jobs: # argo-server serves the production UI bundle at :2746. Point Playwright # there — this also gives production-bundle coverage. ARGO_UI_BASE_URL: http://localhost:2746 + defaults: + run: + # As in e2e-tests: Tilt, k3d, kubectl, Node and Yarn all come from the + # development shell, so the whole job runs in it. + shell: nix develop --command bash --noprofile --norc -eo pipefail {0} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version: "1.26" - cache: true - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "24" # change in all GH Workflows - cache: yarn - cache-dependency-path: ui/yarn.lock + - uses: ./.github/actions/setup-nix + - uses: ./.github/actions/yarn-cache - name: Set up k3d cluster and E2E tooling uses: ./.github/actions/e2e-cluster-up - name: Install UI dependencies and Playwright browser diff --git a/Makefile b/Makefile index 8f1bfb36b7d7..22745fd44967 100644 --- a/Makefile +++ b/Makefile @@ -593,8 +593,8 @@ dist/argosay: cp test/e2e/images/argosay/v2/argosay dist/ # The development shell supplies tilt and k3d, so these two targets exist only -# for the environments that do not use it yet: CI and the dev container. They go -# when those move to the flake. +# for the dev container, which does not use it yet. They go when it moves to the +# flake. # renovate: datasource=github-releases depName=tilt-dev/tilt TILT_VERSION ?= 0.37.3 @@ -609,9 +609,8 @@ tilt: ## Install the pinned Tilt to $(GOPATH)/bin if not already present dir=$$(go env GOPATH)/bin ; mkdir -p "$$dir" ; \ echo "installing tilt v$(TILT_VERSION) to $$dir" ; \ : "tilt can't be go installed (its go.mod has replace directives), so it" ; \ - : "comes from GitHub's release CDN. CI builds it once (the e2e-tools job)" ; \ - : "and shares it across the matrix, rather than fetching it in 14 parallel" ; \ - : "jobs (which throttles the CDN) — so no download retries are needed." ; \ + : "comes from GitHub's release CDN. One download per dev container build" ; \ + : "doesn't need retries; CI gets tilt from the shell, not from here." ; \ : "Download to a temp file so a truncated download fails tar cleanly." ; \ tmp=$$(mktemp) ; \ curl -fsSL "https://github.com/tilt-dev/tilt/releases/download/v$(TILT_VERSION)/tilt.$(TILT_VERSION).$$os.$$arch.tar.gz" -o "$$tmp" \ @@ -625,8 +624,8 @@ k3d: ## Install the pinned k3d to $(GOPATH)/bin if not already present else \ echo "installing k3d v$${K3D_VERSION}" ; \ : "go install pulls from the module proxy (proxy.golang.org), which is" ; \ - : "far more reliable under CI load than GitHub's release-asset CDN. The" ; \ - : "ldflags stamp the version k3d reports and uses to tag its helper image" ; \ + : "more reliable than GitHub's release-asset CDN. The ldflags stamp the" ; \ + : "version k3d reports and uses to tag its helper image" ; \ go install -ldflags "-X github.com/k3d-io/k3d/v5/version.Version=v$${K3D_VERSION}" \ github.com/k3d-io/k3d/v5@v$${K3D_VERSION} ; \ fi diff --git a/cmd/argoexec/commands/emissary_test.go b/cmd/argoexec/commands/emissary_test.go index d3b7da2464ed..c65e04d2a46a 100644 --- a/cmd/argoexec/commands/emissary_test.go +++ b/cmd/argoexec/commands/emissary_test.go @@ -55,7 +55,9 @@ func TestEmissary(t *testing.T) { }) t.Run("Sub-process", func(t *testing.T) { _ = os.Remove(varRunArgo + "/ctr/main/stdout") - err = run(`(sleep 60; echo 'should not wait for sub-process')& echo "hello\c"`) + // `printf`, not `echo "hello\c"`: only dash reads that as "hello with no + // newline", and `sh` is bash on macOS and inside the development shell. + err = run(`(sleep 60; echo 'should not wait for sub-process')& printf hello`) require.NoError(t, err) var data []byte data, err = os.ReadFile(varRunArgo + "/ctr/main/stdout") diff --git a/flake.nix b/flake.nix index 2dd5df3f9288..fae710d307e8 100644 --- a/flake.nix +++ b/flake.nix @@ -459,8 +459,14 @@ # Nix's Go sets its own GOROOT; a stale one inherited from the host # (a Homebrew or asdf install, say) makes it build against the wrong # standard library. + # + # Nix also points TMPDIR at a directory of its own, whose name is 17 + # characters longer than /tmp. Go tests that bind a unix socket under + # `t.TempDir()` then run past the 108-byte sun_path limit and fail + # only inside the shell, so hand TMPDIR back to the platform default. shellHook = '' unset GOROOT + unset TMPDIR TMP TEMP TEMPDIR ''; }; diff --git a/workflow/executor/osspecific/command_test.go b/workflow/executor/osspecific/command_test.go index c5247468da7b..990e72827290 100644 --- a/workflow/executor/osspecific/command_test.go +++ b/workflow/executor/osspecific/command_test.go @@ -17,10 +17,15 @@ import ( func TestSimpleStartCloser(t *testing.T) { ctx := logging.TestContext(t.Context()) shell := "sh" + // `echo "...\c"` drops the trailing newline under dash, but prints the + // backslash verbatim under bash — which is what `sh` is on macOS and inside + // the development shell. `printf` means the same thing under both. + command := `printf '%s' "A123456789B123456789C123456789D123456789E123456789"` if runtime.GOOS == "windows" { shell = "pwsh.exe" + command = `echo "A123456789B123456789C123456789D123456789E123456789\c"` } - cmd := exec.CommandContext(ctx, shell, "-c", `echo "A123456789B123456789C123456789D123456789E123456789\c"`) + cmd := exec.CommandContext(ctx, shell, "-c", command) var stdoutWriter bytes.Buffer slowWriter := SlowWriter{ &stdoutWriter,