diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ae687c8b..65394c01 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,6 +10,10 @@ on: pull_request: branches: [main] +permissions: + contents: read + packages: write + concurrency: group: pr-checks-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/.github/workflows/DOCKER-PROD.yml b/.github/workflows/DOCKER-PROD.yml index eca5614b..fd07c64f 100644 --- a/.github/workflows/DOCKER-PROD.yml +++ b/.github/workflows/DOCKER-PROD.yml @@ -23,7 +23,7 @@ on: jobs: build-test-push: runs-on: - labels: bare-metal + group: DH-runners outputs: image-tag: ${{ steps.last_tag_extractor.outputs.last_tag_value }} defaults: diff --git a/.github/workflows/task-build-operator.yml b/.github/workflows/task-build-operator.yml index 8a7c1343..6ca71e68 100644 --- a/.github/workflows/task-build-operator.yml +++ b/.github/workflows/task-build-operator.yml @@ -16,7 +16,7 @@ jobs: binary-hash: ${{ steps.hash-binary.outputs.datahaven-node-hash }} name: Build operator binary runs-on: - labels: bare-metal + group: DH-runners env: RUSTC_WRAPPER: "sccache" CARGO_INCREMENTAL: "0" diff --git a/.github/workflows/task-docker.yml b/.github/workflows/task-docker.yml index 857e1060..2a5f512d 100644 --- a/.github/workflows/task-docker.yml +++ b/.github/workflows/task-docker.yml @@ -18,6 +18,10 @@ on: description: "The tag portion of the docker image (without registry)" value: "${{ jobs.build-test-push.outputs.image-tag }}" +permissions: + contents: read + packages: write + concurrency: group: docker-build-${{ github.ref }} cancel-in-progress: true @@ -46,10 +50,11 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: datahavenxyz/datahaven + images: ghcr.io/datahaven-xyz/datahaven/datahaven flavor: | latest=auto tags: | + type=raw,value=ci-${{ github.run_id }} type=sha,format=short,prefix=sha- type=ref,event=tag type=ref,event=branch @@ -61,7 +66,7 @@ jobs: FULL_TAG=$(echo '${{ steps.meta.outputs.json }}' | jq -r '.tags[-1]') TAG_ONLY=$(echo "$FULL_TAG" | sed 's|.*:||') echo "image-tag=$TAG_ONLY" >> $GITHUB_OUTPUT - echo "image-name=datahavenxyz/datahaven:$TAG_ONLY" >> $GITHUB_OUTPUT + echo "image-name=ghcr.io/datahaven-xyz/datahaven/datahaven:$TAG_ONLY" >> $GITHUB_OUTPUT - name: Log Docker Metadata run: | @@ -79,10 +84,12 @@ jobs: --allow-insecure-entitlement network.host --allow-insecure-entitlement security.insecure - - uses: docker/login-action@v3 + - name: Log in to GHCR + uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image id: build diff --git a/.github/workflows/task-e2e.yml b/.github/workflows/task-e2e.yml index 54016a2a..e10d95e8 100644 --- a/.github/workflows/task-e2e.yml +++ b/.github/workflows/task-e2e.yml @@ -22,13 +22,22 @@ on: required: true type: string +permissions: + contents: read + packages: read + env: FOUNDRY_PROFILE: ci LOG_LEVEL: debug + DOCKER_HOST: unix:///run/user/1020/podman/podman.sock + KURTOSIS_CORE_IMAGE: ghcr.io/stiiifff/kurtosis/kurtosis-core + KURTOSIS_ENGINE_IMAGE: ghcr.io/stiiifff/kurtosis/kurtosis-engine + KURTOSIS_VERSION: 1.11.1 jobs: kurtosis: - runs-on: ubuntu-latest + runs-on: + group: DH-Testing name: E2E Tests with Kurtosis Ethereum Network defaults: run: @@ -40,20 +49,55 @@ jobs: - uses: oven-sh/setup-bun@v2 - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Pull Kurtosis images + run: | + docker pull ${{ env.KURTOSIS_CORE_IMAGE }}:${{ env.KURTOSIS_VERSION }} + docker pull ${{ env.KURTOSIS_ENGINE_IMAGE }}:${{ env.KURTOSIS_VERSION }} - name: Install Kurtosis run: | - echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list - sudo apt update - sudo apt install kurtosis-cli + # Install Kurtosis locally without sudo + if ! command -v kurtosis &> /dev/null; then + echo "Installing Kurtosis $KURTOSIS_VERSION locally" + mkdir -p ~/.local/bin + # wget -q -O kurtosis-cli.tar.gz "https://github.com/kurtosis-tech/kurtosis-cli-release-artifacts/releases/download/${KURTOSIS_VERSION}/kurtosis-cli_${KURTOSIS_VERSION}_linux_amd64.tar.gz" + # tar -xzf kurtosis-cli.tar.gz -C ~/.local/bin + # rm kurtosis-cli.tar.gz + # For now, we use a patched version of Kurtosis CLI & Engine that supports Podman properly + wget -q -O ~/.local/bin/kurtosis https://github.com/stiiifff/kurtosis/releases/download/1.11.99/kurtosis + echo "$HOME/.local/bin" >> $GITHUB_PATH + export PATH="$HOME/.local/bin:$PATH" + else + echo "Kurtosis is already installed: $(kurtosis version)" + fi kurtosis analytics disable - - run: kurtosis version + kurtosis version + - name: Configure Kurtosis cluster = podman + run: | + # Get the config path from Kurtosis itself (portable) + CFG_PATH="$(kurtosis config path)" + mkdir -p "$(dirname "$CFG_PATH")" + # Create/update config with a podman cluster entry + cat > "$CFG_PATH" <<'YML' + config-version: 6 + should-send-metrics: true + kurtosis-clusters: + podman: + type: "podman" + YML + kurtosis cluster set podman + kurtosis cluster get + - name: Start Kurtosis engine with Podman + run: | + kurtosis engine stop + kurtosis clean + kurtosis engine start + kurtosis engine status - uses: actions/cache@v4 with: path: ~/.bun/install/cache key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }} restore-keys: | ${{ runner.os }}-bun- - - name: Cache Foundry libraries uses: actions/cache/restore@v4 with: @@ -73,23 +117,28 @@ jobs: ${{ runner.os }}-foundry-build- - uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Download snowbridge binary + - name: Download Snowbridge relay binary run: | docker create --name temp datahavenxyz/snowbridge-relay:latest mkdir -p tmp/bin docker cp temp:/usr/local/bin/snowbridge-relay tmp/bin/ chmod +x tmp/bin/snowbridge-relay docker rm temp - - run: tmp/bin/snowbridge-relay --help - - run: docker pull datahavenxyz/datahaven:${{ inputs.image-tag }} - - run: | - docker tag datahavenxyz/datahaven:${{ inputs.image-tag }} datahavenxyz/datahaven:local - docker images + tmp/bin/snowbridge-relay --help + + - name: Pull DataHaven node image + run: | + docker pull ghcr.io/datahaven-xyz/datahaven/datahaven:${{ inputs.image-tag }} + docker tag ghcr.io/datahaven-xyz/datahaven/datahaven:${{ inputs.image-tag }} datahavenxyz/datahaven:local + - run: bun install - - run: bun test:e2e + - name: Run E2E tests + run: bun test:e2e + # Try to collect all docker logs and upload it - name: Collect docker logs if: always() diff --git a/.github/workflows/task-rust-lint.yml b/.github/workflows/task-rust-lint.yml index 052b6ce6..6b9f7923 100644 --- a/.github/workflows/task-rust-lint.yml +++ b/.github/workflows/task-rust-lint.yml @@ -25,7 +25,7 @@ jobs: cargo-fmt: name: "Check format with rustfmt" runs-on: - labels: bare-metal + group: DH-runners defaults: run: working-directory: ${{ env.WORKING_DIR }} @@ -44,7 +44,7 @@ jobs: check-rust-lint: name: "Check lint with clippy" runs-on: - labels: bare-metal + group: DH-runners defaults: run: working-directory: ${{ env.WORKING_DIR }} @@ -62,7 +62,7 @@ jobs: check-cargo-sort: name: "Check Cargo sort" runs-on: - labels: bare-metal + group: DH-runners defaults: run: diff --git a/.github/workflows/task-rust-tests.yml b/.github/workflows/task-rust-tests.yml index 6df559f3..8e2adce2 100644 --- a/.github/workflows/task-rust-tests.yml +++ b/.github/workflows/task-rust-tests.yml @@ -17,7 +17,7 @@ jobs: prepare: name: Prepare artifacts for Rust tests runs-on: - labels: bare-metal + group: DH-runners env: RUSTC_WRAPPER: "sccache" CARGO_INCREMENTAL: "0" @@ -57,7 +57,7 @@ jobs: name: Run all Operator Rust tests (/w partitioning) needs: [prepare] runs-on: - labels: bare-metal + group: DH-runners strategy: fail-fast: false matrix: @@ -91,7 +91,7 @@ jobs: name: Check tests were successful needs: [all-rust-tests] runs-on: - labels: bare-metal + group: DH-runners steps: - name: Cleanup test artifacts uses: geekyeggo/delete-artifact@v5 diff --git a/test/launcher/utils/constants.ts b/test/launcher/utils/constants.ts index ff866393..093b66bc 100644 --- a/test/launcher/utils/constants.ts +++ b/test/launcher/utils/constants.ts @@ -25,7 +25,7 @@ export const COMPONENTS = { optionName: "datahaven" }, snowbridge: { - imageName: "snowbridge-relay", + imageName: "datahavenxyz/snowbridge-relay", componentName: "Snowbridge Relayers", optionName: "relayer" } diff --git a/test/utils/docker.ts b/test/utils/docker.ts index 665f541b..e5e57888 100644 --- a/test/utils/docker.ts +++ b/test/utils/docker.ts @@ -1,12 +1,72 @@ +import { existsSync } from "node:fs"; import { type Duplex, PassThrough, Transform } from "node:stream"; import Docker from "dockerode"; import invariant from "tiny-invariant"; import { logger, type ServiceInfo, StandardServiceMappings } from "utils"; -const docker = new Docker({}); +function createDockerConnection(): Docker { + const dockerHost = process.env.DOCKER_HOST; + + if (dockerHost) { + logger.debug(`Using DOCKER_HOST: ${dockerHost}`); + if (dockerHost.startsWith("unix://")) { + return new Docker({ socketPath: dockerHost.replace("unix://", "") }); + } + if (dockerHost.startsWith("tcp://")) { + const url = new URL(dockerHost); + return new Docker({ + host: url.hostname, + port: Number.parseInt(url.port) || 2375, + protocol: "http" + }); + } + } + + const socketPaths = [ + "/var/run/docker.sock", + "/run/user/1000/docker.sock", + `${process.env.HOME}/.docker/run/docker.sock` + ]; + + for (const socketPath of socketPaths) { + try { + if (existsSync(socketPath)) { + logger.debug(`Using Docker socket: ${socketPath}`); + return new Docker({ socketPath }); + } + } catch (error) { + logger.debug(`Failed to access socket ${socketPath}:`, error); + } + } + + logger.debug("Falling back to default Docker configuration"); + return new Docker({}); +} + +const docker = createDockerConnection(); + +async function testDockerConnection(): Promise { + try { + await docker.ping(); + logger.debug("Docker connection successful"); + } catch (error) { + logger.error("Docker connection failed:", error); + throw new Error( + `Failed to connect to Docker daemon: ${error instanceof Error ? error.message : String(error)}` + ); + } +} export const getServicesFromDocker = async (): Promise => { - const containers = await docker.listContainers(); + let containers: Docker.ContainerInfo[]; + try { + containers = await docker.listContainers(); + } catch (error) { + logger.error("Failed to list containers:", error); + await testDockerConnection(); + throw error; + } + const services: ServiceInfo[] = []; for (const mapping of StandardServiceMappings) { @@ -83,7 +143,6 @@ export const getPublicPort = async ( containerName: string, internalPort: number ): Promise => { - const docker = new Docker(); const containers = await docker.listContainers(); const container = containers.find((container) => container.Names.some((name) => name.includes(containerName)) @@ -167,7 +226,6 @@ export const waitForContainerToStart = async ( options?: { timeoutSeconds?: number } ) => { logger.debug(`Waiting for container ${containerName} to start...`); - const docker = new Docker(); const seconds = options?.timeoutSeconds ?? 30; for (let i = 0; i < seconds; i++) {