Skip to content

Commit

Permalink
feat: oci: support --env option in --oci mode
Browse files Browse the repository at this point in the history
* Merge image config ENV and env vars requested by user with the --env
  CLI option.
* Set default SINGULARITY_CONTAINER and SINGULARITY_NAME env variables.
* Set default LD_LIBRARY_PATH to be used later for library
  injection (this is a singularity default).

Fixes sylabs#1029
  • Loading branch information
dtrudg committed Dec 6, 2022
1 parent 3c1fac4 commit 67a1241
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
23 changes: 18 additions & 5 deletions internal/pkg/runtime/launcher/oci/launcher_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@ func checkOpts(lo launcher.Options) error {
badOpt = append(badOpt, "Proot")
}

if len(lo.Env) > 0 {
badOpt = append(badOpt, "Env")
}
if lo.EnvFile != "" {
badOpt = append(badOpt, "EnvFile")
}
Expand Down Expand Up @@ -230,8 +227,9 @@ func checkOpts(lo launcher.Options) error {
}

// createSpec produces an OCI runtime specification, suitable to launch a
// container. This spec excludes ProcessArgs, as these have to be computed where
// the image config is available, to account for the image's CMD / ENTRYPOINT.
// container. This spec excludes ProcessArgs and Env, as these have to be
// computed where the image config is available, to account for the image's CMD
// / ENTRYPOINT / ENV.
func (l *Launcher) createSpec() (*specs.Spec, error) {
spec := minimalSpec()

Expand Down Expand Up @@ -318,12 +316,20 @@ func (l *Launcher) Exec(ctx context.Context, image string, process string, args
return fmt.Errorf("while creating OCI spec: %w", err)
}

// Assemble the runtime & user-requested environment, which will be merged
// with the image ENV and set in the container at runtime.
rtEnv := defaultEnv(image, bundleDir)
// --env flag
rtEnv = mergeMap(rtEnv, l.cfg.Env)
// TODO - --env-file, SINGULARITYENV_

b, err := native.New(
native.OptBundlePath(bundleDir),
native.OptImageRef(image),
native.OptSysCtx(sysCtx),
native.OptImgCache(imgCache),
native.OptProcessArgs(process, args),
native.OptProcessEnv(rtEnv),
)
if err != nil {
return err
Expand Down Expand Up @@ -351,3 +357,10 @@ func (l *Launcher) Exec(ctx context.Context, image string, process string, args
}
return err
}

func mergeMap(a map[string]string, b map[string]string) map[string]string {
for k, v := range b {
a[k] = v
}
return a
}
8 changes: 8 additions & 0 deletions internal/pkg/runtime/launcher/oci/process_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,11 @@ func (l *Launcher) getReverseUserMaps() (uidMap, gidMap []specs.LinuxIDMapping,

return uidMap, gidMap, nil
}

// defaultEnv returns default environment variables set in the container.
func defaultEnv(image, bundle string) map[string]string {
return map[string]string{
"SINGULARITY_CONTAINER": bundle,
"SINGULARITY_NAME": image,
}
}
77 changes: 77 additions & 0 deletions pkg/ocibundle/native/bundle_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import (
"github.com/sylabs/singularity/pkg/sylog"
)

const singularityLibs = "/.singularity.d/libs"

// Bundle is a native OCI bundle, created from imageRef.
type Bundle struct {
// imageRef is the reference to the OCI image source, e.g. docker://ubuntu:latest.
Expand All @@ -53,6 +55,8 @@ type Bundle struct {
process string
// args are the command arguments, which may override the image's CMD.
args []string
// env is the container environment to set, which will be merged with the image's env.
env map[string]string
// Generic bundle properties
ocibundle.Bundle
}
Expand Down Expand Up @@ -104,6 +108,14 @@ func OptProcessArgs(process string, args []string) Option {
}
}

// OptEnv sets the environment to be set, merged with the image ENV.
func OptProcessEnv(env map[string]string) Option {
return func(b *Bundle) error {
b.env = env
return nil
}
}

// New returns a bundle interface to create/delete an OCI bundle from an OCI image ref.
func New(opts ...Option) (ocibundle.Bundle, error) {
b := Bundle{
Expand Down Expand Up @@ -156,6 +168,8 @@ func (b *Bundle) Create(ctx context.Context, ociConfig *specs.Spec) error {
// consult the image Config to handle combining ENTRYPOINT/CMD with user
// provided args.
b.setProcessArgs(g)
// Ditto for environment handling (merge image and user/rt requested).
b.setProcessEnv(g)

return b.writeConfig(g)
}
Expand Down Expand Up @@ -185,6 +199,69 @@ func (b *Bundle) setProcessArgs(g *generate.Generator) {
g.SetProcessArgs(processArgs)
}

// setProcessEnv compines the image config ENV with the ENV requested in the runtime provided spec.
// APPEND_PATH and PREPEND_PATH are honored as with the native singularity runtime.
// LD_LIBRARY_PATH is modified to always include the singularity lib bind directory.
func (b *Bundle) setProcessEnv(g *generate.Generator) {
if g.Config == nil {
g.Config = &specs.Spec{}
}
if g.Config.Process == nil {
g.Config.Process = &specs.Process{}
}
g.Config.Process.Env = b.imageSpec.Config.Env

path := ""
appendPath := ""
prependPath := ""
ldLibraryPath := ""

// Obtain PATH, and LD_LIBRARY_PATH if set in the image config.
for _, env := range b.imageSpec.Config.Env {
e := strings.SplitN(env, "=", 2)
if len(e) < 2 {
continue
}
if e[0] == "PATH" {
path = e[1]
}
if e[0] == "LD_LIBRARY_PATH" {
ldLibraryPath = e[1]
}
}

// Apply env vars from spec, except PATH and LD_LIBRARY_PATH releated.
for k, v := range b.env {
switch k {
case "PATH":
path = v
case "APPEND_PATH":
appendPath = v
case "PREPEND_PATH":
prependPath = v
case "LD_LIBRARY_PATH":
ldLibraryPath = v
default:
g.AddProcessEnv(k, v)
}
}

// Compute and set optionally APPEND-ed / PREPEND-ed PATH.
if appendPath != "" {
path = path + ":" + appendPath
}
if prependPath != "" {
path = prependPath + ":" + path
}
g.AddProcessEnv("PATH", path)

// Ensure LD_LIBRARY_PATH always contains singularity lib binding dir.
if !strings.Contains(ldLibraryPath, singularityLibs) {
ldLibraryPath = strings.TrimPrefix(ldLibraryPath+":"+singularityLibs, ":")
}
g.AddProcessEnv("LD_LIBRARY_PATH", ldLibraryPath)
}

func (b *Bundle) writeConfig(g *generate.Generator) error {
return tools.SaveBundleConfig(b.bundlePath, g)
}
Expand Down

0 comments on commit 67a1241

Please sign in to comment.