return err
}
+ // Set up a cache and tmp dir for singularity build
+ err = os.Mkdir(e.tmpdir+"/cache", 0700)
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(e.tmpdir + "/cache")
+ err = os.Mkdir(e.tmpdir+"/tmp", 0700)
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(e.tmpdir + "/tmp")
+
build := exec.Command("singularity", "build", imageFilename, "docker-archive://"+e.tmpdir+"/image.tar")
+ build.Env = os.Environ()
+ build.Env = append(build.Env, "SINGULARITY_CACHEDIR="+e.tmpdir+"/cache")
+ build.Env = append(build.Env, "SINGULARITY_TMPDIR="+e.tmpdir+"/tmp")
e.logf("%v", build.Args)
out, err := build.CombinedOutput()
// INFO: Starting build...
return nil
}
-func (e *singularityExecutor) Start() error {
- args := []string{"singularity", "exec", "--containall", "--no-home", "--cleanenv", "--pwd", e.spec.WorkingDir}
+func (e *singularityExecutor) execCmd(path string) *exec.Cmd {
+ args := []string{path, "exec", "--containall", "--cleanenv", "--pwd", e.spec.WorkingDir}
if !e.spec.EnableNetwork {
args = append(args, "--net", "--network=none")
}
+
+ if e.spec.CUDADeviceCount != 0 {
+ args = append(args, "--nv")
+ }
+
readonlyflag := map[bool]string{
false: "rw",
true: "ro",
sort.Strings(binds)
for _, path := range binds {
mount := e.spec.BindMounts[path]
- args = append(args, "--bind", mount.HostPath+":"+path+":"+readonlyflag[mount.ReadOnly])
+ if path == e.spec.Env["HOME"] {
+ // Singularity treates $HOME as special case
+ args = append(args, "--home", mount.HostPath+":"+path)
+ } else {
+ args = append(args, "--bind", mount.HostPath+":"+path+":"+readonlyflag[mount.ReadOnly])
+ }
}
// This is for singularity 3.5.2. There are some behaviors
env := make([]string, 0, len(e.spec.Env))
for k, v := range e.spec.Env {
if k == "HOME" {
- // $HOME is a special case on Singularity 3.5,
- // but is just a normal variable on Singularity 3.6+
- // I think this will work with both
- args = append(args, "--home="+v)
+ // Singularity treates $HOME as special case, this is handled
+ // with --home above
+ continue
}
env = append(env, "SINGULARITYENV_"+k+"="+v)
}
+ // Singularity always makes all nvidia devices visible to the
+ // container. If a resource manager such as slurm or LSF told
+ // us to select specific devices we need to propagate that.
+ if cudaVisibleDevices := os.Getenv("CUDA_VISIBLE_DEVICES"); cudaVisibleDevices != "" {
+ // If a resource manager such as slurm or LSF told
+ // us to select specific devices we need to propagate that.
+ env = append(env, "SINGULARITYENV_CUDA_VISIBLE_DEVICES="+cudaVisibleDevices)
+ }
+ // Singularity's default behavior is to evaluate each
+ // SINGULARITYENV_* env var with a shell as a double-quoted
+ // string and pass the result to the contained
+ // process. Singularity 3.10+ has an option to pass env vars
+ // through literally without evaluating, which is what we
+ // want. See https://github.com/sylabs/singularity/pull/704
+ // and https://dev.arvados.org/issues/19081
+ env = append(env, "SINGULARITY_NO_EVAL=1")
+
args = append(args, e.imageFilename)
args = append(args, e.spec.Command...)
- path, err := exec.LookPath(args[0])
- if err != nil {
- return err
- }
- child := &exec.Cmd{
+ return &exec.Cmd{
Path: path,
Args: args,
Env: env,
Stdout: e.spec.Stdout,
Stderr: e.spec.Stderr,
}
+}
+
+func (e *singularityExecutor) Start() error {
+ path, err := exec.LookPath("singularity")
+ if err != nil {
+ return err
+ }
+ child := e.execCmd(path)
err = child.Start()
if err != nil {
return err