diff options
-rw-r--r-- | .cirrus.yml | 2 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | cmd/podman/shared/create.go | 71 | ||||
-rw-r--r-- | cmd/podman/shared/parse/parse.go | 19 | ||||
l--------- | contrib/cirrus/apiv2_test.sh | 1 | ||||
-rw-r--r-- | libpod/container_validate.go | 99 | ||||
-rw-r--r-- | libpod/options.go | 87 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 28 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 17 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 15 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 12 | ||||
-rw-r--r-- | pkg/api/server/register_volumes.go | 2 | ||||
-rw-r--r-- | pkg/env/env.go | 126 | ||||
-rw-r--r-- | pkg/inspect/inspect.go | 2 | ||||
-rw-r--r-- | pkg/spec/spec.go | 5 | ||||
-rw-r--r-- | test/apiv2/01-basic.at | 12 | ||||
-rw-r--r-- | test/apiv2/40-pods.at | 3 | ||||
-rwxr-xr-x | test/apiv2/test-apiv2 | 6 |
18 files changed, 337 insertions, 178 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 2106ac96d..151153b14 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -398,6 +398,7 @@ testing_task: unit_test_script: '$SCRIPT_BASE/unit_test.sh |& ${TIMESTAMP}' integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}' system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}' + apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP}' build_release_script: '$SCRIPT_BASE/build_release.sh |& ${TIMESTAMP}' # For PRs this confirms uploading releases after merge, is functional. upload_release_archive_script: '$SCRIPT_BASE/upload_release_archive.sh |& ${TIMESTAMP}' @@ -447,6 +448,7 @@ special_testing_rootless_task: setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}' system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}' + apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP}' on_failure: failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh' @@ -346,6 +346,14 @@ remotesystem: fi;\ exit $$rc +.PHONY: localapiv2 +localapiv2: + env PODMAN=./bin/podman ./test/apiv2/test-apiv2 + +.PHONY: remoteapiv2 +remoteapiv2: + true + .PHONY: system.test-binary system.test-binary: .install.ginkgo $(GO) test -c ./test/system diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 0814eeba3..0ce578bef 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -18,6 +18,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" + envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/inspect" ns "github.com/containers/libpod/pkg/namespaces" @@ -473,19 +474,49 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. } // ENVIRONMENT VARIABLES - env := EnvVariablesFromData(data) + // + // Precedence order (higher index wins): + // 1) env-host, 2) image data, 3) env-file, 4) env + var env map[string]string + + // Start with env-host if c.Bool("env-host") { - for _, e := range os.Environ() { - pair := strings.SplitN(e, "=", 2) - if _, ok := env[pair[0]]; !ok { - if len(pair) > 1 { - env[pair[0]] = pair[1] - } + osEnv, err := envLib.ParseSlice(os.Environ()) + if err != nil { + return nil, errors.Wrap(err, "error parsing host environment variables") + } + env = osEnv + } + + // Image data overrides any previous variables + if data != nil { + configEnv, err := envLib.ParseSlice(data.Config.Env) + if err != nil { + return nil, errors.Wrap(err, "error pasing image environment variables") + } + env = envLib.Join(env, configEnv) + } + + // env-file overrides any previous variables + if c.IsSet("env-file") { + for _, f := range c.StringSlice("env-file") { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return nil, err } + // File env is overridden by env. + env = envLib.Join(env, fileEnv) } } - if err := parse.ReadKVStrings(env, c.StringSlice("env-file"), c.StringArray("env")); err != nil { - return nil, errors.Wrapf(err, "unable to process environment variables") + + // env overrides any previous variables + cmdlineEnv := c.StringSlice("env") + if len(cmdlineEnv) > 0 { + parsedEnv, err := envLib.ParseSlice(cmdlineEnv) + if err != nil { + return nil, err + } + env = envLib.Join(env, parsedEnv) } // LABEL VARIABLES @@ -822,28 +853,6 @@ func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateC return ctr, nil } -var defaultEnvVariables = map[string]string{ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "TERM": "xterm", -} - -// EnvVariablesFromData gets sets the default environment variables -// for containers, and reads the variables from the image data, if present. -func EnvVariablesFromData(data *inspect.ImageData) map[string]string { - env := defaultEnvVariables - if data != nil { - for _, e := range data.Config.Env { - split := strings.SplitN(e, "=", 2) - if len(split) > 1 { - env[split[0]] = split[1] - } else { - env[split[0]] = "" - } - } - } - return env -} - func makeHealthCheckFromCli(c *GenericCLIResults) (*manifest.Schema2HealthConfig, error) { inCommand := c.String("healthcheck-command") inInterval := c.String("healthcheck-interval") diff --git a/cmd/podman/shared/parse/parse.go b/cmd/podman/shared/parse/parse.go index 79449029d..03cda268c 100644 --- a/cmd/podman/shared/parse/parse.go +++ b/cmd/podman/shared/parse/parse.go @@ -90,6 +90,8 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { // all environment variables, even those sourced from files, but // that would require a substantial rework. if err := parseEnvFile(labels, file); err != nil { + // FIXME: parseEnvFile is using parseEnv, so we need to add extra + // logic for labels. return nil, err } } @@ -107,23 +109,6 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { return labels, nil } -// reads a file of line terminated key=value pairs, and overrides any keys -// present in the file with additional pairs specified in the override parameter -// for env-file and labels-file flags -func ReadKVStrings(env map[string]string, files []string, override []string) error { - for _, ef := range files { - if err := parseEnvFile(env, ef); err != nil { - return err - } - } - for _, line := range override { - if err := parseEnv(env, line); err != nil { - return err - } - } - return nil -} - func parseEnv(env map[string]string, line string) error { data := strings.SplitN(line, "=", 2) diff --git a/contrib/cirrus/apiv2_test.sh b/contrib/cirrus/apiv2_test.sh new file mode 120000 index 000000000..cbc481d6b --- /dev/null +++ b/contrib/cirrus/apiv2_test.sh @@ -0,0 +1 @@ +integration_test.sh
\ No newline at end of file diff --git a/libpod/container_validate.go b/libpod/container_validate.go new file mode 100644 index 000000000..b7f0aadff --- /dev/null +++ b/libpod/container_validate.go @@ -0,0 +1,99 @@ +package libpod + +import ( + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +// Validate that the configuration of a container is valid. +func (c *Container) validate() error { + imageIDSet := c.config.RootfsImageID != "" + imageNameSet := c.config.RootfsImageName != "" + rootfsSet := c.config.Rootfs != "" + + // If one of RootfsImageIDor RootfsImageName are set, both must be set. + if (imageIDSet || imageNameSet) && !(imageIDSet && imageNameSet) { + return errors.Wrapf(define.ErrInvalidArg, "both RootfsImageName and RootfsImageID must be set if either is set") + } + + // Cannot set RootfsImageID and Rootfs at the same time + if imageIDSet && rootfsSet { + return errors.Wrapf(define.ErrInvalidArg, "cannot set both an image ID and rootfs for a container") + } + + // Must set at least one of RootfsImageID or Rootfs + if !(imageIDSet || rootfsSet) { + return errors.Wrapf(define.ErrInvalidArg, "must set root filesystem source to either image or rootfs") + } + + // Cannot make a network namespace if we are joining another container's + // network namespace + if c.config.CreateNetNS && c.config.NetNsCtr != "" { + return errors.Wrapf(define.ErrInvalidArg, "cannot both create a network namespace and join another container's network namespace") + } + + // Not creating cgroups has a number of requirements, mostly related to + // the PID namespace. + if c.config.NoCgroups || c.config.CgroupsMode == "disabled" { + if c.config.PIDNsCtr != "" { + return errors.Wrapf(define.ErrInvalidArg, "cannot join another container's PID namespace if not creating cgroups") + } + + if c.config.CgroupParent != "" { + return errors.Wrapf(define.ErrInvalidArg, "cannot set cgroup parent if not creating cgroups") + } + + // Ensure we have a PID namespace + if c.config.Spec.Linux == nil { + return errors.Wrapf(define.ErrInvalidArg, "must provide Linux namespace configuration in OCI spec when using NoCgroups") + } + foundPid := false + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + foundPid = true + if ns.Path != "" { + return errors.Wrapf(define.ErrInvalidArg, "containers not creating CGroups must create a private PID namespace - cannot use another") + } + break + } + } + if !foundPid { + return errors.Wrapf(define.ErrInvalidArg, "containers not creating CGroups must create a private PID namespace") + } + } + + // Rootless has some requirements, compared to networks. + if rootless.IsRootless() { + if len(c.config.Networks) > 0 { + return errors.Wrapf(define.ErrInvalidArg, "cannot join CNI networks if running rootless") + } + + // TODO: Should we make sure network mode is set to Slirp if set + // at all? + } + + // Can only set static IP or MAC is creating a network namespace. + if !c.config.CreateNetNS && (c.config.StaticIP != nil || c.config.StaticMAC != nil) { + return errors.Wrapf(define.ErrInvalidArg, "cannot set static IP or MAC address if not creating a network namespace") + } + + // Cannot set static IP or MAC if joining >1 CNI network. + if len(c.config.Networks) > 1 && (c.config.StaticIP != nil || c.config.StaticMAC != nil) { + return errors.Wrapf(define.ErrInvalidArg, "cannot set static IP or MAC address if joining more than one CNI network") + } + + // Using image resolv.conf conflicts with various DNS settings. + if c.config.UseImageResolvConf && + (len(c.config.DNSSearch) > 0 || len(c.config.DNSServer) > 0 || + len(c.config.DNSOption) > 0) { + return errors.Wrapf(define.ErrInvalidArg, "cannot configure DNS options if using image's resolv.conf") + } + + if c.config.UseImageHosts && len(c.config.HostAdd) > 0 { + return errors.Wrapf(define.ErrInvalidArg, "cannot add to /etc/hosts if using image's /etc/hosts") + } + + return nil +} diff --git a/libpod/options.go b/libpod/options.go index d01e8a85f..98de71af2 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -599,13 +599,6 @@ func WithRootFSFromImage(imageID string, imageName string) CtrCreateOption { return define.ErrCtrFinalized } - if ctr.config.RootfsImageID != "" || ctr.config.RootfsImageName != "" { - return errors.Wrapf(define.ErrInvalidArg, "container already configured with root filesystem") - } - if ctr.config.Rootfs != "" { - return errors.Wrapf(define.ErrInvalidArg, "cannot set both an image ID and a rootfs for a container") - } - ctr.config.RootfsImageID = imageID ctr.config.RootfsImageName = imageName @@ -815,10 +808,6 @@ func WithNetNSFrom(nsCtr *Container) CtrCreateOption { return err } - if ctr.config.CreateNetNS { - return errors.Wrapf(define.ErrInvalidArg, "cannot join another container's net ns as we are making a new net ns") - } - ctr.config.NetNsCtr = nsCtr.ID() return nil @@ -839,10 +828,6 @@ func WithPIDNSFrom(nsCtr *Container) CtrCreateOption { return err } - if ctr.config.NoCgroups { - return errors.Wrapf(define.ErrInvalidArg, "container has disabled creation of CGroups, which is incompatible with sharing a PID namespace") - } - ctr.config.PIDNsCtr = nsCtr.ID() return nil @@ -921,16 +906,8 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { deps := make([]string, 0, len(ctrs)) for _, dep := range ctrs { - if !dep.valid { - return errors.Wrapf(define.ErrCtrRemoved, "container %s is not valid", dep.ID()) - } - - if dep.ID() == ctr.ID() { - return errors.Wrapf(define.ErrInvalidArg, "must specify another container") - } - - if ctr.config.Pod != "" && dep.config.Pod != ctr.config.Pod { - return errors.Wrapf(define.ErrInvalidArg, "container has joined pod %s and dependency container %s is not a member of the pod", ctr.config.Pod, dep.ID()) + if err := checkDependencyContainer(dep, ctr); err != nil { + return err } deps = append(deps, dep.ID()) @@ -952,20 +929,6 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmo return define.ErrCtrFinalized } - if rootless.IsRootless() { - if len(networks) > 0 { - return errors.Wrapf(define.ErrInvalidArg, "cannot use CNI networks with rootless containers") - } - } - - if len(networks) > 1 && (ctr.config.StaticIP != nil || ctr.config.StaticMAC != nil) { - return errors.Wrapf(define.ErrInvalidArg, "cannot join more than one CNI network if configuring a static IP or MAC address") - } - - if ctr.config.NetNsCtr != "" { - return errors.Wrapf(define.ErrInvalidArg, "container is already set to join another container's net ns, cannot create a new net ns") - } - ctr.config.PostConfigureNetNS = postConfigureNetNS ctr.config.NetMode = namespaces.NetworkMode(netmode) ctr.config.CreateNetNS = true @@ -988,14 +951,6 @@ func WithStaticIP(ip net.IP) CtrCreateOption { return define.ErrCtrFinalized } - if !ctr.config.CreateNetNS { - return errors.Wrapf(define.ErrInvalidArg, "cannot set a static IP if the container is not creating a network namespace") - } - - if len(ctr.config.Networks) > 1 { - return errors.Wrapf(define.ErrInvalidArg, "cannot set a static IP if joining more than 1 CNI network") - } - ctr.config.StaticIP = ip return nil @@ -1013,14 +968,6 @@ func WithStaticMAC(mac net.HardwareAddr) CtrCreateOption { return define.ErrCtrFinalized } - if !ctr.config.CreateNetNS { - return errors.Wrapf(define.ErrInvalidArg, "cannot set a static MAC if the container is not creating a network namespace") - } - - if len(ctr.config.Networks) > 1 { - return errors.Wrapf(define.ErrInvalidArg, "cannot set a static MAC if joining more than 1 CNI network") - } - ctr.config.StaticMAC = mac return nil @@ -1114,10 +1061,6 @@ func WithCgroupParent(parent string) CtrCreateOption { return errors.Wrapf(define.ErrInvalidArg, "cgroup parent cannot be empty") } - if ctr.config.NoCgroups { - return errors.Wrapf(define.ErrInvalidArg, "CgroupParent conflicts with NoCgroups") - } - ctr.config.CgroupParent = parent return nil @@ -1130,9 +1073,6 @@ func WithDNSSearch(searchDomains []string) CtrCreateOption { if ctr.valid { return define.ErrCtrFinalized } - if ctr.config.UseImageResolvConf { - return errors.Wrapf(define.ErrInvalidArg, "cannot add DNS search domains if container will not create /etc/resolv.conf") - } ctr.config.DNSSearch = searchDomains return nil } @@ -1144,9 +1084,6 @@ func WithDNS(dnsServers []string) CtrCreateOption { if ctr.valid { return define.ErrCtrFinalized } - if ctr.config.UseImageResolvConf { - return errors.Wrapf(define.ErrInvalidArg, "cannot add DNS servers if container will not create /etc/resolv.conf") - } var dns []net.IP for _, i := range dnsServers { result := net.ParseIP(i) @@ -1166,9 +1103,6 @@ func WithDNSOption(dnsOptions []string) CtrCreateOption { if ctr.valid { return define.ErrCtrFinalized } - if ctr.config.UseImageResolvConf { - return errors.Wrapf(define.ErrInvalidArg, "cannot add DNS options if container will not create /etc/resolv.conf") - } ctr.config.DNSOption = dnsOptions return nil } @@ -1181,10 +1115,6 @@ func WithHosts(hosts []string) CtrCreateOption { return define.ErrCtrFinalized } - if ctr.config.UseImageHosts { - return errors.Wrapf(define.ErrInvalidArg, "cannot add hosts if container will not create /etc/hosts") - } - ctr.config.HostAdd = hosts return nil } @@ -1282,9 +1212,6 @@ func WithRootFS(rootfs string) CtrCreateOption { if _, err := os.Stat(rootfs); err != nil { return errors.Wrapf(err, "error checking path %q", rootfs) } - if ctr.config.RootfsImageID != "" { - return errors.Wrapf(define.ErrInvalidArg, "cannot set both an image ID and a rootfs for a container") - } ctr.config.Rootfs = rootfs return nil } @@ -1314,12 +1241,6 @@ func WithUseImageResolvConf() CtrCreateOption { return define.ErrCtrFinalized } - if len(ctr.config.DNSServer) != 0 || - len(ctr.config.DNSSearch) != 0 || - len(ctr.config.DNSOption) != 0 { - return errors.Wrapf(define.ErrInvalidArg, "not creating resolv.conf conflicts with DNS options") - } - ctr.config.UseImageResolvConf = true return nil @@ -1334,10 +1255,6 @@ func WithUseImageHosts() CtrCreateOption { return define.ErrCtrFinalized } - if len(ctr.config.HostAdd) != 0 { - return errors.Wrapf(define.ErrInvalidArg, "not creating /etc/hosts conflicts with adding to the hosts file") - } - ctr.config.UseImageHosts = true return nil diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 39284026c..de93fdce7 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -133,7 +133,12 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. return r.setupContainer(ctx, ctr) } -func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Container, err error) { +func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Container, err error) { + // Validate the container + if err := ctr.validate(); err != nil { + return nil, err + } + // Allocate a lock for the container lock, err := r.lockManager.AllocateLock() if err != nil { @@ -190,27 +195,6 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai ctr.config.Name = name } - // If CGroups are disabled, we MUST create a PID namespace. - // Otherwise, the OCI runtime won't be able to stop our container. - if ctr.config.NoCgroups { - if ctr.config.Spec.Linux == nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "must provide Linux namespace configuration in OCI spec when using NoCgroups") - } - foundPid := false - for _, ns := range ctr.config.Spec.Linux.Namespaces { - if ns.Type == spec.PIDNamespace { - foundPid = true - if ns.Path != "" { - return nil, errors.Wrapf(define.ErrInvalidArg, "containers not creating CGroups must create a private PID namespace - cannot use another") - } - break - } - } - if !foundPid { - return nil, errors.Wrapf(define.ErrInvalidArg, "containers not creating CGroups must create a private PID namespace") - } - } - // Check CGroup parent sanity, and set it if it was not set. // Only if we're actually configuring CGroups. if !ctr.config.NoCgroups { diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 08e19edb8..a5e668b04 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -19,13 +19,13 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/adapter/shortcuts" + envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/systemd/generate" "github.com/containers/storage" "github.com/pkg/errors" @@ -987,9 +987,20 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal // Validate given environment variables env := map[string]string{} - if err := parse.ReadKVStrings(env, cli.EnvFile, cli.Env); err != nil { - return ec, errors.Wrapf(err, "unable to process environment variables") + if len(cli.EnvFile) > 0 { + for _, f := range cli.EnvFile { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return ec, err + } + env = envLib.Join(env, fileEnv) + } + } + cliEnv, err := envLib.ParseSlice(cli.Env) + if err != nil { + return ec, errors.Wrap(err, "error parsing environment variables") } + env = envLib.Join(env, cliEnv) streams := new(libpod.AttachStreams) streams.OutputStream = os.Stdout diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 60ee3cb2d..32a84b60d 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -15,11 +15,11 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" + envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/docker/pkg/term" @@ -1025,16 +1025,11 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal ) // default invalid command exit code // Validate given environment variables - env := map[string]string{} - if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil { - return -1, errors.Wrapf(err, "Exec unable to process environment variables") - } - - // Build env slice of key=value strings for Exec - envs := []string{} - for k, v := range env { - envs = append(envs, fmt.Sprintf("%s=%s", k, v)) + cliEnv, err := envLib.ParseSlice(cli.Env) + if err != nil { + return 0, errors.Wrap(err, "error parsing environment variables") } + envs := envLib.Slice(cliEnv) resize := make(chan remotecommand.TerminalSize, 5) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 0d9fa7210..dc856cc8d 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -22,6 +22,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/adapter/shortcuts" ann "github.com/containers/libpod/pkg/annotations" + envLib "github.com/containers/libpod/pkg/env" ns "github.com/containers/libpod/pkg/namespaces" createconfig "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" @@ -916,9 +917,6 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container containerConfig.User = userConfig containerConfig.Security = securityConfig - // Set default environment variables and incorporate data from image, if necessary - envs := shared.EnvVariablesFromData(imageData) - annotations := make(map[string]string) if infraID != "" { annotations[ann.SandboxID] = infraID @@ -927,6 +925,14 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container containerConfig.Annotations = annotations // Environment Variables + envs := map[string]string{} + if imageData != nil { + imageEnv, err := envLib.ParseSlice(imageData.Config.Env) + if err != nil { + return nil, errors.Wrap(err, "error parsing image environment variables") + } + envs = imageEnv + } for _, e := range containerYAML.Env { envs[e.Name] = e.Value } diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go index d1317904b..2cf249cc3 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -25,7 +25,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // '500': // "$ref": "#/responses/InternalError" r.Handle(VersionedPath("/libpod/volumes/create"), s.APIHandler(libpod.CreateVolume)).Methods(http.MethodPost) - // swagger:operation POST /libpod/volumes/json volumes listVolumes + // swagger:operation GET /libpod/volumes/json volumes listVolumes // --- // summary: List volumes // description: Returns a list of networks diff --git a/pkg/env/env.go b/pkg/env/env.go new file mode 100644 index 000000000..31ffab03c --- /dev/null +++ b/pkg/env/env.go @@ -0,0 +1,126 @@ +// Package for processing environment variables. +package env + +// TODO: we need to add tests for this package. + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/pkg/errors" +) + +// DefaultEnvVariables set $PATH, $TERM and $container. +var DefaultEnvVariables = map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM": "xterm", + "container": "podman", +} + +const whiteSpaces = " \t" + +// ParseSlice parses the specified slice and transforms it into an environment +// map. +func ParseSlice(s []string) (map[string]string, error) { + env := make(map[string]string, len(s)) + for _, e := range s { + if err := parseEnv(env, e); err != nil { + return nil, err + } + } + return env, nil +} + +// Slice transforms the specified map of environment variables into a +// slice. If a value is non-empty, the key and value are joined with '='. +func Slice(m map[string]string) []string { + env := make([]string, len(m)) + for k, v := range m { + var s string + if len(v) > 0 { + s = fmt.Sprintf("%s=%s", k, v) + } else { + s = k + } + env = append(env, s) + } + return env +} + +// Join joins the two environment maps with override overriding base. +func Join(base map[string]string, override map[string]string) map[string]string { + if len(base) == 0 { + return override + } + for k, v := range override { + base[k] = v + } + return base +} + +// ParseFile parses the specified path for environment variables and returns them +// as a map. +func ParseFile(path string) (env map[string]string, err error) { + defer func() { + if err != nil { + err = errors.Wrapf(err, "error parsing env file %q", path) + } + }() + + fh, err := os.Open(path) + if err != nil { + return nil, err + } + defer fh.Close() + + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + // trim the line from all leading whitespace first + line := strings.TrimLeft(scanner.Text(), whiteSpaces) + // line is not empty, and not starting with '#' + if len(line) > 0 && !strings.HasPrefix(line, "#") { + if err := parseEnv(env, line); err != nil { + return nil, err + } + } + } + return env, scanner.Err() +} + +func parseEnv(env map[string]string, line string) error { + data := strings.SplitN(line, "=", 2) + + // catch invalid variables such as "=" or "=A" + if data[0] == "" { + return errors.Errorf("invalid environment variable: %q", line) + } + + // trim the front of a variable, but nothing else + name := strings.TrimLeft(data[0], whiteSpaces) + if strings.ContainsAny(name, whiteSpaces) { + return errors.Errorf("name %q has white spaces, poorly formatted name", name) + } + + if len(data) > 1 { + env[name] = data[1] + } else { + if strings.HasSuffix(name, "*") { + name = strings.TrimSuffix(name, "*") + for _, e := range os.Environ() { + part := strings.SplitN(e, "=", 2) + if len(part) < 2 { + continue + } + if strings.HasPrefix(part[0], name) { + env[part[0]] = part[1] + } + } + } else if val, ok := os.LookupEnv(name); ok { + // if only a pass-through variable is given, clean it up. + env[name] = val + } + } + return nil +} diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index 569f208d9..b04ce71a5 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -6,7 +6,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod/driver" "github.com/opencontainers/go-digest" - "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageData holds the inspect information of an image diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 0e5c3f429..8f0630b85 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -8,6 +8,7 @@ import ( libpodconfig "github.com/containers/libpod/libpod/config" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/sysinfo" "github.com/containers/libpod/pkg/util" @@ -152,7 +153,6 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM for key, val := range config.Annotations { g.AddAnnotation(key, val) } - g.AddProcessEnv("container", "podman") addedResources := false @@ -294,6 +294,9 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } } + // Make sure to always set the default variables unless overridden in the + // config. + config.Env = env.Join(env.DefaultEnvVariables, config.Env) for name, val := range config.Env { g.AddProcessEnv(name, val) } diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index b8a049cdf..0e94ddb7a 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -41,10 +41,16 @@ t GET libpod/containers/create 405 # # system info # +# Some day perhaps it will always be runc; for now, cgroupsv2 requires crun +# # FIXME: run 'podman info --format=json', and compare select fields -t GET info 200 \ - .OSType=linux \ - .DefaultRuntime=runc \ +runtime=runc +if have_cgroupsv2; then + runtime=crun +fi +t GET info 200 \ + .OSType=linux \ + .DefaultRuntime~.*$runtime \ .MemTotal~[0-9]\\+ # Timing: make sure server stays responsive diff --git a/test/apiv2/40-pods.at b/test/apiv2/40-pods.at index 8b5651cff..ab345b8f2 100644 --- a/test/apiv2/40-pods.at +++ b/test/apiv2/40-pods.at @@ -23,9 +23,10 @@ t POST libpod/pods/create name=foo 409 .cause="pod already exists" #t POST libpod/pods/create a=b 400 .cause='bad parameter' # FIXME: unimplemented -if root; then +if root || have_cgroupsv2; then t POST libpod/pods/foo/pause '' 204 else + # Rootless cgroupsv1 : unsupported t POST libpod/pods/foo/pause '' 500 \ .cause="this container does not have a cgroup" \ .message~".*pause pods containing rootless containers with cgroup V1" diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index bc2ed142c..f0fb4ae34 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -290,6 +290,12 @@ function rootless() { test "$ROOTLESS" = "true" } +# True if cgroups v2 are enabled +function have_cgroupsv2() { + cgroup_type=$(stat -f -c %T /sys/fs/cgroup) + test "$cgroup_type" = "cgroup2fs" +} + # END infrastructure code ############################################################################### # BEGIN sanity checks |