Skip to content

Commit 0f49f4e

Browse files
feat: initial GitHub Action for harmont pipelines
1 parent 47c3961 commit 0f49f4e

13 files changed

Lines changed: 1409 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,19 @@ jobs:
2525
steps:
2626
- uses: actions/checkout@v4
2727
- name: Install bats
28-
run: |
29-
sudo apt-get update && sudo apt-get install -y bats
28+
run: sudo apt-get update && sudo apt-get install -y bats
3029
- name: Run tests
3130
run: bats tests/*.bats
31+
32+
yaml-lint:
33+
name: Validate action YAML
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v4
37+
- name: Validate all action.yml files
38+
run: |
39+
for f in action.yml setup/action.yml cache-restore/action.yml cache-save/action.yml; do
40+
echo "Validating $f..."
41+
python3 -c "import yaml; yaml.safe_load(open('$f'))"
42+
done
43+
echo "All action YAML files valid."

.github/workflows/test-action.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Test Action
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
permissions:
9+
contents: read
10+
packages: write
11+
12+
jobs:
13+
all-in-one:
14+
name: All-in-one action
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 15
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Run harmont via all-in-one action
21+
uses: ./
22+
with:
23+
pipeline: hello
24+
working-directory: tests/fixtures
25+
cache: 'true'
26+
27+
granular:
28+
name: Granular sub-actions
29+
runs-on: ubuntu-latest
30+
timeout-minutes: 15
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Setup hm
35+
uses: ./setup
36+
with:
37+
version: latest
38+
39+
- name: Restore cache
40+
uses: ./cache-restore
41+
42+
- name: Run pipeline manually
43+
working-directory: tests/fixtures
44+
env:
45+
HM_NONINTERACTIVE: '1'
46+
run: hm run hello
47+
48+
- name: Save cache
49+
if: always()
50+
uses: ./cache-save
51+
52+
setup-only:
53+
name: Setup only (verify install)
54+
runs-on: ubuntu-latest
55+
steps:
56+
- uses: actions/checkout@v4
57+
58+
- name: Setup hm
59+
id: setup
60+
uses: ./setup
61+
with:
62+
version: latest
63+
64+
- name: Verify hm is on PATH
65+
run: |
66+
hm --version
67+
echo "Installed version: ${{ steps.setup.outputs.hm-version }}"
68+
69+
setup-cached:
70+
name: Setup with cache hit (second run)
71+
runs-on: ubuntu-latest
72+
needs: setup-only
73+
steps:
74+
- uses: actions/checkout@v4
75+
76+
- name: Setup hm (should hit cache)
77+
id: setup
78+
uses: ./setup
79+
with:
80+
version: latest
81+
82+
- name: Verify cache was used
83+
run: |
84+
echo "Cache hit: ${{ steps.setup.outputs.cache-hit }}"
85+
hm --version

README.md

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# actions-hm
2+
3+
[![CI](https://img.shields.io/github/actions/workflow/status/harmont-dev/actions-hm/ci.yml?branch=main&logo=github&label=CI)](https://github.com/harmont-dev/actions-hm/actions)
4+
[![GitHub release](https://img.shields.io/github/v/release/harmont-dev/actions-hm?logo=github)](https://github.com/harmont-dev/actions-hm/releases)
5+
[![Marketplace](https://img.shields.io/badge/marketplace-harmont-purple?logo=github)](https://github.com/marketplace/actions/harmont)
6+
7+
Run [harmont](https://harmont.dev) pipelines in GitHub Actions. One step. Automatic Docker image caching via your container registry.
8+
9+
```yaml
10+
- uses: harmont-dev/actions-hm@v1
11+
with:
12+
pipeline: ci
13+
```
14+
15+
That's it. This installs `hm`, pulls cached Docker images from GHCR, runs your pipeline, and pushes updated images back — with automatic cleanup of stale cache entries.
16+
17+
## Why
18+
19+
You already define your CI with harmont. This action lets you run it on GitHub Actions without boilerplate:
20+
21+
- **Zero config caching** — Docker images cached in GHCR with native layer deduplication
22+
- **One step** — no separate setup, login, cache-restore, cache-save dance
23+
- **Fast repeat runs** — `hm` binary cached between runs, images pulled only when changed
24+
- **Auto cleanup** — stale registry images pruned automatically (configurable retention)
25+
- **Granular control** — use sub-actions individually when you need custom steps between them
26+
27+
## Usage
28+
29+
### Minimal (all-in-one)
30+
31+
```yaml
32+
name: CI
33+
34+
on: [push, pull_request]
35+
36+
permissions:
37+
contents: read
38+
packages: write
39+
40+
jobs:
41+
ci:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
- uses: harmont-dev/actions-hm@v1
46+
with:
47+
pipeline: ci
48+
```
49+
50+
### Multiple pipelines
51+
52+
```yaml
53+
jobs:
54+
lint:
55+
runs-on: ubuntu-latest
56+
steps:
57+
- uses: actions/checkout@v4
58+
- uses: harmont-dev/actions-hm@v1
59+
with:
60+
pipeline: lint
61+
62+
test:
63+
runs-on: ubuntu-latest
64+
steps:
65+
- uses: actions/checkout@v4
66+
- uses: harmont-dev/actions-hm@v1
67+
with:
68+
pipeline: test
69+
parallelism: 4
70+
```
71+
72+
### Granular sub-actions
73+
74+
For workflows that need custom steps between setup, cache, and run:
75+
76+
```yaml
77+
jobs:
78+
ci:
79+
runs-on: ubuntu-latest
80+
permissions:
81+
contents: read
82+
packages: write
83+
steps:
84+
- uses: actions/checkout@v4
85+
86+
- uses: harmont-dev/actions-hm/setup@v1
87+
88+
- uses: harmont-dev/actions-hm/cache-restore@v1
89+
90+
- run: |
91+
echo "Custom setup between cache restore and pipeline run"
92+
hm run ci
93+
94+
- uses: harmont-dev/actions-hm/cache-save@v1
95+
if: always()
96+
```
97+
98+
### Pin to specific version
99+
100+
```yaml
101+
- uses: harmont-dev/actions-hm@v1
102+
with:
103+
version: 0.5.0
104+
```
105+
106+
## Inputs
107+
108+
| Input | Default | Description |
109+
|-------|---------|-------------|
110+
| `pipeline` | *(auto)* | Pipeline slug to run. Omit if repo has only one pipeline. |
111+
| `version` | `latest` | `hm` CLI version (`latest` or semver like `0.5.0`) |
112+
| `working-directory` | `.` | Path to repo root where `.harmont/` lives |
113+
| `parallelism` | *(cpu count)* | Max concurrent pipeline chains |
114+
| `cache` | `true` | Enable Docker image caching |
115+
| `cache-registry` | `ghcr.io` | Container registry for image caching |
116+
| `cache-registry-prefix` | *(auto)* | Registry path prefix. Default: `ghcr.io/<owner>/<repo>/harmont-cache` |
117+
| `cache-cleanup` | `true` | Delete stale images from registry after save |
118+
| `cache-cleanup-keep` | `2` | Number of old image versions to keep per step |
119+
| `extra-args` | | Additional arguments passed to `hm run` |
120+
| `token` | `github.token` | GitHub token (needs `packages:write`, `packages:delete` for cleanup) |
121+
122+
## Outputs
123+
124+
| Output | Description |
125+
|--------|-------------|
126+
| `hm-version` | Installed `hm` CLI version |
127+
128+
## Sub-actions
129+
130+
| Action | Purpose |
131+
|--------|---------|
132+
| `harmont-dev/actions-hm/setup@v1` | Install `hm` binary (cached between runs) |
133+
| `harmont-dev/actions-hm/cache-restore@v1` | Pull cached Docker images from registry |
134+
| `harmont-dev/actions-hm/cache-save@v1` | Push Docker images to registry + cleanup |
135+
136+
## How caching works
137+
138+
```
139+
┌─────────────────────────────────────────────────────────────┐
140+
│ GitHub Actions Runner │
141+
│ │
142+
│ 1. Pull manifest:latest from GHCR │
143+
│ 2. Pull each step image (layer dedup = fast) │
144+
│ 3. Re-tag as harmont-local/* so hm recognizes them │
145+
│ 4. hm run ci (uses cached images, skips rebuilds) │
146+
│ 5. Push changed images back to GHCR │
147+
│ 6. Prune images older than cleanup-keep │
148+
│ │
149+
│ Images stored at: │
150+
│ ghcr.io/<owner>/<repo>/harmont-cache/<step>:<hash> │
151+
└─────────────────────────────────────────────────────────────┘
152+
```
153+
154+
**Why GHCR instead of `actions/cache`?**
155+
156+
- No 10 GB size limit (GHCR storage is unlimited for public repos)
157+
- Native Docker layer deduplication — shared base images stored once
158+
- Per-image granularity — only changed images push/pull
159+
- Faster for large images than tar/untar through GHA cache
160+
161+
## Permissions
162+
163+
The action needs `packages:write` on the `GITHUB_TOKEN` to push/pull cache images. For cleanup, it also needs `packages:delete`.
164+
165+
```yaml
166+
permissions:
167+
contents: read
168+
packages: write
169+
```
170+
171+
> **Note:** `packages:delete` is included in `packages:write` for tokens with full `packages` scope. If using a fine-grained PAT, ensure both are granted.
172+
173+
## Migrating from raw workflow steps
174+
175+
If you currently have a manual harmont setup in your workflow:
176+
177+
<details>
178+
<summary>Before (manual setup)</summary>
179+
180+
```yaml
181+
steps:
182+
- uses: actions/checkout@v4
183+
- uses: dtolnay/rust-toolchain@stable
184+
- uses: Swatinem/rust-cache@v2
185+
- run: cargo build -p harmont-cli
186+
- uses: actions/cache/restore@v4
187+
with:
188+
path: .harmont-cache/
189+
key: harmont-v1-will-never-match
190+
restore-keys: harmont-v1-
191+
- run: ./target/debug/hm cache restore .harmont-cache/
192+
- run: ./target/debug/hm run ci
193+
env:
194+
HM_NONINTERACTIVE: '1'
195+
- run: |
196+
hash=$(./target/debug/hm cache save .harmont-cache/)
197+
echo "key=harmont-v1-${hash}" >> "$GITHUB_OUTPUT"
198+
id: cache-manifest
199+
if: always()
200+
- uses: actions/cache/save@v4
201+
if: always()
202+
with:
203+
path: .harmont-cache/
204+
key: ${{ steps.cache-manifest.outputs.key }}
205+
```
206+
207+
</details>
208+
209+
<details>
210+
<summary>After (this action)</summary>
211+
212+
```yaml
213+
steps:
214+
- uses: actions/checkout@v4
215+
- uses: harmont-dev/actions-hm@v1
216+
with:
217+
pipeline: ci
218+
```
219+
220+
</details>
221+
222+
## FAQ
223+
224+
### Do I need Docker on the runner?
225+
226+
Yes. Harmont runs pipeline steps in Docker containers. Use `runs-on: ubuntu-latest` (Docker is pre-installed).
227+
228+
### What about macOS / Windows runners?
229+
230+
macOS runners have Docker available via colima/lima. Windows runners are not currently supported (harmont requires Linux containers).
231+
232+
### Can I use a private registry instead of GHCR?
233+
234+
Yes. Set `cache-registry` to your registry hostname and provide a token with push/pull access:
235+
236+
```yaml
237+
- uses: harmont-dev/actions-hm@v1
238+
with:
239+
pipeline: ci
240+
cache-registry: registry.example.com
241+
token: ${{ secrets.REGISTRY_TOKEN }}
242+
```
243+
244+
### How do I disable caching entirely?
245+
246+
```yaml
247+
- uses: harmont-dev/actions-hm@v1
248+
with:
249+
pipeline: ci
250+
cache: 'false'
251+
```
252+
253+
### How do I force a clean cache rebuild?
254+
255+
Delete the `harmont-cache` packages from your repo's GitHub Packages, or change `cache-registry-prefix` to a new path.
256+
257+
### The first run is slow — is that expected?
258+
259+
Yes. The first run has no cached images, so Docker pulls base images and builds from scratch. Subsequent runs reuse cached images and are significantly faster.
260+
261+
### What permissions does cleanup need?
262+
263+
`packages:delete` (part of the `packages: write` scope). If your token lacks this, set `cache-cleanup: 'false'` — images accumulate but nothing breaks.
264+
265+
## License
266+
267+
MIT

0 commit comments

Comments
 (0)