Skip to content

Commit 7c46b75

Browse files
committed
Add --compat short-hand for improved OCI/Docker compatibility
It is common for users to run docker containers that expect more isolation than is default in Singularity, and that can create files on startup. This is a simple short-hand to enable `--contain-all, --no-init, --no-umask, --writable-tmpfs`. These options give the best chance of an OCI/Docker container working as expected but *without* requiring the user/uts/net namespaces that we can't rely on in all installations / configurations of SingularityCE. Fixes: #75
1 parent 8e53cb8 commit 7c46b75

File tree

4 files changed

+92
-10
lines changed

4 files changed

+92
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
- `--writable-tmpfs` can be used with `singularity build` to run the `%test`
2121
section of the build with a ephemeral tmpfs overlay, permitting tests that
2222
write to the container filesystem.
23+
- `--compat` flag for actions is a new short-hand to enable a number of
24+
options that increase OCI/Docker compatibility. Infers `--containall,
25+
--no-init, --no-umask, --writable-tmpfs`. Does not use user, uts, or
26+
network namespaces as these may not be supported on many installations.
2327

2428
### Bug Fixes
2529

cmd/internal/cli/action_flags.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var (
3939
IsBoot bool
4040
IsFakeroot bool
4141
IsCleanEnv bool
42+
IsCompat bool
4243
IsContained bool
4344
IsContainAll bool
4445
IsWritable bool
@@ -342,6 +343,16 @@ var actionCleanEnvFlag = cmdline.Flag{
342343
EnvKeys: []string{"CLEANENV"},
343344
}
344345

346+
// --compat
347+
var actionCompatFlag = cmdline.Flag{
348+
ID: "actionCompatFlag",
349+
Value: &IsCompat,
350+
DefaultValue: false,
351+
Name: "compat",
352+
Usage: "apply settings for increased OCI/Docker compatibility. Infers --containall, --no-init, --no-umask, --writable-tmpfs.",
353+
EnvKeys: []string{"COMPAT"},
354+
}
355+
345356
// -c|--contain
346357
var actionContainFlag = cmdline.Flag{
347358
ID: "actionContainFlag",
@@ -643,6 +654,7 @@ func init() {
643654
cmdManager.RegisterFlagForCmd(&actionApplyCgroupsFlag, actionsInstanceCmd...)
644655
cmdManager.RegisterFlagForCmd(&actionBindFlag, actionsInstanceCmd...)
645656
cmdManager.RegisterFlagForCmd(&actionCleanEnvFlag, actionsInstanceCmd...)
657+
cmdManager.RegisterFlagForCmd(&actionCompatFlag, actionsInstanceCmd...)
646658
cmdManager.RegisterFlagForCmd(&actionContainAllFlag, actionsInstanceCmd...)
647659
cmdManager.RegisterFlagForCmd(&actionContainFlag, actionsInstanceCmd...)
648660
cmdManager.RegisterFlagForCmd(&actionContainLibsFlag, actionsInstanceCmd...)

cmd/internal/cli/actions.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ func actionPreRun(cmd *cobra.Command, args []string) {
5555
// set PATH after pulling images to be able to find potential
5656
// docker credential helpers outside of standard paths
5757
os.Setenv("PATH", defaultPath)
58+
59+
// --compat infers other options that give increased OCI / Docker compatibility
60+
// Excludes uts/user/net namespaces as these are restrictive for many Singularity
61+
// installs.
62+
if IsCompat {
63+
IsContainAll = true
64+
IsWritableTmpfs = true
65+
NoInit = true
66+
NoUmask = true
67+
}
5868
}
5969

6070
func handleOCI(ctx context.Context, imgCache *cache.Handle, cmd *cobra.Command, pullFrom string) (string, error) {

e2e/actions/actions.go

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,19 @@ func (c actionTests) actionExec(t *testing.T) {
8585
user := e2e.CurrentUser(t)
8686

8787
// Create a temp testfile
88-
testdata, err := fs.MakeTmpDir(c.env.TestDir, "testdata", 0755)
88+
testdata, err := fs.MakeTmpDir(c.env.TestDir, "testdata", 0o755)
8989
if err != nil {
9090
t.Fatal(err)
9191
}
9292
defer os.RemoveAll(testdata)
9393

9494
testdataTmp := filepath.Join(testdata, "tmp")
95-
if err := os.Mkdir(testdataTmp, 0755); err != nil {
95+
if err := os.Mkdir(testdataTmp, 0o755); err != nil {
9696
t.Fatal(err)
9797
}
9898

9999
// Create a temp testfile
100-
tmpfile, err := fs.MakeTmpFile(testdataTmp, "testSingularityExec.", 0644)
100+
tmpfile, err := fs.MakeTmpFile(testdataTmp, "testSingularityExec.", 0o644)
101101
if err != nil {
102102
t.Fatal(err)
103103
}
@@ -1085,19 +1085,19 @@ func (c actionTests) actionBinds(t *testing.T) {
10851085
}
10861086
})(t)
10871087

1088-
if err := fs.Mkdir(hostCanaryDir, 0777); err != nil {
1088+
if err := fs.Mkdir(hostCanaryDir, 0o777); err != nil {
10891089
t.Fatalf("failed to create canary_dir: %s", err)
10901090
}
10911091
if err := fs.Touch(hostCanaryFile); err != nil {
10921092
t.Fatalf("failed to create canary_file: %s", err)
10931093
}
1094-
if err := os.Chmod(hostCanaryFile, 0777); err != nil {
1094+
if err := os.Chmod(hostCanaryFile, 0o777); err != nil {
10951095
t.Fatalf("failed to apply permissions on canary_file: %s", err)
10961096
}
1097-
if err := fs.Mkdir(hostHomeDir, 0777); err != nil {
1097+
if err := fs.Mkdir(hostHomeDir, 0o777); err != nil {
10981098
t.Fatalf("failed to create workspace home directory: %s", err)
10991099
}
1100-
if err := fs.Mkdir(hostWorkDir, 0777); err != nil {
1100+
if err := fs.Mkdir(hostWorkDir, 0o777); err != nil {
11011101
t.Fatalf("failed to create workspace work directory: %s", err)
11021102
}
11031103
}
@@ -1562,7 +1562,7 @@ func (c actionTests) fuseMount(t *testing.T) {
15621562
t.Errorf("could not read ssh private key: %s", err)
15631563
return
15641564
}
1565-
if err := ioutil.WriteFile(userPrivKey, content, 0600); err != nil {
1565+
if err := ioutil.WriteFile(userPrivKey, content, 0o600); err != nil {
15661566
t.Errorf("could not write ssh user private key: %s", err)
15671567
return
15681568
}
@@ -1768,7 +1768,7 @@ func (c actionTests) bindImage(t *testing.T) {
17681768
if err != nil {
17691769
t.Fatal(err)
17701770
}
1771-
if err := os.Chmod(squashDir, 0755); err != nil {
1771+
if err := os.Chmod(squashDir, 0o755); err != nil {
17721772
t.Fatal(err)
17731773
}
17741774

@@ -2073,7 +2073,6 @@ func (c actionTests) actionUmask(t *testing.T) {
20732073
e2e.ExpectOutput(e2e.ExactMatch, "0022"),
20742074
),
20752075
)
2076-
20772076
}
20782077

20792078
func (c actionTests) actionNoMount(t *testing.T) {
@@ -2192,6 +2191,62 @@ func (c actionTests) actionNoMount(t *testing.T) {
21922191
}
21932192
}
21942193

2194+
// actionCompat checks that the --compat flag sets up the expected environment
2195+
// for improved oci/docker compatibility
2196+
func (c actionTests) actionCompat(t *testing.T) {
2197+
e2e.EnsureImage(t, c.env)
2198+
2199+
type test struct {
2200+
name string
2201+
args []string
2202+
exitCode int
2203+
expect e2e.SingularityCmdResultOp
2204+
}
2205+
2206+
tests := []test{
2207+
{
2208+
name: "containall",
2209+
args: []string{"compat", c.env.ImagePath, "sh", "-c", "ls -lah $HOME"},
2210+
exitCode: 0,
2211+
expect: e2e.ExpectOutput(e2e.ContainMatch, "total 0"),
2212+
},
2213+
{
2214+
name: "writable-tmpfs",
2215+
args: []string{"compat", c.env.ImagePath, "sh", "-c", "touch /test"},
2216+
exitCode: 0,
2217+
},
2218+
{
2219+
name: "no-init",
2220+
args: []string{"compat", c.env.ImagePath, "sh", "-c", "ps"},
2221+
exitCode: 0,
2222+
expect: e2e.ExpectOutput(e2e.UnwantedContainMatch, "sinit"),
2223+
},
2224+
{
2225+
name: "no-umask",
2226+
args: []string{"compat", c.env.ImagePath, "sh", "-c", "umask"},
2227+
exitCode: 0,
2228+
expect: e2e.ExpectOutput(e2e.ContainMatch, "0022"),
2229+
},
2230+
}
2231+
2232+
oldUmask := syscall.Umask(0)
2233+
defer syscall.Umask(oldUmask)
2234+
2235+
for _, tt := range tests {
2236+
c.env.RunSingularity(
2237+
t,
2238+
e2e.AsSubtest(tt.name),
2239+
e2e.WithProfile(e2e.UserProfile),
2240+
e2e.WithCommand("exec"),
2241+
e2e.WithArgs(tt.args...),
2242+
e2e.ExpectExit(
2243+
tt.exitCode,
2244+
tt.expect,
2245+
),
2246+
)
2247+
}
2248+
}
2249+
21952250
// E2ETests is the main func to trigger the test suite
21962251
func E2ETests(env e2e.TestEnv) testhelper.Tests {
21972252
c := actionTests{
@@ -2232,6 +2287,7 @@ func E2ETests(env e2e.TestEnv) testhelper.Tests {
22322287
"bind image": c.bindImage, // test bind image
22332288
"umask": c.actionUmask, // test umask propagation
22342289
"no-mount": c.actionNoMount, // test --no-mount
2290+
"compat": c.actionCompat, // test --compat
22352291
"invalidRemote": np(c.invalidRemote), // GHSA-5mv9-q7fq-9394
22362292
}
22372293
}

0 commit comments

Comments
 (0)