Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 100 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
workflow_dispatch:
pull_request:

env:
IMAGE: patternoss/heimdall

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -13,5 +16,101 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: 1.24.5
# setup-go caches the module + build cache keyed on go.sum by default.
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ~/.local/share/pnpm/store
key: ${{ runner.os }}-pnpm-${{ hashFiles('web/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Build and Test
run: ./build.sh
run: ./build.sh --go --ui --test

# Verify the image builds end-to-end (no push) so Dockerfile breakage is caught on PRs.
# Each arch builds on its OWN native runner (no QEMU emulation), in parallel.
docker-build:
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
platforms: ${{ matrix.platform }}
build-args: |
BUILD_VERSION=ci
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}

- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digest-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

# Stitch: collect the per-arch digests and assemble a single throwaway multi-arch tag.
merge:
runs-on: ubuntu-latest
needs: docker-build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Determine throwaway tag
id: tagger
run: echo "tag=ci-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"

- name: Create and push manifest list
working-directory: /tmp/digests
run: |
docker buildx imagetools create -t ${{ env.IMAGE }}:${{ steps.tagger.outputs.tag }} \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)

- name: Inspect
run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.tagger.outputs.tag }}
7 changes: 6 additions & 1 deletion .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ jobs:
fi
- name: Build and push Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
push: true
tags: patternoss/heimdall:${{ env.DOCKER_TAG }}
platforms: linux/amd64,linux/arm64
cache-from: type=registry,ref=patternoss/heimdall:buildcache
cache-to: type=registry,ref=patternoss/heimdall:buildcache,mode=max
53 changes: 46 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
FROM golang:1.24.6
FROM golang:1.24.6 AS go-builder

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
binutils \
binutils-gold \
pkg-config \
&& rm -rf /var/lib/apt/lists/*

RUN apt-get update && apt-get install -y nodejs awscli jq
RUN apt-get update && apt-get install -y --no-install-recommends build-essential binutils binutils-gold pkg-config && rm -rf /var/lib/apt/lists/*
WORKDIR /go/src/github.com/patterninc/heimdall

COPY . .
COPY go.mod go.sum ./
RUN go mod download

COPY build.sh ./
COPY cmd ./cmd
COPY internal ./internal
COPY pkg ./pkg
COPY plugins ./plugins

# Declared right before its only consumer so it invalidates as little as possible.
ARG BUILD_VERSION=dev
RUN BUILD_VERSION="${BUILD_VERSION}" ./build.sh --go

FROM node:20-bookworm AS web-builder

WORKDIR /app

COPY web/pnpm-lock.yaml web/pnpm-workspace.yaml ./web/
RUN corepack enable && corepack prepare pnpm@10.28.2 --activate && (cd web && pnpm fetch)

COPY build.sh ./
COPY web/ ./web/
RUN ./build.sh --ui

FROM node:20-bookworm-slim AS runtime

RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
awscli \
jq \
&& corepack enable && corepack prepare pnpm@10.28.2 --activate \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /go/src/github.com/patterninc/heimdall

# set config file
# Rarely-changing runtime files first, frequently-changing build outputs last.
COPY configs/local.yaml /etc/heimdall/heimdall.yaml
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
# build executables
RUN ./build.sh
COPY assets ./assets
COPY --from=go-builder /go/src/github.com/patterninc/heimdall/dist ./dist
COPY --from=web-builder /app/web ./web

CMD [ "/usr/local/bin/entrypoint.sh" ]
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ GET /api/v1/job/<job_id>/stderr

---

## 🛠️ Building from Source

`build.sh` compiles the Go binaries + plugins and builds the web UI. It builds **only what you ask for** via flags (no defaults):

```bash
./build.sh --go # build Go binaries (cmd/*) + plugins (plugins/*.so)
./build.sh --ui # build the Next.js web UI
./build.sh --test # run the Go test suites
./build.sh --go --ui --test # do everything (what CI runs)
```

Passing no flag builds nothing. Unknown flags fail fast. Outputs land in `dist/` (binaries in `dist/`, plugins in `dist/plugins/`).

---

## 🔌 Supported Plugins

Heimdall supports a growing set of pluggable command types:
Expand Down
44 changes: 32 additions & 12 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,45 @@ set -ex
BUILD_VERSION=$(git describe --tags --abbrev=0 --match "v*" | cut -dv -f2)
WORKING_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
OUTPUT_DIR="${WORKING_DIR}/dist"
COMMANDS=$(ls ${WORKING_DIR}/cmd)
PLUGINS=$(ls ${WORKING_DIR}/plugins)
LDFLAGS="-X main.Build=${BUILD_VERSION}"
BUILD_GO=false
BUILD_WEB=false
RUN_TESTS=false
for arg in "$@"; do
case "${arg}" in
--go) BUILD_GO=true ;;
--ui) BUILD_WEB=true ;;
--test) RUN_TESTS=true ;;
*) echo "unknown flag: ${arg} (use --go, --ui, --test)" >&2; exit 1 ;;
esac
done

# reset output dir
rm -rf ${OUTPUT_DIR} > /dev/null 2>&1
mkdir -p ${OUTPUT_DIR}/web ${OUTPUT_DIR}/plugins

# go environment
(cd ${WORKING_DIR} && go version)
if [ "${RUN_TESTS}" = "true" ]; then
echo "running tests"
(cd ${WORKING_DIR} && go test ./internal/pkg/... && go test ./pkg/...)
fi

# run tests
(cd ${WORKING_DIR} && go test ./internal/pkg/... && go test ./pkg/...)
if [ "${BUILD_GO}" = "true" ]; then
COMMANDS=$(ls ${WORKING_DIR}/cmd)
PLUGINS=$(ls ${WORKING_DIR}/plugins)

# build commands
(cd ${WORKING_DIR} && for item in ${COMMANDS}; do go build -ldflags "${LDFLAGS}" -o dist/$item cmd/$item/$item.go; done)

# build plugins
(cd ${WORKING_DIR} && for item in ${PLUGINS}; do go build -buildmode=plugin -ldflags "${LDFLAGS}" -o dist/plugins/$item.so plugins/$item/$item.go; done)

# build web
(cd ${WORKING_DIR}/web && rm -rf node_modules > /dev/null 2>&1 && corepack enable && pnpm install --frozen-lockfile --ignore-scripts && pnpm run build)
# go environment
(cd ${WORKING_DIR} && go version)

# build commands
(cd ${WORKING_DIR} && echo "${COMMANDS}" | xargs -P 4 -I {} bash -c 'echo "building command: {}" && go build -ldflags "${LDFLAGS}" -o dist/{} cmd/{}/{}.go')

# build plugins
(cd ${WORKING_DIR} && echo "${PLUGINS}" | xargs -P 4 -I {} bash -c 'echo "building plugin: {}" && go build -buildmode=plugin -ldflags "${LDFLAGS}" -o dist/plugins/{}.so plugins/{}/{}.go')
fi

if [ "${BUILD_WEB}" = "true" ]; then
# build web
(cd ${WORKING_DIR}/web && corepack enable && pnpm install --frozen-lockfile --ignore-scripts && pnpm run build)
fi
Loading