diff options
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | cmd/podman/images/search.go | 8 | ||||
-rw-r--r-- | cmd/rootlessport/main.go | 353 | ||||
-rwxr-xr-x | contrib/cirrus/setup_environment.sh | 12 | ||||
-rw-r--r-- | contrib/spec/podman.spec.in | 12 | ||||
-rw-r--r-- | docs/source/markdown/podman-search.1.md | 88 | ||||
-rw-r--r-- | libpod/kube.go | 4 | ||||
-rw-r--r-- | libpod/networking_slirp4netns.go | 10 | ||||
-rw-r--r-- | nix/default-arm64.nix | 2 | ||||
-rw-r--r-- | nix/default.nix | 2 | ||||
-rw-r--r-- | pkg/rootlessport/rootlessport_linux.go | 347 | ||||
-rw-r--r-- | test/e2e/search_test.go | 13 |
12 files changed, 445 insertions, 423 deletions
@@ -39,6 +39,7 @@ LIBPOD_INSTANCE := libpod_dev PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin LIBEXECDIR ?= ${PREFIX}/libexec +LIBEXECPODMAN ?= ${LIBEXECDIR}/podman MANDIR ?= ${PREFIX}/share/man SHAREDIR_CONTAINERS ?= ${PREFIX}/share/containers ETCDIR ?= ${PREFIX}/etc @@ -186,7 +187,7 @@ default: all all: binaries docs .PHONY: binaries -binaries: podman podman-remote ## Build podman and podman-remote binaries +binaries: podman podman-remote rootlessport ## Build podman, podman-remote and rootlessport binaries # Extract text following double-# for targets, as their description for # the `help` target. Otherwise These simple-substitutions are resolved @@ -355,6 +356,15 @@ podman-remote-darwin: ## Build podman-remote for macOS GOARCH=$(GOARCH) \ bin/darwin/podman +bin/rootlessport: .gopathok $(SOURCES) go.mod go.sum + CGO_ENABLED=$(CGO_ENABLED) \ + $(GO) build \ + $(BUILDFLAGS) \ + -o $@ ./cmd/rootlessport + +.PHONY: rootlessport +rootlessport: bin/rootlessport + ### ### Secondary binary-build targets ### @@ -718,11 +728,14 @@ install.bin-nobuild: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman + install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(LIBEXECPODMAN) + install ${SELINUXOPT} -m 755 bin/rootlessport $(DESTDIR)$(LIBEXECPODMAN)/rootlessport + test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(LIBEXECPODMAN)/rootlessport bin/rootlessport install ${SELINUXOPT} -m 755 -d ${DESTDIR}${TMPFILESDIR} install ${SELINUXOPT} -m 644 contrib/tmpfile/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf .PHONY: install.bin -install.bin: podman install.bin-nobuild +install.bin: podman rootlessport install.bin-nobuild .PHONY: install.man-nobuild install.man-nobuild: diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index 42a998461..c9a4793aa 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -20,6 +20,7 @@ import ( type searchOptionsWrapper struct { entities.ImageSearchOptions // CLI only flags + Compatible bool // Docker compat TLSVerifyCLI bool // Used to convert to an optional bool later Format string // For go templating } @@ -92,6 +93,7 @@ func searchFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone) flags.Bool("no-trunc", true, "Do not truncate the output. Default: true") + flags.BoolVar(&searchOptions.Compatible, "compatible", false, "List stars, official and automated columns (Docker compatibility)") authfileFlagName := "authfile" flags.StringVar(&searchOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") @@ -166,7 +168,11 @@ func imageSearch(cmd *cobra.Command, args []string) error { renderHeaders = report.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) default: - row = "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" + row = "{{.Name}}\t{{.Description}}" + if searchOptions.Compatible { + row += "\t{{.Stars}}\t{{.Official}}\t{{.Automated}}" + } + row += "\n" } format := report.EnforceRange(row) diff --git a/cmd/rootlessport/main.go b/cmd/rootlessport/main.go new file mode 100644 index 000000000..feb9f5c06 --- /dev/null +++ b/cmd/rootlessport/main.go @@ -0,0 +1,353 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + "path/filepath" + + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/podman/v3/libpod/network/types" + "github.com/containers/podman/v3/pkg/rootlessport" + "github.com/pkg/errors" + rkport "github.com/rootless-containers/rootlesskit/pkg/port" + rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin" + rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +const ( + // ReexecChildKey is used internally for the second reexec + ReexecChildKey = "rootlessport-child" + reexecChildEnvOpaque = "_CONTAINERS_ROOTLESSPORT_CHILD_OPAQUE" +) + +func main() { + if len(os.Args) > 1 { + fmt.Fprintln(os.Stderr, `too many arguments, rootlessport expects a json config via STDIN`) + os.Exit(1) + } + var err error + if os.Args[0] == ReexecChildKey { + err = child() + } else { + err = parent() + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func loadConfig(r io.Reader) (*rootlessport.Config, io.ReadCloser, io.WriteCloser, error) { + stdin, err := ioutil.ReadAll(r) + if err != nil { + return nil, nil, nil, err + } + var cfg rootlessport.Config + if err := json.Unmarshal(stdin, &cfg); err != nil { + return nil, nil, nil, err + } + if cfg.NetNSPath == "" { + return nil, nil, nil, errors.New("missing NetNSPath") + } + if cfg.ExitFD <= 0 { + return nil, nil, nil, errors.New("missing ExitFD") + } + exitFile := os.NewFile(uintptr(cfg.ExitFD), "exitfile") + if exitFile == nil { + return nil, nil, nil, errors.New("invalid ExitFD") + } + if cfg.ReadyFD <= 0 { + return nil, nil, nil, errors.New("missing ReadyFD") + } + readyFile := os.NewFile(uintptr(cfg.ReadyFD), "readyfile") + if readyFile == nil { + return nil, nil, nil, errors.New("invalid ReadyFD") + } + return &cfg, exitFile, readyFile, nil +} + +func parent() error { + // load config from stdin + cfg, exitR, readyW, err := loadConfig(os.Stdin) + if err != nil { + return err + } + + socketDir := filepath.Join(cfg.TmpDir, "rp") + err = os.MkdirAll(socketDir, 0700) + if err != nil { + return err + } + + // create the parent driver + stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport") + if err != nil { + return err + } + defer os.RemoveAll(stateDir) + driver, err := rkbuiltin.NewParentDriver(&logrusWriter{prefix: "parent: "}, stateDir) + if err != nil { + return err + } + initComplete := make(chan struct{}) + quit := make(chan struct{}) + errCh := make(chan error) + // start the parent driver. initComplete will be closed when the child connected to the parent. + logrus.Infof("Starting parent driver") + go func() { + driverErr := driver.RunParentDriver(initComplete, quit, nil) + if driverErr != nil { + logrus.WithError(driverErr).Warn("Parent driver exited") + } + errCh <- driverErr + close(errCh) + }() + opaque := driver.OpaqueForChild() + logrus.Infof("opaque=%+v", opaque) + opaqueJSON, err := json.Marshal(opaque) + if err != nil { + return err + } + childQuitR, childQuitW, err := os.Pipe() + if err != nil { + return err + } + defer func() { + // stop the child + logrus.Info("Stopping child driver") + if err := childQuitW.Close(); err != nil { + logrus.WithError(err).Warn("Unable to close childQuitW") + } + }() + + // reexec the child process in the child netns + cmd := exec.Command("/proc/self/exe") + cmd.Args = []string{ReexecChildKey} + cmd.Stdin = childQuitR + cmd.Stdout = &logrusWriter{prefix: "child"} + cmd.Stderr = cmd.Stdout + cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON)) + childNS, err := ns.GetNS(cfg.NetNSPath) + if err != nil { + return err + } + if err := childNS.Do(func(_ ns.NetNS) error { + logrus.Infof("Starting child driver in child netns (%q %v)", cmd.Path, cmd.Args) + return cmd.Start() + }); err != nil { + return err + } + + childErrCh := make(chan error) + go func() { + err := cmd.Wait() + childErrCh <- err + close(childErrCh) + }() + + defer func() { + if err := unix.Kill(cmd.Process.Pid, unix.SIGTERM); err != nil { + logrus.WithError(err).Warn("Kill child process") + } + }() + + logrus.Info("Waiting for initComplete") + // wait for the child to connect to the parent +outer: + for { + select { + case <-initComplete: + logrus.Infof("initComplete is closed; parent and child established the communication channel") + break outer + case err := <-childErrCh: + if err != nil { + return err + } + case err := <-errCh: + if err != nil { + return err + } + } + } + + defer func() { + logrus.Info("Stopping parent driver") + quit <- struct{}{} + if err := <-errCh; err != nil { + logrus.WithError(err).Warn("Parent driver returned error on exit") + } + }() + + // let parent expose ports + logrus.Infof("Exposing ports %v", cfg.Mappings) + if err := exposePorts(driver, cfg.Mappings, cfg.ChildIP); err != nil { + return err + } + + // we only need to have a socket to reload ports when we run under rootless cni + if cfg.RootlessCNI { + socketfile := filepath.Join(socketDir, cfg.ContainerID) + // make sure to remove the file if it exists to prevent EADDRINUSE + _ = os.Remove(socketfile) + // workaround to bypass the 108 char socket path limit + // open the fd and use the path to the fd as bind argument + fd, err := unix.Open(socketDir, unix.O_PATH, 0) + if err != nil { + return err + } + socket, err := net.ListenUnix("unixpacket", &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d/%s", fd, cfg.ContainerID), Net: "unixpacket"}) + if err != nil { + return err + } + err = unix.Close(fd) + // remove the socket file on exit + defer os.Remove(socketfile) + if err != nil { + logrus.Warnf("Failed to close the socketDir fd: %v", err) + } + defer socket.Close() + go serve(socket, driver) + } + + logrus.Info("Ready") + + // https://github.com/containers/podman/issues/11248 + // Copy /dev/null to stdout and stderr to prevent SIGPIPE errors + if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { + unix.Dup2(int(f.Fd()), 1) // nolint:errcheck + unix.Dup2(int(f.Fd()), 2) // nolint:errcheck + f.Close() + } + // write and close ReadyFD (convention is same as slirp4netns --ready-fd) + if _, err := readyW.Write([]byte("1")); err != nil { + return err + } + if err := readyW.Close(); err != nil { + return err + } + + // wait for ExitFD to be closed + logrus.Info("Waiting for exitfd to be closed") + if _, err := ioutil.ReadAll(exitR); err != nil { + return err + } + return nil +} + +func serve(listener net.Listener, pm rkport.Manager) { + for { + conn, err := listener.Accept() + if err != nil { + // we cannot log this error, stderr is already closed + continue + } + ctx := context.TODO() + err = handler(ctx, conn, pm) + if err != nil { + conn.Write([]byte(err.Error())) + } else { + conn.Write([]byte("OK")) + } + conn.Close() + } +} + +func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error { + var childIP string + dec := json.NewDecoder(conn) + err := dec.Decode(&childIP) + if err != nil { + return errors.Wrap(err, "rootless port failed to decode ports") + } + portStatus, err := pm.ListPorts(ctx) + if err != nil { + return errors.Wrap(err, "rootless port failed to list ports") + } + for _, status := range portStatus { + err = pm.RemovePort(ctx, status.ID) + if err != nil { + return errors.Wrap(err, "rootless port failed to remove port") + } + } + // add the ports with the new child IP + for _, status := range portStatus { + // set the new child IP + status.Spec.ChildIP = childIP + _, err = pm.AddPort(ctx, status.Spec) + if err != nil { + return errors.Wrap(err, "rootless port failed to add port") + } + } + return nil +} + +func exposePorts(pm rkport.Manager, portMappings []types.OCICNIPortMapping, childIP string) error { + ctx := context.TODO() + for _, i := range portMappings { + hostIP := i.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + spec := rkport.Spec{ + Proto: i.Protocol, + ParentIP: hostIP, + ParentPort: int(i.HostPort), + ChildPort: int(i.ContainerPort), + ChildIP: childIP, + } + if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { + return err + } + if _, err := pm.AddPort(ctx, spec); err != nil { + return err + } + } + return nil +} + +func child() error { + // load the config from the parent + var opaque map[string]string + if err := json.Unmarshal([]byte(os.Getenv(reexecChildEnvOpaque)), &opaque); err != nil { + return err + } + + // start the child driver + quit := make(chan struct{}) + errCh := make(chan error) + go func() { + d := rkbuiltin.NewChildDriver(os.Stderr) + dErr := d.RunChildDriver(opaque, quit) + errCh <- dErr + }() + defer func() { + logrus.Info("Stopping child driver") + quit <- struct{}{} + if err := <-errCh; err != nil { + logrus.WithError(err).Warn("Child driver returned error on exit") + } + }() + + // wait for stdin to be closed + if _, err := ioutil.ReadAll(os.Stdin); err != nil { + return err + } + return nil +} + +type logrusWriter struct { + prefix string +} + +func (w *logrusWriter) Write(p []byte) (int, error) { + logrus.Infof("%s%s", w.prefix, string(p)) + return len(p), nil +} diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index f2afbfef5..3786054a7 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -236,9 +236,19 @@ case "$TEST_FLAVOR" in # Use existing host bits when testing is to happen inside a container # since this script will run again in that environment. # shellcheck disable=SC2154 - if ((CONTAINER==0)) && [[ "$TEST_ENVIRON" == "host" ]]; then + if [[ "$TEST_ENVIRON" == "host" ]]; then + if ((CONTAINER)); then + die "Refusing to config. host-test in container"; + fi remove_packaged_podman_files make install PREFIX=/usr ETCDIR=/etc + elif [[ "$TEST_ENVIRON" == "container" ]]; then + if ((CONTAINER)); then + remove_packaged_podman_files + make install PREFIX=/usr ETCDIR=/etc + fi + else + die "Invalid value for $$TEST_ENVIRON=$TEST_ENVIRON" fi install_test_configs diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 3a4026038..295a953ef 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -3,18 +3,8 @@ %global with_check 0 %global with_unit_test 0 %bcond_without doc -%bcond_without debug -%if %{with debug} -%global _find_debuginfo_dwz_opts %{nil} -%global _dwz_low_mem_die_limit 0 -%else %global debug_package %{nil} -%endif - -%if ! 0%{?gobuild:1} -%define gobuild(o:) go build -buildmode pie -compiler gc -tags="rpm_crashtraceback ${BUILDTAGS:-}" -ldflags "${LDFLAGS:-} -B 0x$(head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') -extldflags '-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld '" -a -v -x %{?**}; -%endif # podman hack directory %define hackdir %{_builddir}/%{repo}-%{shortcommit0} @@ -536,6 +526,8 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_usr}/lib/systemd/user/podman-auto-update.timer %{_usr}/lib/systemd/user/podman-restart.service %{_usr}/lib/tmpfiles.d/podman.conf +%dir %{_libexecdir}/%{name} +%{_libexecdir}/%{name}/rootlessport %if 0%{?with_devel} %files -n libpod-devel -f devel.file-list diff --git a/docs/source/markdown/podman-search.1.md b/docs/source/markdown/podman-search.1.md index c3729aa26..7c37e389c 100644 --- a/docs/source/markdown/podman-search.1.md +++ b/docs/source/markdown/podman-search.1.md @@ -37,6 +37,11 @@ Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` +#### **--compatible** + +After the name and the description, also show the stars, official and automated descriptors as Docker does. +Podman does not show these decsriptors by default since they are not supported by most public container registries. + #### **--filter**, **-f**=*filter* Filter output based on conditions provided (default []) @@ -97,72 +102,39 @@ Print usage statement ## EXAMPLES ``` -$ podman search --limit 3 rhel -INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED -docker.io docker.io/richxsl/rhel7 RHEL 7 image with minimal installation 9 -docker.io docker.io/bluedata/rhel7 RHEL-7.x base container images 1 -docker.io docker.io/gidikern/rhel-oracle-jre RHEL7 with jre8u60 5 [OK] -redhat.com redhat.com/rhel This platform image provides a minimal runti... 0 -redhat.com redhat.com/rhel6 This platform image provides a minimal runti... 0 -redhat.com redhat.com/rhel6.5 This platform image provides a minimal runti... 0 +$ podman search --limit 3 fedora +NAME DESCRIPTION +registry.centos.org/centos +registry.centos.org/cdrage/mosh-centos7 +registry.centos.org/centos/bind +docker.io/library/centos The official build of CentOS. +docker.io/jdeathe/centos-ssh OpenSSH / Supervisor / EPEL/IUS/SCL Repos - ... +docker.io/ansible/centos7-ansible Ansible on Centos7 +quay.io/centos/centos The official CentOS base containers. +quay.io/ukhomeofficedigital/centos-base +quay.io/quarkus/centos-quarkus-maven Quarkus.io builder image for building Quarku... ``` +Note that the Stars, Official and Automated descriptors are only available on Docker Hub and are hence not displayed by default. ``` -$ podman search alpine -INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED -docker.io docker.io/library/alpine A minimal Docker image based on Alpine Linux... 3009 [OK] -docker.io docker.io/mhart/alpine-node Minimal Node.js built on Alpine Linux 332 -docker.io docker.io/anapsix/alpine-java Oracle Java 8 (and 7) with GLIBC 2.23 over A... 272 [OK] -docker.io docker.io/tenstartups/alpine Alpine linux base docker image with useful p... 5 [OK] +$ podman search --format "{{.Name}}\t{{.Stars}}\t{{.Official}}" alpine --limit 3 +docker.io/library/alpine 7956 [OK] +docker.io/alpine/git 192 +docker.io/anapsix/alpine-java 474 +quay.io/libpod/alpine 0 +quay.io/vqcomms/alpine-tools 0 +quay.io/wire/alpine-deps 0 ``` ``` -$ podman search registry.fedoraproject.org/fedora -INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED -fedoraproject.org fedoraproject.org/fedora 0 -fedoraproject.org fedoraproject.org/fedora-minimal 0 -``` +$ podman search --list-tags registry.access.redhat.com/ubi8 --limit 4 +NAME TAG +registry.access.redhat.com/ubi8 8.4-211 +registry.access.redhat.com/ubi8 8.4-206.1626828523-source +registry.access.redhat.com/ubi8 8.4-199 +registry.access.redhat.com/ubi8 8.4-211-source ``` -$ podman search --filter=is-official alpine -INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED -docker.io docker.io/library/alpine A minimal Docker image based on Alpine Linux... 3009 [OK] -``` - -``` -$ podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora -INDEX NAME -fedoraproject.org fedoraproject.org/fedora -fedoraproject.org fedoraproject.org/fedora-minimal -``` - -``` -$ podman search registry.fedoraproject.org/ -INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED -fedoraproject.org registry.fedoraproject.org/f25/cockpit 0 -fedoraproject.org registry.fedoraproject.org/f25/container-engine 0 -fedoraproject.org registry.fedoraproject.org/f25/docker 0 -fedoraproject.org registry.fedoraproject.org/f25/etcd 0 -fedoraproject.org registry.fedoraproject.org/f25/flannel 0 -fedoraproject.org registry.fedoraproject.org/f25/httpd 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-apiserver 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-controller-manager 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-kubelet 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-master 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-node 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-proxy 0 -fedoraproject.org registry.fedoraproject.org/f25/kubernetes-scheduler 0 -fedoraproject.org registry.fedoraproject.org/f25/mariadb 0 -``` - -``` -$ podman search --list-tags registry.redhat.io/rhel -NAME TAG -registry.redhat.io/rhel 7.3-74 -registry.redhat.io/rhel 7.6-301 -registry.redhat.io/rhel 7.1-9 -... -``` Note: This works only with registries that implement the v2 API. If tried with a v1 registry an error will be returned. ## FILES diff --git a/libpod/kube.go b/libpod/kube.go index 816fe9cc3..eb7743886 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -246,7 +246,7 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po return nil, err } for k, v := range annotations { - podAnnotations[define.BindMountPrefix+k] = v + podAnnotations[define.BindMountPrefix+k] = strings.TrimSpace(v) } // Since port bindings for the pod are handled by the // infra container, wipe them here. @@ -366,7 +366,7 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod, return nil, err } for k, v := range annotations { - kubeAnnotations[define.BindMountPrefix+k] = v + kubeAnnotations[define.BindMountPrefix+k] = strings.TrimSpace(v) } if isInit { kubeInitCtrs = append(kubeInitCtrs, kubeCtr) diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index 46cda89a9..ffd53ec2b 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -484,10 +484,14 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath strin } cfgR := bytes.NewReader(cfgJSON) var stdout bytes.Buffer - cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid())) - cmd.Args = []string{rootlessport.ReexecKey} - // Leak one end of the pipe in rootlessport process, the other will be sent to conmon + path, err := r.config.FindHelperBinary(rootlessport.BinaryName, false) + if err != nil { + return err + } + cmd := exec.Command(path) + cmd.Args = []string{rootlessport.BinaryName} + // Leak one end of the pipe in rootlessport process, the other will be sent to conmon if ctr.rootlessPortSyncR != nil { defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR) } diff --git a/nix/default-arm64.nix b/nix/default-arm64.nix index 8868788ae..bb958a193 100644 --- a/nix/default-arm64.nix +++ b/nix/default-arm64.nix @@ -77,10 +77,12 @@ let patchShebangs . make bin/podman make bin/podman-remote + make bin/rootlessport ''; installPhase = '' install -Dm755 bin/podman $out/bin/podman install -Dm755 bin/podman-remote $out/bin/podman-remote + install -Dm755 bin/rootlessport $out/libexec/podman/rootlessport ''; }; in diff --git a/nix/default.nix b/nix/default.nix index 4d15532c2..1dc6f92b6 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -75,10 +75,12 @@ let patchShebangs . make bin/podman make bin/podman-remote + make bin/rootlessport ''; installPhase = '' install -Dm755 bin/podman $out/bin/podman install -Dm755 bin/podman-remote $out/bin/podman-remote + install -Dm755 bin/rootlessport $out/libexec/podman/rootlessport ''; }; in diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go index 37fb7ce79..7b9e5bbfa 100644 --- a/pkg/rootlessport/rootlessport_linux.go +++ b/pkg/rootlessport/rootlessport_linux.go @@ -12,33 +12,12 @@ package rootlessport import ( - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net" - "os" - "os/exec" - "path/filepath" - - "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/podman/v3/libpod/network/types" - "github.com/containers/storage/pkg/reexec" - "github.com/pkg/errors" - rkport "github.com/rootless-containers/rootlesskit/pkg/port" - rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin" - rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) const ( - // ReexecKey is the reexec key for the parent process. - ReexecKey = "containers-rootlessport" - // reexecChildKey is used internally for the second reexec - reexecChildKey = "containers-rootlessport-child" - reexecChildEnvOpaque = "_CONTAINERS_ROOTLESSPORT_CHILD_OPAQUE" + // BinaryName is the binary name for the parent process. + BinaryName = "rootlessport" ) // Config needs to be provided to the process via stdin as a JSON string. @@ -53,325 +32,3 @@ type Config struct { ContainerID string RootlessCNI bool } - -func init() { - reexec.Register(ReexecKey, func() { - if err := parent(); err != nil { - fmt.Println(err) - os.Exit(1) - } - }) - reexec.Register(reexecChildKey, func() { - if err := child(); err != nil { - fmt.Println(err) - os.Exit(1) - } - }) -} - -func loadConfig(r io.Reader) (*Config, io.ReadCloser, io.WriteCloser, error) { - stdin, err := ioutil.ReadAll(r) - if err != nil { - return nil, nil, nil, err - } - var cfg Config - if err := json.Unmarshal(stdin, &cfg); err != nil { - return nil, nil, nil, err - } - if cfg.NetNSPath == "" { - return nil, nil, nil, errors.New("missing NetNSPath") - } - if cfg.ExitFD <= 0 { - return nil, nil, nil, errors.New("missing ExitFD") - } - exitFile := os.NewFile(uintptr(cfg.ExitFD), "exitfile") - if exitFile == nil { - return nil, nil, nil, errors.New("invalid ExitFD") - } - if cfg.ReadyFD <= 0 { - return nil, nil, nil, errors.New("missing ReadyFD") - } - readyFile := os.NewFile(uintptr(cfg.ReadyFD), "readyfile") - if readyFile == nil { - return nil, nil, nil, errors.New("invalid ReadyFD") - } - return &cfg, exitFile, readyFile, nil -} - -func parent() error { - // load config from stdin - cfg, exitR, readyW, err := loadConfig(os.Stdin) - if err != nil { - return err - } - - socketDir := filepath.Join(cfg.TmpDir, "rp") - err = os.MkdirAll(socketDir, 0700) - if err != nil { - return err - } - - // create the parent driver - stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport") - if err != nil { - return err - } - defer os.RemoveAll(stateDir) - driver, err := rkbuiltin.NewParentDriver(&logrusWriter{prefix: "parent: "}, stateDir) - if err != nil { - return err - } - initComplete := make(chan struct{}) - quit := make(chan struct{}) - errCh := make(chan error) - // start the parent driver. initComplete will be closed when the child connected to the parent. - logrus.Infof("Starting parent driver") - go func() { - driverErr := driver.RunParentDriver(initComplete, quit, nil) - if driverErr != nil { - logrus.WithError(driverErr).Warn("Parent driver exited") - } - errCh <- driverErr - close(errCh) - }() - opaque := driver.OpaqueForChild() - logrus.Infof("Opaque=%+v", opaque) - opaqueJSON, err := json.Marshal(opaque) - if err != nil { - return err - } - childQuitR, childQuitW, err := os.Pipe() - if err != nil { - return err - } - defer func() { - // stop the child - logrus.Info("Stopping child driver") - if err := childQuitW.Close(); err != nil { - logrus.WithError(err).Warn("Unable to close childQuitW") - } - }() - - // reexec the child process in the child netns - cmd := exec.Command("/proc/self/exe") - cmd.Args = []string{reexecChildKey} - cmd.Stdin = childQuitR - cmd.Stdout = &logrusWriter{prefix: "child"} - cmd.Stderr = cmd.Stdout - cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON)) - childNS, err := ns.GetNS(cfg.NetNSPath) - if err != nil { - return err - } - if err := childNS.Do(func(_ ns.NetNS) error { - logrus.Infof("Starting child driver in child netns (%q %v)", cmd.Path, cmd.Args) - return cmd.Start() - }); err != nil { - return err - } - - childErrCh := make(chan error) - go func() { - err := cmd.Wait() - childErrCh <- err - close(childErrCh) - }() - - defer func() { - if err := unix.Kill(cmd.Process.Pid, unix.SIGTERM); err != nil { - logrus.WithError(err).Warn("Kill child process") - } - }() - - logrus.Info("Waiting for initComplete") - // wait for the child to connect to the parent -outer: - for { - select { - case <-initComplete: - logrus.Infof("initComplete is closed; parent and child established the communication channel") - break outer - case err := <-childErrCh: - if err != nil { - return err - } - case err := <-errCh: - if err != nil { - return err - } - } - } - - defer func() { - logrus.Info("Stopping parent driver") - quit <- struct{}{} - if err := <-errCh; err != nil { - logrus.WithError(err).Warn("Parent driver returned error on exit") - } - }() - - // let parent expose ports - logrus.Infof("Exposing ports %v", cfg.Mappings) - if err := exposePorts(driver, cfg.Mappings, cfg.ChildIP); err != nil { - return err - } - - // we only need to have a socket to reload ports when we run under rootless cni - if cfg.RootlessCNI { - socketfile := filepath.Join(socketDir, cfg.ContainerID) - // make sure to remove the file if it exists to prevent EADDRINUSE - _ = os.Remove(socketfile) - // workaround to bypass the 108 char socket path limit - // open the fd and use the path to the fd as bind argument - fd, err := unix.Open(socketDir, unix.O_PATH, 0) - if err != nil { - return err - } - socket, err := net.ListenUnix("unixpacket", &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d/%s", fd, cfg.ContainerID), Net: "unixpacket"}) - if err != nil { - return err - } - err = unix.Close(fd) - // remove the socket file on exit - defer os.Remove(socketfile) - if err != nil { - logrus.Warnf("Failed to close the socketDir fd: %v", err) - } - defer socket.Close() - go serve(socket, driver) - } - - logrus.Info("Ready") - - // https://github.com/containers/podman/issues/11248 - // Copy /dev/null to stdout and stderr to prevent SIGPIPE errors - if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { - unix.Dup2(int(f.Fd()), 1) // nolint:errcheck - unix.Dup2(int(f.Fd()), 2) // nolint:errcheck - f.Close() - } - // write and close ReadyFD (convention is same as slirp4netns --ready-fd) - if _, err := readyW.Write([]byte("1")); err != nil { - return err - } - if err := readyW.Close(); err != nil { - return err - } - - // wait for ExitFD to be closed - logrus.Info("Waiting for exitfd to be closed") - if _, err := ioutil.ReadAll(exitR); err != nil { - return err - } - return nil -} - -func serve(listener net.Listener, pm rkport.Manager) { - for { - conn, err := listener.Accept() - if err != nil { - // we cannot log this error, stderr is already closed - continue - } - ctx := context.TODO() - err = handler(ctx, conn, pm) - if err != nil { - conn.Write([]byte(err.Error())) - } else { - conn.Write([]byte("OK")) - } - conn.Close() - } -} - -func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error { - var childIP string - dec := json.NewDecoder(conn) - err := dec.Decode(&childIP) - if err != nil { - return errors.Wrap(err, "rootless port failed to decode ports") - } - portStatus, err := pm.ListPorts(ctx) - if err != nil { - return errors.Wrap(err, "rootless port failed to list ports") - } - for _, status := range portStatus { - err = pm.RemovePort(ctx, status.ID) - if err != nil { - return errors.Wrap(err, "rootless port failed to remove port") - } - } - // add the ports with the new child IP - for _, status := range portStatus { - // set the new child IP - status.Spec.ChildIP = childIP - _, err = pm.AddPort(ctx, status.Spec) - if err != nil { - return errors.Wrap(err, "rootless port failed to add port") - } - } - return nil -} - -func exposePorts(pm rkport.Manager, portMappings []types.OCICNIPortMapping, childIP string) error { - ctx := context.TODO() - for _, i := range portMappings { - hostIP := i.HostIP - if hostIP == "" { - hostIP = "0.0.0.0" - } - spec := rkport.Spec{ - Proto: i.Protocol, - ParentIP: hostIP, - ParentPort: int(i.HostPort), - ChildPort: int(i.ContainerPort), - ChildIP: childIP, - } - if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { - return err - } - if _, err := pm.AddPort(ctx, spec); err != nil { - return err - } - } - return nil -} - -func child() error { - // load the config from the parent - var opaque map[string]string - if err := json.Unmarshal([]byte(os.Getenv(reexecChildEnvOpaque)), &opaque); err != nil { - return err - } - - // start the child driver - quit := make(chan struct{}) - errCh := make(chan error) - go func() { - d := rkbuiltin.NewChildDriver(os.Stderr) - dErr := d.RunChildDriver(opaque, quit) - errCh <- dErr - }() - defer func() { - logrus.Info("Stopping child driver") - quit <- struct{}{} - if err := <-errCh; err != nil { - logrus.WithError(err).Warn("Child driver returned error on exit") - } - }() - - // wait for stdin to be closed - if _, err := ioutil.ReadAll(os.Stdin); err != nil { - return err - } - return nil -} - -type logrusWriter struct { - prefix string -} - -func (w *logrusWriter) Write(p []byte) (int, error) { - logrus.Infof("%s%s", w.prefix, string(p)) - return len(p), nil -} diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 243b1a307..10e991d9f 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -107,7 +107,18 @@ registries = ['{{.Host}}:{{.Port}}']` search.WaitWithDefaultTimeout() Expect(search).Should(Exit(0)) output := string(search.Out.Contents()) - match, _ := regexp.MatchString(`(?m)^quay.io\s+quay.io/libpod/whalesay\s+Static image used for automated testing.+$`, output) + match, _ := regexp.MatchString(`(?m)NAME\s+DESCRIPTION$`, output) + Expect(match).To(BeTrue()) + match, _ = regexp.MatchString(`(?m)quay.io/libpod/whalesay\s+Static image used for automated testing.+$`, output) + Expect(match).To(BeTrue()) + }) + + It("podman search image with --compatible", func() { + search := podmanTest.Podman([]string{"search", "--compatible", "quay.io/libpod/whalesay"}) + search.WaitWithDefaultTimeout() + Expect(search).Should(Exit(0)) + output := string(search.Out.Contents()) + match, _ := regexp.MatchString(`(?m)NAME\s+DESCRIPTION\s+STARS\s+OFFICIAL\s+AUTOMATED$`, output) Expect(match).To(BeTrue()) }) |