diff options
81 files changed, 1400 insertions, 613 deletions
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/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/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/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/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-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/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/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/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..0107e18c4 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) @@ -321,7 +321,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/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/play_kube_test.go b/test/e2e/play_kube_test.go index d771860d8..b0831c823 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}) @@ -672,7 +776,7 @@ var _ = Describe("Podman generate kube", func() { } 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/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/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/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..cd32e5a77 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") @@ -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/system/060-mount.bats b/test/system/060-mount.bats index 75c88e4ad..f11aff773 100644 --- a/test/system/060-mount.bats +++ b/test/system/060-mount.bats @@ -56,7 +56,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" |