diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 42230189b..0e80884bd 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -89,3 +89,12 @@ jobs: ${{ runner.os }}-go- - name: make build run: make build + + dagger-build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Install Dagger + run: curl -fsSL https://dl.dagger.io/dagger/install.sh | sudo BIN_DIR=/usr/local/bin sh + - name: Dagger build + run: make dagger-build diff --git a/Makefile b/Makefile index c77db3ddc..94a45ca10 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,10 @@ build: -o bin/replicated \ cli/main.go +.PHONY: dagger-build +dagger-build: + dagger call build --progress plain export --path bin/replicated + .PHONY: release release: dagger call release \ diff --git a/dagger/build.go b/dagger/build.go new file mode 100644 index 000000000..bc848269a --- /dev/null +++ b/dagger/build.go @@ -0,0 +1,39 @@ +package main + +import ( + "context" + "dagger/replicated/internal/dagger" +) + +// Build compiles the replicated CLI binary. +func (r *Replicated) Build( + ctx context.Context, + + // +defaultPath="./" + source *dagger.Directory, +) (*dagger.File, error) { + image, err := goImage(ctx, source) + if err != nil { + return nil, err + } + goModCache, goBuildCache, err := goCacheVolumes(ctx, source) + if err != nil { + return nil, err + } + + binary := dag.Container(dagger.ContainerOpts{ + Platform: "linux/amd64", + }). + From(image). + WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). + WithoutFile("/go/src/github.com/replicatedhq/replicated/bin/replicated"). + WithWorkdir("/go/src/github.com/replicatedhq/replicated"). + WithMountedCache("/go/pkg/mod", goModCache). + WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). + WithMountedCache("/go/build-cache", goBuildCache). + WithEnvVariable("GOCACHE", "/go/build-cache"). + With(CacheBustingExec([]string{"make", "build"})). + File("/go/src/github.com/replicatedhq/replicated/bin/replicated") + + return binary, nil +} diff --git a/dagger/docs.go b/dagger/docs.go index 35d248606..13459ff83 100644 --- a/dagger/docs.go +++ b/dagger/docs.go @@ -61,12 +61,18 @@ func (r *Replicated) GenerateDocs( } // Generate CLI new docs - goModCache := dag.CacheVolume("replicated-go-mod-122") - goBuildCache := dag.CacheVolume("replicated-go-build-121") + image, err := goImage(ctx, source) + if err != nil { + return errors.Wrap(err, "failed to detect go version") + } + goModCache, goBuildCache, err := goCacheVolumes(ctx, source) + if err != nil { + return errors.Wrap(err, "failed to create cache volumes") + } // generate the docs from this current commit docs := dag.Container(). - From("golang:1.24"). + From(image). WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). WithWorkdir("/go/src/github.com/replicatedhq/replicated"). WithMountedCache("/go/pkg/mod", goModCache). diff --git a/dagger/exec_utils.go b/dagger/exec_utils.go index c03069979..f8d21c797 100644 --- a/dagger/exec_utils.go +++ b/dagger/exec_utils.go @@ -1,10 +1,57 @@ package main import ( + "context" "dagger/replicated/internal/dagger" + "fmt" + "strings" "time" ) +// goVersion reads the go directive from go.mod in the source directory and +// returns the major.minor version (e.g. "1.26" from "go 1.26.1"). +func goVersion(ctx context.Context, source *dagger.Directory) (string, error) { + contents, err := source.File("go.mod").Contents(ctx) + if err != nil { + return "", fmt.Errorf("failed to read go.mod: %w", err) + } + for _, line := range strings.Split(contents, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "go ") { + version := strings.TrimPrefix(line, "go ") + // strip patch version if present (e.g. "1.26.1" -> "1.26") + parts := strings.SplitN(version, ".", 3) + if len(parts) >= 2 { + return parts[0] + "." + parts[1], nil + } + return version, nil + } + } + return "", fmt.Errorf("go directive not found in go.mod") +} + +// goImage returns the golang Docker image tag for the version in go.mod. +func goImage(ctx context.Context, source *dagger.Directory) (string, error) { + v, err := goVersion(ctx, source) + if err != nil { + return "", err + } + return "golang:" + v, nil +} + +// goCacheVolumes returns the mod and build cache volumes keyed by the Go version. +func goCacheVolumes(ctx context.Context, source *dagger.Directory) (*dagger.CacheVolume, *dagger.CacheVolume, error) { + v, err := goVersion(ctx, source) + if err != nil { + return nil, nil, err + } + // replace dots for a clean cache key suffix (e.g. "126" from "1.26") + suffix := strings.ReplaceAll(v, ".", "") + modCache := dag.CacheVolume("replicated-go-mod-" + suffix) + buildCache := dag.CacheVolume("replicated-go-build-" + suffix) + return modCache, buildCache, nil +} + // CacheBustingExec is a helper function that will add a cache busting env var automatically // to the container. This is useful when Exec target is a dynamic event acting on an entity outside // of the container that you absolutely want to re-run every time. diff --git a/dagger/functionality.go b/dagger/functionality.go index 3e7946616..a56d69f32 100644 --- a/dagger/functionality.go +++ b/dagger/functionality.go @@ -11,12 +11,18 @@ func validateFunctionality( // +defaultPath="./" source *dagger.Directory, ) error { - goModCache := dag.CacheVolume("replicated-go-mod-122") - goBuildCache := dag.CacheVolume("replicated-go-build-121") + image, err := goImage(ctx, source) + if err != nil { + return err + } + goModCache, goBuildCache, err := goCacheVolumes(ctx, source) + if err != nil { + return err + } // unit tests unitTest := dag.Container(). - From("golang:1.24"). + From(image). WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", source). WithWorkdir("/go/src/github.com/replicatedhq/replicated"). WithMountedCache("/go/pkg/mod", goModCache). @@ -25,7 +31,7 @@ func validateFunctionality( WithEnvVariable("GOCACHE", "/go/build-cache"). With(CacheBustingExec([]string{"make", "test-unit"})) - _, err := unitTest.Stderr(ctx) + _, err = unitTest.Stderr(ctx) if err != nil { return err } diff --git a/dagger/release.go b/dagger/release.go index f0df52780..3bf76aafc 100644 --- a/dagger/release.go +++ b/dagger/release.go @@ -105,13 +105,19 @@ func (r *Replicated) Release( // copy the source that has the tag included in it updatedSource = tagContainer.Directory("/go/src/github.com/replicatedhq/replicated") - goModCache := dag.CacheVolume("replicated-go-mod-122") - goBuildCache := dag.CacheVolume("replicated-go-build-121") + image, err := goImage(ctx, updatedSource) + if err != nil { + return errors.Wrap(err, "failed to detect go version") + } + goModCache, goBuildCache, err := goCacheVolumes(ctx, updatedSource) + if err != nil { + return errors.Wrap(err, "failed to create cache volumes") + } replicatedBinary := dag.Container(dagger.ContainerOpts{ Platform: "linux/amd64", }). - From("golang:1.24"). + From(image). WithMountedDirectory("/go/src/github.com/replicatedhq/replicated", updatedSource). WithoutFile("/go/src/github.com/replicatedhq/replicated/bin/replicated"). WithWorkdir("/go/src/github.com/replicatedhq/replicated"). diff --git a/dagger/security.go b/dagger/security.go index 55cdd06c3..34a265505 100644 --- a/dagger/security.go +++ b/dagger/security.go @@ -11,8 +11,10 @@ func validateSecurity( // +defaultPath="./" source *dagger.Directory, ) error { - goModCache := dag.CacheVolume("replicated-go-mod-122") - goBuildCache := dag.CacheVolume("replicated-go-build-121") + goModCache, goBuildCache, err := goCacheVolumes(ctx, source) + if err != nil { + return err + } // run semgrep semgrep := dag.Container(). @@ -25,7 +27,7 @@ func validateSecurity( WithEnvVariable("GOCACHE", "/go/build-cache"). With(CacheBustingExec([]string{"semgrep", "scan", "--config=p/golang", "."})) - _, err := semgrep.Stderr(ctx) + _, err = semgrep.Stderr(ctx) if err != nil { return err }