summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml2
-rw-r--r--Makefile8
-rw-r--r--cmd/podman/shared/create.go71
-rw-r--r--cmd/podman/shared/parse/parse.go19
l---------contrib/cirrus/apiv2_test.sh1
-rw-r--r--libpod/container_validate.go99
-rw-r--r--libpod/options.go87
-rw-r--r--libpod/runtime_ctr.go28
-rw-r--r--pkg/adapter/containers.go17
-rw-r--r--pkg/adapter/containers_remote.go15
-rw-r--r--pkg/adapter/pods.go12
-rw-r--r--pkg/api/server/register_volumes.go2
-rw-r--r--pkg/env/env.go126
-rw-r--r--pkg/inspect/inspect.go2
-rw-r--r--pkg/spec/spec.go5
-rw-r--r--test/apiv2/01-basic.at12
-rw-r--r--test/apiv2/40-pods.at3
-rwxr-xr-xtest/apiv2/test-apiv26
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'
diff --git a/Makefile b/Makefile
index 95b5e04d3..7baca10d5 100644
--- a/Makefile
+++ b/Makefile
@@ -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