diff options
-rw-r--r-- | cmd/podman/containers/stats.go | 56 | ||||
-rw-r--r-- | cmd/podman/images/build.go | 1 | ||||
-rw-r--r-- | docs/source/markdown/podman-load.1.md | 4 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 22 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/containers_stats.go | 72 | ||||
-rw-r--r-- | pkg/api/server/register_containers.go | 31 | ||||
-rw-r--r-- | pkg/bindings/containers/containers.go | 51 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 19 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 116 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 7 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 7 | ||||
-rw-r--r-- | test/e2e/load_test.go | 18 | ||||
-rw-r--r-- | test/e2e/stats_test.go | 2 | ||||
-rw-r--r-- | test/system/030-run.bats | 55 |
15 files changed, 373 insertions, 90 deletions
diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index ddb5f32ef..bbd389bbf 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "strings" - "sync" "text/tabwriter" "text/template" @@ -48,8 +47,18 @@ var ( } ) +// statsOptionsCLI is used for storing CLI arguments. Some fields are later +// used in the backend. +type statsOptionsCLI struct { + All bool + Format string + Latest bool + NoReset bool + NoStream bool +} + var ( - statsOptions entities.ContainerStatsOptions + statsOptions statsOptionsCLI defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" defaultStatsHeader = "ID\tNAME\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET IO\tBLOCK IO\tPIDS\n" ) @@ -107,32 +116,37 @@ func stats(cmd *cobra.Command, args []string) error { return errors.New("stats is not supported in rootless mode without cgroups v2") } } - statsOptions.StatChan = make(chan []*define.ContainerStats, 1) - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - for reports := range statsOptions.StatChan { - if err := outputStats(reports); err != nil { - logrus.Error(err) - } - } - wg.Done() - }() - err := registry.ContainerEngine().ContainerStats(registry.Context(), args, statsOptions) - wg.Wait() - return err + // Convert to the entities options. We should not leak CLI-only + // options into the backend and separate concerns. + opts := entities.ContainerStatsOptions{ + Latest: statsOptions.Latest, + Stream: !statsOptions.NoStream, + } + statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts) + if err != nil { + return err + } + for report := range statsChan { + if report.Error != nil { + return report.Error + } + if err := outputStats(report.Stats); err != nil { + logrus.Error(err) + } + } + return nil } -func outputStats(reports []*define.ContainerStats) error { +func outputStats(reports []define.ContainerStats) error { if len(statsOptions.Format) < 1 && !statsOptions.NoReset { tm.Clear() tm.MoveCursor(1, 1) tm.Flush() } - stats := make([]*containerStats, 0, len(reports)) + stats := make([]containerStats, 0, len(reports)) for _, r := range reports { - stats = append(stats, &containerStats{r}) + stats = append(stats, containerStats{r}) } if statsOptions.Format == "json" { return outputJSON(stats) @@ -163,7 +177,7 @@ func outputStats(reports []*define.ContainerStats) error { } type containerStats struct { - *define.ContainerStats + define.ContainerStats } func (s *containerStats) ID() string { @@ -213,7 +227,7 @@ func combineHumanValues(a, b uint64) string { return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) } -func outputJSON(stats []*containerStats) error { +func outputJSON(stats []containerStats) error { type jstat struct { Id string `json:"id"` //nolint Name string `json:"name"` diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 00777c48c..d24bb18b6 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -436,6 +436,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil Quiet: flags.Quiet, RemoveIntermediateCtrs: flags.Rm, ReportWriter: reporter, + Runtime: containerConfig.RuntimePath, RuntimeArgs: runtimeFlags, SignBy: flags.SignBy, SignaturePolicyPath: flags.SignaturePolicy, diff --git a/docs/source/markdown/podman-load.1.md b/docs/source/markdown/podman-load.1.md index 917f102f6..308a3493b 100644 --- a/docs/source/markdown/podman-load.1.md +++ b/docs/source/markdown/podman-load.1.md @@ -9,9 +9,11 @@ podman\-load - Load an image from a container image archive into container stora **podman image load** [*options*] [*name*[:*tag*]] ## DESCRIPTION -**podman load** loads an image from either an **oci-archive** or **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set. +**podman load** loads an image from either an **oci-archive** or a **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set. You can also specify a name for the image if the archive does not contain a named reference, of if you want an additional name for the local image. +The local client further supports loading an **oci-dir** or a **docker-dir** as created with **podman save** (1). + The **quiet** option suppresses the progress output when set. Note: `:` is a restricted character and cannot be part of the file name. diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 63c44eaef..3a904ba87 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -17,6 +17,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func RemoveContainer(w http.ResponseWriter, r *http.Request) { @@ -44,8 +45,25 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) + if err != nil && errors.Cause(err) == define.ErrNoSuchCtr { + // Failed to get container. If force is specified, get the container's ID + // and evict it + if !query.Force { + utils.ContainerNotFound(w, name, err) + return + } + + if _, err := runtime.EvictContainer(r.Context(), name, query.Vols); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + logrus.Debugf("Ignoring error (--allow-missing): %q", err) + w.WriteHeader(http.StatusNoContent) + return + } + logrus.Warn(errors.Wrapf(err, "Failed to evict container: %q", name)) + utils.InternalServerError(w, err) + return + } + w.WriteHeader(http.StatusNoContent) return } diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go new file mode 100644 index 000000000..4d5abe118 --- /dev/null +++ b/pkg/api/handlers/libpod/containers_stats.go @@ -0,0 +1,72 @@ +package libpod + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/containers/podman/v2/libpod" + "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/gorilla/schema" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const DefaultStatsPeriod = 5 * time.Second + +func StatsContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + Containers []string `schema:"containers"` + Stream bool `schema:"stream"` + }{ + Stream: true, + } + 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 + } + + // Reduce code duplication and use the local/abi implementation of + // container stats. + containerEngine := abi.ContainerEngine{Libpod: runtime} + + statsOptions := entities.ContainerStatsOptions{ + Stream: query.Stream, + } + + // Stats will stop if the connection is closed. + statsChan, err := containerEngine.ContainerStats(r.Context(), query.Containers, statsOptions) + if err != nil { + utils.InternalServerError(w, err) + return + } + + // Write header and content type. + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + + // Setup JSON encoder for streaming. + coder := json.NewEncoder(w) + coder.SetEscapeHTML(true) + + for stats := range statsChan { + if err := coder.Encode(stats); err != nil { + // Note: even when streaming, the stats goroutine will + // be notified (and stop) as the connection will be + // closed. + logrus.Errorf("Unable to encode stats: %v", err) + return + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + } +} diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 0ad5d29ea..870c6a90c 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1013,7 +1013,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // tags: // - containers // summary: Get stats for a container - // description: This returns a live stream of a container’s resource usage statistics. + // description: DEPRECATED. This endpoint will be removed with the next major release. Please use /libpod/containers/stats instead. // parameters: // - in: path // name: name @@ -1035,6 +1035,35 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet) + // swagger:operation GET /libpod/containers/stats libpod libpodStatsContainers + // --- + // tags: + // - containers + // summary: Get stats for one or more containers + // description: Return a live stream of resource usage statistics of one or more container. If no container is specified, the statistics of all containers are returned. + // parameters: + // - in: query + // name: containers + // description: names or IDs of containers + // type: array + // items: + // type: string + // - in: query + // name: stream + // type: boolean + // default: true + // description: Stream the output + // produces: + // - application/json + // responses: + // 200: + // description: no error + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/stats"), s.APIHandler(libpod.StatsContainer)).Methods(http.MethodGet) + // swagger:operation GET /libpod/containers/{name}/top libpod libpodTopContainer // --- // tags: diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 981912665..46e4df1d2 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -197,7 +197,56 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error { return response.Process(nil) } -func Stats() {} +func Stats(ctx context.Context, containers []string, stream *bool) (chan entities.ContainerStatsReport, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + if stream != nil { + params.Set("stream", strconv.FormatBool(*stream)) + } + for _, c := range containers { + params.Add("containers", c) + } + + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/stats", params, nil) + if err != nil { + return nil, err + } + + statsChan := make(chan entities.ContainerStatsReport) + + go func() { + defer close(statsChan) + + dec := json.NewDecoder(response.Body) + doStream := true + if stream != nil { + doStream = *stream + } + + streamLabel: // label to flatten the scope + select { + case <-response.Request.Context().Done(): + return // lost connection - maybe the server quit + default: + // fall through and do some work + } + var report entities.ContainerStatsReport + if err := dec.Decode(&report); err != nil { + report = entities.ContainerStatsReport{Error: err} + } + statsChan <- report + + if report.Error != nil || !doStream { + return + } + goto streamLabel + }() + + return statsChan, nil +} // Top gathers statistics about the running processes in a container. The nameOrID can be a container name // or a partial/full ID. The descriptors allow for specifying which data to collect from the process. diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 16997cdd1..7b272f01e 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -411,10 +411,17 @@ type ContainerCpReport struct { // ContainerStatsOptions describes input options for getting // stats on containers type ContainerStatsOptions struct { - All bool - Format string - Latest bool - NoReset bool - NoStream bool - StatChan chan []*define.ContainerStats + // Operate on the latest known container. Only supported for local + // clients. + Latest bool + // Stream stats. + Stream bool +} + +// ContainerStatsReport is used for streaming container stats. +type ContainerStatsReport struct { + // Error from reading stats. + Error error + // Results, set when there is no error. + Stats []define.ContainerStats } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 6c85c9267..f105dc333 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -38,7 +38,7 @@ type ContainerEngine interface { ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error) ContainerRunlabel(ctx context.Context, label string, image string, args []string, opts ContainerRunlabelOptions) error ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) - ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error + ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) (chan ContainerStatsReport, error) ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) ContainerUnmount(ctx context.Context, nameOrIDs []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 21618f555..8b0d53940 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1142,12 +1142,12 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) { }) } -func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { - defer close(options.StatChan) +func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { + statsChan = make(chan entities.ContainerStatsReport, 1) + containerFunc := ic.Libpod.GetRunningContainers + queryAll := false switch { - case len(namesOrIds) > 0: - containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) } case options.Latest: containerFunc = func() ([]*libpod.Container, error) { lastCtr, err := ic.Libpod.GetLatestContainer() @@ -1156,62 +1156,76 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri } return []*libpod.Container{lastCtr}, nil } - case options.All: + case len(namesOrIds) > 0: + containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) } + default: + // No containers, no latest -> query all! + queryAll = true containerFunc = ic.Libpod.GetAllContainers } - ctrs, err := containerFunc() - if err != nil { - return errors.Wrapf(err, "unable to get list of containers") - } - containerStats := map[string]*define.ContainerStats{} - for _, ctr := range ctrs { - initialStats, err := ctr.GetContainerStats(&define.ContainerStats{}) - if err != nil { - // when doing "all", don't worry about containers that are not running - cause := errors.Cause(err) - if options.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) { - continue - } - if cause == cgroups.ErrCgroupV1Rootless { - err = cause - } - return err + go func() { + defer close(statsChan) + var ( + err error + containers []*libpod.Container + containerStats map[string]*define.ContainerStats + ) + containerStats = make(map[string]*define.ContainerStats) + + stream: // label to flatten the scope + select { + case <-ctx.Done(): + // client cancelled + logrus.Debugf("Container stats stopped: context cancelled") + return + default: + // just fall through and do work } - containerStats[ctr.ID()] = initialStats - } - for { - reportStats := []*define.ContainerStats{} - for _, ctr := range ctrs { - id := ctr.ID() - if _, ok := containerStats[ctr.ID()]; !ok { - initialStats, err := ctr.GetContainerStats(&define.ContainerStats{}) - if errors.Cause(err) == define.ErrCtrRemoved || errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrStateInvalid { - // skip dealing with a container that is gone - continue + + // Anonymous func to easily use the return values for streaming. + computeStats := func() ([]define.ContainerStats, error) { + containers, err = containerFunc() + if err != nil { + return nil, errors.Wrapf(err, "unable to get list of containers") + } + + reportStats := []define.ContainerStats{} + for _, ctr := range containers { + prev, ok := containerStats[ctr.ID()] + if !ok { + prev = &define.ContainerStats{} } + + stats, err := ctr.GetContainerStats(prev) if err != nil { - return err + cause := errors.Cause(err) + if queryAll && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) { + continue + } + if cause == cgroups.ErrCgroupV1Rootless { + err = cause + } + return nil, err } - containerStats[id] = initialStats - } - stats, err := ctr.GetContainerStats(containerStats[id]) - if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { - return err + + containerStats[ctr.ID()] = stats + reportStats = append(reportStats, *stats) } - // replace the previous measurement with the current one - containerStats[id] = stats - reportStats = append(reportStats, stats) + return reportStats, nil } - ctrs, err = containerFunc() - if err != nil { - return err - } - options.StatChan <- reportStats - if options.NoStream { - break + + report := entities.ContainerStatsReport{} + report.Stats, report.Error = computeStats() + statsChan <- report + + if report.Error != nil || !options.Stream { + return } + time.Sleep(time.Second) - } - return nil + goto stream + }() + + return statsChan, nil } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 7ea3b16a6..9b03503c6 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -722,6 +722,9 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, func (ic *ContainerEngine) Shutdown(_ context.Context) { } -func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { - return errors.New("not implemented") +func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { + if options.Latest { + return nil, errors.New("latest is not supported for the remote client") + } + return containers.Stats(ic.ClientCxt, namesOrIds, &options.Stream) } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 332a7c2eb..981884109 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -199,6 +199,13 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) return nil, err } defer f.Close() + fInfo, err := f.Stat() + if err != nil { + return nil, err + } + if fInfo.IsDir() { + return nil, errors.Errorf("remote client supports archives only but %q is a directory", opts.Input) + } ref := opts.Name if len(opts.Tag) > 0 { ref += ":" + opts.Tag diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index c517c90d8..ddffadac0 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -123,7 +123,7 @@ var _ = Describe("Podman load", func() { }) It("podman load directory", func() { - SkipIfRemote("FIXME: Remote Load is broken.") + SkipIfRemote("Remote does not support loading directories") outdir := filepath.Join(podmanTest.TempDir, "alpine") save := podmanTest.PodmanNoCache([]string{"save", "--format", "oci-dir", "-o", outdir, ALPINE}) @@ -139,6 +139,22 @@ var _ = Describe("Podman load", func() { Expect(result.ExitCode()).To(Equal(0)) }) + It("podman-remote load directory", func() { + // Remote-only test looking for the specific remote error + // message when trying to load a directory. + if !IsRemote() { + Skip("Remote only test") + } + + result := podmanTest.Podman([]string{"load", "-i", podmanTest.TempDir}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(125)) + + errMsg := fmt.Sprintf("remote client supports archives only but %q is a directory", podmanTest.TempDir) + found, _ := result.ErrorGrepString(errMsg) + Expect(found).Should(BeTrue()) + }) + It("podman load bogus file", func() { save := podmanTest.PodmanNoCache([]string{"load", "-i", "foobar.tar"}) save.WaitWithDefaultTimeout() diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index ff6ddce7e..7ab435007 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -1,4 +1,4 @@ -// +build !remote +// +build package integration diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 518d902a7..11edaf11c 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -61,8 +61,8 @@ echo $rand | 0 | $rand is "$tests_run" "$(grep . <<<$tests | wc -l)" "Ran the full set of tests" } -@test "podman run - globle runtime option" { - skip_if_remote "runtime flag is not passing over remote" +@test "podman run - global runtime option" { + skip_if_remote "runtime flag is not passed over remote" run_podman 126 --runtime-flag invalidflag run --rm $IMAGE is "$output" ".*invalidflag" "failed when passing undefined flags to the runtime" } @@ -337,4 +337,55 @@ echo $rand | 0 | $rand run_podman wait $cid } +# For #7754: json-file was equating to 'none' +@test "podman run --log-driver" { + # '-' means that LogPath will be blank and there's no easy way to test + tests=" +none | - +journald | - +k8s-file | y +json-file | f +" + while read driver do_check; do + msg=$(random_string 15) + run_podman run --name myctr --log-driver $driver $IMAGE echo $msg + + # Simple output check + # Special case: 'json-file' emits a warning, the rest do not + # ...but with podman-remote the warning is on the server only + if [[ $do_check == 'f' ]] && ! is_remote; then # 'f' for 'fallback' + is "${lines[0]}" ".* level=error msg=\"json-file logging specified but not supported. Choosing k8s-file logging instead\"" \ + "Fallback warning emitted" + is "${lines[1]}" "$msg" "basic output sanity check (driver=$driver)" + else + is "$output" "$msg" "basic output sanity check (driver=$driver)" + fi + + # Simply confirm that podman preserved our argument as-is + run_podman inspect --format '{{.HostConfig.LogConfig.Type}}' myctr + is "$output" "$driver" "podman inspect: driver" + + # If LogPath is non-null, check that it exists and has a valid log + run_podman inspect --format '{{.LogPath}}' myctr + if [[ $do_check != '-' ]]; then + is "$output" "/.*" "LogPath (driver=$driver)" + if ! test -e "$output"; then + die "LogPath (driver=$driver) does not exist: $output" + fi + # eg 2020-09-23T13:34:58.644824420-06:00 stdout F 7aiYtvrqFGJWpak + is "$(< $output)" "[0-9T:.+-]\+ stdout F $msg" \ + "LogPath contents (driver=$driver)" + else + is "$output" "" "LogPath (driver=$driver)" + fi + run_podman rm myctr + done < <(parse_table "$tests") + + # Invalid log-driver argument + run_podman 125 run --log-driver=InvalidDriver $IMAGE true + is "$output" "Error: error running container create option: invalid log driver: invalid argument" \ + "--log-driver InvalidDriver" +} + + # vim: filetype=sh |