Skip to content

Commit 5278300

Browse files
authored
Merge pull request #3198 from dtrudg/backport-3159
fix: enforce --platform for all OCI images (release-4.1)
2 parents f55975a + b71b597 commit 5278300

File tree

12 files changed

+127
-38
lines changed

12 files changed

+127
-38
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
execution of an OCI-SIF fails.
1111
- Fix failing builds from local images that have symbolic links for paths that
1212
are part of the base container environment (e.g. /var/tmp -> /tmp).
13+
- Fix issue where `--platform` / `--arch` did not apply when pulling an OCI
14+
image to native SIF via image manifest, rather than image index.
1315

1416
## 4.1.4 \[2024-06-28\]
1517

e2e/docker/docker.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,7 @@ func (c ctx) testDockerPlatform(t *testing.T) {
14111411
exit: 0,
14121412
},
14131413
{
1414-
name: "MultiArchBadPlatform",
1414+
name: "MultiArchInvalidPlatform",
14151415
platform: "windows/m68k",
14161416
uri: "docker://alpine:latest",
14171417
exit: 255,
@@ -1429,11 +1429,17 @@ func (c ctx) testDockerPlatform(t *testing.T) {
14291429
exit: 0,
14301430
},
14311431
{
1432-
name: "SingleArchBadPlatform",
1432+
name: "SingleArchInvalidPlatform",
14331433
platform: "windows/m68k",
14341434
uri: "docker://ppc64le/alpine:latest",
14351435
exit: 255,
14361436
},
1437+
{
1438+
name: "SingleArchMissingPlatform",
1439+
platform: "linux/arm64",
1440+
uri: "docker://ppc64le/alpine:latest",
1441+
exit: 255,
1442+
},
14371443
}
14381444

