diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..c3de53905c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,131 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-linux: + runs-on: ubuntu-latest + container: + image: tiltdev/tilt-ci@sha256:fb737aaed8f1d44d56d4797c1c96046ec940cf2a238d76be29527da2784e8f1e + options: --group-add 118 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + env: + GOBIN: /usr/local/bin + steps: + - name: Check docker sock permissions + run: | + ls -n /var/run/docker.sock + gid=$(ls -n /var/run/docker.sock | awk '{print$4}') + if [ "$gid" -ne 118 ]; then + echo "::error::Unexpected Group ID ($gid) for Docker socket" >> $GITHUB_OUTPUT + exit 1 + fi + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Configure Git safe directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Fetch master branch + run: | + git fetch origin master:refs/remotes/origin/master + git branch -f master origin/master + + - name: Docker login + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Set up Docker + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Pull registry image + run: docker pull registry:2 + + - name: Build + run: make install + + - name: Lint + run: make lint + + - name: Version check + run: make test_install_version_check + + - name: Wire check + run: make wire-check + + - name: Check codegen + run: ./scripts/check-codegen.sh + + - name: Run tests + run: make test-go-ci + + - name: Upload test results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: go-test-results + path: test-results/ + + - name: Notify Slack on failure + if: failure() && github.ref == 'refs/heads/master' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "build-linux failed on master: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + + build-js: + runs-on: ubuntu-latest + needs: build-linux + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22.14.0' + cache: 'yarn' + cache-dependency-path: web/yarn.lock + + - name: Check JS + run: make check-js + + - name: Test JS + run: make test-js-ci + env: + JEST_JUNIT_OUTPUT_DIR: reports/junit/js-test-results.xml + + - name: Test Storybook + run: make test-storybook + + - name: Upload test results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: js-test-results + path: web/reports/junit/ + + - name: Notify Slack on failure + if: failure() && github.ref == 'refs/heads/master' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "build-js failed on master: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..43135f957e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,82 @@ +name: Docs + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-docs: + runs-on: ubuntu-latest + container: + image: docker/tilt-ci@sha256:3b83314ab36d9a529f5af8ca788b00d0926d16ed981a929317aab62696a88d77 + options: --group-add 118 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + steps: + - name: Check docker sock permissions + run: | + ls -n /var/run/docker.sock + gid=$(ls -n /var/run/docker.sock | awk '{print$4}') + if [ "$gid" -ne 118 ]; then + echo "::error::Unexpected Group ID ($gid) for Docker socket" >> $GITHUB_OUTPUT + exit 1 + fi + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + path: tilt + + - name: Configure Git safe directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE/tilt" + + - name: Fetch master branch + run: | + git fetch origin master:refs/remotes/origin/master + git branch -f master origin/master + working-directory: tilt + + - name: Check for Go changes + id: changes + run: | + if ! ./scripts/ci-has-go-changes.sh; then + echo "skip=true" >> $GITHUB_OUTPUT + fi + working-directory: tilt + + - name: Checkout tilt.build repo + if: steps.changes.outputs.skip != 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: tilt-dev/tilt.build + path: tilt.build + + - name: Build Tilt + if: steps.changes.outputs.skip != 'true' + run: make install + working-directory: tilt + + - name: Generate and verify docs + if: steps.changes.outputs.skip != 'true' + working-directory: tilt.build + run: | + tilt dump cli-docs --dir="./docs/cli" + tilt dump api-docs --dir="./api" + make cli-toc + make api + + - name: Notify Slack on failure + if: failure() && github.ref == 'refs/heads/master' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "check-docs failed on master: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000000..40d1c9c0e1 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,169 @@ +name: Integration + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-changes: + runs-on: ubuntu-latest + outputs: + has-go-changes: ${{ steps.check.outputs.go-changes }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Check for Go changes + id: check + run: | + if ./scripts/ci-has-go-changes.sh; then + echo "go-changes=true" >> $GITHUB_OUTPUT + else + echo "go-changes=false" >> $GITHUB_OUTPUT + fi + + build-integration: + needs: check-changes + if: needs.check-changes.outputs.has-go-changes == 'true' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Group 1: Quick K8s tests (analytics, config, crash, crd, demo, env, event, etc.) + - name: quick + pattern: "Test(Opt|ConfigMap|Crash|CRD|TiltDemo|EnvInit|Event|Idempotent|Ignores|ImageTags|LocalResource|NamespaceFlag|TiltArgs|TiltCI|CLI_DockerPrune)" + # Group 2: Docker Compose tests + - name: docker-compose + pattern: "Test(DockerCompose|OneDockerCompose|DisableDC)" + # Group 3: Live Update tests + - name: live-update + pattern: "TestLiveUpdate" + # Group 4: K8s deployment tests (jobs, oneup, watch, etc.) + - name: k8s-deploy + pattern: "Test(Job|OneUp|OneWatch|WatchExec|K8sCustomDeploy|DisableK8s|RestartProcess|SameImg|Shortlived|TooMany|TTLJob)" + container: + image: tiltdev/tilt-integration-ci@sha256:d0dd2ae941a5d67d8c194ce0f72feae4018c88b40ef5cbba89f98c1bf44e9e9c + options: --group-add 118 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + steps: + - name: Check docker sock permissions + run: | + ls -n /var/run/docker.sock + gid=$(ls -n /var/run/docker.sock | awk '{print$4}') + if [ "$gid" -ne 118 ]; then + echo "::error::Unexpected Group ID ($gid) for Docker socket" >> $GITHUB_OUTPUT + exit 1 + fi + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Configure Git safe directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Fetch master branch + run: | + git fetch origin master:refs/remotes/origin/master + git branch -f master origin/master + + - name: Docker login + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Clean Docker resources + run: docker system prune -af --volumes || true + + - name: Setup Kind cluster + run: ctlptl create cluster kind --registry=ctlptl-registry + + - name: Build JS assets + run: make build-js + + - name: Run integration tests (${{ matrix.name }}) + run: | + go test -mod vendor -v -count 1 -tags 'integration' -timeout 30m \ + -run '${{ matrix.pattern }}' ./integration + + - name: Notify Slack on failure + if: failure() && github.ref == 'refs/heads/master' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "build-integration (${{ matrix.name }}) failed on master: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + + test-extensions: + needs: + - check-changes + - build-integration + if: needs.check-changes.outputs.has-go-changes == 'true' + runs-on: ubuntu-latest + container: + image: docker/tilt-extensions-ci@sha256:c22a39c287c7c3afba182f4f46044475b43bd2d4e2bb89f41136eae7d4f4b94d + options: --group-add 118 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + env: + GOBIN: /usr/local/bin + PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} + steps: + - name: Check docker sock permissions + run: | + ls -n /var/run/docker.sock + gid=$(ls -n /var/run/docker.sock | awk '{print$4}') + if [ "$gid" -ne 118 ]; then + echo "::error::Unexpected Group ID ($gid) for Docker socket" >> $GITHUB_OUTPUT + exit 1 + fi + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Configure Git safe directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Fetch master branch + run: | + git fetch origin master:refs/remotes/origin/master + git branch -f master origin/master + + - name: Docker login + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Clean Docker resources + run: docker system prune -af --volumes || true + + - name: Setup Kind cluster + run: ctlptl create cluster kind --registry=ctlptl-registry + + - name: Build and test extensions + run: make build-js install test-extensions + + - name: Notify Slack on failure + if: failure() && github.ref == 'refs/heads/master' + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "test-extensions failed on master: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..b0eeb6ae92 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +name: Lint + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 + with: + scandir: './scripts' + severity: warning + env: + SHELLCHECK_OPTS: -e SC2001 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..f22fbd2ab5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,125 @@ +name: Release + +on: + push: + branches: [master] + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-dry-run: + # Run on master branch pushes only (not tags) + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + container: + image: tiltdev/tilt-releaser@sha256:fe043664aa543e4abd8aa6df29828c00bc3d3205d76296226e084f900a855016 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Build JS + run: make build-js + + - name: Goreleaser dry run + run: goreleaser --verbose --clean --skip=publish --snapshot + timeout-minutes: 20 + + - name: Notify Slack on failure + if: failure() + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "release-dry-run failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + + release: + # Run only on version tags + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + container: + image: tiltdev/tilt-releaser@sha256:fe043664aa543e4abd8aa6df29828c00bc3d3205d76296226e084f900a855016 + env: + DOCKER_CLI_EXPERIMENTAL: enabled + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Notify Slack - Release Started + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "A Tilt release has started! Version: ${{ github.ref_name }}" + } + + - name: Docker login + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ vars.TILT_GITHUB_CLIENT_ID }} + private-key: ${{ secrets.TILT_GITHUB_PRIVATE_KEY_BASE64 }} + owner: tilt-dev + repositories: tilt,tilt.build,cloud.tilt.dev,tilt-extensions + + - name: Configure Git + run: | + git config --global user.email "tilt-team@docker.com" + git config --global user.name "Tilt Dev" + + - name: Run release + run: ./scripts/release-ci.sh + timeout-minutes: 20 + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + + - name: Notify Slack - Release Complete + if: success() + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "Tilt release ${{ github.ref_name }} completed successfully!" + } + + - name: Notify Slack - Release Failed + if: failure() + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "Tilt release ${{ github.ref_name }} FAILED: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..d139b749a8 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,74 @@ +name: Windows + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-changes: + runs-on: ubuntu-latest + outputs: + has-go-changes: ${{ steps.check.outputs.go-changes }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Check for Go changes + id: check + run: | + if ./scripts/ci-has-go-changes.sh; then + echo "go-changes=true" >> $GITHUB_OUTPUT + else + echo "go-changes=false" >> $GITHUB_OUTPUT + fi + + build-windows: + needs: check-changes + if: needs.check-changes.outputs.has-go-changes == 'true' + runs-on: windows-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version: '1.25' + + - name: Install dependencies + run: | + choco install make -y + choco install kustomize -y + choco install kubernetes-helm -y + choco install docker-compose -y + choco install mingw --version=12.2.0.03042023 -y + + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest + + - name: Build Windows binary + run: go install -mod vendor ./cmd/tilt + env: + CGO_ENABLED: 1 + CGO_LDFLAGS: -static + + - name: Run tests + run: make shorttest-ci + env: + CGO_ENABLED: 1 + CGO_LDFLAGS: -static + + - name: Test PowerShell installer + run: ./scripts/install.ps1 + shell: pwsh + + - name: Upload test results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: windows-test-results + path: test-results/ diff --git a/internal/controllers/core/cmd/execer_unix_test.go b/internal/controllers/core/cmd/execer_unix_test.go index 122d859ad3..0f92f0a818 100644 --- a/internal/controllers/core/cmd/execer_unix_test.go +++ b/internal/controllers/core/cmd/execer_unix_test.go @@ -54,5 +54,5 @@ echo BACKGROUND $! assert.Eventually(t, func() bool { err := grandkid.Signal(syscall.SIGCONT) return err != nil && strings.Contains(err.Error(), "process already finished") - }, time.Second, time.Millisecond) + }, 5*time.Second, 10*time.Millisecond) } diff --git a/internal/localexec/execer_test.go b/internal/localexec/execer_test.go index d1c012183b..aead0f1d18 100644 --- a/internal/localexec/execer_test.go +++ b/internal/localexec/execer_test.go @@ -86,7 +86,7 @@ func TestProcessExecer_Run_ProcessGroup(t *testing.T) { childProcStopped := assert.Eventually(t, func() bool { err = proc.Signal(syscall.Signal(0)) return errors.Is(err, os.ErrProcessDone) - }, time.Second, 50*time.Millisecond, "Child process was still running") + }, 5*time.Second, 50*time.Millisecond, "Child process was still running") if !childProcStopped { _ = proc.Kill() } diff --git a/scripts/release-ci.sh b/scripts/release-ci.sh index 262b8aaa62..c83d08182a 100755 --- a/scripts/release-ci.sh +++ b/scripts/release-ci.sh @@ -20,19 +20,11 @@ if [[ "$DOCKER_TOKEN" == "" ]]; then exit 1 fi -if [[ "$TILT_CLOUD_TOKEN" == "" ]]; then - echo "Missing Tilt release token" - exit 1 -fi - DIR=$(dirname "$0") cd "$DIR/.." echo "$DOCKER_TOKEN" | docker login --username "$DOCKER_USERNAME" --password-stdin -mkdir -p ~/.windmill -echo "$TILT_CLOUD_TOKEN" > ~/.windmill/token - git fetch --tags git config --global user.email "tilt-team@docker.com" git config --global user.name "Tilt Dev" diff --git a/scripts/update-codegen.sh b/scripts/update-codegen.sh index b8209e3b6c..7dbafa20da 100755 --- a/scripts/update-codegen.sh +++ b/scripts/update-codegen.sh @@ -11,8 +11,9 @@ if [[ $CI == true ]]; then # TODO - get this working in CI # scripts/update-protobuf-helper.sh - export CODEGEN_UID=$(id -u) - export CODEGEN_GID=$(id -g) + CODEGEN_UID=$(id -u) + CODEGEN_GID=$(id -g) + export CODEGEN_UID CODEGEN_GID scripts/update-codegen-helper.sh exit 0 fi