diff options
-rw-r--r-- | .cirrus.yml | 2 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | cmd/podman/pod_ps.go | 126 | ||||
-rw-r--r-- | cmd/podman/shared/pod.go | 126 | ||||
-rwxr-xr-x | hack/man-page-checker | 12 | ||||
-rwxr-xr-x | hack/xref-helpmsgs-manpages | 307 | ||||
-rw-r--r-- | libpod/runtime_pod.go | 10 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 26 | ||||
-rw-r--r-- | pkg/adapter/pods_remote.go | 7 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/pods.go | 7 | ||||
-rw-r--r-- | pkg/api/handlers/utils/containers.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/utils/pods.go | 45 | ||||
-rw-r--r-- | pkg/bindings/test/pods_test.go | 67 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 17 |
15 files changed, 589 insertions, 173 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 5ec35cccb..c44277b05 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -137,7 +137,7 @@ gating_task: - 'cd $GOSRC && ./hack/podman-commands.sh |& ${TIMESTAMP}' # N/B: need 'clean' so some committed files are re-generated. - '/usr/local/bin/entrypoint.sh clean podman-remote |& ${TIMESTAMP}' - - '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp" |& ${TIMESTAMP}' + - '/usr/local/bin/entrypoint.sh clean podman xref_helpmsgs_manpages BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp" |& ${TIMESTAMP}' - '/usr/local/bin/entrypoint.sh local-cross |& ${TIMESTAMP}' - '/usr/local/bin/entrypoint.sh podman-remote-darwin |& ${TIMESTAMP}' - '/usr/local/bin/entrypoint.sh podman-remote-windows |& ${TIMESTAMP}' @@ -383,6 +383,10 @@ docdir: .PHONY: docs docs: .install.md2man docdir $(MANPAGES) ## Generate documentation +.PHONE: xref_helpmsgs_manpages +xref_helpmsgs_manpages: + ./hack/xref-helpmsgs-manpages + install-podman-remote-%-docs: podman-remote docs $(MANPAGES) rm -rf docs/build/remote mkdir -p docs/build/remote @@ -430,7 +434,7 @@ podman-remote-release-%.zip: cp release.txt "$(TMPDIR)/" cp ./bin/podman-remote-$*$(BINSFX) "$(TMPDIR)/$(SUBDIR)/podman$(BINSFX)" cp -r ./docs/build/remote/$* "$(TMPDIR)/$(SUBDIR)/docs/" - cd "$(TMPDIR)" && \ + cd "$(TMPDIR)/$(SUBDIR)" && \ zip --recurse-paths "$(CURDIR)/$@" "./release.txt" "./" -rm -rf "$(TMPDIR)" diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index d7731e983..7acbd6888 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -4,7 +4,6 @@ import ( "fmt" "reflect" "sort" - "strconv" "strings" "time" @@ -13,7 +12,6 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/adapter" - "github.com/containers/libpod/pkg/util" "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -29,8 +27,6 @@ const ( NUM_CTR_INFO = 10 ) -type PodFilter func(*adapter.Pod) bool - var ( bc_opts shared.PsOptions ) @@ -174,29 +170,23 @@ func podPsCmd(c *cliconfig.PodPsValues) error { opts.Format = genPodPsFormat(c) - var filterFuncs []PodFilter - if c.Filter != "" { - filters := strings.Split(c.Filter, ",") - for _, f := range filters { - filterSplit := strings.Split(f, "=") - if len(filterSplit) < 2 { - return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1]) - if err != nil { - return errors.Wrapf(err, "invalid filter") - } - filterFuncs = append(filterFuncs, generatedFunc) - } - } - var pods []*adapter.Pod + + // If latest is set true filters are ignored. if c.Latest { pod, err := runtime.GetLatestPod() if err != nil { return err } pods = append(pods, pod) + return generatePodPsOutput(pods, opts) + } + + if c.Filter != "" { + pods, err = runtime.GetPodsWithFilters(c.Filter) + if err != nil { + return err + } } else { pods, err = runtime.GetAllPods() if err != nil { @@ -204,19 +194,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { } } - podsFiltered := make([]*adapter.Pod, 0, len(pods)) - for _, pod := range pods { - include := true - for _, filter := range filterFuncs { - include = include && filter(pod) - } - - if include { - podsFiltered = append(podsFiltered, pod) - } - } - - return generatePodPsOutput(podsFiltered, opts) + return generatePodPsOutput(pods, opts) } // podPsCheckFlagsPassed checks if mutually exclusive flags are passed together @@ -235,88 +213,6 @@ func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error { return nil } -func generatePodFilterFuncs(filter, filterValue string) (func(pod *adapter.Pod) bool, error) { - switch filter { - case "ctr-ids": - return func(p *adapter.Pod) bool { - ctrIds, err := p.AllContainersByID() - if err != nil { - return false - } - return util.StringInSlice(filterValue, ctrIds) - }, nil - case "ctr-names": - return func(p *adapter.Pod) bool { - ctrs, err := p.AllContainers() - if err != nil { - return false - } - for _, ctr := range ctrs { - if filterValue == ctr.Name() { - return true - } - } - return false - }, nil - case "ctr-number": - return func(p *adapter.Pod) bool { - ctrIds, err := p.AllContainersByID() - if err != nil { - return false - } - - fVint, err2 := strconv.Atoi(filterValue) - if err2 != nil { - return false - } - return len(ctrIds) == fVint - }, nil - case "ctr-status": - if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { - return nil, errors.Errorf("%s is not a valid status", filterValue) - } - return func(p *adapter.Pod) bool { - ctr_statuses, err := p.Status() - if err != nil { - return false - } - for _, ctr_status := range ctr_statuses { - state := ctr_status.String() - if ctr_status == define.ContainerStateConfigured { - state = "created" - } - if state == filterValue { - return true - } - } - return false - }, nil - case "id": - return func(p *adapter.Pod) bool { - return strings.Contains(p.ID(), filterValue) - }, nil - case "name": - return func(p *adapter.Pod) bool { - return strings.Contains(p.Name(), filterValue) - }, nil - case "status": - if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { - return nil, errors.Errorf("%s is not a valid pod status", filterValue) - } - return func(p *adapter.Pod) bool { - status, err := p.GetPodStatus() - if err != nil { - return false - } - if strings.ToLower(status) == filterValue { - return true - } - return false - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} - // generate the template based on conditions given func genPodPsFormat(c *cliconfig.PodPsValues) string { format := "" diff --git a/cmd/podman/shared/pod.go b/cmd/podman/shared/pod.go index 7b0b497fc..3046953b5 100644 --- a/cmd/podman/shared/pod.go +++ b/cmd/podman/shared/pod.go @@ -2,9 +2,11 @@ package shared import ( "strconv" + "strings" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-connections/nat" "github.com/pkg/errors" @@ -134,4 +136,128 @@ func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { return portBindings, nil } +// GetPodsWithFilters uses the cliconfig to categorize if the latest pod is required. +func GetPodsWithFilters(r *libpod.Runtime, filters string) ([]*libpod.Pod, error) { + filterFuncs, err := GenerateFilterFunction(r, strings.Split(filters, ",")) + if err != nil { + return nil, err + } + return FilterAllPodsWithFilterFunc(r, filterFuncs...) +} + +// FilterAllPodsWithFilterFunc retrieves all pods +// Filters can be provided which will determine which pods are included in the +// output. Multiple filters are handled by ANDing their output, so only pods +// matching all filters are returned +func FilterAllPodsWithFilterFunc(r *libpod.Runtime, filters ...libpod.PodFilter) ([]*libpod.Pod, error) { + pods, err := r.Pods(filters...) + if err != nil { + return nil, err + } + return pods, nil +} + +// GenerateFilterFunction basically gets the filters based on the input by the user +// and filter the pod list based on the criteria. +func GenerateFilterFunction(r *libpod.Runtime, filters []string) ([]libpod.PodFilter, error) { + var filterFuncs []libpod.PodFilter + for _, f := range filters { + filterSplit := strings.Split(f, "=") + if len(filterSplit) < 2 { + return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1]) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter") + } + filterFuncs = append(filterFuncs, generatedFunc) + } + + return filterFuncs, nil +} +func generatePodFilterFuncs(filter, filterValue string) ( + func(pod *libpod.Pod) bool, error) { + switch filter { + case "ctr-ids": + return func(p *libpod.Pod) bool { + ctrIds, err := p.AllContainersByID() + if err != nil { + return false + } + return util.StringInSlice(filterValue, ctrIds) + }, nil + case "ctr-names": + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + for _, ctr := range ctrs { + if filterValue == ctr.Name() { + return true + } + } + return false + }, nil + case "ctr-number": + return func(p *libpod.Pod) bool { + ctrIds, err := p.AllContainersByID() + if err != nil { + return false + } + + fVint, err2 := strconv.Atoi(filterValue) + if err2 != nil { + return false + } + return len(ctrIds) == fVint + }, nil + case "ctr-status": + if !util.StringInSlice(filterValue, + []string{"created", "restarting", "running", "paused", + "exited", "unknown"}) { + return nil, errors.Errorf("%s is not a valid status", filterValue) + } + return func(p *libpod.Pod) bool { + ctr_statuses, err := p.Status() + if err != nil { + return false + } + for _, ctr_status := range ctr_statuses { + state := ctr_status.String() + if ctr_status == define.ContainerStateConfigured { + state = "created" + } + if state == filterValue { + return true + } + } + return false + }, nil + case "id": + return func(p *libpod.Pod) bool { + return strings.Contains(p.ID(), filterValue) + }, nil + case "name": + return func(p *libpod.Pod) bool { + return strings.Contains(p.Name(), filterValue) + }, nil + case "status": + if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { + return nil, errors.Errorf("%s is not a valid pod status", filterValue) + } + return func(p *libpod.Pod) bool { + status, err := p.GetPodStatus() + if err != nil { + return false + } + if strings.ToLower(status) == filterValue { + return true + } + return false + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + var DefaultKernelNamespaces = "cgroup,ipc,net,uts" diff --git a/hack/man-page-checker b/hack/man-page-checker index 528ff800a..17d85d65d 100755 --- a/hack/man-page-checker +++ b/hack/man-page-checker @@ -1,16 +1,6 @@ #!/bin/bash # -# man-page-name-checker - validate and cross-reference man page names -# -# FIXME as of 2019-03-20 there are still four files with inconsistent names: -# -# podman-logs.1.md NAME= podman-container-logs -# podman-info.1.md NAME= podman-system-info -# podman-rm.1.md NAME= podman-container-rm -# podman-rmi.1.md NAME= podman-image-rm -# -# If those four get renamed (with suitable symlink fixes), this script -# can be enabled in CI to prevent future inconsistencies. +# man-page-checker - validate and cross-reference man page names # die() { diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages new file mode 100755 index 000000000..00db3c8de --- /dev/null +++ b/hack/xref-helpmsgs-manpages @@ -0,0 +1,307 @@ +#!/usr/bin/perl +# +# xref-helpmsgs-manpages - cross-reference --help options against man pages +# +package LibPod::CI::XrefHelpmsgsManpages; + +use v5.14; +use utf8; + +use strict; +use warnings; + +(our $ME = $0) =~ s|.*/||; +our $VERSION = '0.1'; + +# For debugging, show data structures using DumpTree($var) +#use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0; + +############################################################################### +# BEGIN user-customizable section + +# Path to podman executable +my $Default_Podman = './bin/podman'; +my $PODMAN = $ENV{PODMAN} || $Default_Podman; + +# Path to podman markdown source files (of the form podman-*.1.md) +my $Markdown_Path = 'docs/source/markdown'; + +# END user-customizable section +############################################################################### + +use FindBin; + +############################################################################### +# BEGIN boilerplate args checking, usage messages + +sub usage { + print <<"END_USAGE"; +Usage: $ME [OPTIONS] + +$ME recursively runs 'podman --help' against +all subcommands; and recursively reads podman-*.1.md files +in $Markdown_Path, then cross-references that each --help +option is listed in the appropriate man page and vice-versa. + +$ME invokes '\$PODMAN' (default: $Default_Podman). + +Exit status is zero if no inconsistencies found, one otherwise + +OPTIONS: + + -v, --verbose show verbose progress indicators + -n, --dry-run make no actual changes + + --help display this message + --version display program name and version +END_USAGE + + exit; +} + +# Command-line options. Note that this operates directly on @ARGV ! +our $debug = 0; +our $verbose = 0; +sub handle_opts { + use Getopt::Long; + GetOptions( + 'debug!' => \$debug, + 'verbose|v' => \$verbose, + + help => \&usage, + version => sub { print "$ME version $VERSION\n"; exit 0 }, + ) or die "Try `$ME --help' for help\n"; +} + +# END boilerplate args checking, usage messages +############################################################################### + +############################## CODE BEGINS HERE ############################### + +# The term is "modulino". +__PACKAGE__->main() unless caller(); + +# Main code. +sub main { + # Note that we operate directly on @ARGV, not on function parameters. + # This is deliberate: it's because Getopt::Long only operates on @ARGV + # and there's no clean way to make it use @_. + handle_opts(); # will set package globals + + # Fetch command-line arguments. Barf if too many. + die "$ME: Too many arguments; try $ME --help\n" if @ARGV; + + my $help = podman_help(); + my $man = podman_man('podman'); + + my $retval = xref_by_help($help, $man) + + xref_by_man($help, $man); + + exit !!$retval; +} + +################## +# xref_by_help # Find keys in '--help' but not in man +################## +sub xref_by_help { + my ($help, $man, @subcommand) = @_; + my $errs = 0; + + for my $k (sort keys %$help) { + if (exists $man->{$k}) { + if (ref $help->{$k}) { + $errs += xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k); + } + # Otherwise, non-ref is leaf node such as a --option + } + else { + my $man = $man->{_path} || 'man'; + warn "$ME: podman @subcommand --help lists $k, but $k not in $man\n"; + ++$errs; + } + } + + return $errs; +} + +################# +# xref_by_man # Find keys in man pages but not in --help +################# +# +# In an ideal world we could share the functionality in one function; but +# there are just too many special cases in man pages. +# +sub xref_by_man { + my ($help, $man, @subcommand) = @_; + + my $errs = 0; + + # FIXME: this generates way too much output + for my $k (grep { $_ ne '_path' } sort keys %$man) { + if (exists $help->{$k}) { + if (ref $man->{$k}) { + $errs += xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); + } + } + elsif ($k ne '--help' && $k ne '-h') { + my $man = $man->{_path} || 'man'; + + # Special case: podman-inspect serves dual purpose (image, ctr) + my %ignore = map { $_ => 1 } qw(-l -s -t --latest --size --type); + next if $man =~ /-inspect/ && $ignore{$k}; + + # Special case: the 'trust' man page is a mess + next if $man =~ /-trust/; + + # Special case: '--net' is an undocumented shortcut + next if $k eq '--net' && $help->{'--network'}; + + # Special case: these are actually global options + next if $k =~ /^--(cni-config-dir|runtime)$/ && $man =~ /-build/; + + # Special case: weirdness with Cobra and global/local options + next if $k eq '--namespace' && $man =~ /-ps/; + + # Special case: these require compiling with 'varlink' tag, + # which doesn't happen in CI gating task. + next if $k eq 'varlink'; + next if "@subcommand" eq 'system' && $k eq 'service'; + + warn "$ME: podman @subcommand: $k in $man, but not --help\n"; + ++$errs; + } + } + + return $errs; +} + + +################# +# podman_help # Parse output of 'podman [subcommand] --help' +################# +sub podman_help { + my %help; + open my $fh, '-|', $PODMAN, @_, '--help' + or die "$ME: Cannot fork: $!\n"; + my $section = ''; + while (my $line = <$fh>) { + # Cobra is blessedly consistent in its output: + # Usage: ... + # Available Commands: + # .... + # Flags: + # .... + # + # Start by identifying the section we're in... + if ($line =~ /^Available\s+(Commands):/) { + $section = lc $1; + } + elsif ($line =~ /^(Flags):/) { + $section = lc $1; + } + + # ...then track commands and options. For subcommands, recurse. + elsif ($section eq 'commands') { + if ($line =~ /^\s{1,4}(\S+)\s/) { + my $subcommand = $1; + print "> podman @_ $subcommand\n" if $debug; + $help{$subcommand} = podman_help(@_, $subcommand) + unless $subcommand eq 'help'; # 'help' not in man + } + } + elsif ($section eq 'flags') { + # Handle '--foo' or '-f, --foo' + if ($line =~ /^\s{1,10}(--\S+)\s/) { + print "> podman @_ $1\n" if $debug; + $help{$1} = 1; + } + elsif ($line =~ /^\s{1,10}(-\S),\s+(--\S+)\s/) { + print "> podman @_ $1, $2\n" if $debug; + $help{$1} = $help{$2} = 1; + } + } + } + close $fh + or die "$ME: Error running 'podman @_ --help'\n"; + + return \%help; +} + + +################ +# podman_man # Parse contents of podman-*.1.md +################ +sub podman_man { + my $command = shift; + my $subpath = "$Markdown_Path/$command.1.md"; + my $manpath = "$FindBin::Bin/../$subpath"; + print "** $subpath \n" if $debug; + + my %man = (_path => $subpath); + open my $fh, '<', $manpath + or die "$ME: Cannot read $manpath: $!\n"; + my $section = ''; + my @most_recent_flags; + while (my $line = <$fh>) { + chomp $line; + next unless $line; # skip empty lines + + # .md files designate sections with leading double hash + if ($line =~ /^##\s*(GLOBAL\s+)?OPTIONS/) { + $section = 'flags'; + } + elsif ($line =~ /^\#\#\s+(SUB)?COMMANDS/) { + $section = 'commands'; + } + elsif ($line =~ /^\#\#/) { + $section = ''; + } + + # This will be a table containing subcommand names, links to man pages. + # The format is slightly different between podman.1.md and subcommands. + elsif ($section eq 'commands') { + # In podman.1.md + if ($line =~ /^\|\s*\[podman-(\S+?)\(\d\)\]/) { + $man{$1} = podman_man("podman-$1"); + } + + # In podman-<subcommand>.1.md + elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { + $man{$1} = podman_man($2); + } + } + + # Flags should always be of the form '**-f**' or '**--flag**', + # possibly separated by comma-space. + elsif ($section eq 'flags') { + # e.g. 'podman run --ip6', documented in man page, but nonexistent + if ($line =~ /^not\s+implemented/i) { + delete $man{$_} for @most_recent_flags; + } + + @most_recent_flags = (); + # Handle any variation of '**--foo**, **-f**' + while ($line =~ s/^\*\*((--[a-z0-9-]+)|(-.))\*\*(,\s+)?//g) { + $man{$1} = 1; + + # Keep track of them, in case we see 'Not implemented' below + push @most_recent_flags, $1; + } + } + } + close $fh; + + # Special case: the 'image trust' man page tries hard to cover both set + # and show, which means it ends up not being machine-readable. + if ($command eq 'podman-image-trust') { + my %set = %man; + my %show = %man; + $show{$_} = 1 for qw(--raw -j --json); + return +{ set => \%set, show => \%show } + } + + return \%man; +} + + +1; diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index e1dc31391..be566e211 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -90,18 +90,10 @@ func (r *Runtime) LookupPod(idOrName string) (*Pod, error) { // output. Multiple filters are handled by ANDing their output, so only pods // matching all filters are returned func (r *Runtime) Pods(filters ...PodFilter) ([]*Pod, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - if !r.valid { - return nil, define.ErrRuntimeStopped - } - - pods, err := r.state.AllPods() + pods, err := r.GetAllPods() if err != nil { return nil, err } - podsFiltered := make([]*Pod, 0, len(pods)) for _, pod := range pods { include := true diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index dc856cc8d..1417bd2b9 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -122,19 +122,31 @@ func (r *LocalRuntime) GetLatestPod() (*Pod, error) { return &pod, err } +// GetPodsWithFilters gets the filtered list of pods based on the filter parameters provided. +func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) { + pods, err := shared.GetPodsWithFilters(r.Runtime, filters) + if err != nil { + return nil, err + } + return r.podstoAdapterPods(pods) +} + +func (r *LocalRuntime) podstoAdapterPods(pod []*libpod.Pod) ([]*Pod, error) { + var pods []*Pod + for _, i := range pod { + + pods = append(pods, &Pod{i}) + } + return pods, nil +} + // GetAllPods gets all pods and wraps it in an adapter pod func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { - var pods []*Pod allPods, err := r.Runtime.GetAllPods() if err != nil { return nil, err } - for _, p := range allPods { - pod := Pod{} - pod.Pod = p - pods = append(pods, &pod) - } - return pods, nil + return r.podstoAdapterPods(allPods) } // LookupPod gets a pod by name or id and wraps it in an adapter pod diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 20f089628..6b8f22f15 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -10,7 +10,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/varlinkapi" @@ -208,6 +208,11 @@ func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { return pods, nil } +// This is a empty implementation stating remoteclient not yet implemented +func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) { + return nil, define.ErrNotImplemented +} + // GetPodsByStatus returns a slice of pods filtered by a libpod status func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*Pod, error) { podIDs, err := iopodman.GetPodsByStatus().Call(r.Conn, statuses) diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 1298e7fa4..e7031f471 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -87,7 +87,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if _, found := r.URL.Query()["limit"]; found { + if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 { last := query.Limit if len(containers) > last { containers = containers[len(containers)-last:] diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index f93c8f8d5..27ec64d89 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -103,7 +103,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { func Pods(w http.ResponseWriter, r *http.Request) { var ( - runtime = r.Context().Value("runtime").(*libpod.Runtime) podInspectData []*libpod.PodInspect ) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -118,12 +117,8 @@ func Pods(w http.ResponseWriter, r *http.Request) { return } - if len(query.Filters) > 0 { - utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) - return - } + pods, err := utils.GetPods(w, r) - pods, err := runtime.GetAllPods() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index d5a79bdc8..bbe4cee3c 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -16,7 +16,7 @@ import ( // ContainerCreateResponse is the response struct for creating a container type ContainerCreateResponse struct { // ID of the container created - ID string `json:"id"` + ID string `json:"Id"` // Warnings during container creation Warnings []string `json:"Warnings"` } diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go new file mode 100644 index 000000000..266ad9a4b --- /dev/null +++ b/pkg/api/handlers/utils/pods.go @@ -0,0 +1,45 @@ +package utils + +import ( + "fmt" + "net/http" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/gorilla/schema" +) + +func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + All bool + Filters map[string][]string `schema:"filters"` + Digests bool + }{} + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + return nil, err + } + var filters = []string{} + if _, found := r.URL.Query()["digests"]; found && query.Digests { + UnSupportedParameter("digests") + } + + if len(query.Filters) > 0 { + for k, v := range query.Filters { + for _, val := range v { + filters = append(filters, fmt.Sprintf("%s=%s", k, val)) + } + } + filterFuncs, err := shared.GenerateFilterFunction(runtime, filters) + if err != nil { + return nil, err + } + return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + } + + return runtime.GetAllPods() + +} diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 7e29265b7..f6c4d465d 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -76,15 +76,66 @@ var _ = Describe("Podman pods", func() { } Expect(StringInSlice(newpod, names)).To(BeTrue()) Expect(StringInSlice("newpod2", names)).To(BeTrue()) + }) - // TODO not working Because: code to list based on filter - // "not yet implemented", - // Validate list pod with filters - //filters := make(map[string][]string) - //filters["name"] = []string{newpod} - //filteredPods, err := pods.List(bt.conn, filters) - //Expect(err).To(BeNil()) - //Expect(len(filteredPods)).To(BeNumerically("==", 1)) + // The test validates the list pod endpoint with passing filters as the params. + It("List pods with filters", func() { + var newpod2 string = "newpod2" + bt.Podcreate(&newpod2) + _, err = bt.RunTopContainer(nil, &trueFlag, &newpod) + Expect(err).To(BeNil()) + + // Expected err with invalid filter params + filters := make(map[string][]string) + filters["dummy"] = []string{"dummy"} + filteredPods, err := pods.List(bt.conn, filters) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // Expected empty response with invalid filters + filters = make(map[string][]string) + filters["name"] = []string{"dummy"} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 0)) + + // Validate list pod with name filter + filters = make(map[string][]string) + filters["name"] = []string{newpod2} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + var names []string + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod2", names)).To(BeTrue()) + + // Validate list pod with id filter + filters = make(map[string][]string) + response, err := pods.Inspect(bt.conn, newpod) + id := response.Config.ID + filters["id"] = []string{id} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + names = names[:0] + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod", names)).To(BeTrue()) + + // Using multiple filters + filters["name"] = []string{newpod} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + names = names[:0] + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod", names)).To(BeTrue()) }) // The test validates if the exists responds diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index db898e706..6643bfbbf 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -108,10 +108,9 @@ do_pause () } static char ** -get_cmd_line_args (pid_t pid) +get_cmd_line_args () { int fd; - char path[PATH_MAX]; char *buffer; size_t allocated; size_t used = 0; @@ -119,11 +118,7 @@ get_cmd_line_args (pid_t pid) int i, argc = 0; char **argv; - if (pid) - sprintf (path, "/proc/%d/cmdline", pid); - else - strcpy (path, "/proc/self/cmdline"); - fd = open (path, O_RDONLY); + fd = open ("/proc/self/cmdline", O_RDONLY); if (fd < 0) return NULL; @@ -196,7 +191,7 @@ can_use_shortcut () return false; #endif - argv = get_cmd_line_args (0); + argv = get_cmd_line_args (); if (argv == NULL) return false; @@ -542,7 +537,6 @@ create_pause_process (const char *pause_pid_file_path, char **argv) int reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) { - pid_t ppid = getpid (); char uid[16]; char gid[16]; char **argv; @@ -559,7 +553,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) sprintf (uid, "%d", geteuid ()); sprintf (gid, "%d", getegid ()); - argv = get_cmd_line_args (ppid); + argv = get_cmd_line_args (); if (argv == NULL) { fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); @@ -724,7 +718,6 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re int ret; pid_t pid; char b; - pid_t ppid = getpid (); char **argv; char uid[16]; char gid[16]; @@ -801,7 +794,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re _exit (EXIT_FAILURE); } - argv = get_cmd_line_args (ppid); + argv = get_cmd_line_args (); if (argv == NULL) { fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); |