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
79 changes: 34 additions & 45 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const (
cacheMountUnset = ""

BazelDefaultSocketID = "bazel-default" // Default ID for bazel socket

)

// getSccacheSource returns a Source for downloading and verifying sccache using SourceHTTP
Expand Down Expand Up @@ -115,9 +114,9 @@ func WithCacheDirConstraints(opts ...llb.ConstraintsOpt) CacheConfigOption {
})
}

func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...CacheConfigOption) llb.RunOption {
func (c *CacheConfig) ToRunOption(worker llb.State, cacheIdentity string, opts ...CacheConfigOption) llb.RunOption {
if c.Dir != nil {
return c.Dir.ToRunOption(distroKey, CacheDirOptionFunc(func(info *CacheDirInfo) {
return c.Dir.ToRunOption(cacheIdentity, CacheDirOptionFunc(func(info *CacheDirInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
Expand All @@ -127,7 +126,7 @@ func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...Ca
}

if c.GoBuild != nil {
return c.GoBuild.ToRunOption(distroKey, GoBuildCacheOptionFunc(func(info *GoBuildCacheInfo) {
return c.GoBuild.ToRunOption(cacheIdentity, GoBuildCacheOptionFunc(func(info *GoBuildCacheInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
Expand All @@ -137,7 +136,7 @@ func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...Ca
}

if c.RustSCCache != nil {
return c.RustSCCache.ToRunOption(distroKey, SCCacheOptionFunc(func(info *SCCacheInfo) {
return c.RustSCCache.ToRunOption(cacheIdentity, SCCacheOptionFunc(func(info *SCCacheInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
Expand All @@ -147,7 +146,7 @@ func (c *CacheConfig) ToRunOption(worker llb.State, distroKey string, opts ...Ca
}

if c.Bazel != nil {
return c.Bazel.ToRunOption(worker, distroKey, BazelCacheOptionFunc(func(info *BazelCacheInfo) {
return c.Bazel.ToRunOption(worker, cacheIdentity, BazelCacheOptionFunc(func(info *BazelCacheInfo) {
var cacheInfo CacheInfo
for _, opt := range opts {
opt.SetCacheConfigOption(&cacheInfo)
Expand Down Expand Up @@ -227,12 +226,12 @@ type CacheDir struct {
Sharing string `json:"sharing" yaml:"sharing" jsonschema:"enum=shared,enum=locked,enum=private"`

// NoAutoNamespace disables the automatic prefixing of the cache key with the
// target specific information such as distro and CPU architecture, which may
// be auto-injected to prevent common issues that would cause an invalid cache.
// build environment identity and CPU architecture, which may be auto-injected
// to prevent common issues that would cause an invalid cache.
NoAutoNamespace bool `json:"no_auto_namespace" yaml:"no_auto_namespace"`
}

func (c *CacheDir) ToRunOption(distroKey string, opts ...CacheDirOption) llb.RunOption {
func (c *CacheDir) ToRunOption(cacheIdentity string, opts ...CacheDirOption) llb.RunOption {
return RunOptFunc(func(ei *llb.ExecInfo) {
var sharing llb.CacheMountSharingMode
switch c.Sharing {
Expand Down Expand Up @@ -260,17 +259,11 @@ func (c *CacheDir) ToRunOption(distroKey string, opts ...CacheDirOption) llb.Run
}

if !c.NoAutoNamespace {
platform := ei.Platform

if platform == nil {
platform = info.Platform
}

if platform == nil {
p := platforms.DefaultSpec()
platform = &p
}
key = fmt.Sprintf("%s-%s-%s", distroKey, platforms.Format(*platform), key)
key = PersistentCacheID{
Environment: cacheIdentity,
Platform: execCacheIDPlatform(ei, info.Platform),
Key: key,
}.String()
Comment on lines +262 to +266
}

llb.AddMount(c.Dest, llb.Scratch(), llb.AsPersistentCacheDir(key, sharing)).SetRunOption(ei)
Expand Down Expand Up @@ -345,7 +338,7 @@ func WithGoCacheConstraints(opts ...llb.ConstraintsOpt) CacheConfigOption {

const goBuildCacheDir = "/tmp/dalec/gobuild-cache"

func (c *GoBuildCache) ToRunOption(distroKey string, opts ...GoBuildCacheOption) llb.RunOption {
func (c *GoBuildCache) ToRunOption(cacheIdentity string, opts ...GoBuildCacheOption) llb.RunOption {
return RunOptFunc(func(ei *llb.ExecInfo) {
if c.Disabled {
return
Expand All @@ -356,20 +349,12 @@ func (c *GoBuildCache) ToRunOption(distroKey string, opts ...GoBuildCacheOption)
opt.SetGoBuildCacheOption(&info)
}

platform := ei.Platform

if platform == nil {
platform = info.Platform
}
if platform == nil {
p := platforms.DefaultSpec()
platform = &p
}

key := fmt.Sprintf("%s-%s-dalec-gobuildcache", distroKey, platforms.Format(*platform))
if c.Scope != "" {
key = fmt.Sprintf("%s-%s", key, c.Scope)
}
key := PersistentCacheID{
Environment: cacheIdentity,
Platform: execCacheIDPlatform(ei, info.Platform),
Type: cacheTypeGoBuild,
Key: c.Scope,
}.String()
llb.AddMount(goBuildCacheDir, llb.Scratch(), llb.AsPersistentCacheDir(key, llb.CacheMountShared)).SetRunOption(ei)
llb.AddEnv("GOCACHE", goBuildCacheDir).SetRunOption(ei)
})
Expand Down Expand Up @@ -416,7 +401,7 @@ const (
sccacheBinary = "/tmp/internal/dalec/sccache/sccache"
)

func (c *SCCache) ToRunOption(distroKey string, opts ...SCCacheOption) llb.RunOption {
func (c *SCCache) ToRunOption(cacheIdentity string, opts ...SCCacheOption) llb.RunOption {
// TODO: Future improvement - allow pulling sccache from build context instead of GitHub
// This would provide better security and flexibility by allowing users to:
// 1. Bring their own verified sccache binary
Expand All @@ -443,10 +428,12 @@ func (c *SCCache) ToRunOption(distroKey string, opts ...SCCacheOption) llb.RunOp
platform = &p
}

key := fmt.Sprintf("%s-%s-dalec-rustsccache", distroKey, platforms.Format(*platform))
if c.Scope != "" {
key = fmt.Sprintf("%s-%s", key, c.Scope)
}
key := PersistentCacheID{
Environment: cacheIdentity,
Platform: FormatCacheIDPlatform(*platform),
Type: cacheTypeRustSccache,
Key: c.Scope,
}.String()

// Set up cache mount for sccache compilation cache
llb.AddMount(sccacheCacheDir, llb.Scratch(), llb.AsPersistentCacheDir(key, llb.CacheMountShared)).SetRunOption(ei)
Expand Down Expand Up @@ -531,7 +518,7 @@ type BazelCacheOption interface {
SetBazelCacheOption(*BazelCacheInfo)
}

func (c *BazelCache) ToRunOption(worker llb.State, distroKey string, opts ...BazelCacheOption) llb.RunOption {
func (c *BazelCache) ToRunOption(worker llb.State, cacheIdentity string, opts ...BazelCacheOption) llb.RunOption {
return RunOptFunc(func(ei *llb.ExecInfo) {
var info BazelCacheInfo

Expand All @@ -549,10 +536,12 @@ func (c *BazelCache) ToRunOption(worker llb.State, distroKey string, opts ...Baz
platform = &p
}

key := fmt.Sprintf("%s-%s-dalec-bazelcache", distroKey, platforms.Format(*platform))
if c.Scope != "" {
key = fmt.Sprintf("%s-%s", key, c.Scope)
}
key := PersistentCacheID{
Environment: cacheIdentity,
Platform: FormatCacheIDPlatform(*platform),
Type: cacheTypeBazel,
Key: c.Scope,
}.String()

// See bazelrc https://bazel.build/run/bazelrc for more information on the bazelrc file

Expand Down
75 changes: 75 additions & 0 deletions cache_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package dalec

import (
"strings"

"github.com/containerd/platforms"
"github.com/moby/buildkit/client/llb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

const (
cacheTypeGoBuild = "dalec-gobuildcache"
cacheTypeRustSccache = "dalec-rustsccache"
cacheTypeBazel = "dalec-bazelcache"
)

// PersistentCacheID describes a Dalec persistent BuildKit cache mount ID.
type PersistentCacheID struct {
// Namespace is an optional global namespace prepended to the whole cache ID.
Namespace string
Comment on lines +19 to +20
// Environment identifies the build environment that owns the cache.
Environment string
// Platform identifies the platform when a cache must be platform-scoped.
Platform string
// Type identifies the Dalec cache type.
Type string
// Key identifies user-provided cache key material, scope, or a sub-cache.
Key string
}

// String format the cache ID from its non-empty parts.
func (id PersistentCacheID) String() string {
parts := make([]string, 0, 4)
for _, part := range []string{id.Environment, id.Platform, id.Type, id.Key} {
if part != "" {
parts = append(parts, part)
}
}

cacheID := strings.Join(parts, "-")
if id.Namespace == "" {
return cacheID
}

ns := strings.TrimRight(id.Namespace, "/")
if cacheID == "" {
return ns
}
return ns + "/" + cacheID
}

// FormatCacheIDPlatform formats a platform for use in cache IDs.
func FormatCacheIDPlatform(p ocispecs.Platform) string {
return platforms.Format(p)
}

// FormatSafeCacheIDPlatform formats a platform without path separators.
func FormatSafeCacheIDPlatform(p ocispecs.Platform) string {
return strings.NewReplacer("/", "_", ":", "_").Replace(FormatCacheIDPlatform(p))
}

func defaultedCacheIDPlatform(p *ocispecs.Platform) string {
if p == nil {
dp := platforms.DefaultSpec()
p = &dp
}
return FormatCacheIDPlatform(*p)
}

func execCacheIDPlatform(ei *llb.ExecInfo, fallback *ocispecs.Platform) string {
if ei.Platform != nil {
return defaultedCacheIDPlatform(ei.Platform)
}
return defaultedCacheIDPlatform(fallback)
}
75 changes: 75 additions & 0 deletions cache_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package dalec

import (
"testing"

ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

func TestPersistentCacheIDString(t *testing.T) {
t.Parallel()

tests := []struct {
name string
id PersistentCacheID
want string
}{
{
name: "all parts",
id: PersistentCacheID{
Namespace: "tenant",
Environment: "ubuntu22.04",
Platform: "linux/amd64",
Type: "dalec-gobuildcache",
Key: "scope",
},
want: "tenant/ubuntu22.04-linux/amd64-dalec-gobuildcache-scope",
},
{
name: "empty parts omitted",
id: PersistentCacheID{
Environment: "azlinux3.0",
Type: "dalec-bazelcache",
},
want: "azlinux3.0-dalec-bazelcache",
},
{
name: "trailing namespace slash trimmed",
id: PersistentCacheID{
Namespace: "ci/",
Type: "dalec-gomod-proxy-cache",
},
want: "ci/dalec-gomod-proxy-cache",
},
{
name: "user key preserved",
id: PersistentCacheID{
Key: "/tmp/cache",
},
want: "/tmp/cache",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

if got := tt.id.String(); got != tt.want {
t.Fatalf("expected %q, got %q", tt.want, got)
}
})
}
}

func TestFormatSafeCacheIDPlatform(t *testing.T) {
t.Parallel()

p := ocispecs.Platform{
OS: "linux",
Architecture: "arm64",
}

if got, want := FormatSafeCacheIDPlatform(p), "linux_arm64"; got != want {
t.Fatalf("expected %q, got %q", want, got)
}
}
14 changes: 12 additions & 2 deletions cmd/website/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,18 @@ func generateSite(toolchain llb.State) llb.StateOption {
hugoCacheID = "dalec-website-hugo"
)
cacheMounts := dalec.RunOptFunc(func(ei *llb.ExecInfo) {
llb.AddMount("/go/pkg/mod", llb.Scratch(), llb.AsPersistentCacheDir(modsCacheID, llb.CacheMountLocked)).SetRunOption(ei)
llb.AddMount("/cache", llb.Scratch(), llb.AsPersistentCacheDir(hugoCacheID, llb.CacheMountLocked)).SetRunOption(ei)
goModPersistentCacheID := dalec.PersistentCacheID{Type: modsCacheID}.String()
hugoPersistentCacheID := dalec.PersistentCacheID{Type: hugoCacheID}.String()
llb.AddMount(
"/go/pkg/mod",
llb.Scratch(),
llb.AsPersistentCacheDir(goModPersistentCacheID, llb.CacheMountLocked),
).SetRunOption(ei)
llb.AddMount(
"/cache",
llb.Scratch(),
llb.AsPersistentCacheDir(hugoPersistentCacheID, llb.CacheMountLocked),
).SetRunOption(ei)
})
generated := toolchain.Run(
cacheMounts,
Expand Down
2 changes: 1 addition & 1 deletion docs/spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@
"type": [
"boolean"
],
"description": "NoAutoNamespace disables the automatic prefixing of the cache key with the\ntarget specific information such as distro and CPU architecture, which may\nbe auto-injected to prevent common issues that would cause an invalid cache."
"description": "NoAutoNamespace disables the automatic prefixing of the cache key with the\nbuild environment identity and CPU architecture, which may be auto-injected\nto prevent common issues that would cause an invalid cache."
},
"sharing": {
"enum": [
Expand Down
6 changes: 5 additions & 1 deletion generator_gomod.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const (
BuildArgDalecGomodProxy = "DALEC_GOMOD_PROXY"
)

func gomodProxyCacheID() string {
return PersistentCacheID{Type: GomodCacheKey}.String()
}

func (g *GeneratorGomod) processBuildArgs(args map[string]string, allowArg func(key string) bool) error {
var errs []error
lex := shell.NewLex('\\')
Expand Down Expand Up @@ -122,7 +126,7 @@ func withGomod(gomodOpts gomodGeneratorOpts) func(llb.State) llb.State {
llb.AddEnv("GIT_SSH_COMMAND", "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"),
llb.Dir(filepath.Join(joinedWorkDir, path)),
srcMount,
llb.AddMount(proxyPath, llb.Scratch(), llb.AsPersistentCacheDir(GomodCacheKey, llb.CacheMountShared)),
llb.AddMount(proxyPath, llb.Scratch(), llb.AsPersistentCacheDir(gomodProxyCacheID(), llb.CacheMountShared)),
WithConstraints(opts...),
g.Gomod._sourceMap.GetLocation(in),
}
Expand Down
Loading