Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package apko for openSUSE:Factory checked in at 2026-08-06 16:41:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/apko (Old) and /work/SRC/openSUSE:Factory/.apko.new.16738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "apko" Thu Aug 6 16:41:18 2026 rev:133 rq:1370055 version:1.2.35 Changes: -------- --- /work/SRC/openSUSE:Factory/apko/apko.changes 2026-08-04 23:28:38.170252474 +0200 +++ /work/SRC/openSUSE:Factory/.apko.new.16738/apko.changes 2026-08-06 16:41:21.700319256 +0200 @@ -1,0 +2,27 @@ +Thu Aug 06 11:00:52 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.2.35: + * fix(apk): honour offline mode in InitDB key discovery without a + cache (#2377) + * test: set source date epoch to `0` (#2372) + * Disable Go module caching in goreleaser workflows (#2375) + +------------------------------------------------------------------- +Wed Aug 05 10:35:10 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.2.34: + * apk: handle Alpine key fetches without a cache (#2374) + * build: add a disk cache opt-out (#2373) + +------------------------------------------------------------------- +Wed Aug 05 05:18:08 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.2.33: + * build(deps): bump github.com/google/go-containerregistry from + 0.21.7 to 0.21.8 (#2371) + * build(deps): bump chainguard.dev/sdk from 0.1.163 to 0.1.164 + (#2370) + * build(deps): bump zizmorcore/zizmor-action from 0.6.1 to 0.6.2 + (#2369) + +------------------------------------------------------------------- Old: ---- apko-1.2.32.obscpio New: ---- apko-1.2.35.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ apko.spec ++++++ --- /var/tmp/diff_new_pack.uaGfcT/_old 2026-08-06 16:41:25.304443614 +0200 +++ /var/tmp/diff_new_pack.uaGfcT/_new 2026-08-06 16:41:25.308443752 +0200 @@ -17,7 +17,7 @@ Name: apko -Version: 1.2.32 +Version: 1.2.35 Release: 0 Summary: Build OCI images from APK packages directly without Dockerfile License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.uaGfcT/_old 2026-08-06 16:41:25.356445408 +0200 +++ /var/tmp/diff_new_pack.uaGfcT/_new 2026-08-06 16:41:25.360445547 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/chainguard-dev/apko.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v1.2.32</param> + <param name="revision">refs/tags/v1.2.35</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.uaGfcT/_old 2026-08-06 16:41:25.388446512 +0200 +++ /var/tmp/diff_new_pack.uaGfcT/_new 2026-08-06 16:41:25.392446651 +0200 @@ -3,6 +3,6 @@ <param name="url">https://github.com/chainguard-dev/apko</param> <param name="changesrevision">861f83f69e6fa9114405a2f7bb5cf6585ad00421</param></service><service name="tar_scm"> <param name="url">https://github.com/chainguard-dev/apko.git</param> - <param name="changesrevision">ea62bef5271a0c94f3fba641ee0e8d506b9b57ef</param></service></servicedata> + <param name="changesrevision">766682c7e7f87cc932ff02abeb889f5eab5edc9f</param></service></servicedata> (No newline at EOF) ++++++ apko-1.2.32.obscpio -> apko-1.2.35.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/go.mod new/apko-1.2.35/go.mod --- old/apko-1.2.32/go.mod 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/go.mod 2026-08-06 11:49:06.000000000 +0200 @@ -3,12 +3,12 @@ go 1.26.0 require ( - chainguard.dev/sdk v0.1.163 + chainguard.dev/sdk v0.1.164 github.com/chainguard-dev/clog v1.8.1 github.com/charmbracelet/log v1.0.0 github.com/go-git/go-git/v5 v5.19.2 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.21.7 + github.com/google/go-containerregistry v0.21.8 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-retryablehttp v0.7.8 @@ -66,7 +66,7 @@ github.com/cyphar/filepath-securejoin v0.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v29.6.1+incompatible // indirect + github.com/docker/cli v29.6.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -97,7 +97,7 @@ github.com/mattn/go-runewidth v0.0.24 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/moby/api v1.55.0 // indirect - github.com/moby/moby/client v0.5.0 // indirect + github.com/moby/moby/client v0.5.1 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/go.sum new/apko-1.2.35/go.sum --- old/apko-1.2.32/go.sum 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/go.sum 2026-08-06 11:49:06.000000000 +0200 @@ -1,7 +1,7 @@ chainguard.dev/go-grpc-kit v0.17.17 h1:Jwhc0zyUwQbC2hNcsi+YMeUX/JUnM+dXVCkTw6wtPzs= chainguard.dev/go-grpc-kit v0.17.17/go.mod h1:qn0meP6RtrbLicE1bgBZnnVU9dvX95eLs0x0T6kZ+b4= -chainguard.dev/sdk v0.1.163 h1:G85vXLU7qJ2pJo1hHcPRYWWZnGRUAVIomZfoq9dg8Dk= -chainguard.dev/sdk v0.1.163/go.mod h1:y5AGo04erWjKtEyGX7fO9zZA4vtj9cLGXZjgIYxeMdo= +chainguard.dev/sdk v0.1.164 h1:k2Ir/l1cuHDS/vg29sLNjJYm2kInnCfR5w0uFD0Y1Hg= +chainguard.dev/sdk v0.1.164/go.mod h1:y5AGo04erWjKtEyGX7fO9zZA4vtj9cLGXZjgIYxeMdo= cloud.google.com/go/auth v0.22.0 h1:Xp9wAKkLoeaYb5pYZZoQGz4E9sdPxIbzS3gywZE3ciQ= cloud.google.com/go/auth v0.22.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -66,8 +66,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v29.6.1+incompatible h1:oO7F4nn3Ovr/5TlfTUWFbMwBSS/B7Xs6Epv26gBrUP8= -github.com/docker/cli v29.6.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.6.2+incompatible h1:/bjePvcbbFTnRrMfWJBY7AjfICdsiLVgHn6LwTVOcqw= +github.com/docker/cli v29.6.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= @@ -111,8 +111,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= -github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/go-containerregistry v0.21.8 h1:Ig/zIsnztdCUNaiNNczE+MoP5xcyUMfvpvfOr1xyMLE= +github.com/google/go-containerregistry v0.21.8/go.mod h1:dP5XNKcL7kMFF/TB3LfvWmVhAcv7iqkHb3oDK8aauTo= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -172,8 +172,8 @@ github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= -github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= +github.com/moby/moby/client v0.5.1 h1:tYNaJno4c0HXz12y5BiqEDy0rVTYkWzI26lGvnTMiJw= +github.com/moby/moby/client v0.5.1/go.mod h1:odLstlZ6uSnfvAgVxMpvgmb8SUdd+siH2T0GBuxVAlM= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/internal/cli/build_test.go new/apko-1.2.35/internal/cli/build_test.go --- old/apko-1.2.32/internal/cli/build_test.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/internal/cli/build_test.go 2026-08-06 11:49:06.000000000 +0200 @@ -16,7 +16,6 @@ import ( "bytes" - "context" "encoding/json" "os" "path/filepath" @@ -34,7 +33,9 @@ ) func TestBuild(t *testing.T) { - ctx := context.Background() + unsetSourceDateEpoch(t) + + ctx := t.Context() tmp := t.TempDir() golden := filepath.Join("testdata", "golden") @@ -114,11 +115,13 @@ } func TestBuildWithBase(t *testing.T) { + unsetSourceDateEpoch(t) + // top_image golden file can be regenerated using ./internal/cli/testdata/regenerate_golden_top_image.sh script. // TODO(sfc-gh-mhazy) Check sboms after base image support is reflected in them. - ctx := context.Background() + ctx := t.Context() tmp := t.TempDir() apkoTempDir := t.TempDir() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/internal/cli/helpers_test.go new/apko-1.2.35/internal/cli/helpers_test.go --- old/apko-1.2.32/internal/cli/helpers_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/apko-1.2.35/internal/cli/helpers_test.go 2026-08-06 11:49:06.000000000 +0200 @@ -0,0 +1,31 @@ +// Copyright 2026 Chainguard, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli_test + +import ( + "os" + "testing" +) + +// unsetSourceDateEpoch clears SOURCE_DATE_EPOCH for the duration of the +// test, so the build derives its epoch from the installed packages. The +// golden fixtures were generated this way. t.Setenv registers restoration +// of the caller's value and prevents the test from running in parallel. +func unsetSourceDateEpoch(t *testing.T) { + t.Helper() + + t.Setenv("SOURCE_DATE_EPOCH", "") + os.Unsetenv("SOURCE_DATE_EPOCH") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/internal/cli/publish_test.go new/apko-1.2.35/internal/cli/publish_test.go --- old/apko-1.2.32/internal/cli/publish_test.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/internal/cli/publish_test.go 2026-08-06 11:49:06.000000000 +0200 @@ -16,7 +16,6 @@ import ( "archive/tar" - "context" "fmt" "io" "io/fs" @@ -45,7 +44,9 @@ ) func TestPublish(t *testing.T) { - ctx := context.Background() + unsetSourceDateEpoch(t) + + ctx := t.Context() tmp := t.TempDir() // Set up a registry that requires we see a magic header. @@ -119,7 +120,9 @@ } func TestPublishLayering(t *testing.T) { - ctx := context.Background() + unsetSourceDateEpoch(t) + + ctx := t.Context() tmp := t.TempDir() // Set up a registry that requires we see a magic header. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/apk/apk/implementation.go new/apko-1.2.35/pkg/apk/apk/implementation.go --- old/apko-1.2.32/pkg/apk/apk/implementation.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/apk/apk/implementation.go 2026-08-06 11:49:06.000000000 +0200 @@ -66,6 +66,7 @@ ignoreMknodErrors bool client *http.Client cache *cache + offline bool ignoreSignatures bool noSignatureIndexes []string auth auth.Authenticator @@ -95,6 +96,9 @@ return nil, err } } + if opt.cache != nil { + opt.cache.offline = opt.offline + } if opt.fs == nil { // This is expensive so we only want to do it if we aren't passed WithFS. @@ -114,6 +118,9 @@ client.Logger = clog.FromContext(ctx) httpClient := client.StandardClient() + if opt.offline { + httpClient.Transport = offlineTransport{} + } // Create default PackageGetter if none provided packageGetter := opt.packageGetter @@ -138,6 +145,7 @@ ignoreMknodErrors: opt.ignoreMknodErrors, version: opt.version, cache: opt.cache, + offline: opt.offline, ignoreSignatures: opt.ignoreSignatures, noSignatureIndexes: opt.noSignatureIndexes, installedFiles: map[string]*Package{}, @@ -345,8 +353,12 @@ if ver, ok := ParseAlpineVersion(repo); ok { if err := a.fetchAlpineKeys(ctx, ver); err != nil { var nokeysErr *NoKeysFoundError - if !a.cache.offline && !errors.As(err, &nokeysErr) { - return fmt.Errorf("failed to fetch alpine-keys: %w", err) + if !a.offline && !errors.As(err, &nokeysErr) { + return &AlpineKeyFetchError{ + Repository: repo, + Version: ver, + Err: err, + } } log.Debugf("ignoring missing keys: %v", err) } @@ -896,6 +908,22 @@ return fmt.Sprintf("no keys found for arch %s and releases %v", e.arch, e.releases) } +// AlpineKeyFetchError reports a failure to retrieve signing keys for an +// Alpine package repository. +type AlpineKeyFetchError struct { + Repository string + Version string + Err error +} + +func (e *AlpineKeyFetchError) Error() string { + return fmt.Sprintf("failed to fetch Alpine keys for %s: %v", e.Repository, e.Err) +} + +func (e *AlpineKeyFetchError) Unwrap() error { + return e.Err +} + // FetchAlpineReleases fetches and returns the Alpine releases metadata from alpinelinux.org. func FetchAlpineReleases(ctx context.Context, client *http.Client) (*Releases, error) { u := alpineReleasesURL diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/apk/apk/implementation_test.go new/apko-1.2.35/pkg/apk/apk/implementation_test.go --- old/apko-1.2.32/pkg/apk/apk/implementation_test.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/apk/apk/implementation_test.go 2026-08-06 11:49:06.000000000 +0200 @@ -111,6 +111,48 @@ require.Len(t, ent, 0) // No keys discovered } +func TestInitDBWithoutCacheReturnsAlpineKeyFetchError(t *testing.T) { + const repository = "https://example.invalid/alpine/v3.22/main" + + src := apkfs.NewMemFS() + a, err := New(t.Context(), + WithFS(src), + WithIgnoreMknodErrors(ignoreMknodErrors), + WithTransport(&testLocalTransport{fail: true}), + ) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + err = a.InitDB(ctx, repository) + var got *AlpineKeyFetchError + require.ErrorAs(t, err, &got) + require.Equal(t, &AlpineKeyFetchError{ + Repository: repository, + Version: "v3.22", + Err: got.Err, + }, got) + require.ErrorIs(t, err, context.Canceled) +} + +func TestInitDBOfflineWithoutCacheIgnoresMissingAlpineKeys(t *testing.T) { + src := apkfs.NewMemFS() + a, err := New(t.Context(), + WithFS(src), + WithIgnoreMknodErrors(ignoreMknodErrors), + WithOffline(true), + ) + require.NoError(t, err) + + err = a.InitDB(t.Context(), "https://example.invalid/alpine/v3.22/main") + require.NoError(t, err) + + ent, err := fs.ReadDir(src, "etc/apk/keys") + require.NoError(t, err) + require.Empty(t, ent) +} + func TestInitDB_ChainguardDiscovery(t *testing.T) { src := apkfs.NewMemFS() apk, err := New(t.Context(), WithFS(src), WithIgnoreMknodErrors(ignoreMknodErrors)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/apk/apk/options.go new/apko-1.2.35/pkg/apk/apk/options.go --- old/apko-1.2.32/pkg/apk/apk/options.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/apk/apk/options.go 2026-08-06 11:49:06.000000000 +0200 @@ -33,6 +33,7 @@ fs apkfs.FullFS version string cache *cache + offline bool noSignatureIndexes []string auth auth.Authenticator ignoreSignatures bool @@ -113,10 +114,19 @@ } } o.cache = &cache{ - dir: cacheDir, - offline: offline, - shared: shared, + dir: cacheDir, + shared: shared, } + o.offline = offline + return nil + } +} + +// WithOffline controls whether network requests are permitted. Cached and +// local resources remain available in offline mode. +func WithOffline(offline bool) Option { + return func(o *opts) error { + o.offline = offline return nil } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/apk/apk/transport.go new/apko-1.2.35/pkg/apk/apk/transport.go --- old/apko-1.2.32/pkg/apk/apk/transport.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/apk/apk/transport.go 2026-08-06 11:49:06.000000000 +0200 @@ -21,6 +21,25 @@ "net/http" ) +// OfflineNetworkError reports a network request blocked by offline mode. +type OfflineNetworkError struct { + Method string + URL string +} + +func (e *OfflineNetworkError) Error() string { + return fmt.Sprintf("network request blocked in offline mode: %s %s", e.Method, e.URL) +} + +type offlineTransport struct{} + +func (offlineTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return nil, &OfflineNetworkError{ + Method: req.Method, + URL: req.URL.Redacted(), + } +} + type rangeRetryTransport struct { base http.RoundTripper } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/build/build.go new/apko-1.2.35/pkg/build/build.go --- old/apko-1.2.32/pkg/build/build.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/build/build.go 2026-08-06 11:49:06.000000000 +0200 @@ -289,22 +289,18 @@ HTTPResponseMaxSize: bc.o.SizeLimits.HTTPResponseMaxSize, }), } - // only try to pass the cache dir if one of the following is true: - // - the user has explicitly set a cache dir - // - the user's system-determined cachedir, as set by os.UserCacheDir(), can be found - // if neither of these are true, then we don't want to pass a cache dir, because - // go-apk will try to set it to os.UserCacheDir() which returns an error if $HOME - // is not set. - - // note that this is not easy to do in a switch statement, because of the second - // condition, if err := ...; err == nil {} - if bc.o.CacheDir != "" { - apkOpts = append(apkOpts, apk.WithCache(bc.o.CacheDir, bc.o.Offline, bc.o.SharedCache)) - } else if _, err := os.UserCacheDir(); err == nil { - apkOpts = append(apkOpts, apk.WithCache(bc.o.CacheDir, bc.o.Offline, bc.o.SharedCache)) - } else { - log.Warnf("cache disabled because cache dir was not set, and cannot determine system default: %v", err) + // WithCache resolves an empty directory through os.UserCacheDir. Check it + // here so builds can run in environments without a system cache directory. + if bc.o.DiskCacheEnabled { + if bc.o.CacheDir != "" { + apkOpts = append(apkOpts, apk.WithCache(bc.o.CacheDir, bc.o.Offline, bc.o.SharedCache)) + } else if _, err := os.UserCacheDir(); err == nil { + apkOpts = append(apkOpts, apk.WithCache(bc.o.CacheDir, bc.o.Offline, bc.o.SharedCache)) + } else { + log.Warnf("cache disabled because cache dir was not set, and cannot determine system default: %v", err) + } } + apkOpts = append(apkOpts, apk.WithOffline(bc.o.Offline)) if bc.ic.Contents.BaseImage != nil { imgPath, err := paths.ResolvePath(bc.ic.Contents.BaseImage.Image, bc.o.IncludePaths) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/build/build_test.go new/apko-1.2.35/pkg/build/build_test.go --- old/apko-1.2.32/pkg/build/build_test.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/build/build_test.go 2026-08-06 11:49:06.000000000 +0200 @@ -21,12 +21,15 @@ "fmt" "net/http" "net/http/httptest" + "os" "path/filepath" "strings" + "sync/atomic" "testing" "github.com/stretchr/testify/require" + "chainguard.dev/apko/pkg/apk/apk" "chainguard.dev/apko/pkg/apk/auth" "chainguard.dev/apko/pkg/apk/fs" "chainguard.dev/apko/pkg/build" @@ -127,6 +130,125 @@ require.Equal(t, installed[1].Version, "1.0.0-r0") } +func TestLockImageConfigurationWithoutDiskCache(t *testing.T) { + cacheDir := t.TempDir() + t.Setenv("XDG_CACHE_HOME", cacheDir) + + srv := httptest.NewServer(http.FileServer(http.Dir("testdata/packages"))) + defer srv.Close() + + ic := types.ImageConfiguration{ + Contents: types.ImageContents{ + Repositories: []string{srv.URL}, + Keyring: []string{srv.URL + "/melange.rsa.pub"}, + Packages: []string{"pretend-baselayout"}, + }, + Archs: []types.Architecture{types.ParseArchitecture("amd64")}, + } + + _, _, err := build.LockImageConfiguration(t.Context(), ic, build.WithoutDiskCache()) + require.NoError(t, err) + + entries, err := os.ReadDir(cacheDir) + require.NoError(t, err) + require.Empty(t, entries) +} + +func TestLockImageConfigurationOfflineWithoutDiskCache(t *testing.T) { + t.Run("local repository", func(t *testing.T) { + repository, err := filepath.Abs("testdata/packages") + require.NoError(t, err) + + ic := types.ImageConfiguration{ + Contents: types.ImageContents{ + Repositories: []string{repository}, + Keyring: []string{filepath.Join(repository, "melange.rsa.pub")}, + Packages: []string{"pretend-baselayout"}, + }, + Archs: []types.Architecture{types.ParseArchitecture("amd64")}, + } + + _, _, err = build.LockImageConfiguration(t.Context(), ic, + build.WithOffline(true), + build.WithoutDiskCache(), + ) + require.NoError(t, err) + }) + + t.Run("remote repository", func(t *testing.T) { + var requests atomic.Int64 + files := http.FileServer(http.Dir("testdata/packages")) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests.Add(1) + files.ServeHTTP(w, r) + })) + defer srv.Close() + + ic := types.ImageConfiguration{ + Contents: types.ImageContents{ + Repositories: []string{srv.URL}, + Keyring: []string{srv.URL + "/melange.rsa.pub"}, + Packages: []string{"pretend-baselayout"}, + }, + Archs: []types.Architecture{types.ParseArchitecture("amd64")}, + } + + _, _, err := build.LockImageConfiguration(t.Context(), ic, + build.WithCache(t.TempDir(), true, apk.NewCache(false)), + build.WithoutDiskCache(), + ) + var got *apk.OfflineNetworkError + require.ErrorAs(t, err, &got) + require.Equal(t, &apk.OfflineNetworkError{ + Method: http.MethodGet, + URL: srv.URL + "/melange.rsa.pub", + }, got) + require.Equal(t, int64(0), requests.Load()) + }) +} + +func TestDiskCacheOptions(t *testing.T) { + sharedCache := apk.NewCache(false) + + tests := []struct { + name string + options []build.Option + wantEnabled bool + }{ + { + name: "default", + wantEnabled: true, + }, + { + name: "disabled", + options: []build.Option{build.WithoutDiskCache()}, + }, + { + name: "empty directory enables automatic location", + options: []build.Option{ + build.WithoutDiskCache(), + build.WithCache("", false, sharedCache), + }, + wantEnabled: true, + }, + { + name: "last option disables", + options: []build.Option{ + build.WithCache("custom", false, sharedCache), + build.WithoutDiskCache(), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, _, err := build.NewOptions(tt.options...) + require.NoError(t, err) + require.Equal(t, tt.wantEnabled, got.DiskCacheEnabled) + }) + } +} + func TestBuildImageWithCertPackages(t *testing.T) { ctx := context.Background() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/build/options.go new/apko-1.2.35/pkg/build/options.go --- old/apko-1.2.32/pkg/build/options.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/build/options.go 2026-08-06 11:49:06.000000000 +0200 @@ -202,15 +202,35 @@ } } -// WithCache set the cache directory to use +// WithCache enables disk caching in cacheDir. An empty cacheDir uses the +// system cache directory. func WithCache(cacheDir string, offline bool, shared *apk.Cache) Option { return func(bc *Context) error { bc.o.CacheDir = cacheDir + bc.o.DiskCacheEnabled = true bc.o.Offline = offline bc.o.SharedCache = shared return nil } } + +// WithOffline controls whether network requests are permitted. Cached and +// local resources remain available in offline mode. +func WithOffline(offline bool) Option { + return func(bc *Context) error { + bc.o.Offline = offline + return nil + } +} + +// WithoutDiskCache keeps downloaded packages and repository indexes out of +// the filesystem cache. In-memory package-resolution caches remain available. +func WithoutDiskCache() Option { + return func(bc *Context) error { + bc.o.DiskCacheEnabled = false + return nil + } +} func WithLockFile(lockFile string) Option { return func(bc *Context) error { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apko-1.2.32/pkg/options/options.go new/apko-1.2.35/pkg/options/options.go --- old/apko-1.2.32/pkg/options/options.go 2026-08-04 07:48:25.000000000 +0200 +++ new/apko-1.2.35/pkg/options/options.go 2026-08-06 11:49:06.000000000 +0200 @@ -76,6 +76,7 @@ TagSuffix string `json:"tagSuffix,omitempty"` Local bool `json:"local,omitempty"` CacheDir string `json:"cacheDir,omitempty"` + DiskCacheEnabled bool `json:"-"` Offline bool `json:"offline,omitempty"` SharedCache *apk.Cache `json:"-"` Lockfile string `json:"lockfile,omitempty"` @@ -90,11 +91,12 @@ type Auth struct{ User, Pass string } var Default = Options{ - Arch: types.ParseArchitecture(runtime.GOARCH), - SourceDateEpoch: time.Unix(0, 0).UTC(), - Auth: auth.DefaultAuthenticators, - SharedCache: apk.NewCache(false), - SizeLimits: DefaultSizeLimits(), + Arch: types.ParseArchitecture(runtime.GOARCH), + SourceDateEpoch: time.Unix(0, 0).UTC(), + Auth: auth.DefaultAuthenticators, + DiskCacheEnabled: true, + SharedCache: apk.NewCache(false), + SizeLimits: DefaultSizeLimits(), } // Tempdir returns the temporary directory where apko will create ++++++ apko.obsinfo ++++++ --- /var/tmp/diff_new_pack.uaGfcT/_old 2026-08-06 16:41:26.056469562 +0200 +++ /var/tmp/diff_new_pack.uaGfcT/_new 2026-08-06 16:41:26.060469701 +0200 @@ -1,5 +1,5 @@ name: apko -version: 1.2.32 -mtime: 1785822505 -commit: ea62bef5271a0c94f3fba641ee0e8d506b9b57ef +version: 1.2.35 +mtime: 1786009746 +commit: 766682c7e7f87cc932ff02abeb889f5eab5edc9f ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/apko/vendor.tar.gz /work/SRC/openSUSE:Factory/.apko.new.16738/vendor.tar.gz differ: char 13, line 1
