summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml2
-rw-r--r--CONTRIBUTING.md13
-rw-r--r--cmd/podman/common/create.go10
-rw-r--r--cmd/podman/common/util.go2
-rw-r--r--cmd/podman/containers/create.go1
-rw-r--r--cmd/podman/containers/mount.go21
-rw-r--r--cmd/podman/containers/ps.go53
-rw-r--r--cmd/podman/containers/rm.go13
-rw-r--r--cmd/podman/containers/run.go1
-rw-r--r--cmd/podman/generate/systemd.go7
-rw-r--r--cmd/podman/images/diff.go9
-rw-r--r--cmd/podman/images/history.go57
-rw-r--r--cmd/podman/images/mount.go27
-rw-r--r--cmd/podman/images/search.go56
-rw-r--r--cmd/podman/inspect/inspect.go49
-rw-r--r--cmd/podman/networks/create.go4
-rw-r--r--cmd/podman/networks/inspect.go38
-rw-r--r--cmd/podman/networks/list.go2
-rw-r--r--cmd/podman/play/kube.go1
-rw-r--r--cmd/podman/pods/inspect.go24
-rw-r--r--cmd/podman/pods/ps.go108
-rw-r--r--cmd/podman/pods/stats.go94
-rw-r--r--cmd/podman/root.go19
-rw-r--r--cmd/podman/root_test.go34
-rw-r--r--cmd/podman/volumes/list.go54
-rw-r--r--contrib/cirrus/lib.sh29
-rwxr-xr-xcontrib/cirrus/runner.sh3
-rwxr-xr-xcontrib/cirrus/setup_environment.sh7
-rwxr-xr-xcontrib/cirrus/shellcheck.sh2
-rw-r--r--docs/source/Introduction.rst4
-rw-r--r--docs/source/markdown/podman-play-kube.1.md15
-rw-r--r--docs/source/markdown/podman-rm.1.md9
-rw-r--r--docs/source/markdown/podman-search.1.md4
-rwxr-xr-xhack/get_ci_vm.sh330
-rw-r--r--libpod/container.go19
-rw-r--r--libpod/container_config.go5
-rw-r--r--libpod/container_inspect.go3
-rw-r--r--libpod/container_internal.go24
-rw-r--r--libpod/container_internal_linux.go12
-rw-r--r--libpod/define/container_inspect.go3
-rw-r--r--libpod/image/docker_registry_options.go1
-rw-r--r--libpod/kube.go40
-rw-r--r--libpod/network/config.go (renamed from pkg/network/config.go)11
-rw-r--r--libpod/network/create.go195
-rw-r--r--libpod/network/devices.go (renamed from pkg/network/devices.go)0
-rw-r--r--libpod/network/files.go (renamed from pkg/network/files.go)0
-rw-r--r--libpod/network/ip.go (renamed from pkg/network/ip.go)0
-rw-r--r--libpod/network/lock.go26
-rw-r--r--libpod/network/netconflist.go (renamed from pkg/network/netconflist.go)0
-rw-r--r--libpod/network/netconflist_test.go (renamed from pkg/network/netconflist_test.go)0
-rw-r--r--libpod/network/network.go (renamed from pkg/network/network.go)10
-rw-r--r--libpod/network/network_test.go (renamed from pkg/network/network_test.go)0
-rw-r--r--libpod/network/subnet.go (renamed from pkg/network/subnet.go)0
-rw-r--r--libpod/network/subnet_test.go (renamed from pkg/network/subnet_test.go)0
-rw-r--r--libpod/oci_conmon_linux.go12
-rw-r--r--libpod/pod_api.go241
-rw-r--r--libpod/runtime_ctr.go1
-rw-r--r--nix/nixpkgs.json6
-rw-r--r--pkg/api/handlers/compat/containers_create.go9
-rw-r--r--pkg/api/handlers/compat/events.go3
-rw-r--r--pkg/api/handlers/compat/images.go42
-rw-r--r--pkg/api/handlers/compat/networks.go8
-rw-r--r--pkg/api/handlers/libpod/pods.go6
-rw-r--r--pkg/api/server/register_images.go25
-rw-r--r--pkg/domain/entities/containers.go1
-rw-r--r--pkg/domain/entities/play.go2
-rw-r--r--pkg/domain/infra/abi/containers.go34
-rw-r--r--pkg/domain/infra/abi/network.go176
-rw-r--r--pkg/domain/infra/abi/play.go89
-rw-r--r--pkg/domain/infra/abi/play_test.go254
-rw-r--r--pkg/domain/infra/abi/pods.go6
-rw-r--r--pkg/parallel/ctr/ctr.go (renamed from pkg/parallel/parallel_linux.go)45
-rw-r--r--pkg/parallel/parallel.go30
-rw-r--r--pkg/varlinkapi/pods.go7
-rw-r--r--test/apiv2/10-images.at3
-rw-r--r--test/apiv2/12-imagesMore.at44
-rw-r--r--test/apiv2/20-containers.at2
-rw-r--r--test/e2e/build_test.go9
-rw-r--r--test/e2e/common_test.go4
-rw-r--r--test/e2e/exec_test.go2
-rw-r--r--test/e2e/generate_kube_test.go32
-rw-r--r--test/e2e/images_test.go5
-rw-r--r--test/e2e/load_test.go2
-rw-r--r--test/e2e/logs_test.go2
-rw-r--r--test/e2e/namespace_test.go7
-rw-r--r--test/e2e/network_create_test.go3
-rw-r--r--test/e2e/network_test.go2
-rw-r--r--test/e2e/play_kube_test.go239
-rw-r--r--test/e2e/pod_infra_container_test.go1
-rw-r--r--test/e2e/pod_pod_namespaces.go1
-rw-r--r--test/e2e/pod_ps_test.go3
-rw-r--r--test/e2e/pod_stats_test.go5
-rw-r--r--test/e2e/prune_test.go5
-rw-r--r--test/e2e/ps_test.go12
-rw-r--r--test/e2e/pull_test.go12
-rw-r--r--test/e2e/rm_test.go1
-rw-r--r--test/e2e/rmi_test.go4
-rw-r--r--test/e2e/run_entrypoint_test.go2
-rw-r--r--test/e2e/run_env_test.go13
-rw-r--r--test/e2e/run_networking_test.go17
-rw-r--r--test/e2e/run_restart_test.go5
-rw-r--r--test/e2e/run_test.go12
-rw-r--r--test/e2e/run_working_dir.go1
-rw-r--r--test/e2e/search_test.go6
-rw-r--r--test/e2e/system_df_test.go9
-rw-r--r--test/e2e/volume_ls_test.go7
-rw-r--r--test/system/055-rm.bats15
-rw-r--r--test/system/060-mount.bats2
-rw-r--r--test/system/070-build.bats9
109 files changed, 1884 insertions, 1062 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index b23ec1a90..da33c81e2 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -47,7 +47,7 @@ env:
TEST_ENVIRON: host # 'host' or 'container'
PODBIN_NAME: podman # 'podman' or 'remote'
PRIV_NAME: root # 'root' or 'rootless'
- DISTRO_NV: $FEDORA_NAME # any {PRIOR_,}{FEDORA,UBUNTU}_NAME value
+ DISTRO_NV: # any {PRIOR_,}{FEDORA,UBUNTU}_NAME value
VM_IMAGE_NAME: # One of the "Google-cloud VM Images" (above)
CTR_FQIN: # One of the "Container FQIN's" (above)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ba321921c..a813fcc35 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -46,7 +46,7 @@ This section describes how to start a contribution to Podman.
### Prepare your environment
-Read the [install documentation to see how to install dependencies](install.md) .
+Read the [install documentation to see how to install dependencies](https://podman.io/getting-started/installation#build-and-run-dependencies).
The install documentation will illustrate the following steps:
- install libs and tools
@@ -86,6 +86,17 @@ Makefile allow you to install needed tools:
$ make install.tools
```
+### Prerequisite before build
+
+You need install some dependencies before building a binary.
+
+#### Fedora
+
+ ```shell
+ $ sudo dnf install gpgme-devel libseccomp-devel.x86_64 libseccomp-devel.x86_64 systemd-devel
+ $ export PKG_CONFIG_PATH="/usr/lib/pkgconfig"
+ ```
+
### Building binaries and test your changes
To test your changes do `make binaries` to generate your binaries.
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index bb5eb9f38..0ec422313 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -166,10 +166,12 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"env", "e", containerConfig.Env(),
"Set environment variables in container",
)
- createFlags.BoolVar(
- &cf.EnvHost,
- "env-host", false, "Use all current host environment variables in container",
- )
+ if !registry.IsRemote() {
+ createFlags.BoolVar(
+ &cf.EnvHost,
+ "env-host", false, "Use all current host environment variables in container",
+ )
+ }
createFlags.StringSliceVar(
&cf.EnvFile,
"env-file", []string{},
diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go
index 17e779c86..a971aa957 100644
--- a/cmd/podman/common/util.go
+++ b/cmd/podman/common/util.go
@@ -200,8 +200,6 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string)
}
newPort.HostPort = hostStart
}
- } else {
- newPort.HostPort = newPort.ContainerPort
}
hport := newPort.HostPort
diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go
index d75352848..809162e76 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -64,7 +64,6 @@ func createFlags(flags *pflag.FlagSet) {
_ = flags.MarkHidden("signature-policy")
if registry.IsRemote() {
_ = flags.MarkHidden("authfile")
- _ = flags.MarkHidden("env-host")
_ = flags.MarkHidden("http-proxy")
}
}
diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go
index f2df5e99e..c4dfb513f 100644
--- a/cmd/podman/containers/mount.go
+++ b/cmd/podman/containers/mount.go
@@ -6,6 +6,7 @@ import (
"text/tabwriter"
"text/template"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
@@ -75,9 +76,6 @@ func init() {
}
func mount(_ *cobra.Command, args []string) error {
- var (
- errs utils.OutputErrors
- )
if len(args) > 0 && mountOpts.Latest {
return errors.Errorf("--latest and containers cannot be used together")
}
@@ -85,7 +83,9 @@ func mount(_ *cobra.Command, args []string) error {
if err != nil {
return err
}
+
if len(args) > 0 || mountOpts.Latest || mountOpts.All {
+ var errs utils.OutputErrors
for _, r := range reports {
if r.Err == nil {
fmt.Println(r.Path)
@@ -96,21 +96,21 @@ func mount(_ *cobra.Command, args []string) error {
return errs.PrintErrors()
}
- switch mountOpts.Format {
- case "json":
+ switch {
+ case parse.MatchesJSONFormat(mountOpts.Format):
return printJSON(reports)
- case "":
- // do nothing
+ case mountOpts.Format == "":
+ break // print defaults
default:
- return errors.Errorf("unknown --format argument: %s", mountOpts.Format)
+ return errors.Errorf("unknown --format argument: %q", mountOpts.Format)
}
mrs := make([]mountReporter, 0, len(reports))
for _, r := range reports {
mrs = append(mrs, mountReporter{r})
}
- row := "{{.ID}} {{.Path}}\n"
- format := "{{range . }}" + row + "{{end}}"
+
+ format := "{{range . }}{{.ID}}\t{{.Path}}\n{{end}}"
tmpl, err := template.New("mounts").Parse(format)
if err != nil {
return err
@@ -139,6 +139,7 @@ func printJSON(reports []*entities.ContainerMountReport) error {
if err != nil {
return err
}
+
fmt.Println(string(b))
return nil
}
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index c4c8b60f3..8082a74c2 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -12,7 +12,9 @@ import (
tm "github.com/buger/goterm"
"github.com/containers/buildah/pkg/formats"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -176,47 +178,51 @@ func ps(cmd *cobra.Command, args []string) error {
return err
}
}
- if listOpts.Format == "json" {
+
+ switch {
+ case parse.MatchesJSONFormat(listOpts.Format):
return jsonOut(listContainers)
- }
- if listOpts.Quiet {
+ case listOpts.Quiet:
return quietOut(listContainers)
}
+ // Output table Watch > 0 will refresh screen
responses := make([]psReporter, 0, len(listContainers))
for _, r := range listContainers {
responses = append(responses, psReporter{r})
}
- headers, format := createPsOut()
- if cmd.Flag("format").Changed {
- format = strings.TrimPrefix(listOpts.Format, "table ")
- if !strings.HasPrefix(format, "\n") {
- format += "\n"
- }
- }
- format = "{{range . }}" + format + "{{end}}"
- if !listOpts.Quiet && !cmd.Flag("format").Changed {
- format = headers + format
+ var headers, format string
+ if cmd.Flags().Changed("format") {
+ headers = ""
+ format = report.NormalizeFormat(listOpts.Format)
+ } else {
+ headers, format = createPsOut()
}
+ format = headers + "{{range . }}" + format + "{{end}}"
+
tmpl, err := template.New("listContainers").Parse(format)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ defer w.Flush()
+
if listOpts.Watch > 0 {
for {
var responses []psReporter
tm.Clear()
tm.MoveCursor(1, 1)
tm.Flush()
- listContainers, err := getResponses()
- for _, r := range listContainers {
- responses = append(responses, psReporter{r})
- }
- if err != nil {
+
+ if ctnrs, err := getResponses(); err != nil {
return err
+ } else {
+ for _, r := range ctnrs {
+ responses = append(responses, psReporter{r})
+ }
}
+
if err := tmpl.Execute(w, responses); err != nil {
return err
}
@@ -232,11 +238,11 @@ func ps(cmd *cobra.Command, args []string) error {
if err := tmpl.Execute(w, responses); err != nil {
return err
}
- return w.Flush()
}
return nil
}
+// cannot use report.Headers() as it doesn't support structures as fields
func createPsOut() (string, string) {
var row string
if listOpts.Namespace {
@@ -257,12 +263,9 @@ func createPsOut() (string, string) {
headers += "\tSIZE"
row += "\t{{.Size}}"
}
- if !strings.HasSuffix(headers, "\n") {
- headers += "\n"
- }
- if !strings.HasSuffix(row, "\n") {
- row += "\n"
- }
+
+ headers = report.NormalizeFormat(headers)
+ row = report.NormalizeFormat(row)
return headers, row
}
diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go
index f8f12234d..a7739b3ba 100644
--- a/cmd/podman/containers/rm.go
+++ b/cmd/podman/containers/rm.go
@@ -57,13 +57,12 @@ func rmFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
- flags.BoolVar(&rmOptions.Storage, "storage", false, "Remove container from storage library")
flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container")
flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
- if registry.IsRemote() {
- _ = flags.MarkHidden("ignore")
- _ = flags.MarkHidden("cidfile")
+ if !registry.IsRemote() {
+ // This option is deprecated, but needs to still exists for backwards compatibility
+ flags.Bool("storage", false, "Remove container from storage library")
_ = flags.MarkHidden("storage")
}
}
@@ -97,12 +96,6 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit
var (
errs utils.OutputErrors
)
- // Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore
- if rmOptions.Storage {
- if rmOptions.All || rmOptions.Ignore || rmOptions.Latest || rmOptions.Volumes || rmOptions.CIDFiles != nil {
- return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile")
- }
- }
responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions)
if err != nil {
if setExit {
diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go
index 8052b033e..19c984bbf 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -67,7 +67,6 @@ func runFlags(flags *pflag.FlagSet) {
_ = flags.MarkHidden("signature-policy")
if registry.IsRemote() {
_ = flags.MarkHidden("authfile")
- _ = flags.MarkHidden("env-host")
_ = flags.MarkHidden("http-proxy")
_ = flags.MarkHidden("preserve-fds")
}
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
index f690836a4..02e826549 100644
--- a/cmd/podman/generate/systemd.go
+++ b/cmd/podman/generate/systemd.go
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -97,10 +98,10 @@ func systemd(cmd *cobra.Command, args []string) error {
}
}
- switch format {
- case "json":
+ switch {
+ case parse.MatchesJSONFormat(format):
return printJSON(report.Units)
- case "":
+ case format == "":
return printDefault(report.Units)
default:
return errors.Errorf("unknown --format argument: %s", format)
diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go
index 26147345e..05a05fa04 100644
--- a/cmd/podman/images/diff.go
+++ b/cmd/podman/images/diff.go
@@ -1,6 +1,7 @@
package images
import (
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -49,11 +50,11 @@ func diff(cmd *cobra.Command, args []string) error {
return err
}
- switch diffOpts.Format {
- case "":
- return report.ChangesToTable(results)
- case "json":
+ switch {
+ case parse.MatchesJSONFormat(diffOpts.Format):
return report.ChangesToJSON(results)
+ case diffOpts.Format == "":
+ return report.ChangesToTable(results)
default:
return errors.New("only supported value for '--format' is 'json'")
}
diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go
index 30abf0ada..fa4b368c6 100644
--- a/cmd/podman/images/history.go
+++ b/cmd/podman/images/history.go
@@ -10,7 +10,9 @@ import (
"time"
"unicode"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"
"github.com/pkg/errors"
@@ -28,9 +30,9 @@ var (
Use: "history [flags] IMAGE",
Short: "Show history of a specified image",
Long: long,
- Example: "podman history quay.io/fedora/fedora",
Args: cobra.ExactArgs(1),
RunE: history,
+ Example: "podman history quay.io/fedora/fedora",
}
imageHistoryCmd = &cobra.Command{
@@ -39,7 +41,7 @@ var (
Short: historyCmd.Short,
Long: historyCmd.Long,
RunE: historyCmd.RunE,
- Example: `podman image history imageID`,
+ Example: `podman image history quay.io/fedora/fedora`,
}
opts = struct {
@@ -79,7 +81,7 @@ func history(cmd *cobra.Command, args []string) error {
return err
}
- if opts.format == "json" {
+ if parse.MatchesJSONFormat(opts.format) {
var err error
if len(results.Layers) == 0 {
_, err = fmt.Fprintf(os.Stdout, "[]\n")
@@ -100,69 +102,66 @@ func history(cmd *cobra.Command, args []string) error {
}
return err
}
- hr := make([]historyreporter, 0, len(results.Layers))
+
+ hr := make([]historyReporter, 0, len(results.Layers))
for _, l := range results.Layers {
- hr = append(hr, historyreporter{l})
+ hr = append(hr, historyReporter{l})
}
+
+ hdrs := report.Headers(historyReporter{}, map[string]string{
+ "CreatedBy": "CREATED BY",
+ })
+
// Defaults
- hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n"
row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
-
switch {
- case len(opts.format) > 0:
- hdr = ""
- row = opts.format
- if !strings.HasSuffix(opts.format, "\n") {
- row += "\n"
- }
+ case cmd.Flags().Changed("format"):
+ row = report.NormalizeFormat(opts.format)
case opts.quiet:
- hdr = ""
row = "{{.ID}}\n"
- case opts.human:
- row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- case opts.noTrunc:
- row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
}
- format := hdr + "{{range . }}" + row + "{{end}}"
+ format := "{{range . }}" + row + "{{end}}"
tmpl, err := template.New("report").Parse(format)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
- err = tmpl.Execute(w, hr)
- if err != nil {
- fmt.Fprintln(os.Stderr, errors.Wrapf(err, "failed to print report"))
+ defer w.Flush()
+
+ if !opts.quiet && !cmd.Flags().Changed("format") {
+ if err := tmpl.Execute(w, hdrs); err != nil {
+ return errors.Wrapf(err, "failed to write report column headers")
+ }
}
- w.Flush()
- return nil
+ return tmpl.Execute(w, hr)
}
-type historyreporter struct {
+type historyReporter struct {
entities.ImageHistoryLayer
}
-func (h historyreporter) Created() string {
+func (h historyReporter) Created() string {
if opts.human {
return units.HumanDuration(time.Since(h.ImageHistoryLayer.Created)) + " ago"
}
return h.ImageHistoryLayer.Created.Format(time.RFC3339)
}
-func (h historyreporter) Size() string {
+func (h historyReporter) Size() string {
s := units.HumanSizeWithPrecision(float64(h.ImageHistoryLayer.Size), 3)
i := strings.LastIndexFunc(s, unicode.IsNumber)
return s[:i+1] + " " + s[i+1:]
}
-func (h historyreporter) CreatedBy() string {
+func (h historyReporter) CreatedBy() string {
if len(h.ImageHistoryLayer.CreatedBy) > 45 {
return h.ImageHistoryLayer.CreatedBy[:45-3] + "..."
}
return h.ImageHistoryLayer.CreatedBy
}
-func (h historyreporter) ID() string {
+func (h historyReporter) ID() string {
if !opts.noTrunc && len(h.ImageHistoryLayer.ID) >= 12 {
return h.ImageHistoryLayer.ID[0:12]
}
diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go
index fac06e324..0a972ea81 100644
--- a/cmd/podman/images/mount.go
+++ b/cmd/podman/images/mount.go
@@ -6,6 +6,7 @@ import (
"text/tabwriter"
"text/template"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -24,7 +25,7 @@ var (
mountCommand = &cobra.Command{
Use: "mount [flags] [IMAGE...]",
- Short: "Mount an images's root filesystem",
+ Short: "Mount an image's root filesystem",
Long: mountDescription,
RunE: mount,
Example: `podman image mount imgID
@@ -56,18 +57,18 @@ func init() {
mountFlags(mountCommand.Flags())
}
-func mount(_ *cobra.Command, args []string) error {
- var (
- errs utils.OutputErrors
- )
+func mount(cmd *cobra.Command, args []string) error {
if len(args) > 0 && mountOpts.All {
return errors.New("when using the --all switch, you may not pass any image names or IDs")
}
+
reports, err := registry.ImageEngine().Mount(registry.GetContext(), args, mountOpts)
if err != nil {
return err
}
+
if len(args) > 0 || mountOpts.All {
+ var errs utils.OutputErrors
for _, r := range reports {
if r.Err == nil {
fmt.Println(r.Path)
@@ -78,22 +79,22 @@ func mount(_ *cobra.Command, args []string) error {
return errs.PrintErrors()
}
- switch mountOpts.Format {
- case "json":
+ switch {
+ case parse.MatchesJSONFormat(mountOpts.Format):
return printJSON(reports)
- case "":
- // do nothing
+ case mountOpts.Format == "":
+ break // default format
default:
- return errors.Errorf("unknown --format argument: %s", mountOpts.Format)
+ return errors.Errorf("unknown --format argument: %q", mountOpts.Format)
}
mrs := make([]mountReporter, 0, len(reports))
for _, r := range reports {
mrs = append(mrs, mountReporter{r})
}
- row := "{{.ID}} {{.Path}}\n"
- format := "{{range . }}" + row + "{{end}}"
- tmpl, err := template.New("mounts").Parse(format)
+
+ row := "{{range . }}{{.ID}}\t{{.Path}}\n{{end}}"
+ tmpl, err := template.New("mounts").Parse(row)
if err != nil {
return err
}
diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go
index c7f16a838..b8f590585 100644
--- a/cmd/podman/images/search.go
+++ b/cmd/podman/images/search.go
@@ -2,15 +2,14 @@ package images
import (
"os"
- "reflect"
- "strings"
+ "text/tabwriter"
+ "text/template"
- "github.com/containers/buildah/pkg/formats"
"github.com/containers/common/pkg/auth"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/util/camelcase"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -86,11 +85,6 @@ func searchFlags(flags *pflag.FlagSet) {
flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output")
flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
-
- if registry.IsRemote() {
- _ = flags.MarkHidden("authfile")
- _ = flags.MarkHidden("tls-verify")
- }
}
// imageSearch implements the command for searching images.
@@ -125,41 +119,29 @@ func imageSearch(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
-
- format := genSearchFormat(searchOptions.Format)
if len(searchReport) == 0 {
return nil
}
- out := formats.StdoutTemplateArray{Output: searchToGeneric(searchReport), Template: format, Fields: searchHeaderMap()}
- return out.Out()
-}
-
-// searchHeaderMap returns the headers of a SearchResult.
-func searchHeaderMap() map[string]string {
- s := new(entities.ImageSearchReport)
- v := reflect.Indirect(reflect.ValueOf(s))
- values := make(map[string]string, v.NumField())
- for i := 0; i < v.NumField(); i++ {
- key := v.Type().Field(i).Name
- value := key
- values[key] = strings.ToUpper(strings.Join(camelcase.Split(value), " "))
+ hdrs := report.Headers(entities.ImageSearchReport{}, nil)
+ row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n"
+ if cmd.Flags().Changed("format") {
+ row = report.NormalizeFormat(searchOptions.Format)
}
- return values
-}
+ row = "{{range .}}" + row + "{{end}}"
-func genSearchFormat(format string) string {
- if format != "" {
- // "\t" from the command line is not being recognized as a tab
- // replacing the string "\t" to a tab character if the user passes in "\t"
- return strings.Replace(format, `\t`, "\t", -1)
+ tmpl, err := template.New("search").Parse(row)
+ if err != nil {
+ return err
}
- return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t"
-}
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ defer w.Flush()
-func searchToGeneric(params []entities.ImageSearchReport) (genericParams []interface{}) {
- for _, v := range params {
- genericParams = append(genericParams, interface{}(v))
+ if !cmd.Flags().Changed("format") {
+ if err := tmpl.Execute(w, hdrs); err != nil {
+ return errors.Wrapf(err, "failed to write search column headers")
+ }
}
- return genericParams
+
+ return tmpl.Execute(w, searchReport)
}
diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go
index f29527412..658463650 100644
--- a/cmd/podman/inspect/inspect.go
+++ b/cmd/podman/inspect/inspect.go
@@ -4,10 +4,14 @@ import (
"context"
"fmt"
"os"
+ "regexp"
"strings"
+ "text/tabwriter"
+ "text/template"
- "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@@ -24,6 +28,9 @@ const (
AllType = "all"
)
+// Pull in configured json library
+var json = registry.JSONLibrary()
+
// AddInspectFlagSet takes a command and adds the inspect flags and returns an
// InspectOptions object.
func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions {
@@ -80,7 +87,7 @@ func newInspector(options entities.InspectOptions) (*inspector, error) {
// inspect inspects the specified container/image names or IDs.
func (i *inspector) inspect(namesOrIDs []string) error {
// data - dumping place for inspection results.
- var data []interface{} //nolint
+ var data []interface{} // nolint
var errs []error
ctx := context.Background()
@@ -134,15 +141,19 @@ func (i *inspector) inspect(namesOrIDs []string) error {
data = []interface{}{}
}
- var out formats.Writer
- if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat
- out = formats.JSONStructArray{Output: data}
- } else {
- out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)}
+ var err error
+ switch {
+ case parse.MatchesJSONFormat(i.options.Format) || i.options.Format == "":
+ err = printJSON(data)
+ default:
+ row := inspectNormalize(i.options.Format)
+ row = "{{range . }}" + report.NormalizeFormat(row) + "{{end}}"
+ err = printTmpl(tmpType, row, data)
}
- if err := out.Out(); err != nil {
+ if err != nil {
logrus.Errorf("Error printing inspect output: %v", err)
}
+
if len(errs) > 0 {
if len(errs) > 1 {
for _, err := range errs[1:] {
@@ -154,8 +165,22 @@ func (i *inspector) inspect(namesOrIDs []string) error {
return nil
}
+func printJSON(data []interface{}) error {
+ enc := json.NewEncoder(os.Stdout)
+ return enc.Encode(data)
+}
+
+func printTmpl(typ, row string, data []interface{}) error {
+ t, err := template.New(typ + " inspect").Parse(row)
+ if err != nil {
+ return err
+ }
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ return t.Execute(w, data)
+}
+
func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, []error, error) {
- var data []interface{} //nolint
+ var data []interface{} // nolint
allErrs := []error{}
for _, name := range namesOrIDs {
ctrData, errs, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options)
@@ -179,9 +204,11 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte
return data, allErrs, nil
}
-func inspectFormat(row string) string {
+func inspectNormalize(row string) string {
+ m := regexp.MustCompile(`{{\s*\.Id\s*}}`)
+ row = m.ReplaceAllString(row, "{{.ID}}")
+
r := strings.NewReplacer(
- "{{.Id}}", formats.IDString,
".Src", ".Source",
".Dst", ".Destination",
".ImageID", ".Image",
diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go
index 68a577ae1..17f39bd8b 100644
--- a/cmd/podman/networks/create.go
+++ b/cmd/podman/networks/create.go
@@ -7,7 +7,6 @@ import (
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/network"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -56,9 +55,6 @@ func networkCreate(cmd *cobra.Command, args []string) error {
var (
name string
)
- if err := network.IsSupportedDriver(networkCreateOptions.Driver); err != nil {
- return err
- }
if len(args) > 0 {
if !define.NameRegex.MatchString(args[0]) {
return define.RegexError
diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go
index c5872def7..c36125948 100644
--- a/cmd/podman/networks/inspect.go
+++ b/cmd/podman/networks/inspect.go
@@ -3,12 +3,13 @@ package network
import (
"encoding/json"
"fmt"
- "io"
"os"
- "strings"
+ "text/tabwriter"
"text/template"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -39,31 +40,32 @@ func init() {
flags.StringVarP(&networkInspectOptions.Format, "format", "f", "", "Pretty-print network to JSON or using a Go template")
}
-func networkInspect(cmd *cobra.Command, args []string) error {
+func networkInspect(_ *cobra.Command, args []string) error {
responses, err := registry.ContainerEngine().NetworkInspect(registry.Context(), args, entities.NetworkInspectOptions{})
if err != nil {
return err
}
- b, err := json.MarshalIndent(responses, "", " ")
- if err != nil {
- return err
- }
- if strings.ToLower(networkInspectOptions.Format) == "json" || networkInspectOptions.Format == "" {
- fmt.Println(string(b))
- } else {
- var w io.Writer = os.Stdout
- //There can be more than 1 in the inspect output.
- format := "{{range . }}" + networkInspectOptions.Format + "{{end}}"
- tmpl, err := template.New("inspectNetworks").Parse(format)
+
+ switch {
+ case parse.MatchesJSONFormat(networkInspectOptions.Format) || networkInspectOptions.Format == "":
+ b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
- if err := tmpl.Execute(w, responses); err != nil {
+ fmt.Println(string(b))
+ default:
+ row := report.NormalizeFormat(networkInspectOptions.Format)
+ // There can be more than 1 in the inspect output.
+ row = "{{range . }}" + row + "{{end}}"
+ tmpl, err := template.New("inspectNetworks").Parse(row)
+ if err != nil {
return err
}
- if flusher, ok := w.(interface{ Flush() error }); ok {
- return flusher.Flush()
- }
+
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 0, ' ', 0)
+ defer w.Flush()
+
+ return tmpl.Execute(w, responses)
}
return nil
}
diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go
index b6fb2bb80..c53f50c9f 100644
--- a/cmd/podman/networks/list.go
+++ b/cmd/podman/networks/list.go
@@ -10,8 +10,8 @@ import (
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/validate"
+ "github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/network"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go
index 54a6d0677..976d720ee 100644
--- a/cmd/podman/play/kube.go
+++ b/cmd/podman/play/kube.go
@@ -60,6 +60,7 @@ func init() {
flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles")
+ flags.StringSliceVar(&kubeOptions.ConfigMaps, "configmap", []string{}, "`Pathname` of a YAML file containing a kubernetes configmap")
}
_ = flags.MarkHidden("signature-policy")
}
diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go
index bc20352b0..cad15d10f 100644
--- a/cmd/podman/pods/inspect.go
+++ b/cmd/podman/pods/inspect.go
@@ -3,9 +3,13 @@ package pods
import (
"context"
"fmt"
+ "os"
+ "text/tabwriter"
+ "text/template"
- "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@@ -57,11 +61,19 @@ func inspect(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- var data interface{} = responses
- var out formats.Writer = formats.JSONStruct{Output: data}
- if inspectOptions.Format != "json" {
- out = formats.StdoutTemplate{Output: data, Template: inspectOptions.Format}
+
+ if parse.MatchesJSONFormat(inspectOptions.Format) {
+ enc := json.NewEncoder(os.Stdout)
+ return enc.Encode(responses)
+ }
+
+ row := report.NormalizeFormat(inspectOptions.Format)
+
+ t, err := template.New("pod inspect").Parse(row)
+ if err != nil {
+ return err
}
- return out.Out()
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ return t.Execute(w, *responses)
}
diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index 7b755cb22..b7952e6e3 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -3,7 +3,6 @@ package pods
import (
"context"
"fmt"
- "io"
"os"
"sort"
"strings"
@@ -11,7 +10,9 @@ import (
"text/template"
"time"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"
@@ -34,10 +35,9 @@ var (
)
var (
- defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED"
- inputFilters []string
- noTrunc bool
- psInput entities.PodPSOptions
+ inputFilters []string
+ noTrunc bool
+ psInput entities.PodPSOptions
)
func init() {
@@ -62,11 +62,6 @@ func init() {
}
func pods(cmd *cobra.Command, _ []string) error {
- var (
- w io.Writer = os.Stdout
- row string
- )
-
if psInput.Quiet && len(psInput.Format) > 0 {
return errors.New("quiet and format cannot be used together")
}
@@ -89,80 +84,79 @@ func pods(cmd *cobra.Command, _ []string) error {
return err
}
- if psInput.Format == "json" {
+ switch {
+ case parse.MatchesJSONFormat(psInput.Format):
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
+ case psInput.Quiet:
+ for _, p := range responses {
+ fmt.Println(p.Id)
+ }
+ return nil
}
+ // Formatted output below
lpr := make([]ListPodReporter, 0, len(responses))
for _, r := range responses {
lpr = append(lpr, ListPodReporter{r})
}
- headers, row := createPodPsOut()
- if psInput.Quiet {
- row = "{{.Id}}\n"
- }
- if cmd.Flag("format").Changed {
- row = psInput.Format
- if !strings.HasPrefix(row, "\n") {
- row += "\n"
- }
- }
- format := "{{range . }}" + row + "{{end}}"
- if !psInput.Quiet && !cmd.Flag("format").Changed {
- format = headers + format
+
+ headers := report.Headers(ListPodReporter{}, map[string]string{
+ "ContainerIds": "IDS",
+ "ContainerNames": "NAMES",
+ "ContainerStatuses": "STATUS",
+ "Namespace": "NAMESPACES",
+ "NumberOfContainers": "# OF CONTAINERS",
+ "InfraId": "INFRA ID",
+ })
+ row := podPsFormat()
+ if cmd.Flags().Changed("format") {
+ row = report.NormalizeFormat(psInput.Format)
}
- tmpl, err := template.New("listPods").Parse(format)
+ row = "{{range . }}" + row + "{{end}}"
+
+ tmpl, err := template.New("listPods").Parse(row)
if err != nil {
return err
}
- if !psInput.Quiet {
- w = tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
- }
- if err := tmpl.Execute(w, lpr); err != nil {
- return err
- }
- if flusher, ok := w.(interface{ Flush() error }); ok {
- return flusher.Flush()
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ defer w.Flush()
+
+ if !psInput.Quiet && !cmd.Flag("format").Changed {
+ if err := tmpl.Execute(w, headers); err != nil {
+ return err
+ }
}
- return nil
+ return tmpl.Execute(w, lpr)
}
-func createPodPsOut() (string, string) {
- var row string
- headers := defaultHeaders
- row += "{{.Id}}"
-
- row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}"
+func podPsFormat() string {
+ row := []string{"{{.Id}}", "{{.Name}}", "{{.Status}}", "{{.Created}}}"}
if psInput.CtrIds {
- headers += "\tIDS"
- row += "\t{{.ContainerIds}}"
+ row = append(row, "{{.ContainerIds}}")
}
+
if psInput.CtrNames {
- headers += "\tNAMES"
- row += "\t{{.ContainerNames}}"
+ row = append(row, "{{.ContainerNames}}")
}
+
if psInput.CtrStatus {
- headers += "\tSTATUS"
- row += "\t{{.ContainerStatuses}}"
+ row = append(row, "{{.ContainerStatuses}}")
}
+
if psInput.Namespace {
- headers += "\tCGROUP\tNAMESPACES"
- row += "\t{{.Cgroup}}\t{{.Namespace}}"
+ row = append(row, "{{.Cgroup}}", "{{.Namespace}}")
}
- if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds {
- headers += "\t# OF CONTAINERS"
- row += "\t{{.NumberOfContainers}}"
+ if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds {
+ row = append(row, "{{.NumberOfContainers}}")
}
- headers += "\tINFRA ID\n"
- row += "\t{{.InfraId}}\n"
- return headers, row
+ return strings.Join(row, "\t") + "\n"
}
// ListPodReporter is a struct for pod ps output
@@ -180,7 +174,7 @@ func (l ListPodReporter) Labels() map[string]string {
return l.ListPodsReport.Labels
}
-// NumberofContainers returns an int representation for
+// NumberOfContainers returns an int representation for
// the number of containers belonging to the pod
func (l ListPodReporter) NumberOfContainers() int {
return len(l.Containers)
@@ -192,7 +186,7 @@ func (l ListPodReporter) ID() string {
}
// Id returns the Pod id
-func (l ListPodReporter) Id() string { //nolint
+func (l ListPodReporter) Id() string { // nolint
if noTrunc {
return l.ListPodsReport.Id
}
@@ -206,7 +200,7 @@ func (l ListPodReporter) InfraID() string {
// InfraId returns the infra container id for the pod
// depending on trunc
-func (l ListPodReporter) InfraId() string { //nolint
+func (l ListPodReporter) InfraId() string { // nolint
if len(l.ListPodsReport.InfraId) == 0 {
return ""
}
diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go
index 1d916dbfa..2f59e4e47 100644
--- a/cmd/podman/pods/stats.go
+++ b/cmd/podman/pods/stats.go
@@ -4,18 +4,16 @@ import (
"context"
"fmt"
"os"
- "reflect"
- "strings"
"text/tabwriter"
"text/template"
"time"
"github.com/buger/goterm"
- "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/util/camelcase"
"github.com/spf13/cobra"
)
@@ -67,11 +65,18 @@ func stats(cmd *cobra.Command, args []string) error {
return err
}
- format := statsOptions.Format
- doJSON := strings.ToLower(format) == formats.JSONString
- header := getPodStatsHeader(format)
+ row := report.NormalizeFormat(statsOptions.Format)
+ doJSON := parse.MatchesJSONFormat(row)
- for {
+ headers := report.Headers(entities.PodStatsReport{}, map[string]string{
+ "CPU": "CPU %",
+ "MemUsage": "MEM USAGE/ LIMIT",
+ "MEM": "MEM %",
+ "NET IO": "NET IO",
+ "BlockIO": "BLOCK IO",
+ })
+
+ for ; ; time.Sleep(time.Second) {
reports, err := registry.ContainerEngine().PodStats(context.Background(), args, statsOptions.PodStatsOptions)
if err != nil {
return err
@@ -87,16 +92,17 @@ func stats(cmd *cobra.Command, args []string) error {
goterm.MoveCursor(1, 1)
goterm.Flush()
}
- if len(format) == 0 {
+ if cmd.Flags().Changed("format") {
+ if err := printFormattedPodStatsLines(headers, row, reports); err != nil {
+ return err
+ }
+ } else {
printPodStatsLines(reports)
- } else if err := printFormattedPodStatsLines(format, reports, header); err != nil {
- return err
}
}
if statsOptions.NoStream {
break
}
- time.Sleep(time.Second)
}
return nil
@@ -115,72 +121,32 @@ func printPodStatsLines(stats []*entities.PodStatsReport) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
outFormat := "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS")
- for _, i := range stats {
- if len(stats) == 0 {
- fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--")
- } else {
+ if len(stats) == 0 {
+ fmt.Fprintf(w, outFormat, "--", "--", "--", "--", "--", "--", "--", "--", "--")
+ } else {
+ for _, i := range stats {
fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS)
}
}
w.Flush()
}
-func printFormattedPodStatsLines(format string, stats []*entities.PodStatsReport, headerNames map[string]string) error {
+func printFormattedPodStatsLines(headerNames []map[string]string, row string, stats []*entities.PodStatsReport) error {
if len(stats) == 0 {
return nil
}
- // Use a tabwriter to align column format
- w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
- // Spit out the header if "table" is present in the format
- if strings.HasPrefix(format, "table") {
- hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
- format = hformat
- headerTmpl, err := template.New("header").Parse(hformat)
- if err != nil {
- return err
- }
- if err := headerTmpl.Execute(w, headerNames); err != nil {
- return err
- }
- fmt.Fprintln(w, "")
- }
+ row = "{{range .}}" + row + "{{end}}"
- // Spit out the data rows now
- dataTmpl, err := template.New("data").Parse(format)
+ tmpl, err := template.New("pod stats").Parse(row)
if err != nil {
return err
}
- for _, s := range stats {
- if err := dataTmpl.Execute(w, s); err != nil {
- return err
- }
- fmt.Fprintln(w, "")
- }
- // Flush the writer
- return w.Flush()
-
-}
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
+ defer w.Flush()
-// getPodStatsHeader returns the stats header for the specified options.
-func getPodStatsHeader(format string) map[string]string {
- headerNames := make(map[string]string)
- if format == "" {
- return headerNames
- }
- // Make a map of the field names for the headers
- v := reflect.ValueOf(entities.PodStatsReport{})
- t := v.Type()
- for i := 0; i < t.NumField(); i++ {
- split := camelcase.Split(t.Field(i).Name)
- value := strings.ToUpper(strings.Join(split, " "))
- switch value {
- case "CPU", "MEM":
- value += " %"
- case "MEM USAGE":
- value = "MEM USAGE / LIMIT"
- }
- headerNames[t.Field(i).Name] = value
+ if err := tmpl.Execute(w, headerNames); err != nil {
+ return err
}
- return headerNames
+ return tmpl.Execute(w, stats)
}
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 1e73f7540..6293fa17d 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/validate"
+ "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/parallel"
"github.com/containers/podman/v2/pkg/rootless"
@@ -84,7 +85,7 @@ func init() {
func Execute() {
if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil {
- fmt.Fprintln(os.Stderr, "Error:", err.Error())
+ fmt.Fprintln(os.Stderr, formatError(err))
} else if registry.GetExitCode() == registry.ExecErrorCodeGeneric {
// The exitCode modified from registry.ExecErrorCodeGeneric,
// indicates an application
@@ -331,3 +332,19 @@ func resolveDestination() (string, string, string) {
}
return cfg.Engine.ActiveService, uri, ident
}
+
+func formatError(err error) string {
+ var message string
+ if errors.Cause(err) == define.ErrOCIRuntime {
+ // OCIRuntimeErrors include the reason for the failure in the
+ // second to last message in the error chain.
+ message = fmt.Sprintf(
+ "Error: %s: %s",
+ define.ErrOCIRuntime.Error(),
+ strings.TrimSuffix(err.Error(), ": "+define.ErrOCIRuntime.Error()),
+ )
+ } else {
+ message = "Error: " + err.Error()
+ }
+ return message
+}
diff --git a/cmd/podman/root_test.go b/cmd/podman/root_test.go
new file mode 100644
index 000000000..0473128df
--- /dev/null
+++ b/cmd/podman/root_test.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/pkg/errors"
+)
+
+func TestFormatError(t *testing.T) {
+ err := errors.New("unknown error")
+ output := formatError(err)
+ expected := fmt.Sprintf("Error: %v", err)
+
+ if output != expected {
+ t.Errorf("Expected \"%s\" to equal \"%s\"", output, err.Error())
+ }
+}
+
+func TestFormatOCIError(t *testing.T) {
+ expectedPrefix := "Error: "
+ expectedSuffix := "OCI runtime output"
+ err := errors.Wrap(define.ErrOCIRuntime, expectedSuffix)
+ output := formatError(err)
+
+ if !strings.HasPrefix(output, expectedPrefix) {
+ t.Errorf("Expected \"%s\" to start with \"%s\"", output, expectedPrefix)
+ }
+ if !strings.HasSuffix(output, expectedSuffix) {
+ t.Errorf("Expected \"%s\" to end with \"%s\"", output, expectedSuffix)
+ }
+}
diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go
index d198e51a7..18765a499 100644
--- a/cmd/podman/volumes/list.go
+++ b/cmd/podman/volumes/list.go
@@ -3,13 +3,14 @@ package volumes
import (
"context"
"fmt"
- "io"
"os"
"strings"
"text/tabwriter"
"text/template"
+ "github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@@ -55,7 +56,6 @@ func init() {
}
func list(cmd *cobra.Command, args []string) error {
- var w io.Writer = os.Stdout
if cliOpts.Quiet && cmd.Flag("format").Changed {
return errors.New("quiet and format flags cannot be used together")
}
@@ -73,40 +73,40 @@ func list(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- if cliOpts.Format == "json" {
- return outputJSON(responses)
- }
- if len(responses) < 1 {
+ switch {
+ case parse.MatchesJSONFormat(cliOpts.Format):
+ return outputJSON(responses)
+ case len(responses) < 1:
return nil
}
- // "\t" from the command line is not being recognized as a tab
- // replacing the string "\t" to a tab character if the user passes in "\t"
- cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1)
+ return outputTemplate(cmd, responses)
+}
+
+func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) error {
+ headers := report.Headers(entities.VolumeListReport{}, map[string]string{
+ "Name": "VOLUME NAME",
+ })
+
+ row := report.NormalizeFormat(cliOpts.Format)
if cliOpts.Quiet {
- cliOpts.Format = "{{.Name}}\n"
+ row = "{{.Name}}\n"
}
- headers := "DRIVER\tVOLUME NAME\n"
- row := cliOpts.Format
- if !strings.HasSuffix(cliOpts.Format, "\n") {
- row += "\n"
- }
- format := "{{range . }}" + row + "{{end}}"
- if !cliOpts.Quiet && !cmd.Flag("format").Changed {
- w = tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0)
- format = headers + format
- }
- tmpl, err := template.New("listVolume").Parse(format)
+ row = "{{range . }}" + row + "{{end}}"
+
+ tmpl, err := template.New("list volume").Parse(row)
if err != nil {
return err
}
- if err := tmpl.Execute(w, responses); err != nil {
- return err
- }
- if flusher, ok := w.(interface{ Flush() error }); ok {
- return flusher.Flush()
+ w := tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0)
+ defer w.Flush()
+
+ if !cliOpts.Quiet && !cmd.Flag("format").Changed {
+ if err := tmpl.Execute(w, headers); err != nil {
+ return errors.Wrapf(err, "failed to write report column headers")
+ }
}
- return nil
+ return tmpl.Execute(w, responses)
}
func outputJSON(vols []*entities.VolumeListReport) error {
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index 23987938b..e5124d8e4 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -6,18 +6,20 @@
# BEGIN Global export of all variables
set -a
-# Due to differences across platforms and runtime execution environments,
-# handling of the (otherwise) default shell setup is non-uniform. Rather
-# than attempt to workaround differences, simply force-load/set required
-# items every time this library is utilized.
-source /etc/profile
-source /etc/environment
-USER="$(whoami)"
-HOME="$(getent passwd $USER | cut -d : -f 6)"
-# Some platforms set and make this read-only
-[[ -n "$UID" ]] || \
- UID=$(getent passwd $USER | cut -d : -f 3)
-GID=$(getent passwd $USER | cut -d : -f 4)
+if [[ "$CI" == "true" ]]; then
+ # Due to differences across platforms and runtime execution environments,
+ # handling of the (otherwise) default shell setup is non-uniform. Rather
+ # than attempt to workaround differences, simply force-load/set required
+ # items every time this library is utilized.
+ source /etc/profile
+ source /etc/environment
+ USER="$(whoami)"
+ HOME="$(getent passwd $USER | cut -d : -f 6)"
+ # Some platforms set and make this read-only
+ [[ -n "$UID" ]] || \
+ UID=$(getent passwd $USER | cut -d : -f 3)
+ GID=$(getent passwd $USER | cut -d : -f 4)
+fi
# During VM Image build, the 'containers/automation' installation
# was performed. The final step of that installation sets the
@@ -43,6 +45,9 @@ OS_RELEASE_ID="$(source /etc/os-release; echo $ID)"
OS_RELEASE_VER="$(source /etc/os-release; echo $VERSION_ID | cut -d '.' -f 1)"
# Combined to ease soe usage
OS_REL_VER="${OS_RELEASE_ID}-${OS_RELEASE_VER}"
+# This is normally set from .cirrus.yml but default is necessary when
+# running under hack/get_ci_vm.sh since it cannot infer the value.
+DISTRO_NV="${DISTRO_NV:-$OS_REL_VER}"
# Essential default paths, many are overridden when executing under Cirrus-CI
GOPATH="${GOPATH:-/var/tmp/go}"
diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh
index 8a85acbd1..bfac8e7cb 100755
--- a/contrib/cirrus/runner.sh
+++ b/contrib/cirrus/runner.sh
@@ -139,6 +139,9 @@ function _run_vendor() {
}
function _run_build() {
+ # Ensure always start from clean-slate with all vendor modules downloaded
+ make clean
+ make vendor
make podman-release
make podman-remote-linux-release
}
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index c064b6840..156c9b7b2 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -67,9 +67,8 @@ case "$CG_FS_TYPE" in
*) die_unknown CG_FS_TYPE
esac
-# Required to be defined by caller: Which distribution are we testing on
-# shellcheck disable=SC2154
-case "$DISTRO_NV" in
+# Which distribution are we testing on.
+case "$OS_RELEASE_ID" in
ubuntu*) ;;
fedora*)
if ((CONTAINER==0)); then # Not yet running inside a container
@@ -83,7 +82,7 @@ case "$DISTRO_NV" in
setsebool container_manage_cgroup true
fi
;;
- *) die_unknown DISTRO_NV
+ *) die_unknown OS_RELEASE_ID
esac
# Required to be defined by caller: The environment where primary testing happens
diff --git a/contrib/cirrus/shellcheck.sh b/contrib/cirrus/shellcheck.sh
index edf8248d3..667d30c91 100755
--- a/contrib/cirrus/shellcheck.sh
+++ b/contrib/cirrus/shellcheck.sh
@@ -11,6 +11,6 @@ shellcheck --color=always --format=tty \
--enable add-default-case,avoid-nullary-conditions,check-unassigned-uppercase \
--exclude SC2046,SC2034,SC2090,SC2064 \
--wiki-link-count=0 --severity=warning \
- $SCRIPT_BASE/*.sh
+ $SCRIPT_BASE/*.sh hack/get_ci_vm.sh
echo "Shellcheck: PASS"
diff --git a/docs/source/Introduction.rst b/docs/source/Introduction.rst
index a1f9d605e..9dcae8a83 100644
--- a/docs/source/Introduction.rst
+++ b/docs/source/Introduction.rst
@@ -100,7 +100,7 @@ To summarize, Podman makes it easy to find, run, build and share containers.
* Find: whether finding a container on dockerhub.io or quay.io, an internal registry server, or directly from a vendor, a couple of `podman search`_, and `podman pull`_ commands make it easy
* Run: it's easy to consume pre-built images with everything needed to run an entire application, or start from a Linux distribution base image with the `podman run`_ command
-* Build: creating new layers with small tweaks, or major overhauls is easy with `podman build`
-* Share: Podman let’s you push your newly built containers anywhere you want with a single `podman push`_ command
+* Build: creating new layers with small tweaks, or major overhauls is easy with `podman build`_
+* Share: Podman lets you push your newly built containers anywhere you want with a single `podman push`_ command
For more instructions on use cases, take a look at our :doc:`Tutorials` page.
diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md
index dd9441800..519b153f4 100644
--- a/docs/source/markdown/podman-play-kube.1.md
+++ b/docs/source/markdown/podman-play-kube.1.md
@@ -30,6 +30,12 @@ environment variable. `export REGISTRY_AUTH_FILE=path`
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.
Default certificates directory is _/etc/containers/certs.d_. (Not available for remote commands)
+**--configmap**=*path*
+
+Use Kubernetes configmap YAML at path to provide a source for environment variable values within the containers of the pod.
+
+Note: The *--configmap* option can be used multiple times or a comma-separated list of paths can be used to pass multiple Kubernetes configmap YAMLs.
+
**--creds**
The [username[:password]] to use to authenticate with the registry if required.
@@ -66,6 +72,15 @@ $ podman play kube demo.yml
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
```
+Provide `configmap-foo.yml` and `configmap-bar.yml` as sources for environment variables within the containers.
+```
+$ podman play kube demo.yml --configmap configmap-foo.yml,configmap-bar.yml
+52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
+
+$ podman play kube demo.yml --configmap configmap-foo.yml --configmap configmap-bar.yml
+52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
+```
+
CNI network(s) can be specified as comma-separated list using ``--network``
```
$ podman play kube demo.yml --network cni1,cni2
diff --git a/docs/source/markdown/podman-rm.1.md b/docs/source/markdown/podman-rm.1.md
index e3e6740df..36904a128 100644
--- a/docs/source/markdown/podman-rm.1.md
+++ b/docs/source/markdown/podman-rm.1.md
@@ -43,13 +43,6 @@ to run containers such as CRI-O, the last started container could be from either
The latest option is not supported on the remote client.
-**--storage**
-
-Remove external containers from the storage library.
-This is only possible with containers that are not present in libpod can be seen by **podman ps --all --storage**).
-It is used to remove external containers from **podman build** and **buildah**, and orphan containers which were only partially removed by **podman rm**.
-The storage option conflicts with the **--all**, **--latest**, and **--volumes** options.
-
**--volumes**, **-v**
Remove anonymous volumes associated with the container. This does not include named volumes
@@ -96,7 +89,7 @@ $ podman rm -f --latest
**125** The command fails for any other reason
## SEE ALSO
-podman(1), podman-image-rm(1), podman-ps(1), podman-build(1)
+podman(1), podman-image-rm(1), podman-ps(1), podman-build(1), buildah(1), cri-o(1)
## HISTORY
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
diff --git a/docs/source/markdown/podman-search.1.md b/docs/source/markdown/podman-search.1.md
index 75bfeb058..2c2a8f012 100644
--- a/docs/source/markdown/podman-search.1.md
+++ b/docs/source/markdown/podman-search.1.md
@@ -27,7 +27,7 @@ Note, searching without a search term will only work for registries that impleme
**--authfile**=*path*
-Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json (Not available for remote commands)
+Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json
Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE
environment variable. `export REGISTRY_AUTH_FILE=path`
@@ -74,7 +74,7 @@ Do not truncate the output
Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true,
then TLS verification will be used. If set to false, then TLS verification will not be used if needed. If not specified,
default registries will be searched through (in /etc/containers/registries.conf), and TLS will be skipped if a default
-registry is listed in the insecure registries. (Not available for remote commands)
+registry is listed in the insecure registries.
**--help**, **-h**
diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh
index adf3b1bf2..f8c7e792e 100755
--- a/hack/get_ci_vm.sh
+++ b/hack/get_ci_vm.sh
@@ -1,49 +1,82 @@
#!/usr/bin/env bash
+#
+# For help and usage information, simply execute the script w/o any arguments.
+#
+# This script is intended to be run by podman developers who need to debug
+# problems specifically related to Cirrus-CI automated testing. However,
+# because it's only loosely coupled to the `.cirrus.yml` configuration, it must
+# orchestrate VMs in GCP directly. This means users need to have
+# pre-authorization (access) to manipulate google-cloud resoures. Additionally,
+# there are no guarantees it will remain in-sync with other automation-related
+# scripts. Therefore it may not always function for everybody in every
+# future scenario without updates/modifications/tweaks.
+
set -e
-RED="\e[1;36;41m"
-YEL="\e[1;33;44m"
+RED="\e[1;31m"
+YEL="\e[1;32m"
NOR="\e[0m"
USAGE_WARNING="
-${YEL}WARNING: This will not work without local sudo access to run podman,${NOR}
- ${YEL}and prior authorization to use the libpod GCP project. Also,${NOR}
- ${YEL}possession of the proper ssh private key is required.${NOR}
+${YEL}WARNING: This will not work without podman,${NOR}
+ ${YEL}and prior authorization to use the libpod GCP project.${NOR}
"
-# TODO: Many/most of these values should come from .cirrus.yml
+# These values come from .cirrus.yml gce_instance clause
ZONE="${ZONE:-us-central1-a}"
CPUS="2"
MEMORY="4Gb"
DISK="200"
PROJECT="libpod-218412"
GOSRC="/var/tmp/go/src/github.com/containers/podman"
-GCLOUD_IMAGE=${GCLOUD_IMAGE:-quay.io/cevich/gcloud_centos:latest}
-GCLOUD_SUDO=${GCLOUD_SUDO-sudo}
+GIT_REPO="https://github.com/containers/podman.git"
+
+# Container image with necessary runtime elements
+GCLOUD_IMAGE="${GCLOUD_IMAGE:-docker.io/google/cloud-sdk:alpine}"
+GCLOUD_CFGDIR=".config/gcloud"
+
+SCRIPT_FILENAME=$(basename ${BASH_SOURCE[0]})
+HOOK_FILENAME="hook_${SCRIPT_FILENAME}"
# Shared tmp directory between container and us
-TMPDIR=$(mktemp -d --tmpdir $(basename $0)_tmpdir_XXXXXX)
+TMPDIR=$(mktemp -d --tmpdir ${SCRIPT_FILENAME}_tmpdir_XXXXXX)
-LIBPODROOT=$(realpath "$(dirname $0)/../")
+show_usage() {
+ echo -e "\n${RED}ERROR: $1${NOR}"
+ echo -e "${YEL}Usage: $SCRIPT_FILENAME <image_name>${NOR}"
+ echo ""
+ if [[ -r ".cirrus.yml" ]]
+ then
+ echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}"
+ image_hints
+ echo ""
+ echo -e "${YEL}Optional:${NOR} If a $HOME/$GCLOUD_CFGDIR/$HOOK_FILENAME executable exists during"
+ echo "VM creation, it will be executed remotely after cloning"
+ echo "$GIT_REPO. The"
+ echo "current local working branch name and commit ID, will be provided as"
+ echo "it's arguments."
+ fi
+ exit 1
+}
+
+LIBPODROOT=$(realpath "$(dirname ${BASH_SOURCE[0]})/../")
# else: Assume $PWD is the root of the libpod repository
-[[ "$LIBPODROOT" != "/" ]] || LIBPODROOT=$PWD
+[[ "$LIBPODROOT" != "/" ]] || \
+ show_usage "Must execute script from within clone of containers/podman repo."
-# Command shortcuts save some typing (assumes $LIBPODROOT is subdir of $HOME)
-PGCLOUD="$GCLOUD_SUDO podman run -it --rm -e AS_ID=$UID -e AS_USER=$USER --security-opt label=disable -v $TMPDIR:$HOME -v $HOME/.config/gcloud:$HOME/.config/gcloud -v $HOME/.config/gcloud/ssh:$HOME/.ssh -v $LIBPODROOT:$LIBPODROOT $GCLOUD_IMAGE --configuration=libpod --project=$PROJECT"
-SCP_CMD="$PGCLOUD compute scp"
+[[ "$UID" -ne 0 ]] || \
+ show_usage "Must execute script as a regular (non-root) user."
+
+[[ "${LIBPODROOT#$HOME}" != "$LIBPODROOT" ]] || \
+ show_usage "Clone of containers/podman must be a subdirectory of \$HOME ($HOME)"
+# Disable SELinux labeling to allow read-only mounting of repository files
+PGCLOUD="podman run -it --rm --security-opt label=disable -v $TMPDIR:$TMPDIR -v $HOME/.config/gcloud:/root/.config/gcloud -v $HOME/.config/gcloud/ssh:/root/.ssh -v $LIBPODROOT:$LIBPODROOT:ro $GCLOUD_IMAGE gcloud --configuration=libpod --project=$PROJECT"
+SCP_CMD="$PGCLOUD compute scp"
showrun() {
- if [[ "$1" == "--background" ]]
- then
- shift
- # Properly escape any nested spaces, so command can be copy-pasted
- echo '+ '$(printf " %q" "$@")' &' > /dev/stderr
- "$@" &
- echo -e "${RED}<backgrounded>${NOR}"
- else
- echo '+ '$(printf " %q" "$@") > /dev/stderr
- "$@"
- fi
+ echo '+ '$(printf " %q" "$@") > /dev/stderr
+ echo ""
+ "$@"
}
cleanup() {
@@ -52,6 +85,7 @@ cleanup() {
wait
# set GCLOUD_DEBUG to leave tmpdir behind for postmortem
+ # shellcheck disable=SC2154
test -z "$GCLOUD_DEBUG" && rm -rf $TMPDIR
# Not always called from an exit handler, but should always exit when called
@@ -61,32 +95,18 @@ trap cleanup EXIT
delvm() {
echo -e "\n"
- echo -e "\n${YEL}Offering to Delete $VMNAME ${RED}(Might take a minute or two)${NOR}"
- echo -e "\n${YEL}Note: It's safe to answer N, then re-run script again later.${NOR}"
+ echo -e "\n${YEL}Offering to Delete $VMNAME${NOR}"
+ echo -e "${RED}(Deletion might take a minute or two)${NOR}"
+ echo -e "${YEL}Note: It's safe to answer N, then re-run script again later.${NOR}"
showrun $CLEANUP_CMD # prompts for Yes/No
cleanup
}
-show_usage() {
- echo -e "\n${RED}ERROR: $1${NOR}"
- echo -e "${YEL}Usage: $(basename $0) [-m <SPECIALMODE>] [-u <ROOTLESS_USER> ] <image_name>${NOR}"
- echo "Use -m <SPECIALMODE> with a supported value documented in contrib/cirrus/README.md."
- echo "With '-m rootless' must also specify -u <ROOTLESS_USER> with name of user to create & use"
- echo ""
- if [[ -r ".cirrus.yml" ]]
- then
- echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}"
- image_hints
- echo ""
- fi
- exit 1
-}
-
get_env_vars() {
# Deal with both YAML and embedded shell-like substitutions in values
# if substitution fails, fall back to printing naked env. var as-is.
python3 -c '
-import yaml,re
+import sys,yaml,re
env=yaml.load(open(".cirrus.yml"), Loader=yaml.SafeLoader)["env"]
dollar_env_var=re.compile(r"\$(\w+)")
dollarcurly_env_var=re.compile(r"\$\{(\w+)\}")
@@ -98,11 +118,10 @@ class ReIterKey(dict):
rep=r"{\1}" # Convert env vars markup to -> str.format_map(re_iter_key) markup
out=ReIterKey()
for k,v in env.items():
- v=str(v)
- if "ENCRYPTED" not in v:
- out[k]=dollar_env_var.sub(rep, dollarcurly_env_var.sub(rep, v))
+ if "ENCRYPTED" not in str(v) and bool(v):
+ out[k]=dollar_env_var.sub(rep, dollarcurly_env_var.sub(rep, str(v)))
for k,v in out.items():
- print("{0}=\"{1}\"".format(k, v.format_map(out)))
+ sys.stdout.write("{0}=\"{1}\"\n".format(k, str(v).format_map(out)))
'
}
@@ -110,8 +129,14 @@ image_hints() {
get_env_vars | fgrep '_CACHE_IMAGE_NAME' | awk -F "=" '{print $2}'
}
-
+unset VM_IMAGE_NAME
+unset VMNAME
+unset CREATE_CMD
+unset SSH_CMD
+unset CLEANUP_CMD
+declare -xa ENVS
parse_args(){
+ local arg
echo -e "$USAGE_WARNING"
if [[ "$USER" =~ "root" ]]
@@ -119,86 +144,41 @@ parse_args(){
show_usage "This script must be run as a regular user."
fi
- ENVS="$(get_env_vars)"
- [[ "$#" -ge "1" ]] || \
- show_usage "Must specify at least one command-line parameter."
-
- IMAGE_NAME=""
- ROOTLESS_USER=""
- SPECIALMODE="none"
- for arg
- do
- if [[ "$SPECIALMODE" == "GRABNEXT" ]] && [[ "${arg:0:1}" != "-" ]]
- then
- SPECIALMODE="$arg"
- echo -e "${YEL}Using \$SPECIALMODE=$SPECIALMODE.${NOR}"
- continue
- elif [[ "$ROOTLESS_USER" == "GRABNEXT" ]] && [[ "${arg:0:1}" != "-" ]]
- then
- ROOTLESS_USER="$arg"
- echo -e "${YEL}Using \$ROOTLESS_USER=$ROOTLESS_USER.${NOR}"
- continue
- fi
- case "$arg" in
- -m)
- SPECIALMODE="GRABNEXT"
- ;;
- -u)
- ROOTLESS_USER="GRABNEXT"
- ;;
- *)
- [[ "${arg:0:1}" != "-" ]] || \
- show_usage "Unknown command-line option '$arg'."
- [[ -z "$IMAGE_NAME" ]] || \
- show_usage "Must specify exactly one image name, got '$IMAGE_NAME' and '$arg'."
- IMAGE_NAME="$arg"
- ;;
- esac
- done
+ [[ "$#" -eq 1 ]] || \
+ show_usage "Must specify a VM Image name to use, and the test flavor."
- if [[ "$SPECIALMODE" == "GRABNEXT" ]]
- then
- show_usage "Must specify argument to -m option."
- fi
+ VM_IMAGE_NAME="$1"
- if [[ "$ROOTLESS_USER" == "GRABNEXT" ]]
- then
- show_usage "Must specify argument to -u option."
- fi
+ # Word-splitting is desireable in this case
+ # shellcheck disable=SC2207
+ ENVS=(
+ $(get_env_vars)
+ "VM_IMAGE_NAME=$VM_IMAGE_NAME"
+ )
- if [[ -z "$IMAGE_NAME" ]]
- then
- show_usage "No image-name specified."
- fi
+ VMNAME="${VMNAME:-${USER}-${VM_IMAGE_NAME}}"
- if [[ "$SPECIALMODE" == "rootless" ]] && [[ -z "$ROOTLESS_USER" ]]
- then
- show_usage "With '-m rootless' must also pass -u <username> of rootless user."
- fi
-
- if echo "$IMAGE_NAME" | grep -q "image-builder-image"
- then
- echo -e "Creating an image-builder VM, I hope you know what you're doing.\n"
- IBI_ARGS="--scopes=compute-rw,storage-rw,userinfo-email"
- SSHUSER="centos"
- else
- unset IBI_ARGS
- SSHUSER="root"
- fi
+ CREATE_CMD="$PGCLOUD compute instances create --zone=$ZONE --image=${VM_IMAGE_NAME} --custom-cpu=$CPUS --custom-memory=$MEMORY --boot-disk-size=$DISK --labels=in-use-by=$USER $VMNAME"
- ENVS="$ENVS SPECIALMODE=\"$SPECIALMODE\""
+ SSH_CMD="$PGCLOUD compute ssh root@$VMNAME"
- [[ -z "$ROOTLESS_USER" ]] || \
- ENVS="$ENVS ROOTLESS_USER=$ROOTLESS_USER"
-
- SETUP_CMD="env $ENVS ADD_SECOND_PARTITIO=True $GOSRC/contrib/cirrus/setup_environment.sh"
- VMNAME="${VMNAME:-${USER}-${IMAGE_NAME}}"
+ CLEANUP_CMD="$PGCLOUD compute instances delete --zone $ZONE --delete-disks=all $VMNAME"
+}
- CREATE_CMD="$PGCLOUD compute instances create --zone=$ZONE --image=${IMAGE_NAME} --custom-cpu=$CPUS --custom-memory=$MEMORY --boot-disk-size=$DISK --labels=in-use-by=$USER $IBI_ARGS $VMNAME"
+# Returns true if user has run an 'init' and has a valid token for
+# the specific project-id and named-configuration argumens in $PGCLOUD.
+function has_valid_credentials() {
+ if $PGCLOUD info |& grep -Eq 'Account:.*None'; then
+ return 1
+ fi
- SSH_CMD="$PGCLOUD compute ssh $SSHUSER@$VMNAME"
+ # It's possible for 'gcloud info' to list expired credentials,
+ # e.g. 'ERROR: ... invalid grant: Bad Request'
+ if $PGCLOUD auth print-access-token |& grep -q 'ERROR'; then
+ return 1
+ fi
- CLEANUP_CMD="$PGCLOUD compute instances delete --zone $ZONE --delete-disks=all $VMNAME"
+ return 0
}
##### main
@@ -209,23 +189,17 @@ parse_args(){
cd "$LIBPODROOT"
parse_args "$@"
-
-# Ensure mount-points and data directories exist on host as $USER. Also prevents
-# permission-denied errors during cleanup() b/c `sudo podman` created mount-points
-# owned by root.
-mkdir -p $TMPDIR/${LIBPODROOT##$HOME}
mkdir -p $TMPDIR/.ssh
mkdir -p {$HOME,$TMPDIR}/.config/gcloud/ssh
chmod 700 {$HOME,$TMPDIR}/.config/gcloud/ssh $TMPDIR/.ssh
-cd $LIBPODROOT
+echo -e "\n${YEL}Pulling gcloud image...${NOR}"
+podman pull $GCLOUD_IMAGE
-# Attempt to determine if named 'libpod' gcloud configuration exists
-showrun $PGCLOUD info > $TMPDIR/gcloud-info
-if egrep -q "Account:.*None" $TMPDIR/gcloud-info
+if ! has_valid_credentials
then
echo -e "\n${YEL}WARNING: Can't find gcloud configuration for libpod, running init.${NOR}"
- echo -e " ${RED}Please choose "#1: Re-initialize" and "login" if asked.${NOR}"
+ echo -e " ${RED}Please choose \"#1: Re-initialize\" and \"login\" if asked.${NOR}"
showrun $PGCLOUD init --project=$PROJECT --console-only --skip-diagnostics
# Verify it worked (account name == someone@example.com)
@@ -236,68 +210,52 @@ then
exit 5
fi
- # If this is the only config, make it the default to avoid persistent warnings from gcloud
+ # If this is the only config, make it the default to avoid
+ # persistent warnings from gcloud about there being no default.
[[ -r "$HOME/.config/gcloud/configurations/config_default" ]] || \
- ln "$HOME/.config/gcloud/configurations/config_libpod" \
- "$HOME/.config/gcloud/configurations/config_default"
+ ln "$HOME/.config/gcloud/configurations/config_libpod" \
+ "$HOME/.config/gcloud/configurations/config_default"
fi
-# Couldn't make rsync work with gcloud's ssh wrapper because ssh-keys generated on the fly
-TARBALL=$VMNAME.tar.bz2
-echo -e "\n${YEL}Packing up local repository into a tarball.${NOR}"
-showrun --background tar cjf $TMPDIR/$TARBALL --warning=no-file-changed --exclude-vcs-ignores -C $LIBPODROOT .
-
-trap delvm INT # Allow deleting VM if CTRL-C during create
-# This fails if VM already exists: permit this usage to re-init
+trap delvm EXIT # Allow deleting VM if CTRL-C during create
echo -e "\n${YEL}Trying to creating a VM named $VMNAME${NOR}\n${YEL}in GCE region/zone $ZONE${NOR}"
-echo -e "For faster access, export ZONE='something-closer-<any letter>'"
-echo 'List of regions and zones: https://cloud.google.com/compute/docs/regions-zones/'
-echo -e "${RED}(might take a minute/two. Errors ignored).${NOR}"
-showrun $CREATE_CMD || true # allow re-running commands below when "delete: N"
-
-# Any subsequent failure should prompt for VM deletion
-trap - INT
-trap delvm EXIT
-
-echo -e "\n${YEL}Waiting up to 30s for ssh port to open${NOR}"
-trap 'COUNT=9999' INT
-ATTEMPTS=10
-for (( COUNT=1 ; COUNT <= $ATTEMPTS ; COUNT++ ))
-do
- if $SSH_CMD --command "true"; then break; else sleep 3s; fi
-done
-if (( COUNT > $ATTEMPTS ))
-then
- echo -e "\n${RED}Failed${NOR}"
- exit 7
-fi
-echo -e "${YEL}Got it${NOR}"
-
-echo -e "\n${YEL}Removing and re-creating $GOSRC on $VMNAME.${NOR}"
-showrun $SSH_CMD --command "rm -rf $GOSRC"
-showrun $SSH_CMD --command "mkdir -p $GOSRC"
-
-echo -e "\n${YEL}Transferring tarball to $VMNAME.${NOR}"
-wait
-showrun $SCP_CMD $HOME/$TARBALL $SSHUSER@$VMNAME:/tmp/$TARBALL
-
-echo -e "\n${YEL}Unpacking tarball into $GOSRC on $VMNAME.${NOR}"
-showrun $SSH_CMD --command "tar xjf /tmp/$TARBALL -C $GOSRC"
+echo -e "For faster terminal access, export ZONE='<something-closer>'"
+echo -e 'Zone-list at: https://cloud.google.com/compute/docs/regions-zones/\n'
+if showrun $CREATE_CMD; then # Freshly created VM needs initial setup
+
+ echo -e "\n${YEL}Waiting up to 30s for ssh port to open${NOR}"
+ ATTEMPTS=10
+ trap "exit 1" INT
+ while ((ATTEMPTS)) && ! $SSH_CMD --command "true"; do
+ let "ATTEMPTS--"
+ echo -e "${RED}Nope, not yet.${NOR}"
+ sleep 3s
+ done
+ trap - INT
+ if ! ((ATTEMPTS)); then
+ echo -e "\n${RED}Failed${NOR}"
+ exit 7
+ fi
+ echo -e "${YEL}Got it. Cloning upstream repository as a starting point.${NOR}"
-echo -e "\n${YEL}Removing tarball on $VMNAME.${NOR}"
-showrun $SSH_CMD --command "rm -f /tmp/$TARBALL"
+ showrun $SSH_CMD -- "mkdir -p $GOSRC"
+ showrun $SSH_CMD -- "git clone --progress $GIT_REPO $GOSRC"
-echo -e "\n${YEL}Executing environment setup${NOR}"
-showrun $SSH_CMD --command "$SETUP_CMD"
+ if [[ -x "$HOME/$GCLOUD_CFGDIR/$HOOK_FILENAME" ]]; then
+ echo -e "\n${YEL}Copying hook to VM and executing (ignoring errors).${NOR}"
+ $PGCLOUD compute scp "/root/$GCLOUD_CFGDIR/$HOOK_FILENAME" root@$VMNAME:.
+ if ! showrun $SSH_CMD -- "cd $GOSRC && bash /root/$HOOK_FILENAME $(git branch --show-current) $(git rev-parse HEAD)"; then
+ echo "-e ${RED}Hook exited: $?${NOR}"
+ fi
+ fi
+fi
-VMIP=$($PGCLOUD compute instances describe $VMNAME --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
+echo -e "\n${YEL}Generating connection script for $VMNAME.${NOR}"
+echo -e "Note: Script can be re-used in another terminal if needed."
+echo -e "${RED}(option to delete VM presented upon exiting).${NOR}"
+# TODO: This is fairly fragile, specifically the quoting for the remote command.
+echo '#!/bin/bash' > $TMPDIR/ssh
+echo "$SSH_CMD -- -t 'cd $GOSRC && exec env \"${ENVS[*]}\" bash -il'" >> $TMPDIR/ssh
+chmod +x $TMPDIR/ssh
-echo -e "\n${YEL}Connecting to $VMNAME${NOR}\nPublic IP Address: $VMIP\n${RED}(option to delete VM upon logout).${NOR}\n"
-if [[ -n "$ROOTLESS_USER" ]]
-then
- echo "Re-chowning source files after transfer"
- showrun $SSH_CMD --command "chown -R $ROOTLESS_USER $GOSRC"
- echo "Connecting as user $ROOTLESS_USER"
- SSH_CMD="$PGCLOUD compute ssh $ROOTLESS_USER@$VMNAME"
-fi
-showrun $SSH_CMD -- -t "cd $GOSRC && exec env $ENVS bash -il"
+showrun $TMPDIR/ssh
diff --git a/libpod/container.go b/libpod/container.go
index 9b4ccbd5f..01419500e 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -888,9 +888,22 @@ func (c *Container) NamespacePath(linuxNS LinuxNS) (string, error) { //nolint:in
return fmt.Sprintf("/proc/%d/ns/%s", c.state.PID, linuxNS.String()), nil
}
+// CgroupManager returns the cgroup manager used by the given container.
+func (c *Container) CgroupManager() string {
+ cgroupManager := c.config.CgroupManager
+ if cgroupManager == "" {
+ cgroupManager = c.runtime.config.Engine.CgroupManager
+ }
+ return cgroupManager
+}
+
// CGroupPath returns a cgroups "path" for a given container.
func (c *Container) CGroupPath() (string, error) {
+ cgroupManager := c.CgroupManager()
+
switch {
+ case c.config.NoCgroups || c.config.CgroupsMode == "disabled":
+ return "", errors.Wrapf(define.ErrNoCgroups, "this container is not creating cgroups")
case c.config.CgroupsMode == cgroupSplit:
if c.config.CgroupParent != "" {
return "", errors.Errorf("cannot specify cgroup-parent with cgroup-mode %q", cgroupSplit)
@@ -906,9 +919,9 @@ func (c *Container) CGroupPath() (string, error) {
return "", errors.Errorf("invalid cgroup for conmon %q", cg)
}
return strings.TrimSuffix(cg, "/supervisor") + "/container", nil
- case c.runtime.config.Engine.CgroupManager == config.CgroupfsCgroupsManager:
+ case cgroupManager == config.CgroupfsCgroupsManager:
return filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())), nil
- case c.runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager:
+ case cgroupManager == config.SystemdCgroupsManager:
if rootless.IsRootless() {
uid := rootless.GetRootlessUID()
parts := strings.SplitN(c.config.CgroupParent, "/", 2)
@@ -922,7 +935,7 @@ func (c *Container) CGroupPath() (string, error) {
}
return filepath.Join(c.config.CgroupParent, createUnitName("libpod", c.ID())), nil
default:
- return "", errors.Wrapf(define.ErrInvalidArg, "unsupported CGroup manager %s in use", c.runtime.config.Engine.CgroupManager)
+ return "", errors.Wrapf(define.ErrInvalidArg, "unsupported CGroup manager %s in use", cgroupManager)
}
}
diff --git a/libpod/container_config.go b/libpod/container_config.go
index fc93140dd..e264da4da 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -275,13 +275,16 @@ type ContainerMiscConfig struct {
StopTimeout uint `json:"stopTimeout,omitempty"`
// Time container was created
CreatedTime time.Time `json:"createdTime"`
+ // CgroupManager is the cgroup manager used to create this container.
+ // If empty, the runtime default will be used.
+ CgroupManager string `json:"cgroupManager,omitempty"`
// NoCgroups indicates that the container will not create CGroups. It is
// incompatible with CgroupParent. Deprecated in favor of CgroupsMode.
NoCgroups bool `json:"noCgroups,omitempty"`
// CgroupsMode indicates how the container will create cgroups
// (disabled, no-conmon, enabled). It supersedes NoCgroups.
CgroupsMode string `json:"cgroupsMode,omitempty"`
- // Cgroup parent of the container
+ // Cgroup parent of the container.
CgroupParent string `json:"cgroupParent"`
// LogPath log location
LogPath string `json:"logPath"`
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 835dccd71..b8bce1272 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -729,7 +729,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
// CGroup parent
// Need to check if it's the default, and not print if so.
defaultCgroupParent := ""
- switch c.runtime.config.Engine.CgroupManager {
+ switch c.CgroupManager() {
case config.CgroupfsCgroupsManager:
defaultCgroupParent = CgroupfsDefaultCgroupParent
case config.SystemdCgroupsManager:
@@ -738,6 +738,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
if c.config.CgroupParent != defaultCgroupParent {
hostConfig.CgroupParent = c.config.CgroupParent
}
+ hostConfig.CgroupManager = c.CgroupManager()
// PID namespace mode
pidMode := ""
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index d64d3ab87..4ae571de6 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -976,6 +976,21 @@ func (c *Container) completeNetworkSetup() error {
}
}
}
+ // check if we have a bindmount for /etc/hosts
+ if hostsBindMount, ok := state.BindMounts["/etc/hosts"]; ok && len(c.cniHosts()) > 0 {
+ ctrHostPath := filepath.Join(c.state.RunDir, "hosts")
+ if hostsBindMount == ctrHostPath {
+ // read the existing hosts
+ b, err := ioutil.ReadFile(hostsBindMount)
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(hostsBindMount, append(b, []byte(c.cniHosts())...), 0644); err != nil {
+ return err
+ }
+ }
+ }
+
// check if we have a bindmount for resolv.conf
resolvBindMount := state.BindMounts["/etc/resolv.conf"]
if len(outResolvConf) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 {
@@ -997,6 +1012,15 @@ func (c *Container) completeNetworkSetup() error {
return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644)
}
+func (c *Container) cniHosts() string {
+ var hosts string
+ if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
+ ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
+ hosts += fmt.Sprintf("%s\t%s %s\n", ipAddress, c.Hostname(), c.Config().Name)
+ }
+ return hosts
+}
+
// Initialize a container, creating it in the runtime
func (c *Container) init(ctx context.Context, retainRetries bool) error {
span, _ := opentracing.StartSpanFromContext(ctx, "init")
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 894982973..3a71c6601 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1543,10 +1543,7 @@ func (c *Container) getHosts() string {
// When using slirp4netns, the interface gets a static IP
hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", "10.0.2.100", c.Hostname(), c.Config().Name)
}
- if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
- ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
- hosts += fmt.Sprintf("%s\t%s %s\n", ipAddress, c.Hostname(), c.Config().Name)
- }
+ hosts += c.cniHosts()
return hosts
}
@@ -1968,6 +1965,7 @@ func (c *Container) getOCICgroupPath() (string, error) {
if err != nil {
return "", err
}
+ cgroupManager := c.CgroupManager()
switch {
case (rootless.IsRootless() && !unified) || c.config.NoCgroups:
return "", nil
@@ -1980,14 +1978,14 @@ func (c *Container) getOCICgroupPath() (string, error) {
return "", err
}
return filepath.Join(selfCgroup, "container"), nil
- case c.runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager:
+ case cgroupManager == config.SystemdCgroupsManager:
// When the OCI runtime is set to use Systemd as a cgroup manager, it
// expects cgroups to be passed as follows:
// slice:prefix:name
systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID())
logrus.Debugf("Setting CGroups for container %s to %s", c.ID(), systemdCgroups)
return systemdCgroups, nil
- case c.runtime.config.Engine.CgroupManager == config.CgroupfsCgroupsManager:
+ case cgroupManager == config.CgroupfsCgroupsManager:
cgroupPath, err := c.CGroupPath()
if err != nil {
return "", err
@@ -1995,7 +1993,7 @@ func (c *Container) getOCICgroupPath() (string, error) {
logrus.Debugf("Setting CGroup path for container %s to %s", c.ID(), cgroupPath)
return cgroupPath, nil
default:
- return "", errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager %s requested", c.runtime.config.Engine.CgroupManager)
+ return "", errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager %s requested", cgroupManager)
}
}
diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go
index 44c3d515b..38b3a6686 100644
--- a/libpod/define/container_inspect.go
+++ b/libpod/define/container_inspect.go
@@ -236,6 +236,9 @@ type InspectContainerHostConfig struct {
// include a Mounts field in inspect.
// Format: <src>:<destination>[:<comma-separated options>]
Binds []string `json:"Binds"`
+ // CgroupManager is the cgroup manager used by the container.
+ // At present, allowed values are either "cgroupfs" or "systemd".
+ CgroupManager string `json:"CgroupManager,omitempty"`
// CgroupMode is the configuration of the container's cgroup namespace.
// Populated as follows:
// private - a cgroup namespace has been created
diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go
index 257b7ae8d..835473a1f 100644
--- a/libpod/image/docker_registry_options.go
+++ b/libpod/image/docker_registry_options.go
@@ -55,6 +55,7 @@ func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, add
sc.DockerRegistryUserAgent = parent.DockerRegistryUserAgent
sc.OSChoice = parent.OSChoice
sc.ArchitectureChoice = parent.ArchitectureChoice
+ sc.BlobInfoCacheDir = parent.BlobInfoCacheDir
}
return sc
}
diff --git a/libpod/kube.go b/libpod/kube.go
index 6df79e394..cd5064c84 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -307,18 +307,40 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
kubeContainer.StdinOnce = false
kubeContainer.TTY = c.config.Spec.Process.Terminal
- // TODO add CPU limit support.
if c.config.Spec.Linux != nil &&
- c.config.Spec.Linux.Resources != nil &&
- c.config.Spec.Linux.Resources.Memory != nil &&
- c.config.Spec.Linux.Resources.Memory.Limit != nil {
- if kubeContainer.Resources.Limits == nil {
- kubeContainer.Resources.Limits = v1.ResourceList{}
+ c.config.Spec.Linux.Resources != nil {
+ if c.config.Spec.Linux.Resources.Memory != nil &&
+ c.config.Spec.Linux.Resources.Memory.Limit != nil {
+ if kubeContainer.Resources.Limits == nil {
+ kubeContainer.Resources.Limits = v1.ResourceList{}
+ }
+
+ qty := kubeContainer.Resources.Limits.Memory()
+ qty.Set(*c.config.Spec.Linux.Resources.Memory.Limit)
+ kubeContainer.Resources.Limits[v1.ResourceMemory] = *qty
}
- qty := kubeContainer.Resources.Limits.Memory()
- qty.Set(*c.config.Spec.Linux.Resources.Memory.Limit)
- kubeContainer.Resources.Limits[v1.ResourceMemory] = *qty
+ if c.config.Spec.Linux.Resources.CPU != nil &&
+ c.config.Spec.Linux.Resources.CPU.Quota != nil &&
+ c.config.Spec.Linux.Resources.CPU.Period != nil {
+ quota := *c.config.Spec.Linux.Resources.CPU.Quota
+ period := *c.config.Spec.Linux.Resources.CPU.Period
+
+ if quota > 0 && period > 0 {
+ cpuLimitMilli := int64(1000 * float64(quota) / float64(period))
+
+ // Kubernetes: precision finer than 1m is not allowed
+ if cpuLimitMilli >= 1 {
+ if kubeContainer.Resources.Limits == nil {
+ kubeContainer.Resources.Limits = v1.ResourceList{}
+ }
+
+ qty := kubeContainer.Resources.Limits.Cpu()
+ qty.SetMilli(cpuLimitMilli)
+ kubeContainer.Resources.Limits[v1.ResourceCPU] = *qty
+ }
+ }
+ }
}
return kubeContainer, kubeVolumes, nil
diff --git a/pkg/network/config.go b/libpod/network/config.go
index 0115433e1..a08e684d8 100644
--- a/pkg/network/config.go
+++ b/libpod/network/config.go
@@ -3,6 +3,8 @@ package network
import (
"encoding/json"
"net"
+
+ "github.com/containers/storage/pkg/lockfile"
)
// TODO once the containers.conf file stuff is worked out, this should be modified
@@ -17,8 +19,17 @@ const (
// DefaultPodmanDomainName is used for the dnsname plugin to define
// a localized domain name for a created network
DefaultPodmanDomainName = "dns.podman"
+ // LockFileName is used for obtaining a lock and is appended
+ // to libpod's tmpdir in practice
+ LockFileName = "cni.lock"
)
+// CNILock is for preventing name collision and
+// unpredictable results when doing some CNI operations.
+type CNILock struct {
+ lockfile.Locker
+}
+
// GetDefaultPodmanNetwork outputs the default network for podman
func GetDefaultPodmanNetwork() (*net.IPNet, error) {
_, n, err := net.ParseCIDR("10.88.1.0/24")
diff --git a/libpod/network/create.go b/libpod/network/create.go
new file mode 100644
index 000000000..a9ed4c4ef
--- /dev/null
+++ b/libpod/network/create.go
@@ -0,0 +1,195 @@
+package network
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/containernetworking/cni/pkg/version"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtime) (*entities.NetworkCreateReport, error) {
+ var fileName string
+ if err := isSupportedDriver(options.Driver); err != nil {
+ return nil, err
+ }
+ config, err := r.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ // Acquire a lock for CNI
+ l, err := acquireCNILock(filepath.Join(config.Engine.TmpDir, LockFileName))
+ if err != nil {
+ return nil, err
+ }
+ defer l.releaseCNILock()
+ if len(options.MacVLAN) > 0 {
+ fileName, err = createMacVLAN(r, name, options)
+ } else {
+ fileName, err = createBridge(r, name, options)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &entities.NetworkCreateReport{Filename: fileName}, nil
+}
+
+// createBridge creates a CNI network
+func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+ isGateway := true
+ ipMasq := true
+ subnet := &options.Subnet
+ ipRange := options.Range
+ runtimeConfig, err := r.GetConfig()
+ if err != nil {
+ return "", err
+ }
+ // if range is provided, make sure it is "in" network
+ if subnet.IP != nil {
+ // if network is provided, does it conflict with existing CNI or live networks
+ err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
+ } else {
+ // if no network is provided, figure out network
+ subnet, err = GetFreeNetwork(runtimeConfig)
+ }
+ if err != nil {
+ return "", err
+ }
+ gateway := options.Gateway
+ if gateway == nil {
+ // if no gateway is provided, provide it as first ip of network
+ gateway = CalcGatewayIP(subnet)
+ }
+ // if network is provided and if gateway is provided, make sure it is "in" network
+ if options.Subnet.IP != nil && options.Gateway != nil {
+ if !subnet.Contains(gateway) {
+ return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
+ }
+ }
+ if options.Internal {
+ isGateway = false
+ ipMasq = false
+ }
+
+ // if a range is given, we need to ensure it is "in" the network range.
+ if options.Range.IP != nil {
+ if options.Subnet.IP == nil {
+ return "", errors.New("you must define a subnet range to define an ip-range")
+ }
+ firstIP, err := FirstIPInSubnet(&options.Range)
+ if err != nil {
+ return "", err
+ }
+ lastIP, err := LastIPInSubnet(&options.Range)
+ if err != nil {
+ return "", err
+ }
+ if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
+ return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String())
+ }
+ }
+ bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig)
+ if err != nil {
+ return "", err
+ }
+
+ if len(name) > 0 {
+ netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ } else {
+ // If no name is given, we give the name of the bridge device
+ name = bridgeDeviceName
+ }
+
+ ncList := NewNcList(name, version.Current())
+ var plugins []CNIPlugins
+ var routes []IPAMRoute
+
+ defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP))
+ if err != nil {
+ return "", err
+ }
+ routes = append(routes, defaultRoute)
+ ipamConfig, err := NewIPAMHostLocalConf(subnet, routes, ipRange, gateway)
+ if err != nil {
+ return "", err
+ }
+
+ // TODO need to iron out the role of isDefaultGW and IPMasq
+ bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
+ plugins = append(plugins, bridge)
+ plugins = append(plugins, NewPortMapPlugin())
+ plugins = append(plugins, NewFirewallPlugin())
+ // if we find the dnsname plugin, we add configuration for it
+ if HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS {
+ // Note: in the future we might like to allow for dynamic domain names
+ plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName))
+ }
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ if err := os.MkdirAll(GetCNIConfDir(runtimeConfig), 0755); err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
+
+func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+ var (
+ plugins []CNIPlugins
+ )
+ liveNetNames, err := GetLiveNetworkNames()
+ if err != nil {
+ return "", err
+ }
+
+ config, err := r.GetConfig()
+ if err != nil {
+ return "", err
+ }
+
+ // Make sure the host-device exists
+ if !util.StringInSlice(options.MacVLAN, liveNetNames) {
+ return "", errors.Errorf("failed to find network interface %q", options.MacVLAN)
+ }
+ if len(name) > 0 {
+ netNames, err := GetNetworkNamesFromFileSystem(config)
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ } else {
+ name, err = GetFreeDeviceName(config)
+ if err != nil {
+ return "", err
+ }
+ }
+ ncList := NewNcList(name, version.Current())
+ macvlan := NewMacVLANPlugin(options.MacVLAN)
+ plugins = append(plugins, macvlan)
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(GetCNIConfDir(config), fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
diff --git a/pkg/network/devices.go b/libpod/network/devices.go
index a5d23fae4..a5d23fae4 100644
--- a/pkg/network/devices.go
+++ b/libpod/network/devices.go
diff --git a/pkg/network/files.go b/libpod/network/files.go
index a2090491f..a2090491f 100644
--- a/pkg/network/files.go
+++ b/libpod/network/files.go
diff --git a/pkg/network/ip.go b/libpod/network/ip.go
index ba93a0d05..ba93a0d05 100644
--- a/pkg/network/ip.go
+++ b/libpod/network/ip.go
diff --git a/libpod/network/lock.go b/libpod/network/lock.go
new file mode 100644
index 000000000..0395359eb
--- /dev/null
+++ b/libpod/network/lock.go
@@ -0,0 +1,26 @@
+package network
+
+import (
+ "github.com/containers/storage"
+)
+
+// acquireCNILock gets a lock that should be used in create and
+// delete cases to avoid unwanted collisions in network names.
+// TODO this uses a file lock and should be converted to shared memory
+// when we have a more general shared memory lock in libpod
+func acquireCNILock(lockPath string) (*CNILock, error) {
+ l, err := storage.GetLockfile(lockPath)
+ if err != nil {
+ return nil, err
+ }
+ l.Lock()
+ cnilock := CNILock{
+ Locker: l,
+ }
+ return &cnilock, nil
+}
+
+// ReleaseCNILock unlocks the previously held lock
+func (l *CNILock) releaseCNILock() {
+ l.Unlock()
+}
diff --git a/pkg/network/netconflist.go b/libpod/network/netconflist.go
index 8187fdb39..8187fdb39 100644
--- a/pkg/network/netconflist.go
+++ b/libpod/network/netconflist.go
diff --git a/pkg/network/netconflist_test.go b/libpod/network/netconflist_test.go
index 5893bf985..5893bf985 100644
--- a/pkg/network/netconflist_test.go
+++ b/libpod/network/netconflist_test.go
diff --git a/pkg/network/network.go b/libpod/network/network.go
index c4c1ff67f..7327a1a7d 100644
--- a/pkg/network/network.go
+++ b/libpod/network/network.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"net"
"os"
+ "path/filepath"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
@@ -20,8 +21,8 @@ var DefaultNetworkDriver = "bridge"
// SupportedNetworkDrivers describes the list of supported drivers
var SupportedNetworkDrivers = []string{DefaultNetworkDriver}
-// IsSupportedDriver checks if the user provided driver is supported
-func IsSupportedDriver(driver string) error {
+// isSupportedDriver checks if the user provided driver is supported
+func isSupportedDriver(driver string) error {
if util.StringInSlice(driver, SupportedNetworkDrivers) {
return nil
}
@@ -168,6 +169,11 @@ func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) e
// RemoveNetwork removes a given network by name. If the network has container associated with it, that
// must be handled outside the context of this.
func RemoveNetwork(config *config.Config, name string) error {
+ l, err := acquireCNILock(filepath.Join(config.Engine.TmpDir, LockFileName))
+ if err != nil {
+ return err
+ }
+ defer l.releaseCNILock()
cniPath, err := GetCNIConfigPathByName(config, name)
if err != nil {
return err
diff --git a/pkg/network/network_test.go b/libpod/network/network_test.go
index 1969e792c..1969e792c 100644
--- a/pkg/network/network_test.go
+++ b/libpod/network/network_test.go
diff --git a/pkg/network/subnet.go b/libpod/network/subnet.go
index 90f0cdfce..90f0cdfce 100644
--- a/pkg/network/subnet.go
+++ b/libpod/network/subnet.go
diff --git a/pkg/network/subnet_test.go b/libpod/network/subnet_test.go
index 917c3be88..917c3be88 100644
--- a/pkg/network/subnet_test.go
+++ b/libpod/network/subnet_test.go
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index 7fb374e0d..94630e57b 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -57,7 +57,6 @@ type ConmonOCIRuntime struct {
path string
conmonPath string
conmonEnv []string
- cgroupManager string
tmpDir string
exitsDir string
socketsDir string
@@ -102,7 +101,6 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime
runtime.runtimeFlags = runtimeFlags
runtime.conmonEnv = runtimeCfg.Engine.ConmonEnvVars
- runtime.cgroupManager = runtimeCfg.Engine.CgroupManager
runtime.tmpDir = runtimeCfg.Engine.TmpDir
runtime.logSizeMax = runtimeCfg.Containers.LogSizeMax
runtime.noPivot = runtimeCfg.Engine.NoPivotRoot
@@ -149,10 +147,6 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime
runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits")
runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket")
- if runtime.cgroupManager != config.CgroupfsCgroupsManager && runtime.cgroupManager != config.SystemdCgroupsManager {
- return nil, errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager specified: %s", runtime.cgroupManager)
- }
-
// Create the exit files and attach sockets directories
if err := os.MkdirAll(runtime.exitsDir, 0750); err != nil {
// The directory is allowed to exist
@@ -1325,7 +1319,7 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
args = append(args, rFlags...)
}
- if r.cgroupManager == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit {
+ if ctr.CgroupManager() == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit {
args = append(args, "-s")
}
@@ -1442,8 +1436,10 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec
}
if mustCreateCgroup {
+ // TODO: This should be a switch - we are not guaranteed that
+ // there are only 2 valid cgroup managers
cgroupParent := ctr.CgroupParent()
- if r.cgroupManager == config.SystemdCgroupsManager {
+ if ctr.CgroupManager() == config.SystemdCgroupsManager {
unitName := createUnitName("libpod-conmon", ctr.ID())
realCgroupParent := cgroupParent
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index 0ae180356..f2ddba9c9 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/events"
"github.com/containers/podman/v2/pkg/cgroups"
+ "github.com/containers/podman/v2/pkg/parallel"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -99,47 +100,52 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m
return nil, err
}
- ctrErrors := make(map[string]error)
-
// TODO: There may be cases where it makes sense to order stops based on
// dependencies. Should we bother with this?
- // Stop to all containers
- for _, ctr := range allCtrs {
- ctr.lock.Lock()
+ ctrErrChan := make(map[string]<-chan error)
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
-
- // Ignore containers that are not running
- if ctr.state.State != define.ContainerStateRunning {
- ctr.lock.Unlock()
- continue
- }
- stopTimeout := ctr.config.StopTimeout
- if timeout > -1 {
- stopTimeout = uint(timeout)
- }
- if err := ctr.stop(stopTimeout); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ // Enqueue a function for each container with the parallel executor.
+ for _, ctr := range allCtrs {
+ c := ctr
+ logrus.Debugf("Adding parallel job to stop container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, func() error {
+ // TODO: Might be better to batch stop and cleanup
+ // together?
+ if timeout > -1 {
+ if err := c.StopWithTimeout(uint(timeout)); err != nil {
+ return err
+ }
+ } else {
+ if err := c.Stop(); err != nil {
+ return err
+ }
+ }
- if cleanup {
- if err := ctr.cleanup(ctx); err != nil {
- ctrErrors[ctr.ID()] = err
+ if cleanup {
+ return c.Cleanup(ctx)
}
- }
- ctr.lock.Unlock()
+ return nil
+ })
+
+ ctrErrChan[c.ID()] = retChan
}
p.newPodEvent(events.Stop)
+ ctrErrors := make(map[string]error)
+
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
+ }
+ }
+
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error stopping some containers")
}
@@ -169,45 +175,29 @@ func (p *Pod) Cleanup(ctx context.Context) (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Clean up all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
-
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
-
- // Ignore containers that are running/paused
- if !ctr.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateExited) {
- ctr.lock.Unlock()
- continue
- }
-
- // Check for running exec sessions, ignore containers with them.
- sessions, err := ctr.getActiveExecSessions()
- if err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
- if len(sessions) > 0 {
- ctr.lock.Unlock()
- continue
- }
+ c := ctr
+ logrus.Debugf("Adding parallel job to clean up container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, func() error {
+ return c.Cleanup(ctx)
+ })
- // TODO: Should we handle restart policy here?
+ ctrErrChan[c.ID()] = retChan
+ }
- ctr.newContainerEvent(events.Cleanup)
+ ctrErrors := make(map[string]error)
- if err := ctr.cleanup(ctx); err != nil {
- ctrErrors[ctr.ID()] = err
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
}
-
- ctr.lock.Unlock()
}
if len(ctrErrors) > 0 {
@@ -229,7 +219,7 @@ func (p *Pod) Cleanup(ctx context.Context) (map[string]error, error) {
// containers. The container ID is mapped to the error encountered. The error is
// set to ErrPodPartialFail.
// If both error and the map are nil, all containers were paused without error
-func (p *Pod) Pause() (map[string]error, error) {
+func (p *Pod) Pause(ctx context.Context) (map[string]error, error) {
p.lock.Lock()
defer p.lock.Unlock()
@@ -252,37 +242,34 @@ func (p *Pod) Pause() (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Pause to all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
+ c := ctr
+ logrus.Debugf("Adding parallel job to pause container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, c.Pause)
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrChan[c.ID()] = retChan
+ }
- // Ignore containers that are not running
- if ctr.state.State != define.ContainerStateRunning {
- ctr.lock.Unlock()
- continue
- }
+ p.newPodEvent(events.Pause)
- if err := ctr.pause(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrors := make(map[string]error)
- ctr.lock.Unlock()
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
+ }
}
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error pausing some containers")
}
- defer p.newPodEvent(events.Pause)
return nil, nil
}
@@ -298,7 +285,7 @@ func (p *Pod) Pause() (map[string]error, error) {
// containers. The container ID is mapped to the error encountered. The error is
// set to ErrPodPartialFail.
// If both error and the map are nil, all containers were unpaused without error.
-func (p *Pod) Unpause() (map[string]error, error) {
+func (p *Pod) Unpause(ctx context.Context) (map[string]error, error) {
p.lock.Lock()
defer p.lock.Unlock()
@@ -311,38 +298,34 @@ func (p *Pod) Unpause() (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Pause to all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
+ c := ctr
+ logrus.Debugf("Adding parallel job to unpause container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, c.Unpause)
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrChan[c.ID()] = retChan
+ }
- // Ignore containers that are not paused
- if ctr.state.State != define.ContainerStatePaused {
- ctr.lock.Unlock()
- continue
- }
+ p.newPodEvent(events.Unpause)
- if err := ctr.unpause(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ ctrErrors := make(map[string]error)
- ctr.lock.Unlock()
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
+ }
}
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error unpausing some containers")
}
-
- defer p.newPodEvent(events.Unpause)
return nil, nil
}
@@ -411,7 +394,7 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) {
// containers. The container ID is mapped to the error encountered. The error is
// set to ErrPodPartialFail.
// If both error and the map are nil, all containers were signalled successfully.
-func (p *Pod) Kill(signal uint) (map[string]error, error) {
+func (p *Pod) Kill(ctx context.Context, signal uint) (map[string]error, error) {
p.lock.Lock()
defer p.lock.Unlock()
@@ -424,44 +407,36 @@ func (p *Pod) Kill(signal uint) (map[string]error, error) {
return nil, err
}
- ctrErrors := make(map[string]error)
+ ctrErrChan := make(map[string]<-chan error)
- // Send a signal to all containers
+ // Enqueue a function for each container with the parallel executor.
for _, ctr := range allCtrs {
- ctr.lock.Lock()
-
- if err := ctr.syncContainer(); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ c := ctr
+ logrus.Debugf("Adding parallel job to kill container %s", c.ID())
+ retChan := parallel.Enqueue(ctx, func() error {
+ return c.Kill(signal)
+ })
- // Ignore containers that are not running
- if ctr.state.State != define.ContainerStateRunning {
- ctr.lock.Unlock()
- continue
- }
+ ctrErrChan[c.ID()] = retChan
+ }
- if err := ctr.ociRuntime.KillContainer(ctr, signal, false); err != nil {
- ctr.lock.Unlock()
- ctrErrors[ctr.ID()] = err
- continue
- }
+ p.newPodEvent(events.Kill)
- logrus.Debugf("Killed container %s with signal %d", ctr.ID(), signal)
+ ctrErrors := make(map[string]error)
- ctr.state.StoppedByUser = true
- if err := ctr.save(); err != nil {
- ctrErrors[ctr.ID()] = err
+ // Get returned error for every container we worked on
+ for id, channel := range ctrErrChan {
+ if err := <-channel; err != nil {
+ if errors.Cause(err) == define.ErrCtrStateInvalid || errors.Cause(err) == define.ErrCtrStopped {
+ continue
+ }
+ ctrErrors[id] = err
}
-
- ctr.lock.Unlock()
}
if len(ctrErrors) > 0 {
return ctrErrors, errors.Wrapf(define.ErrPodPartialFail, "error killing some containers")
}
- defer p.newPodEvent(events.Kill)
return nil, nil
}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index abb97293f..51b4c5f03 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -208,6 +208,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
// Check CGroup parent sanity, and set it if it was not set.
// Only if we're actually configuring CGroups.
if !ctr.config.NoCgroups {
+ ctr.config.CgroupManager = r.config.Engine.CgroupManager
switch r.config.Engine.CgroupManager {
case config.CgroupfsCgroupsManager:
if ctr.config.CgroupParent == "" {
diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json
index cd885fce2..31795516c 100644
--- a/nix/nixpkgs.json
+++ b/nix/nixpkgs.json
@@ -1,7 +1,7 @@
{
"url": "https://github.com/nixos/nixpkgs",
- "rev": "d5a689edda8219a1e20fd3871174b994cf0a94a3",
- "date": "2020-09-13T01:58:20+02:00",
- "sha256": "0m6nmi1fx0glfbg52kqdjgidxylk4p5xnx9v35wlsfi1j2xhkia4",
+ "rev": "c095d986c73b4e3d82af299b4175b9b475ebbf3a",
+ "date": "2020-10-07T23:58:44-03:00",
+ "sha256": "0ygv3wq26mxvy6kahs95ivl6n80bac3pbh6xmgw9ijcnnr03lm01",
"fetchSubmodules": false
}
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index a24dbaa47..4ce31cc83 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -14,8 +14,10 @@ import (
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/namespaces"
+ "github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/signal"
createconfig "github.com/containers/podman/v2/pkg/spec"
+ "github.com/containers/podman/v2/pkg/specgen"
"github.com/containers/storage"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@@ -134,6 +136,11 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input
Sysctl: input.HostConfig.Sysctls,
}
+ var netmode namespaces.NetworkMode
+ if rootless.IsRootless() {
+ netmode = namespaces.NetworkMode(specgen.Slirp)
+ }
+
network := createconfig.NetworkConfig{
DNSOpt: input.HostConfig.DNSOptions,
DNSSearch: input.HostConfig.DNSSearch,
@@ -144,7 +151,7 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input
IPAddress: "",
LinkLocalIP: nil, // docker-only
MacAddress: input.MacAddress,
- // NetMode: nil,
+ NetMode: netmode,
Network: input.HostConfig.NetworkMode.NetworkName(),
NetworkAlias: nil, // docker-only now
PortBindings: input.HostConfig.PortBindings,
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 9efdd1261..a729b84d4 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -139,7 +139,8 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
+ case <-r.Context().Done():
+ return
}
-
}
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index 9d8bc497a..f49ce59da 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -55,6 +55,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
return
}
+ defer os.Remove(tmpfile.Name())
if err := tmpfile.Close(); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
return
@@ -69,7 +70,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
return
}
defer rdr.Close()
- defer os.Remove(tmpfile.Name())
utils.WriteResponse(w, http.StatusOK, rdr)
}
@@ -398,3 +398,43 @@ func LoadImages(w http.ResponseWriter, r *http.Request) {
Stream: fmt.Sprintf("Loaded image: %s\n", id),
})
}
+
+func ExportImages(w http.ResponseWriter, r *http.Request) {
+ // 200 OK
+ // 500 Error
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Names string `schema:"names"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ images := make([]string, 0)
+ images = append(images, strings.Split(query.Names, ",")...)
+ tmpfile, err := ioutil.TempFile("", "api.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
+ return
+ }
+ if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ rdr, err := os.Open(tmpfile.Name())
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
+ return
+ }
+ defer rdr.Close()
+ utils.WriteResponse(w, http.StatusOK, rdr)
+}
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index c5387b1e9..a46784a6c 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -12,10 +12,10 @@ import (
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
- "github.com/containers/podman/v2/pkg/network"
"github.com/docker/docker/api/types"
dockerNetwork "github.com/docker/docker/api/types/network"
"github.com/gorilla/schema"
@@ -210,6 +210,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
report, err := getNetworkResourceByName(name, runtime)
if err != nil {
utils.InternalServerError(w, err)
+ return
}
reports = append(reports, report)
}
@@ -267,9 +268,9 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
}
}
ce := abi.ContainerEngine{Libpod: runtime}
- _, err := ce.NetworkCreate(r.Context(), name, ncOptions)
- if err != nil {
+ if _, err := ce.NetworkCreate(r.Context(), name, ncOptions); err != nil {
utils.InternalServerError(w, err)
+ return
}
report := types.NetworkCreate{
CheckDuplicate: networkCreate.CheckDuplicate,
@@ -307,6 +308,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
}
if err := network.RemoveNetwork(config, name); err != nil {
utils.InternalServerError(w, err)
+ return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 3aa554171..5422411cf 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -270,7 +270,7 @@ func PodPause(w http.ResponseWriter, r *http.Request) {
utils.PodNotFound(w, name, err)
return
}
- responses, err := pod.Pause()
+ responses, err := pod.Pause(r.Context())
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
@@ -294,7 +294,7 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) {
utils.PodNotFound(w, name, err)
return
}
- responses, err := pod.Unpause()
+ responses, err := pod.Unpause(r.Context())
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err)
return
@@ -402,7 +402,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
return
}
- responses, err := pod.Kill(uint(sig))
+ responses, err := pod.Kill(r.Context(), uint(sig))
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err)
return
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index cb0d26d1e..ad779203d 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -275,6 +275,31 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
r.Handle(VersionedPath("/images/{name:.*}/get"), s.APIHandler(compat.ExportImage)).Methods(http.MethodGet)
// Added non version path to URI to support docker non versioned paths
r.Handle("/images/{name:.*}/get", s.APIHandler(compat.ExportImage)).Methods(http.MethodGet)
+ // swagger:operation GET /images/get compat get
+ // ---
+ // tags:
+ // - images (compat)
+ // summary: Export several images
+ // description: Get a tarball containing all images and metadata for several image repositories
+ // parameters:
+ // - in: query
+ // name: names
+ // type: string
+ // required: true
+ // description: one or more image names or IDs comma separated
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // description: no error
+ // schema:
+ // type: string
+ // format: binary
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/get"), s.APIHandler(compat.ExportImages)).Methods(http.MethodGet)
+ // Added non version path to URI to support docker non versioned paths
+ r.Handle("/images/get", s.APIHandler(compat.ExportImages)).Methods(http.MethodGet)
// swagger:operation GET /images/{name:.*}/history compat imageHistory
// ---
// tags:
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 7b272f01e..3b6dd106f 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -131,7 +131,6 @@ type RmOptions struct {
Force bool
Ignore bool
Latest bool
- Storage bool
Volumes bool
}
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index 2ba369b83..356e6869d 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -24,6 +24,8 @@ type PlayKubeOptions struct {
// SeccompProfileRoot - path to a directory containing seccomp
// profiles.
SeccompProfileRoot string
+ // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs.
+ ConfigMaps []string
}
// PlayKubePod represents a single pod and associated containers created by play kube
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index d92911e0c..ac7523094 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -23,7 +23,7 @@ import (
"github.com/containers/podman/v2/pkg/checkpoint"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi/terminal"
- "github.com/containers/podman/v2/pkg/parallel"
+ parallelctr "github.com/containers/podman/v2/pkg/parallel/ctr"
"github.com/containers/podman/v2/pkg/ps"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/signal"
@@ -157,7 +157,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
return nil, err
}
- errMap, err := parallel.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
+ errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
var err error
if options.Timeout != nil {
err = c.StopWithTimeout(*options.Timeout)
@@ -273,16 +273,6 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st
func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, options entities.RmOptions) ([]*entities.RmReport, error) {
reports := []*entities.RmReport{}
- if options.Storage {
- for _, ctr := range namesOrIds {
- report := entities.RmReport{Id: ctr}
- if err := ic.Libpod.RemoveStorageContainer(ctr, options.Force); err != nil {
- report.Err = err
- }
- reports = append(reports, &report)
- }
- return reports, nil
- }
names := namesOrIds
for _, cidFile := range options.CIDFiles {
@@ -294,6 +284,22 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
names = append(names, id)
}
+ // Attempt to remove named containers directly from storage, if container is defined in libpod
+ // this will fail and code will fall through to removing the container from libpod.`
+ tmpNames := []string{}
+ for _, ctr := range names {
+ report := entities.RmReport{Id: ctr}
+ if err := ic.Libpod.RemoveStorageContainer(ctr, options.Force); err != nil {
+ // remove container names that we successfully deleted
+ tmpNames = append(tmpNames, ctr)
+ } else {
+ reports = append(reports, &report)
+ }
+ }
+ if len(tmpNames) < len(names) {
+ names = tmpNames
+ }
+
ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod)
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
// Failed to get containers. If force is specified, get the containers ID
@@ -302,7 +308,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
return nil, err
}
- for _, ctr := range namesOrIds {
+ for _, ctr := range names {
logrus.Debugf("Evicting container %q", ctr)
report := entities.RmReport{Id: ctr}
id, err := ic.Libpod.EvictContainer(ctx, ctr, options.Volumes)
@@ -321,7 +327,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
return reports, nil
}
- errMap, err := parallel.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
+ errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
err := ic.Libpod.RemoveContainer(ctx, c, options.Force, options.Volumes)
if err != nil {
if options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go
index 5acfea853..f40df828a 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -2,19 +2,13 @@ package abi
import (
"context"
- "encoding/json"
"fmt"
- "io/ioutil"
- "os"
- "path/filepath"
"strings"
"github.com/containernetworking/cni/libcni"
- cniversion "github.com/containernetworking/cni/pkg/version"
- "github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/network"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
)
@@ -111,173 +105,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
}
func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) {
- var (
- err error
- fileName string
- )
- if len(options.MacVLAN) > 0 {
- fileName, err = createMacVLAN(ic.Libpod, name, options)
- } else {
- fileName, err = createBridge(ic.Libpod, name, options)
- }
- if err != nil {
- return nil, err
- }
- return &entities.NetworkCreateReport{Filename: fileName}, nil
-}
-
-// createBridge creates a CNI network
-func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
- isGateway := true
- ipMasq := true
- subnet := &options.Subnet
- ipRange := options.Range
- runtimeConfig, err := r.GetConfig()
- if err != nil {
- return "", err
- }
- // if range is provided, make sure it is "in" network
- if subnet.IP != nil {
- // if network is provided, does it conflict with existing CNI or live networks
- err = network.ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
- } else {
- // if no network is provided, figure out network
- subnet, err = network.GetFreeNetwork(runtimeConfig)
- }
- if err != nil {
- return "", err
- }
- gateway := options.Gateway
- if gateway == nil {
- // if no gateway is provided, provide it as first ip of network
- gateway = network.CalcGatewayIP(subnet)
- }
- // if network is provided and if gateway is provided, make sure it is "in" network
- if options.Subnet.IP != nil && options.Gateway != nil {
- if !subnet.Contains(gateway) {
- return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
- }
- }
- if options.Internal {
- isGateway = false
- ipMasq = false
- }
-
- // if a range is given, we need to ensure it is "in" the network range.
- if options.Range.IP != nil {
- if options.Subnet.IP == nil {
- return "", errors.New("you must define a subnet range to define an ip-range")
- }
- firstIP, err := network.FirstIPInSubnet(&options.Range)
- if err != nil {
- return "", err
- }
- lastIP, err := network.LastIPInSubnet(&options.Range)
- if err != nil {
- return "", err
- }
- if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
- return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String())
- }
- }
- bridgeDeviceName, err := network.GetFreeDeviceName(runtimeConfig)
- if err != nil {
- return "", err
- }
-
- if len(name) > 0 {
- netNames, err := network.GetNetworkNamesFromFileSystem(runtimeConfig)
- if err != nil {
- return "", err
- }
- if util.StringInSlice(name, netNames) {
- return "", errors.Errorf("the network name %s is already used", name)
- }
- } else {
- // If no name is given, we give the name of the bridge device
- name = bridgeDeviceName
- }
-
- ncList := network.NewNcList(name, cniversion.Current())
- var plugins []network.CNIPlugins
- var routes []network.IPAMRoute
-
- defaultRoute, err := network.NewIPAMDefaultRoute(network.IsIPv6(subnet.IP))
- if err != nil {
- return "", err
- }
- routes = append(routes, defaultRoute)
- ipamConfig, err := network.NewIPAMHostLocalConf(subnet, routes, ipRange, gateway)
- if err != nil {
- return "", err
- }
-
- // TODO need to iron out the role of isDefaultGW and IPMasq
- bridge := network.NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
- plugins = append(plugins, bridge)
- plugins = append(plugins, network.NewPortMapPlugin())
- plugins = append(plugins, network.NewFirewallPlugin())
- // if we find the dnsname plugin, we add configuration for it
- if network.HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS {
- // Note: in the future we might like to allow for dynamic domain names
- plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName))
- }
- ncList["plugins"] = plugins
- b, err := json.MarshalIndent(ncList, "", " ")
- if err != nil {
- return "", err
- }
- if err := os.MkdirAll(network.GetCNIConfDir(runtimeConfig), 0755); err != nil {
- return "", err
- }
- cniPathName := filepath.Join(network.GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
- err = ioutil.WriteFile(cniPathName, b, 0644)
- return cniPathName, err
-}
-
-func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
- var (
- plugins []network.CNIPlugins
- )
- liveNetNames, err := network.GetLiveNetworkNames()
- if err != nil {
- return "", err
- }
-
- config, err := r.GetConfig()
- if err != nil {
- return "", err
- }
-
- // Make sure the host-device exists
- if !util.StringInSlice(options.MacVLAN, liveNetNames) {
- return "", errors.Errorf("failed to find network interface %q", options.MacVLAN)
- }
- if len(name) > 0 {
- netNames, err := network.GetNetworkNamesFromFileSystem(config)
- if err != nil {
- return "", err
- }
- if util.StringInSlice(name, netNames) {
- return "", errors.Errorf("the network name %s is already used", name)
- }
- } else {
- name, err = network.GetFreeDeviceName(config)
- if err != nil {
- return "", err
- }
- }
- ncList := network.NewNcList(name, cniversion.Current())
- macvlan := network.NewMacVLANPlugin(options.MacVLAN)
- plugins = append(plugins, macvlan)
- ncList["plugins"] = plugins
- b, err := json.MarshalIndent(ncList, "", " ")
- if err != nil {
- return "", err
- }
- cniPathName := filepath.Join(network.GetCNIConfDir(config), fmt.Sprintf("%s.conflist", name))
- err = ioutil.WriteFile(cniPathName, b, 0644)
- return cniPathName, err
+ return network.Create(name, options, ic.Libpod)
}
func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool {
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 40edc1ae3..2de98d8f5 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -311,6 +311,22 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
ctrRestartPolicy = libpod.RestartPolicyAlways
}
+ configMaps := []v1.ConfigMap{}
+ for _, p := range options.ConfigMaps {
+ f, err := os.Open(p)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ cm, err := readConfigMapFromFile(f)
+ if err != nil {
+ return nil, errors.Wrapf(err, "%q", p)
+ }
+
+ configMaps = append(configMaps, cm)
+ }
+
containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers))
for _, container := range podYAML.Spec.Containers {
pullPolicy := util.PullImageMissing
@@ -334,7 +350,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil {
return nil, err
}
- conf, err := kubeContainerToCreateConfig(ctx, container, newImage, namespaces, volumes, pod.ID(), podName, podInfraID, seccompPaths)
+ conf, err := kubeContainerToCreateConfig(ctx, container, newImage, namespaces, volumes, pod.ID(), podName, podInfraID, configMaps, seccompPaths)
if err != nil {
return nil, err
}
@@ -447,7 +463,7 @@ func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfi
}
// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
-func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, podName, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
+func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
var (
containerConfig createconfig.CreateConfig
pidConfig createconfig.PidConfig
@@ -572,8 +588,17 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
}
envs = imageEnv
}
- for _, e := range containerYAML.Env {
- envs[e.Name] = e.Value
+ for _, env := range containerYAML.Env {
+ value := envVarValue(env, configMaps)
+
+ envs[env.Name] = value
+ }
+ for _, envFrom := range containerYAML.EnvFrom {
+ cmEnvs := envVarsFromConfigMap(envFrom, configMaps)
+
+ for k, v := range cmEnvs {
+ envs[k] = v
+ }
}
containerConfig.Env = envs
@@ -594,6 +619,62 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
return &containerConfig, nil
}
+// readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag
+func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {
+ var cm v1.ConfigMap
+
+ content, err := ioutil.ReadAll(r)
+ if err != nil {
+ return cm, errors.Wrapf(err, "unable to read ConfigMap YAML content")
+ }
+
+ if err := yaml.Unmarshal(content, &cm); err != nil {
+ return cm, errors.Wrapf(err, "unable to read YAML as Kube ConfigMap")
+ }
+
+ if cm.Kind != "ConfigMap" {
+ return cm, errors.Errorf("invalid YAML kind: %q. [ConfigMap] is the only supported by --configmap", cm.Kind)
+ }
+
+ return cm, nil
+}
+
+// envVarsFromConfigMap returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container
+func envVarsFromConfigMap(envFrom v1.EnvFromSource, configMaps []v1.ConfigMap) map[string]string {
+ envs := map[string]string{}
+
+ if envFrom.ConfigMapRef != nil {
+ cmName := envFrom.ConfigMapRef.Name
+
+ for _, c := range configMaps {
+ if cmName == c.Name {
+ envs = c.Data
+ break
+ }
+ }
+ }
+
+ return envs
+}
+
+// envVarValue returns the environment variable value configured within the container's env setting.
+// It gets the value from a configMap if specified, otherwise returns env.Value
+func envVarValue(env v1.EnvVar, configMaps []v1.ConfigMap) string {
+ for _, c := range configMaps {
+ if env.ValueFrom != nil {
+ if env.ValueFrom.ConfigMapKeyRef != nil {
+ if env.ValueFrom.ConfigMapKeyRef.Name == c.Name {
+ if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok {
+ return value
+ }
+ }
+ }
+ }
+ }
+
+ return env.Value
+}
+
// kubeSeccompPaths holds information about a pod YAML's seccomp configuration
// it holds both container and pod seccomp paths
type kubeSeccompPaths struct {
diff --git a/pkg/domain/infra/abi/play_test.go b/pkg/domain/infra/abi/play_test.go
new file mode 100644
index 000000000..5595476c3
--- /dev/null
+++ b/pkg/domain/infra/abi/play_test.go
@@ -0,0 +1,254 @@
+package abi
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var configMapList = []v1.ConfigMap{
+ {
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "bar",
+ },
+ Data: map[string]string{
+ "myvar": "bar",
+ },
+ },
+ {
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ },
+ Data: map[string]string{
+ "myvar": "foo",
+ },
+ },
+}
+
+func TestReadConfigMapFromFile(t *testing.T) {
+ tests := []struct {
+ name string
+ configMapContent string
+ expectError bool
+ expectedErrorMsg string
+ expected v1.ConfigMap
+ }{
+ {
+ "ValidConfigMap",
+ `
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: foo
+data:
+ myvar: foo
+`,
+ false,
+ "",
+ v1.ConfigMap{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ },
+ Data: map[string]string{
+ "myvar": "foo",
+ },
+ },
+ },
+ {
+ "InvalidYAML",
+ `
+Invalid YAML
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: foo
+data:
+ myvar: foo
+`,
+ true,
+ "unable to read YAML as Kube ConfigMap",
+ v1.ConfigMap{},
+ },
+ {
+ "InvalidKind",
+ `
+apiVersion: v1
+kind: InvalidKind
+metadata:
+ name: foo
+data:
+ myvar: foo
+`,
+ true,
+ "invalid YAML kind",
+ v1.ConfigMap{},
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ buf := bytes.NewBufferString(test.configMapContent)
+ cm, err := readConfigMapFromFile(buf)
+
+ if test.expectError {
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), test.expectedErrorMsg)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, test.expected, cm)
+ }
+ })
+ }
+}
+
+func TestEnvVarsFromConfigMap(t *testing.T) {
+ tests := []struct {
+ name string
+ envFrom v1.EnvFromSource
+ configMapList []v1.ConfigMap
+ expected map[string]string
+ }{
+ {
+ "ConfigMapExists",
+ v1.EnvFromSource{
+ ConfigMapRef: &v1.ConfigMapEnvSource{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ },
+ },
+ configMapList,
+ map[string]string{
+ "myvar": "foo",
+ },
+ },
+ {
+ "ConfigMapDoesNotExist",
+ v1.EnvFromSource{
+ ConfigMapRef: &v1.ConfigMapEnvSource{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "doesnotexist",
+ },
+ },
+ },
+ configMapList,
+ map[string]string{},
+ },
+ {
+ "EmptyConfigMapList",
+ v1.EnvFromSource{
+ ConfigMapRef: &v1.ConfigMapEnvSource{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ },
+ },
+ []v1.ConfigMap{},
+ map[string]string{},
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ result := envVarsFromConfigMap(test.envFrom, test.configMapList)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+func TestEnvVarValue(t *testing.T) {
+ tests := []struct {
+ name string
+ envVar v1.EnvVar
+ configMapList []v1.ConfigMap
+ expected string
+ }{
+ {
+ "ConfigMapExists",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ Key: "myvar",
+ },
+ },
+ },
+ configMapList,
+ "foo",
+ },
+ {
+ "ContainerKeyDoesNotExistInConfigMap",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ Key: "doesnotexist",
+ },
+ },
+ },
+ configMapList,
+ "",
+ },
+ {
+ "ConfigMapDoesNotExist",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "doesnotexist",
+ },
+ Key: "myvar",
+ },
+ },
+ },
+ configMapList,
+ "",
+ },
+ {
+ "EmptyConfigMapList",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ Key: "myvar",
+ },
+ },
+ },
+ []v1.ConfigMap{},
+ "",
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ result := envVarValue(test.envVar, test.configMapList)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 747da9fd4..258640a81 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -66,7 +66,7 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt
for _, p := range pods {
report := entities.PodKillReport{Id: p.ID()}
- conErrs, err := p.Kill(uint(sig))
+ conErrs, err := p.Kill(ctx, uint(sig))
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
report.Errs = []error{err}
reports = append(reports, &report)
@@ -92,7 +92,7 @@ func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, op
}
for _, p := range pods {
report := entities.PodPauseReport{Id: p.ID()}
- errs, err := p.Pause()
+ errs, err := p.Pause(ctx)
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
report.Errs = []error{err}
continue
@@ -117,7 +117,7 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string,
}
for _, p := range pods {
report := entities.PodUnpauseReport{Id: p.ID()}
- errs, err := p.Unpause()
+ errs, err := p.Unpause(ctx)
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
report.Errs = []error{err}
continue
diff --git a/pkg/parallel/parallel_linux.go b/pkg/parallel/ctr/ctr.go
index 442db1502..e8c1292b8 100644
--- a/pkg/parallel/parallel_linux.go
+++ b/pkg/parallel/ctr/ctr.go
@@ -1,11 +1,10 @@
-package parallel
+package ctr
import (
"context"
- "sync"
"github.com/containers/podman/v2/libpod"
- "github.com/pkg/errors"
+ "github.com/containers/podman/v2/pkg/parallel"
"github.com/sirupsen/logrus"
)
@@ -14,44 +13,28 @@ import (
// If no error is returned, each container specified in ctrs will have an entry
// in the resulting map; containers with no error will be set to nil.
func ContainerOp(ctx context.Context, ctrs []*libpod.Container, applyFunc func(*libpod.Container) error) (map[*libpod.Container]error, error) {
- jobControlLock.RLock()
- defer jobControlLock.RUnlock()
-
// We could use a sync.Map but given Go's lack of generic I'd rather
// just use a lock on a normal map...
// The expectation is that most of the time is spent in applyFunc
// anyways.
var (
- errMap = make(map[*libpod.Container]error)
- errLock sync.Mutex
- allDone sync.WaitGroup
+ errMap = make(map[*libpod.Container]<-chan error)
)
for _, ctr := range ctrs {
- // Block until a thread is available
- if err := jobControl.Acquire(ctx, 1); err != nil {
- return nil, errors.Wrapf(err, "error acquiring job control semaphore")
- }
-
- allDone.Add(1)
-
c := ctr
- go func() {
- logrus.Debugf("Launching job on container %s", c.ID())
-
- err := applyFunc(c)
- errLock.Lock()
- errMap[c] = err
- errLock.Unlock()
-
- allDone.Done()
- jobControl.Release(1)
- }()
+ logrus.Debugf("Starting parallel job on container %s", c.ID())
+ errChan := parallel.Enqueue(ctx, func() error {
+ return applyFunc(c)
+ })
+ errMap[c] = errChan
}
- allDone.Wait()
+ finalErr := make(map[*libpod.Container]error)
+ for ctr, errChan := range errMap {
+ err := <-errChan
+ finalErr[ctr] = err
+ }
- return errMap, nil
+ return finalErr, nil
}
-
-// TODO: Add an Enqueue() function that returns a promise
diff --git a/pkg/parallel/parallel.go b/pkg/parallel/parallel.go
index c9e4da50d..4da7e0f89 100644
--- a/pkg/parallel/parallel.go
+++ b/pkg/parallel/parallel.go
@@ -1,6 +1,7 @@
package parallel
import (
+ "context"
"sync"
"github.com/pkg/errors"
@@ -42,3 +43,32 @@ func SetMaxThreads(threads uint) error {
func GetMaxThreads() uint {
return numThreads
}
+
+// Enqueue adds a single function to the parallel jobs queue. This function will
+// be run when an unused thread is available.
+// Returns a receive-only error channel that will return the error (if any) from
+// the provided function fn when fn has finished executing. The channel will be
+// closed after this.
+func Enqueue(ctx context.Context, fn func() error) <-chan error {
+ retChan := make(chan error)
+
+ go func() {
+ jobControlLock.RLock()
+ defer jobControlLock.RUnlock()
+
+ defer close(retChan)
+
+ if err := jobControl.Acquire(ctx, 1); err != nil {
+ retChan <- errors.Wrapf(err, "error acquiring job control semaphore")
+ return
+ }
+
+ err := fn()
+
+ jobControl.Release(1)
+
+ retChan <- err
+ }()
+
+ return retChan
+}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 189434780..6d03afb7a 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -3,6 +3,7 @@
package varlinkapi
import (
+ "context"
"encoding/json"
"fmt"
"strconv"
@@ -207,7 +208,7 @@ func (i *VarlinkAPI) KillPod(call iopodman.VarlinkCall, name string, signal int6
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- ctrErrs, err := pod.Kill(killSignal)
+ ctrErrs, err := pod.Kill(context.TODO(), killSignal)
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err
@@ -221,7 +222,7 @@ func (i *VarlinkAPI) PausePod(call iopodman.VarlinkCall, name string) error {
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- ctrErrs, err := pod.Pause()
+ ctrErrs, err := pod.Pause(context.TODO())
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err
@@ -235,7 +236,7 @@ func (i *VarlinkAPI) UnpausePod(call iopodman.VarlinkCall, name string) error {
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- ctrErrs, err := pod.Unpause()
+ ctrErrs, err := pod.Unpause(context.TODO())
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at
index bdc298ae3..f669bc892 100644
--- a/test/apiv2/10-images.at
+++ b/test/apiv2/10-images.at
@@ -68,4 +68,7 @@ for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME; do
t GET "libpod/images/$i/get?compress=false" 200 '[POSIX tar archive]'
done
+# Export more than one image
+t GET images/get?names=alpine,busybox 200 '[POSIX tar archive]'
+
# vim: filetype=sh
diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at
new file mode 100644
index 000000000..30ccf0cfc
--- /dev/null
+++ b/test/apiv2/12-imagesMore.at
@@ -0,0 +1,44 @@
+# -*- sh -*-
+#
+# Tests for more image-related endpoints
+#
+
+podman pull -q $IMAGE
+
+t GET libpod/images/json 200 \
+ .[0].Id~[0-9a-f]\\{64\\}
+iid=$(jq -r '.[0].Id' <<<"$output")
+
+# Retrieve the image tree
+t GET libpod/images/$IMAGE/tree 200 \
+ .Tree~^Image
+
+# Tag nonesuch image
+t POST "libpod/images/nonesuch/tag?repo=myrepo&tag=mytag" '' 404
+
+# Tag the image
+t POST "libpod/images/$IMAGE/tag?repo=localhost:5000/myrepo&tag=mytag" '' 201
+
+t GET libpod/images/$IMAGE/json 200 \
+ .RepoTags[1]=localhost:5000/myrepo:mytag
+
+# Run registry container
+podman run -d --name registry -p 5000:5000 docker.io/library/registry:2.6 /entrypoint.sh /etc/docker/registry/config.yml
+
+# Push to local registry
+t POST libpod/images/localhost:5000/myrepo:mytag/push\?tlsVerify\=false '' 200
+
+# Untag the image
+t POST "libpod/images/$iid/untag?repo=localhost:5000/myrepo&tag=mytag" '' 201
+
+t GET libpod/images/$IMAGE/json 200 \
+ .RepoTags[-1]=$IMAGE
+
+# Remove the registry container
+t DELETE libpod/containers/registry?force=true 204
+
+# Remove images
+t DELETE libpod/images/$IMAGE 200 \
+ .ExitCode=0
+t DELETE libpod/images/docker.io/library/registry:2.6 200 \
+ .ExitCode=0
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 28289955a..d7e5bfee8 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -206,7 +206,7 @@ t POST containers/${cid_top}/stop "" 204
t DELETE containers/$cid 204
t DELETE containers/$cid_top 204
-# test the apiv2 create, should't ignore the ENV and WORKDIR from the image
+# test the apiv2 create, shouldn't ignore the ENV and WORKDIR from the image
t POST containers/create '"Image":"'$ENV_WORKDIR_IMG'","Env":["testKey1"]' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go
index e3e1044aa..5155bcbc7 100644
--- a/test/e2e/build_test.go
+++ b/test/e2e/build_test.go
@@ -89,7 +89,6 @@ var _ = Describe("Podman build", func() {
// Check that builds with different values for the squash options
// create the appropriate number of layers, then clean up after.
It("podman build basic alpine with squash", func() {
- SkipIfRemote("FIXME: This is broken should be fixed")
session := podmanTest.PodmanNoCache([]string{"build", "-f", "build/squash/Dockerfile.squash-a", "-t", "test-squash-a:latest", "build/squash"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -221,8 +220,12 @@ var _ = Describe("Podman build", func() {
})
It("podman build --http_proxy flag", func() {
- SkipIfRemote("FIXME: This is broken should be fixed")
+ SkipIfRemote("FIXME: This is broken should be fixed") // This is hanging currently.
os.Setenv("http_proxy", "1.2.3.4")
+ if IsRemote() {
+ podmanTest.StopRemoteService()
+ podmanTest.StartRemoteService()
+ }
podmanTest.RestoreAllArtifacts()
dockerfile := `FROM docker.io/library/alpine:latest
RUN printenv http_proxy`
@@ -230,7 +233,7 @@ RUN printenv http_proxy`
dockerfilePath := filepath.Join(podmanTest.TempDir, "Dockerfile")
err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755)
Expect(err).To(BeNil())
- session := podmanTest.PodmanNoCache([]string{"build", "--file", dockerfilePath, podmanTest.TempDir})
+ session := podmanTest.PodmanNoCache([]string{"build", "--http-proxy", "--file", dockerfilePath, podmanTest.TempDir})
session.Wait(120)
Expect(session.ExitCode()).To(Equal(0))
ok, _ := session.GrepString("1.2.3.4")
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index c663a4dca..ec910109b 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -453,7 +453,7 @@ func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegrat
func (p *PodmanTestIntegration) Cleanup() {
// Remove all containers
stopall := p.Podman([]string{"stop", "-a", "--time", "0"})
- stopall.Wait(90)
+ stopall.WaitWithDefaultTimeout()
podstop := p.Podman([]string{"pod", "stop", "-a", "-t", "0"})
podstop.WaitWithDefaultTimeout()
@@ -461,7 +461,7 @@ func (p *PodmanTestIntegration) Cleanup() {
podrm.WaitWithDefaultTimeout()
session := p.Podman([]string{"rm", "-fa"})
- session.Wait(90)
+ session.WaitWithDefaultTimeout()
p.StopRemoteService()
// Nuke tempdir
diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go
index 93a713f28..a698cd4b3 100644
--- a/test/e2e/exec_test.go
+++ b/test/e2e/exec_test.go
@@ -284,8 +284,6 @@ var _ = Describe("Podman exec", func() {
})
It("podman exec preserves container groups with --user and --group-add", func() {
- SkipIfRemote("FIXME: This is broken SECCOMP Failues?")
-
dockerfile := `FROM registry.fedoraproject.org/fedora-minimal
RUN groupadd -g 4000 first
RUN groupadd -g 4001 second
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index 05a7f4ddf..3c4a1008b 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -260,6 +260,38 @@ var _ = Describe("Podman generate kube", func() {
}
})
+ It("podman generate kube on pod with cpu limit", func() {
+ podName := "testCpuLimit"
+ podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName})
+ podSession.WaitWithDefaultTimeout()
+ Expect(podSession.ExitCode()).To(Equal(0))
+
+ ctr1Name := "ctr1"
+ ctr1Session := podmanTest.Podman([]string{"create", "--name", ctr1Name, "--pod", podName,
+ "--cpus", "0.5", ALPINE, "top"})
+ ctr1Session.WaitWithDefaultTimeout()
+ Expect(ctr1Session.ExitCode()).To(Equal(0))
+
+ ctr2Name := "ctr2"
+ ctr2Session := podmanTest.Podman([]string{"create", "--name", ctr2Name, "--pod", podName,
+ "--cpu-period", "100000", "--cpu-quota", "50000", ALPINE, "top"})
+ ctr2Session.WaitWithDefaultTimeout()
+ Expect(ctr2Session.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", podName})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ pod := new(v1.Pod)
+ err := yaml.Unmarshal(kube.Out.Contents(), pod)
+ Expect(err).To(BeNil())
+
+ for _, ctr := range pod.Spec.Containers {
+ cpuLimit := ctr.Resources.Limits.Cpu().MilliValue()
+ Expect(cpuLimit).To(Equal(int64(500)))
+ }
+ })
+
It("podman generate kube on pod with ports", func() {
podName := "test"
podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName, "-p", "4000:4000", "-p", "5000:5000"})
diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go
index d9ad10fe9..9344132d9 100644
--- a/test/e2e/images_test.go
+++ b/test/e2e/images_test.go
@@ -176,7 +176,6 @@ var _ = Describe("Podman images", func() {
})
It("podman images filter before image", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
dockerfile := `FROM docker.io/library/alpine:latest
RUN apk update && apk add strace
`
@@ -340,7 +339,7 @@ WORKDIR /test
})
It("podman images --all flag", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("FIXME This should work on podman-remote, problem is with podman-remote build")
podmanTest.RestoreAllArtifacts()
dockerfile := `FROM docker.io/library/alpine:latest
RUN mkdir hello
@@ -372,7 +371,7 @@ LABEL "com.example.vendor"="Example Vendor"
})
It("podman with images with no layers", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("FIXME This should work on podman-remote, problem is with podman-remote build")
dockerfile := strings.Join([]string{
`FROM scratch`,
`LABEL org.opencontainers.image.authors="<somefolks@example.org>"`,
diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go
index ddffadac0..dd91381d9 100644
--- a/test/e2e/load_test.go
+++ b/test/e2e/load_test.go
@@ -243,7 +243,7 @@ var _ = Describe("Podman load", func() {
})
It("podman load localhost registry from dir", func() {
- SkipIfRemote("FIXME: podman-remote load is currently broken.")
+ SkipIfRemote("podman-remote does not support loading directories")
outfile := filepath.Join(podmanTest.TempDir, "load")
setup := podmanTest.PodmanNoCache([]string{"tag", BB, "hello:world"})
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index 9b3163856..664d4831e 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -127,7 +127,7 @@ var _ = Describe("Podman logs", func() {
})
It("two containers showing short container IDs", func() {
- SkipIfRemote("FIXME: remote does not support multiple containers")
+ SkipIfRemote("FIXME: podman-remote logs does not support showing two containers at the same time")
log1 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"})
log1.WaitWithDefaultTimeout()
Expect(log1.ExitCode()).To(Equal(0))
diff --git a/test/e2e/namespace_test.go b/test/e2e/namespace_test.go
index 92df3df48..951e98dfc 100644
--- a/test/e2e/namespace_test.go
+++ b/test/e2e/namespace_test.go
@@ -33,9 +33,14 @@ var _ = Describe("Podman namespaces", func() {
})
It("podman namespace test", func() {
- SkipIfRemote("FIXME This should work on Remote")
podman1 := podmanTest.Podman([]string{"--namespace", "test1", "run", "-d", ALPINE, "echo", "hello"})
podman1.WaitWithDefaultTimeout()
+ if IsRemote() {
+ // --namespace flag not supported in podman remote
+ Expect(podman1.ExitCode()).To(Equal(125))
+ Expect(podman1.ErrorToString()).To(ContainSubstring("unknown flag: --namespace"))
+ return
+ }
Expect(podman1.ExitCode()).To(Equal(0))
podman2 := podmanTest.Podman([]string{"--namespace", "test2", "ps", "-aq"})
diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go
index edd76739f..21f03901b 100644
--- a/test/e2e/network_create_test.go
+++ b/test/e2e/network_create_test.go
@@ -8,7 +8,7 @@ import (
"strings"
cniversion "github.com/containernetworking/cni/pkg/version"
- "github.com/containers/podman/v2/pkg/network"
+ "github.com/containers/podman/v2/libpod/network"
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -137,7 +137,6 @@ var _ = Describe("Podman network create", func() {
})
It("podman network create with name and subnet", func() {
- SkipIfRemote("FIXME, this should work on --remote")
var (
results []network.NcList
)
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index a15359ea3..cbfd72da6 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -28,7 +28,7 @@ func removeConf(confPath string) {
// generateNetworkConfig generates a cni config with a random name
// it returns the network name and the filepath
func generateNetworkConfig(p *PodmanTestIntegration) (string, string) {
- // generate a random name to preven conflicts with other tests
+ // generate a random name to prevent conflicts with other tests
name := "net" + stringid.GenerateNonCryptoID()
path := filepath.Join(p.CNIConfigDir, fmt.Sprintf("%s.conflist", name))
conf := fmt.Sprintf(`{
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index d771860d8..b6a390950 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -25,6 +25,19 @@ spec:
hostname: unknown
`
+var configMapYamlTemplate = `
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Name }}
+data:
+{{ with .Data }}
+ {{ range $key, $value := . }}
+ {{ $key }}: {{ $value }}
+ {{ end }}
+{{ end }}
+`
+
var podYamlTemplate = `
apiVersion: v1
kind: Pod
@@ -75,6 +88,26 @@ spec:
- name: HOSTNAME
- name: container
value: podman
+ {{ range .Env }}
+ - name: {{ .Name }}
+ {{ if (eq .ValueFrom "configmap") }}
+ valueFrom:
+ configMapKeyRef:
+ name: {{ .RefName }}
+ key: {{ .RefKey }}
+ {{ else }}
+ value: {{ .Value }}
+ {{ end }}
+ {{ end }}
+ {{ with .EnvFrom}}
+ envFrom:
+ {{ range . }}
+ {{ if (eq .From "configmap") }}
+ - configMapRef:
+ name: {{ .Name }}
+ {{ end }}
+ {{ end }}
+ {{ end }}
image: {{ .Image }}
name: {{ .Name }}
imagePullPolicy: {{ .PullPolicy }}
@@ -226,6 +259,7 @@ var (
defaultPodName = "testPod"
defaultVolName = "testVol"
defaultDeploymentName = "testDeployment"
+ defaultConfigMapName = "testConfigMap"
seccompPwdEPERM = []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`)
)
@@ -244,34 +278,64 @@ func writeYaml(content string, fileName string) error {
return nil
}
-func generatePodKubeYaml(pod *Pod, fileName string) error {
+func generateKubeYaml(kind string, object interface{}, pathname string) error {
+ var yamlTemplate string
templateBytes := &bytes.Buffer{}
- t, err := template.New("pod").Parse(podYamlTemplate)
+ switch kind {
+ case "configmap":
+ yamlTemplate = configMapYamlTemplate
+ case "pod":
+ yamlTemplate = podYamlTemplate
+ case "deployment":
+ yamlTemplate = deploymentYamlTemplate
+ default:
+ return fmt.Errorf("unsupported kubernetes kind")
+ }
+
+ t, err := template.New(kind).Parse(yamlTemplate)
if err != nil {
return err
}
- if err := t.Execute(templateBytes, pod); err != nil {
+ if err := t.Execute(templateBytes, object); err != nil {
return err
}
- return writeYaml(templateBytes.String(), fileName)
+ return writeYaml(templateBytes.String(), pathname)
}
-func generateDeploymentKubeYaml(deployment *Deployment, fileName string) error {
- templateBytes := &bytes.Buffer{}
+// ConfigMap describes the options a kube yaml can be configured at configmap level
+type ConfigMap struct {
+ Name string
+ Data map[string]string
+}
- t, err := template.New("deployment").Parse(deploymentYamlTemplate)
- if err != nil {
- return err
+func getConfigMap(options ...configMapOption) *ConfigMap {
+ cm := ConfigMap{
+ Name: defaultConfigMapName,
+ Data: map[string]string{},
}
- if err := t.Execute(templateBytes, deployment); err != nil {
- return err
+ for _, option := range options {
+ option(&cm)
}
- return writeYaml(templateBytes.String(), fileName)
+ return &cm
+}
+
+type configMapOption func(*ConfigMap)
+
+func withConfigMapName(name string) configMapOption {
+ return func(configmap *ConfigMap) {
+ configmap.Name = name
+ }
+}
+
+func withConfigMapData(k, v string) configMapOption {
+ return func(configmap *ConfigMap) {
+ configmap.Data[k] = v
+ }
}
// Pod describes the options a kube yaml can be configured at pod level
@@ -450,12 +514,14 @@ type Ctr struct {
VolumeMountPath string
VolumeName string
VolumeReadOnly bool
+ Env []Env
+ EnvFrom []EnvFrom
}
// getCtr takes a list of ctrOptions and returns a Ctr with sane defaults
// and the configured options
func getCtr(options ...ctrOption) *Ctr {
- c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, defaultCtrArg, true, false, nil, nil, "", "", "", false, "", "", false}
+ c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, defaultCtrArg, true, false, nil, nil, "", "", "", false, "", "", false, []Env{}, []EnvFrom{}}
for _, option := range options {
option(&c)
}
@@ -524,6 +590,31 @@ func withVolumeMount(mountPath string, readonly bool) ctrOption {
}
}
+func withEnv(name, value, valueFrom, refName, refKey string) ctrOption {
+ return func(c *Ctr) {
+ e := Env{
+ Name: name,
+ Value: value,
+ ValueFrom: valueFrom,
+ RefName: refName,
+ RefKey: refKey,
+ }
+
+ c.Env = append(c.Env, e)
+ }
+}
+
+func withEnvFrom(name, from string) ctrOption {
+ return func(c *Ctr) {
+ e := EnvFrom{
+ Name: name,
+ From: from,
+ }
+
+ c.EnvFrom = append(c.EnvFrom, e)
+ }
+}
+
func getCtrNameInPod(pod *Pod) string {
return fmt.Sprintf("%s-%s", pod.Name, defaultCtrName)
}
@@ -544,6 +635,19 @@ func getVolume(vType, vPath string) *Volume {
}
}
+type Env struct {
+ Name string
+ Value string
+ ValueFrom string
+ RefName string
+ RefKey string
+}
+
+type EnvFrom struct {
+ Name string
+ From string
+}
+
var _ = Describe("Podman generate kube", func() {
var (
tempdir string
@@ -581,7 +685,7 @@ var _ = Describe("Podman generate kube", func() {
})
It("podman play kube fail with nonexist authfile", func() {
- err := generatePodKubeYaml(getPod(), kubeYaml)
+ err := generateKubeYaml("pod", getPod(), kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", "--authfile", "/tmp/nonexist", kubeYaml})
@@ -592,7 +696,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test correct command", func() {
pod := getPod()
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -609,7 +713,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test correct command with only set command in yaml file", func() {
pod := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg(nil))))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -626,7 +730,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test correct command with only set args in yaml file", func() {
pod := getPod(withCtr(getCtr(withImage(redis), withCmd(nil), withArg([]string{"echo", "hello"}))))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -644,7 +748,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test correct output", func() {
p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
- err := generatePodKubeYaml(p, kubeYaml)
+ err := generateKubeYaml("pod", p, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -665,14 +769,14 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test restartPolicy", func() {
// podName, set, expect
testSli := [][]string{
- {"testPod1", "", "always"}, // Default eqaul to always
+ {"testPod1", "", "always"}, // Default equal to always
{"testPod2", "Always", "always"},
{"testPod3", "OnFailure", "on-failure"},
{"testPod4", "Never", "no"},
}
for _, v := range testSli {
pod := getPod(withPodName(v[0]), withRestartPolicy(v[1]))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -686,9 +790,52 @@ var _ = Describe("Podman generate kube", func() {
}
})
+ It("podman play kube test env value from configmap", func() {
+ SkipIfRemote("configmap list is not supported as a param")
+ cmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
+ cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
+ err := generateKubeYaml("configmap", cm, cmYamlPathname)
+ Expect(err).To(BeNil())
+
+ pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "FOO"))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", cmYamlPathname})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(Equal(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
+ })
+
+ It("podman play kube test get all key-value pairs from configmap as envs", func() {
+ SkipIfRemote("configmap list is not supported as a param")
+ cmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
+ cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO1", "foo1"), withConfigMapData("FOO2", "foo2"))
+ err := generateKubeYaml("configmap", cm, cmYamlPathname)
+ Expect(err).To(BeNil())
+
+ pod := getPod(withCtr(getCtr(withEnvFrom("foo", "configmap"))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", cmYamlPathname})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(Equal(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring(`FOO1=foo1`))
+ Expect(inspect.OutputToString()).To(ContainSubstring(`FOO2=foo2`))
+ })
+
It("podman play kube test hostname", func() {
pod := getPod()
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -704,7 +851,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube test with customized hostname", func() {
hostname := "myhostname"
pod := getPod(withHostname(hostname))
- err := generatePodKubeYaml(getPod(withHostname(hostname)), kubeYaml)
+ err := generateKubeYaml("pod", getPod(withHostname(hostname)), kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -727,7 +874,7 @@ var _ = Describe("Podman generate kube", func() {
"test4.podman.io",
}),
)
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -746,7 +893,7 @@ var _ = Describe("Podman generate kube", func() {
ctr := getCtr(withCapAdd([]string{capAdd}), withCmd([]string{"cat", "/proc/self/status"}), withArg(nil))
pod := getPod(withCtr(ctr))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -764,7 +911,7 @@ var _ = Describe("Podman generate kube", func() {
ctr := getCtr(withCapDrop([]string{capDrop}))
pod := getPod(withCtr(ctr))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -780,7 +927,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube no security context", func() {
// expect play kube to not fail if no security context is specified
pod := getPod(withCtr(getCtr(withSecurityContext(false))))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -793,7 +940,7 @@ var _ = Describe("Podman generate kube", func() {
})
It("podman play kube seccomp container level", func() {
- SkipIfRemote("FIXME This is broken")
+ SkipIfRemote("podman-remote does not support --seccomp-profile-root flag")
// expect play kube is expected to set a seccomp label if it's applied as an annotation
jsonFile, err := podmanTest.CreateSeccompJson(seccompPwdEPERM)
if err != nil {
@@ -805,7 +952,7 @@ var _ = Describe("Podman generate kube", func() {
ctr := getCtr(withCmd([]string{"pwd"}), withArg(nil))
pod := getPod(withCtr(ctr), withAnnotation(ctrAnnotation, "localhost/"+filepath.Base(jsonFile)))
- err = generatePodKubeYaml(pod, kubeYaml)
+ err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
// CreateSeccompJson will put the profile into podmanTest.TempDir. Use --seccomp-profile-root to tell play kube where to look
@@ -820,7 +967,7 @@ var _ = Describe("Podman generate kube", func() {
})
It("podman play kube seccomp pod level", func() {
- SkipIfRemote("FIXME: This should work with --remote")
+ SkipIfRemote("podman-remote does not support --seccomp-profile-root flag")
// expect play kube is expected to set a seccomp label if it's applied as an annotation
jsonFile, err := podmanTest.CreateSeccompJson(seccompPwdEPERM)
if err != nil {
@@ -832,7 +979,7 @@ var _ = Describe("Podman generate kube", func() {
ctr := getCtr(withCmd([]string{"pwd"}), withArg(nil))
pod := getPod(withCtr(ctr), withAnnotation("seccomp.security.alpha.kubernetes.io/pod", "localhost/"+filepath.Base(jsonFile)))
- err = generatePodKubeYaml(pod, kubeYaml)
+ err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
// CreateSeccompJson will put the profile into podmanTest.TempDir. Use --seccomp-profile-root to tell play kube where to look
@@ -848,7 +995,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube with pull policy of never should be 125", func() {
ctr := getCtr(withPullPolicy("never"), withImage(BB_GLIBC))
- err := generatePodKubeYaml(getPod(withCtr(ctr)), kubeYaml)
+ err := generateKubeYaml("pod", getPod(withCtr(ctr)), kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -858,7 +1005,7 @@ var _ = Describe("Podman generate kube", func() {
It("podman play kube with pull policy of missing", func() {
ctr := getCtr(withPullPolicy("missing"), withImage(BB))
- err := generatePodKubeYaml(getPod(withCtr(ctr)), kubeYaml)
+ err := generateKubeYaml("pod", getPod(withCtr(ctr)), kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -884,7 +1031,7 @@ var _ = Describe("Podman generate kube", func() {
oldBBinspect := inspect.InspectImageJSON()
ctr := getCtr(withPullPolicy("always"), withImage(BB))
- err := generatePodKubeYaml(getPod(withCtr(ctr)), kubeYaml)
+ err := generateKubeYaml("pod", getPod(withCtr(ctr)), kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -915,7 +1062,7 @@ var _ = Describe("Podman generate kube", func() {
oldBBinspect := inspect.InspectImageJSON()
ctr := getCtr(withImage(BB))
- err := generatePodKubeYaml(getPod(withCtr(ctr)), kubeYaml)
+ err := generateKubeYaml("pod", getPod(withCtr(ctr)), kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -973,7 +1120,7 @@ spec:
// Deployment related tests
It("podman play kube deployment 1 replica test correct command", func() {
deployment := getDeployment()
- err := generateDeploymentKubeYaml(deployment, kubeYaml)
+ err := generateKubeYaml("deployment", deployment, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -993,7 +1140,7 @@ spec:
var i, numReplicas int32
numReplicas = 5
deployment := getDeployment(withReplicas(numReplicas))
- err := generateDeploymentKubeYaml(deployment, kubeYaml)
+ err := generateKubeYaml("deployment", deployment, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1016,7 +1163,7 @@ spec:
ctr := getCtr(withHostIP(ip, port), withImage(BB))
pod := getPod(withCtr(ctr))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1033,7 +1180,7 @@ spec:
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume(`""`, hostPathLocation)))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1048,7 +1195,7 @@ spec:
f.Close()
pod := getPod(withVolume(getVolume(`""`, hostPathLocation)))
- err = generatePodKubeYaml(pod, kubeYaml)
+ err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1060,7 +1207,7 @@ spec:
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume("File", hostPathLocation)))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1075,7 +1222,7 @@ spec:
f.Close()
pod := getPod(withVolume(getVolume("File", hostPathLocation)))
- err = generatePodKubeYaml(pod, kubeYaml)
+ err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1087,7 +1234,7 @@ spec:
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume("FileOrCreate", hostPathLocation)))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1103,7 +1250,7 @@ spec:
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume("DirectoryOrCreate", hostPathLocation)))
- err := generatePodKubeYaml(pod, kubeYaml)
+ err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1123,7 +1270,7 @@ spec:
f.Close()
pod := getPod(withVolume(getVolume("Socket", hostPathLocation)))
- err = generatePodKubeYaml(pod, kubeYaml)
+ err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1139,7 +1286,7 @@ spec:
ctr := getCtr(withVolumeMount(hostPathLocation, true), withImage(BB))
pod := getPod(withVolume(getVolume("File", hostPathLocation)), withCtr(ctr))
- err = generatePodKubeYaml(pod, kubeYaml)
+ err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
@@ -1162,7 +1309,7 @@ spec:
withReplicas(numReplicas),
withPod(getPod(withLabel(expectedLabelKey, expectedLabelValue))),
)
- err := generateDeploymentKubeYaml(deployment, kubeYaml)
+ err := generateKubeYaml("deployment", deployment, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go
index 063c71b9f..797d51c33 100644
--- a/test/e2e/pod_infra_container_test.go
+++ b/test/e2e/pod_infra_container_test.go
@@ -377,7 +377,6 @@ var _ = Describe("Podman pod create", func() {
})
It("podman run --add-host in pod", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/pod_pod_namespaces.go b/test/e2e/pod_pod_namespaces.go
index 3139bf561..41e9c5683 100644
--- a/test/e2e/pod_pod_namespaces.go
+++ b/test/e2e/pod_pod_namespaces.go
@@ -61,7 +61,6 @@ var _ = Describe("Podman pod create", func() {
})
It("podman pod container dontshare PIDNS", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go
index 17ed6a9c0..a299d3cf2 100644
--- a/test/e2e/pod_ps_test.go
+++ b/test/e2e/pod_ps_test.go
@@ -8,6 +8,7 @@ import (
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman ps", func() {
@@ -63,7 +64,7 @@ var _ = Describe("Podman ps", func() {
result := podmanTest.Podman([]string{"pod", "ps", "-q"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).To(Exit(0))
Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0))
Expect(podid).To(ContainSubstring(result.OutputToStringArray()[0]))
})
diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go
index 1ffbe282b..41fc59267 100644
--- a/test/e2e/pod_stats_test.go
+++ b/test/e2e/pod_stats_test.go
@@ -6,6 +6,7 @@ import (
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman pod stats", func() {
@@ -156,9 +157,9 @@ var _ = Describe("Podman pod stats", func() {
session := podmanTest.RunTopContainerInPod("", podid)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.CID}} {{.Pod}} {{.Mem}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}} {{.Pod}}\""})
+ stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "table {{.CID}} {{.Pod}} {{.Mem}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}} {{.Pod}}"})
stats.WaitWithDefaultTimeout()
- Expect(stats.ExitCode()).To(Equal(0))
+ Expect(stats).To(Exit(0))
})
It("podman stats with invalid GO template", func() {
diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go
index 24b88bfdd..969f96165 100644
--- a/test/e2e/prune_test.go
+++ b/test/e2e/prune_test.go
@@ -88,7 +88,7 @@ var _ = Describe("Podman prune", func() {
})
It("podman image prune skip cache images", func() {
- SkipIfRemote("FIXME should work on podman --remote")
+ SkipIfRemote("FIXME: podman-remote build is not working the same as local build")
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
none := podmanTest.Podman([]string{"images", "-a"})
@@ -110,7 +110,7 @@ var _ = Describe("Podman prune", func() {
})
It("podman image prune dangling images", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("FIXME: podman-remote build is not working the same as local build")
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
@@ -147,7 +147,6 @@ var _ = Describe("Podman prune", func() {
})
It("podman system image prune unused images", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
podmanTest.RestoreAllArtifacts()
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
prune := podmanTest.PodmanNoCache([]string{"system", "prune", "-a", "--force"})
diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go
index 0f2ce2d46..48ef566ce 100644
--- a/test/e2e/ps_test.go
+++ b/test/e2e/ps_test.go
@@ -11,6 +11,7 @@ import (
"github.com/docker/go-units"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman ps", func() {
@@ -218,17 +219,16 @@ var _ = Describe("Podman ps", func() {
})
It("podman ps namespace flag with go template format", func() {
- Skip("FIXME: table still not supported in podman ps command")
_, ec, _ := podmanTest.RunLsContainer("test1")
Expect(ec).To(Equal(0))
result := podmanTest.Podman([]string{"ps", "-a", "--format", "table {{.ID}} {{.Image}} {{.ImageID}} {{.Labels}}"})
result.WaitWithDefaultTimeout()
- Expect(strings.Contains(result.OutputToStringArray()[0], "table")).To(BeFalse())
- Expect(strings.Contains(result.OutputToStringArray()[0], "ID")).To(BeTrue())
- Expect(strings.Contains(result.OutputToStringArray()[0], "ImageID")).To(BeTrue())
- Expect(strings.Contains(result.OutputToStringArray()[1], "alpine:latest")).To(BeTrue())
- Expect(result.ExitCode()).To(Equal(0))
+
+ Expect(result.OutputToStringArray()[0]).ToNot(ContainSubstring("table"))
+ Expect(result.OutputToStringArray()[0]).ToNot(ContainSubstring("ImageID"))
+ Expect(result.OutputToStringArray()[0]).To(ContainSubstring("alpine:latest"))
+ Expect(result).Should(Exit(0))
})
It("podman ps ancestor filter flag", func() {
diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go
index edc17fdbf..08ab50de1 100644
--- a/test/e2e/pull_test.go
+++ b/test/e2e/pull_test.go
@@ -234,7 +234,8 @@ var _ = Describe("Podman pull", func() {
})
It("podman pull from docker-archive", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("podman-remote does not support pulling from docker-archive")
+
podmanTest.RestoreArtifact(ALPINE)
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.PodmanNoCache([]string{"save", "-o", tarfn, "alpine"})
@@ -296,7 +297,8 @@ var _ = Describe("Podman pull", func() {
})
It("podman pull from oci-archive", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("podman-remote does not support pulling from oci-archive")
+
podmanTest.RestoreArtifact(ALPINE)
tarfn := filepath.Join(podmanTest.TempDir, "oci-alp.tar")
session := podmanTest.PodmanNoCache([]string{"save", "--format", "oci-archive", "-o", tarfn, "alpine"})
@@ -315,7 +317,8 @@ var _ = Describe("Podman pull", func() {
})
It("podman pull from local directory", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("podman-remote does not support pulling from local directory")
+
podmanTest.RestoreArtifact(ALPINE)
dirpath := filepath.Join(podmanTest.TempDir, "alpine")
os.MkdirAll(dirpath, os.ModePerm)
@@ -340,7 +343,8 @@ var _ = Describe("Podman pull", func() {
})
It("podman pull from local OCI directory", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("podman-remote does not support pulling from OCI directory")
+
podmanTest.RestoreArtifact(ALPINE)
dirpath := filepath.Join(podmanTest.TempDir, "alpine")
os.MkdirAll(dirpath, os.ModePerm)
diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go
index 7eff8c6ed..524c07cc6 100644
--- a/test/e2e/rm_test.go
+++ b/test/e2e/rm_test.go
@@ -236,7 +236,6 @@ var _ = Describe("Podman rm", func() {
})
It("podman rm --ignore bogus container and a running container", func() {
-
session := podmanTest.RunTopContainer("test1")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go
index 8a5014899..7cb489113 100644
--- a/test/e2e/rmi_test.go
+++ b/test/e2e/rmi_test.go
@@ -185,7 +185,8 @@ var _ = Describe("Podman rmi", func() {
})
It("podman rmi with cached images", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("FIXME This should work on podman-remote, problem is with podman-remote build")
+
session := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
@@ -255,7 +256,6 @@ var _ = Describe("Podman rmi", func() {
})
It("podman rmi -a with parent|child images", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
dockerfile := `FROM docker.io/library/alpine:latest AS base
RUN touch /1
ENV LOCAL=/1
diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go
index 13a9abf9b..db802946e 100644
--- a/test/e2e/run_entrypoint_test.go
+++ b/test/e2e/run_entrypoint_test.go
@@ -90,7 +90,7 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
})
It("podman run user entrypoint overrides image entrypoint and image cmd", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("FIXME: podman-remote not handling passing --entrypoint=\"\" flag correctly")
dockerfile := `FROM docker.io/library/alpine:latest
CMD ["-i"]
ENTRYPOINT ["grep", "Alpine", "/etc/os-release"]
diff --git a/test/e2e/run_env_test.go b/test/e2e/run_env_test.go
index 3f488ada5..9882b936a 100644
--- a/test/e2e/run_env_test.go
+++ b/test/e2e/run_env_test.go
@@ -90,11 +90,15 @@ var _ = Describe("Podman run", func() {
})
It("podman run --env-host environment test", func() {
- SkipIfRemote("FIXME, We should check that --env-host reports correct error on podman-remote")
env := append(os.Environ(), "FOO=BAR")
session := podmanTest.PodmanAsUser([]string{"run", "--rm", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env)
-
session.WaitWithDefaultTimeout()
+ if IsRemote() {
+ // podman-remote does not support --env-host
+ Expect(session.ExitCode()).To(Equal(125))
+ Expect(session.ErrorToString()).To(ContainSubstring("unknown flag: --env-host"))
+ return
+ }
Expect(session.ExitCode()).To(Equal(0))
match, _ := session.GrepString("BAR")
Expect(match).Should(BeTrue())
@@ -108,8 +112,11 @@ var _ = Describe("Podman run", func() {
})
It("podman run --http-proxy test", func() {
- SkipIfRemote("FIXME: Should report proper error when http-proxy is not supported")
os.Setenv("http_proxy", "1.2.3.4")
+ if IsRemote() {
+ podmanTest.StopRemoteService()
+ podmanTest.StartRemoteService()
+ }
session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "http_proxy"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 044e56e6c..e14482db7 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -73,7 +73,7 @@ var _ = Describe("Podman run networking", func() {
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("80"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Not(Equal("80")))
Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
@@ -111,7 +111,7 @@ var _ = Describe("Podman run networking", func() {
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports["80/udp"])).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostPort).To(Equal("80"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostPort).To(Not(Equal("80")))
Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostIP).To(Equal(""))
})
@@ -195,7 +195,7 @@ var _ = Describe("Podman run networking", func() {
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("80"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Not(Equal("80")))
Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
@@ -477,6 +477,17 @@ var _ = Describe("Podman run networking", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman run --uidmap /etc/hosts contains --hostname", func() {
+ SkipIfRootless("uidmap population of cninetworks not supported for rootless users")
+ session := podmanTest.Podman([]string{"run", "--uidmap", "0:100000:1000", "--rm", "--hostname", "foohostname", ALPINE, "grep", "foohostname", "/etc/hosts"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "--uidmap", "0:100000:1000", "--rm", "--hostname", "foohostname", "-v", "/etc/hosts:/etc/hosts", ALPINE, "grep", "foohostname", "/etc/hosts"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ })
+
It("podman run network in user created network namespace", func() {
SkipIfRootless("ip netns is not supported for rootless users")
if Containerized() {
diff --git a/test/e2e/run_restart_test.go b/test/e2e/run_restart_test.go
index 1bef3f954..85621a762 100644
--- a/test/e2e/run_restart_test.go
+++ b/test/e2e/run_restart_test.go
@@ -33,11 +33,14 @@ var _ = Describe("Podman run restart containers", func() {
})
It("Podman start after successful run", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
session := podmanTest.Podman([]string{"run", "--name", "test", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"wait", "test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
session2 := podmanTest.Podman([]string{"start", "--attach", "test"})
session2.WaitWithDefaultTimeout()
Expect(session2.ExitCode()).To(Equal(0))
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 05aede122..e6bba9f67 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -39,7 +39,6 @@ var _ = Describe("Podman run", func() {
podmanTest.Cleanup()
f := CurrentGinkgoTestDescription()
processTestResult(f)
-
})
It("podman run a container based on local image", func() {
@@ -321,7 +320,6 @@ var _ = Describe("Podman run", func() {
It("podman run user capabilities test with image", func() {
// We need to ignore the containers.conf on the test distribution for this test
os.Setenv("CONTAINERS_CONF", "/dev/null")
- SkipIfRemote("FIXME This should work on podman-remote")
dockerfile := `FROM busybox
USER bin`
podmanTest.BuildImage(dockerfile, "test", "false")
@@ -396,7 +394,7 @@ USER bin`
})
It("podman run sysctl test", func() {
- SkipIfRootless("Network sysctls are not avalable root rootless")
+ SkipIfRootless("Network sysctls are not available root rootless")
session := podmanTest.Podman([]string{"run", "--rm", "--sysctl", "net.core.somaxconn=65535", ALPINE, "sysctl", "net.core.somaxconn"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -565,7 +563,7 @@ USER bin`
})
It("podman run with secrets", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("--default-mount-file option is not supported in podman-remote")
containersDir := filepath.Join(podmanTest.TempDir, "containers")
err := os.MkdirAll(containersDir, 0755)
Expect(err).To(BeNil())
@@ -725,7 +723,6 @@ USER bin`
})
It("podman run with built-in volume image", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
session := podmanTest.Podman([]string{"run", "--rm", redis, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -1041,7 +1038,6 @@ USER mail`
})
It("podman run with restart-policy always restarts containers", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
testDir := filepath.Join(podmanTest.RunRoot, "restart-test")
err := os.MkdirAll(testDir, 0755)
Expect(err).To(BeNil())
@@ -1051,11 +1047,11 @@ USER mail`
Expect(err).To(BeNil())
file.Close()
- session := podmanTest.Podman([]string{"run", "-dt", "--restart", "always", "-v", fmt.Sprintf("%s:/tmp/runroot:Z", testDir), fedoraMinimal, "bash", "-c", "date +%N > /tmp/runroot/ran && while test -r /tmp/runroot/running; do sleep 0.1s; done"})
+ session := podmanTest.Podman([]string{"run", "-dt", "--restart", "always", "-v", fmt.Sprintf("%s:/tmp/runroot:Z", testDir), ALPINE, "sh", "-c", "date +%N > /tmp/runroot/ran && while test -r /tmp/runroot/running; do sleep 0.1s; done"})
found := false
testFile := filepath.Join(testDir, "ran")
- for i := 0; i < 10; i++ {
+ for i := 0; i < 30; i++ {
time.Sleep(1 * time.Second)
if _, err := os.Stat(testFile); err == nil {
found = true
diff --git a/test/e2e/run_working_dir.go b/test/e2e/run_working_dir.go
index 85aa0cffe..7d8db361c 100644
--- a/test/e2e/run_working_dir.go
+++ b/test/e2e/run_working_dir.go
@@ -50,7 +50,6 @@ var _ = Describe("Podman run", func() {
})
It("podman run a container on an image with a workdir", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
dockerfile := `FROM alpine
RUN mkdir -p /home/foobar
WORKDIR /etc/foobar`
diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go
index 19365909d..043da9059 100644
--- a/test/e2e/search_test.go
+++ b/test/e2e/search_test.go
@@ -237,7 +237,7 @@ registries = ['{{.Host}}:{{.Port}}']`
})
It("podman search attempts HTTP if registry is in registries.insecure and force secure is false", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("--tls-verify is not supported on podman-remote search")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
@@ -278,7 +278,7 @@ registries = ['{{.Host}}:{{.Port}}']`
})
It("podman search doesn't attempt HTTP if force secure is true", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("--tls-verify is not supported on podman-remote search")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
@@ -317,7 +317,7 @@ registries = ['{{.Host}}:{{.Port}}']`
})
It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
+ SkipIfRemote("--tls-verify is not supported on podman-remote search")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
diff --git a/test/e2e/system_df_test.go b/test/e2e/system_df_test.go
index aee5dafb8..365e36fc7 100644
--- a/test/e2e/system_df_test.go
+++ b/test/e2e/system_df_test.go
@@ -3,6 +3,7 @@ package integration
import (
"fmt"
"os"
+ "strconv"
"strings"
. "github.com/containers/podman/v2/test/utils"
@@ -35,7 +36,6 @@ var _ = Describe("podman system df", func() {
})
It("podman system df", func() {
- SkipIfRemote("FIXME This should work on podman-remote")
session := podmanTest.Podman([]string{"create", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -48,6 +48,11 @@ var _ = Describe("podman system df", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"images", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ totImages := strconv.Itoa(len(session.OutputToStringArray()))
+
session = podmanTest.Podman([]string{"system", "df"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -55,7 +60,7 @@ var _ = Describe("podman system df", func() {
images := strings.Fields(session.OutputToStringArray()[1])
containers := strings.Fields(session.OutputToStringArray()[2])
volumes := strings.Fields(session.OutputToStringArray()[3])
- Expect(images[1]).To(Equal("11"))
+ Expect(images[1]).To(Equal(string(totImages)))
Expect(containers[1]).To(Equal("2"))
Expect(volumes[2]).To(Equal("1"))
})
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index 4a2c2d324..1cb6440aa 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -7,6 +7,7 @@ import (
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman volume ls", func() {
@@ -56,15 +57,15 @@ var _ = Describe("Podman volume ls", func() {
})
It("podman ls volume with Go template", func() {
- Skip("FIXME: table still not supported in podman volume command")
session := podmanTest.Podman([]string{"volume", "create", "myvol"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"volume", "ls", "--format", "table {{.Name}} {{.Driver}} {{.Scope}}"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(len(session.OutputToStringArray())).To(Equal(2))
+
+ Expect(session).Should(Exit(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(1), session.OutputToString())
})
It("podman ls volume with --filter flag", func() {
diff --git a/test/system/055-rm.bats b/test/system/055-rm.bats
index c8475c3e9..7176ae4b8 100644
--- a/test/system/055-rm.bats
+++ b/test/system/055-rm.bats
@@ -33,6 +33,21 @@ load helpers
run_podman rm -f $cid
}
+@test "podman rm container from storage" {
+ if is_remote; then
+ skip "only applicable for local podman"
+ fi
+ rand=$(random_string 30)
+ run_podman create --name $rand $IMAGE /bin/true
+
+ # Create a container that podman does not know about
+ run buildah from $IMAGE
+ cid="$output"
+
+ # rm should succeed
+ run_podman rm $rand $cid
+}
+
# I'm sorry! This test takes 13 seconds. There's not much I can do about it,
# please know that I think it's justified: podman 1.5.0 had a strange bug
# in with exit status was not preserved on some code paths with 'rm -f'
diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats
index cd3b8ff5b..ece87acf6 100644
--- a/test/system/060-mount.bats
+++ b/test/system/060-mount.bats
@@ -61,7 +61,7 @@ load helpers
# 'image mount', no args, tells us what's mounted
run_podman image mount
- is "$output" "$IMAGE $mount_path" "podman image mount with no args"
+ is "$output" "$IMAGE *$mount_path" "podman image mount with no args"
# Clean up
run_podman image umount $IMAGE
diff --git a/test/system/070-build.bats b/test/system/070-build.bats
index 1329c6168..287323bbf 100644
--- a/test/system/070-build.bats
+++ b/test/system/070-build.bats
@@ -174,12 +174,19 @@ EOF
run_podman build -t build_test -f build-test/Containerfile build-test
local iid="${lines[-1]}"
+
+ if is_remote; then
+ ENVHOST=""
+ else
+ ENVHOST="--env-host"
+ fi
+
# Run without args - should run the above script. Verify its output.
export MYENV2="$s_env2"
export MYENV3="env-file-should-override-env-host!"
run_podman run --rm \
--env-file=$PODMAN_TMPDIR/env-file \
- --env-host \
+ ${ENVHOST} \
-e MYENV4="$s_env4" \
build_test
is "${lines[0]}" "$workdir" "container default command: pwd"