From 975d79aedbf9284b77c47ececd544cb9b0f98d71 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Thu, 10 Feb 2022 13:45:46 -0700 Subject: Add 409 response to swagger godoc When attempting to create a network with a name that already exists, a 409 status code will be returned [NO NEW TESTS NEEDED] Signed-off-by: Jhon Honce --- pkg/api/server/register_networks.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index baa1fe6fb..4466c938f 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -320,6 +320,8 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // $ref: "#/responses/NetworkCreateReport" // 400: // $ref: "#/responses/BadParamError" + // 409: + // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost) -- cgit v1.2.3-54-g00ecf From a72e22160a2cf4fcf868d7095bcdf9eb5e92f3cd Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Thu, 10 Feb 2022 20:37:50 -0500 Subject: Make sure building with relative paths work correctly. Fixes: https://github.com/containers/podman/issues/12763 Signed-off-by: Daniel J Walsh --- pkg/bindings/images/build.go | 2 ++ test/system/070-build.bats | 28 +++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index a363f2c6e..c508cb767 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -352,11 +352,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } c = tmpFile.Name() } + c = filepath.Clean(c) cfDir := filepath.Dir(c) if absDir, err := filepath.EvalSymlinks(cfDir); err == nil { name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator))) c = filepath.Join(absDir, name) } + containerfile, err := filepath.Abs(c) if err != nil { logrus.Errorf("Cannot find absolute path of %v: %v", c, err) diff --git a/test/system/070-build.bats b/test/system/070-build.bats index d5f7365e8..a95acd986 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -88,12 +88,10 @@ EOF containerfile=$PODMAN_TMPDIR/Containerfile cat >$containerfile < /$rand_filename EOF - # The 'apk' command can take a long time to fetch files; bump timeout - PODMAN_TIMEOUT=240 run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile + run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile is "$output" ".*COMMIT" "COMMIT seen in log" run_podman run --rm build_test cat /$rand_filename @@ -188,6 +186,30 @@ EOF run_podman rmi -f build_test $iid } +@test "podman build test -f ./relative" { + rand_filename=$(random_string 20) + rand_content=$(random_string 50) + + tmpdir=$PODMAN_TMPDIR/build-test + mkdir -p $tmpdir + mkdir -p $PODMAN_TMPDIR/reldir + + containerfile=$PODMAN_TMPDIR/reldir/Containerfile + cat >$containerfile < /$rand_filename +EOF + + cd $PODMAN_TMPDIR + run_podman build -t build_test -f ./reldir/Containerfile --format=docker $tmpdir + is "$output" ".*COMMIT" "COMMIT seen in log" + + run_podman run --rm build_test cat /$rand_filename + is "$output" "$rand_content" "reading generated file in image" + + run_podman rmi -f build_test +} + @test "podman build - URLs" { tmpdir=$PODMAN_TMPDIR/build-test mkdir -p $tmpdir -- cgit v1.2.3-54-g00ecf From d77b4f92c013b0d8773750abb75ca7816454e8a3 Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Fri, 11 Feb 2022 10:16:02 +0000 Subject: Fix checkpoint/restore pod tests Checkpoint/restore pod tests are not running with an older runc and now that runc 1.1.0 appears in the repositories it was detected that the tests were failing. This was not detected in CI as CI was not using runc 1.1.0 yet. Signed-off-by: Adrian Reber --- libpod/runtime_ctr.go | 5 +++++ pkg/checkpoint/checkpoint_restore.go | 7 +++++++ test/e2e/checkpoint_test.go | 4 ---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 3799b463f..44364100e 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -192,6 +192,11 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf } // Reset the log path to point to the default ctr.config.LogPath = "" + // Later in validate() the check is for nil. JSONDeepCopy sets it to an empty + // object. Resetting it to nil if it was nil before. + if config.StaticMAC == nil { + ctr.config.StaticMAC = nil + } } ctr.config.Spec = rSpec diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index 1ebd6a455..270b5b6c4 100644 --- a/pkg/checkpoint/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -140,6 +140,13 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod) } ctrConfig.NetNsCtr = infraContainer.ID() + for net, opts := range ctrConfig.Networks { + opts.StaticIPs = nil + opts.StaticMAC = nil + ctrConfig.Networks[net] = opts + } + ctrConfig.StaticIP = nil + ctrConfig.StaticMAC = nil } if ctrConfig.PIDNsCtr != "" { diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 5f1e4b1d1..5abc672e9 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -1081,10 +1081,6 @@ var _ = Describe("Podman checkpoint", func() { }) namespaceCombination := []string{ - "cgroup,ipc,net,uts,pid", - "cgroup,ipc,net,uts", - "cgroup,ipc,net", - "cgroup,ipc", "ipc,net,uts,pid", "ipc,net,uts", "ipc,net", -- cgit v1.2.3-54-g00ecf From 22cfa98605620ee7528fbf84260bcc34762b0cc5 Mon Sep 17 00:00:00 2001 From: Lokesh Mandvekar Date: Fri, 11 Feb 2022 13:34:18 -0500 Subject: enable netavark specific tests These are copies of the CNI tests with modifications wherever neccessary. Signed-off-by: Lokesh Mandvekar --- test/e2e/common_test.go | 14 ++++--- test/e2e/network_create_test.go | 22 +++++++++- test/e2e/network_test.go | 57 ++++++++++++++++++++++++-- test/e2e/run_networking_test.go | 91 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 166 insertions(+), 18 deletions(-) diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index f843a8984..b1cd76d27 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -771,15 +771,15 @@ func SkipIfNotActive(unit string, reason string) { } } -func SkipIfNetavark(p *PodmanTestIntegration) { - if p.NetworkBackend == Netavark { - Skip("This test is not compatible with the netavark network backend") +func SkipIfCNI(p *PodmanTestIntegration) { + if p.NetworkBackend == CNI { + Skip("this test is not compatible with the CNI network backend") } } -func SkipUntilAardvark(p *PodmanTestIntegration) { +func SkipIfNetavark(p *PodmanTestIntegration) { if p.NetworkBackend == Netavark { - Skip("Re-enable when aardvark is functional") + Skip("This test is not compatible with the netavark network backend") } } @@ -1038,3 +1038,7 @@ func ncz(port int) bool { } return false } + +func createNetworkName(name string) string { + return name + stringid.GenerateNonCryptoID()[:10] +} diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 7589adaab..395759ee6 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -330,8 +330,8 @@ var _ = Describe("Podman network create", func() { Expect(nc).To(ExitWithError()) }) - It("podman network create with internal should not have dnsname", func() { - SkipUntilAardvark(podmanTest) + It("podman CNI network create with internal should not have dnsname", func() { + SkipIfNetavark(podmanTest) net := "internal-test" + stringid.GenerateNonCryptoID() nc := podmanTest.Podman([]string{"network", "create", "--internal", net}) nc.WaitWithDefaultTimeout() @@ -348,6 +348,24 @@ var _ = Describe("Podman network create", func() { Expect(nc.OutputToString()).ToNot(ContainSubstring("dnsname")) }) + It("podman Netavark network create with internal should have dnsname", func() { + SkipIfCNI(podmanTest) + net := "internal-test" + stringid.GenerateNonCryptoID() + nc := podmanTest.Podman([]string{"network", "create", "--internal", net}) + nc.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(nc).Should(Exit(0)) + // Not performing this check on remote tests because it is a logrus error which does + // not come back via stderr on the remote client. + if !IsRemote() { + Expect(nc.ErrorToString()).To(BeEmpty()) + } + nc = podmanTest.Podman([]string{"network", "inspect", net}) + nc.WaitWithDefaultTimeout() + Expect(nc).Should(Exit(0)) + Expect(nc.OutputToString()).To(ContainSubstring(`"dns_enabled": true`)) + }) + It("podman network create with invalid name", func() { for _, name := range []string{"none", "host", "bridge", "private", "slirp4netns", "container", "ns"} { nc := podmanTest.Podman([]string{"network", "create", name}) diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index bd30a1f5d..89a9005f5 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -466,10 +466,61 @@ var _ = Describe("Podman network", func() { Expect(lines[1]).To(Equal(netName2)) }) - It("podman network with multiple aliases", func() { - SkipUntilAardvark(podmanTest) + It("podman CNI network with multiple aliases", func() { + SkipIfNetavark(podmanTest) + var worked bool + netName := createNetworkName("aliasTest") + session := podmanTest.Podman([]string{"network", "create", netName}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(netName) + Expect(session).Should(Exit(0)) + + interval := time.Duration(250 * time.Millisecond) + for i := 0; i < 6; i++ { + n := podmanTest.Podman([]string{"network", "exists", netName}) + n.WaitWithDefaultTimeout() + worked = n.ExitCode() == 0 + if worked { + break + } + time.Sleep(interval) + interval *= 2 + } + + top := podmanTest.Podman([]string{"run", "-dt", "--name=web", "--network=" + netName, "--network-alias=web1", "--network-alias=web2", nginx}) + top.WaitWithDefaultTimeout() + Expect(top).Should(Exit(0)) + interval = time.Duration(250 * time.Millisecond) + // Wait for the nginx service to be running + for i := 0; i < 6; i++ { + // Test curl against the container's name + c1 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web"}) + c1.WaitWithDefaultTimeout() + worked = c1.ExitCode() == 0 + if worked { + break + } + time.Sleep(interval) + interval *= 2 + } + Expect(worked).To(BeTrue()) + + // Nginx is now running so no need to do a loop + // Test against the first alias + c2 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web1"}) + c2.WaitWithDefaultTimeout() + Expect(c2).Should(Exit(0)) + + // Test against the second alias + c3 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web2"}) + c3.WaitWithDefaultTimeout() + Expect(c3).Should(Exit(0)) + }) + + It("podman Netavark network with multiple aliases", func() { + SkipIfCNI(podmanTest) var worked bool - netName := "aliasTest" + stringid.GenerateNonCryptoID() + netName := createNetworkName("aliasTest") session := podmanTest.Podman([]string{"network", "create", netName}) session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(netName) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 4c056df10..aa1887f84 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -715,8 +715,8 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(run.OutputToString()).To(ContainSubstring(ipAddr)) }) - It("podman cni network works across user ns", func() { - SkipUntilAardvark(podmanTest) + It("podman CNI network works across user ns", func() { + SkipIfNetavark(podmanTest) netName := stringid.GenerateNonCryptoID() create := podmanTest.Podman([]string{"network", "create", netName}) create.WaitWithDefaultTimeout() @@ -740,6 +740,31 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(log.OutputToString()).To(Equal("podman")) }) + It("podman Netavark network works across user ns", func() { + SkipIfCNI(podmanTest) + netName := createNetworkName("") + create := podmanTest.Podman([]string{"network", "create", netName}) + create.WaitWithDefaultTimeout() + Expect(create).Should(Exit(0)) + defer podmanTest.removeNetwork(netName) + + name := "nc-server" + run := podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "-d", "--name", name, "--net", netName, ALPINE, "nc", "-l", "-p", "9480"}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + + // NOTE: we force the k8s-file log driver to make sure the + // tests are passing inside a container. + run = podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "sh", "-c", fmt.Sprintf("echo podman | nc -w 1 %s.dns.podman 9480", name)}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + + log := podmanTest.Podman([]string{"logs", name}) + log.WaitWithDefaultTimeout() + Expect(log).Should(Exit(0)) + Expect(log.OutputToString()).To(Equal("podman")) + }) + It("podman run with new:pod and static-ip", func() { netName := stringid.GenerateNonCryptoID() ipAddr := "10.25.40.128" @@ -814,14 +839,50 @@ EXPOSE 2004-2005/tcp`, ALPINE) pingTest("--net=private") }) - It("podman run check dnsname plugin", func() { - SkipUntilAardvark(podmanTest) + It("podman run check dnsname plugin with CNI", func() { + SkipIfNetavark(podmanTest) + pod := "testpod" + session := podmanTest.Podman([]string{"pod", "create", "--name", pod}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + net := createNetworkName("IntTest") + session = podmanTest.Podman([]string{"network", "create", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(session).Should(Exit(0)) + + pod2 := "testpod2" + session = podmanTest.Podman([]string{"pod", "create", "--network", net, "--name", pod2}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--name", "con1", "--network", net, ALPINE, "nslookup", "con1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--name", "con2", "--pod", pod, "--network", net, ALPINE, "nslookup", "con2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--name", "con3", "--pod", pod2, ALPINE, "nslookup", "con1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(1)) + Expect(session.ErrorToString()).To(ContainSubstring("can't resolve 'con1'")) + + session = podmanTest.Podman([]string{"run", "--name", "con4", "--network", net, ALPINE, "nslookup", pod2 + ".dns.podman"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + }) + + It("podman run check dnsname plugin with Netavark", func() { + SkipIfCNI(podmanTest) pod := "testpod" session := podmanTest.Podman([]string{"pod", "create", "--name", pod}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - net := "IntTest" + stringid.GenerateNonCryptoID() + net := createNetworkName("IntTest") session = podmanTest.Podman([]string{"network", "create", net}) session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(net) @@ -850,9 +911,23 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(session).Should(Exit(0)) }) - It("podman run check dnsname adds dns search domain", func() { - SkipUntilAardvark(podmanTest) - net := "dnsname" + stringid.GenerateNonCryptoID() + It("podman run check dnsname adds dns search domain with CNI", func() { + SkipIfNetavark(podmanTest) + net := createNetworkName("dnsname") + session := podmanTest.Podman([]string{"network", "create", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--network", net, ALPINE, "cat", "/etc/resolv.conf"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("search dns.podman")) + }) + + It("podman run check dnsname adds dns search domain with Netavark", func() { + SkipIfCNI(podmanTest) + net := createNetworkName("dnsname") session := podmanTest.Podman([]string{"network", "create", net}) session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(net) -- cgit v1.2.3-54-g00ecf From f35e03ec81bcbf8751d288a030c409a34a36e7ef Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Sat, 12 Feb 2022 22:06:00 +0100 Subject: create: Fix key=value annotation in the flag output [NO NEW TESTS NEEDED] Signed-off-by: Morten Linderud --- cmd/podman/common/create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 5fefbacdf..1121806d5 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -36,7 +36,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, createFlags.StringSliceVar( &cf.Annotation, annotationFlagName, []string{}, - "Add annotations to container (key:value)", + "Add annotations to container (key=value)", ) _ = cmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) -- cgit v1.2.3-54-g00ecf From 8ad29421eb739f959d27213718a003b54e6c5cda Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Tue, 15 Feb 2022 16:46:23 +0100 Subject: podman network: add documentation for netavark Add some docs about the different network backends. Also remove the CNI word from network since we refer to either a netavark or CNI config. Signed-off-by: Paul Holzinger --- docs/source/markdown/podman-network-create.1.md | 2 +- docs/source/markdown/podman-network-inspect.1.md | 2 +- docs/source/markdown/podman-network-ls.1.md | 2 +- docs/source/markdown/podman-network-rm.1.md | 2 +- docs/source/markdown/podman-network.1.md | 38 +++++++++++++++--------- docs/source/markdown/podman.1.md | 6 ++-- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md index 5be0c2595..357a06cea 100644 --- a/docs/source/markdown/podman-network-create.1.md +++ b/docs/source/markdown/podman-network-create.1.md @@ -1,7 +1,7 @@ % podman-network-create(1) ## NAME -podman\-network-create - Create a Podman CNI network +podman\-network-create - Create a Podman network ## SYNOPSIS **podman network create** [*options*] name diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md index 726f167e5..ba9cc94d5 100644 --- a/docs/source/markdown/podman-network-inspect.1.md +++ b/docs/source/markdown/podman-network-inspect.1.md @@ -1,7 +1,7 @@ % podman-network-inspect(1) ## NAME -podman\-network\-inspect - Displays the raw CNI network configuration for one or more networks +podman\-network\-inspect - Displays the raw network configuration for one or more networks ## SYNOPSIS **podman network inspect** [*options*] *network* [*network* ...] diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md index 99b734157..72a3b05e2 100644 --- a/docs/source/markdown/podman-network-ls.1.md +++ b/docs/source/markdown/podman-network-ls.1.md @@ -1,7 +1,7 @@ % podman-network-ls(1) ## NAME -podman\-network\-ls - Display a summary of CNI networks +podman\-network\-ls - Display a summary of networks ## SYNOPSIS **podman network ls** [*options*] diff --git a/docs/source/markdown/podman-network-rm.1.md b/docs/source/markdown/podman-network-rm.1.md index 12102ba5a..c6e33c571 100644 --- a/docs/source/markdown/podman-network-rm.1.md +++ b/docs/source/markdown/podman-network-rm.1.md @@ -1,7 +1,7 @@ % podman-network-rm(1) ## NAME -podman\-network\-rm - Remove one or more CNI networks +podman\-network\-rm - Remove one or more networks ## SYNOPSIS **podman network rm** [*options*] [*network...*] diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md index 29ee70139..bc75cce3b 100644 --- a/docs/source/markdown/podman-network.1.md +++ b/docs/source/markdown/podman-network.1.md @@ -1,27 +1,37 @@ % podman-network(1) ## NAME -podman\-network - Manage Podman CNI networks +podman\-network - Manage Podman networks ## SYNOPSIS **podman network** *subcommand* ## DESCRIPTION -The network command manages CNI networks for Podman. +The network command manages networks for Podman. + +Podman supports two network backends [Netavark](https://github.com/containers/netavark) +and [CNI](https://www.cni.dev/). Support for netavark was added in Podman v4.0. To configure +the network backend use the `network_backend`key under the `[Network]` in +**[containers.conf(5)](https://github.com/containers/common/blob/master/docs/containers.conf.5.md)**. +New systems should use netavark by default, to check what backed is used run +`podman info --format {{.Host.NetworkBackend}}`. + +All network commands work for both backends but CNI and Netavark use different config files +so networks have to be created again after a backend change. ## COMMANDS -| Command | Man Page | Description | -| ---------- | -------------------------------------------------------------- | ------------------------------------------------------------------- | -| connect | [podman-network-connect(1)](podman-network-connect.1.md) | Connect a container to a network | -| create | [podman-network-create(1)](podman-network-create.1.md) | Create a Podman CNI network | -| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md) | Disconnect a container from a network | -| exists | [podman-network-exists(1)](podman-network-exists.1.md) | Check if the given network exists | -| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw CNI network configuration for one or more networks | -| ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of CNI networks | -| prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks | -| reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers | -| rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more CNI networks | +| Command | Man Page | Description | +| ---------- | -------------------------------------------------------------- | --------------------------------------------------------------- | +| connect | [podman-network-connect(1)](podman-network-connect.1.md) | Connect a container to a network | +| create | [podman-network-create(1)](podman-network-create.1.md) | Create a Podman network | +| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md) | Disconnect a container from a network | +| exists | [podman-network-exists(1)](podman-network-exists.1.md) | Check if the given network exists | +| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw network configuration for one or more networks | +| ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of networks | +| prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks | +| reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers | +| rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more networks | ## SEE ALSO -**[podman(1)](podman.1.md)** +**[podman(1)](podman.1.md)**, **[containers.conf(5)](https://github.com/containers/common/blob/main/docs/containers.conf.5.md)** diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index a77e1ecbd..b318001e4 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -300,7 +300,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-events(1)](podman-events.1.md) | Monitor Podman events | | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | -| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. | +| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. | | [podman-healthcheck(1)](podman-healthcheck.1.md) | Manage healthchecks for containers | | [podman-history(1)](podman-history.1.md) | Show the history of an image. | | [podman-image(1)](podman-image.1.md) | Manage images. | @@ -317,9 +317,9 @@ the exit codes follow the `chroot` standard, see below: | [podman-machine(1)](podman-machine.1.md) | Manage Podman's virtual machine | | [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. | | [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. | -| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. | +| [podman-network(1)](podman-network.1.md) | Manage Podman networks. | | [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. | -| [podman-play(1)](podman-play.1.md) | Play containers, pods or volumes based on a structured input file. | +| [podman-play(1)](podman-play.1.md) | Play containers, pods or volumes based on a structured input file. | | [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. | | [podman-port(1)](podman-port.1.md) | List port mappings for a container. | | [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. | -- cgit v1.2.3-54-g00ecf From 77e51e188c2519ee8b58e79548dd5c8bcb31221e Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 16 Feb 2022 10:33:57 +0100 Subject: e2e: merge after/since image-filter tests Merge the two tests to speed up testing. Both built the exact same images. Signed-off-by: Valentin Rothberg --- test/e2e/images_test.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 6a534c9c8..d34c911ad 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -186,25 +186,21 @@ WORKDIR /test Expect(result.OutputToString()).To(Equal("/test")) }) - It("podman images filter since image", func() { + It("podman images filter since/after image", func() { dockerfile := `FROM scratch ` podmanTest.BuildImage(dockerfile, "foobar.com/one:latest", "false") podmanTest.BuildImage(dockerfile, "foobar.com/two:latest", "false") podmanTest.BuildImage(dockerfile, "foobar.com/three:latest", "false") + + // `since` filter result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "since=foobar.com/one:latest"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToStringArray()).To(HaveLen(2)) - }) - It("podman image list filter after image", func() { - dockerfile := `FROM scratch -` - podmanTest.BuildImage(dockerfile, "foobar.com/one:latest", "false") - podmanTest.BuildImage(dockerfile, "foobar.com/two:latest", "false") - podmanTest.BuildImage(dockerfile, "foobar.com/three:latest", "false") - result := podmanTest.Podman([]string{"image", "list", "-q", "-f", "after=foobar.com/one:latest"}) + // `after` filter + result = podmanTest.Podman([]string{"image", "list", "-q", "-f", "after=foobar.com/one:latest"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToStringArray()).Should(HaveLen(2), "list filter output: %q", result.OutputToString()) -- cgit v1.2.3-54-g00ecf From bd8ac0017ef61144920c305af5cc9bae6803d69b Mon Sep 17 00:00:00 2001 From: Patrycja Guzik Date: Tue, 15 Feb 2022 23:54:45 +0100 Subject: Unify ls --filter docs for networks and pods Signed-off-by: Patrycja Guzik #13078 follow-up --- docs/source/markdown/podman-network-ls.1.md | 34 +++++++++++--------- docs/source/markdown/podman-pod-ps.1.md | 48 +++++++++++++++++------------ 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md index 72a3b05e2..d5bdb6a39 100644 --- a/docs/source/markdown/podman-network-ls.1.md +++ b/docs/source/markdown/podman-network-ls.1.md @@ -12,20 +12,26 @@ Displays a list of existing podman networks. ## OPTIONS #### **--filter**, **-f**=*filter=value* -Filter output based on conditions given. -Multiple filters can be given with multiple uses of the --filter option. -Filters with the same key work inclusive with the only exception being -`label` which is exclusive. Filters with different keys always work exclusive. - -Valid filters are listed below: - -| **Filter** | **Description** | -| ---------- | ----------------------------------------------------------------- | -| name | [Name] Network name (accepts regex) | -| id | [ID] Full or partial network ID | -| label | [Key] or [Key=Value] Label assigned to a network | -| driver | [Driver] `bridge` or ,`macvlan` is supported | -| until | [Until] Show all networks that were created before the given time | +Provide filter values. + +The *filters* argument format is of `key=value`. If there is more than one *filter*, then pass multiple OPTIONS: **--filter** *foo=bar* **--filter** *bif=baz*. + +Supported filters: + +| **Filter** | **Description** | +| ---------- | ------------------------------------------------------------------------------------------------ | +| driver | Filter by driver type. | +| id | Filter by full or partial network ID. | +| label | Filter by network with (or without, in the case of label!=[...] is used) the specified labels. | +| name | Filter by network name (accepts `regex`). | +| until | Filter by networks created before given timestamp. | + + +The `driver` filter accepts values: `bridge`, `macvlan`, `ipvlan`. + +The `label` *filter* accepts two formats. One is the `label`=*key* or `label`=*key*=*value*, which shows images with the specified labels. The other format is the `label!`=*key* or `label!`=*key*=*value*, which shows images without the specified labels. + +The `until` *filter* can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time. #### **--format**=*format* diff --git a/docs/source/markdown/podman-pod-ps.1.md b/docs/source/markdown/podman-pod-ps.1.md index a0581df50..8a9c3f7cc 100644 --- a/docs/source/markdown/podman-pod-ps.1.md +++ b/docs/source/markdown/podman-pod-ps.1.md @@ -86,25 +86,35 @@ Default: created #### **--filter**, **-f**=*filter* -Filter output based on conditions given. -Multiple filters can be given with multiple uses of the --filter flag. -Filters with the same key work inclusive with the only exception being -`label` which is exclusive. Filters with different keys always work exclusive. - -Valid filters are listed below: - -| **Filter** | **Description** | -| ---------- | ------------------------------------------------------------------------------------- | -| id | [ID] Pod's ID (accepts regex) | -| name | [Name] Pod's name (accepts regex) | -| label | [Key] or [Key=Value] Label assigned to a container | -| until | Only list pods created before given timestamp | -| status | Pod's status: `stopped`, `running`, `paused`, `exited`, `dead`, `created`, `degraded` | -| network | [Network] name or full ID of network | -| ctr-names | Container name within the pod (accepts regex) | -| ctr-ids | Container ID within the pod (accepts regex) | -| ctr-status | Container status within the pod | -| ctr-number | Number of containers in the pod | +Provide filter values. + +The *filters* argument format is of `key=value`. If there is more than one *filter*, then pass multiple OPTIONS: **--filter** *foo=bar* **--filter** *bif=baz*. + +Supported filters: + +| Filter | Description | +| ---------- | -------------------------------------------------------------------------------------------------- | +| *ctr-ids* | Filter by container ID within the pod. | +| *ctr-names* | Filter by container name within the pod. | +| *ctr-number*| Filter by number of containers in the pod. | +| *ctr-status*| Filter by container status within the pod. | +| *id* | Filter by pod ID. | +| *label* | Filter by container with (or without, in the case of label!=[...] is used) the specified labels. | +| *name* | Filter by pod name. | +| *network* | Filter by network name or full ID of network. | +| *status* | Filter by pod status. | +| *until* | Filter by pods created before given timestamp. | + +The `ctr-ids`, `ctr-names`, `id`, `name` filters accept `regex` format. + +The `ctr-status` filter accepts values: `created`, `running`, `paused`, `stopped`, `exited`, `unknown`. + +The `label` *filter* accepts two formats. One is the `label`=*key* or `label`=*key*=*value*, which removes containers with the specified labels. The other format is the `label!`=*key* or `label!`=*key*=*value*, which removes containers without the specified labels. + +The `until` *filter* can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time. + +The `status` filter accepts values: `stopped`, `running`, `paused`, `exited`, `dead`, `created`, `degraded`. + #### **--help**, **-h** -- cgit v1.2.3-54-g00ecf From 809da6b0ba8619bd8565a87388cf2cafad33cf99 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 16 Feb 2022 00:48:02 -0600 Subject: Update to podman4 copr stream Signed-off-by: Jason T. Greene --- pkg/machine/wsl/machine.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 3edf3ddf6..6862e3579 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -335,6 +335,16 @@ func provisionWSLDist(v *MachineVM) (string, error) { return "", errors.Wrap(err, "package upgrade on guest OS failed") } + fmt.Println("Enabling Copr") + if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "-y", "'dnf-command(copr)'"); err != nil { + return "", errors.Wrap(err, "enabling copr failed") + } + + fmt.Println("Enabling podman4 repo") + if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "-y", "copr", "enable", "rhcontainerbot/podman4"); err != nil { + return "", errors.Wrap(err, "enabling copr failed") + } + if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil { return "", errors.Wrap(err, "package installation on guest OS failed") -- cgit v1.2.3-54-g00ecf From 2128236da5f61f705c69b62fcac3eb7315e00a01 Mon Sep 17 00:00:00 2001 From: esendjer Date: Fri, 11 Feb 2022 02:58:53 +0500 Subject: ignition: propagate proxy settings from a host into a vm Set proxy settings (such as `HTTP_PROXY`, and others) for the whole guest OS with setting up `DefaultEnvironment` with a `systemd` configuration file `default-env.conf`, a `profile.d` scenario file - `default-env.sh` and a `environment.d` configuration file `default-env.conf` The **actual** environment variables are read by podman at a start, then they are encrypted with base64 into a single string and after are provided into a VM through QEMU Firmware Configuration (fw_cfg) Device Inside a VM a systemd service `envset-fwcfg.service` reads the providead encrypted string from fw_cfg, decrypts and then adds to the files - `/etc/systemd/system.conf.d/default-env.conf` - `/etc/profile.d/default-env.sh` - `/etc/environment.d/default-env.conf` At the end this service execute `systemctl daemon-reload` to propagate new variables for systemd manager [NO NEW TESTS NEEDED] Closes #13168 Signed-off-by: esendjer --- pkg/machine/ignition.go | 85 +++++++++++++++++++++++++++++++++------------ pkg/machine/qemu/machine.go | 16 +++++++++ 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index 206c9144f..47b1836f0 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -145,7 +145,42 @@ ExecStartPost=/bin/touch /var/lib/%N.stamp [Install] WantedBy=default.target - ` +` + // This service gets environment variables that are provided + // through qemu fw_cfg and then sets them into systemd/system.conf.d, + // profile.d and environment.d files + // + // Currently, it is used for propagating + // proxy settings e.g. HTTP_PROXY and others, on a start avoiding + // a need of re-creating/re-initiating a VM + envset := `[Unit] +Description=Environment setter from QEMU FW_CFG +[Service] +Type=oneshot +RemainAfterExit=yes +Environment=FWCFGRAW=/sys/firmware/qemu_fw_cfg/by_name/opt/com.coreos/environment/raw +Environment=SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf +Environment=ENVD_CONF=/etc/environment.d/default-env.conf +Environment=PROFILE_CONF=/etc/profile.d/default-env.sh +ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} &&\ + echo "[Manager]\n#Got from QEMU FW_CFG\nDefaultEnvironment=$(/usr/bin/base64 -d ${FWCFGRAW} | sed -e "s+|+ +g")\n" > ${SYSTEMD_CONF} ||\ + echo "[Manager]\n#Got nothing from QEMU FW_CFG\n#DefaultEnvironment=\n" > ${SYSTEMD_CONF}' +ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\ + echo "#Got from QEMU FW_CFG"> ${ENVD_CONF};\ + IFS="|";\ + for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\ + echo "$iprxy" >> ${ENVD_CONF}; done ) || \ + echo "#Got nothing from QEMU FW_CFG"> ${ENVD_CONF}' +ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\ + echo "#Got from QEMU FW_CFG"> ${PROFILE_CONF};\ + IFS="|";\ + for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\ + echo "export $iprxy" >> ${PROFILE_CONF}; done ) || \ + echo "#Got nothing from QEMU FW_CFG"> ${PROFILE_CONF}' +ExecStartPost=/usr/bin/systemctl daemon-reload +[Install] +WantedBy=sysinit.target +` _ = ready ignSystemd := Systemd{ Units: []Unit{ @@ -173,6 +208,11 @@ WantedBy=default.target Name: "remove-moby.service", Contents: &deMoby, }, + { + Enabled: boolToPtr(true), + Name: "envset-fwcfg.service", + Contents: &envset, + }, }} ignConfig := Config{ Ignition: ignVersion, @@ -226,6 +266,25 @@ func getDirs(usrName string) []Directory { DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)}, }) + // The directory is used by envset-fwcfg.service + // for propagating environment variables that got + // from a host + dirs = append(dirs, Directory{ + Node: Node{ + Group: getNodeGrp("root"), + Path: "/etc/systemd/system.conf.d", + User: getNodeUsr("root"), + }, + DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)}, + }, Directory{ + Node: Node{ + Group: getNodeGrp("root"), + Path: "/etc/environment.d", + User: getNodeUsr("root"), + }, + DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)}, + }) + return dirs } @@ -363,24 +422,6 @@ Delegate=memory pids cpu io }, }) - setProxyOpts := getProxyVariables() - if setProxyOpts != "" { - files = append(files, File{ - Node: Node{ - Group: getNodeGrp("root"), - Path: "/etc/profile.d/proxy-opts.sh", - User: getNodeUsr("root"), - }, - FileEmbedded1: FileEmbedded1{ - Append: nil, - Contents: Resource{ - Source: encodeDataURLPtr(setProxyOpts), - }, - Mode: intToPtr(0644), - }, - }) - } - setDockerHost := `export DOCKER_HOST="unix://$(podman info -f "{{.Host.RemoteSocket.Path}}")" ` @@ -506,11 +547,11 @@ func prepareCertFile(path string, name string) (File, error) { return file, nil } -func getProxyVariables() string { - proxyOpts := "" +func GetProxyVariables() map[string]string { + proxyOpts := make(map[string]string) for _, variable := range config.ProxyEnv { if value, ok := os.LookupEnv(variable); ok { - proxyOpts += fmt.Sprintf("\n export %s=%s", variable, value) + proxyOpts[variable] = value } } return proxyOpts diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index eb7b35ece..240442e49 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -1,9 +1,11 @@ +//go:build (amd64 && !windows) || (arm64 && !windows) // +build amd64,!windows arm64,!windows package qemu import ( "bufio" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -123,6 +125,20 @@ func (p *Provider) LoadVMByName(name string) (machine.VM, error) { return nil, err } err = json.Unmarshal(b, vm) + + // It is here for providing the ability to propagate + // proxy settings (e.g. HTTP_PROXY and others) on a start + // and avoid a need of re-creating/re-initiating a VM + if proxyOpts := machine.GetProxyVariables(); len(proxyOpts) > 0 { + proxyStr := "name=opt/com.coreos/environment,string=" + var proxies string + for k, v := range proxyOpts { + proxies = fmt.Sprintf("%s%s=\"%s\"|", proxies, k, v) + } + proxyStr = fmt.Sprintf("%s%s", proxyStr, base64.StdEncoding.EncodeToString([]byte(proxies))) + vm.CmdLine = append(vm.CmdLine, "-fw_cfg", proxyStr) + } + logrus.Debug(vm.CmdLine) return vm, err } -- cgit v1.2.3-54-g00ecf From f71dfcb5dabf288073e81eb9b19013e4eb6f22cb Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Sat, 29 Jan 2022 03:10:28 -0600 Subject: Initial implementation of mac forwarding using a privileged docker sock claim helper Signed-off-by: Jason T. Greene --- Makefile | 12 +- cmd/podman-mac-helper/install.go | 244 ++++++++++++++++++++++++++++++++++ cmd/podman-mac-helper/main.go | 149 +++++++++++++++++++++ cmd/podman-mac-helper/service.go | 85 ++++++++++++ cmd/podman-mac-helper/uninstall.go | 60 +++++++++ pkg/machine/qemu/claim_darwin.go | 63 +++++++++ pkg/machine/qemu/claim_unsupported.go | 20 +++ pkg/machine/qemu/machine.go | 163 +++++++++++++++++++++-- 8 files changed, 787 insertions(+), 9 deletions(-) create mode 100644 cmd/podman-mac-helper/install.go create mode 100644 cmd/podman-mac-helper/main.go create mode 100644 cmd/podman-mac-helper/service.go create mode 100644 cmd/podman-mac-helper/uninstall.go create mode 100644 pkg/machine/qemu/claim_darwin.go create mode 100644 pkg/machine/qemu/claim_unsupported.go diff --git a/Makefile b/Makefile index 9e9b3676e..0a5389ce9 100644 --- a/Makefile +++ b/Makefile @@ -376,13 +376,23 @@ podman-winpath: .gopathok $(SOURCES) go.mod go.sum ./cmd/winpath .PHONY: podman-remote-darwin -podman-remote-darwin: ## Build podman-remote for macOS +podman-remote-darwin: podman-mac-helper ## Build podman-remote for macOS $(MAKE) \ CGO_ENABLED=$(DARWIN_GCO) \ GOOS=darwin \ GOARCH=$(GOARCH) \ bin/darwin/podman +.PHONY: podman-mac-helper +podman-mac-helper: ## Build podman-mac-helper for macOS + CGO_ENABLED=0 \ + GOOS=darwin \ + GOARCH=$(GOARCH) \ + $(GO) build \ + $(BUILDFLAGS) \ + -o bin/darwin/podman-mac-helper \ + ./cmd/podman-mac-helper + bin/rootlessport: .gopathok $(SOURCES) go.mod go.sum CGO_ENABLED=$(CGO_ENABLED) \ $(GO) build \ diff --git a/cmd/podman-mac-helper/install.go b/cmd/podman-mac-helper/install.go new file mode 100644 index 000000000..7f623ecb6 --- /dev/null +++ b/cmd/podman-mac-helper/install.go @@ -0,0 +1,244 @@ +//go:build darwin +// +build darwin + +package main + +import ( + "bytes" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "syscall" + "text/template" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + rwx_rx_rx = 0755 + rw_r_r = 0644 +) + +const launchConfig = ` + + + + Label + com.github.containers.podman.helper-{{.User}} + ProgramArguments + + {{.Program}} + service + {{.Target}} + + inetdCompatibility + + Wait + + + UserName + root + Sockets + + Listeners + + SockFamily + Unix + SockPathName + /private/var/run/podman-helper-{{.User}}.socket + SockPathOwner + {{.UID}} + SockPathMode + + 384 + SockType + stream + + + + +` + +type launchParams struct { + Program string + User string + UID string + Target string +} + +var installCmd = &cobra.Command{ + Use: "install", + Short: "installs the podman helper agent", + Long: "installs the podman helper agent, which manages the /var/run/docker.sock link", + PreRun: silentUsage, + RunE: install, +} + +func init() { + addPrefixFlag(installCmd) + rootCmd.AddCommand(installCmd) +} + +func install(cmd *cobra.Command, args []string) error { + userName, uid, homeDir, err := getUser() + if err != nil { + return err + } + + labelName := fmt.Sprintf("com.github.containers.podman.helper-%s.plist", userName) + fileName := filepath.Join("/Library", "LaunchDaemons", labelName) + + if _, err := os.Stat(fileName); err == nil || !os.IsNotExist(err) { + return errors.New("helper is already installed, uninstall first") + } + + prog, err := installExecutable(userName) + if err != nil { + return err + } + + target := filepath.Join(homeDir, ".local", "share", "containers", "podman", "machine", "podman.sock") + var buf bytes.Buffer + t := template.Must(template.New("launchdConfig").Parse(launchConfig)) + err = t.Execute(&buf, launchParams{prog, userName, uid, target}) + if err != nil { + return err + } + + file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r) + if err != nil { + return errors.Wrap(err, "error creating helper plist file") + } + defer file.Close() + _, err = buf.WriteTo(file) + if err != nil { + return err + } + + if err = runDetectErr("launchctl", "load", fileName); err != nil { + return errors.Wrap(err, "launchctl failed loading service") + } + + return nil +} + +func restrictRecursive(targetDir string, until string) error { + for targetDir != until && len(targetDir) > 1 { + info, err := os.Lstat(targetDir) + if err != nil { + return err + } + if info.Mode()&fs.ModeSymlink != 0 { + return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir) + } + if err = os.Chown(targetDir, 0, 0); err != nil { + return errors.Wrap(err, "could not update ownership of helper path") + } + if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil { + return errors.Wrap(err, "could not update permissions of helper path") + } + targetDir = filepath.Dir(targetDir) + } + + return nil +} + +func verifyRootDeep(path string) error { + path = filepath.Clean(path) + current := "/" + segs := strings.Split(path, "/") + depth := 0 + for i := 1; i < len(segs); i++ { + seg := segs[i] + current = filepath.Join(current, seg) + info, err := os.Lstat(current) + if err != nil { + return err + } + + stat := info.Sys().(*syscall.Stat_t) + if stat.Uid != 0 { + return errors.Errorf("installation target path must be solely owned by root: %s is not", current) + } + + if info.Mode()&fs.ModeSymlink != 0 { + target, err := os.Readlink(current) + if err != nil { + return err + } + + targetParts := strings.Split(target, "/") + segs = append(targetParts, segs[i+1:]...) + + if depth++; depth > 1000 { + return errors.New("reached max recursion depth, link structure is cyclical or too complex") + } + + if !filepath.IsAbs(target) { + current = filepath.Dir(current) + i = -1 // Start at 0 + } else { + current = "/" + i = 0 // Skip empty first segment + } + } + } + + return nil +} + +func installExecutable(user string) (string, error) { + // Since the installed executable runs as root, as a precaution verify root ownership of + // the entire installation path, and utilize sticky + read only perms for the helper path + // suffix. The goal is to help users harden against privilege escalation from loose + // filesystem permissions. + // + // Since userpsace package management tools, such as brew, delegate management of system + // paths to standard unix users, the daemon executable is copied into a separate more + // restricted area of the filesystem. + if err := verifyRootDeep(installPrefix); err != nil { + return "", err + } + + targetDir := filepath.Join(installPrefix, "podman", "helper", user) + if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil { + return "", errors.Wrap(err, "could not create helper directory structure") + } + + // Correct any incorrect perms on previously existing directories and verify no symlinks + if err := restrictRecursive(targetDir, installPrefix); err != nil { + return "", err + } + + exec, err := os.Executable() + if err != nil { + return "", err + } + install := filepath.Join(targetDir, filepath.Base(exec)) + + return install, copyFile(install, exec, rwx_rx_rx) +} + +func copyFile(dest string, source string, perms fs.FileMode) error { + in, err := os.Open(source) + if err != nil { + return err + } + + defer in.Close() + out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms) + if err != nil { + return err + } + + defer out.Close() + if _, err := io.Copy(out, in); err != nil { + return err + } + + return nil +} diff --git a/cmd/podman-mac-helper/main.go b/cmd/podman-mac-helper/main.go new file mode 100644 index 000000000..8d995519f --- /dev/null +++ b/cmd/podman-mac-helper/main.go @@ -0,0 +1,149 @@ +//go:build darwin +// +build darwin + +package main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + defaultPrefix = "/usr/local" + dockerSock = "/var/run/docker.sock" +) + +var installPrefix string + +var rootCmd = &cobra.Command{ + Use: "podman-mac-helper", + Short: "A system helper to manage docker.sock", + Long: `podman-mac-helper is a system helper service and tool for managing docker.sock `, + CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, + SilenceErrors: true, +} + +// Note, this code is security sensitive since it runs under privilege. +// Limit actions to what is strictly necessary, and take appropriate +// safeguards +// +// After installation the service call is ran under launchd in a nowait +// inetd style fashion, so stdin, stdout, and stderr are all pointing to +// an accepted connection +// +// This service is installed once per user and will redirect +// /var/run/docker to the fixed user-assigned unix socket location. +// +// Control communication is restricted to each user specific service via +// unix file permissions + +func main() { + if os.Geteuid() != 0 { + fmt.Printf("This command must be ran as root via sudo or osascript\n") + os.Exit(1) + } + + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) + } +} + +func getUserInfo(name string) (string, string, string, error) { + // We exec id instead of using user.Lookup to remain compat + // with CGO disabled. + cmd := exec.Command("/usr/bin/id", "-P", name) + output, err := cmd.StdoutPipe() + if err != nil { + return "", "", "", err + } + + if err := cmd.Start(); err != nil { + return "", "", "", err + } + + entry := readCapped(output) + elements := strings.Split(entry, ":") + if len(elements) < 9 || elements[0] != name { + return "", "", "", errors.New("Could not lookup user") + } + + return elements[0], elements[2], elements[8], nil +} + +func getUser() (string, string, string, error) { + name, found := os.LookupEnv("SUDO_USER") + if !found { + name, found = os.LookupEnv("USER") + if !found { + return "", "", "", errors.New("could not determine user") + } + } + + _, uid, home, err := getUserInfo(name) + if err != nil { + return "", "", "", fmt.Errorf("could not lookup user: %s", name) + } + id, err := strconv.Atoi(uid) + if err != nil { + return "", "", "", fmt.Errorf("invalid uid for user: %s", name) + } + if id == 0 { + return "", "", "", fmt.Errorf("unexpected root user") + } + + return name, uid, home, nil +} + +// Used for commands that don't return a proper exit code +func runDetectErr(name string, args ...string) error { + cmd := exec.Command(name, args...) + errReader, err := cmd.StderrPipe() + if err != nil { + return err + } + + err = cmd.Start() + if err == nil { + errString := readCapped(errReader) + if len(errString) > 0 { + re := regexp.MustCompile(`\r?\n`) + err = errors.New(re.ReplaceAllString(errString, ": ")) + } + } + + if werr := cmd.Wait(); werr != nil { + err = werr + } + + return err +} + +func readCapped(reader io.Reader) string { + // Cap output + buffer := make([]byte, 2048) + n, _ := io.ReadFull(reader, buffer) + _, _ = io.Copy(ioutil.Discard, reader) + if n > 0 { + return string(buffer[:n]) + } + + return "" +} + +func addPrefixFlag(cmd *cobra.Command) { + cmd.Flags().StringVar(&installPrefix, "prefix", defaultPrefix, "Sets the install location prefix") +} + +func silentUsage(cmd *cobra.Command, args []string) { + cmd.SilenceUsage = true + cmd.SilenceErrors = true +} diff --git a/cmd/podman-mac-helper/service.go b/cmd/podman-mac-helper/service.go new file mode 100644 index 000000000..65cd89f34 --- /dev/null +++ b/cmd/podman-mac-helper/service.go @@ -0,0 +1,85 @@ +//go:build darwin +// +build darwin + +package main + +import ( + "fmt" + "io" + "io/fs" + "os" + "time" + + "github.com/spf13/cobra" +) + +const ( + trigger = "GO\n" + fail = "NO" + success = "OK" +) + +var serviceCmd = &cobra.Command{ + Use: "service", + Short: "services requests", + Long: "services requests", + PreRun: silentUsage, + Run: serviceRun, + Hidden: true, +} + +func init() { + rootCmd.AddCommand(serviceCmd) +} + +func serviceRun(cmd *cobra.Command, args []string) { + info, err := os.Stdin.Stat() + if err != nil || info.Mode()&fs.ModeSocket == 0 { + fmt.Fprintln(os.Stderr, "This is an internal command that is not intended for standard terminal usage") + os.Exit(1) + } + + os.Exit(service()) +} + +func service() int { + defer os.Stdout.Close() + defer os.Stdin.Close() + defer os.Stderr.Close() + if len(os.Args) < 3 { + fmt.Print(fail) + return 1 + } + target := os.Args[2] + + request := make(chan bool) + go func() { + buf := make([]byte, 3) + _, err := io.ReadFull(os.Stdin, buf) + request <- err == nil && string(buf) == trigger + }() + + valid := false + select { + case valid = <-request: + case <-time.After(5 * time.Second): + } + + if !valid { + fmt.Println(fail) + return 2 + } + + err := os.Remove(dockerSock) + if err == nil || os.IsNotExist(err) { + err = os.Symlink(target, dockerSock) + } + + if err != nil { + fmt.Print(fail) + return 3 + } + + fmt.Print(success) + return 0 +} diff --git a/cmd/podman-mac-helper/uninstall.go b/cmd/podman-mac-helper/uninstall.go new file mode 100644 index 000000000..f72d0efd1 --- /dev/null +++ b/cmd/podman-mac-helper/uninstall.go @@ -0,0 +1,60 @@ +//go:build darwin +// +build darwin + +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var uninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "uninstalls the podman helper agent", + Long: "uninstalls the podman helper agent, which manages the /var/run/docker.sock link", + PreRun: silentUsage, + RunE: uninstall, +} + +func init() { + addPrefixFlag(uninstallCmd) + rootCmd.AddCommand(uninstallCmd) +} + +func uninstall(cmd *cobra.Command, args []string) error { + userName, _, _, err := getUser() + if err != nil { + return err + } + + labelName := fmt.Sprintf("com.github.containers.podman.helper-%s", userName) + fileName := filepath.Join("/Library", "LaunchDaemons", labelName+".plist") + + if err = runDetectErr("launchctl", "unload", fileName); err != nil { + // Try removing the service by label in case the service is half uninstalled + if rerr := runDetectErr("launchctl", "remove", labelName); rerr != nil { + // Exit code 3 = no service to remove + if exitErr, ok := rerr.(*exec.ExitError); !ok || exitErr.ExitCode() != 3 { + fmt.Fprintf(os.Stderr, "Warning: service unloading failed: %s\n", err.Error()) + fmt.Fprintf(os.Stderr, "Warning: remove also failed: %s\n", rerr.Error()) + } + } + } + + if err := os.Remove(fileName); err != nil { + if !os.IsNotExist(err) { + return errors.Errorf("could not remove plist file: %s", fileName) + } + } + + helperPath := filepath.Join(installPrefix, "podman", "helper", userName) + if err := os.RemoveAll(helperPath); err != nil { + return errors.Errorf("could not remove helper binary path: %s", helperPath) + } + return nil +} diff --git a/pkg/machine/qemu/claim_darwin.go b/pkg/machine/qemu/claim_darwin.go new file mode 100644 index 000000000..66aed9ad8 --- /dev/null +++ b/pkg/machine/qemu/claim_darwin.go @@ -0,0 +1,63 @@ +package qemu + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "os/user" + "path/filepath" + "time" +) + +func dockerClaimSupported() bool { + return true +} + +func dockerClaimHelperInstalled() bool { + u, err := user.Current() + if err != nil { + return false + } + + labelName := fmt.Sprintf("com.github.containers.podman.helper-%s", u.Username) + fileName := filepath.Join("/Library", "LaunchDaemons", labelName+".plist") + info, err := os.Stat(fileName) + return err == nil && info.Mode().IsRegular() +} + +func claimDockerSock() bool { + u, err := user.Current() + if err != nil { + return false + } + + helperSock := fmt.Sprintf("/var/run/podman-helper-%s.socket", u.Username) + con, err := net.DialTimeout("unix", helperSock, time.Second*5) + if err != nil { + return false + } + _ = con.SetWriteDeadline(time.Now().Add(time.Second * 5)) + _, err = fmt.Fprintln(con, "GO") + if err != nil { + return false + } + _ = con.SetReadDeadline(time.Now().Add(time.Second * 5)) + read, err := ioutil.ReadAll(con) + + return err == nil && string(read) == "OK" +} + +func findClaimHelper() string { + exe, err := os.Executable() + if err != nil { + return "" + } + + exe, err = filepath.EvalSymlinks(exe) + if err != nil { + return "" + } + + return filepath.Join(filepath.Dir(exe), "podman-mac-helper") +} diff --git a/pkg/machine/qemu/claim_unsupported.go b/pkg/machine/qemu/claim_unsupported.go new file mode 100644 index 000000000..e0b3dd3d3 --- /dev/null +++ b/pkg/machine/qemu/claim_unsupported.go @@ -0,0 +1,20 @@ +//go:build !darwin && !windows +// +build !darwin,!windows + +package qemu + +func dockerClaimHelperInstalled() bool { + return false +} + +func claimDockerSock() bool { + return false +} + +func dockerClaimSupported() bool { + return false +} + +func findClaimHelper() string { + return "" +} diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 240442e49..81a6b4935 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -6,10 +6,13 @@ package qemu import ( "bufio" "encoding/base64" + "context" "encoding/json" "fmt" + "io/fs" "io/ioutil" "net" + "net/http" "os" "os/exec" "path/filepath" @@ -39,8 +42,21 @@ func GetQemuProvider() machine.Provider { } const ( - VolumeTypeVirtfs = "virtfs" - MountType9p = "9p" + VolumeTypeVirtfs = "virtfs" + MountType9p = "9p" + dockerSock = "/var/run/docker.sock" + dockerConnectTimeout = 5 * time.Second + apiUpTimeout = 20 * time.Second +) + +type apiForwardingState int + +const ( + noForwarding apiForwardingState = iota + claimUnsupported + notInstalled + machineLocal + dockerGlobal ) // NewMachine initializes an instance of a virtual machine based on the qemu @@ -318,7 +334,8 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { wait time.Duration = time.Millisecond * 500 ) - if err := v.startHostNetworking(); err != nil { + forwardSock, forwardState, err := v.startHostNetworking() + if err != nil { return errors.Errorf("unable to start host networking: %q", err) } @@ -439,6 +456,9 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return fmt.Errorf("unknown mount type: %s", mount.Type) } } + + printAPIForwardInstructions(forwardState, forwardSock) + return nil } @@ -869,19 +889,19 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { // startHostNetworking runs a binary on the host system that allows users // to setup port forwarding to the podman virtual machine -func (v *MachineVM) startHostNetworking() error { +func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { cfg, err := config.Default() if err != nil { - return err + return "", noForwarding, err } binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false) if err != nil { - return err + return "", noForwarding, err } qemuSocket, pidFile, err := v.getSocketandPid() if err != nil { - return err + return "", noForwarding, err } attr := new(os.ProcAttr) // Pass on stdin, stdout, stderr @@ -891,12 +911,74 @@ func (v *MachineVM) startHostNetworking() error { cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...) // Add the ssh port cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) + + cmd, forwardSock, state := v.setupAPIForwarding(cmd) + if logrus.GetLevel() == logrus.DebugLevel { cmd = append(cmd, "--debug") fmt.Println(cmd) } _, err = os.StartProcess(cmd[0], cmd, attr) - return err + return forwardSock, state, err +} + +func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) { + socket, err := v.getForwardSocketPath() + + if err != nil { + return cmd, "", noForwarding + } + + cmd = append(cmd, []string{"-forward-sock", socket}...) + cmd = append(cmd, []string{"-forward-dest", "/run/podman/podman.sock"}...) + cmd = append(cmd, []string{"-forward-user", "root"}...) + cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...) + link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock") + + // The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket) + // This allows the helper to only have to maintain one constant target to the user, which can be + // repositioned without updating docker.sock. + if !dockerClaimSupported() { + return cmd, socket, claimUnsupported + } + + if !dockerClaimHelperInstalled() { + return cmd, socket, notInstalled + } + + if !alreadyLinked(socket, link) { + if checkSockInUse(link) { + return cmd, socket, machineLocal + } + + _ = os.Remove(link) + if err = os.Symlink(socket, link); err != nil { + logrus.Warnf("could not create user global API forwarding link: %s", err.Error()) + return cmd, socket, machineLocal + } + } + + if !alreadyLinked(link, dockerSock) { + if checkSockInUse(dockerSock) { + return cmd, socket, machineLocal + } + + if !claimDockerSock() { + logrus.Warn("podman helper is installed, but was not able to claim the global docker sock") + return cmd, socket, machineLocal + } + } + + return cmd, dockerSock, dockerGlobal +} + +func (v *MachineVM) getForwardSocketPath() (string, error) { + path, err := machine.GetDataDir(v.Name) + if err != nil { + logrus.Errorf("Error resolving data dir: %s", err.Error()) + return "", nil + } + return filepath.Join(path, "podman.sock"), nil } func (v *MachineVM) getSocketandPid() (string, string, error) { @@ -912,3 +994,68 @@ func (v *MachineVM) getSocketandPid() (string, string, error) { qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name)) return qemuSocket, pidFile, nil } + +func checkSockInUse(sock string) bool { + if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket { + _, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout) + return err == nil + } + + return false +} + +func alreadyLinked(target string, link string) bool { + read, err := os.Readlink(link) + return err == nil && read == target +} + +func waitAndPingAPI(sock string) { + client := http.Client{ + Transport: &http.Transport{ + DialContext: func(context.Context, string, string) (net.Conn, error) { + con, err := net.DialTimeout("unix", sock, apiUpTimeout) + if err == nil { + con.SetDeadline(time.Now().Add(apiUpTimeout)) + } + return con, err + }, + }, + } + + resp, err := client.Get("http://host/_ping") + if err == nil { + defer resp.Body.Close() + } + if err != nil || resp.StatusCode != 200 { + logrus.Warn("API socket failed ping test") + } +} + +func printAPIForwardInstructions(forwardState apiForwardingState, forwardSock string) { + if forwardState != noForwarding { + waitAndPingAPI(forwardSock) + fmt.Printf("API forwarding listening on: %s\n", forwardSock) + if forwardState == dockerGlobal { + fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") + } else { + stillString := "still " + switch forwardState { + case notInstalled: + fmt.Printf("\nThe system helper service is not installed; the default Docker API socket address can't be used by podman.\n") + if helper := findClaimHelper(); len(helper) > 0 { + fmt.Printf("If you would like to install it run the following command:\n") + fmt.Printf("\n\tsudo %s install\n\n", helper) + } + case machineLocal: + fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n") + case claimUnsupported: + fallthrough + default: + stillString = "" + } + fmt.Printf("You can %sconnect Docker API clients by setting DOCKER HOST using the\n", stillString) + fmt.Printf("following command in your terminal session:\n") + fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) + } + } +} -- cgit v1.2.3-54-g00ecf From 1a8c715f1fa3fb417cd7ca3cde5e1c7e5391b3f4 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Sat, 29 Jan 2022 03:10:28 -0600 Subject: Introduce podman machine init --root=t|f and podman machine set --root=t|f Switch default to rootless for mac and windows Signed-off-by: Jason T. Greene --- cmd/podman/machine/init.go | 5 +- cmd/podman/machine/set.go | 56 ++++++++++++ docs/source/markdown/podman-machine-init.1.md | 9 ++ docs/source/markdown/podman-machine-set.1.md | 59 +++++++++++++ docs/source/markdown/podman-machine.1.md | 1 + pkg/machine/config.go | 9 +- pkg/machine/connection.go | 25 ++++++ pkg/machine/qemu/config.go | 2 + pkg/machine/qemu/machine.go | 122 ++++++++++++++++++++------ pkg/machine/wsl/machine.go | 70 ++++++++++++--- 10 files changed, 318 insertions(+), 40 deletions(-) create mode 100644 cmd/podman/machine/set.go create mode 100644 docs/source/markdown/podman-machine-set.1.md diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 0834aa381..ab13d8651 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -26,7 +26,7 @@ var ( var ( initOpts = machine.InitOptions{} - defaultMachineName = "podman-machine-default" + defaultMachineName = machine.DefaultMachineName now bool ) @@ -99,6 +99,9 @@ func init() { IgnitionPathFlagName := "ignition-path" flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file") _ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault) + + rootfulFlagName := "rootful" + flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container exectution") } // TODO should we allow for a users to append to the qemu cmdline? diff --git a/cmd/podman/machine/set.go b/cmd/podman/machine/set.go new file mode 100644 index 000000000..c978206f0 --- /dev/null +++ b/cmd/podman/machine/set.go @@ -0,0 +1,56 @@ +// +build amd64 arm64 + +package machine + +import ( + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/pkg/machine" + "github.com/spf13/cobra" +) + +var ( + setCmd = &cobra.Command{ + Use: "set [options] [NAME]", + Short: "Sets a virtual machine setting", + Long: "Sets an updatable virtual machine setting", + RunE: setMachine, + Args: cobra.MaximumNArgs(1), + Example: `podman machine set --root=false`, + ValidArgsFunction: completion.AutocompleteNone, + } +) + +var ( + setOpts = machine.SetOptions{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: setCmd, + Parent: machineCmd, + }) + flags := setCmd.Flags() + + rootfulFlagName := "rootful" + flags.BoolVar(&setOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution") +} + +func setMachine(cmd *cobra.Command, args []string) error { + var ( + vm machine.VM + err error + ) + + vmName := defaultMachineName + if len(args) > 0 && len(args[0]) > 0 { + vmName = args[0] + } + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) + if err != nil { + return err + } + + return vm.Set(vmName, setOpts) +} diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md index b515e8763..36db5b1cd 100644 --- a/docs/source/markdown/podman-machine-init.1.md +++ b/docs/source/markdown/podman-machine-init.1.md @@ -55,6 +55,14 @@ Memory (in MB). Start the virtual machine immediately after it has been initialized. +#### **--rootful**=*true|false* + +Whether this machine should prefer rootful (`true`) or rootless (`false`) +container execution. This option will also determine the remote connection default +if there is no existing remote connection configurations. + +API forwarding, if available, will follow this setting. + #### **--timezone** Set the timezone for the machine and containers. Valid values are `local` or @@ -84,6 +92,7 @@ Print usage statement. ``` $ podman machine init $ podman machine init myvm +$ podman machine init --rootful $ podman machine init --disk-size 50 $ podman machine init --memory=1024 myvm $ podman machine init -v /Users:/mnt/Users diff --git a/docs/source/markdown/podman-machine-set.1.md b/docs/source/markdown/podman-machine-set.1.md new file mode 100644 index 000000000..e69779564 --- /dev/null +++ b/docs/source/markdown/podman-machine-set.1.md @@ -0,0 +1,59 @@ +% podman-machine-set(1) + +## NAME +podman\-machine\-set - Sets a virtual machine setting + +## SYNOPSIS +**podman machine set** [*options*] [*name*] + +## DESCRIPTION + +Sets an updatable virtual machine setting. + +Options mirror values passed to `podman machine init`. Only a limited +subset can be changed after machine initialization. + +## OPTIONS + +#### **--rootful**=*true|false* + +Whether this machine should prefer rootful (`true`) or rootless (`false`) +container execution. This option will also update the current podman +remote connection default if it is currently pointing at the specified +machine name (or `podman-machine-default` if no name is specified). + +API forwarding, if available, will follow this setting. + +#### **--help** + +Print usage statement. + +## EXAMPLES + +To switch the default VM `podman-machine-default` from rootless to rootful: + +``` +$ podman machine set --rootful +``` + +or more explicitly: + +``` +$ podman machine set --rootful=true +``` + +To switch the default VM `podman-machine-default` from rootful to rootless: +``` +$ podman machine set --rootful=false +``` + +To switch the VM `myvm` from rootless to rootful: +``` +$ podman machine set --rootful myvm +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-machine(1)](podman-machine.1.md)** + +## HISTORY +February 2022, Originally compiled by Jason Greene diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md index 8d9e77ea5..3bdfd0be9 100644 --- a/docs/source/markdown/podman-machine.1.md +++ b/docs/source/markdown/podman-machine.1.md @@ -16,6 +16,7 @@ podman\-machine - Manage Podman's virtual machine | init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | | list | [podman-machine-list(1)](podman-machine-list.1.md) | List virtual machines | | rm | [podman-machine-rm(1)](podman-machine-rm.1.md) | Remove a virtual machine | +| set | [podman-machine-set(1)](podman-machine-set.1.md) | Sets a virtual machine setting | | ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | | start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | | stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 97237f5e5..efb1eda15 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -27,6 +27,7 @@ type InitOptions struct { URI url.URL Username string ReExec bool + Rootful bool } type QemuMachineStatus = string @@ -35,7 +36,8 @@ const ( // Running indicates the qemu vm is running Running QemuMachineStatus = "running" // Stopped indicates the vm has stopped - Stopped QemuMachineStatus = "stopped" + Stopped QemuMachineStatus = "stopped" + DefaultMachineName string = "podman-machine-default" ) type Provider interface { @@ -89,6 +91,10 @@ type ListResponse struct { IdentityPath string } +type SetOptions struct { + Rootful bool +} + type SSHOptions struct { Username string Args []string @@ -107,6 +113,7 @@ type RemoveOptions struct { type VM interface { Init(opts InitOptions) (bool, error) Remove(name string, opts RemoveOptions) (string, func() error, error) + Set(name string, opts SetOptions) error SSH(name string, opts SSHOptions) error Start(name string, opts StartOptions) error Stop(name string, opts StopOptions) error diff --git a/pkg/machine/connection.go b/pkg/machine/connection.go index d28ffcef1..841b2afa6 100644 --- a/pkg/machine/connection.go +++ b/pkg/machine/connection.go @@ -39,6 +39,31 @@ func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) erro return cfg.Write() } +func AnyConnectionDefault(name ...string) (bool, error) { + cfg, err := config.ReadCustomConfig() + if err != nil { + return false, err + } + for _, n := range name { + if n == cfg.Engine.ActiveService { + return true, nil + } + } + + return false, nil +} + +func ChangeDefault(name string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + cfg.Engine.ActiveService = name + + return cfg.Write() +} + func RemoveConnection(name string) error { cfg, err := config.ReadCustomConfig() if err != nil { diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index e76509bb1..c619b7dd4 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -33,6 +33,8 @@ type MachineVM struct { QMPMonitor Monitor // RemoteUsername of the vm user RemoteUsername string + // Whether this machine should run in a rootful or rootless manner + Rootful bool } type Mount struct { diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 81a6b4935..9beec2173 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -5,14 +5,15 @@ package qemu import ( "bufio" - "encoding/base64" "context" + "encoding/base64" "encoding/json" "fmt" "io/fs" "io/ioutil" "net" "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -166,14 +167,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { key string ) sshDir := filepath.Join(homedir.Get(), ".ssh") - // GetConfDir creates the directory so no need to check for - // its existence - vmConfigDir, err := machine.GetConfDir(vmtype) - if err != nil { - return false, err - } - jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json" v.IdentityPath = filepath.Join(sshDir, v.Name) + v.Rootful = opts.Rootful switch opts.ImagePath { case "testing", "next", "stable", "": @@ -256,29 +251,33 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { // This kind of stinks but no other way around this r/n if len(opts.IgnitionPath) < 1 { uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername) - if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil { - return false, err + uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") + identity := filepath.Join(sshDir, v.Name) + + uris := []url.URL{uri, uriRoot} + names := []string{v.Name, v.Name + "-root"} + + // The first connection defined when connections is empty will become the default + // regardless of IsDefault, so order according to rootful + if opts.Rootful { + uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0] } - uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") - if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil { - return false, err + for i := 0; i < 2; i++ { + if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil { + return false, err + } } } else { fmt.Println("An ignition path was provided. No SSH connection was added to Podman") } // Write the JSON file - b, err := json.MarshalIndent(v, "", " ") - if err != nil { - return false, err - } - if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil { - return false, err - } + v.writeConfig() // User has provided ignition file so keygen // will be skipped. if len(opts.IgnitionPath) < 1 { + var err error key, err = machine.CreateSSHKeys(v.IdentityPath) if err != nil { return false, err @@ -325,6 +324,30 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return err == nil, err } +func (v *MachineVM) Set(name string, opts machine.SetOptions) error { + if v.Rootful == opts.Rootful { + return nil + } + + changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root") + if err != nil { + return err + } + + if changeCon { + newDefault := v.Name + if opts.Rootful { + newDefault += "-root" + } + if err := machine.ChangeDefault(newDefault); err != nil { + return err + } + } + + v.Rootful = opts.Rootful + return v.writeConfig() +} + // Start executes the qemu command line and forks it func (v *MachineVM) Start(name string, _ machine.StartOptions) error { var ( @@ -457,7 +480,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } } - printAPIForwardInstructions(forwardState, forwardSock) + waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name) return nil } @@ -929,9 +952,17 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa return cmd, "", noForwarding } + destSock := "/run/user/1000/podman/podman.sock" + forwardUser := "core" + + if v.Rootful { + destSock = "/run/podman/podman.sock" + forwardUser = "root" + } + cmd = append(cmd, []string{"-forward-sock", socket}...) - cmd = append(cmd, []string{"-forward-dest", "/run/podman/podman.sock"}...) - cmd = append(cmd, []string{"-forward-user", "root"}...) + cmd = append(cmd, []string{"-forward-dest", destSock}...) + cmd = append(cmd, []string{"-forward-user", forwardUser}...) cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...) link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock") @@ -1031,19 +1062,32 @@ func waitAndPingAPI(sock string) { } } -func printAPIForwardInstructions(forwardState apiForwardingState, forwardSock string) { +func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) { if forwardState != noForwarding { waitAndPingAPI(forwardSock) + if !rootFul { + fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") + fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") + fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") + + suffix := "" + if name != machine.DefaultMachineName { + suffix = " " + name + } + fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) + } + fmt.Printf("API forwarding listening on: %s\n", forwardSock) if forwardState == dockerGlobal { - fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") + fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") } else { stillString := "still " switch forwardState { case notInstalled: - fmt.Printf("\nThe system helper service is not installed; the default Docker API socket address can't be used by podman.\n") + fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n") + fmt.Printf("address can't be used by podman. ") if helper := findClaimHelper(); len(helper) > 0 { - fmt.Printf("If you would like to install it run the following command:\n") + fmt.Printf("If you would like to install it run the\nfollowing command:\n") fmt.Printf("\n\tsudo %s install\n\n", helper) } case machineLocal: @@ -1053,9 +1097,31 @@ func printAPIForwardInstructions(forwardState apiForwardingState, forwardSock st default: stillString = "" } - fmt.Printf("You can %sconnect Docker API clients by setting DOCKER HOST using the\n", stillString) + + fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString) fmt.Printf("following command in your terminal session:\n") fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) } } } + +func (v *MachineVM) writeConfig() error { + // GetConfDir creates the directory so no need to check for + // its existence + vmConfigDir, err := machine.GetConfDir(vmtype) + if err != nil { + return err + } + + jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json" + // Write the JSON file + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil { + return err + } + + return nil +} diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 6862e3579..5b0c757f0 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package wsl @@ -8,6 +9,7 @@ import ( "fmt" "io" "io/ioutil" + "net/url" "os" "os/exec" "path/filepath" @@ -35,9 +37,6 @@ const ( ErrorSuccessRebootRequired = 3010 ) -// Usermode networking avoids potential nftables compatibility issues between the distro -// and the WSL Kernel. Additionally it avoids fw rule conflicts between distros, since -// all instances run under the same Kernel at runtime const containersConf = `[containers] [engine] @@ -162,6 +161,8 @@ type MachineVM struct { Port int // RemoteUsername of the vm user RemoteUsername string + // Whether this machine should run in a rootful or rootless manner + Rootful bool } type ExitCodeError struct { @@ -227,12 +228,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { homeDir := homedir.Get() sshDir := filepath.Join(homeDir, ".ssh") v.IdentityPath = filepath.Join(sshDir, v.Name) + v.Rootful = opts.Rootful if err := downloadDistro(v, opts); err != nil { return false, err } - if err := writeJSON(v); err != nil { + if err := v.writeConfig(); err != nil { return false, err } @@ -282,7 +284,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error { return machine.DownloadImage(dd) } -func writeJSON(v *MachineVM) error { +func (v *MachineVM) writeConfig() error { vmConfigDir, err := machine.GetConfDir(vmtype) if err != nil { return err @@ -302,14 +304,26 @@ func writeJSON(v *MachineVM) error { } func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error { + uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername) uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") - if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil { - return err + identity := filepath.Join(sshDir, v.Name) + + uris := []url.URL{uri, uriRoot} + names := []string{v.Name, v.Name + "-root"} + + // The first connection defined when connections is empty will become the default + // regardless of IsDefault, so order according to rootful + if opts.Rootful { + uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0] + } + + for i := 0; i < 2; i++ { + if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil { + return err + } } - user := opts.Username - uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername) - return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault) + return nil } func provisionWSLDist(v *MachineVM) (string, error) { @@ -714,6 +728,30 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error { return cmd.Run() } +func (v *MachineVM) Set(name string, opts machine.SetOptions) error { + if v.Rootful == opts.Rootful { + return nil + } + + changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root") + if err != nil { + return err + } + + if changeCon { + newDefault := v.Name + if opts.Rootful { + newDefault += "-root" + } + if err := machine.ChangeDefault(newDefault); err != nil { + return err + } + } + + v.Rootful = opts.Rootful + return v.writeConfig() +} + func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if v.isRunning() { return errors.Errorf("%q is already running", name) @@ -726,6 +764,18 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return errors.Wrap(err, "WSL bootstrap script failed") } + if !v.Rootful { + fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") + fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") + fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") + + suffix := "" + if name != machine.DefaultMachineName { + suffix = " " + name + } + fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) + } + globalName, pipeName, err := launchWinProxy(v) if err != nil { fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.") -- cgit v1.2.3-54-g00ecf From ae9ad416a35a23afd11426fb4a2e65e9b45bd22f Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Thu, 17 Feb 2022 09:27:46 -0700 Subject: Fix manifest 4.0 Endpoints Branch forced 4.0 only endpoints Signed-off-by: Jhon Honce --- pkg/api/handlers/libpod/manifests.go | 2 +- pkg/bindings/manifests/manifests.go | 115 +++++++++++++++-------------------- pkg/bindings/test/common_test.go | 8 ++- pkg/bindings/test/manifests_test.go | 4 +- 4 files changed, 57 insertions(+), 72 deletions(-) diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 250736579..ad662f32c 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -401,7 +401,7 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) { case len(report.Errors) > 0 && len(report.Images) > 0: statusCode = http.StatusConflict case len(report.Errors) > 0: - statusCode = http.StatusInternalServerError + statusCode = http.StatusBadRequest } utils.WriteResponse(w, statusCode, report) } diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index 458cb913a..b01aaea6e 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -2,10 +2,8 @@ package manifests import ( "context" - "errors" - "fmt" + "io/ioutil" "net/http" - "net/url" "strconv" "strings" @@ -14,8 +12,10 @@ import ( "github.com/containers/podman/v4/pkg/api/handlers" "github.com/containers/podman/v4/pkg/bindings" "github.com/containers/podman/v4/pkg/bindings/images" - "github.com/containers/podman/v4/version" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" ) // Create creates a manifest for the given name. Optional images to be associated with @@ -91,74 +91,27 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) options = new(AddOptions) } - if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) { - optionsv4 := ModifyOptions{ - All: options.All, - Annotations: options.Annotation, - Arch: options.Arch, - Features: options.Features, - Images: options.Images, - OS: options.OS, - OSFeatures: nil, - OSVersion: options.OSVersion, - Variant: options.Variant, - } - optionsv4.WithOperation("update") - return Modify(ctx, name, options.Images, &optionsv4) - } - - // API Version < 4.0.0 - conn, err := bindings.GetClient(ctx) - if err != nil { - return "", err - } - opts, err := jsoniter.MarshalToString(options) - if err != nil { - return "", err - } - reader := strings.NewReader(opts) - - headers := make(http.Header) - v := version.APIVersion[version.Libpod][version.MinimalAPI] - headers.Add("API-Version", - fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)) - response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/manifests/%s/add", nil, headers, name) - if err != nil { - return "", err + optionsv4 := ModifyOptions{ + All: options.All, + Annotations: options.Annotation, + Arch: options.Arch, + Features: options.Features, + Images: options.Images, + OS: options.OS, + OSFeatures: nil, + OSVersion: options.OSVersion, + Variant: options.Variant, } - defer response.Body.Close() - - var idr handlers.IDResponse - return idr.ID, response.Process(&idr) + optionsv4.WithOperation("update") + return Modify(ctx, name, options.Images, &optionsv4) } // Remove deletes a manifest entry from a manifest list. Both name and the digest to be // removed are mandatory inputs. The ID of the new manifest list is returned as a string. func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, error) { - if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) { - optionsv4 := new(ModifyOptions).WithOperation("remove") - return Modify(ctx, name, []string{digest}, optionsv4) - } + optionsv4 := new(ModifyOptions).WithOperation("remove") + return Modify(ctx, name, []string{digest}, optionsv4) - // API Version < 4.0.0 - conn, err := bindings.GetClient(ctx) - if err != nil { - return "", err - } - - headers := http.Header{} - headers.Add("API-Version", "3.4.0") - - params := url.Values{} - params.Set("digest", digest) - response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, headers, name) - if err != nil { - return "", err - } - defer response.Body.Close() - - var idr handlers.IDResponse - return idr.ID, response.Process(&idr) } // Push takes a manifest list and pushes to a destination. If the destination is not specified, @@ -229,8 +182,36 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp } defer response.Body.Close() - var idr handlers.IDResponse - return idr.ID, response.Process(&idr) + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", errors.Wrap(err, "unable to process API response") + } + + if response.IsSuccess() || response.IsRedirection() { + var report entities.ManifestModifyReport + if err = jsoniter.Unmarshal(data, &report); err != nil { + return "", errors.Wrap(err, "unable to decode API response") + } + + err = errorhandling.JoinErrors(report.Errors) + if err != nil { + errModel := errorhandling.ErrorModel{ + Because: (errors.Cause(err)).Error(), + Message: err.Error(), + ResponseCode: response.StatusCode, + } + return report.ID, &errModel + } + return report.ID, nil + } else { + errModel := errorhandling.ErrorModel{ + ResponseCode: response.StatusCode, + } + if err = jsoniter.Unmarshal(data, &errModel); err != nil { + return "", errors.Wrap(err, "unable to decode API response") + } + return "", &errModel + } } // Annotate modifies the given manifest list using options and the optional list of images diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index f51e5f404..b75588251 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "strings" + "testing" "time" "github.com/containers/podman/v4/libpod/define" @@ -151,7 +152,12 @@ func createTempDirInTempDir() (string, error) { } func (b *bindingTest) startAPIService() *gexec.Session { - cmd := []string{"--log-level=debug", "system", "service", "--timeout=0", b.sock} + logLevel := "debug" + if testing.Verbose() { + logLevel = "trace" + } + + cmd := []string{"--log-level=" + logLevel, "system", "service", "--timeout=0", b.sock} session := b.runPodman(cmd) sock := strings.TrimPrefix(b.sock, "unix://") diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index 64becda43..895e1a29d 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -87,7 +87,6 @@ var _ = Describe("podman manifest", func() { list, err := manifests.Inspect(bt.conn, id, nil) Expect(err).ToNot(HaveOccurred()) - Expect(len(list.Manifests)).To(BeNumerically("==", 1)) // add bogus name to existing list should fail @@ -96,7 +95,7 @@ var _ = Describe("podman manifest", func() { Expect(err).To(HaveOccurred()) code, _ = bindings.CheckResponseCode(err) - Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + Expect(code).To(BeNumerically("==", http.StatusBadRequest)) }) It("remove digest", func() { @@ -129,7 +128,6 @@ var _ = Describe("podman manifest", func() { // removal on good manifest with good digest should work data, err = manifests.Inspect(bt.conn, id, nil) Expect(err).ToNot(HaveOccurred()) - Expect(data.Manifests).Should(BeEmpty()) }) -- cgit v1.2.3-54-g00ecf From 15cb918556940e5bf5735cc726ae36968ccded15 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 17 Feb 2022 12:32:51 -0500 Subject: Fix lint Signed-off-by: Matthew Heon --- pkg/bindings/manifests/manifests.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index b01aaea6e..18798e615 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -111,7 +111,6 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, error) { optionsv4 := new(ModifyOptions).WithOperation("remove") return Modify(ctx, name, []string{digest}, optionsv4) - } // Push takes a manifest list and pushes to a destination. If the destination is not specified, @@ -203,15 +202,14 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp return report.ID, &errModel } return report.ID, nil - } else { - errModel := errorhandling.ErrorModel{ - ResponseCode: response.StatusCode, - } - if err = jsoniter.Unmarshal(data, &errModel); err != nil { - return "", errors.Wrap(err, "unable to decode API response") - } - return "", &errModel } + errModel := errorhandling.ErrorModel{ + ResponseCode: response.StatusCode, + } + if err = jsoniter.Unmarshal(data, &errModel); err != nil { + return "", errors.Wrap(err, "unable to decode API response") + } + return "", &errModel } // Annotate modifies the given manifest list using options and the optional list of images -- cgit v1.2.3-54-g00ecf From 84c8870ac236578c41713113fc09a29a5f727bdd Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 16 Feb 2022 14:05:07 -0500 Subject: Release notes for v4.0.0 final Signed-off-by: Matthew Heon --- RELEASE_NOTES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1cd2f9381..4c07b033a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -29,6 +29,7 @@ - The `podman machine init` command now supports a new VM type, `wsl`, available only on Windows; this uses WSL as a backend for `podman machine`, instead of creating a separate VM and managing it via QEMU ([#12503](https://github.com/containers/podman/pull/12503)). - The `podman machine init` command now supports a new option, `--now`, to start the VM immediately after creating it. - The `podman machine init` command now supports a new option, `--volume`, to mount contents from the host into the created virtual machine. +- Virtual machines created by `podman machine` now automatically mount the Podman API socket to the host, so consumers of the Podman or Docker APIs can use them directly from the host machine ([#11462](https://github.com/containers/podman/issues/11462)). - Virtual machines created by `podman machine` now automatically mount certificates from the host's keychain into the virtual machine ([#11507](https://github.com/containers/podman/issues/11507)). - Virtual machines created by `podman machine` now automatically propagate standard proxy environment variables from the host into the virtual machine, including copying any required certificates from `SSL_FILE_CERT` into the VM. - The `podman machine ssh` command now supports a new option, `--username`, to specify the username to connect to the VM with. @@ -136,7 +137,7 @@ - Fixed a bug where the `podman build` command did not properly propagate non-0 exit codes from Buildah when builds failed. - Fixed a bug where the remote Podman client's `podman build` command could fail to build images when the remote client was run on Windows and the Containerfile contained `COPY` instructions ([#13119](https://github.com/containers/podman/issues/13119)). - Fixed a bug where the remote Podman client's `--secret` option to the `podman build` command was nonfunctional. -- Fixed a bug where the remote Podman client's `podman build` command would error if given a relative path to a Containerfile ([#12841](https://github.com/containers/podman/issues/12841)). +- Fixed a bug where the remote Podman client's `podman build` command would error if given a relative path to a Containerfile ([#12841](https://github.com/containers/podman/issues/12841) and [#12763](https://github.com/containers/podman/issues/12763)). - Fixed a bug where the `podman generate kube` command would sometimes omit environment variables set in containers from generated YAML. - Fixed a bug where setting `userns=auto` in `containers.conf` was not respected ([#12615](https://github.com/containers/podman/issues/12615)). - Fixed a bug where the `podman run` command would fail if the host machine did not have a `/etc/hosts` file ([#12667](https://github.com/containers/podman/issues/12667)). -- cgit v1.2.3-54-g00ecf From 49f8da7271ed9b9256a182aca004700047e271ed Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 16 Feb 2022 14:10:39 -0500 Subject: Bump to v4.0.0 Signed-off-by: Matthew Heon --- test/apiv2/01-basic.at | 2 +- version/version.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index 06db62785..e4348a9a7 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -19,7 +19,7 @@ for i in /version version; do t GET $i 200 \ .Components[0].Name="Podman Engine" \ .Components[0].Details.APIVersion~4[0-9.-]\\+ \ - .Components[0].Details.MinAPIVersion=3.3.1 \ + .Components[0].Details.MinAPIVersion=4.0.0 \ .Components[0].Details.Os=linux \ .ApiVersion=1.40 \ .MinAPIVersion=1.24 \ diff --git a/version/version.go b/version/version.go index da7402967..02d110198 100644 --- a/version/version.go +++ b/version/version.go @@ -27,7 +27,7 @@ const ( // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -var Version = semver.MustParse("4.0.0-dev") +var Version = semver.MustParse("4.0.0") // See https://docs.docker.com/engine/api/v1.40/ // libpod compat handlers are expected to honor docker API versions @@ -38,7 +38,7 @@ var Version = semver.MustParse("4.0.0-dev") var APIVersion = map[Tree]map[Level]semver.Version{ Libpod: { CurrentAPI: Version, - MinimalAPI: semver.MustParse("3.3.1"), + MinimalAPI: semver.MustParse("4.0.0"), }, Compat: { CurrentAPI: semver.MustParse("1.40.0"), -- cgit v1.2.3-54-g00ecf From d59749d64dc0d86a06b953b8fe41bb6d102b8556 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 16 Feb 2022 14:10:58 -0500 Subject: Bump to v4.0.1-dev Signed-off-by: Matthew Heon --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 02d110198..3ec5e6d84 100644 --- a/version/version.go +++ b/version/version.go @@ -27,7 +27,7 @@ const ( // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -var Version = semver.MustParse("4.0.0") +var Version = semver.MustParse("4.0.1-dev") // See https://docs.docker.com/engine/api/v1.40/ // libpod compat handlers are expected to honor docker API versions -- cgit v1.2.3-54-g00ecf