Skip to content

Commit

Permalink
fix: ensure source baseenv not overwritten in local build
Browse files Browse the repository at this point in the history
When building from a local image, we need to insert the base environment
(default runscripts etc), into the rootfs after extracting it. This is
because of an issue with `unsquashfs` that causes it to fail in the presence
of certain symlinks (#3151).

We also want any runscript etc. in the source image to be preservered,
and not replaced with defaults.

Modify the base environment insertion to accept a bool controlling
whether it overwrites any existing files. For the local image case,
where we insert the baseenv after extraction, do not overwrite. In all
other cases keep existing behaviour by setting overwrite to true.

Fixes #3353
  • Loading branch information
dtrudg committed Oct 18, 2024
1 parent 42dc6b3 commit ad8b104
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ orbs:
parameters:
go-version:
type: string
default: '1.22.7'
default: '1.23.0'

executors:
node:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# SingularityCE Changelog

## Changes Since Last Release

### Bug Fixes

- Fix regression from 4.1.5 that overwrites source image runscript, environment
etc. in build from local image.

## 4.2.1 \[2024-09-13\]

### Bug Fixes
Expand Down
44 changes: 41 additions & 3 deletions e2e/build/regressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,14 +637,35 @@ func (c *imgBuildTests) issue2607(t *testing.T) {
}

// Check that the build process from an image doesn't fail when the source image
// includes symlinks.
// includes symlinks. Also confirm that the base environment files from the
// source image are not overwritten (#3353).
func (c *imgBuildTests) issue3084(t *testing.T) {
e2e.EnsureImage(t, c.env)
require.Command(t, "mksquashfs")

// Extract standard test image to a sandbox dir.
rootfs := filepath.Join(c.env.TestDir, "issue_3084_rootfs")
if err := os.Mkdir(rootfs, 0o755); err != nil {
t.Fatal(err)
}
c.env.RunSingularity(
t,
e2e.WithProfile(e2e.UserProfile),
e2e.WithCommand("build"),
e2e.WithArgs("--force", "--sandbox", rootfs, c.env.ImagePath),
e2e.ExpectExit(
0,
),
)

// Remove existing `/tmp`, `/var`. Create `/var/tmp` -> `/tmp` and `/var/log` ->
// `/tmp` symlinks that cause unsquashfs extraction issue.
if err := os.RemoveAll(filepath.Join(rootfs, "tmp")); err != nil {
t.Fatal(err)
}
if err := os.RemoveAll(filepath.Join(rootfs, "var")); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(rootfs, "tmp"), 0o755); err != nil {
t.Fatal(err)
}
Expand All @@ -657,24 +678,41 @@ func (c *imgBuildTests) issue3084(t *testing.T) {
if err := os.Symlink(filepath.Join(rootfs, "tmp"), filepath.Join(rootfs, "var", "log")); err != nil {
t.Fatal(err)
}

// Build from resulting structure as a squashfs
image := filepath.Join(c.env.TestDir, "issue_3084.img")
if err := squashfs.Mksquashfs([]string{rootfs}, image); err != nil {
t.Fatal(err)
}

destImage := filepath.Join(c.env.TestDir, "issue_3084_dest.img")
c.env.RunSingularity(
t,
e2e.WithProfile(e2e.RootProfile),
e2e.WithCommand("build"),
e2e.WithArgs(destImage, image),
e2e.ExpectExit(
0,
),
e2e.PostRun(func(_ *testing.T) {
os.Remove(destImage)
os.Remove(image)
os.RemoveAll(rootfs)
}),
)

// https://github.com/sylabs/singularity/issues/3353
// The source test image runscript outputs "Running command: $*" Make sure
// we see it... the runscript should still be in place.
c.env.RunSingularity(
t,
e2e.WithProfile(e2e.UserProfile),
e2e.WithCommand("run"),
e2e.WithArgs(destImage, "/bin/true"),
e2e.ExpectExit(
0,
e2e.ExpectOutput(e2e.ContainMatch, "Running command: /bin/true"),
),
e2e.PostRun(func(_ *testing.T) {
os.Remove(destImage)
}),
)
}
47 changes: 28 additions & 19 deletions internal/pkg/build/sources/base_environment.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -343,14 +343,18 @@ func makeSymlinks(rootPath string) error {
return nil
}

func makeFile(name string, perm os.FileMode, s string) (err error) {
func makeFile(name string, perm os.FileMode, s string, overwrite bool) (err error) {
// #4532 - If the file already exists ensure it has requested permissions
// as OpenFile won't set on an existing file and some docker
// containers have hosts or resolv.conf without write perm.
if fs.IsFile(name) {
if err = os.Chmod(name, perm); err != nil {
return
}
if !overwrite {
sylog.Debugf("not writing to %s - file exists, overwrite is false", name)
return
}
}
// Create the file if it's not in the container, or truncate and write s
// into it otherwise.
Expand All @@ -364,50 +368,55 @@ func makeFile(name string, perm os.FileMode, s string) (err error) {
return
}

func makeFiles(rootPath string) error {
if err := makeFile(filepath.Join(rootPath, "etc", "hosts"), 0o644, ""); err != nil {
func makeFiles(rootPath string, overwrite bool) error {
if err := makeFile(filepath.Join(rootPath, "etc", "hosts"), 0o644, "", overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, "etc", "resolv.conf"), 0o644, ""); err != nil {
if err := makeFile(filepath.Join(rootPath, "etc", "resolv.conf"), 0o644, "", overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "exec"), 0o755, execFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "exec"), 0o755, execFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "run"), 0o755, runFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "run"), 0o755, runFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "shell"), 0o755, shellFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "shell"), 0o755, shellFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "start"), 0o755, startFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "start"), 0o755, startFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "test"), 0o755, testFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "actions", "test"), 0o755, testFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "01-base.sh"), 0o755, baseShFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "01-base.sh"), 0o755, baseShFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "90-environment.sh"), 0o755, environmentShFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "90-environment.sh"), 0o755, environmentShFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "95-apps.sh"), 0o755, appsShFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "95-apps.sh"), 0o755, appsShFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "99-base.sh"), 0o755, base99ShFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "99-base.sh"), 0o755, base99ShFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "99-runtimevars.sh"), 0o755, base99runtimevarsShFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "env", "99-runtimevars.sh"), 0o755, base99runtimevarsShFileContent, overwrite); err != nil {
return err
}
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "runscript"), 0o755, runscriptFileContent); err != nil {
if err := makeFile(filepath.Join(rootPath, ".singularity.d", "runscript"), 0o755, runscriptFileContent, overwrite); err != nil {
return err
}
return makeFile(filepath.Join(rootPath, ".singularity.d", "startscript"), 0o755, startscriptFileContent)
return makeFile(filepath.Join(rootPath, ".singularity.d", "startscript"), 0o755, startscriptFileContent, overwrite)
}

