diff options
36 files changed, 493 insertions, 78 deletions
@@ -305,7 +305,7 @@ testunit: libpodimage ## Run unittest on the built image localunit: test/goecho/goecho varlink_generate hack/check_root.sh make localunit rm -rf ${COVERAGE_PATH} && mkdir -p ${COVERAGE_PATH} - ginkgo \ + $(GOBIN)/ginkgo \ -r \ $(TESTFLAGS) \ --skipPackage test/e2e,pkg/apparmor,test/endpoint,pkg/bindings,hack \ @@ -321,16 +321,16 @@ localunit: test/goecho/goecho varlink_generate .PHONY: ginkgo ginkgo: - ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 -debug test/e2e/. hack/. + $(GOBIN)/ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 -debug test/e2e/. hack/. .PHONY: ginkgo-remote ginkgo-remote: - ginkgo -v $(TESTFLAGS) -tags "$(REMOTETAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. + $(GOBIN)/ginkgo -v $(TESTFLAGS) -tags "$(REMOTETAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. .PHONY: endpoint ifneq (,$(findstring varlink,$(BUILDTAGS))) endpoint: - ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/. + $(GOBIN)/ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/. endpoint: endif diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 024045b9d..82a3513f5 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -5,10 +5,9 @@ import ( "os" "strings" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/cmd/podman/parse" "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/util" "github.com/pkg/errors" @@ -83,9 +82,10 @@ func saveFlags(flags *pflag.FlagSet) { } -func save(cmd *cobra.Command, args []string) error { +func save(cmd *cobra.Command, args []string) (finalErr error) { var ( - tags []string + tags []string + succeeded = false ) if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir && saveOpts.Format == "") { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") @@ -95,7 +95,22 @@ func save(cmd *cobra.Command, args []string) error { if terminal.IsTerminal(int(fi.Fd())) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } - saveOpts.Output = "/dev/stdout" + pipePath, cleanup, err := setupPipe() + if err != nil { + return err + } + if cleanup != nil { + defer func() { + errc := cleanup() + if succeeded { + writeErr := <-errc + if writeErr != nil && finalErr == nil { + finalErr = writeErr + } + } + }() + } + saveOpts.Output = pipePath } if err := parse.ValidateFileName(saveOpts.Output); err != nil { return err @@ -103,5 +118,9 @@ func save(cmd *cobra.Command, args []string) error { if len(args) > 1 { tags = args[1:] } - return registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) + err := registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) + if err == nil { + succeeded = true + } + return err } diff --git a/cmd/podman/images/utils_linux.go b/cmd/podman/images/utils_linux.go new file mode 100644 index 000000000..5521abab4 --- /dev/null +++ b/cmd/podman/images/utils_linux.go @@ -0,0 +1,47 @@ +package images + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// setupPipe for fixing https://github.com/containers/podman/issues/7017 +// uses named pipe since containers/image EvalSymlinks fails with /dev/stdout +// the caller should use the returned function to clean up the pipeDir +func setupPipe() (string, func() <-chan error, error) { + errc := make(chan error) + pipeDir, err := ioutil.TempDir(os.TempDir(), "pipeDir") + if err != nil { + return "", nil, err + } + pipePath := filepath.Join(pipeDir, "saveio") + err = unix.Mkfifo(pipePath, 0600) + if err != nil { + if e := os.RemoveAll(pipeDir); e != nil { + logrus.Errorf("error removing named pipe: %q", e) + } + return "", nil, errors.Wrapf(err, "error creating named pipe") + } + go func() { + fpipe, err := os.Open(pipePath) + if err != nil { + errc <- err + return + } + _, err = io.Copy(os.Stdout, fpipe) + fpipe.Close() + errc <- err + }() + return pipePath, func() <-chan error { + if e := os.RemoveAll(pipeDir); e != nil { + logrus.Errorf("error removing named pipe: %q", e) + } + return errc + }, nil +} diff --git a/cmd/podman/images/utils_unsupported.go b/cmd/podman/images/utils_unsupported.go new file mode 100644 index 000000000..69d1df786 --- /dev/null +++ b/cmd/podman/images/utils_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package images + +func setupPipe() (string, func() <-chan error, error) { + return "/dev/stdout", nil, nil +} diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 2aa7267c2..dd9c75ece 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -290,6 +290,7 @@ func resolveDestination() (string, string) { cfg, err := config.ReadCustomConfig() if err != nil { + logrus.Warning(errors.Wrap(err, "unable to read local containers.conf")) return registry.DefaultAPIAddress(), "" } diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 89cea10ca..af13b970c 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -124,6 +124,7 @@ func add(cmd *cobra.Command, args []string) error { cfg.Engine.ServiceDestinations = map[string]config.Destination{ args[0]: dst, } + cfg.Engine.ActiveService = args[0] } else { cfg.Engine.ServiceDestinations[args[0]] = dst } @@ -181,12 +182,20 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) } - config := &ssh.ClientConfig{ + if len(authMethods) == 0 { + pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) + if err != nil { + return "", err + } + authMethods = append(authMethods, ssh.Password(string(pass))) + } + + cfg := &ssh.ClientConfig{ User: uri.User.Username(), Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - dial, err := ssh.Dial("tcp", uri.Host, config) + dial, err := ssh.Dial("tcp", uri.Host, cfg) if err != nil { return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) } diff --git a/contrib/msi/podman.wxs b/contrib/msi/podman.wxs index c2c2cea4f..ff8160a53 100644 --- a/contrib/msi/podman.wxs +++ b/contrib/msi/podman.wxs @@ -24,8 +24,7 @@ <CreateFolder/> </Component> <Component Id="MainExecutable" Guid="73752F94-6589-4C7B-ABED-39D655A19714"> - <File Id="520C6E17-77A2-4F41-9611-30FA763A0702" Name="podman-remote-windows.exe" Source="bin/podman-remote-windows.exe"/> - <File Id="A14218A0-4180-44AC-B109-7C63B3099DCA" Name="podman.bat" Source="podman.bat" KeyPath="yes"/> + <File Id="520C6E17-77A2-4F41-9611-30FA763A0702" Name="podman.exe" Source="bin/podman-remote-windows.exe" KeyPath="yes"/> </Component> </Directory> </Directory> @@ -33,7 +32,7 @@ </Directory> <Property Id="setx" Value="setx.exe"/> - <CustomAction Id="ChangePath" ExeCommand="PATH "%PATH%;[INSTALLDIR] "" Property="setx" Execute="deferred" Impersonate="yes" Return="check"/> + <CustomAction Id="ChangePath" ExeCommand="PATH "%PATH%;[INSTALLDIR]"" Property="setx" Execute="deferred" Impersonate="yes" Return="check"/> <Feature Id="Complete" Level="1"> <ComponentRef Id="INSTALLDIR_Component"/> diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 2411eaabc..363aa60d7 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -91,6 +91,7 @@ Recommends: container-selinux Recommends: slirp4netns Recommends: fuse-overlayfs %endif +Recommends: xz # vendored libraries # awk '{print "Provides: bundled(golang("$1")) = "$2}' vendor.conf | sort diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst index a3ff24e89..096bdbedf 100644 --- a/docs/source/Commands.rst +++ b/docs/source/Commands.rst @@ -98,7 +98,7 @@ Commands :doc:`top <markdown/podman-top.1>` Display the running processes of a container -:doc:`umount <markdown/podman-umount.1>` Unmounts working container's root filesystem +:doc:`unmount <markdown/podman-unmount.1>` Unmounts working container's root filesystem :doc:`unpause <markdown/podman-unpause.1>` Unpause the processes in one or more containers diff --git a/docs/source/image.rst b/docs/source/image.rst index fe3a7aa3b..2b0ef3d43 100644 --- a/docs/source/image.rst +++ b/docs/source/image.rst @@ -18,7 +18,7 @@ Image :doc:`load <markdown/podman-load.1>` Load an image from container archive -:doc:`mount <markdown/podman-images-mount.1>` Mount an image's root filesystem. +:doc:`mount <markdown/podman-image-mount.1>` Mount an image's root filesystem. :doc:`prune <markdown/podman-image-prune.1>` Remove unused images @@ -40,6 +40,6 @@ Image :doc:`trust <markdown/podman-image-trust.1>` Manage container image trust policy -:doc:`untag <markdown/podman-untag.1>` Removes one or more names from a locally-stored image - :doc:`unmount <markdown/podman-unmount.1>` Unmount an image's root filesystem + +:doc:`untag <markdown/podman-untag.1>` Removes one or more names from a locally-stored image diff --git a/docs/source/managecontainers.rst b/docs/source/managecontainers.rst index 2e787c9e9..849fd1d25 100644 --- a/docs/source/managecontainers.rst +++ b/docs/source/managecontainers.rst @@ -37,10 +37,10 @@ Manage Containers :doc:`port <markdown/podman-port.1>` List port mappings or a specific mapping for the container -:doc:`restart <markdown/podman-restart.1>` Restart one or more containers - :doc:`prune <markdown/podman-container-prune.1>` Remove all stopped containers +:doc:`restart <markdown/podman-restart.1>` Restart one or more containers + :doc:`restore <markdown/podman-container-restore.1>` Restores one or more containers from a checkpoint :doc:`rm <markdown/podman-rm.1>` Remove one or more containers diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 5c58d59fc..9df76e48e 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -563,6 +563,7 @@ Valid values are: - `private`: create a new namespace for the container (default) - `slirp4netns[:OPTIONS,...]`: use slirp4netns to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default is false. + - **cidr=CIDR**: Specify ip range to use for this network. (Default is `10.0.2.0/24`). - **enable_ipv6=true|false**: Enable IPv6. Default is false. (Required for `outbound_addr6`). - **outbound_addr=INTERFACE**: Specify the outbound interface slirp should bind to (ipv4 traffic only). - **outbound_addr=IPv4**: Specify the outbound ipv4 address slirp should bind to. @@ -801,8 +802,8 @@ Run container in systemd mode. The default is *true*. The value *always* enforces the systemd mode is enforced without looking at the executable name. Otherwise, if set to true and the -command you are running inside the container is systemd, /usr/sbin/init -or /sbin/init. +command you are running inside the container is systemd, /usr/sbin/init, +/sbin/init or /usr/local/sbin/init. If the command you are running inside of the container is systemd, Podman will setup tmpfs mount points in the following directories: diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index db742e429..799cd1408 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -570,9 +570,15 @@ Valid _mode_ values are: - **ns:**_path_: path to a network namespace to join; - `private`: create a new namespace for the container (default) - **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default to false. + - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default is false. + - **cidr=CIDR**: Specify ip range to use for this network. (Default is `10.0.2.0/24`). + - **enable_ipv6=true|false**: Enable IPv6. Default is false. (Required for `outbound_addr6`). + - **outbound_addr=INTERFACE**: Specify the outbound interface slirp should bind to (ipv4 traffic only). + - **outbound_addr=IPv4**: Specify the outbound ipv4 address slirp should bind to. + - **outbound_addr6=INTERFACE**: Specify the outbound interface slirp should bind to (ipv6 traffic only). + - **outbound_addr6=IPv6**: Specify the outbound ipv6 address slirp should bind to. + - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. + - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. **--network-alias**=*alias* @@ -831,8 +837,8 @@ Run container in systemd mode. The default is **true**. The value *always* enforces the systemd mode is enforced without looking at the executable name. Otherwise, if set to **true** and the -command you are running inside the container is systemd, _/usr/sbin/init_ -or _/sbin/init_. +command you are running inside the container is systemd, _/usr/sbin/init_, +_/sbin/init_ or _/usr/local/sbin/init_. If the command you are running inside of the container is systemd Podman will setup tmpfs mount points in the following directories: diff --git a/docs/source/system.rst b/docs/source/system.rst index e3dfa9d01..566fd1a95 100644 --- a/docs/source/system.rst +++ b/docs/source/system.rst @@ -1,7 +1,7 @@ System ====== -:doc:`connection <markdown/podman-system-conection.1>` Manage the destination(s) for Podman service(s) +:doc:`connection <connection>` Manage the destination(s) for Podman service(s) :doc:`df <markdown/podman-system-df.1>` Show podman disk usage diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index 16b596589..7b617eed7 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -26,8 +26,14 @@ $| = 1; my $Default_Podman = './bin/podman'; my $PODMAN = $ENV{PODMAN} || $Default_Podman; +# Path to all doc files, including .rst and (down one level) markdown +my $Docs_Path = 'docs/source'; + # Path to podman markdown source files (of the form podman-*.1.md) -my $Markdown_Path = 'docs/source/markdown'; +my $Markdown_Path = "$Docs_Path/markdown"; + +# Global error count +my $Errs = 0; # END user-customizable section ############################################################################### @@ -96,35 +102,38 @@ sub main { my $help = podman_help(); my $man = podman_man('podman'); + my $rst = podman_rst(); + + xref_by_help($help, $man); + xref_by_man($help, $man); - my $retval = xref_by_help($help, $man) - + xref_by_man($help, $man); + xref_rst($help, $rst); - exit !!$retval; + exit !!$Errs; } +############################################################################### +# BEGIN cross-referencing + ################## # 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); + 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; + ++$Errs; } } - - return $errs; } ################# @@ -137,13 +146,11 @@ sub xref_by_help { 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); + xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); } } elsif ($k ne '--help' && $k ne '-h') { @@ -175,13 +182,38 @@ sub xref_by_man { next if "@subcommand" eq 'system' && $k eq 'service'; warn "$ME: podman @subcommand: $k in $man, but not --help\n"; - ++$errs; + ++$Errs; } } +} - return $errs; +############## +# xref_rst # Cross-check *.rst files against help +############## +sub xref_rst { + my ($help, $rst, @subcommand) = @_; + + # Cross-check against rst (but only subcommands, not options). + # We key on $help because that is Absolute Truth: anything in podman --help + # must be referenced in an rst (the converse is not true). + for my $k (sort grep { $_ !~ /^-/ } keys %$help) { + # Check for subcommands, if any (eg podman system -> connection -> add) + if (ref $help->{$k}) { + xref_rst($help->{$k}, $rst->{$k}, @subcommand, $k); + } + + # Check that command is mentioned in at least one .rst file + if (! exists $rst->{$k}{_desc}) { + my @podman = ("podman", @subcommand, $k); + warn "$ME: no link in *.rst for @podman\n"; + ++$Errs; + } + } } +# END cross-referencing +############################################################################### +# BEGIN data gathering ################# # podman_help # Parse output of 'podman [subcommand] --help' @@ -249,6 +281,7 @@ sub podman_man { or die "$ME: Cannot read $manpath: $!\n"; my $section = ''; my @most_recent_flags; + my $previous_subcmd = ''; while (my $line = <$fh>) { chomp $line; next unless $line; # skip empty lines @@ -278,6 +311,11 @@ sub podman_man { elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { # $1 will be changed by recursion _*BEFORE*_ left-hand assignment my $subcmd = $1; + if ($previous_subcmd gt $subcmd) { + warn "$ME: $subpath: '$previous_subcmd' and '$subcmd' are out of order\n"; + ++$Errs; + } + $previous_subcmd = $subcmd; $man{$subcmd} = podman_man($2); } } @@ -315,4 +353,76 @@ sub podman_man { } +################ +# podman_rst # Parse contents of docs/source/*.rst +################ +sub podman_rst { + my %rst; + + # Read all .rst files, looking for ":doc:`subcmd <target>` description" + for my $rst (glob "$Docs_Path/*.rst") { + open my $fh, '<', $rst + or die "$ME: Cannot read $rst: $!\n"; + + # The basename of foo.rst is usually, but not always, the name of + # a podman subcommand. There are a few special cases: + (my $command = $rst) =~ s!^.*/(.*)\.rst!$1!; + + my $subcommand_href = \%rst; + if ($command eq 'Commands') { + ; + } + elsif ($command eq 'managecontainers') { + $subcommand_href = $rst{container} //= { }; + } + elsif ($command eq 'connection') { + $subcommand_href = $rst{system}{connection} //= { }; + } + else { + $subcommand_href = $rst{$command} //= { }; + } + + my $previous_subcommand = ''; + while (my $line = <$fh>) { + if ($line =~ /^:doc:`(\S+)\s+<(.*?)>`\s+(.*)/) { + my ($subcommand, $target, $desc) = ($1, $2, $3); + + # Check that entries are in alphabetical order + if ($subcommand lt $previous_subcommand) { + warn "$ME: $rst:$.: '$previous_subcommand' and '$subcommand' are out of order\n"; + ++$Errs; + } + $previous_subcommand = $subcommand; + + # Mark this subcommand as documented. + $subcommand_href->{$subcommand}{_desc} = $desc; + + # Check for invalid links. These will be one of two forms: + # <markdown/foo.1> -> markdown/foo.1.md + # <foo> -> foo.rst + if ($target =~ m!^markdown/!) { + if (! -e "$Docs_Path/$target.md") { + warn "$ME: $rst:$.: '$subcommand' links to nonexistent $target\n"; + ++$Errs; + } + } + else { + if (! -e "$Docs_Path/$target.rst") { + warn "$ME: $rst:$.: '$subcommand' links to nonexistent $target.rst\n"; + } + } + } + } + close $fh; + } + + # Special case: 'image trust set/show' are documented in image-trust.1 + $rst{image}{trust}{$_} = { _desc => 'ok' } for (qw(set show)); + + return \%rst; +} + +# END data gathering +############################################################################### + 1; diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 9fb9738dc..fdee3877c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -626,7 +626,7 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro Destination: "/sys/fs/cgroup/systemd", Type: "bind", Source: "/sys/fs/cgroup/systemd", - Options: []string{"bind", "nodev", "noexec", "nosuid"}, + Options: []string{"bind", "nodev", "noexec", "nosuid", "rprivate"}, } g.AddMount(systemdMnt) g.AddLinuxMaskedPaths("/sys/fs/cgroup/systemd/release_agent") diff --git a/libpod/events/config.go b/libpod/events/config.go index c34408e63..bb35c03c0 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -101,6 +101,8 @@ const ( Attach Status = "attach" // AutoUpdate ... AutoUpdate Status = "auto-update" + // Build ... + Build Status = "build" // Checkpoint ... Checkpoint Status = "checkpoint" // Cleanup ... diff --git a/libpod/events/events.go b/libpod/events/events.go index 0253b1ee5..722c9595e 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -127,6 +127,8 @@ func StringToStatus(name string) (Status, error) { switch name { case Attach.String(): return Attach, nil + case Build.String(): + return Build, nil case Checkpoint.String(): return Checkpoint, nil case Cleanup.String(): diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index ed8f82c46..6f266e5d6 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -171,6 +171,7 @@ type slirpFeatures struct { HasMTU bool HasEnableSandbox bool HasEnableSeccomp bool + HasCIDR bool HasOutboundAddr bool HasIPv6 bool } @@ -199,6 +200,7 @@ func checkSlirpFlags(path string) (*slirpFeatures, error) { HasMTU: strings.Contains(string(out), "--mtu"), HasEnableSandbox: strings.Contains(string(out), "--enable-sandbox"), HasEnableSeccomp: strings.Contains(string(out), "--enable-seccomp"), + HasCIDR: strings.Contains(string(out), "--cidr"), HasOutboundAddr: strings.Contains(string(out), "--outbound-addr"), HasIPv6: strings.Contains(string(out), "--enable-ipv6"), }, nil @@ -227,6 +229,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { havePortMapping := len(ctr.Config().PortMappings) > 0 logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID)) + cidr := "" isSlirpHostForward := false disableHostLoopback := true enableIPv6 := false @@ -240,6 +243,12 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { option, value := parts[0], parts[1] switch option { + case "cidr": + ipv4, _, err := net.ParseCIDR(value) + if err != nil || ipv4.To4() == nil { + return errors.Errorf("invalid cidr %q", value) + } + cidr = value case "port_handler": switch value { case "slirp4netns": @@ -309,6 +318,13 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { cmdArgs = append(cmdArgs, "--enable-seccomp") } + if cidr != "" { + if !slirpFeatures.HasCIDR { + return errors.Errorf("cidr not supported") + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--cidr=%s", cidr)) + } + if enableIPv6 { if !slirpFeatures.HasIPv6 { return errors.Errorf("enable_ipv6 not supported") diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 4b5129f44..a95cd1d7a 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -17,6 +17,7 @@ import ( "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" @@ -150,9 +151,21 @@ func removeStorageContainers(ctrIDs []string, store storage.Store) error { return nil } +// newBuildEvent creates a new event based on completion of a built image +func (r *Runtime) newImageBuildCompleteEvent(idOrName string) { + e := events.NewEvent(events.Build) + e.Type = events.Image + e.Name = idOrName + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("unable to write build event: %q", err) + } +} + // Build adds the runtime to the imagebuildah call func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, dockerfiles ...string) (string, reference.Canonical, error) { id, ref, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...) + // Write event for build completion + r.newImageBuildCompleteEvent(id) return id, ref, err } diff --git a/nix/default.nix b/nix/default.nix index 4fe818b39..cc8786ce0 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -7,6 +7,15 @@ let libassuan = (static pkg.libassuan); libgpgerror = (static pkg.libgpgerror); libseccomp = (static pkg.libseccomp); + glib = (static pkg.glib).overrideAttrs(x: { + outputs = [ "bin" "out" "dev" ]; + mesonFlags = [ + "-Ddefault_library=static" + "-Ddevbindir=${placeholder ''dev''}/bin" + "-Dgtk_doc=false" + "-Dnls=disabled" + ]; + }); }; }; }); diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json index 8eeb4f470..976284ed4 100644 --- a/nix/nixpkgs.json +++ b/nix/nixpkgs.json @@ -1,7 +1,7 @@ { "url": "https://github.com/nixos/nixpkgs", - "rev": "b49e7987632e4c7ab3a093fdfc433e1826c4b9d7", - "date": "2020-07-26T09:18:52+02:00", - "sha256": "1mj6fy0p24izmasl653s5z4f2ka9v3b6mys45kjrqmkv889yk2r6", + "rev": "d6a445fe821052861b379d9b6c02d21623c25464", + "date": "2020-08-11T04:28:16+01:00", + "sha256": "064scwaxg8qg4xbmq07hag57saa4bhsb4pgg5h5vfs4nhhwvchg9", "fetchSubmodules": false } diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index e820e1c8b..ef9644de8 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -180,8 +180,9 @@ func pingNewConnection(ctx context.Context) error { } func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) { + // if you modify the authmethods or their conditionals, you will also need to make similar + // changes in the client (currently cmd/podman/system/connection/add getUDS). authMethods := []ssh.AuthMethod{} - if len(identity) > 0 { auth, err := terminal.PublicKey(identity, []byte(passPhrase)) if err != nil { @@ -205,6 +206,13 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) ( if pw, found := _url.User.Password(); found { authMethods = append(authMethods, ssh.Password(pw)) } + if len(authMethods) == 0 { + pass, err := terminal.ReadPassword("Login password:") + if err != nil { + return Connection{}, err + } + authMethods = append(authMethods, ssh.Password(string(pass))) + } callback := ssh.InsecureIgnoreHostKey() if secure { diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index c7bfdcd2b..b255c5da4 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" @@ -73,8 +74,16 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti } for i, layer := range results { - hold := entities.ImageHistoryLayer{} - _ = utils.DeepCopy(&hold, layer) + // Created time comes over as an int64 so needs conversion to time.time + t := time.Unix(layer.Created, 0) + hold := entities.ImageHistoryLayer{ + ID: layer.ID, + Created: t.UTC(), + CreatedBy: layer.CreatedBy, + Tags: layer.Tags, + Size: layer.Size, + Comment: layer.Comment, + } history.Layers[i] = hold } return &history, nil diff --git a/pkg/network/network.go b/pkg/network/network.go index b24c72f5f..db625da56 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -137,6 +137,15 @@ func networkIntersect(n1, n2 *net.IPNet) bool { // ValidateUserNetworkIsAvailable returns via an error if a network is available // to be used func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) error { + if len(userNet.IP) == 0 || len(userNet.Mask) == 0 { + return errors.Errorf("network %s's ip or mask cannot be empty", userNet.String()) + } + + ones, bit := userNet.Mask.Size() + if ones == 0 || bit == 0 { + return errors.Errorf("network %s's mask is invalid", userNet.String()) + } + networks, err := GetNetworksFromFilesystem(config) if err != nil { return err diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 1a1bb4526..8289e2089 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -142,11 +142,6 @@ func (s *SpecGenerator) Validate() error { return err } - // The following are defaults as needed by container creation - if len(s.WorkDir) < 1 { - s.WorkDir = "/" - } - // Set defaults if network info is not provided if s.NetNS.NSMode == "" { s.NetNS.NSMode = Bridge diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 65f8197bc..53d160442 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -135,15 +135,18 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.Annotations = annotations // workdir - if newImage != nil { - workingDir, err := newImage.WorkingDir(ctx) - if err != nil { - return nil, err - } - if len(s.WorkDir) < 1 && len(workingDir) > 1 { + if s.WorkDir == "" { + if newImage != nil { + workingDir, err := newImage.WorkingDir(ctx) + if err != nil { + return nil, err + } s.WorkDir = workingDir } } + if s.WorkDir == "" { + s.WorkDir = "/" + } if len(s.SeccompProfilePath) < 1 { p, err := libpod.DefaultSeccompPath() diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index b61ac2c30..fda4c098c 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -164,13 +164,19 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } if len(command) > 0 { - if command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd") { + useSystemdCommands := map[string]bool{ + "/sbin/init": true, + "/usr/sbin/init": true, + "/usr/local/sbin/init": true, + } + if useSystemdCommands[command[0]] || (filepath.Base(command[0]) == "systemd") { useSystemd = true } } default: return nil, errors.Wrapf(err, "invalid value %q systemd option requires 'true, false, always'", s.Systemd) } + logrus.Debugf("using systemd mode: %t", useSystemd) if useSystemd { // is StopSignal was not set by the user then set it to systemd // expected StopSigal @@ -241,13 +247,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. // If the user did not set an workdir but the image did, ensure it is // created. if s.WorkDir == "" && img != nil { - newWD, err := img.WorkingDir(ctx) - if err != nil { - return nil, err - } - if newWD != "" { - options = append(options, libpod.WithCreateWorkingDir()) - } + options = append(options, libpod.WithCreateWorkingDir()) } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 4352ef718..5e4cc3399 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -158,8 +158,9 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, configSpec.Linux.Seccomp = seccompConfig } - // Clear default Seccomp profile from Generator for privileged containers - if s.SeccompProfilePath == "unconfined" || s.Privileged { + // Clear default Seccomp profile from Generator for unconfined containers + // and privileged containers which do not specify a seccomp profile. + if s.SeccompProfilePath == "unconfined" || (s.Privileged && (s.SeccompProfilePath == config.SeccompOverridePath || s.SeccompProfilePath == config.SeccompDefaultPath)) { configSpec.Linux.Seccomp = nil } diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index 2d3e20f67..e9309a2d4 100644 --- a/pkg/varlinkapi/create.go +++ b/pkg/varlinkapi/create.go @@ -704,7 +704,12 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. if err != nil { return nil, errors.Wrapf(err, "cannot parse bool %s", c.String("systemd")) } - if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { + useSystemdCommands := map[string]bool{ + "/sbin/init": true, + "/usr/sbin/init": true, + "/usr/local/sbin/init": true, + } + if x && (useSystemdCommands[command[0]] || (filepath.Base(command[0]) == "systemd")) { systemd = true } } diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index fff3f3b1f..4c032c072 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -3,6 +3,32 @@ # network-related tests # -t GET /networks/non-existing-network 404 +t GET networks/non-existing-network 404 \ + .cause='network not found' + +if root; then + t POST libpod/networks/create?name=network1 '' 200 \ + .Filename~.*/network1\\.conflist + + # --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}}' + t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}' 200 \ + .Filename~.*/network2\\.conflist + + # test for empty mask + t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[]}' 500 \ + .cause~'.*cannot be empty' + # test for invalid mask + t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[0,255,255,0]}' 500 \ + .cause~'.*mask is invalid' + + # clean the network + t DELETE libpod/networks/network1 200 \ + .[0].Name~network1 \ + .[0].Err=null + t DELETE libpod/networks/network2 200 \ + .[0].Name~network2 \ + .[0].Err=null + +fi # vim: filetype=sh diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index d735217d6..83befe730 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -293,6 +293,22 @@ var _ = Describe("Podman run networking", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run slirp4netns network with different cidr", func() { + slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) + Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) + + networkConfiguration := "slirp4netns:cidr=192.168.0.0/24,allow_host_loopback=true" + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, ALPINE, "ping", "-c1", "192.168.0.2"}) + session.Wait(30) + + if strings.Contains(slirp4netnsHelp.OutputToString(), "cidr") { + Expect(session.ExitCode()).To(Equal(0)) + } else { + Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("cidr not supported")) + } + }) + It("podman run network bind to 127.0.0.1", func() { slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index dc44d3b3f..30e565894 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -193,22 +193,46 @@ var _ = Describe("Podman run", func() { Expect(conData[0].Config.Annotations["io.podman.annotations.init"]).To(Equal("FALSE")) }) - It("podman run seccomp test", func() { - + forbidGetCWDSeccompProfile := func() string { in := []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`) jsonFile, err := podmanTest.CreateSeccompJson(in) if err != nil { fmt.Println(err) Skip("Failed to prepare seccomp.json for test.") } + return jsonFile + } + + It("podman run seccomp test", func() { + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + match, _ := session.GrepString("Operation not permitted") + Expect(match).Should(BeTrue()) + }) - session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", jsonFile}, ""), ALPINE, "pwd"}) + It("podman run seccomp test --privileged", func() { + session := podmanTest.Podman([]string{"run", "-it", "--privileged", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"}) session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) match, _ := session.GrepString("Operation not permitted") Expect(match).Should(BeTrue()) }) + It("podman run seccomp test --privileged no profile should be unconfined", func() { + session := podmanTest.Podman([]string{"run", "-it", "--privileged", ALPINE, "grep", "Seccomp", "/proc/self/status"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("0")) + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman run seccomp test no profile should be default", func() { + session := podmanTest.Podman([]string{"run", "-it", ALPINE, "grep", "Seccomp", "/proc/self/status"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("2")) + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run capabilities test", func() { session := podmanTest.Podman([]string{"run", "--rm", "--cap-add", "all", ALPINE, "cat", "/proc/self/status"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_working_dir.go b/test/e2e/run_working_dir.go new file mode 100644 index 000000000..93330deba --- /dev/null +++ b/test/e2e/run_working_dir.go @@ -0,0 +1,69 @@ +package integration + +import ( + "os" + "strings" + + . "github.com/containers/podman/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman run", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman run a container without workdir", func() { + session := podmanTest.Podman([]string{"run", ALPINE, "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("/")) + }) + + It("podman run a container using non existing --workdir", func() { + if !strings.Contains(podmanTest.OCIRuntime, "crun") { + Skip("Test only works on crun") + } + session := podmanTest.Podman([]string{"run", "--workdir", "/home/foobar", ALPINE, "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(127)) + }) + + It("podman run a container on an image with a workdir", func() { + SkipIfRemote() + dockerfile := `FROM alpine +RUN mkdir -p /home/foobar +WORKDIR /etc/foobar` + podmanTest.BuildImage(dockerfile, "test", "false") + + session := podmanTest.Podman([]string{"run", "test", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("/etc/foobar")) + + session = podmanTest.Podman([]string{"run", "--workdir", "/home/foobar", "test", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("/home/foobar")) + }) +}) diff --git a/test/system/110-history.bats b/test/system/110-history.bats index b83e90fe4..5dc221d61 100644 --- a/test/system/110-history.bats +++ b/test/system/110-history.bats @@ -3,8 +3,6 @@ load helpers @test "podman history - basic tests" { - skip_if_remote "FIXME: pending #7122" - tests=" | .*[0-9a-f]\\\{12\\\} .* CMD .* LABEL --format '{{.ID}} {{.Created}}' | .*[0-9a-f]\\\{12\\\} .* ago diff --git a/test/system/120-load.bats b/test/system/120-load.bats index 2fcabcd8a..14dae4c8a 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -26,6 +26,16 @@ verify_iid_and_name() { is "$new_img_name" "$1" "Name & tag of restored image" } +@test "podman save to pipe and load" { + # We can't use run_podman because that uses the BATS 'run' function + # which redirects stdout and stderr. Here we need to guarantee + # that podman's stdout is a pipe, not any other form of redirection + $PODMAN save --format oci-archive $IMAGE | cat >$PODMAN_TMPDIR/test.tar + [ $status -eq 0 ] + + run_podman load -i $PODMAN_TMPDIR/test.tar +} + @test "podman load - by image ID" { # FIXME: how to build a simple archive instead? |