From 5e50ba3ecbdd738679849d7a86fef0c4ab7f109d Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Fri, 14 Aug 2020 11:19:02 +0200 Subject: podman support for IPv6 networks podman containers using IPv6 were missing the default route, breaking deployments trying to use them. The problem is that the default route was hardcoded to IPv4, this takes into consideration the podman subnet IP family to generate the corresponding default route. Signed-off-by: Antonio Ojea --- pkg/domain/infra/abi/network.go | 2 +- pkg/network/ip.go | 5 +++++ pkg/network/netconflist.go | 15 ++++++++++++--- pkg/network/netconflist_test.go | 38 ++++++++++++++++++++++++++++++++++++++ test/e2e/network_create_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 pkg/network/netconflist_test.go diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 26383129c..fd63fc80f 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -191,7 +191,7 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate var plugins []network.CNIPlugins var routes []network.IPAMRoute - defaultRoute, err := network.NewIPAMDefaultRoute() + defaultRoute, err := network.NewIPAMDefaultRoute(network.IsIPv6(subnet.IP)) if err != nil { return "", err } diff --git a/pkg/network/ip.go b/pkg/network/ip.go index 1798cd939..ba93a0d05 100644 --- a/pkg/network/ip.go +++ b/pkg/network/ip.go @@ -12,3 +12,8 @@ func CalcGatewayIP(ipn *net.IPNet) net.IP { nid := ipn.IP.Mask(ipn.Mask) return ip.NextIP(nid) } + +// IsIPv6 returns if netIP is IPv6. +func IsIPv6(netIP net.IP) bool { + return netIP != nil && netIP.To4() == nil +} diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go index 4271d3f54..8187fdb39 100644 --- a/pkg/network/netconflist.go +++ b/pkg/network/netconflist.go @@ -6,6 +6,11 @@ import ( "path/filepath" ) +const ( + defaultIPv4Route = "0.0.0.0/0" + defaultIPv6Route = "::/0" +) + // NcList describes a generic map type NcList map[string]interface{} @@ -86,9 +91,13 @@ func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer } // NewIPAMDefaultRoute creates a new IPAMDefault route of -// 0.0.0.0/0 -func NewIPAMDefaultRoute() (IPAMRoute, error) { - _, n, err := net.ParseCIDR("0.0.0.0/0") +// 0.0.0.0/0 for IPv4 or ::/0 for IPv6 +func NewIPAMDefaultRoute(isIPv6 bool) (IPAMRoute, error) { + route := defaultIPv4Route + if isIPv6 { + route = defaultIPv6Route + } + _, n, err := net.ParseCIDR(route) if err != nil { return IPAMRoute{}, err } diff --git a/pkg/network/netconflist_test.go b/pkg/network/netconflist_test.go new file mode 100644 index 000000000..a82a0140a --- /dev/null +++ b/pkg/network/netconflist_test.go @@ -0,0 +1,38 @@ +package network + +import ( + "reflect" + "testing" +) + +func TestNewIPAMDefaultRoute(t *testing.T) { + + tests := []struct { + name string + isIPv6 bool + want IPAMRoute + }{ + { + name: "IPv4 default route", + isIPv6: false, + want: IPAMRoute{defaultIPv4Route}, + }, + { + name: "IPv6 default route", + isIPv6: true, + want: IPAMRoute{defaultIPv6Route}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := NewIPAMDefaultRoute(tt.isIPv6) + if err != nil { + t.Errorf("no errorr expected: %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewIPAMDefaultRoute() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index a69004208..f635f3c6c 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -179,6 +179,47 @@ var _ = Describe("Podman network create", func() { Expect(subnet.Contains(containerIP)).To(BeTrue()) }) + It("podman network create with name and IPv6 subnet", func() { + SkipIfRemote() + var ( + results []network.NcList + ) + nc := podmanTest.Podman([]string{"network", "create", "--subnet", "fd00:1:2:3:4::/64", "newIPv6network"}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).To(BeZero()) + + defer podmanTest.removeCNINetwork("newIPv6network") + + // Inspect the network configuration + inspect := podmanTest.Podman([]string{"network", "inspect", "newIPv6network"}) + inspect.WaitWithDefaultTimeout() + + // JSON the network configuration into something usable + err := json.Unmarshal([]byte(inspect.OutputToString()), &results) + Expect(err).To(BeNil()) + result := results[0] + Expect(result["name"]).To(Equal("newIPv6network")) + + // JSON the bridge info + bridgePlugin, err := genericPluginsToBridge(result["plugins"], "bridge") + Expect(err).To(BeNil()) + Expect(bridgePlugin.IPAM.Routes[0].Dest).To(Equal("::/0")) + + // Once a container executes a new network, the nic will be created. We should clean those up + // best we can + defer removeNetworkDevice(bridgePlugin.BrName) + + try := podmanTest.Podman([]string{"run", "-it", "--rm", "--network", "newIPv6network", ALPINE, "sh", "-c", "ip addr show eth0 | grep global | awk ' /inet6 / {print $2}'"}) + try.WaitWithDefaultTimeout() + + _, subnet, err := net.ParseCIDR("fd00:1:2:3:4::/64") + Expect(err).To(BeNil()) + containerIP, _, err := net.ParseCIDR(try.OutputToString()) + Expect(err).To(BeNil()) + // Ensure that the IP the container got is within the subnet the user asked for + Expect(subnet.Contains(containerIP)).To(BeTrue()) + }) + It("podman network create with invalid subnet", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/17000", "fail"}) nc.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf From cb4c5fc9c262f14c935e843224ee80e098d58224 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 13 Aug 2020 12:53:28 +0200 Subject: podman.service: use sdnotiy Commit 2b6dd3fb4384 set the killmode of the podman.service to the systemd default which ultimately lead to the problem that systemd will kill *all* processes inside the unit's cgroup and hence kill all containers whenever the service is stopped. Fix it by setting the type to sdnotify and the killmode to process. `podman system service` will send the necessary notify messages when the NOTIFY_SOCKET is set and unset it right after to prevent the backend and container runtimes from jumping in between and send messages as well. Fixes: #7294 Signed-off-by: Valentin Rothberg --- contrib/systemd/system/podman.service | 3 +- pkg/api/server/server.go | 27 ++++++- .../coreos/go-systemd/v22/daemon/sdnotify.go | 84 ++++++++++++++++++++++ .../coreos/go-systemd/v22/daemon/watchdog.go | 73 +++++++++++++++++++ vendor/modules.txt | 1 + 5 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go create mode 100644 vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go diff --git a/contrib/systemd/system/podman.service b/contrib/systemd/system/podman.service index c8751168d..e14bbe078 100644 --- a/contrib/systemd/system/podman.service +++ b/contrib/systemd/system/podman.service @@ -6,5 +6,6 @@ Documentation=man:podman-system-service(1) StartLimitIntervalSec=0 [Service] -Type=simple +Type=notify +KillMode=process ExecStart=/usr/bin/podman system service diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 1c6007745..bfa5fcdce 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -2,6 +2,7 @@ package server import ( "context" + "fmt" "log" "net" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/containers/libpod/v2/pkg/api/handlers" "github.com/containers/libpod/v2/pkg/api/server/idletracker" "github.com/coreos/go-systemd/v22/activation" + "github.com/coreos/go-systemd/v22/daemon" "github.com/gorilla/mux" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -147,8 +149,31 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li return &server, nil } -// Serve starts responding to HTTP requests +// If the NOTIFY_SOCKET is set, communicate the PID and readiness, and +// further unset NOTIFY_SOCKET to prevent containers from sending +// messages and unset INVOCATION_ID so conmon and containers are in +// the correct cgroup. +func setupSystemd() { + if len(os.Getenv("NOTIFY_SOCKET")) == 0 { + return + } + payload := fmt.Sprintf("MAINPID=%d", os.Getpid()) + payload += "\n" + payload += daemon.SdNotifyReady + if sent, err := daemon.SdNotify(true, payload); err != nil { + logrus.Errorf("Error notifying systemd of Conmon PID: %s", err.Error()) + } else if sent { + logrus.Debugf("Notify sent successfully") + } + + if err := os.Unsetenv("INVOCATION_ID"); err != nil { + logrus.Errorf("Error unsetting INVOCATION_ID: %s", err.Error()) + } +} + +// Serve starts responding to HTTP requests. func (s *APIServer) Serve() error { + setupSystemd() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) errChan := make(chan error, 1) diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go new file mode 100644 index 000000000..ba4ae31f1 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go @@ -0,0 +1,84 @@ +// Copyright 2014 Docker, Inc. +// Copyright 2015-2018 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Package daemon provides a Go implementation of the sd_notify protocol. +// It can be used to inform systemd of service start-up completion, watchdog +// events, and other status changes. +// +// https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description +package daemon + +import ( + "net" + "os" +) + +const ( + // SdNotifyReady tells the service manager that service startup is finished + // or the service finished loading its configuration. + SdNotifyReady = "READY=1" + + // SdNotifyStopping tells the service manager that the service is beginning + // its shutdown. + SdNotifyStopping = "STOPPING=1" + + // SdNotifyReloading tells the service manager that this service is + // reloading its configuration. Note that you must call SdNotifyReady when + // it completed reloading. + SdNotifyReloading = "RELOADING=1" + + // SdNotifyWatchdog tells the service manager to update the watchdog + // timestamp for the service. + SdNotifyWatchdog = "WATCHDOG=1" +) + +// SdNotify sends a message to the init daemon. It is common to ignore the error. +// If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET` +// will be unconditionally unset. +// +// It returns one of the following: +// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset) +// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data) +// (true, nil) - notification supported, data has been sent +func SdNotify(unsetEnvironment bool, state string) (bool, error) { + socketAddr := &net.UnixAddr{ + Name: os.Getenv("NOTIFY_SOCKET"), + Net: "unixgram", + } + + // NOTIFY_SOCKET not set + if socketAddr.Name == "" { + return false, nil + } + + if unsetEnvironment { + if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil { + return false, err + } + } + + conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) + // Error connecting to NOTIFY_SOCKET + if err != nil { + return false, err + } + defer conn.Close() + + if _, err = conn.Write([]byte(state)); err != nil { + return false, err + } + return true, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go b/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go new file mode 100644 index 000000000..7a0e0d3a5 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go @@ -0,0 +1,73 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package daemon + +import ( + "fmt" + "os" + "strconv" + "time" +) + +// SdWatchdogEnabled returns watchdog information for a service. +// Processes should call daemon.SdNotify(false, daemon.SdNotifyWatchdog) every +// time / 2. +// If `unsetEnvironment` is true, the environment variables `WATCHDOG_USEC` and +// `WATCHDOG_PID` will be unconditionally unset. +// +// It returns one of the following: +// (0, nil) - watchdog isn't enabled or we aren't the watched PID. +// (0, err) - an error happened (e.g. error converting time). +// (time, nil) - watchdog is enabled and we can send ping. +// time is delay before inactive service will be killed. +func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) { + wusec := os.Getenv("WATCHDOG_USEC") + wpid := os.Getenv("WATCHDOG_PID") + if unsetEnvironment { + wusecErr := os.Unsetenv("WATCHDOG_USEC") + wpidErr := os.Unsetenv("WATCHDOG_PID") + if wusecErr != nil { + return 0, wusecErr + } + if wpidErr != nil { + return 0, wpidErr + } + } + + if wusec == "" { + return 0, nil + } + s, err := strconv.Atoi(wusec) + if err != nil { + return 0, fmt.Errorf("error converting WATCHDOG_USEC: %s", err) + } + if s <= 0 { + return 0, fmt.Errorf("error WATCHDOG_USEC must be a positive number") + } + interval := time.Duration(s) * time.Microsecond + + if wpid == "" { + return interval, nil + } + p, err := strconv.Atoi(wpid) + if err != nil { + return 0, fmt.Errorf("error converting WATCHDOG_PID: %s", err) + } + if os.Getpid() != p { + return 0, nil + } + + return interval, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 17c195326..a3d21b273 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -200,6 +200,7 @@ github.com/containers/storage/pkg/unshare github.com/coreos/go-iptables/iptables # github.com/coreos/go-systemd/v22 v22.1.0 github.com/coreos/go-systemd/v22/activation +github.com/coreos/go-systemd/v22/daemon github.com/coreos/go-systemd/v22/dbus github.com/coreos/go-systemd/v22/internal/dlopen github.com/coreos/go-systemd/v22/journal -- cgit v1.2.3-54-g00ecf From ceae3a99d54154fac6073f30bba2618768d05362 Mon Sep 17 00:00:00 2001 From: Jonathan Dieter Date: Tue, 11 Aug 2020 15:51:16 +0100 Subject: Fix hang when `path` doesn't exist I'm not sure if this is an OS-specific issue, but on CentOS 8, if `path` doesn't exist, this hangs while waiting to read from this socket, even though the socket is closed by the `reexec_in_user_namespace`. Switching to a pipe fixes the problem, and pipes shouldn't be an issue since this is Linux-specific code. Signed-off-by: Jonathan Dieter --- pkg/rootless/rootless_linux.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 2e580347d..fb9f3e156 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -363,14 +363,12 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st lastErr = nil break } else { - fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) + r, w, err := os.Pipe() if err != nil { lastErr = err continue } - r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file") - defer errorhandling.CloseQuiet(r) if _, _, err := becomeRootInUserNS("", path, w); err != nil { -- cgit v1.2.3-54-g00ecf From 34f4a892e4056dd660db85db6ee8baa0470dd6f2 Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Thu, 23 Jul 2020 15:54:12 -0400 Subject: podman save use named pipe podman save uses named pipe as output path, not directly using /dev/stdout. fix #7017 Signed-off-by: Qi Wang Signed-off-by: Matt Heon --- cmd/podman/images/save.go | 31 +++++++++++++++++----- cmd/podman/images/utils_linux.go | 47 ++++++++++++++++++++++++++++++++++ cmd/podman/images/utils_unsupported.go | 7 +++++ test/system/120-load.bats | 10 ++++++++ 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 cmd/podman/images/utils_linux.go create mode 100644 cmd/podman/images/utils_unsupported.go diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index f84d97b17..00eb9f1e6 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -5,10 +5,9 @@ import ( "os" "strings" - "github.com/containers/libpod/v2/libpod/define" - "github.com/containers/libpod/v2/cmd/podman/parse" "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/domain/entities" "github.com/containers/libpod/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/test/system/120-load.bats b/test/system/120-load.bats index 611799f8d..ec959ca73 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" { skip_if_remote "FIXME: pending #7123" -- cgit v1.2.3-54-g00ecf From c14b6c366288c875155d8bbb33c322843b438741 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 12 Aug 2020 09:15:02 -0400 Subject: Change /sys/fs/cgroup/systemd mount to rprivate I used the wrong propagation first time around because I forgot that rprivate is the default propagation. Oops. Switch to rprivate so we're using the default. Signed-off-by: Matthew Heon --- libpod/container_internal_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index d72e3ad47..b9e4f9a93 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -575,7 +575,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") -- cgit v1.2.3-54-g00ecf From c22ea20bf6f12ccf289a48cc185d5159ce7fdaf4 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Mon, 3 Aug 2020 12:53:33 -0500 Subject: add event for image build upon image build completion, a new image type event is written for "build". more intricate details, like pulling an image, that might be done by build must be implemented in different vendored packages only after libpod is split from podman. Fixes: #7022 Signed-off-by: Brent Baude Signed-off-by: Matt Heon --- libpod/events/config.go | 2 ++ libpod/events/events.go | 2 ++ libpod/runtime_img.go | 16 ++++++++++++++++ 3 files changed, 20 insertions(+) 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/runtime_img.go b/libpod/runtime_img.go index 7c75dbf98..1a82cdc07 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -11,7 +11,11 @@ import ( "github.com/containers/buildah/imagebuildah" "github.com/containers/image/v5/docker/reference" + ociarchive "github.com/containers/image/v5/oci/archive" + "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/types" "github.com/containers/libpod/v2/libpod/define" + "github.com/containers/libpod/v2/libpod/events" "github.com/containers/libpod/v2/libpod/image" "github.com/containers/libpod/v2/pkg/util" "github.com/containers/storage" @@ -147,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 } -- cgit v1.2.3-54-g00ecf From 09bd563e2bc544aa987e90c5d831f146198eebb5 Mon Sep 17 00:00:00 2001 From: zhangguanzhang Date: Sun, 9 Aug 2020 22:05:26 +0800 Subject: Add parameter verification for api creation network Signed-off-by: zhangguanzhang --- pkg/network/network.go | 9 +++++++++ test/apiv2/35-networks.at | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pkg/network/network.go b/pkg/network/network.go index 37f3f721a..4da6bc969 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/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 -- cgit v1.2.3-54-g00ecf From c539091bb0346cca036f0b7c4c4fe4423dcc66ee Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Thu, 6 Aug 2020 14:24:09 -0500 Subject: Replace deepcopy on history results the deepcopy in the remote history code path was throwing an uncaught error on a type mismatch. we now manually do the conversion and fix the type mismatch on the fly. Fixes: #7122 Signed-off-by: Brent Baude --- pkg/domain/infra/tunnel/images.go | 13 +++++++++++-- test/system/110-history.bats | 2 -- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 2e30621c5..2e027a6e1 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" @@ -65,8 +66,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/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 -- cgit v1.2.3-54-g00ecf From 76ae0c907591b60374febdd8bd41f90cbc5444b6 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 11 Aug 2020 11:29:49 +0200 Subject: Enable systemd mode for /usr/local/sbin/init Podman 1.6.2 changed systemd mode auto-detection from commands ending in ``init`` to hard-coded paths ``/sbin/init`` and ``/usr/sbin/init``. This broke FreeIPA container. ``podman run`` and ``podman create`` now activate systemd mode when the command is ``/usr/local/sbin/init``. Fixes: https://github.com/containers/podman/issues/7287 Signed-off-by: Christian Heimes --- docs/source/markdown/podman-create.1.md | 4 ++-- docs/source/markdown/podman-run.1.md | 4 ++-- pkg/specgen/generate/container_create.go | 3 ++- pkg/varlinkapi/create.go | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 715d6c1c9..ffce338b4 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -777,8 +777,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 fab648593..a500bfc41 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -809,8 +809,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/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index be1e3b48e..630e59854 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -153,13 +153,14 @@ 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") { + if command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || command[0] == "/usr/local/sbin/init" || (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 diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index ac93939d9..249505486 100644 --- a/pkg/varlinkapi/create.go +++ b/pkg/varlinkapi/create.go @@ -704,7 +704,7 @@ 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")) { + if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || command[0] == "/usr/local/sbin/init" || (filepath.Base(command[0]) == "systemd")) { systemd = true } } -- cgit v1.2.3-54-g00ecf From 7fb53bc240cea153fb054bcd307d3b1a8945a435 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 11 Aug 2020 13:29:17 +0200 Subject: Use set for systemd commands Signed-off-by: Christian Heimes --- pkg/specgen/generate/container_create.go | 7 ++++++- pkg/varlinkapi/create.go | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 630e59854..b31bc91e0 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -153,7 +153,12 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } if len(command) > 0 { - if command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || command[0] == "/usr/local/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 } } diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index 249505486..7661173b8 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" || command[0] == "/usr/local/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 } } -- cgit v1.2.3-54-g00ecf From 66fcafa4d45a26b59ad3662419cd3c778e23c39c Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Mon, 10 Aug 2020 10:16:28 +0200 Subject: Allow specifying seccomp profiles for privileged containers To sync the behavior between AppArmor and seccomp it is now possible to also specify seccomp profiles for privileged containers. Signed-off-by: Sascha Grunert --- pkg/specgen/generate/security.go | 5 +++-- test/e2e/run_test.go | 30 +++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index fcd1622f9..840dcb72d 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/test/e2e/run_test.go b/test/e2e/run_test.go index 95eecd042..13f0db550 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -183,22 +183,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() -- cgit v1.2.3-54-g00ecf From d4c3365454d903077ece3c1a31367f639ee24900 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 31 Jul 2020 17:08:06 -0400 Subject: Ensure WORKDIR from images is created A recent crun change stopped the creation of the container's working directory if it does not exist. This is arguably correct for user-specified directories, to protect against typos; it is definitely not correct for image WORKDIR, where the image author definitely intended for the directory to be used. This makes Podman create the working directory and chown it to container root, if it does not already exist, and only if it was specified by an image, not the user. Signed-off-by: Matthew Heon --- libpod/container.go | 4 ++++ libpod/container_internal_linux.go | 27 ++++++++++++++++++++++++++- libpod/options.go | 13 +++++++++++++ pkg/specgen/generate/container_create.go | 11 +++++++++++ test/e2e/run_test.go | 10 ++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/libpod/container.go b/libpod/container.go index 9ad938a5c..644647bc9 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -261,6 +261,10 @@ type ContainerConfig struct { Mounts []string `json:"mounts,omitempty"` // NamedVolumes lists the named volumes to mount into the container. NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` + // CreateWorkingDir indicates that Libpod should create the container's + // working directory if it does not exist. Some OCI runtimes do this by + // default, but others do not. + CreateWorkingDir bool `json:"createWorkingDir,omitempty"` // Security Config diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index b9e4f9a93..0d9a1c824 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -157,7 +157,32 @@ func (c *Container) prepare() error { } // Save changes to container state - return c.save() + if err := c.save(); err != nil { + return err + } + + // Ensure container entrypoint is created (if required) + if c.config.CreateWorkingDir { + workdir, err := securejoin.SecureJoin(c.state.Mountpoint, c.WorkingDir()) + if err != nil { + return errors.Wrapf(err, "error creating path to container %s working dir", c.ID()) + } + rootUID := c.RootUID() + rootGID := c.RootGID() + + if err := os.MkdirAll(workdir, 0755); err != nil { + if os.IsExist(err) { + return nil + } + return errors.Wrapf(err, "error creating container %s working dir", c.ID()) + } + + if err := os.Chown(workdir, rootUID, rootGID); err != nil { + return errors.Wrapf(err, "error chowning container %s working directory to container root", c.ID()) + } + } + + return nil } // cleanupNetwork unmounts and cleans up the container's network diff --git a/libpod/options.go b/libpod/options.go index 560b406e2..a4e4b99e9 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1395,6 +1395,19 @@ func WithCreateCommand(cmd []string) CtrCreateOption { } } +// WithCreateWorkingDir tells Podman to create the container's working directory +// if it does not exist. +func WithCreateWorkingDir() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.CreateWorkingDir = true + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index b31bc91e0..42be5e812 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -215,6 +215,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Entrypoint != nil { options = append(options, libpod.WithEntrypoint(s.Entrypoint)) } + // 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()) + } + } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) } diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 13f0db550..ef275b32e 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1060,4 +1060,14 @@ USER mail` Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring(limit)) }) + + It("podman run makes entrypoint from image", func() { + dockerfile := `FROM busybox +WORKDIR /madethis` + podmanTest.BuildImage(dockerfile, "test", "false") + session := podmanTest.Podman([]string{"run", "--rm", "test", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("/madethis")) + }) }) -- cgit v1.2.3-54-g00ecf From 32f0c8f624e0400a6decf219d6fe889f12272729 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 7 Aug 2020 13:57:02 -0400 Subject: Do not use image CMD if user gave ENTRYPOINT This matches Docker behavior, and seems to make sense - the CMD may have been specific to the original entrypoint and probably does not make sense if it was changed. While we're in here, greatly simplify the logic for populating the SpecGen's Command. We create the full command when making the OCI spec, so the client should not be doing any more than setting it to the Command the user passed in, and completely ignoring ENTRYPOINT. Fixes #7115 Signed-off-by: Matthew Heon --- cmd/podman/common/specgen.go | 18 +----------------- pkg/specgen/generate/oci.go | 4 +++- test/e2e/run_test.go | 11 +++++------ 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 2333f2f7e..48a2069ff 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -387,8 +387,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Annotations = annotations s.WorkDir = c.Workdir - userCommand := []string{} - var command []string if c.Entrypoint != nil { entrypoint := []string{} if ep := *c.Entrypoint; len(ep) > 0 { @@ -398,27 +396,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } } s.Entrypoint = entrypoint - // Build the command - // If we have an entry point, it goes first - command = entrypoint } // Include the command used to create the container. s.ContainerCreateCommand = os.Args if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - userCommand = append(userCommand, inputCommand...) - } - - switch { - case len(inputCommand) > 0: - s.Command = userCommand - case c.Entrypoint != nil: - s.Command = []string{} - default: - s.Command = command + s.Command = inputCommand } // SHM Size diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f279aac1c..f1c9f2a1a 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -96,8 +96,10 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand = append(finalCommand, entrypoint...) + // Only use image command if the user did not manually set an + // entrypoint. command := s.Command - if command == nil && img != nil { + if command == nil && img != nil && s.Entrypoint == nil { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index ef275b32e..681498ea1 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1061,13 +1061,12 @@ USER mail` Expect(session.OutputToString()).To(ContainSubstring(limit)) }) - It("podman run makes entrypoint from image", func() { - dockerfile := `FROM busybox -WORKDIR /madethis` - podmanTest.BuildImage(dockerfile, "test", "false") - session := podmanTest.Podman([]string{"run", "--rm", "test", "pwd"}) + It("podman run --entrypoint does not use image command", func() { + session := podmanTest.Podman([]string{"run", "--entrypoint", "/bin/echo", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(ContainSubstring("/madethis")) + // We can't guarantee the output is completely empty, some + // nonprintables seem to work their way in. + Expect(session.OutputToString()).To(Not(ContainSubstring("/bin/sh"))) }) }) -- cgit v1.2.3-54-g00ecf From fc24c0cc102de911387ed6b768173604fecb1eee Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 5 Aug 2020 16:28:47 -0400 Subject: Fix handling of working dir Buildah and podman build can create images without a working dir. FROM fedora WORKDIR /test If you build this image with caching twice, the second time the image will not have a working dir. Similarly if you execute podman run --workdir /foobar fedora It blows up since the workingdir is not created automatically. Finally there was duplicated code for getting the workingdir out of an image, that this PR removes. Signed-off-by: Daniel J Walsh --- pkg/specgen/container_validate.go | 5 --- pkg/specgen/generate/container.go | 15 ++++--- pkg/specgen/generate/container_create.go | 8 +--- test/e2e/run_working_dir.go | 69 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 test/e2e/run_working_dir.go diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 57dd2aba7..a979a7f4a 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -135,11 +135,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 f0d52d0c3..59fee80f1 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 42be5e812..6c0a702d6 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -218,13 +218,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/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")) + }) +}) -- cgit v1.2.3-54-g00ecf From 4f349554adf4811d1081f55c2ba90952c57475e6 Mon Sep 17 00:00:00 2001 From: Parker Van Roy Date: Mon, 10 Aug 2020 14:37:12 -0400 Subject: Error pass through for more accurate error reporting Included old error + wrapped Signed-off-by: Parker Van Roy --- pkg/domain/infra/abi/images.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 5f19f416a..a4dc92820 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -108,7 +108,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti if err != nil { imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) if err != nil { - return nil, errors.Errorf("invalid image reference %q", rawImage) + return nil, errors.Wrapf(err, "invalid image reference %q", rawImage) } } -- cgit v1.2.3-54-g00ecf From 35d2db807239eb193edd0d775a95aadb3279f01f Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Wed, 5 Aug 2020 15:10:12 -0700 Subject: Default .Repository and .Tag values to Refactor the processing of Repository and Tag fields to default to when printing via --format flag. Previously, the default format would print but --format {{.Tag}} would not in some cases. Fixes #7123 Signed-off-by: Jhon Honce --- cmd/podman/images/list.go | 18 +++++++++++++----- test/system/120-load.bats | 2 -- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index ea88b519b..60af5c847 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -195,6 +195,7 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { } else { h.ImageSummary = *e h.Repository = "" + h.Tag = "" imgs = append(imgs, h) } listFlag.readOnly = e.IsReadOnly() @@ -205,27 +206,34 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { } func tokenRepoTag(ref string) (string, string, error) { - if ref == ":" { return "", "", nil } repo, err := reference.Parse(ref) if err != nil { - return "", "", err + return "", "", err } named, ok := repo.(reference.Named) if !ok { - return ref, "", nil + return ref, "", nil + } + name := named.Name() + if name == "" { + name = "" } tagged, ok := repo.(reference.Tagged) if !ok { - return named.Name(), "", nil + return name, "", nil + } + tag := tagged.Tag() + if tag == "" { + tag = "" } - return named.Name(), tagged.Tag(), nil + return name, tag, nil } diff --git a/test/system/120-load.bats b/test/system/120-load.bats index ec959ca73..310ee55de 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -38,8 +38,6 @@ verify_iid_and_name() { @test "podman load - by image ID" { - skip_if_remote "FIXME: pending #7123" - # FIXME: how to build a simple archive instead? get_iid_and_name -- cgit v1.2.3-54-g00ecf From eff0c29098fe988327112c37884d57a7b244ac40 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 4 Aug 2020 15:51:13 -0400 Subject: Unconditionally retrieve pod names via API The ListContainers API previously had a Pod parameter, which determined if pod name was returned (but, notably, not Pod ID, which was returned unconditionally). This was fairly confusing, so we decided to deprecate/remove the parameter and return it unconditionally. To do this without serious performance implications, we need to avoid expensive JSON decodes of pod configuration in the DB. The way our Bolt tables are structured, retrieving name given ID is actually quite cheap, but we did not expose this via the Libpod API. Add a new GetName API to do this. Fixes #7214 Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 55 +++++++++++++++++++++++++++++++++++ libpod/in_memory_state.go | 30 +++++++++++++++++++ libpod/runtime.go | 16 ++++++++++ libpod/state.go | 6 ++++ pkg/api/handlers/libpod/containers.go | 3 +- pkg/api/server/register_containers.go | 3 +- pkg/bindings/containers/containers.go | 5 +--- pkg/bindings/test/containers_test.go | 19 ++++++++++-- pkg/domain/infra/tunnel/containers.go | 2 +- pkg/domain/infra/tunnel/helpers.go | 2 +- pkg/ps/ps.go | 7 +++-- 11 files changed, 134 insertions(+), 14 deletions(-) diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 38881d3e4..4e2630526 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -424,6 +424,61 @@ func (s *BoltState) SetNamespace(ns string) error { return nil } +// GetName returns the name associated with a given ID. Since IDs are globally +// unique, it works for both containers and pods. +// Returns ErrNoSuchCtr if the ID does not exist. +func (s *BoltState) GetName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + idBytes := []byte(id) + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + name := "" + + err = db.View(func(tx *bolt.Tx) error { + idBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + nameBytes := idBkt.Get(idBytes) + if nameBytes == nil { + return define.ErrNoSuchCtr + } + + if s.namespaceBytes != nil { + nsBkt, err := getNSBucket(tx) + if err != nil { + return err + } + + idNs := nsBkt.Get(idBytes) + if !bytes.Equal(idNs, s.namespaceBytes) { + return define.ErrNoSuchCtr + } + } + + name = string(nameBytes) + return nil + }) + if err != nil { + return "", err + } + + return name, nil +} + // Container retrieves a single container from the state by its full ID func (s *BoltState) Container(id string) (*Container, error) { if id == "" { diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 794212bf0..b0eae0992 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -106,6 +106,36 @@ func (s *InMemoryState) SetNamespace(ns string) error { return nil } +// GetName retrieves the name associated with a given ID. +// Works with both Container and Pod IDs. +func (s *InMemoryState) GetName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + var idIndex *truncindex.TruncIndex + if s.namespace != "" { + nsIndex, ok := s.namespaceIndexes[s.namespace] + if !ok { + // We have no containers in the namespace + // Return false + return "", define.ErrNoSuchCtr + } + idIndex = nsIndex.idIndex + } else { + idIndex = s.idIndex + } + + fullID, err := idIndex.Get(id) + if err != nil { + if err == truncindex.ErrNotExist { + return "", define.ErrNoSuchCtr + } + return "", errors.Wrapf(err, "error performing truncindex lookup for ID %s", id) + } + return fullID, nil +} + // Container retrieves a container from its full ID func (s *InMemoryState) Container(id string) (*Container, error) { if id == "" { diff --git a/libpod/runtime.go b/libpod/runtime.go index 24370d50e..867644be7 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -732,6 +732,22 @@ func (r *Runtime) GetStore() storage.Store { return r.store } +// GetName retrieves the name associated with a given full ID. +// This works for both containers and pods, and does not distinguish between the +// two. +// If the given ID does not correspond to any existing Pod or Container, +// ErrNoSuchCtr is returned. +func (r *Runtime) GetName(id string) (string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return "", define.ErrRuntimeStopped + } + + return r.state.GetName(id) +} + // DBConfig is a set of Libpod runtime configuration settings that are saved in // a State when it is first created, and can subsequently be retrieved. type DBConfig struct { diff --git a/libpod/state.go b/libpod/state.go index 6206a2994..44632b02f 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -43,6 +43,12 @@ type State interface { // containers and pods in all namespaces will be returned. SetNamespace(ns string) error + // Resolve an ID into a Name. Since Podman names and IDs are globally + // unique between Pods and Containers, the ID may belong to either a pod + // or container. Despite this, we will always return ErrNoSuchCtr if the + // ID does not exist. + GetName(id string) (string, error) + // Return a container from the database from its full ID. // If the container is not in the set namespace, an error will be // returned. diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 2303ff17a..abfc79a0b 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -39,7 +39,6 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { Filters map[string][]string `schema:"filters"` Last int `schema:"last"` Namespace bool `schema:"namespace"` - Pod bool `schema:"pod"` Size bool `schema:"size"` Sync bool `schema:"sync"` }{ @@ -59,7 +58,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { Size: query.Size, Sort: "", Namespace: query.Namespace, - Pod: query.Pod, + Pod: true, Sync: query.Sync, } pss, err := ps.GetContainerLists(runtime, opts) diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 18ff2f423..a0c9ac574 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -661,11 +661,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // type: boolean // description: Include namespace information // default: false - // - in: query // name: pod // type: boolean // default: false - // description: Include Pod ID and Name if applicable + // description: Ignored. Previously included details on pod name and ID that are currently included by default. // - in: query // name: size // type: boolean diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index c479e5dcb..d7e67567b 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -24,7 +24,7 @@ var ( // the most recent number of containers. The pod and size booleans indicate that pod information and rootfs // size information should also be included. Finally, the sync bool synchronizes the OCI runtime and // container state. -func List(ctx context.Context, filters map[string][]string, all *bool, last *int, pod, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck +func List(ctx context.Context, filters map[string][]string, all *bool, last *int, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -37,9 +37,6 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int if last != nil { params.Set("last", strconv.Itoa(*last)) } - if pod != nil { - params.Set("pod", strconv.FormatBool(*pod)) - } if size != nil { params.Set("size", strconv.FormatBool(*size)) } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 4b2c78353..0685a3377 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -509,7 +509,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil) Expect(err).To(BeNil()) - containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil, nil) + containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil) Expect(err).To(BeNil()) err = containers.Kill(bt.conn, containerLatestList[0].Names[0], "SIGTERM") Expect(err).To(BeNil()) @@ -754,8 +754,23 @@ var _ = Describe("Podman containers ", func() { // Validate list container with id filter filters := make(map[string][]string) filters["id"] = []string{cid} - c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil, nil) + c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil) Expect(err).To(BeNil()) Expect(len(c)).To(Equal(1)) }) + + It("List containers always includes pod information", func() { + podName := "testpod" + ctrName := "testctr" + bt.Podcreate(&podName) + _, err := bt.RunTopContainer(&ctrName, bindings.PTrue, &podName) + Expect(err).To(BeNil()) + + lastNum := 1 + + c, err := containers.List(bt.conn, nil, bindings.PTrue, &lastNum, nil, nil) + Expect(err).To(BeNil()) + Expect(len(c)).To(Equal(1)) + Expect(c[0].PodName).To(Equal(podName)) + }) }) diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 8835248ca..dd72064a3 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -496,7 +496,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) { - return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Pod, &options.Size, &options.Sync) + return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Size, &options.Sync) } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 9974c4d1d..90e52c5c0 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -20,7 +20,7 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam if all && len(namesOrIDs) > 0 { return nil, errors.New("cannot lookup containers and all") } - c, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, nil, bindings.PTrue) + c, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, bindings.PTrue) if err != nil { return nil, err } diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 2b81311af..d4fd96596 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -175,11 +175,14 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities State: conState.String(), } if opts.Pod && len(conConfig.Pod) > 0 { - pod, err := rt.GetPod(conConfig.Pod) + podName, err := rt.GetName(conConfig.Pod) if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + return entities.ListContainer{}, errors.Wrapf(define.ErrNoSuchPod, "could not find container %s pod (id %s) in state", conConfig.ID, conConfig.Pod) + } return entities.ListContainer{}, err } - ps.PodName = pod.Name() + ps.PodName = podName } if opts.Namespace { -- cgit v1.2.3-54-g00ecf From a8a3325445fe2120896be997f74e426b36f11b9c Mon Sep 17 00:00:00 2001 From: TomSweeneyRedHat Date: Thu, 23 Jul 2020 19:27:33 -0400 Subject: [CI:DOCS] BZ1860126 - Fix userns defaults in run man page Addresses the multiple "default" userns values found in the podman-run(1) man page: http://docs.podman.io/en/latest/markdown/podman-run.1.html. This in response to: https://bugzilla.redhat.com/show_bug.cgi?id=1860126 which this PR wil fix. Signed-off-by: TomSweeneyRedHat --- docs/source/markdown/podman-run.1.md | 10 ++++++---- pkg/namespaces/namespaces.go | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index a500bfc41..25bb44c06 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -875,20 +875,22 @@ Ulimit options. You can use **host** to copy the current configuration from the Sets the username or UID used and optionally the groupname or GID for the specified command. -Without this argument the command will be run as root in the container. +Without this argument, the command will run as the user specified in the container image. Unless overridden by a `USER` command in the Containerfile or by a value passed to this option, this user generally defaults to root. + +When a user namespace is not in use, the UID and GID used within the container and on the host will match. When user namespaces are in use, however, the UID and GID in the container may correspond to another UID and GID on the host. In rootless containers, for example, a user namespace is always used, and root in the container will by default correspond to the UID and GID of the user invoking Podman. **--userns**=**auto**|**host**|**keep-id**|**container:**_id_|**ns:**_namespace_ -Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value means user namespaces are disabled. +Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with they `--uidmapping` and `--gidmapping` options. - **auto**: automatically create a namespace. It is possible to specify other options to `auto`. The supported options are **size=SIZE** to specify an explicit size for the automatic user namespace. e.g. `--userns=auto:size=8192`. If `size` is not specified, `auto` will guess a size for the user namespace. **uidmapping=HOST_UID:CONTAINER_UID:SIZE** to force a UID mapping to be present in the user namespace. **gidmapping=HOST_UID:CONTAINER_UID:SIZE** to force a GID mapping to be present in the user namespace. -- **host**: run in the user namespace of the caller. This is the default if no user namespace options are set. The processes running in the container will have the same privileges on the host as any other process launched by the calling user. +- **host**: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). - **keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. - **ns**: run the container in the given existing user namespace. -- **private**: create a new namespace for the container (default) +- **private**: create a new namespace for the container. - **container**: join the user namespace of the specified container. This option is incompatible with **--gidmap**, **--uidmap**, **--subuid** and **--subgid**. diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 2ffbde977..c2f255530 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -91,7 +91,7 @@ func (n UsernsMode) IsHost() bool { return n == hostType } -// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is lept inside of the namespace. +// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is kept inside of the namespace. func (n UsernsMode) IsKeepID() bool { return n == "keep-id" } -- cgit v1.2.3-54-g00ecf From dbcb6f5892852ecf846faeef10d9b0237c472f99 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 17 Aug 2020 15:55:54 -0400 Subject: Update release notes for v2.0.5 Signed-off-by: Matthew Heon --- RELEASE_NOTES.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 017a9c744..9a609ff0d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,51 @@ # Release Notes +## 2.0.5 +### Features +- Rootless Podman will now add an entry to `/etc/passwd` for the user who ran Podman if run with `--userns=keep-id`. + +### Changes +- Podman's automatic systemd integration (activated by the `--systemd=true` flag, set by default) will now activate for containers using `/usr/local/sbin/init` as their command, instead of just `/usr/sbin/init` and `/sbin/init` (and any path ending in `systemd`). +- Seccomp profiles specified by the `--security-opt seccomp=...` flag to `podman create` and `podman run` will now be honored even if the container was created using `--privileged`. + +### Bugfixes +- Fixed a bug where the `podman play kube` would not honor the `hostIP` field for port forwarding ([#5964](https://github.com/containers/podman/issues/5964)). +- Fixed a bug where the `podman generate systemd` command would panic on an invalid restart policy being specified ([#7271](https://github.com/containers/podman/issues/7271)). +- Fixed a bug where the `podman images` command could take a very long time (several minutes) to complete when a large number of images were present. +- Fixed a bug where the `podman logs` command with the `--tail` flag would not work properly when a large amount of output would be printed ((#7230)[https://github.com/containers/podman/issues/7230]). +- Fixed a bug where the `podman exec` command with remote Podman would not return a non-zero exit code when the exec session failed to start (e.g. invoking a non-existent command) ([#6893](https://github.com/containers/podman/issues/6893)). +- Fixed a bug where the `podman load` command with remote Podman would did not honor user-specified tags ([#7124](https://github.com/containers/podman/issues/7124)). +- Fixed a bug where the `podman system service` command, when run as a non-root user by Systemd, did not properly handle the Podman pause process and would not restart properly as a result ([#7180](https://github.com/containers/podman/issues/7180)). +- Fixed a bug where the `--publish` flag to `podman create`, `podman run`, and `podman pod create` did not properly handle a host IP of 0.0.0.0 (attempting to bind to literal 0.0.0.0, instead of all IPs on the system) ([#7104](https://github.com/containers/podman/issues/7014)). +- Fixed a bug where the `podman start --attach` command would not print the container's exit code when the command exited due to the container exiting. +- Fixed a bug where the `podman rm` command with remote Podman would not remove volumes, even if the `--volumes` flag was specified ([#7128](https://github.com/containers/podman/issues/7128)). +- Fixed a bug where the `podman run` command with remote Podman and the `--rm` flag could exit before the container was fully removed. +- Fixed a bug where the `--pod new:...` flag to `podman run` and `podman create` would create a pod that did not share any namespaces. +- Fixed a bug where the `--preserve-fds` flag to `podman run` and `podman exec` could close the wrong file descriptors while trying to close user-provided descriptors after passing them into the container. +- Fixed a bug where default environment variables (`$PATH` and `$TERM`) were not set in containers when not provided by the image. +- Fixed a bug where pod infra containers were not properly unmounted after exiting. +- Fixed a bug where networks created with `podman network create` with an IPv6 subnet did not properly set an IPv6 default route. +- Fixed a bug where the `podman save` command would not work properly when its output was piped to another command ([#7017](https://github.com/containers/podman/issues/7017)). +- Fixed a bug where containers using a systemd init on a cgroups v1 system could leak mounts under `/sys/fs/cgroup/systemd` to the host. +- Fixed a bug where `podman build` would not generate an event on completion ([#7022](https://github.com/containers/podman/issues/7022)). +- Fixed a bug where the `podman history` command with remote Podman printed incorrect creation times for layers ([#7122](https://github.com/containers/podman/issues/7122)). +- Fixed a bug where Podman would not create working directories specified by the container image if they did not exist. +- Fixed a bug where Podman did not clear `CMD` from the container image if the user overrode `ENTRYPOINT` ([#7115](https://github.com/containers/podman/issues/7115)). +- Fixed a bug where error parsing image names were not fully reported (part of the error message containing the exact issue was dropped). +- Fixed a bug where the `podman images` command with remote Podman did not support printing image tags in Go templates supplied to the `--format` flag ([#7123](https://github.com/containers/podman/issues/7123)). + +### API +- Fixed a bug where the libpod and compat Build endpoints did not accept the `application/tar` content type (instead only accepting `application/x-tar`) ([#7185](https://github.com/containers/podman/issues/7185)). +- Fixed a bug where the libpod Exists endpoint would attempt to write a second header in some error conditions ([#7197](https://github.com/containers/podman/issues/7197)). +- Fixed a bug where compat and libpod Network Inspect and Network Remove endpoints would return a 500 instead of 404 when the requested network was not found. +- Added a versioned `_ping` endpoint (e.g. `http://localhost/v1.40/_ping`). +- Fixed a bug where containers started through a systemd-managed instance of the REST API would be shut down when `podman system service` shut down due to its idle timeout ([#7294](https://github.com/containers/podman/issues/7294)). +- Added stronger parameter verification for the libpod Network Create endpoint to ensure subnet mask is a valid value. +- The `Pod` URL parameter to the Libpod Container List endpoint has been deprecated; the information previously gated by the `Pod` boolean will now be included in the response unconditionally. + +### Misc +- Updated Buildah to v1.15.1 + ## 2.0.4 ### Bugfixes - Fixed a bug where the output of `podman image search` did not populate the Description field as it was mistakenly assigned to the ID field. -- cgit v1.2.3-54-g00ecf From 23348e7f313a570c513772a88db7f2741c8242ba Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 31 Jul 2020 13:52:14 -0400 Subject: Ensure DefaultEnvVariables is used in Specgen When we rewrote Podman's pkg/spec, one of the things that was lost was our use of a set of default environment variables, that ensure all containers have at least $PATH and $TERM set. While we're in the process of re-adding it, change it from a variable to a function, so we can ensure the Join function does not overwrite it and corrupt the defaults. Signed-off-by: Matthew Heon --- cmd/podman/common/specgen.go | 5 ++--- pkg/env/env.go | 14 ++++++++------ pkg/spec/spec.go | 4 ++-- pkg/specgen/generate/container.go | 9 +++++++++ pkg/specgen/generate/oci.go | 1 - 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 48a2069ff..2074ed4fa 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -308,9 +308,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // // Precedence order (higher index wins): // 1) env-host, 2) image data, 3) env-file, 4) env - env := map[string]string{ - "container": "podman", - } + env := make(map[string]string) + env["container"] = "podman" // First transform the os env into a map. We need it for the labels later in // any case. diff --git a/pkg/env/env.go b/pkg/env/env.go index a16007a50..0d55e5560 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -12,14 +12,16 @@ import ( "github.com/pkg/errors" ) -// DefaultEnvVariables sets $PATH and $TERM. -var DefaultEnvVariables = map[string]string{ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "TERM": "xterm", -} - const whiteSpaces = " \t" +// DefaultEnvVariables returns a default environment, with $PATH and $TERM set. +func DefaultEnvVariables() map[string]string { + return map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM": "xterm", + } +} + // Slice transforms the specified map of environment variables into a // slice. If a value is non-empty, the key and value are joined with '='. func Slice(m map[string]string) []string { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b974772d5..0dbdc76bb 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -321,13 +321,13 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // config. var defaultEnv map[string]string if runtimeConfig == nil { - defaultEnv = env.DefaultEnvVariables + defaultEnv = env.DefaultEnvVariables() } else { defaultEnv, err = env.ParseSlice(runtimeConfig.Containers.Env) if err != nil { return nil, errors.Wrap(err, "Env fields in containers.conf failed ot parse") } - defaultEnv = env.Join(env.DefaultEnvVariables, defaultEnv) + defaultEnv = env.Join(env.DefaultEnvVariables(), defaultEnv) } if err := addRlimits(config, &g); err != nil { diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 59fee80f1..06ffa3df6 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -86,6 +86,15 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.Env = envLib.Join(envLib.Join(defaultEnvs, envs), s.Env) + // Ensure that default environment variables are populated. + // Container must have PATH and TERM set, even if nothing else set them. + baseEnv := envLib.DefaultEnvVariables() + for k, v := range baseEnv { + if _, ok := s.Env[k]; !ok { + s.Env[k] = v + } + } + // Labels and Annotations annotations := make(map[string]string) if newImage != nil { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f1c9f2a1a..aefc7204c 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -260,7 +260,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt for key, val := range s.Annotations { g.AddAnnotation(key, val) } - g.AddProcessEnv("container", "podman") g.Config.Linux.Resources = s.ResourceLimits -- cgit v1.2.3-54-g00ecf From 44e5d0c1e8272f92d0fa6d41075a0127b241f003 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 18 Aug 2020 12:13:05 -0400 Subject: HACK: Disable build-each-commit Signed-off-by: Matthew Heon --- .cirrus.yml | 50 +++++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index af28b823b..87c135b78 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -269,36 +269,36 @@ varlink_api_task: failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh' -build_each_commit_task: +# build_each_commit_task: - depends_on: - - "gating" - - "vendor" - - "varlink_api" +# depends_on: +# - "gating" +# - "vendor" +# - "varlink_api" - only_if: >- - $CIRRUS_BRANCH != $DEST_BRANCH && - $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:IMG.*' && - $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:DOCS.*' +# only_if: >- +# $CIRRUS_BRANCH != $DEST_BRANCH && +# $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:IMG.*' && +# $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:DOCS.*' - gce_instance: - cpu: 8 - memory: "8Gb" +# gce_instance: +# cpu: 8 +# memory: "8Gb" - env: - MOD_CONTAINERS_CONF: 'false' +# env: +# MOD_CONTAINERS_CONF: 'false' - timeout_in: 30m +# timeout_in: 30m - setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' - build_each_commit_script: - # set -x by default, no need to spew contents of lib.sh - - 'source $SCRIPT_BASE/lib.sh &> /dev/null' - - 'git fetch --depth 50 origin $DEST_BRANCH |& ${TIMESTAMP}' - - 'make build-all-new-commits GIT_BASE_BRANCH=origin/$DEST_BRANCH |& ${TIMESTAMP}' +# setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' +# build_each_commit_script: +# # set -x by default, no need to spew contents of lib.sh +# - 'source $SCRIPT_BASE/lib.sh &> /dev/null' +# - 'git fetch --depth 50 origin $DEST_BRANCH |& ${TIMESTAMP}' +# - 'make build-all-new-commits GIT_BASE_BRANCH=origin/$DEST_BRANCH |& ${TIMESTAMP}' - on_failure: - failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh' +# on_failure: +# failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh' build_without_cgo_task: @@ -368,7 +368,6 @@ testing_task: - "gating" - "vendor" - "varlink_api" - - "build_each_commit" - "build_without_cgo" - "container_image_build" @@ -433,7 +432,6 @@ special_testing_rootless_task: - "gating" - "varlink_api" - "vendor" - - "build_each_commit" - "build_without_cgo" only_if: >- @@ -469,7 +467,6 @@ special_testing_in_podman_task: - "gating" - "varlink_api" - "vendor" - - "build_each_commit" - "build_without_cgo" only_if: >- @@ -700,7 +697,6 @@ success_task: - "gating" - "vendor" - "varlink_api" - - "build_each_commit" - "build_without_cgo" - "container_image_build" - "meta" -- cgit v1.2.3-54-g00ecf From e2a1242cee2589aee7dbdcb535d6e51ec3fb3a45 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 18 Aug 2020 15:55:44 -0400 Subject: Fix one import path pointing to containers/podman Signed-off-by: Matthew Heon --- test/e2e/run_working_dir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/run_working_dir.go b/test/e2e/run_working_dir.go index 93330deba..64f1dc247 100644 --- a/test/e2e/run_working_dir.go +++ b/test/e2e/run_working_dir.go @@ -4,7 +4,7 @@ import ( "os" "strings" - . "github.com/containers/podman/v2/test/utils" + . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -- cgit v1.2.3-54-g00ecf From 579360e862ceb82e88b65081d1b3fe3dc8ae2680 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 18 Aug 2020 16:30:29 -0400 Subject: Fix imports for runtime_img.go Signed-off-by: Matthew Heon --- libpod/runtime_img.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 1a82cdc07..8f2f82ad3 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -10,22 +10,18 @@ import ( "os" "github.com/containers/buildah/imagebuildah" + "github.com/containers/image/v5/directory" + dockerarchive "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" ociarchive "github.com/containers/image/v5/oci/archive" - "github.com/containers/image/v5/oci/layout" - "github.com/containers/image/v5/types" "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/libpod/events" "github.com/containers/libpod/v2/libpod/image" "github.com/containers/libpod/v2/pkg/util" "github.com/containers/storage" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" - - "github.com/containers/image/v5/directory" - dockerarchive "github.com/containers/image/v5/docker/archive" - ociarchive "github.com/containers/image/v5/oci/archive" - v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // Runtime API -- cgit v1.2.3-54-g00ecf From 98a4f898541cd01fdf62fe107d8fb48612482f33 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 12:14:40 -0400 Subject: Bump github.com/containers/common to v0.14.7 Signed-off-by: Matthew Heon --- go.mod | 2 +- go.sum | 4 +- .../containers/common/pkg/config/config.go | 57 ++++++++++++++++++++-- .../containers/common/pkg/config/containers.conf | 17 +++++++ .../containers/common/pkg/config/default.go | 1 + .../containers/common/version/version.go | 2 +- vendor/modules.txt | 2 +- 7 files changed, 76 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 34d657d85..cf595b492 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921 github.com/containernetworking/plugins v0.8.6 github.com/containers/buildah v1.15.1 - github.com/containers/common v0.14.6 + github.com/containers/common v0.14.7 github.com/containers/conmon v2.0.18+incompatible github.com/containers/image/v5 v5.5.2 github.com/containers/psgo v1.5.1 diff --git a/go.sum b/go.sum index 4a352fc58..1f1545677 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/containers/buildah v1.15.1 h1:fVYZedNKir1B7qW43KR3zmkjHH+ZAmPoPQix9zH github.com/containers/buildah v1.15.1/go.mod h1:AQPeirYl0bqtXuJaxM9d/xslMm+1qrABc73AEFw0M9U= github.com/containers/common v0.14.0 h1:hiZFDPf6ajKiDmojN5f5X3gboKPO73NLrYb0RXfrQiA= github.com/containers/common v0.14.0/go.mod h1:9olhlE+WhYof1npnMJdyRMX14/yIUint6zyHzcyRVAg= -github.com/containers/common v0.14.6 h1:GhMuqWEgH1e2YRXcTUYXOVakgj2srAGBkG1bqmOn+x8= -github.com/containers/common v0.14.6/go.mod h1:9olhlE+WhYof1npnMJdyRMX14/yIUint6zyHzcyRVAg= +github.com/containers/common v0.14.7 h1:KcyupqUqY9GFnxBck5Ww/tMstbvEmekQys8k8GiYAC0= +github.com/containers/common v0.14.7/go.mod h1:9olhlE+WhYof1npnMJdyRMX14/yIUint6zyHzcyRVAg= github.com/containers/conmon v2.0.18+incompatible h1:rjwjNnE756NuXcdE/uUmj4kDbrykslPuBMHI31wh43E= github.com/containers/conmon v2.0.18+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.4.4/go.mod h1:g7cxNXitiLi6pEr9/L9n/0wfazRuhDKXU15kV86N8h8= diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index d60464739..28ab06e67 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -195,7 +195,7 @@ type EngineConfig struct { // The first path pointing to a valid file will be used. ConmonPath []string `toml:"conmon_path,omitempty"` - //DetachKeys is the sequence of keys used to detach a container. + // DetachKeys is the sequence of keys used to detach a container. DetachKeys string `toml:"detach_keys,omitempty"` // EnablePortReservation determines whether engine will reserve ports on the @@ -266,12 +266,20 @@ type EngineConfig struct { // Indicates whether the application should be running in Remote mode Remote bool `toml:"-"` + // RemoteURI is deprecated, see ActiveService // RemoteURI containers connection information used to connect to remote system. RemoteURI string `toml:"remote_uri,omitempty"` - // Identity key file for RemoteURI + // RemoteIdentity is deprecated, ServiceDestinations + // RemoteIdentity key file for RemoteURI RemoteIdentity string `toml:"remote_identity,omitempty"` + // ActiveService index to Destinations added v2.0.3 + ActiveService string `toml:"active_service,omitempty"` + + // Destinations mapped by service Names + ServiceDestinations map[string]Destination `toml:"service_destinations,omitempty"` + // RuntimePath is the path to OCI runtime binary for launching containers. // The first path pointing to a valid file will be used This is used only // when there are no OCIRuntime/OCIRuntimes defined. It is used only to be @@ -387,6 +395,15 @@ type NetworkConfig struct { NetworkConfigDir string `toml:"network_config_dir,omitempty"` } +// Destination represents destination for remote service +type Destination struct { + // URI, required. Example: ssh://root@example.com:22/run/podman/podman.sock + URI string `toml:"uri"` + + // Identity file with ssh key, optional + Identity string `toml:"identity,omitempty"` +} + // NewConfig creates a new Config. It starts with an empty config and, if // specified, merges the config at `userConfigPath` path. Depending if we're // running as root or rootless, we then merge the system configuration followed @@ -872,8 +889,8 @@ func customConfigFile() (string, error) { return OverrideContainersConfig, nil } -//ReadCustomConfig reads the custom config and only generates a config based on it -//If the custom config file does not exists, function will return an empty config +// ReadCustomConfig reads the custom config and only generates a config based on it +// If the custom config file does not exists, function will return an empty config func ReadCustomConfig() (*Config, error) { path, err := customConfigFile() if err != nil { @@ -930,3 +947,35 @@ func (c *Config) Write() error { } return nil } + +// Reload reloads the configuration from containers.conf files +func Reload() (*Config, error) { + var err error + config, err = NewConfig("") + if err != nil { + return nil, errors.Wrapf(err, "containers.conf reload failed") + } + return Default() +} + +func (c *Config) ActiveDestination() (string, string, error){ + if uri, found := os.LookupEnv("CONTAINER_HOST"); found { + var ident string + if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found { + ident = v + } + return uri, ident, nil + } + + switch { + case c.Engine.ActiveService != "": + d, found := c.Engine.ServiceDestinations[c.Engine.ActiveService] + if !found { + return "", "", errors.Errorf("%q service destination not found", c.Engine.ActiveService) + } + return d.URI, d.Identity, nil + case c.Engine.RemoteURI != "": + return c.Engine.RemoteURI, c.Engine.RemoteIdentity, nil + } + return "", "", errors.New("no service destination configured") +} diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf index 181f559b5..c50793dc4 100644 --- a/vendor/github.com/containers/common/pkg/config/containers.conf +++ b/vendor/github.com/containers/common/pkg/config/containers.conf @@ -116,6 +116,7 @@ # # env = [ # "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", +# "TERM=xterm", # ] # Pass all host environment variables into the container. @@ -367,6 +368,22 @@ # Number of seconds to wait for container to exit before sending kill signal. # stop_timeout = 10 +# Index to the active service +# active_service = production + +# map of service destinations +# [service_destinations] +# [service_destinations.production] +# URI to access the Podman service +# Examples: +# rootless "unix://run/user/$UID/podman/podman.sock" (Default) +# rootfull "unix://run/podman/podman.sock (Default) +# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock +# remote rootfull ssh://root@10.10.1.136:22/run/podman/podman.sock +# uri="ssh://user@production.example.com/run/user/1001/podman/podman.sock" +# Path to file containing ssh identity key +# identity = "~/.ssh/id_rsa" + # Paths to look for a valid OCI runtime (runc, runv, kata, etc) [engine.runtimes] # runc = [ diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index 7a70a2b10..2dde8686f 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -176,6 +176,7 @@ func DefaultConfig() (*Config, error) { EnableLabeling: selinuxEnabled(), Env: []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", }, EnvHost: false, HTTPProxy: false, diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index d3f67d04c..cce745b59 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.14.6" +const Version = "0.14.7-dev" diff --git a/vendor/modules.txt b/vendor/modules.txt index a3d21b273..9e3152def 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -84,7 +84,7 @@ github.com/containers/buildah/pkg/secrets github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/pkg/umask github.com/containers/buildah/util -# github.com/containers/common v0.14.6 +# github.com/containers/common v0.14.7 github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/auth github.com/containers/common/pkg/capabilities -- cgit v1.2.3-54-g00ecf From b216b3391c3b58dc876d8cff2deb969b1c5d8f98 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 18 Aug 2020 16:40:36 -0400 Subject: Revert "remove podman system connection" This reverts commit 66e1626282fab661ac12a354f70b3b2221c69d7c. We are reenabling podman-system-connection. Signed-off-by: Matthew Heon --- cmd/podman/system/connection.go | 208 +++++++++++++++++++++ docs/source/markdown/podman-system-connection.1.md | 37 ++++ docs/source/markdown/podman-system.1.md | 1 + 3 files changed, 246 insertions(+) create mode 100644 cmd/podman/system/connection.go create mode 100644 docs/source/markdown/podman-system-connection.1.md diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go new file mode 100644 index 000000000..bdb113ea3 --- /dev/null +++ b/cmd/podman/system/connection.go @@ -0,0 +1,208 @@ +package system + +import ( + "bytes" + "fmt" + "net" + "net/url" + "os" + "os/user" + "regexp" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/libpod/define" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/containers/libpod/v2/pkg/terminal" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +const schemaPattern = "^[A-Za-z][A-Za-z0-9+.-]*:" + +var ( + // Skip creating engines since this command will obtain connection information to engine + noOp = func(cmd *cobra.Command, args []string) error { + return nil + } + connectionCmd = &cobra.Command{ + Use: "connection [flags] destination", + Args: cobra.ExactArgs(1), + Long: `Store ssh destination information in podman configuration. + "destination" is of the form [user@]hostname or + an URI of the form ssh://[user@]hostname[:port] +`, + Short: "Record remote ssh destination", + PersistentPreRunE: noOp, + PersistentPostRunE: noOp, + TraverseChildren: false, + RunE: connection, + Example: `podman system connection server.fubar.com + podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 + podman system connection --identity ~/.ssh/dev_rsa --port 22 root@server.fubar.com`, + } + + cOpts = struct { + Identity string + Port int + UDSPath string + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: connectionCmd, + Parent: systemCmd, + }) + + flags := connectionCmd.Flags() + flags.IntVarP(&cOpts.Port, "port", "p", 22, "port number for destination") + flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") +} + +func connection(cmd *cobra.Command, args []string) error { + // Default to ssh: schema if none given + dest := []byte(args[0]) + if match, err := regexp.Match(schemaPattern, dest); err != nil { + return errors.Wrapf(err, "internal regex error %q", schemaPattern) + } else if !match { + dest = append([]byte("ssh://"), dest...) + } + + uri, err := url.Parse(string(dest)) + if err != nil { + return errors.Wrapf(err, "failed to parse %q", string(dest)) + } + + if uri.User.Username() == "" { + if uri.User, err = getUserInfo(uri); err != nil { + return err + } + } + + if cmd.Flag("socket-path").Changed { + uri.Path = cmd.Flag("socket-path").Value.String() + } + + if cmd.Flag("port").Changed { + uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").Value.String()) + } + + if uri.Port() == "" { + uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) + } + + if uri.Path == "" { + if uri.Path, err = getUDS(cmd, uri); err != nil { + return errors.Wrapf(err, "failed to connect to %q", uri.String()) + } + } + + custom, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if cmd.Flag("identity").Changed { + custom.Engine.RemoteIdentity = cOpts.Identity + } + + custom.Engine.RemoteURI = uri.String() + return custom.Write() +} + +func getUserInfo(uri *url.URL) (*url.Userinfo, error) { + var ( + usr *user.User + err error + ) + if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { + usr, err = user.LookupId(u) + if err != nil { + return nil, errors.Wrapf(err, "failed to find user %q", u) + } + } else { + usr, err = user.Current() + if err != nil { + return nil, errors.Wrapf(err, "failed to obtain current user") + } + } + + pw, set := uri.User.Password() + if set { + return url.UserPassword(usr.Username, pw), nil + } + return url.User(usr.Username), nil +} + +func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { + var authMethods []ssh.AuthMethod + passwd, set := uri.User.Password() + if set { + authMethods = append(authMethods, ssh.Password(passwd)) + } + + ident := cmd.Flag("identity") + if ident.Changed { + auth, err := terminal.PublicKey(ident.Value.String(), []byte(passwd)) + if err != nil { + return "", errors.Wrapf(err, "Failed to read identity %q", ident.Value.String()) + } + authMethods = append(authMethods, auth) + } + + if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { + logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) + + c, err := net.Dial("unix", sock) + if err != nil { + return "", err + } + a := agent.NewClient(c) + authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) + } + + config := &ssh.ClientConfig{ + User: uri.User.Username(), + Auth: authMethods, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + dial, err := ssh.Dial("tcp", uri.Host, config) + if err != nil { + return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) + } + defer dial.Close() + + session, err := dial.NewSession() + if err != nil { + return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) + } + defer session.Close() + + // Override podman binary for testing etc + podman := "podman" + if v, found := os.LookupEnv("PODMAN_BINARY"); found { + podman = v + } + run := podman + " info --format=json" + + var buffer bytes.Buffer + session.Stdout = &buffer + if err := session.Run(run); err != nil { + return "", errors.Wrapf(err, "failed to run %q", run) + } + + var info define.Info + if err := json.Unmarshal(buffer.Bytes(), &info); err != nil { + return "", errors.Wrapf(err, "failed to parse 'podman info' results") + } + + if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { + return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host) + } + return info.Host.RemoteSocket.Path, nil +} diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md new file mode 100644 index 000000000..ed73980d6 --- /dev/null +++ b/docs/source/markdown/podman-system-connection.1.md @@ -0,0 +1,37 @@ +% podman-system-connection(1) + +## NAME +podman\-system\-connection - Record ssh destination for remote podman service + +## SYNOPSIS +**podman system connection** [*options*] [*ssh destination*] + +## DESCRIPTION +Record ssh destination for remote podman service(s). The ssh destination is given as one of: + - [user@]hostname[:port] + - ssh://[user@]hostname[:port] + +The user will be prompted for the remote ssh login password or key file pass phrase as required. `ssh-agent` is supported if it is running. + +## OPTIONS + +**-p**, **--port**=*port* + +Port for ssh destination. The default value is `22`. + +**--socket-path**=*path* + +Path to podman service unix domain socket on the ssh destination host + +## EXAMPLE +``` +$ podman system connection podman.fubar.com + +$ podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 + +``` +## SEE ALSO +podman-system(1) , containers.conf(5) , connections.conf(5) + +## HISTORY +June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system.1.md b/docs/source/markdown/podman-system.1.md index 814462ed6..1f19fd0b6 100644 --- a/docs/source/markdown/podman-system.1.md +++ b/docs/source/markdown/podman-system.1.md @@ -14,6 +14,7 @@ The system command allows you to manage the podman systems | Command | Man Page | Description | | ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | | df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. | +| connection | [podman-system-connection(1)](podman-system-connection.1.md) | Record ssh destination for remote podman service. | | info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | | migrate | [podman-system-migrate(1)](podman-system-migrate.1.md) | Migrate existing containers to a new podman version. | | prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data. | -- cgit v1.2.3-54-g00ecf From 7c13b8c12f0382c921d10ba917c558d3ed6c0fb4 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Wed, 15 Jul 2020 08:43:29 -0700 Subject: Fix `podman system connection` panic Signed-off-by: Jhon Honce --- cmd/podman/system/connection.go | 3 ++- docs/source/markdown/podman-system-connection.1.md | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go index bdb113ea3..bf015191f 100644 --- a/cmd/podman/system/connection.go +++ b/cmd/podman/system/connection.go @@ -60,7 +60,8 @@ func init() { }) flags := connectionCmd.Flags() - flags.IntVarP(&cOpts.Port, "port", "p", 22, "port number for destination") + flags.IntVarP(&cOpts.Port, "port", "p", 22, "SSH port number for destination") + flags.StringVar(&cOpts.Identity, "identity", "", "path to SSH identity file") flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") } diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md index ed73980d6..66cb656ae 100644 --- a/docs/source/markdown/podman-system-connection.1.md +++ b/docs/source/markdown/podman-system-connection.1.md @@ -15,6 +15,12 @@ The user will be prompted for the remote ssh login password or key file pass phr ## OPTIONS +**--identity**=*path* + +Path to ssh identity file. If the identity file has been encrypted, Podman prompts the user for the passphrase. +If no identity file is provided and no user is given, Podman defaults to the user running the podman command. +Podman prompts for the login password on the remote server. + **-p**, **--port**=*port* Port for ssh destination. The default value is `22`. -- cgit v1.2.3-54-g00ecf From ee956b04b05eed25d3aec967d8c68e7d1418fa09 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Tue, 21 Jul 2020 10:36:44 -0700 Subject: [WIP] Refactor podman system connection * Add support to manage multiple connections * Add connection * Remove connection * Rename connection * Set connection as default * Add markdown/man pages * Fix recursion in hack/xref-helpmsgs-manpages Signed-off-by: Jhon Honce Signed-off-by: Matt Heon --- cmd/podman/main.go | 1 + cmd/podman/root.go | 31 ++- cmd/podman/system/connection.go | 201 ++----------------- cmd/podman/system/connection/add.go | 223 +++++++++++++++++++++ cmd/podman/system/connection/default.go | 46 +++++ cmd/podman/system/connection/list.go | 84 ++++++++ cmd/podman/system/connection/remove.go | 49 +++++ cmd/podman/system/connection/rename.go | 54 +++++ .../markdown/podman-system-connection-add.1.md | 46 +++++ .../markdown/podman-system-connection-default.1.md | 20 ++ .../markdown/podman-system-connection-list.1.md | 24 +++ .../markdown/podman-system-connection-remove.1.md | 20 ++ .../markdown/podman-system-connection-rename.1.md | 20 ++ docs/source/markdown/podman-system-connection.1.md | 43 ++-- docs/source/markdown/podman-system.1.md | 21 +- hack/xref-helpmsgs-manpages | 11 +- test/e2e/system_connection_test.go | 176 ++++++++++++++++ 17 files changed, 836 insertions(+), 234 deletions(-) create mode 100644 cmd/podman/system/connection/add.go create mode 100644 cmd/podman/system/connection/default.go create mode 100644 cmd/podman/system/connection/list.go create mode 100644 cmd/podman/system/connection/remove.go create mode 100644 cmd/podman/system/connection/rename.go create mode 100644 docs/source/markdown/podman-system-connection-add.1.md create mode 100644 docs/source/markdown/podman-system-connection-default.1.md create mode 100644 docs/source/markdown/podman-system-connection-list.1.md create mode 100644 docs/source/markdown/podman-system-connection-remove.1.md create mode 100644 docs/source/markdown/podman-system-connection-rename.1.md create mode 100644 test/e2e/system_connection_test.go diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 5f740a006..f46f74547 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -14,6 +14,7 @@ import ( _ "github.com/containers/libpod/v2/cmd/podman/pods" "github.com/containers/libpod/v2/cmd/podman/registry" _ "github.com/containers/libpod/v2/cmd/podman/system" + _ "github.com/containers/libpod/v2/cmd/podman/system/connection" _ "github.com/containers/libpod/v2/cmd/podman/volumes" "github.com/containers/libpod/v2/pkg/rootless" "github.com/containers/libpod/v2/pkg/terminal" diff --git a/cmd/podman/root.go b/cmd/podman/root.go index b2c9f9c2c..7895a5f64 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -221,16 +221,12 @@ func loggingHook() { func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { cfg := opts.Config + uri, ident := resolveDestination() lFlags := cmd.Flags() - custom, _ := config.ReadCustomConfig() - defaultURI := custom.Engine.RemoteURI - if defaultURI == "" { - defaultURI = registry.DefaultAPIAddress() - } lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") - lFlags.StringVar(&opts.URI, "url", defaultURI, "URL to access Podman service (CONTAINER_HOST)") - lFlags.StringVar(&opts.Identity, "identity", custom.Engine.RemoteIdentity, "path to SSH identity file, (CONTAINER_SSHKEY)") + lFlags.StringVar(&opts.URI, "url", uri, "URL to access Podman service (CONTAINER_HOST)") + lFlags.StringVar(&opts.Identity, "identity", ident, "path to SSH identity file, (CONTAINER_SSHKEY)") pFlags := cmd.PersistentFlags() pFlags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") @@ -277,3 +273,24 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { pFlags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") } } + +func resolveDestination() (string, string) { + if uri, found := os.LookupEnv("CONTAINER_HOST"); found { + var ident string + if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found { + ident = v + } + return uri, ident + } + + cfg, err := config.ReadCustomConfig() + if err != nil { + return registry.DefaultAPIAddress(), "" + } + + uri, ident, err := cfg.ActiveDestination() + if err != nil { + return registry.DefaultAPIAddress(), "" + } + return uri, ident +} diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go index bf015191f..b1c538803 100644 --- a/cmd/podman/system/connection.go +++ b/cmd/podman/system/connection.go @@ -1,209 +1,34 @@ package system import ( - "bytes" - "fmt" - "net" - "net/url" - "os" - "os/user" - "regexp" - - "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/cmd/podman/registry" - "github.com/containers/libpod/v2/libpod/define" + "github.com/containers/libpod/v2/cmd/podman/validate" "github.com/containers/libpod/v2/pkg/domain/entities" - "github.com/containers/libpod/v2/pkg/terminal" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" ) -const schemaPattern = "^[A-Za-z][A-Za-z0-9+.-]*:" - var ( - // Skip creating engines since this command will obtain connection information to engine + // Skip creating engines since this command will obtain connection information to said engines noOp = func(cmd *cobra.Command, args []string) error { return nil } - connectionCmd = &cobra.Command{ - Use: "connection [flags] destination", - Args: cobra.ExactArgs(1), - Long: `Store ssh destination information in podman configuration. - "destination" is of the form [user@]hostname or - an URI of the form ssh://[user@]hostname[:port] -`, - Short: "Record remote ssh destination", - PersistentPreRunE: noOp, - PersistentPostRunE: noOp, - TraverseChildren: false, - RunE: connection, - Example: `podman system connection server.fubar.com - podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 - podman system connection --identity ~/.ssh/dev_rsa --port 22 root@server.fubar.com`, - } - cOpts = struct { - Identity string - Port int - UDSPath string - }{} + ConnectionCmd = &cobra.Command{ + Use: "connection", + Short: "Manage remote ssh destinations", + Long: `Manage ssh destination information in podman configuration`, + DisableFlagsInUseLine: true, + PersistentPreRunE: noOp, + RunE: validate.SubCommandExists, + PersistentPostRunE: noOp, + TraverseChildren: false, + } ) func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: connectionCmd, + Command: ConnectionCmd, Parent: systemCmd, }) - - flags := connectionCmd.Flags() - flags.IntVarP(&cOpts.Port, "port", "p", 22, "SSH port number for destination") - flags.StringVar(&cOpts.Identity, "identity", "", "path to SSH identity file") - flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") -} - -func connection(cmd *cobra.Command, args []string) error { - // Default to ssh: schema if none given - dest := []byte(args[0]) - if match, err := regexp.Match(schemaPattern, dest); err != nil { - return errors.Wrapf(err, "internal regex error %q", schemaPattern) - } else if !match { - dest = append([]byte("ssh://"), dest...) - } - - uri, err := url.Parse(string(dest)) - if err != nil { - return errors.Wrapf(err, "failed to parse %q", string(dest)) - } - - if uri.User.Username() == "" { - if uri.User, err = getUserInfo(uri); err != nil { - return err - } - } - - if cmd.Flag("socket-path").Changed { - uri.Path = cmd.Flag("socket-path").Value.String() - } - - if cmd.Flag("port").Changed { - uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").Value.String()) - } - - if uri.Port() == "" { - uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) - } - - if uri.Path == "" { - if uri.Path, err = getUDS(cmd, uri); err != nil { - return errors.Wrapf(err, "failed to connect to %q", uri.String()) - } - } - - custom, err := config.ReadCustomConfig() - if err != nil { - return err - } - - if cmd.Flag("identity").Changed { - custom.Engine.RemoteIdentity = cOpts.Identity - } - - custom.Engine.RemoteURI = uri.String() - return custom.Write() -} - -func getUserInfo(uri *url.URL) (*url.Userinfo, error) { - var ( - usr *user.User - err error - ) - if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { - usr, err = user.LookupId(u) - if err != nil { - return nil, errors.Wrapf(err, "failed to find user %q", u) - } - } else { - usr, err = user.Current() - if err != nil { - return nil, errors.Wrapf(err, "failed to obtain current user") - } - } - - pw, set := uri.User.Password() - if set { - return url.UserPassword(usr.Username, pw), nil - } - return url.User(usr.Username), nil -} - -func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { - var authMethods []ssh.AuthMethod - passwd, set := uri.User.Password() - if set { - authMethods = append(authMethods, ssh.Password(passwd)) - } - - ident := cmd.Flag("identity") - if ident.Changed { - auth, err := terminal.PublicKey(ident.Value.String(), []byte(passwd)) - if err != nil { - return "", errors.Wrapf(err, "Failed to read identity %q", ident.Value.String()) - } - authMethods = append(authMethods, auth) - } - - if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { - logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) - - c, err := net.Dial("unix", sock) - if err != nil { - return "", err - } - a := agent.NewClient(c) - authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) - } - - config := &ssh.ClientConfig{ - User: uri.User.Username(), - Auth: authMethods, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - } - dial, err := ssh.Dial("tcp", uri.Host, config) - if err != nil { - return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) - } - defer dial.Close() - - session, err := dial.NewSession() - if err != nil { - return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) - } - defer session.Close() - - // Override podman binary for testing etc - podman := "podman" - if v, found := os.LookupEnv("PODMAN_BINARY"); found { - podman = v - } - run := podman + " info --format=json" - - var buffer bytes.Buffer - session.Stdout = &buffer - if err := session.Run(run); err != nil { - return "", errors.Wrapf(err, "failed to run %q", run) - } - - var info define.Info - if err := json.Unmarshal(buffer.Bytes(), &info); err != nil { - return "", errors.Wrapf(err, "failed to parse 'podman info' results") - } - - if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { - return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host) - } - return info.Host.RemoteSocket.Path, nil } diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go new file mode 100644 index 000000000..7522eb190 --- /dev/null +++ b/cmd/podman/system/connection/add.go @@ -0,0 +1,223 @@ +package connection + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/url" + "os" + "os/user" + "regexp" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/libpod/define" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/containers/libpod/v2/pkg/terminal" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +const schemaPattern = "^[A-Za-z][A-Za-z0-9+.-]*:" + +var ( + addCmd = &cobra.Command{ + Use: "add [flags] NAME DESTINATION", + Args: cobra.ExactArgs(2), + Short: "Record destination for the Podman service", + Long: `Add destination to podman configuration. + "destination" is of the form [user@]hostname or + an URI of the form ssh://[user@]hostname[:port] +`, + RunE: add, + Example: `podman system connection add laptop server.fubar.com + podman system connection add --identity ~/.ssh/dev_rsa testing ssh://root@server.fubar.com:2222 + podman system connection add --identity ~/.ssh/dev_rsa --port 22 production root@server.fubar.com + `, + } + + cOpts = struct { + Identity string + Port int + UDSPath string + Default bool + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: addCmd, + Parent: system.ConnectionCmd, + }) + + flags := addCmd.Flags() + flags.IntVarP(&cOpts.Port, "port", "p", 22, "SSH port number for destination") + flags.StringVar(&cOpts.Identity, "identity", "", "path to SSH identity file") + flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") + flags.BoolVarP(&cOpts.Default, "default", "d", false, "Set connection to be default") +} + +func add(cmd *cobra.Command, args []string) error { + // Default to ssh: schema if none given + dest := args[1] + if match, err := regexp.Match(schemaPattern, []byte(dest)); err != nil { + return errors.Wrapf(err, "internal regex error %q", schemaPattern) + } else if !match { + dest = "ssh://" + dest + } + + uri, err := url.Parse(dest) + if err != nil { + return errors.Wrapf(err, "failed to parse %q", dest) + } + + if uri.User.Username() == "" { + if uri.User, err = getUserInfo(uri); err != nil { + return err + } + } + + if cmd.Flags().Changed("socket-path") { + uri.Path = cmd.Flag("socket-path").Value.String() + } + + if cmd.Flags().Changed("port") { + uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").Value.String()) + } + + if uri.Port() == "" { + uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) + } + + if uri.Path == "" { + if uri.Path, err = getUDS(cmd, uri); err != nil { + return errors.Wrapf(err, "failed to connect to %q", uri.String()) + } + } + + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if cmd.Flags().Changed("default") { + if cOpts.Default { + cfg.Engine.ActiveService = args[0] + } + } + + dst := config.Destination{ + URI: uri.String(), + } + + if cmd.Flags().Changed("identity") { + dst.Identity = cOpts.Identity + } + + if cfg.Engine.ServiceDestinations == nil { + cfg.Engine.ServiceDestinations = map[string]config.Destination{ + args[0]: dst, + } + } else { + cfg.Engine.ServiceDestinations[args[0]] = dst + } + return cfg.Write() +} + +func getUserInfo(uri *url.URL) (*url.Userinfo, error) { + var ( + usr *user.User + err error + ) + if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { + usr, err = user.LookupId(u) + if err != nil { + return nil, errors.Wrapf(err, "failed to find user %q", u) + } + } else { + usr, err = user.Current() + if err != nil { + return nil, errors.Wrapf(err, "failed to obtain current user") + } + } + + pw, set := uri.User.Password() + if set { + return url.UserPassword(usr.Username, pw), nil + } + return url.User(usr.Username), nil +} + +func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { + var authMethods []ssh.AuthMethod + passwd, set := uri.User.Password() + if set { + authMethods = append(authMethods, ssh.Password(passwd)) + } + + if cmd.Flags().Changed("identity") { + value := cmd.Flag("identity").Value.String() + auth, err := terminal.PublicKey(value, []byte(passwd)) + if err != nil { + return "", errors.Wrapf(err, "Failed to read identity %q", value) + } + authMethods = append(authMethods, auth) + } + + if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { + logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) + + c, err := net.Dial("unix", sock) + if err != nil { + return "", err + } + a := agent.NewClient(c) + authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) + } + + config := &ssh.ClientConfig{ + User: uri.User.Username(), + Auth: authMethods, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + dial, err := ssh.Dial("tcp", uri.Host, config) + if err != nil { + return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) + } + defer dial.Close() + + session, err := dial.NewSession() + if err != nil { + return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) + } + defer session.Close() + + // Override podman binary for testing etc + podman := "podman" + if v, found := os.LookupEnv("PODMAN_BINARY"); found { + podman = v + } + run := podman + " info --format=json" + + var buffer bytes.Buffer + session.Stdout = &buffer + if err := session.Run(run); err != nil { + return "", errors.Wrapf(err, "failed to run %q", run) + } + + var info define.Info + if err := json.Unmarshal(buffer.Bytes(), &info); err != nil { + return "", errors.Wrapf(err, "failed to parse 'podman info' results") + } + + if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { + return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host) + } + return info.Host.RemoteSocket.Path, nil +} diff --git a/cmd/podman/system/connection/default.go b/cmd/podman/system/connection/default.go new file mode 100644 index 000000000..b85343dc2 --- /dev/null +++ b/cmd/podman/system/connection/default.go @@ -0,0 +1,46 @@ +package connection + +import ( + "fmt" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Skip creating engines since this command will obtain connection information to said engines + dfltCmd = &cobra.Command{ + Use: "default NAME", + Args: cobra.ExactArgs(1), + Short: "Set named destination as default", + Long: `Set named destination as default for the Podman service`, + DisableFlagsInUseLine: true, + RunE: defaultRunE, + Example: `podman system connection default testing`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: dfltCmd, + Parent: system.ConnectionCmd, + }) +} + +func defaultRunE(cmd *cobra.Command, args []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found { + return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0]) + } + + cfg.Engine.ActiveService = args[0] + return cfg.Write() +} diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go new file mode 100644 index 000000000..c0a9087f5 --- /dev/null +++ b/cmd/podman/system/connection/list.go @@ -0,0 +1,84 @@ +package connection + +import ( + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/cmd/podman/validate" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Args: validate.NoArgs, + Short: "List destination for the Podman service(s)", + Long: `List destination information for the Podman service(s) in podman configuration`, + DisableFlagsInUseLine: true, + Example: `podman system connection list + podman system connection ls`, + RunE: list, + TraverseChildren: false, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: listCmd, + Parent: system.ConnectionCmd, + }) +} + +type namedDestination struct { + Name string + config.Destination +} + +func list(_ *cobra.Command, _ []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if len(cfg.Engine.ServiceDestinations) == 0 { + return nil + } + + hdrs := []map[string]string{{ + "Identity": "Identity", + "Name": "Name", + "URI": "URI", + }} + + rows := make([]namedDestination, 0) + for k, v := range cfg.Engine.ServiceDestinations { + if k == cfg.Engine.ActiveService { + k += "*" + } + + r := namedDestination{ + Name: k, + Destination: config.Destination{ + Identity: v.Identity, + URI: v.URI, + }, + } + rows = append(rows, r) + } + + // TODO: Allow user to override format + format := "{{range . }}{{.Name}}\t{{.Identity}}\t{{.URI}}\n{{end}}" + tmpl := template.Must(template.New("connection").Parse(format)) + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + + _ = tmpl.Execute(w, hdrs) + return tmpl.Execute(w, rows) +} diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go new file mode 100644 index 000000000..a2ca66c8d --- /dev/null +++ b/cmd/podman/system/connection/remove.go @@ -0,0 +1,49 @@ +package connection + +import ( + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Skip creating engines since this command will obtain connection information to said engines + rmCmd = &cobra.Command{ + Use: "remove NAME", + Args: cobra.ExactArgs(1), + Aliases: []string{"rm"}, + Long: `Delete named destination from podman configuration`, + Short: "Delete named destination", + DisableFlagsInUseLine: true, + RunE: rm, + Example: `podman system connection remove devl + podman system connection rm devl`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCmd, + Parent: system.ConnectionCmd, + }) +} + +func rm(_ *cobra.Command, args []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if cfg.Engine.ServiceDestinations != nil { + delete(cfg.Engine.ServiceDestinations, args[0]) + } + + if cfg.Engine.ActiveService == args[0] { + cfg.Engine.ActiveService = "" + } + + return cfg.Write() +} diff --git a/cmd/podman/system/connection/rename.go b/cmd/podman/system/connection/rename.go new file mode 100644 index 000000000..d6cd55c31 --- /dev/null +++ b/cmd/podman/system/connection/rename.go @@ -0,0 +1,54 @@ +package connection + +import ( + "fmt" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Skip creating engines since this command will obtain connection information to said engines + renameCmd = &cobra.Command{ + Use: "rename OLD NEW", + Aliases: []string{"mv"}, + Args: cobra.ExactArgs(2), + Short: "Rename \"old\" to \"new\"", + Long: `Rename destination for the Podman service from "old" to "new"`, + DisableFlagsInUseLine: true, + RunE: rename, + Example: `podman system connection rename laptop devl, + podman system connection mv laptop devl`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: renameCmd, + Parent: system.ConnectionCmd, + }) +} + +func rename(cmd *cobra.Command, args []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found { + return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0]) + } + + cfg.Engine.ServiceDestinations[args[1]] = cfg.Engine.ServiceDestinations[args[0]] + delete(cfg.Engine.ServiceDestinations, args[0]) + + if cfg.Engine.ActiveService == args[0] { + cfg.Engine.ActiveService = args[1] + } + + return cfg.Write() +} diff --git a/docs/source/markdown/podman-system-connection-add.1.md b/docs/source/markdown/podman-system-connection-add.1.md new file mode 100644 index 000000000..5059803a2 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-add.1.md @@ -0,0 +1,46 @@ +% podman-system-connection-add(1) + +## NAME +podman\-system\-connection\-add - Record destination for the Podman service + +## SYNOPSIS +**podman system connection add** [*options*] *name* *destination* + +## DESCRIPTION +Record ssh destination for remote podman service(s). The ssh destination is given as one of: + - [user@]hostname[:port] + - ssh://[user@]hostname[:port] + +The user will be prompted for the remote ssh login password or key file pass phrase as required. The `ssh-agent` is supported if it is running. + +## OPTIONS + +**-d**, **--default**=*false* + +Make the new destination the default for this user. + +**--identity**=*path* + +Path to ssh identity file. If the identity file has been encrypted, Podman prompts the user for the passphrase. +If no identity file is provided and no user is given, Podman defaults to the user running the podman command. +Podman prompts for the login password on the remote server. + +**-p**, **--port**=*port* + +Port for ssh destination. The default value is `22`. + +**--socket-path**=*path* + +Path to the Podman service unix domain socket on the ssh destination host + +## EXAMPLE +``` +$ podman system connection add QA podman.example.com + +$ podman system connection add --identity ~/.ssh/dev_rsa production ssh://root@server.example.com:2222 +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-default.1.md b/docs/source/markdown/podman-system-connection-default.1.md new file mode 100644 index 000000000..f324f8c01 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-default.1.md @@ -0,0 +1,20 @@ +% podman-system-connection-default(1) + +## NAME +podman\-system\-connection\-default - Set named destination as default for the Podman service + +## SYNOPSIS +**podman system connection default** *name* + +## DESCRIPTION +Set named ssh destination as default destination for the Podman service. + +## EXAMPLE +``` +$ podman system connection default production +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-list.1.md b/docs/source/markdown/podman-system-connection-list.1.md new file mode 100644 index 000000000..f5fb5c8e3 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-list.1.md @@ -0,0 +1,24 @@ +% podman-system-connection-list(1) + +## NAME +podman\-system\-connection\-list - List the destination for the Podman service(s) + +## SYNOPSIS +**podman system connection list** + +**podman system connection ls** + +## DESCRIPTION +List ssh destination(s) for podman service(s). + +## EXAMPLE +``` +$ podman system connection list +Name URI Identity +devl ssh://root@example.com/run/podman/podman.sock ~/.ssh/id_rsa +``` +## SEE ALSO +podman-system(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-remove.1.md b/docs/source/markdown/podman-system-connection-remove.1.md new file mode 100644 index 000000000..faa767176 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-remove.1.md @@ -0,0 +1,20 @@ +% podman-system-connection-remove(1) + +## NAME +podman\-system\-connection\-remove - Delete named destination + +## SYNOPSIS +**podman system connection remove** *name* + +## DESCRIPTION +Delete named ssh destination. + +## EXAMPLE +``` +$ podman system connection remove production +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-rename.1.md b/docs/source/markdown/podman-system-connection-rename.1.md new file mode 100644 index 000000000..819cb697f --- /dev/null +++ b/docs/source/markdown/podman-system-connection-rename.1.md @@ -0,0 +1,20 @@ +% podman-system-connection-rename(1) + +## NAME +podman\-system\-connection\-rename - Rename the destination for Podman service + +## SYNOPSIS +**podman system connection rename** *old* *new* + +## DESCRIPTION +Rename ssh destination from *old* to *new*. + +## EXAMPLE +``` +$ podman system connection rename laptop devel +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md index 66cb656ae..86199c6b9 100644 --- a/docs/source/markdown/podman-system-connection.1.md +++ b/docs/source/markdown/podman-system-connection.1.md @@ -1,43 +1,34 @@ % podman-system-connection(1) ## NAME -podman\-system\-connection - Record ssh destination for remote podman service +podman\-system\-connection - Manage the destination(s) for Podman service(s) -## SYNOPSIS -**podman system connection** [*options*] [*ssh destination*] +## SYNOPSISManage the destination(s) for Podman service(s) +**podman system connection** *subcommand* ## DESCRIPTION -Record ssh destination for remote podman service(s). The ssh destination is given as one of: - - [user@]hostname[:port] - - ssh://[user@]hostname[:port] +Manage the destination(s) for Podman service(s). -The user will be prompted for the remote ssh login password or key file pass phrase as required. `ssh-agent` is supported if it is running. +The user will be prompted for the ssh login password or key file pass phrase as required. The `ssh-agent` is supported if it is running. -## OPTIONS +## COMMANDS -**--identity**=*path* - -Path to ssh identity file. If the identity file has been encrypted, Podman prompts the user for the passphrase. -If no identity file is provided and no user is given, Podman defaults to the user running the podman command. -Podman prompts for the login password on the remote server. - -**-p**, **--port**=*port* - -Port for ssh destination. The default value is `22`. - -**--socket-path**=*path* - -Path to podman service unix domain socket on the ssh destination host +| Command | Man Page | Description | +| ------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------- | +| add | [podman-system-connection-add(1)](podman-system-connection-add.1.md) | Record destination for the Podman service | +| default | [podman-system-connection-default(1)](podman-system-connection-default.1.md) | Set named destination as default for the Podman service | +| list | [podman-system-connection-list(1)](podman-system-connection-list.1.md) | List the destination for the Podman service(s) | +| remove | [podman-system-connection-remove(1)](podman-system-connection-remove.1.md) | Delete named destination | +| rename | [podman-system-connection-rename(1)](podman-system-connection-rename.1.md) | Rename the destination for Podman service | ## EXAMPLE ``` -$ podman system connection podman.fubar.com - -$ podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 - +$ podman system connection list +Name URI Identity +devl ssh://root@example.com/run/podman/podman.sock ~/.ssh/id_rsa ``` ## SEE ALSO -podman-system(1) , containers.conf(5) , connections.conf(5) +podman-system(1) , containers.conf(5) ## HISTORY June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system.1.md b/docs/source/markdown/podman-system.1.md index 1f19fd0b6..9ac73237e 100644 --- a/docs/source/markdown/podman-system.1.md +++ b/docs/source/markdown/podman-system.1.md @@ -11,17 +11,16 @@ The system command allows you to manage the podman systems ## COMMANDS -| Command | Man Page | Description | -| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | -| df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. | -| connection | [podman-system-connection(1)](podman-system-connection.1.md) | Record ssh destination for remote podman service. | -| info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | -| migrate | [podman-system-migrate(1)](podman-system-migrate.1.md) | Migrate existing containers to a new podman version. | -| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data. | -| renumber | [podman-system-renumber(1)](podman-system-renumber.1.md) | Migrate lock numbers to handle a change in maximum number of locks. | -| reset | [podman-system-reset(1)](podman-system-reset.1.md) | Reset storage back to initial state. | -| service | [podman-service(1)](podman-system-service.1.md) | Run an API service | - +| Command | Man Page | Description | +| ------- | ------------------------------------------------------------ | -------------------------------------------------------------------- | +| connection | [podman-system-connection(1)](podman-system-connection.1.md) | Manage the destination(s) for Podman service(s) | +| df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. | +| info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | +| migrate | [podman-system-migrate(1)](podman-system-migrate.1.md) | Migrate existing containers to a new podman version. | +| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data. | +| renumber | [podman-system-renumber(1)](podman-system-renumber.1.md) | Migrate lock numbers to handle a change in maximum number of locks. | +| reset | [podman-system-reset(1)](podman-system-reset.1.md) | Reset storage back to initial state. | +| service | [podman-system-service(1)](podman-system-service.1.md) | Run an API service | ## SEE ALSO podman(1) diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index c1e9dffc4..16b596589 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -16,6 +16,9 @@ our $VERSION = '0.1'; # For debugging, show data structures using DumpTree($var) #use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0; +# unbuffer output +$| = 1; + ############################################################################### # BEGIN user-customizable section @@ -266,12 +269,16 @@ sub podman_man { elsif ($section eq 'commands') { # In podman.1.md if ($line =~ /^\|\s*\[podman-(\S+?)\(\d\)\]/) { - $man{$1} = podman_man("podman-$1"); + # $1 will be changed by recursion _*BEFORE*_ left-hand assignment + my $subcmd = $1; + $man{$subcmd} = podman_man("podman-$1"); } # In podman-.1.md elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { - $man{$1} = podman_man($2); + # $1 will be changed by recursion _*BEFORE*_ left-hand assignment + my $subcmd = $1; + $man{$subcmd} = podman_man($2); } } diff --git a/test/e2e/system_connection_test.go b/test/e2e/system_connection_test.go new file mode 100644 index 000000000..4c750ee7f --- /dev/null +++ b/test/e2e/system_connection_test.go @@ -0,0 +1,176 @@ +package integration + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/containers/common/pkg/config" + . "github.com/containers/libpod/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("podman system connection", func() { + ConfPath := struct { + Value string + IsSet bool + }{} + + var ( + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + ConfPath.Value, ConfPath.IsSet = os.LookupEnv("CONTAINERS_CONF") + conf, err := ioutil.TempFile("", "containersconf") + if err != nil { + panic(err) + } + os.Setenv("CONTAINERS_CONF", conf.Name()) + + tempdir, err := CreateTempDirInTempDir() + if err != nil { + panic(err) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + }) + + AfterEach(func() { + podmanTest.Cleanup() + os.Remove(os.Getenv("CONTAINERS_CONF")) + if ConfPath.IsSet { + os.Setenv("CONTAINERS_CONF", ConfPath.Value) + } else { + os.Unsetenv("CONTAINERS_CONF") + } + + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("add", func() { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(Equal("QA")) + Expect(cfg.Engine.ServiceDestinations["QA"]).To(Equal( + config.Destination{ + URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + Identity: "~/.ssh/id_rsa", + }, + )) + + cmd = []string{"system", "connection", "rename", + "QA", + "QE", + } + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + cfg, err = config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(Equal("QE")) + Expect(cfg.Engine.ServiceDestinations["QE"]).To(Equal( + config.Destination{ + URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + Identity: "~/.ssh/id_rsa", + }, + )) + }) + + It("remove", func() { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + for i := 0; i < 2; i++ { + cmd = []string{"system", "connection", "remove", "QA"} + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(BeEmpty()) + Expect(cfg.Engine.ServiceDestinations).To(BeEmpty()) + } + }) + + It("default", func() { + for _, name := range []string{"devl", "qe"} { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + name, + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + } + + cmd := []string{"system", "connection", "default", "devl"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(Equal("devl")) + + cmd = []string{"system", "connection", "list"} + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("Name *Identity *URI")) + }) + + It("failed default", func() { + cmd := []string{"system", "connection", "default", "devl"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).ShouldNot(Exit(0)) + Expect(session.Err).Should(Say("destination is not defined")) + }) + + It("failed rename", func() { + cmd := []string{"system", "connection", "rename", "devl", "QE"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).ShouldNot(Exit(0)) + Expect(session.Err).Should(Say("destination is not defined")) + }) + + It("empty list", func() { + cmd := []string{"system", "connection", "list"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + Expect(session.Err).Should(Say("")) + }) +}) -- cgit v1.2.3-54-g00ecf From 402d002184fa33b8aa7bfe91a83b04ba80425e28 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 19 Aug 2020 17:41:36 -0400 Subject: Unmount c/storage containers before removing them When `podman rmi --force` is run, it will remove any containers that depend on the image. This includes Podman containers, but also any other c/storage users who may be using it. With Podman containers, we use the standard Podman removal function for containers, which handles all edge cases nicely, shutting down running containers, ensuring they're unmounted, etc. Unfortunately, no such convient function exists (or can exist) for all c/storage containers. Identifying the PID of a Buildah, CRI-O, or Podman container is extremely different, and those are just the implementations under the containers org. We can't reasonably be able to know if a c/storage container is *in use* and safe for removal if it's not a Podman container. At the very least, though, we can attempt to unmount a storage container before removing it. If it is in use, this will fail (probably with a not-particularly-helpful error message), but if it is not in use but not fully cleaned up, this should make our removing it much more robust than it normally is. Signed-off-by: Matthew Heon --- libpod/runtime_img.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 8f2f82ad3..9515aa468 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -140,6 +140,10 @@ func storageContainers(imageID string, store storage.Store) ([]string, error) { // Removes the containers passed in the array. func removeStorageContainers(ctrIDs []string, store storage.Store) error { for _, ctrID := range ctrIDs { + if _, err := store.Unmount(ctrID, true); err != nil { + return errors.Wrapf(err, "could not unmount container %q to remove it", ctrID) + } + if err := store.DeleteContainer(ctrID); err != nil { return errors.Wrapf(err, "could not remove container %q", ctrID) } -- cgit v1.2.3-54-g00ecf From b5b782f2584e59534ae1d66551de0e04fc3c0038 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Tue, 18 Aug 2020 11:28:46 +0200 Subject: generate systemd: quote arguments with whitespace Make sure that arguments with whitespace are properly quoted so they are interpreted as one (and not multiple ones) by systemd. Now `-e tz="america/new york"` will be generated as `-e "tz=america/new york"`. The quotes are moving but the argument is still correct. Fixes: #7285 Signed-off-by: Valentin Rothberg --- pkg/systemd/generate/common.go | 13 +++++++++++++ pkg/systemd/generate/common_test.go | 25 +++++++++++++++++++++++++ pkg/systemd/generate/containers.go | 1 + pkg/systemd/generate/containers_test.go | 4 ++-- pkg/systemd/generate/pods.go | 1 + pkg/systemd/generate/pods_test.go | 4 ++-- 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index d6d18a810..1fc4479ff 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -1,6 +1,7 @@ package generate import ( + "strconv" "strings" "github.com/pkg/errors" @@ -53,3 +54,15 @@ func filterPodFlags(command []string) []string { } return processed } + +// quoteArguments makes sure that all arguments with at least one whitespace +// are quoted to make sure those are interpreted as one argument instead of +// multiple ones. +func quoteArguments(command []string) []string { + for i := range command { + if strings.ContainsAny(command[i], " \t") { + command[i] = strconv.Quote(command[i]) + } + } + return command +} diff --git a/pkg/systemd/generate/common_test.go b/pkg/systemd/generate/common_test.go index 389c30f59..d0ec5637c 100644 --- a/pkg/systemd/generate/common_test.go +++ b/pkg/systemd/generate/common_test.go @@ -28,3 +28,28 @@ func TestFilterPodFlags(t *testing.T) { } } } + +func TestQuoteArguments(t *testing.T) { + tests := []struct { + input []string + output []string + }{ + { + []string{"foo", "bar=\"arg\""}, + []string{"foo", "bar=\"arg\""}, + }, + { + []string{"foo", "bar=\"arg with space\""}, + []string{"foo", "\"bar=\\\"arg with space\\\"\""}, + }, + { + []string{"foo", "bar=\"arg with\ttab\""}, + []string{"foo", "\"bar=\\\"arg with\\ttab\\\"\""}, + }, + } + + for _, test := range tests { + quoted := quoteArguments(test.input) + assert.Equal(t, test.output, quoted) + } +} diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index b77fb5e11..068bb3c82 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -241,6 +241,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst startCommand = append(startCommand, "--replace") } startCommand = append(startCommand, info.CreateCommand[index:]...) + startCommand = quoteArguments(startCommand) info.ExecStartPre = "/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}" info.ExecStart = strings.Join(startCommand, " ") diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 13138c0cd..73fc5e856 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -117,7 +117,7 @@ After=network-online.target Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id -ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42 ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id PIDFile=%t/jadda-jadda.pid @@ -296,7 +296,7 @@ WantedBy=multi-user.target default.target` PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", StopTimeout: 42, PodmanVersion: "CI", - CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, + CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN", "foo=arg \"with \" space"}, EnvVariable: EnvVariable, }, goodWithNameAndGeneric, diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index 1c7ef7792..df807585a 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -292,6 +292,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) } startCommand = append(startCommand, podCreateArgs...) + startCommand = quoteArguments(startCommand) info.ExecStartPre1 = "/bin/rm -f {{.PIDFile}} {{.PodIDFile}}" info.ExecStartPre2 = strings.Join(startCommand, " ") diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index 4089b44e5..9754d3215 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -75,7 +75,7 @@ Before=container-1.service container-2.service Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id -ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo "bar=arg with space" --replace ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10 ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id @@ -118,7 +118,7 @@ WantedBy=multi-user.target default.target` StopTimeout: 10, PodmanVersion: "CI", RequiredServices: []string{"container-1", "container-2"}, - CreateCommand: []string{"podman", "pod", "create", "--name", "foo"}, + CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "bar=arg with space"}, }, podGoodNamedNew, true, -- cgit v1.2.3-54-g00ecf From d3ef4770d4e33f3f6c12895b726320b9c9f311c2 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Tue, 18 Aug 2020 19:37:05 +0200 Subject: fix podman version output to include git commit and builttime Add the go module version v2 to the libpod path. Signed-off-by: Paul Holzinger --- Makefile | 2 +- test/system/001-basic.bats | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bef7fe2a2..da8997ea3 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ else BUILD_INFO ?= $(shell date "+$(DATE_FMT)") ISODATE ?= $(shell date --iso-8601) endif -LIBPOD := ${PROJECT}/libpod +LIBPOD := ${PROJECT}/v2/libpod GCFLAGS ?= all=-trimpath=${PWD} ASMFLAGS ?= all=-trimpath=${PWD} LDFLAGS_PODMAN ?= \ diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 71595f419..b12836b9e 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -24,6 +24,13 @@ function setup() { is "${lines[0]}" "Version:[ ]\+[1-9][0-9.]\+" "Version line 1" is "$output" ".*Go Version: \+" "'Go Version' in output" is "$output" ".*API Version: \+" "API version in output" + + # Test that build date is reasonable, e.g. after 2019-01-01 + local built=$(expr "$output" : ".*Built: \+\(.*\)" | head -n1) + local built_t=$(date --date="$built" +%s) + if [ $built_t -lt 1546300800 ]; then + die "Preposterous 'Built' time in podman version: '$built'" + fi } -- cgit v1.2.3-54-g00ecf From ce1389bde7c62ef4eaf9ed8b2f0a68a4c14bbb9d Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 17 Aug 2020 15:58:16 +0200 Subject: abi: fix detection for systemd create a scope everytime we don't own the current cgroup and we are running on systemd. Closes: https://github.com/containers/podman/issues/6734 Signed-off-by: Giuseppe Scrivano --- pkg/domain/infra/abi/system.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 435902ded..043dfe49e 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -70,8 +70,13 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) if err != nil { return err } + + initCommand, err := ioutil.ReadFile("/proc/1/comm") + // On errors, default to systemd + runsUnderSystemd := err != nil || string(initCommand) == "systemd" + unitName := fmt.Sprintf("podman-%d.scope", os.Getpid()) - if conf.Engine.CgroupManager == config.SystemdCgroupsManager { + if runsUnderSystemd || conf.Engine.CgroupManager == config.SystemdCgroupsManager { if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil { logrus.Warnf("Failed to add podman to systemd sandbox cgroup: %v", err) } -- cgit v1.2.3-54-g00ecf From 0ef668878572986951df57fb38596683e5750d07 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Tue, 18 Aug 2020 12:19:28 +0200 Subject: fix podman create/run UTS NS docs Add better error message when using `--pod` and `--hostname`. Improve the docs to better explain the uts hostname relation. Add more valid options for the `--uts` flag. Signed-off-by: Paul Holzinger --- docs/source/markdown/podman-create.1.md | 14 ++++++++------ docs/source/markdown/podman-run.1.md | 9 ++++----- pkg/specgen/container_validate.go | 3 +++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index ffce338b4..00f16d0a3 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -336,7 +336,7 @@ value can be expressed in a time format such as `1m22s`. The default value is ` Container host name -Sets the container host name that is available inside the container. +Sets the container host name that is available inside the container. Can only be used with a private UTS namespace `--uts=private` (default). If `--pod` is specified and the pod shares the UTS namespace (default) the pods hostname will be used. **--help** @@ -862,12 +862,14 @@ Set the user namespace mode for the container. It defaults to the **PODMAN_USER This option is incompatible with --gidmap, --uidmap, --subuid and --subgid -**--uts**=*host* +**--uts**=*mode* -Set the UTS mode for the container - **host**: use the host's UTS namespace inside the container. - **ns**: specify the user namespace to use. - Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. +Set the UTS namespace mode for the container. The following values are supported: + +- **host**: use the host's UTS namespace inside the container. +- **private**: create a new namespace for the container (default). +- **ns:[path]**: run the container in the given existing UTS namespace. +- **container:[container]**: join the UTS namespace of the specified container. **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 25bb44c06..5febf1c66 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -356,7 +356,7 @@ Print usage statement Container host name -Sets the container host name that is available inside the container. +Sets the container host name that is available inside the container. Can only be used with a private UTS namespace `--uts=private` (default). If `--pod` is specified and the pod shares the UTS namespace (default) the pods hostname will be used. **--http-proxy**=**true**|**false** @@ -900,10 +900,9 @@ This option is incompatible with **--gidmap**, **--uidmap**, **--subuid** and ** Set the UTS namespace mode for the container. The following values are supported: - **host**: use the host's UTS namespace inside the container. -- **private**: create a new namespace for the container (default) -- **ns**: use own UTS namespace. - -**NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. +- **private**: create a new namespace for the container (default). +- **ns:[path]**: run the container in the given existing UTS namespace. +- **container:[container]**: join the UTS namespace of the specified container. **--volume**, **-v**[=[[_source-volume_|_host-dir_:]_container-dir_[:_options_]]] diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index a979a7f4a..4dd2ab0b3 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -43,6 +43,9 @@ func (s *SpecGenerator) Validate() error { } // Cannot set hostname and utsns if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { + if s.ContainerBasicConfig.UtsNS.IsPod() { + return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when joining the pod UTS namespace") + } return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when running in the host UTS namespace") } // systemd values must be true, false, or always -- cgit v1.2.3-54-g00ecf From 855ce48a929126eb940196d7bd5581f1532ffacf Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 12:26:33 -0400 Subject: Further release notes updates for v2.0.5 Signed-off-by: Matthew Heon --- RELEASE_NOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9a609ff0d..23c3f7a76 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,6 +3,7 @@ ## 2.0.5 ### Features - Rootless Podman will now add an entry to `/etc/passwd` for the user who ran Podman if run with `--userns=keep-id`. +- The `podman system connection` command has been reworked to support multiple connections, and reenabled for use! ### Changes - Podman's automatic systemd integration (activated by the `--systemd=true` flag, set by default) will now activate for containers using `/usr/local/sbin/init` as their command, instead of just `/usr/sbin/init` and `/sbin/init` (and any path ending in `systemd`). @@ -33,6 +34,10 @@ - Fixed a bug where Podman did not clear `CMD` from the container image if the user overrode `ENTRYPOINT` ([#7115](https://github.com/containers/podman/issues/7115)). - Fixed a bug where error parsing image names were not fully reported (part of the error message containing the exact issue was dropped). - Fixed a bug where the `podman images` command with remote Podman did not support printing image tags in Go templates supplied to the `--format` flag ([#7123](https://github.com/containers/podman/issues/7123)). +- Fixed a bug where the `podman rmi --force` command would not attempt to unmount containers it was removing, which could cause a failure to remove the image. +- Fixed a bug where the `podman generate systemd --new` command could incorrectly quote arguments to Podman that contained whitespace, leading to nonfunctional unit files ([#7285](https://github.com/containers/podman/issues/7285)). +- Fixed a bug where the `podman version` command did not properly include build time and Git commit. +- Fixed a bug where running systemd in a Podman container on a system that did not use the `systemd` cgroup manager would fail ([#6734](https://github.com/containers/podman/issues/6734)). ### API - Fixed a bug where the libpod and compat Build endpoints did not accept the `application/tar` content type (instead only accepting `application/x-tar`) ([#7185](https://github.com/containers/podman/issues/7185)). -- cgit v1.2.3-54-g00ecf From 14379d6dbd6caaf331a86a994c9127a71918d6f4 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 4 Aug 2020 13:26:06 -0500 Subject: remove --latest for all remote commands instead of hiding the latest options for podman-remote or catching an error if podman --remote -l is used, we no longer add the latest option to any remote command. podman will error with a "unknown flag" option. Fixes: #7127 Signed-off-by: Brent Baude --- cmd/podman/root.go | 10 ---------- cmd/podman/validate/args.go | 32 ++++++++++++++++++++------------ cmd/podman/validate/latest.go | 7 +++---- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 7895a5f64..1165f1b84 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -6,7 +6,6 @@ import ( "path" "runtime" "runtime/pprof" - "strconv" "strings" "github.com/containers/common/pkg/config" @@ -112,15 +111,6 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { cfg := registry.PodmanConfig() - // Validate --remote and --latest not given on same command - latest := cmd.Flags().Lookup("latest") - if latest != nil { - value, _ := strconv.ParseBool(latest.Value.String()) - if cfg.Remote && value { - return errors.Errorf("For %s \"--remote\" and \"--latest\", are mutually exclusive flags", cmd.CommandPath()) - } - } - // Prep the engines if _, err := registry.NewImageEngine(cmd, args); err != nil { return err diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index a33f47959..aacb41e69 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/containers/podman/v2/cmd/podman/registry" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -47,17 +48,20 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error { // CheckAllLatestAndCIDFile checks that --all and --latest are used correctly. // If cidfile is set, also check for the --cidfile flag. func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { + var specifiedLatest bool argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !cidfile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("cidfile") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + if !registry.IsRemote() { + specifiedLatest, _ = c.Flags().GetBool("latest") + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !cidfile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("cidfile") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") specifiedCIDFile := false if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { specifiedCIDFile = true @@ -98,17 +102,21 @@ func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool // CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. // If withIDFile is set, also check for the --pod-id-file flag. func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { + var specifiedLatest bool argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !withIDFile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("pod-id-file") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + if !registry.IsRemote() { + // remote clients have no latest flag + specifiedLatest, _ = c.Flags().GetBool("latest") + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !withIDFile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("pod-id-file") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") specifiedPodIDFile := false if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { specifiedPodIDFile = true diff --git a/cmd/podman/validate/latest.go b/cmd/podman/validate/latest.go index 0ebed7227..a9049ca2c 100644 --- a/cmd/podman/validate/latest.go +++ b/cmd/podman/validate/latest.go @@ -7,9 +7,8 @@ import ( func AddLatestFlag(cmd *cobra.Command, b *bool) { // Initialization flag verification - cmd.Flags().BoolVarP(b, "latest", "l", false, - "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") - if registry.IsRemote() { - _ = cmd.Flags().MarkHidden("latest") + if !registry.IsRemote() { + cmd.Flags().BoolVarP(b, "latest", "l", false, + "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") } } -- cgit v1.2.3-54-g00ecf From bcd9b8125c4f042e596b2b9263f60825f32e15aa Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 18 Aug 2020 06:47:19 -0400 Subject: Add support for --connection * override --url and/or --identity fields from containers.conf * --connection flag has higher precedence than ActiveService from containers.conf. Which is set via podman system connection default * Add newline to error message printed on stderr * Added --connection to bash completion and documentation * Updated bindings to query server in case of no path or / Closes #jira-991 Fixes #7276 Signed-off-by: Daniel J Walsh Signed-off-by: Jhon Honce Squashed commits to work around CI issue Signed-off-by: Matthew Heon --- cmd/podman/early_init_linux.go | 2 +- cmd/podman/root.go | 38 +++++++++++++++++++++++++++------ cmd/podman/system/connection/add.go | 2 +- completions/bash/podman | 24 +++++++++++++++------ docs/source/markdown/podman-remote.1.md | 2 +- docs/source/markdown/podman.1.md | 3 +++ 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/cmd/podman/early_init_linux.go b/cmd/podman/early_init_linux.go index b43450a7f..8e2f1bd21 100644 --- a/cmd/podman/early_init_linux.go +++ b/cmd/podman/early_init_linux.go @@ -32,7 +32,7 @@ func setUMask() { func earlyInitHook() { if err := setRLimits(); err != nil { - fmt.Fprint(os.Stderr, "Failed to set rlimits: "+err.Error()) + fmt.Fprintf(os.Stderr, "Failed to set rlimits: %s\n", err.Error()) } setUMask() diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 1165f1b84..44e014906 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -111,6 +111,30 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { cfg := registry.PodmanConfig() + // --connection is not as "special" as --remote so we can wait and process it here + var connErr error + conn := cmd.Root().LocalFlags().Lookup("connection") + if conn != nil && conn.Changed { + cfg.Engine.ActiveService = conn.Value.String() + + var err error + cfg.URI, cfg.Identity, err = cfg.ActiveDestination() + if err != nil { + connErr = errors.Wrap(err, "failed to resolve active destination") + } + + if err := cmd.Root().LocalFlags().Set("url", cfg.URI); err != nil { + connErr = errors.Wrap(err, "failed to override --url flag") + } + + if err := cmd.Root().LocalFlags().Set("identity", cfg.Identity); err != nil { + connErr = errors.Wrap(err, "failed to override --identity flag") + } + } + if connErr != nil { + return connErr + } + // Prep the engines if _, err := registry.NewImageEngine(cmd, args); err != nil { return err @@ -211,10 +235,11 @@ func loggingHook() { func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { cfg := opts.Config - uri, ident := resolveDestination() + srv, uri, ident := resolveDestination() lFlags := cmd.Flags() lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") + lFlags.StringVarP(&opts.Engine.ActiveService, "connection", "c", srv, "Connection to use for remote Podman service") lFlags.StringVar(&opts.URI, "url", uri, "URL to access Podman service (CONTAINER_HOST)") lFlags.StringVar(&opts.Identity, "identity", ident, "path to SSH identity file, (CONTAINER_SSHKEY)") @@ -264,23 +289,24 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { } } -func resolveDestination() (string, string) { +func resolveDestination() (string, string, string) { if uri, found := os.LookupEnv("CONTAINER_HOST"); found { var ident string if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found { ident = v } - return uri, ident + return "", uri, ident } cfg, err := config.ReadCustomConfig() if err != nil { - return registry.DefaultAPIAddress(), "" + logrus.Warning(errors.Wrap(err, "unable to read local containers.conf")) + return "", registry.DefaultAPIAddress(), "" } uri, ident, err := cfg.ActiveDestination() if err != nil { - return registry.DefaultAPIAddress(), "" + return "", registry.DefaultAPIAddress(), "" } - return uri, ident + return cfg.Engine.ActiveService, uri, ident } diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 7522eb190..77a2edf8a 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -95,7 +95,7 @@ func add(cmd *cobra.Command, args []string) error { uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) } - if uri.Path == "" { + if uri.Path == "" || uri.Path == "/" { if uri.Path, err = getUDS(cmd, uri); err != nil { return errors.Wrapf(err, "failed to connect to %q", uri.String()) } diff --git a/completions/bash/podman b/completions/bash/podman index abcf54416..bd72649bb 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -3546,22 +3546,32 @@ _podman_volume() { _podman_podman() { local options_with_args=" - --config -c + --cni-config-dir + --conmon + --connection -c + --events-backend + --hooks-dir + --identity + --log-level + --namespace + --network-cmd-path --root --runroot --storage-driver --storage-opt - --log-level - --namespace - " + --tmpdir + --runtime + --url + " local boolean_options=" --help - -h + --syslog --version + -h + -r, --remote -v - --syslog " - commands=" + commands=" attach auto-update build diff --git a/docs/source/markdown/podman-remote.1.md b/docs/source/markdown/podman-remote.1.md index 0d7be1e5d..23ccaf0e6 100644 --- a/docs/source/markdown/podman-remote.1.md +++ b/docs/source/markdown/podman-remote.1.md @@ -23,7 +23,7 @@ Podman-remote provides a local client interacting with a Podman backend node thr ## GLOBAL OPTIONS -**--connection**=*name* +**--connection**=*name*, **-c** Remote connection name diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 01a054ff4..7839ce8fb 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -31,6 +31,9 @@ Note: CGroup manager is not supported in rootless mode when using CGroups Versio **--cni-config-dir** Path of the configuration directory for CNI networks. (Default: `/etc/cni/net.d`) +**--connection**, **-c** +Connection to use for remote podman (Default connection is configured in `containers.conf`) + **--conmon** Path of the conmon binary (Default path is configured in `containers.conf`) -- cgit v1.2.3-54-g00ecf From 314813c162071ce66c7a39db6c9bac30897af568 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 12:42:35 -0400 Subject: Final set of updates to release notes Signed-off-by: Matthew Heon --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 23c3f7a76..b5eea154e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,7 @@ ### Features - Rootless Podman will now add an entry to `/etc/passwd` for the user who ran Podman if run with `--userns=keep-id`. - The `podman system connection` command has been reworked to support multiple connections, and reenabled for use! +- Podman now has a new global flag, `--connection`, to specify a connection to a remote Podman API instance. ### Changes - Podman's automatic systemd integration (activated by the `--systemd=true` flag, set by default) will now activate for containers using `/usr/local/sbin/init` as their command, instead of just `/usr/sbin/init` and `/sbin/init` (and any path ending in `systemd`). @@ -50,6 +51,7 @@ ### Misc - Updated Buildah to v1.15.1 +- Updated containers/image library to v5.5.2 ## 2.0.4 ### Bugfixes -- cgit v1.2.3-54-g00ecf From de75ae29ff461ebc4932a2ad7047855f51797dda Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 12:52:18 -0400 Subject: Fix imports (podman -> libpod for v2.0 branch) Signed-off-by: Matthew Heon --- cmd/podman/validate/args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index aacb41e69..cf500c0a3 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/pkg/errors" "github.com/spf13/cobra" ) -- cgit v1.2.3-54-g00ecf From 7fc0fbfb657f9a1620e76ea0d48a30b1ee4af4b8 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 14:17:48 -0400 Subject: Fix a system test failure Signed-off-by: Matthew Heon --- test/system/015-help.bats | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/system/015-help.bats b/test/system/015-help.bats index 42d3bd3ec..201b23b5e 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -40,7 +40,9 @@ function check_help() { [ -n "$usage" ] || die "podman $cmd: no Usage message found" # e.g. 'podman ps' should not show 'podman container ps' in usage - is "$usage" " $command_string .*" "Usage string matches command" + # Trailing space in usage handles 'podman system renumber' which + # has no ' [flags]' + is "$usage " " $command_string .*" "Usage string matches command" # If usage ends in '[command]', recurse into subcommands if expr "$usage" : '.*\[command\]$' >/dev/null; then -- cgit v1.2.3-54-g00ecf From f12f2456afe50ebf4622945bebd71e7134b9dd9c Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 14:18:12 -0400 Subject: Fix a Makefile issue Signed-off-by: Matthew Heon --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index da8997ea3..bef7fe2a2 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ else BUILD_INFO ?= $(shell date "+$(DATE_FMT)") ISODATE ?= $(shell date --iso-8601) endif -LIBPOD := ${PROJECT}/v2/libpod +LIBPOD := ${PROJECT}/libpod GCFLAGS ?= all=-trimpath=${PWD} ASMFLAGS ?= all=-trimpath=${PWD} LDFLAGS_PODMAN ?= \ -- cgit v1.2.3-54-g00ecf From 7fc3c25410bd5ee053473ffd5df2209f41840ec0 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 20 Aug 2020 11:07:36 +0200 Subject: fix pod creation with "new:" syntax followup + allow hostname Fixes: 4c75fe3f70ed ("fix pod creation with "new:" syntax") Commit 4c75fe3f70ed passes all net options to the pod but forgot to unset the options for the container creation. This leads to erros when using flags like `--ip` since we tried setting the ip on the pod and container which obviously fails. I didn't notice the bug because we don't throw an error when specifing port bindings on a container which joins the pods network namespace. (#7373) Also allow the use of `--hostname` and pass that option to the pod and unset it for the container. The container has to use the pods hostname anyway. This would error otherwise. Added tests to prevent regression. Signed-off-by: Paul Holzinger --- cmd/podman/containers/create.go | 5 +++++ test/e2e/run_networking_test.go | 32 ++++++++++++++++++++++++++++++-- test/e2e/run_test.go | 8 ++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 0cd56f540..04a6ff9ba 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -292,7 +292,12 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions Infra: true, Net: netOpts, CreateCommand: os.Args, + Hostname: s.ContainerBasicConfig.Hostname, } + // Unset config values we passed to the pod to prevent them being used twice for the container and pod. + s.ContainerBasicConfig.Hostname = "" + s.ContainerNetworkConfig = specgen.ContainerNetworkConfig{} + s.Pod = podName return registry.ContainerEngine().PodCreate(context.Background(), createOptions) } diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 88e289f1f..a33b23fab 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -437,8 +437,8 @@ var _ = Describe("Podman run networking", func() { It("podman run in custom CNI network with --static-ip", func() { SkipIfRootless() netName := "podmantestnetwork" - ipAddr := "10.20.30.128" - create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.20.30.0/24", netName}) + ipAddr := "10.25.30.128" + create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.30.0/24", netName}) create.WaitWithDefaultTimeout() Expect(create.ExitCode()).To(BeZero()) @@ -446,5 +446,33 @@ var _ = Describe("Podman run networking", func() { run.WaitWithDefaultTimeout() Expect(run.ExitCode()).To(BeZero()) Expect(run.OutputToString()).To(ContainSubstring(ipAddr)) + + netrm := podmanTest.Podman([]string{"network", "rm", netName}) + netrm.WaitWithDefaultTimeout() + Expect(netrm.ExitCode()).To(BeZero()) + }) + + It("podman run with new:pod and static-ip", func() { + SkipIfRemote() + SkipIfRootless() + netName := "podmantestnetwork2" + ipAddr := "10.25.40.128" + podname := "testpod" + create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.40.0/24", netName}) + create.WaitWithDefaultTimeout() + Expect(create.ExitCode()).To(BeZero()) + + run := podmanTest.Podman([]string{"run", "-t", "-i", "--rm", "--pod", "new:" + podname, "--net", netName, "--ip", ipAddr, ALPINE, "ip", "addr"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(BeZero()) + Expect(run.OutputToString()).To(ContainSubstring(ipAddr)) + + podrm := podmanTest.Podman([]string{"pod", "rm", "-f", podname}) + podrm.WaitWithDefaultTimeout() + Expect(podrm.ExitCode()).To(BeZero()) + + netrm := podmanTest.Podman([]string{"network", "rm", netName}) + netrm.WaitWithDefaultTimeout() + Expect(netrm.ExitCode()).To(BeZero()) }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 681498ea1..6064be122 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -835,6 +835,14 @@ USER mail` Expect(match).To(BeTrue()) }) + It("podman run --pod new with hostname", func() { + hostname := "abc" + session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", "--hostname", hostname, ALPINE, "cat", "/etc/hostname"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(hostname)) + }) + It("podman run --rm should work", func() { session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "ls"}) session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf