Skip to content

Commit 0a61443

Browse files
author
github-actions
committed
Merge branch 'main' into cras
2 parents aee00f9 + 11905f8 commit 0a61443

File tree

26 files changed

+477
-226
lines changed

26 files changed

+477
-226
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
a new file, the container sees the update in `/etc/resolv.conf`.
2020
- Correctly escape ENV vars when importing OCI containers to native SIF, so that
2121
they match podman / docker behaviour.
22+
- Clarify error when trying to build --oci from a non-Dockerfile spec.
23+
- When images are pulled implicitly by actions (run/shell/exec...), and the
24+
cache is disabled, correctly clean up the temporary files.
25+
- Ensure singularity-buildkitd runs effective GC at the start of each run.
26+
- Apply --debug flag to buildkit logging correctly.
2227

2328
### New Features & Functionality
2429

cmd/internal/cli/actions.go

Lines changed: 145 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2020, Control Command Inc. All rights reserved.
2-
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
2+
// Copyright (c) 2018-2026, Sylabs Inc. All rights reserved.
33
// This software is licensed under a 3-clause BSD license. Please consult the
44
// LICENSE.md file distributed with the sources of this project regarding your
55
// rights to use or distribute this software.
@@ -11,6 +11,7 @@ import (
1111
"errors"
1212
"fmt"
1313
"os"
14+
"path/filepath"
1415
"strings"
1516

1617
"github.com/spf13/cobra"
@@ -53,14 +54,15 @@ type contextKey string
5354

5455
const (
5556
keyOrigImageURI contextKey = "origImageURI"
57+
keyPullTempDir contextKey = "pullTempDir"
5658
)
5759

5860
// actionPreRun will:
5961
// - do the proper path unsetting;
6062
// - and implement flag inferences for:
6163
// --compat
6264
// --hostname
63-
// - run replaceURIWithImage;
65+
// - retrieve remote images to the cache or a temporary directory for execution
6466
func actionPreRun(cmd *cobra.Command, args []string) {
6567
// For compatibility - we still set USER_PATH so it will be visible in the
6668
// container, and can be used there if needed. USER_PATH is not used by
@@ -87,39 +89,76 @@ func actionPreRun(cmd *cobra.Command, args []string) {
8789
utsNamespace = true
8890
}
8991

90-
origImageURI := replaceURIWithImage(cmd.Context(), cmd, args)
92+
// Store the original image URI in the command context, so it can be used by
93+
// any fallback logic.
94+
origImageURI := args[0]
9195
cmd.SetContext(context.WithValue(cmd.Context(), keyOrigImageURI, &origImageURI))
92-
}
9396

94-
func handleOCI(ctx context.Context, imgCache *cache.Handle, cmd *cobra.Command, pullFrom string) (string, error) {
95-
ociAuth, err := makeOCICredentials(cmd)
96-
if err != nil {
97-
sylog.Fatalf("While creating Docker credentials: %v", err)
98-
}
97+
// Replace remote URI with a local image path, pulling to cache or a
98+
// temporary directory as needed.
99+
localImage, pullTempDir := uriToImage(cmd.Context(), cmd, origImageURI)
100+
args[0] = localImage
99101

100-
pullOpts := oci.PullOptions{
101-
TmpDir: tmpDir,
102-
OciAuth: ociAuth,
103-
DockerHost: dockerHost,
104-
NoHTTPS: noHTTPS,
105-
OciSif: isOCI,
106-
KeepLayers: keepLayers,
107-
Platform: getOCIPlatform(),
108-
ReqAuthFile: reqAuthFile,
109-
}
102+
// Track the pullTempDir (if set) in the context, so it can be cleaned up on container exit.
103+
cmd.SetContext(context.WithValue(cmd.Context(), keyPullTempDir, &pullTempDir))
104+
}
110105

111-
return oci.Pull(ctx, imgCache, pullFrom, pullOpts)
106+
func uriToCacheImage(ctx context.Context, refType string, cmd *cobra.Command, imgCache *cache.Handle, pullFrom string) (string, error) {
107+
switch refType {
108+
case uri.Library:
109+
return handleLibrary(ctx, imgCache, "", pullFrom)
110+
case uri.Oras:
111+
ociAuth, err := makeOCICredentials(cmd)
112+
if err != nil {
113+
return "", fmt.Errorf("while creating docker credentials: %v", err)
114+
}
115+
return oras.PullToCache(ctx, imgCache, pullFrom, ociAuth, reqAuthFile)
116+
case uri.Shub:
117+
return shub.PullToCache(ctx, imgCache, pullFrom, noHTTPS)
118+
case ociimage.SupportedTransport(refType):
119+
return handleOCI(ctx, cmd, imgCache, "", pullFrom)
120+
case uri.HTTP:
121+
return net.PullToCache(ctx, imgCache, pullFrom)
122+
case uri.HTTPS:
123+
return net.PullToCache(ctx, imgCache, pullFrom)
124+
default:
125+
return "", fmt.Errorf("unsupported transport type: %s", refType)
126+
}
112127
}
113128

114-
func handleOras(ctx context.Context, imgCache *cache.Handle, cmd *cobra.Command, pullFrom string) (string, error) {
115-
ociAuth, err := makeOCICredentials(cmd)
129+
func uriToTempImage(ctx context.Context, refType string, cmd *cobra.Command, imgCache *cache.Handle, pullFrom string) (string, string, error) {
130+
pullTempDir, err := os.MkdirTemp(tmpDir, "singularity-action-pull-")
116131
if err != nil {
117-
return "", fmt.Errorf("while creating docker credentials: %v", err)
132+
return "", "", fmt.Errorf("unable to create temporary directory: %w", err)
118133
}
119-
return oras.Pull(ctx, imgCache, pullFrom, tmpDir, ociAuth, reqAuthFile)
134+
tmpImage := filepath.Join(pullTempDir, "image")
135+
sylog.Debugf("Cache disabled, pulling image to temporary file: %s", tmpImage)
136+
137+
switch refType {
138+
case uri.Library:
139+
_, err = handleLibrary(ctx, imgCache, tmpImage, pullFrom)
140+
case uri.Oras:
141+
ociAuth, authErr := makeOCICredentials(cmd)
142+
if authErr != nil {
143+
return "", "", fmt.Errorf("while creating docker credentials: %v", authErr)
144+
}
145+
_, err = oras.PullToFile(ctx, imgCache, tmpImage, pullFrom, ociAuth, reqAuthFile)
146+
case uri.Shub:
147+
_, err = shub.PullToFile(ctx, imgCache, tmpImage, pullFrom, noHTTPS)
148+
case ociimage.SupportedTransport(refType):
149+
_, err = handleOCI(ctx, cmd, imgCache, tmpImage, pullFrom)
150+
case uri.HTTP:
151+
_, err = net.PullToFile(ctx, imgCache, tmpImage, pullFrom)
152+
case uri.HTTPS:
153+
_, err = net.PullToFile(ctx, imgCache, tmpImage, pullFrom)
154+
default:
155+
return "", "", fmt.Errorf("unsupported transport type: %s", refType)
156+
}
157+
158+
return tmpImage, pullTempDir, err
120159
}
121160

122-
func handleLibrary(ctx context.Context, imgCache *cache.Handle, pullFrom string) (string, error) {
161+
func handleLibrary(ctx context.Context, imgCache *cache.Handle, tmpImage, pullFrom string) (string, error) {
123162
r, err := library.NormalizeLibraryRef(pullFrom)
124163
if err != nil {
125164
return "", err
@@ -149,50 +188,71 @@ func handleLibrary(ctx context.Context, imgCache *cache.Handle, pullFrom string)
149188
TmpDir: tmpDir,
150189
Platform: getOCIPlatform(),
151190
}
152-
return library.Pull(ctx, imgCache, r, pullOpts)
153-
}
154191

155-
func handleShub(ctx context.Context, imgCache *cache.Handle, pullFrom string) (string, error) {
156-
return shub.Pull(ctx, imgCache, pullFrom, tmpDir, noHTTPS)
192+
var imagePath string
193+
if tmpImage == "" {
194+
imagePath, err = library.PullToCache(ctx, imgCache, r, pullOpts)
195+
} else {
196+
imagePath, err = library.PullToFile(ctx, imgCache, tmpImage, r, pullOpts)
197+
}
198+
199+
if err != nil && err != library.ErrLibraryPullUnsigned {
200+
return "", err
201+
}
202+
if err == library.ErrLibraryPullUnsigned {
203+
sylog.Warningf("Skipping container verification")
204+
}
205+
return imagePath, nil
157206
}
158207

159-
func handleNet(ctx context.Context, imgCache *cache.Handle, pullFrom string) (string, error) {
160-
return net.Pull(ctx, imgCache, pullFrom, tmpDir)
208+
func handleOCI(ctx context.Context, cmd *cobra.Command, imgCache *cache.Handle, tmpImage, pullFrom string) (string, error) {
209+
ociAuth, err := makeOCICredentials(cmd)
210+
if err != nil {
211+
sylog.Fatalf("While creating Docker credentials: %v", err)
212+
}
213+
214+
pullOpts := oci.PullOptions{
215+
TmpDir: tmpDir,
216+
OciAuth: ociAuth,
217+
DockerHost: dockerHost,
218+
NoHTTPS: noHTTPS,
219+
OciSif: isOCI,
220+
KeepLayers: keepLayers,
221+
Platform: getOCIPlatform(),
222+
ReqAuthFile: reqAuthFile,
223+
}
224+
225+
if tmpImage == "" {
226+
return oci.PullToCache(ctx, imgCache, pullFrom, pullOpts)
227+
}
228+
return oci.PullToFile(ctx, imgCache, tmpImage, pullFrom, pullOpts)
161229
}
162230

163-
func replaceURIWithImage(ctx context.Context, cmd *cobra.Command, args []string) string {
164-
origImageURI := args[0]
165-
t, _ := uri.Split(origImageURI)
231+
// uriToImage will pull a remote image to the cache, or a temporary directory if
232+
// the cache is disabled. It returns a path to the pulled image, and the
233+
// temporary directory that should be removed when the container exits, where
234+
// applicable.
235+
func uriToImage(ctx context.Context, cmd *cobra.Command, origImageURI string) (imagePath, tempDir string) {
236+
refType, _ := uri.Split(origImageURI)
166237
// If joining an instance (instance://xxx), or we have a bare filename then
167238
// no retrieval / conversion is required.
168-
if t == "instance" || t == "" {
169-
return origImageURI
239+
if refType == "instance" || refType == "" {
240+
return origImageURI, ""
170241
}
171242

172-
var image string
173-
var err error
174-
175-
// Create a cache handle only when we know we are using a URI
176243
imgCache := getCacheHandle(cache.Config{Disable: disableCache})
177244
if imgCache == nil {
178245
sylog.Fatalf("failed to create a new image cache handle")
179246
}
180247

181-
switch t {
182-
case uri.Library:
183-
image, err = handleLibrary(ctx, imgCache, origImageURI)
184-
case uri.Oras:
185-
image, err = handleOras(ctx, imgCache, cmd, origImageURI)
186-
case uri.Shub:
187-
image, err = handleShub(ctx, imgCache, origImageURI)
188-
case ociimage.SupportedTransport(t):
189-
image, err = handleOCI(ctx, imgCache, cmd, origImageURI)
190-
case uri.HTTP:
191-
image, err = handleNet(ctx, imgCache, origImageURI)
192-
case uri.HTTPS:
193-
image, err = handleNet(ctx, imgCache, origImageURI)
194-
default:
195-
sylog.Fatalf("Unsupported transport type: %s", t)
248+
// If the cache is disabled, then we pull to a temporary location, which
249+
// will need to be removed on container exit. Otherwise, we pull to the
250+
// cache and run directly from there.
251+
var err error
252+
if disableCache {
253+
imagePath, tempDir, err = uriToTempImage(ctx, refType, cmd, imgCache, origImageURI)
254+
} else {
255+
imagePath, err = uriToCacheImage(ctx, refType, cmd, imgCache, origImageURI)
196256
}
197257

198258
// If we are in OCI mode, then we can still attempt to run from a directory
@@ -206,16 +266,26 @@ func replaceURIWithImage(ctx context.Context, cmd *cobra.Command, args []string)
206266
}
207267
sylog.Warningf("%v", err)
208268
sylog.Warningf("OCI-SIF could not be created, falling back to unpacking OCI bundle in temporary sandbox dir")
209-
return origImageURI
269+
return origImageURI, ""
210270
}
211271

212272
if err != nil {
213273
sylog.Fatalf("Unable to handle %s uri: %v", origImageURI, err)
214274
}
215275

216-
args[0] = image
276+
return imagePath, tempDir
277+
}
217278

218-
return origImageURI
279+
func pullTempDirFromContext(ctx context.Context) string {
280+
pullTempDirPtr := ctx.Value(keyPullTempDir)
281+
if pullTempDirPtr != nil {
282+
pullTempDir, ok := pullTempDirPtr.(*string)
283+
if !ok {
284+
sylog.Fatalf("unable to recover pull temp dir (expected string, found: %T) from context", pullTempDirPtr)
285+
}
286+
return *pullTempDir
287+
}
288+
return ""
219289
}
220290

221291
// ExecCmd represents the exec command
@@ -227,10 +297,11 @@ var ExecCmd = &cobra.Command{
227297
Run: func(cmd *cobra.Command, args []string) {
228298
// singularity exec <image> <command> [args...]
229299
ep := launcher.ExecParams{
230-
Image: args[0],
231-
Action: "exec",
232-
Process: args[1],
233-
Args: args[2:],
300+
Image: args[0],
301+
PullTempDir: pullTempDirFromContext(cmd.Context()),
302+
Action: "exec",
303+
Process: args[1],
304+
Args: args[2:],
234305
}
235306
if err := launchContainer(cmd, ep); err != nil {
236307
sylog.Fatalf("%s", err)
@@ -252,8 +323,9 @@ var ShellCmd = &cobra.Command{
252323
Run: func(cmd *cobra.Command, args []string) {
253324
// singularity shell <image>
254325
ep := launcher.ExecParams{
255-
Image: args[0],
256-
Action: "shell",
326+
Image: args[0],
327+
PullTempDir: pullTempDirFromContext(cmd.Context()),
328+
Action: "shell",
257329
}
258330
if err := launchContainer(cmd, ep); err != nil {
259331
sylog.Fatalf("%s", err)
@@ -275,9 +347,10 @@ var RunCmd = &cobra.Command{
275347
Run: func(cmd *cobra.Command, args []string) {
276348
// singularity run <image> [args...]
277349
ep := launcher.ExecParams{
278-
Image: args[0],
279-
Action: "run",
280-
Args: args[1:],
350+
Image: args[0],
351+
PullTempDir: pullTempDirFromContext(cmd.Context()),
352+
Action: "run",
353+
Args: args[1:],
281354
}
282355
if err := launchContainer(cmd, ep); err != nil {
283356
sylog.Fatalf("%s", err)
@@ -299,9 +372,10 @@ var TestCmd = &cobra.Command{
299372
Run: func(cmd *cobra.Command, args []string) {
300373
// singularity test <image> [args...]
301374
ep := launcher.ExecParams{
302-
Image: args[0],
303-
Action: "test",
304-
Args: args[1:],
375+
Image: args[0],
376+
PullTempDir: pullTempDirFromContext(cmd.Context()),
377+
Action: "test",
378+
Args: args[1:],
305379
}
306380
if err := launchContainer(cmd, ep); err != nil {
307381
sylog.Fatalf("%s", err)
@@ -396,6 +470,7 @@ func launchContainer(cmd *cobra.Command, ep launcher.ExecParams) error {
396470
launcher.OptNoCompat(noCompat),
397471
launcher.OptTmpSandbox(tmpSandbox),
398472
launcher.OptNoTmpSandbox(noTmpSandbox),
473+
launcher.OptPullTempDir(ep.PullTempDir),
399474
}
400475

401476
// Explicitly use the interface type here, as we will add alternative launchers later...

cmd/internal/cli/build_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ func runBuild(cmd *cobra.Command, args []string) {
211211
}
212212

213213
if isOCI {
214+
if !fs.IsFile(spec) {
215+
sylog.Fatalf("When building with --oci the build source must be a Dockerfile.")
216+
}
217+
214218
reqArch := ""
215219
if cmd.Flags().Lookup("arch").Changed {
216220
reqArch = buildArgs.arch

cmd/singularity-buildkitd/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ func main() {
4444
RootDir: rootDir,
4545
}
4646

47+
sylog.SyncLogrusLevel()
48+
4749
if err := bkdaemon.Run(context.Background(), daemonOpts, bkSocket); err != nil {
4850
sylog.Fatalf("%s: %v", bkdaemon.DaemonName, err)
4951
}

docs/content.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ Enterprise Performance Computing (EPC)`
4949
5050
BUILD SPEC:
5151
52-
The build spec target is a definition (def) file, local image, or URI that can
53-
be used to create a Singularity container. Several different local target
54-
formats exist:
52+
In native mode, the build spec target is a definition (def) file, local image,
53+
or URI that can be used to create a Singularity container. Several different
54+
local target formats exist:
5555
5656
def file : This is a recipe for building a container (examples below)
5757
directory: A directory structure containing a (ch)root file system
@@ -63,7 +63,10 @@ Enterprise Performance Computing (EPC)`
6363
library:// an image library (default https://cloud.sylabs.io/library)
6464
docker:// a Docker/OCI registry (default Docker Hub)
6565
shub:// a Singularity registry (default Singularity Hub)
66-
oras:// an OCI registry that holds SIF files using ORAS`
66+
oras:// an OCI registry that holds SIF files using ORAS
67+
68+
When run with the --oci flag, the spec must be a valid Dockerfile, and output
69+
is always an OCI-SIF image.`
6770

6871
BuildExample string = `
6972
@@ -160,7 +163,10 @@ Enterprise Performance Computing (EPC)`
160163
Build a base sandbox from DockerHub, make changes to it, then build sif
161164
$ singularity build --sandbox /tmp/debian docker://debian:latest
162165
$ singularity exec --writable /tmp/debian apt-get install python
163-
$ singularity build /tmp/debian2.sif /tmp/debian`
166+
$ singularity build /tmp/debian2.sif /tmp/debian
167+
168+
Build an OCI-SIF image from a Dockerfile:
169+
$ singularity build --oci /tmp/myimage.oci.sif /path/to/Dockerfile`
164170

165171
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
166172
// Cache

0 commit comments

Comments
 (0)