Skip to content

Commit 88093f8

Browse files
NucleoFusionbupd
andauthored
Feature: Moving to pure Dagger based CI/CD Pipeline from Goreleaser (#547)
Co-authored-by: Prasanth Baskar <bupdprasanth@gmail.com>
1 parent 6af5f11 commit 88093f8

21 files changed

Lines changed: 1451 additions & 671 deletions

File tree

.dagger/README.md

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,103 @@
1-
# 🛠️ Harbor CLI Dagger Pipeline
1+
# 🛠️ Harbor CLI Dagger Pipeline
22

3-
We use [Dagger](https://dagger.io) to define a CI/CD pipeline for building, linting, and publishing the [Harbor CLI](https://github.com/goharbor/harbor-cli).
4-
This README will help beginners understand how to use Dagger in local development and CI workflows.
3+
We use [Dagger](https://dagger.io) to define a **modular and reproducible CI/CD pipeline** for building, linting, testing, and publishing the [Harbor CLI](https://github.com/goharbor/harbor-cli).
4+
This README provides a clear reference for contributors and maintainers to understand, run, and extend the pipeline locally or in CI.
55

6-
## Prerequisites
7-
8-
Before you start, ensure you have the following:
9-
10-
1. Dagger: Install the latest version of Dagger. You can check the official documentation for installation steps: [Dagger Installation Guide](https://docs.dagger.io/install).
11-
12-
## Dagger Setup and Development Mode
6+
---
137

14-
### Run Dagger Develop
8+
## 🚧 Prerequisites
159

16-
```bash
17-
dagger develop
18-
```
10+
Before using the pipeline, make sure you have:
1911

20-
This command will generate the necessary files and configuration for building and running Dagger.
12+
1. **Dagger CLI** — Install the latest version from the official docs:
13+
👉 [Dagger Installation Guide](https://docs.dagger.io/install)
14+
2. **Go** — Installed according to the version specified in the project’s `go.mod`.
15+
3. **Docker** — Required if you’re publishing images.
2116

17+
---
2218

23-
## 📦 Dagger Functions Explained
19+
## ⚙️ Setup and Development Mode
2420

25-
### 🔧 `BuildDev(platform)`
21+
### Run Dagger in Development Mode
2622

27-
Builds a development binary for your target platform.
23+
To start the Dagger session and enable live code reloads:
2824

2925
```bash
30-
dagger call build-dev --platform="linux/amd64" export --path=bin/harbor-dev
26+
dagger develop
3127
```
3228

33-
### 🧼 `LintReport()`
29+
This command prepares the environment for pipeline development and local testing.
3430

35-
Runs `golangci-lint` on your code and saves the report to a file.
31+
## 📦 Dagger Functions Overview
3632

37-
```bash
38-
dagger call lint-report export --path=./LintReport.json
39-
```
33+
| **Name** | **Description** |
34+
|--------------------------------|-------------------------------------------------------------------------------------------------|
35+
| `lint` | Runs `golangci-lint` and prints the report as a string to stdout. |
36+
| `lint-report` | Runs `golangci-lint` and writes the lint report to a file. |
37+
| `pipeline` | Executes the **full CI/CD pipeline** including build, test, lint, and publish stages. |
38+
| `run-doc` | Generates CLI documentation and returns the directory containing generated files. |
39+
| `test` | Runs all Go tests in the repository. |
40+
| `test-report` | Executes Go tests and outputs a structured JSON test report. |
41+
| `test-coverage` | Runs Go tests with coverage tracking. |
42+
| `test-coverage-report` | Processes coverage data and returns a formatted Markdown report. |
43+
| `vulnerability-check` | Runs `govulncheck` to detect known vulnerabilities in dependencies. |
44+
| `vulnerability-check-report` | Runs `govulncheck` and saves results to a file (`vulnerability-check.report`). |
45+
| `build-dev` | Create build of Harbor CLI for local testing and development|
4046

41-
### 📝 `TestCoverageReport()`
47+
---
4248

43-
Runs go test coverage tools and creates a report.
44-
```bash
45-
dagger call test-coverage-report export --path=coverage-report.md
46-
```
49+
## 🧩 Example Usage
4750

48-
### `CheckCoverageThreshold(context, threshold)`
51+
Below are some common commands to run specific Dagger functions locally:
4952

50-
Runs go test coverage tools and creates a report. The total coverage is compared to a threshold that can be set to e.g. 80%.
5153
```bash
52-
dagger call check-coverage-threshold --threshold 80.0
53-
```
54+
# Development build for binaries
5455

55-
### 🚀 `PublishImage(registry, imageTags)`
56+
dagger call build-dev --source=. --platform="linux/amd64" export --path=bin/harbor-dev
5657

57-
Builds and publishes the Harbor CLI image to the given container registry with proper OCI metadata labels.
58+
# Print report to stdout
59+
dagger call lint
5860

59-
Before running the command you have to export you registry password
61+
# Save report to a file
62+
dagger call lint-report export --path=LintReport.json
6063

61-
```shell
62-
export REGPASS=Harbor12345
63-
```
64+
# Run Tests
65+
dagger call test
6466

65-
```bash
66-
dagger call publish-image \
67-
--registry=demo.goharbor.io \
68-
--registry-username=harbor-cli \
69-
--registry-password=env:REGPASS \
70-
--imageTags=v0.1.0,latest
71-
```
67+
# Generate a JSON Report
68+
dagger call test-report export --path=TestReport.json
7269

73-
---
70+
# Test Coverage
71+
dagger call test-coverage
7472

75-
## ⚙️ Configuration Constants
73+
# Generate a Markdown Report
74+
dagger call test-coverage-report export --path=coverage-report.md
7675

77-
Dagger uses these constant versions (you can modify them as needed):
76+
# Vulnerability Check
77+
dagger call vulnerability-check
7878

79-
```go
80-
const (
81-
GO_VERSION = "1.24.2"
82-
GOLANGCILINT_VERSION = "v2.1.2"
83-
SYFT_VERSION = "v1.9.0"
84-
GORELEASER_VERSION = "v2.3.2"
85-
)
79+
# Generate a Report
80+
dagger call vulnerability-check-report export --path=vuln.report
81+
82+
# Generate CLI docs
83+
dagger call run-doc export --path=docs/cli
8684
```
8785

88-
---
8986

90-
## 💡 Tips for Beginners
87+
## 💡 Tips for Contributors
9188

92-
- Every container step is **reproducible** you can build locally or in GitHub Actions without changes.
93-
- Use Dagger to cache Go builds and lint output, speeding up re-runs.
89+
- Every step in Dagger is **deterministic and reproducible** — what you run locally is identical to CI.
90+
- Use Dagger’s built-in caching to accelerate Go builds, lint runs, and dependency installs.
91+
- Modular functions let you run only what you need, improving iteration speed and debugging efficiency.
92+
- Prefer using `dagger develop` for fast iteration and testing new steps before committing.
93+
- Store output reports (lint, test, coverage) under a consistent `/reports` directory for easier CI integration.
94+
- If you modify or add new pipeline steps, document them under **Dagger Functions Overview** to maintain clarity.
95+
- Always validate pipelines with `dagger call pipeline` locally before merging into main.
9496

9597
---
9698

9799
## 📚 References
98100

99-
- [Dagger Go SDK Docs](https://pkg.go.dev/dagger.io/dagger)
100-
- [golangci-lint](https://golangci-lint.run/)
101-
- [Goreleaser](https://goreleaser.com/)
101+
- [Dagger Go SDK](https://pkg.go.dev/dagger.io/dagger)
102+
- [golangci-lint Docs](https://golangci-lint.run/)
103+
- [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck)

.dagger/apk.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"dagger/harbor-cli/internal/dagger"
8+
)
9+
10+
func (m *HarborCli) Apk(ctx context.Context,
11+
buildDir *dagger.Directory,
12+
// +ignore=[".gitignore"]
13+
// +defaultPath="."
14+
source *dagger.Directory,
15+
) (*dagger.Directory, error) {
16+
if !m.IsInitialized {
17+
err := m.init(ctx, source)
18+
if err != nil {
19+
return nil, err
20+
}
21+
}
22+
23+
buildfile := dag.File("APKBUILD", apkbuild(m.AppVersion))
24+
25+
archs := []struct {
26+
Arch string
27+
ApkArch string
28+
}{
29+
{"arm64", "aarch64"},
30+
{"amd64", "x86_64"},
31+
}
32+
33+
for _, arch := range archs {
34+
filename := fmt.Sprintf("bin/harbor-cli_%s_linux_%s", m.AppVersion, arch.Arch)
35+
binary := buildDir.File(filename)
36+
37+
apk := dag.Container(dagger.ContainerOpts{
38+
Platform: dagger.Platform(fmt.Sprintf("linux/%s", arch.Arch)),
39+
}).
40+
From("alpine:3.19").
41+
WithExec([]string{"apk", "add", "--no-cache", "alpine-sdk", "abuild"}).
42+
WithWorkdir("/build").
43+
WithFile("/build/harbor-cli", binary).
44+
WithFile("/build/APKBUILD", buildfile).
45+
46+
// create builder user + abuild group
47+
WithExec([]string{"adduser", "-D", "builder"}).
48+
WithExec([]string{"addgroup", "builder", "abuild"}).
49+
WithExec([]string{"chown", "-R", "builder:builder", "/build"}).
50+
51+
// switch to builder FIRST
52+
WithUser("builder").
53+
WithEnvVariable("HOME", "/home/builder").
54+
55+
// generate signing key AS BUILDER (this is critical)
56+
WithExec([]string{"abuild-keygen", "-a", "-n"}).
57+
58+
// switch back to root ONLY to trust the public key
59+
WithUser("root").
60+
WithExec([]string{
61+
"sh", "-c",
62+
"mkdir -p /etc/apk/keys && cp /home/builder/.abuild/*.rsa.pub /etc/apk/keys/",
63+
}).
64+
65+
// back to builder for the build
66+
WithUser("builder").
67+
WithEnvVariable("HOME", "/home/builder").
68+
69+
// sanity check (keep this until stable)
70+
WithExec([]string{"sh", "-c", "ls -l ~/.abuild && echo HOME=$HOME"}).
71+
72+
// run abuild
73+
WithExec([]string{"abuild", "-rd"})
74+
75+
apkFile := apk.
76+
Directory("/home/builder/packages").
77+
Directory(arch.ApkArch).
78+
File(fmt.Sprintf("harbor-cli-%s-r0.apk", m.AppVersion))
79+
80+
apkFileName := fmt.Sprintf("apk/harbor-cli_%s_%s.apk", m.AppVersion, arch.Arch)
81+
buildDir = buildDir.WithFile(apkFileName, apkFile)
82+
}
83+
84+
return buildDir, nil
85+
}
86+
87+
func apkbuild(ver string) string {
88+
return fmt.Sprintf(`# APKBUILD
89+
pkgname=harbor-cli
90+
pkgver=%s
91+
pkgrel=0
92+
pkgdesc="Harbor CLI — a command-line interface for interacting with your Harbor container registry."
93+
url="https://github.com/goharbor/harbor-cli"
94+
arch="x86_64 aarch64"
95+
license="Apache-2.0"
96+
depends=""
97+
makedepends=""
98+
source=""
99+
maintainer="NucleoFusion <lakshit.singh.mail@gmail.com>"
100+
builddir="/build"
101+
102+
package() {
103+
install -Dm755 "$builddir/harbor-cli" \
104+
"$pkgdir/usr/bin/harbor-cli"
105+
}
106+
`, ver)
107+
}

.dagger/apt.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"dagger/harbor-cli/internal/dagger"
8+
)
9+
10+
func (m *HarborCli) AptBuild(ctx context.Context,
11+
buildDir *dagger.Directory,
12+
// +ignore=[".gitignore"]
13+
// +defaultPath="."
14+
source *dagger.Directory,
15+
token *dagger.Secret,
16+
) error {
17+
if !m.IsInitialized {
18+
err := m.init(ctx, source)
19+
if err != nil {
20+
return err
21+
}
22+
}
23+
24+
archs := []string{"amd64", "arm64"}
25+
root := dag.Directory()
26+
root = root.WithDirectory("pool/main/m", buildDir.Directory("deb"))
27+
githubToken, err := token.Plaintext(ctx)
28+
if err != nil {
29+
return err
30+
}
31+
32+
// Base container
33+
container := dag.Container().
34+
From("debian:bookworm-slim").
35+
WithExec([]string{"apt-get", "update"}).
36+
WithExec([]string{"apt-get", "install", "-y", "dpkg-dev", "gzip", "git"}).
37+
WithEnvVariable("GH_TOKEN", githubToken).
38+
WithMountedDirectory("/repo", root).
39+
WithWorkdir("/repo")
40+
41+
// Building `Package` file for each arch
42+
for _, arch := range archs {
43+
pkgDir := fmt.Sprintf("buildDirs/stable/main/binary-%s", arch)
44+
poolDir := "pool/main/m"
45+
46+
container = container.WithExec([]string{
47+
"bash", "-c",
48+
fmt.Sprintf("mkdir -p %s && dpkg-scanpackages -a %s %s /dev/null > %s/Packages && gzip -9c %s/Packages > %s/Packages.gz && rm -rf %s/Packages",
49+
pkgDir, arch, poolDir, pkgDir, pkgDir, pkgDir, pkgDir),
50+
})
51+
}
52+
53+
// Release File
54+
container = container.WithExec([]string{
55+
"bash", "-c",
56+
`cat <<EOF > /repo/buildDirs/stable/Release
57+
Origin: https://github.com/goharbor/harbor-cli
58+
Label: HarborCLI
59+
Suite: stable
60+
Codename: stable
61+
Architectures: amd64 arm64
62+
Components: main
63+
Description: Harbor CLI — a command-line interface for interacting with your Harbor container registry.
64+
EOF`,
65+
})
66+
67+
container = container.
68+
WithWorkdir("/repo").
69+
WithExec([]string{
70+
"bash", "-c",
71+
fmt.Sprintf(`
72+
set -e
73+
cd /repo
74+
75+
git init
76+
git remote add origin https://x-access-token:$GH_TOKEN@github.com/nucleofusion/harbor-cli.git
77+
git checkout -B gh-pages || git checkout --orphan gh-pages
78+
79+
git config user.name "github-actions[bot]"
80+
git config user.email "github-actions[bot]@users.noreply.github.com"
81+
82+
git add buildDirs pool
83+
84+
git commit -m "Update APT repo for %s" || echo "No changes to commit"
85+
git push origin gh-pages -f
86+
`, m.AppVersion),
87+
})
88+
89+
_, err = container.Sync(ctx)
90+
if err != nil {
91+
return fmt.Errorf("failed to run container: %w", err)
92+
}
93+
94+
return nil
95+
}
96+
97+
// GH-PAGES Structure
98+
//
99+
// /
100+
// ├── dist/
101+
// │ └── stable/
102+
// │ ├── Release
103+
// │ └── main/
104+
// │ ├── binary-amd64/
105+
// │ │ └── Packages.gz
106+
// │ └── binary-arm64/
107+
// │ └── Packages.gz
108+
// └── pool/
109+
// └── main/
110+
// └── m/
111+
// ├── myapp_1.0.0_amd64.deb
112+
// └── myapp_1.0.0_arm64.deb
113+
//

0 commit comments

Comments
 (0)