14391445
for _, p := range []e2e.Profile{e2e.UserProfile, e2e.OCIUserProfile} {

internal/pkg/client/library/push.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/sylabs/singularity/v4/internal/pkg/client/ocisif"
1919
"github.com/sylabs/singularity/v4/internal/pkg/client/progress"
2020
"github.com/sylabs/singularity/v4/internal/pkg/remote/endpoint"
21+
"github.com/sylabs/singularity/v4/internal/pkg/util/machine"
2122
"github.com/sylabs/singularity/v4/pkg/sylog"
2223
"golang.org/x/term"
2324
)
@@ -55,7 +56,7 @@ func Push(ctx context.Context, sourceFile string, destRef *scslibrary.Ref, opts
5556

5657
// pushNative pushes a non-OCI SIF image, as a SIF, using the library client.
5758
func pushNative(ctx context.Context, sourceFile string, destRef *scslibrary.Ref, opts PushOptions) (uploadResponse *scslibrary.UploadImageComplete, err error) {
58-
arch, err := sifArch(sourceFile)
59+
arch, err := machine.SifArch(sourceFile)
5960
if err != nil {
6061
return nil, err
6162
}
@@ -99,20 +100,6 @@ func pushNative(ctx context.Context, sourceFile string, destRef *scslibrary.Ref,
99100
return libraryClient.UploadImage(ctx, f, destRef.Path, arch, destRef.Tags, opts.Description, progressBar)
100101
}
101102

102-
func sifArch(filename string) (string, error) {
103-
f, err := sif.LoadContainerFromPath(filename, sif.OptLoadWithFlag(os.O_RDONLY))
104-
if err != nil {
105-
return "", fmt.Errorf("unable to open: %v: %w", filename, err)
106-
}
107-
defer f.UnloadContainer()
108-
109-
arch := f.PrimaryArch()
110-
if arch == "unknown" {
111-
return arch, fmt.Errorf("unknown architecture in SIF file")
112-
}
113-
return arch, nil
114-
}
115-
116103
// pushOCI pushes an OCI SIF image, as an OCI image, using the ocisif client.
117104
func pushOCI(ctx context.Context, sourceFile string, destRef *scslibrary.Ref, opts PushOptions) error {
118105
sylog.Infof("Pushing an OCI-SIF to the library OCI registry. Use `--oci` to pull this image.")

internal/pkg/client/net/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func PullToFile(ctx context.Context, imgCache *cache.Handle, pullTo, pullFrom st
196196

197197
src, err := pull(ctx, imgCache, directTo, pullFrom)
198198
if err != nil {
199-
return "", fmt.Errorf("error fetching image to cache: %v", err)
199+
return "", fmt.Errorf("error fetching image: %v", err)
200200
}
201201

202202
if directTo == "" {

internal/pkg/client/oci/nativesif.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/sylabs/singularity/v4/internal/pkg/build"
1414
"github.com/sylabs/singularity/v4/internal/pkg/cache"
1515
"github.com/sylabs/singularity/v4/internal/pkg/ociimage"
16+
"github.com/sylabs/singularity/v4/internal/pkg/ociplatform"
17+
"github.com/sylabs/singularity/v4/internal/pkg/util/machine"
1618
buildtypes "github.com/sylabs/singularity/v4/pkg/build/types"
1719
"github.com/sylabs/singularity/v4/pkg/sylog"
1820
)
@@ -50,6 +52,18 @@ func pullNativeSIF(ctx context.Context, imgCache *cache.Handle, directTo, pullFr
5052
return "", err
5153
}
5254
} else {
55+
// Ensure what's retrieved from the cache matches the target platform
56+
sifArch, err := machine.SifArch(cacheEntry.Path)
57+
if err != nil {
58+
return "", err
59+
}
60+
sifPlatform, err := ociplatform.PlatformFromArch(sifArch)
61+
if err != nil {
62+
return "", fmt.Errorf("could not determine OCI platform from cached image architecture %q: %w", sifArch, err)
63+
}
64+
if !sifPlatform.Satisfies(opts.Platform) {
65+
return "", fmt.Errorf("image (%s) does not satisfy required platform (%s)", sifPlatform, opts.Platform)
66+
}
5367
sylog.Infof("Using cached SIF image")
5468
}
5569
imagePath = cacheEntry.Path

internal/pkg/client/oci/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func PullToFile(ctx context.Context, imgCache *cache.Handle, pullTo, pullFrom st
9999
src, err = pullNativeSIF(ctx, imgCache, directTo, pullFrom, opts)
100100
}
101101
if err != nil {
102-
return "", fmt.Errorf("error fetching image to cache: %v", err)
102+
return "", fmt.Errorf("error fetching image: %v", err)
103103
}
104104

105105
if directTo == "" {

internal/pkg/client/ocisif/ocisif.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ import (
2222
"github.com/google/go-containerregistry/pkg/v1/remote"
2323
"github.com/google/go-containerregistry/pkg/v1/types"
2424
"github.com/sylabs/oci-tools/pkg/mutate"
25-
ocisif "github.com/sylabs/oci-tools/pkg/sif"
25+
ocitsif "github.com/sylabs/oci-tools/pkg/sif"
2626
"github.com/sylabs/sif/v2/pkg/sif"
2727
"github.com/sylabs/singularity/v4/internal/pkg/cache"
2828
"github.com/sylabs/singularity/v4/internal/pkg/client/progress"
2929
"github.com/sylabs/singularity/v4/internal/pkg/ociimage"
3030
"github.com/sylabs/singularity/v4/internal/pkg/ociplatform"
31+
"github.com/sylabs/singularity/v4/internal/pkg/ocisif"
3132
"github.com/sylabs/singularity/v4/internal/pkg/remote/credential/ociauth"
3233
"github.com/sylabs/singularity/v4/internal/pkg/util/fs"
3334
"github.com/sylabs/singularity/v4/pkg/sylog"
@@ -107,6 +108,19 @@ func PullOCISIF(ctx context.Context, imgCache *cache.Handle, directTo, pullFrom
107108
return "", err
108109
}
109110
} else {
111+
// Ensure what's retrieved from the cache matches the target platform
112+
fi, err := sif.LoadContainerFromPath(cacheEntry.Path)
113+
if err != nil {
114+
return "", err
115+
}
116+
defer fi.UnloadContainer()
117+
img, err := ocisif.GetSingleImage(fi)
118+
if err != nil {
119+
return "", fmt.Errorf("while getting image: %w", err)
120+
}
121+
if err := ociplatform.CheckImagePlatform(opts.Platform, img); err != nil {
122+
return "", err
123+
}
110124
sylog.Infof("Using cached OCI-SIF image")
111125
}
112126
imagePath = cacheEntry.Path
@@ -138,10 +152,6 @@ func createOciSif(ctx context.Context, tOpts *ociimage.TransportOptions, imgCach
138152
return fmt.Errorf("while fetching OCI image: %w", err)
139153
}
140154

141-
if err := ociplatform.CheckImagePlatform(tOpts.Platform, img); err != nil {
142-
return fmt.Errorf("while checking OCI image: %w", err)
143-
}
144-
145155
digest, err := img.Digest()
146156
if err != nil {
147157
return err
@@ -185,7 +195,7 @@ func writeImageToOCISif(img ggcrv1.Image, imageDest string) error {
185195
ii := ggcrmutate.AppendManifests(empty.Index, ggcrmutate.IndexAddendum{
186196
Add: img,
187197
})
188-
return ocisif.Write(imageDest, ii, ocisif.OptWriteWithSpareDescriptorCapacity(spareDescriptorCapacity))
198+
return ocitsif.Write(imageDest, ii, ocitsif.OptWriteWithSpareDescriptorCapacity(spareDescriptorCapacity))
189199
}
190200

191201
// convertImageToOciSif will convert an image to an oci-sif with squashfs layer
@@ -209,7 +219,7 @@ func convertImageToOciSif(img ggcrv1.Image, digest ggcrv1.Hash, imageDest, workD
209219
ii := ggcrmutate.AppendManifests(empty.Index, ggcrmutate.IndexAddendum{
210220
Add: img,
211221
})
212-
return ocisif.Write(imageDest, ii, ocisif.OptWriteWithSpareDescriptorCapacity(spareDescriptorCapacity))
222+
return ocitsif.Write(imageDest, ii, ocitsif.OptWriteWithSpareDescriptorCapacity(spareDescriptorCapacity))
213223
}
214224

215225
func imgLayersToSquashfs(img ggcrv1.Image, digest ggcrv1.Hash, workDir string) (sqfsImage ggcrv1.Image, err error) {
@@ -266,7 +276,7 @@ func PushOCISIF(ctx context.Context, sourceFile, destRef string, ociAuth *authn.
266276
}
267277
defer fi.UnloadContainer()
268278

269-
ix, err := ocisif.ImageIndexFromFileImage(fi)
279+
ix, err := ocitsif.ImageIndexFromFileImage(fi)
270280
if err != nil {
271281
return fmt.Errorf("only OCI-SIF files can be pushed to docker/OCI registries")
272282
}

internal/pkg/client/oras/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func PullToFile(ctx context.Context, imgCache *cache.Handle, pullTo, pullFrom st
8686

8787
src, err := pull(ctx, imgCache, directTo, pullFrom, ociAuth, reqAuthFile)
8888
if err != nil {
89-
return "", fmt.Errorf("error fetching image to cache: %v", err)
89+
return "", fmt.Errorf("error fetching image: %v", err)
9090
}
9191

9292
if directTo == "" {

internal/pkg/client/shub/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ func PullToFile(ctx context.Context, imgCache *cache.Handle, pullTo, pullFrom st
199199

200200
src, err := pull(ctx, imgCache, directTo, pullFrom, noHTTPS)
201201
if err != nil {
202-
return "", fmt.Errorf("error fetching image to cache: %v", err)
202+
return "", fmt.Errorf("error fetching image: %v", err)
203203
}
204204

205205
if directTo == "" {

internal/pkg/ociimage/fetch.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
ggcrv1 "github.com/google/go-containerregistry/pkg/v1"
2121
"github.com/sylabs/singularity/v4/internal/pkg/cache"
2222
"github.com/sylabs/singularity/v4/internal/pkg/client/progress"
23+
"github.com/sylabs/singularity/v4/internal/pkg/ociplatform"
2324
"github.com/sylabs/singularity/v4/pkg/sylog"
2425
)
2526

@@ -56,7 +57,8 @@ func cachedImage(ctx context.Context, imgCache *cache.Handle, srcImg ggcrv1.Imag
5657
// docker daemon, it will be pulled into the local cache - which is a
5758
// multi-image OCI layout. If the cache is disabled, the image will be fetched
5859
// into a subdirectory of the provided tmpDir. The caller is responsible for
59-
// cleaning up tmpDir.
60+
// cleaning up tmpDir. The platform of the image will be checked against
61+
// tOpts.Platform.
6062
func LocalImage(ctx context.Context, tOpts *TransportOptions, imgCache *cache.Handle, imageURI, tmpDir string) (ggcrv1.Image, error) {
6163
// oci-archive tarball is a local file, but current ggcr cannot read
6264
// directly. Must always extract to a layoutdir .
@@ -70,7 +72,16 @@ func LocalImage(ctx context.Context, tOpts *TransportOptions, imgCache *cache.Ha
7072
}
7173
// Docker tarballs and OCI layouts are already local
7274
if srcType == TarballSourceSink || srcType == OCISourceSink {
73-
return srcType.Image(ctx, srcRef, tOpts, nil)
75+
img, err := srcType.Image(ctx, srcRef, tOpts, nil)
76+
if err != nil {
77+
return nil, err
78+
}
79+
// Verify against requested platform - ggcr doesn't filter on platform
80+
// when pulling a manifest directly, only on pulling from an image index.
81+
if err := ociplatform.CheckImagePlatform(tOpts.Platform, img); err != nil {
82+
return nil, fmt.Errorf("while checking OCI image: %w", err)
83+
}
84+
return img, nil
7485
}
7586

7687
return LocalImageLayout(ctx, tOpts, imgCache, imageURI, tmpDir)
@@ -82,7 +93,8 @@ func LocalImage(ctx context.Context, tOpts *TransportOptions, imgCache *cache.Ha
8293
// docker daemon, it will be pulled into the local cache - which is a
8394
// multi-image OCI layout. If the cache is disabled, the image will be fetched
8495
// into a subdirectory of the provided tmpDir. The caller is responsible for
85-
// cleaning up tmpDir.
96+
// cleaning up tmpDir. The platform of the image will be checked against
97+
// tOpts.Platform.
8698
func LocalImageLayout(ctx context.Context, tOpts *TransportOptions, imgCache *cache.Handle, imageURI, tmpDir string) (ggcrv1.Image, error) {
8799
if strings.HasPrefix(imageURI, "oci-archive:") {
88100
// oci-archive is a straight tar of an OCI layout, so extract to a tempDir
@@ -98,19 +110,27 @@ func LocalImageLayout(ctx context.Context, tOpts *TransportOptions, imgCache *ca
98110
return nil, err
99111
}
100112

101-
// We might already have an OCI layout at this point
102-
if srcType == OCISourceSink {
103-
return srcType.Image(ctx, srcRef, tOpts, nil)
104-
}
105-
106-
// Registry / Docker Daemon images need to be fetched
107113
rt := progress.NewRoundTripper(ctx, nil)
108114
srcImg, err := srcType.Image(ctx, srcRef, tOpts, rt)
109115
if err != nil {
110116
rt.ProgressShutdown()
111117
return nil, err
112118
}
113119

120+
// Verify against requested platform - ggcr doesn't filter on platform when
121+
// pulling a manifest directly, only on pulling from an image index.
122+
if err := ociplatform.CheckImagePlatform(tOpts.Platform, srcImg); err != nil {
123+
return nil, fmt.Errorf("while checking OCI image: %w", err)
124+
}
125+
126+
// We might already have an OCI layout at this point - which is local.
127+
if srcType == OCISourceSink {
128+
rt.ProgressShutdown()
129+
return srcImg, nil
130+
}
131+
132+
// Registry / Docker Daemon images need to be fetched
133+
114134
if imgCache != nil && !imgCache.IsDisabled() {
115135
// Ensure the image is cached, and return reference to the cached image.
116136
cachedImg, err := cachedImage(ctx, imgCache, srcImg)

0 commit comments

Comments
 (0)