From 934ba3580c397b00c86288da10b4a562302b2e95 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Sat, 2 May 2026 17:00:55 -0500 Subject: [PATCH 1/2] fix(build): wire devcontainer.json cacheFrom into docker build args GetCacheFrom() at config.go:278-283 had zero call sites. build/options.go only read CLI-level RegistryCache, so devcontainer.json cacheFrom values never reached the Docker build command. Wire GetCacheFrom() result into buildOptions.CacheFrom alongside RegistryCache. CLI registry cache entries come first, then config values. BUILDKIT_INLINE_CACHE fallback only activates when no cache sources exist. Co-Authored-By: Paperclip --- pkg/devcontainer/build/options.go | 6 +- pkg/devcontainer/build/options_test.go | 84 ++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 pkg/devcontainer/build/options_test.go diff --git a/pkg/devcontainer/build/options.go b/pkg/devcontainer/build/options.go index 11865bace..160e7e103 100644 --- a/pkg/devcontainer/build/options.go +++ b/pkg/devcontainer/build/options.go @@ -154,7 +154,11 @@ func NewOptions(params NewOptionsParams) (*BuildOptions, error) { ), } } - } else { + } + if configCacheFrom := params.ParsedConfig.Config.GetCacheFrom(); len(configCacheFrom) > 0 { + buildOptions.CacheFrom = append(buildOptions.CacheFrom, configCacheFrom...) + } + if len(buildOptions.CacheFrom) == 0 { buildOptions.BuildArgs["BUILDKIT_INLINE_CACHE"] = "1" } diff --git a/pkg/devcontainer/build/options_test.go b/pkg/devcontainer/build/options_test.go new file mode 100644 index 000000000..701c4f3d9 --- /dev/null +++ b/pkg/devcontainer/build/options_test.go @@ -0,0 +1,84 @@ +package build + +import ( + "testing" + + "github.com/devsy-org/devsy/pkg/devcontainer/config" + "github.com/devsy-org/devsy/pkg/provider" + "github.com/devsy-org/devsy/pkg/types" +) + +func substitutedConfig(cfg *config.DevContainerConfig) *config.SubstitutedConfig { + return &config.SubstitutedConfig{Config: cfg, Raw: cfg} +} + +func TestNewOptions_CacheFrom_ConfigOnly(t *testing.T) { + params := NewOptionsParams{ + ParsedConfig: substitutedConfig(&config.DevContainerConfig{ + DockerfileContainer: config.DockerfileContainer{ + Build: &config.ConfigBuildOptions{ + CacheFrom: types.StrArray{"myregistry.io/cache:latest", "other:tag"}, + }, + }, + }), + Options: provider.BuildOptions{}, + } + opts, err := NewOptions(params) + if err != nil { + t.Fatal(err) + } + if len(opts.CacheFrom) != 2 { + t.Fatalf("expected 2 CacheFrom entries, got %d: %v", len(opts.CacheFrom), opts.CacheFrom) + } + if opts.CacheFrom[0] != "myregistry.io/cache:latest" || opts.CacheFrom[1] != "other:tag" { + t.Fatalf("unexpected CacheFrom: %v", opts.CacheFrom) + } + if _, ok := opts.BuildArgs["BUILDKIT_INLINE_CACHE"]; ok { + t.Fatal("BUILDKIT_INLINE_CACHE should not be set when cacheFrom is configured") + } +} + +func TestNewOptions_CacheFrom_CLIAndConfig(t *testing.T) { + params := NewOptionsParams{ + ParsedConfig: substitutedConfig(&config.DevContainerConfig{ + DockerfileContainer: config.DockerfileContainer{ + Build: &config.ConfigBuildOptions{ + CacheFrom: types.StrArray{"myregistry.io/cache:latest"}, + }, + }, + }), + Options: provider.BuildOptions{ + RegistryCache: "registry.example.com/cache", + }, + } + opts, err := NewOptions(params) + if err != nil { + t.Fatal(err) + } + if len(opts.CacheFrom) != 2 { + t.Fatalf("expected 2 CacheFrom entries, got %d: %v", len(opts.CacheFrom), opts.CacheFrom) + } + if opts.CacheFrom[0] != "type=registry,ref=registry.example.com/cache" { + t.Fatalf("expected CLI registry cache first, got: %s", opts.CacheFrom[0]) + } + if opts.CacheFrom[1] != "myregistry.io/cache:latest" { + t.Fatalf("expected config cache second, got: %s", opts.CacheFrom[1]) + } +} + +func TestNewOptions_CacheFrom_Fallback(t *testing.T) { + params := NewOptionsParams{ + ParsedConfig: substitutedConfig(&config.DevContainerConfig{}), + Options: provider.BuildOptions{}, + } + opts, err := NewOptions(params) + if err != nil { + t.Fatal(err) + } + if len(opts.CacheFrom) != 0 { + t.Fatalf("expected empty CacheFrom, got: %v", opts.CacheFrom) + } + if opts.BuildArgs["BUILDKIT_INLINE_CACHE"] != "1" { + t.Fatal("expected BUILDKIT_INLINE_CACHE=1 as fallback") + } +} From 80534e01b01c8fbfc9669d3e7a0a31061f9f1daf Mon Sep 17 00:00:00 2001 From: Samuel K Date: Sat, 2 May 2026 17:07:41 -0500 Subject: [PATCH 2/2] fix(build): extract repeated test string to const for goconst lint Co-Authored-By: Paperclip --- pkg/devcontainer/build/options_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/devcontainer/build/options_test.go b/pkg/devcontainer/build/options_test.go index 701c4f3d9..aba67820f 100644 --- a/pkg/devcontainer/build/options_test.go +++ b/pkg/devcontainer/build/options_test.go @@ -8,6 +8,8 @@ import ( "github.com/devsy-org/devsy/pkg/types" ) +const testCacheImage = "myregistry.io/cache:latest" + func substitutedConfig(cfg *config.DevContainerConfig) *config.SubstitutedConfig { return &config.SubstitutedConfig{Config: cfg, Raw: cfg} } @@ -17,7 +19,7 @@ func TestNewOptions_CacheFrom_ConfigOnly(t *testing.T) { ParsedConfig: substitutedConfig(&config.DevContainerConfig{ DockerfileContainer: config.DockerfileContainer{ Build: &config.ConfigBuildOptions{ - CacheFrom: types.StrArray{"myregistry.io/cache:latest", "other:tag"}, + CacheFrom: types.StrArray{testCacheImage, "other:tag"}, }, }, }), @@ -30,7 +32,7 @@ func TestNewOptions_CacheFrom_ConfigOnly(t *testing.T) { if len(opts.CacheFrom) != 2 { t.Fatalf("expected 2 CacheFrom entries, got %d: %v", len(opts.CacheFrom), opts.CacheFrom) } - if opts.CacheFrom[0] != "myregistry.io/cache:latest" || opts.CacheFrom[1] != "other:tag" { + if opts.CacheFrom[0] != testCacheImage || opts.CacheFrom[1] != "other:tag" { t.Fatalf("unexpected CacheFrom: %v", opts.CacheFrom) } if _, ok := opts.BuildArgs["BUILDKIT_INLINE_CACHE"]; ok { @@ -43,7 +45,7 @@ func TestNewOptions_CacheFrom_CLIAndConfig(t *testing.T) { ParsedConfig: substitutedConfig(&config.DevContainerConfig{ DockerfileContainer: config.DockerfileContainer{ Build: &config.ConfigBuildOptions{ - CacheFrom: types.StrArray{"myregistry.io/cache:latest"}, + CacheFrom: types.StrArray{testCacheImage}, }, }, }), @@ -61,7 +63,7 @@ func TestNewOptions_CacheFrom_CLIAndConfig(t *testing.T) { if opts.CacheFrom[0] != "type=registry,ref=registry.example.com/cache" { t.Fatalf("expected CLI registry cache first, got: %s", opts.CacheFrom[0]) } - if opts.CacheFrom[1] != "myregistry.io/cache:latest" { + if opts.CacheFrom[1] != testCacheImage { t.Fatalf("expected config cache second, got: %s", opts.CacheFrom[1]) } }