func makeBaseEnv(rootPath string) (err error) {
// makeBaseEnv inserts Singularity specific directories, symlinks, and files
// into the contatiner rootfs. If overwrite is true, then any existing files
// will be overwritten with new content. If overwrite is false, existing files
// (e.g. where the rootfs has been extracted from an existing image) will not be
// modified.
func makeBaseEnv(rootPath string, overwrite bool) (err error) {
var info os.FileInfo

// Ensure we can write into the root of rootPath
Expand All @@ -431,7 +440,7 @@ func makeBaseEnv(rootPath string) (err error) {
err = fmt.Errorf("build: failed to make environment symlinks: %v", err)
return err
}
if err = makeFiles(rootPath); err != nil {
if err = makeFiles(rootPath, overwrite); err != nil {
err = fmt.Errorf("build: failed to make environment files: %v", err)
return err
}
Expand Down
12 changes: 6 additions & 6 deletions internal/pkg/build/sources/base_environment_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -49,9 +49,9 @@ func TestMakeFiles(t *testing.T) {
if err := makeDirs(d); err != nil {
return err
}
return makeFiles(d)
return makeFiles(d, false)
})
testWithBadDir(t, makeFiles)
testWithBadDir(t, func(d string) error { return makeFiles(d, false) })
// #4532 - Check that we can succeed with an existing file that doesn't have
// write permission.
testWithGoodDir(t, func(d string) error {
Expand All @@ -62,14 +62,14 @@ func TestMakeFiles(t *testing.T) {
if err != nil {
t.Fatalf("Failed to make test hosts file: %s", err)
}
return makeFiles(d)
return makeFiles(d, false)
})
}

func TestMakeBaseEnv(t *testing.T) {
test.DropPrivilege(t)
defer test.ResetPrivilege(t)

testWithGoodDir(t, makeBaseEnv)
testWithBadDir(t, makeBaseEnv)
testWithGoodDir(t, func(d string) error { return makeBaseEnv(d, false) })
testWithBadDir(t, func(d string) error { return makeBaseEnv(d, false) })
}
4 changes: 2 additions & 2 deletions internal/pkg/build/sources/conveyorPacker_arch.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -204,7 +204,7 @@ func (cp *ArchConveyorPacker) getPacConf(pacmanConfURL string) (pacConf string,
}

func (cp *ArchConveyorPacker) insertBaseEnv() (err error) {
if err = makeBaseEnv(cp.b.RootfsPath); err != nil {
if err = makeBaseEnv(cp.b.RootfsPath, true); err != nil {
return
}
return nil
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/build/sources/conveyorPacker_busybox.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (c *BusyBoxConveyor) insertBusyBox(mirrorurl string) (busyBoxPath string, e
}

func (c *BusyBoxConveyor) insertBaseEnv() (err error) {
if err = makeBaseEnv(c.b.RootfsPath); err != nil {
if err = makeBaseEnv(c.b.RootfsPath, true); err != nil {
return
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/build/sources/conveyorPacker_debootstrap.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -267,7 +267,7 @@ func (cp *DebootstrapConveyorPacker) getRecipeHeaderInfo() (err error) {
}

func (cp *DebootstrapConveyorPacker) insertBaseEnv(b *types.Bundle) (err error) {
if err = makeBaseEnv(b.RootfsPath); err != nil {
if err = makeBaseEnv(b.RootfsPath, true); err != nil {
return
}
return nil
Expand Down
8 changes: 2 additions & 6 deletions internal/pkg/build/sources/conveyorPacker_library.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2020, Control Command Inc. All rights reserved.
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -38,10 +38,6 @@ func (cp *LibraryConveyorPacker) Get(ctx context.Context, b *types.Bundle) (err
libraryURL := b.Opts.LibraryURL
authToken := b.Opts.LibraryAuthToken

if err = makeBaseEnv(cp.b.RootfsPath); err != nil {
return fmt.Errorf("while inserting base environment: %v", err)
}

// check for custom library from definition
customLib, ok := b.Recipe.Header["library"]
if ok {
Expand Down Expand Up @@ -82,7 +78,7 @@ func (cp *LibraryConveyorPacker) Get(ctx context.Context, b *types.Bundle) (err
}

// insert base metadata before unpacking fs
if err = makeBaseEnv(cp.b.RootfsPath); err != nil {
if err = makeBaseEnv(cp.b.RootfsPath, true); err != nil {
return fmt.Errorf("while inserting base environment: %v", err)
}

Expand Down
8 changes: 5 additions & 3 deletions internal/pkg/build/sources/conveyorPacker_local.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -112,9 +112,11 @@ func (cp *LocalConveyorPacker) Pack(ctx context.Context) (*types.Bundle, error)
return nil, fmt.Errorf("while unpacking local image: %v", err)
}

// insert base metadata AFTER unpacking fs to avoid conflicts with contained files/symlinks
// Insert base metadata after unpacking fs to avoid unsquashfs failure on
// existing files/symlink. Call makeBaseEnv with overwrite=false so we don't
// overwrite runscripts etc. that were extracted from the image.
sylog.Infof("Inserting Singularity configuration...")
if err = makeBaseEnv(b.RootfsPath); err != nil {
if err = makeBaseEnv(b.RootfsPath, false); err != nil {
return nil, fmt.Errorf("while inserting base environment: %v", err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/build/sources/conveyorPacker_oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func (cp *OCIConveyorPacker) unpackRootfs(ctx context.Context) error {
}

func (cp *OCIConveyorPacker) insertBaseEnv() (err error) {
if err = makeBaseEnv(cp.b.RootfsPath); err != nil {
if err = makeBaseEnv(cp.b.RootfsPath, true); err != nil {
sylog.Errorf("%v", err)
}
return
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/build/sources/conveyorPacker_oras.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020, Sylabs Inc. All rights reserved.
// Copyright (c) 2020-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -45,7 +45,7 @@ func (cp *OrasConveyorPacker) Get(ctx context.Context, b *types.Bundle) (err err
}

// insert base metadata before unpacking fs
if err = makeBaseEnv(b.RootfsPath); err != nil {
if err = makeBaseEnv(b.RootfsPath, true); err != nil {
return fmt.Errorf("while inserting base environment: %v", err)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/build/sources/conveyorPacker_scratch.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -47,7 +47,7 @@ func (cp *ScratchConveyorPacker) Pack(context.Context) (b *types.Bundle, err err
}

func (c *ScratchConveyor) insertBaseEnv() (err error) {
if err = makeBaseEnv(c.b.RootfsPath); err != nil {
if err = makeBaseEnv(c.b.RootfsPath, true); err != nil {
return
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/build/sources/conveyorPacker_shub.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
Expand Down Expand Up @@ -34,7 +34,7 @@ func (cp *ShubConveyorPacker) Get(ctx context.Context, b *types.Bundle) (err err
}

// insert base metadata before unpacking fs
if err = makeBaseEnv(cp.b.RootfsPath); err != nil {
if err = makeBaseEnv(cp.b.RootfsPath, true); err != nil {
return fmt.Errorf("while inserting base environment: %v", err)
}

Expand Down
Loading

0 comments on commit ad8b104

Please sign in to comment.