diff options
71 files changed, 1031 insertions, 560 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: @@ -770,20 +783,29 @@ install.docker-full: install.docker install.docker-docs .PHONY: install.systemd ifneq (,$(findstring systemd,$(BUILDTAGS))) -install.systemd: +PODMAN_UNIT_FILES = contrib/systemd/auto-update/podman-auto-update.service \ + contrib/systemd/system/podman.service \ + contrib/systemd/system/podman-restart.service + +%.service: %.service.in + sed -e 's;@@PODMAN@@;$(BINDIR)/podman;g' $< >$@.tmp.$$ \ + && mv -f $@.tmp.$$ $@ + +install.systemd: $(PODMAN_UNIT_FILES) install ${SELINUXOPT} -m 755 -d ${DESTDIR}${SYSTEMDDIR} ${DESTDIR}${USERSYSTEMDDIR} # User services install ${SELINUXOPT} -m 644 contrib/systemd/auto-update/podman-auto-update.service ${DESTDIR}${USERSYSTEMDDIR}/podman-auto-update.service install ${SELINUXOPT} -m 644 contrib/systemd/auto-update/podman-auto-update.timer ${DESTDIR}${USERSYSTEMDDIR}/podman-auto-update.timer - install ${SELINUXOPT} -m 644 contrib/systemd/user/podman.socket ${DESTDIR}${USERSYSTEMDDIR}/podman.socket - install ${SELINUXOPT} -m 644 contrib/systemd/user/podman.service ${DESTDIR}${USERSYSTEMDDIR}/podman.service - install ${SELINUXOPT} -m 644 contrib/systemd/user/podman-restart.service ${DESTDIR}${USERSYSTEMDDIR}/podman-restart.service + install ${SELINUXOPT} -m 644 contrib/systemd/system/podman.socket ${DESTDIR}${USERSYSTEMDDIR}/podman.socket + install ${SELINUXOPT} -m 644 contrib/systemd/system/podman.service ${DESTDIR}${USERSYSTEMDDIR}/podman.service + install ${SELINUXOPT} -m 644 contrib/systemd/system/podman-restart.service ${DESTDIR}${USERSYSTEMDDIR}/podman-restart.service # System services install ${SELINUXOPT} -m 644 contrib/systemd/auto-update/podman-auto-update.service ${DESTDIR}${SYSTEMDDIR}/podman-auto-update.service install ${SELINUXOPT} -m 644 contrib/systemd/auto-update/podman-auto-update.timer ${DESTDIR}${SYSTEMDDIR}/podman-auto-update.timer install ${SELINUXOPT} -m 644 contrib/systemd/system/podman.socket ${DESTDIR}${SYSTEMDDIR}/podman.socket install ${SELINUXOPT} -m 644 contrib/systemd/system/podman.service ${DESTDIR}${SYSTEMDDIR}/podman.service install ${SELINUXOPT} -m 644 contrib/systemd/system/podman-restart.service ${DESTDIR}${SYSTEMDDIR}/podman-restart.service + rm -f $(PODMAN_UNIT_FILES) else install.systemd: endif diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 32d4c039e..8a6fea18c 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -251,7 +251,7 @@ spelled with complete minutiae. binaries under the "binary", then "bin" links. Tar these files as `podman-static.tar.gz`. 1. The `podman-vX.Y.Z.dmg` file is produced manually by someone in - posession of a developer signing key. + possession of a developer signing key. 1. In the directory where you downloaded the archives, run `sha256sum *.tar.gz *.zip *.msi > shasums` to generate SHA sums. 1. Go to `https://github.com/containers/podman/releases/tag/vX.Y.Z` and diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 11e8f6870..d21feaabc 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -11,9 +11,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/utils" "github.com/docker/go-units" "github.com/pkg/errors" @@ -113,16 +111,6 @@ func checkStatOptions(cmd *cobra.Command, args []string) error { } func stats(cmd *cobra.Command, args []string) error { - if rootless.IsRootless() { - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return err - } - if !unified { - return errors.New("stats is not supported in rootless mode without cgroups v2") - } - } - // Convert to the entities options. We should not leak CLI-only // options into the backend and separate concerns. opts := entities.ContainerStatsOptions{ diff --git a/cmd/podman/images/buildx.go b/cmd/podman/images/buildx.go index 5c8e5aaa0..2577a3a74 100644 --- a/cmd/podman/images/buildx.go +++ b/cmd/podman/images/buildx.go @@ -14,11 +14,12 @@ var ( // If we are adding new buildx features, we will add them by default // to podman build. buildxCmd = &cobra.Command{ - Use: "buildx", - Short: "Build images", - Long: "Build images", - RunE: validate.SubCommandExists, - Hidden: true, + Use: "buildx", + Aliases: []string{"builder"}, + Short: "Build images", + Long: "Build images", + RunE: validate.SubCommandExists, + Hidden: true, } ) diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index 6c39e5c69..fc7451c41 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -36,6 +36,11 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: pruneCmd, + Parent: buildxCmd, + }) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: pruneCmd, Parent: imageCmd, }) diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index 11e54578a..c9a4793aa 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -3,6 +3,7 @@ package images import ( "fmt" "os" + "strings" "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/completion" @@ -19,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 } @@ -79,7 +81,7 @@ func searchFlags(cmd *cobra.Command) { filterFlagName := "filter" flags.StringSliceVarP(&searchOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])") - //TODO add custom filter function + // TODO add custom filter function _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) formatFlagName := "format" @@ -90,7 +92,8 @@ func searchFlags(cmd *cobra.Command) { flags.IntVar(&searchOptions.Limit, limitFlagName, 0, "Limit the number of results") _ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone) - flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") + 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") @@ -132,11 +135,20 @@ func imageSearch(cmd *cobra.Command, args []string) error { if err != nil { return err } - if len(searchReport) == 0 { return nil } + noTrunc, _ := cmd.Flags().GetBool("no-trunc") + isJSON := report.IsJSON(searchOptions.Format) + for i, element := range searchReport { + d := strings.ReplaceAll(element.Description, "\n", " ") + if len(d) > 44 && !(noTrunc || isJSON) { + d = strings.TrimSpace(d[:44]) + "..." + } + searchReport[i].Description = d + } + hdrs := report.Headers(entities.ImageSearchReport{}, nil) renderHeaders := true var row string @@ -145,18 +157,22 @@ func imageSearch(cmd *cobra.Command, args []string) error { if len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } - if report.IsJSON(searchOptions.Format) { + if isJSON { listTagsEntries := buildListTagsJSON(searchReport) return printArbitraryJSON(listTagsEntries) } row = "{{.Name}}\t{{.Tag}}\n" - case report.IsJSON(searchOptions.Format): + case isJSON: return printArbitraryJSON(searchReport) case cmd.Flags().Changed("format"): 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) @@ -190,7 +206,7 @@ func printArbitraryJSON(v interface{}) error { } func buildListTagsJSON(searchReport []entities.ImageSearchReport) []listEntryTag { - entries := []listEntryTag{} + entries := make([]listEntryTag, 0) ReportLoop: for _, report := range searchReport { diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index d5aaf09ce..7c2c72171 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -132,7 +132,7 @@ func create(cmd *cobra.Command, args []string) error { } createOptions.Share = nil } else { - // reassign certain optios for lbpod api, these need to be populated in spec + // reassign certain options for lbpod api, these need to be populated in spec flags := cmd.Flags() infraOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags, false) if err != nil { diff --git a/cmd/podman/system/dial_stdio.go b/cmd/podman/system/dial_stdio.go new file mode 100644 index 000000000..eae89f38e --- /dev/null +++ b/cmd/podman/system/dial_stdio.go @@ -0,0 +1,145 @@ +package system + +import ( + "context" + "io" + "os" + + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/cmd/podman/validate" + "github.com/containers/podman/v3/pkg/bindings" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + dialStdioCommand = &cobra.Command{ + Use: "dial-stdio", + Short: "Proxy the stdio stream to the daemon connection. Should not be invoked manually.", + Args: validate.NoArgs, + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDialStdio() + }, + Example: "podman system dial-stdio", + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: dialStdioCommand, + Parent: systemCmd, + }) +} + +func runDialStdio() error { + ctx := registry.Context() + cfg := registry.PodmanConfig() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + bindCtx, err := bindings.NewConnection(ctx, cfg.URI) + if err != nil { + return errors.Wrap(err, "failed to open connection to podman") + } + conn, err := bindings.GetClient(bindCtx) + if err != nil { + return errors.Wrap(err, "failed to get connection after initialization") + } + netConn, err := conn.GetDialer(bindCtx) + if err != nil { + return errors.Wrap(err, "failed to open the raw stream connection") + } + defer netConn.Close() + + var connHalfCloser halfCloser + switch t := netConn.(type) { + case halfCloser: + connHalfCloser = t + case halfReadWriteCloser: + connHalfCloser = &nopCloseReader{t} + default: + return errors.New("the raw stream connection does not implement halfCloser") + } + + stdin2conn := make(chan error, 1) + conn2stdout := make(chan error, 1) + go func() { + stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream") + }() + go func() { + conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout") + }() + select { + case err = <-stdin2conn: + if err != nil { + return err + } + // wait for stdout + err = <-conn2stdout + case err = <-conn2stdout: + // return immediately + } + return err +} + +// Below portion taken from original docker CLI +// https://github.com/docker/cli/blob/v20.10.9/cli/command/system/dial_stdio.go +func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error { + defer func() { + if err := from.CloseRead(); err != nil { + logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err) + } + if err := to.CloseWrite(); err != nil { + logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err) + } + }() + if _, err := io.Copy(to, from); err != nil { + return errors.Wrapf(err, "error while Copy (%s)", debugDescription) + } + return nil +} + +type halfReadCloser interface { + io.Reader + CloseRead() error +} + +type halfWriteCloser interface { + io.Writer + CloseWrite() error +} + +type halfCloser interface { + halfReadCloser + halfWriteCloser +} + +type halfReadWriteCloser interface { + io.Reader + halfWriteCloser +} + +type nopCloseReader struct { + halfReadWriteCloser +} + +func (x *nopCloseReader) CloseRead() error { + return nil +} + +type halfReadCloserWrapper struct { + io.ReadCloser +} + +func (x *halfReadCloserWrapper) CloseRead() error { + return x.Close() +} + +type halfWriteCloserWrapper struct { + io.WriteCloser +} + +func (x *halfWriteCloserWrapper) CloseWrite() error { + return x.Close() +} diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 0a4be6aea..b9bd7538f 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -93,7 +93,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities return err } defer func() { - if err := server.Shutdown(false); err != nil { + if err := server.Shutdown(true); err != nil { logrus.Warnf("Error when stopping API service: %s", err) } }() 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 fa513932f..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} @@ -435,7 +425,7 @@ BUILDTAGS=$BUILDTAGS make binaries %install install -dp %{buildroot}%{_unitdir} install -dp %{buildroot}%{_usr}/lib/systemd/user -PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \ +PODMAN_VERSION=%{version} %{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} ETCDIR=%{_sysconfdir} \ install.bin-nobuild \ install.remote-nobuild \ %if %{with doc} @@ -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/contrib/systemd/auto-update/podman-auto-update.service b/contrib/systemd/auto-update/podman-auto-update.service.in index dc5fac8cf..de4460d60 100644 --- a/contrib/systemd/auto-update/podman-auto-update.service +++ b/contrib/systemd/auto-update/podman-auto-update.service.in @@ -6,8 +6,8 @@ After=network-online.target [Service] Type=oneshot -ExecStart=/usr/bin/podman auto-update -ExecStartPost=/usr/bin/podman image prune -f +ExecStart=@@PODMAN@@ auto-update +ExecStartPost=@@PODMAN@@ image prune -f [Install] WantedBy=multi-user.target default.target diff --git a/contrib/systemd/system/podman-restart.service b/contrib/systemd/system/podman-restart.service.in index baf12b3ae..a2951e111 100644 --- a/contrib/systemd/system/podman-restart.service +++ b/contrib/systemd/system/podman-restart.service.in @@ -6,7 +6,7 @@ StartLimitIntervalSec=0 [Service] Type=oneshot Environment=LOGGING="--log-level=info" -ExecStart=/usr/bin/podman $LOGGING start --all --filter restart-policy=always +ExecStart=@@PODMAN@@ $LOGGING start --all --filter restart-policy=always [Install] WantedBy=multi-user.target diff --git a/contrib/systemd/system/podman.service b/contrib/systemd/system/podman.service.in index cefb13ae3..132671dff 100644 --- a/contrib/systemd/system/podman.service +++ b/contrib/systemd/system/podman.service.in @@ -9,7 +9,7 @@ StartLimitIntervalSec=0 Type=exec KillMode=process Environment=LOGGING="--log-level=info" -ExecStart=/usr/bin/podman $LOGGING system service +ExecStart=@@PODMAN@@ $LOGGING system service [Install] WantedBy=multi-user.target diff --git a/docs/source/markdown/podman-manifest.1.md b/docs/source/markdown/podman-manifest.1.md index 6b82cc1ad..964f89afe 100644 --- a/docs/source/markdown/podman-manifest.1.md +++ b/docs/source/markdown/podman-manifest.1.md @@ -24,5 +24,49 @@ The `podman manifest` command provides subcommands which can be used to: | remove | [podman-manifest-remove(1)](podman-manifest-remove.1.md) | Remove an image from a manifest list or image index. | | rm | [podman-manifest-rme(1)](podman-manifest-rm.1.md) | Remove manifest list or image index from local storage. | +## EXAMPLES + +### Building a multi-arch manifest list from a Containerfile + +Assuming the `Containerfile` uses `RUN` instructions, the host needs +a way to execute non-native binaries. Configuring this is beyond +the scope of this example. Building a multi-arch manifest list +`shazam` in parallel across 4-threads can be done like this: + + $ platarch=linux/amd64,linux/ppc64le,linux/arm64,linux/s390x + $ podman build --jobs=4 --platform=$platarch --manifest shazam . + +**Note:** The `--jobs` argument is optional, and the `-t` or `--tag` +option should *not* be used. + +### Assembling a multi-arch manifest from separately built images + +Assuming `example.com/example/shazam:$arch` images are built separately +on other hosts and pushed to the `example.com` registry. They may +be combined into a manifest list, and pushed using a simple loop: + + $ REPO=example.com/example/shazam + $ podman manifest create $REPO:latest + $ for IMGTAG in amd64 s390x ppc64le arm64; do \ + podman manifest add $REPO:latest docker://$REPO:IMGTAG; \ + done + $ podman manifest push --all $REPO:latest + +**Note:** The `add` instruction argument order is `<manifest>` then `<image>`. +Also, the `--all` push option is required to ensure all contents are +pushed, not just the native platform/arch. + +### Removing and tagging a manifest list before pushing + +Special care is needed when removing and pushing manifest lists, as opposed +to the contents. You almost always want to use the `manifest rm` and +`manifest push --all` subcommands. For example, a rename and push could +be performed like this: + + $ podman tag localhost/shazam example.com/example/shazam + $ podman manifest rm localhost/shazam + $ podman manifest push --all example.com/example/shazam + + ## SEE ALSO podman(1), podman-manifest-add(1), podman-manifest-annotate(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-manifest-push(1), podman-manifest-remove(1) diff --git a/docs/source/markdown/podman-pod-logs.1.md b/docs/source/markdown/podman-pod-logs.1.md index 8378f2eea..5ccc69bb9 100644 --- a/docs/source/markdown/podman-pod-logs.1.md +++ b/docs/source/markdown/podman-pod-logs.1.md @@ -15,7 +15,7 @@ Note: Long running command of `podman pod log` with a `-f` or `--follow` needs t #### **--container**, **-c** -By default `podman pod logs` retrives logs for all the containers available within the pod differentiate by field `container`. However there are use-cases where user would want to limit the log stream only to a particular container of a pod for such cases `-c` can be used like `podman pod logs -c ctrNameorID podname`. +By default `podman pod logs` retrieves logs for all the containers available within the pod differentiate by field `container`. However there are use-cases where user would want to limit the log stream only to a particular container of a pod for such cases `-c` can be used like `podman pod logs -c ctrNameorID podname`. #### **--follow**, **-f** diff --git a/docs/source/markdown/podman-search.1.md b/docs/source/markdown/podman-search.1.md index d541e5c93..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 []) @@ -81,7 +86,7 @@ The result contains the Image name and its tag, one line for every tag associate #### **--no-trunc** -Do not truncate the output (default *false*). +Do not truncate the output (default *true*). #### **--tls-verify** @@ -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/docs/source/markdown/podman-volume-import.1.md b/docs/source/markdown/podman-volume-import.1.md index 6bb868774..88b7b1b5b 100644 --- a/docs/source/markdown/podman-volume-import.1.md +++ b/docs/source/markdown/podman-volume-import.1.md @@ -22,7 +22,7 @@ Print usage statement ## EXAMPLES ``` -$ gunzip -c hellow.tar.gz | podman volume import myvol - +$ gunzip -c hello.tar.gz | podman volume import myvol - ``` ``` $ podman volume import myvol test.tar @@ -44,7 +44,7 @@ require ( github.com/mattn/go-isatty v0.0.14 github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 github.com/mrunalp/fileutils v0.5.0 - github.com/onsi/ginkgo v1.16.4 + github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.16.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.2-0.20210819154149-5ad6f50d6283 @@ -718,8 +718,9 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 867ecc2ad..f652a3ebb 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -2016,7 +2016,7 @@ func (c *Container) generateHosts(path string) (string, error) { } // based on networking mode we may want to append the localhost -// if there isn't any record for it and also this shoud happen +// if there isn't any record for it and also this should happen // in slirp4netns and similar network modes. func (c *Container) appendLocalhost(hosts string) string { if !strings.Contains(hosts, "localhost") && diff --git a/libpod/kube.go b/libpod/kube.go index 452c2b02e..43acd856d 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/network/cni/cni_conversion.go b/libpod/network/cni/cni_conversion.go index 93d871767..01e149114 100644 --- a/libpod/network/cni/cni_conversion.go +++ b/libpod/network/cni/cni_conversion.go @@ -103,7 +103,7 @@ func createNetworkFromCNIConfigList(conf *libcni.NetworkConfigList, confPath str } default: - // A warning would be good but users would get this warning everytime so keep this at info level. + // A warning would be good but users would get this warning every time so keep this at info level. logrus.Infof("Unsupported CNI config type %s in %s, this network can still be used but inspect or list cannot show all information", firstPlugin.Network.Type, confPath) } diff --git a/libpod/network/cni/cni_types.go b/libpod/network/cni/cni_types.go index fbf917c2d..87beceff3 100644 --- a/libpod/network/cni/cni_types.go +++ b/libpod/network/cni/cni_types.go @@ -182,7 +182,7 @@ func newIPAMLocalHostRange(subnet types.IPNet, leaseRange *types.LeaseRange, gw hostRange.RangeStart = leaseRange.StartIP.String() } if leaseRange.EndIP != nil { - hostRange.RangeStart = leaseRange.EndIP.String() + hostRange.RangeEnd = leaseRange.EndIP.String() } } diff --git a/libpod/network/cni/config_test.go b/libpod/network/cni/config_test.go index 5b0feb859..0dfc6173c 100644 --- a/libpod/network/cni/config_test.go +++ b/libpod/network/cni/config_test.go @@ -621,7 +621,7 @@ var _ = Describe("Config", func() { err = libpodNet.NetworkRemove(network1.Name) Expect(err).To(BeNil()) - endIP := "10.0.0.10" + endIP := "10.0.0.30" network = types.Network{ Driver: "bridge", Subnets: []types.Subnet{ @@ -665,6 +665,22 @@ var _ = Describe("Config", func() { Expect(network1.Subnets[0].Gateway.String()).To(Equal("10.0.0.1")) Expect(network1.Subnets[0].LeaseRange.StartIP.String()).To(Equal(startIP)) Expect(network1.Subnets[0].LeaseRange.EndIP.String()).To(Equal(endIP)) + + // create a new interface to force a config load from disk + libpodNet, err = getNetworkInterface(cniConfDir, false) + Expect(err).To(BeNil()) + + network1, err = libpodNet.NetworkInspect(network1.Name) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.ID).ToNot(BeEmpty()) + Expect(network1.NetworkInterface).ToNot(BeEmpty()) + Expect(network1.Driver).To(Equal("bridge")) + Expect(network1.Subnets).To(HaveLen(1)) + Expect(network1.Subnets[0].Subnet.String()).To(Equal(subnet)) + Expect(network1.Subnets[0].Gateway.String()).To(Equal("10.0.0.1")) + Expect(network1.Subnets[0].LeaseRange.StartIP.String()).To(Equal(startIP)) + Expect(network1.Subnets[0].LeaseRange.EndIP.String()).To(Equal(endIP)) }) It("create bridge with subnet and invalid lease range", func() { @@ -1313,7 +1329,7 @@ var _ = Describe("Config", func() { Expect(networks).To(HaveLen(0)) }) - It("crate bridge network with used interface name", func() { + It("create bridge network with used interface name", func() { network := types.Network{ NetworkInterface: "cni-podman9", } diff --git a/libpod/network/types/network.go b/libpod/network/types/network.go index 2fe4f3da2..657c1ca6a 100644 --- a/libpod/network/types/network.go +++ b/libpod/network/types/network.go @@ -137,7 +137,7 @@ type NetInterface struct { MacAddress net.HardwareAddr `json:"mac_address"` } -// NetAddress contains the subnet and gatway. +// NetAddress contains the subnet and gateway. type NetAddress struct { // Subnet of this NetAddress. Note that the subnet contains the // actual ip of the net interface and not the network address. 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/libpod/pod.go b/libpod/pod.go index 068a835f6..0e5ac4906 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -390,7 +390,7 @@ func (p *Pod) InfraContainerID() (string, error) { return p.infraContainerID() } -// infraContainer is the unlocked versio of InfraContainer which returns the infra container +// infraContainer is the unlocked version of InfraContainer which returns the infra container func (p *Pod) infraContainer() (*Container, error) { id, err := p.infraContainerID() if err != nil { diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index b08693529..ed3cc971c 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -230,11 +230,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) - // TODO: do we want to set force here when removing - // containers? - // I'm inclined to say no, in case someone accidentally - // wipes a container they're using... - if err := r.removeContainer(ctx, ctr, false, false, false, timeout); err != nil { + if err := r.removeContainer(ctx, ctr, force, false, false, timeout); err != nil { return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name()) } } diff --git a/libpod/shutdown/handler.go b/libpod/shutdown/handler.go index b0feafa0b..9add05c9c 100644 --- a/libpod/shutdown/handler.go +++ b/libpod/shutdown/handler.go @@ -5,9 +5,10 @@ import ( "os/signal" "sync" "syscall" + "time" "github.com/pkg/errors" - "github.com/sirupsen/logrus" + logrusImport "github.com/sirupsen/logrus" ) var ( @@ -25,6 +26,7 @@ var ( // Ordering that on-shutdown handlers will be invoked. handlerOrder []string shutdownInhibit sync.RWMutex + logrus = logrusImport.WithField("PID", os.Getpid()) ) // Start begins handling SIGTERM and SIGINT and will run the given on-signal @@ -44,25 +46,31 @@ func Start() error { go func() { select { case <-cancelChan: + logrus.Infof("Received shutdown.Stop(), terminating!") signal.Stop(sigChan) close(sigChan) close(cancelChan) stopped = true return case sig := <-sigChan: - logrus.Infof("Received shutdown signal %v, terminating!", sig) + logrus.Infof("Received shutdown signal %q, terminating!", sig.String()) shutdownInhibit.Lock() handlerLock.Lock() + for _, name := range handlerOrder { handler, ok := handlers[name] if !ok { - logrus.Errorf("Shutdown handler %s definition not found!", name) + logrus.Errorf("Shutdown handler %q definition not found!", name) continue } - logrus.Infof("Invoking shutdown handler %s", name) + + logrus.Infof("Invoking shutdown handler %q", name) + start := time.Now() if err := handler(sig); err != nil { - logrus.Errorf("Running shutdown handler %s: %v", name, err) + logrus.Errorf("Running shutdown handler %q: %v", name, err) } + logrus.Debugf("Completed shutdown handler %q, duration %v", name, + time.Since(start).Round(time.Second)) } handlerLock.Unlock() shutdownInhibit.Unlock() 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/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 01282513e..e9cc3e2b6 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -22,7 +22,6 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { query := struct { Term string `json:"term"` Limit int `json:"limit"` - NoTrunc bool `json:"noTrunc"` Filters map[string][]string `json:"filters"` TLSVerify bool `json:"tlsVerify"` ListTags bool `json:"listTags"` @@ -50,7 +49,6 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { options := entities.ImageSearchOptions{ Authfile: authfile, Limit: query.Limit, - NoTrunc: query.NoTrunc, ListTags: query.ListTags, Filters: filters, } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index aa573eaa6..95a8b4939 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -1090,10 +1090,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // default: 25 // description: maximum number of results // - in: query - // name: noTrunc - // type: boolean - // description: do not truncate any of the result strings - // - in: query // name: filters // type: string // description: | diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 6e9578cd1..8c5c7aeeb 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -207,7 +207,7 @@ func (s *APIServer) setupSystemd() { func (s *APIServer) Serve() error { s.setupPprof() - if err := shutdown.Register("server", func(sig os.Signal) error { + if err := shutdown.Register("service", func(sig os.Signal) error { return s.Shutdown(true) }); err != nil { return err @@ -272,20 +272,24 @@ func (s *APIServer) setupPprof() { // Shutdown is a clean shutdown waiting on existing clients func (s *APIServer) Shutdown(halt bool) error { - if s.idleTracker.Duration == UnlimitedServiceDuration && !halt { - logrus.Debug("API service shutdown request ignored as Duration is UnlimitedService") + switch { + case halt: + logrus.Debug("API service forced shutdown, ignoring timeout Duration") + case s.idleTracker.Duration == UnlimitedServiceDuration: + logrus.Debug("API service shutdown request ignored as timeout Duration is UnlimitedService") return nil } shutdownOnce.Do(func() { - if logrus.IsLevelEnabled(logrus.DebugLevel) { - _, file, line, _ := runtime.Caller(1) - logrus.Debugf("API service shutdown by %s:%d, %d/%d connection(s)", - file, line, s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections()) - } + logrus.Debugf("API service shutdown, %d/%d connection(s)", + s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections()) // Gracefully shutdown server(s), duration of wait same as idle window - ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration) + deadline := 1 * time.Second + if s.idleTracker.Duration > 0 { + deadline = s.idleTracker.Duration + } + ctx, cancel := context.WithTimeout(context.Background(), deadline) go func() { defer cancel() @@ -296,7 +300,6 @@ func (s *APIServer) Shutdown(halt bool) error { }() <-ctx.Done() }) - return nil } diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index e2c46e481..dc75dac5a 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -349,6 +349,17 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, return &APIResponse{response, req}, err } +// Get raw Transport.DialContext from client +func (c *Connection) GetDialer(ctx context.Context) (net.Conn, error) { + client := c.Client + transport := client.Transport.(*http.Transport) + if transport.DialContext != nil && transport.TLSClientConfig == nil { + return transport.DialContext(ctx, c.URI.Scheme, c.URI.String()) + } + + return nil, errors.New("Unable to get dial context") +} + // FiltersToString converts our typical filter format of a // map[string][]string to a query/html safe string. func FiltersToString(filters map[string][]string) (string, error) { diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index dc6bd91c3..a44a3527f 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -133,8 +133,6 @@ type SearchOptions struct { Filters map[string][]string // Limit the number of results. Limit *int - // NoTrunc will not truncate the output. - NoTrunc *bool // SkipTLSVerify to skip HTTPS and certificate verification. SkipTLSVerify *bool // ListTags search the available tags of the repository diff --git a/pkg/bindings/images/types_search_options.go b/pkg/bindings/images/types_search_options.go index e38ef9fb1..4424f1504 100644 --- a/pkg/bindings/images/types_search_options.go +++ b/pkg/bindings/images/types_search_options.go @@ -62,21 +62,6 @@ func (o *SearchOptions) GetLimit() int { return *o.Limit } -// WithNoTrunc set field NoTrunc to given value -func (o *SearchOptions) WithNoTrunc(value bool) *SearchOptions { - o.NoTrunc = &value - return o -} - -// GetNoTrunc returns value of field NoTrunc -func (o *SearchOptions) GetNoTrunc() bool { - if o.NoTrunc == nil { - var z bool - return z - } - return *o.NoTrunc -} - // WithSkipTLSVerify set field SkipTLSVerify to given value func (o *SearchOptions) WithSkipTLSVerify(value bool) *SearchOptions { o.SkipTLSVerify = &value diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 4bb8de69b..f1ef538e4 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -129,8 +129,8 @@ func init() { func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) { if cgroup2 { controllers := []controller{} - subtreeControl := cgroupRoot + "/cgroup.subtree_control" - // rootless cgroupv2: check available controllers for current user ,systemd or servicescope will inherit + controllersFile := cgroupRoot + "/cgroup.controllers" + // rootless cgroupv2: check available controllers for current user, systemd or servicescope will inherit if rootless.IsRootless() { userSlice, err := getCgroupPathForCurrentProcess() if err != nil { @@ -138,13 +138,13 @@ func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) } //userSlice already contains '/' so not adding here basePath := cgroupRoot + userSlice - subtreeControl = fmt.Sprintf("%s/cgroup.subtree_control", basePath) + controllersFile = fmt.Sprintf("%s/cgroup.controllers", basePath) } - subtreeControlBytes, err := ioutil.ReadFile(subtreeControl) + controllersFileBytes, err := ioutil.ReadFile(controllersFile) if err != nil { - return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", subtreeControl) + return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", controllersFile) } - for _, controllerName := range strings.Fields(string(subtreeControlBytes)) { + for _, controllerName := range strings.Fields(string(controllersFileBytes)) { c := controller{ name: controllerName, symlink: false, diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 2822b1ad7..ac5e6f410 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -218,8 +218,6 @@ type ImageSearchOptions struct { Filters []string // Limit the number of results. Limit int - // NoTrunc will not truncate the output. - NoTrunc bool // SkipTLSVerify to skip HTTPS and certificate verification. SkipTLSVerify types.OptionalBool // ListTags search the available tags of the repository diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 6ca142618..c30129001 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1319,6 +1319,15 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri if options.Interval < 1 { return nil, errors.New("Invalid interval, must be a positive number greater zero") } + if rootless.IsRootless() { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + if !unified { + return nil, errors.New("stats is not supported in rootless mode without cgroups v2") + } + } statsChan = make(chan entities.ContainerStatsReport, 1) containerFunc := ic.Libpod.GetRunningContainers diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 8a0b87cab..d2222c017 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -417,6 +417,7 @@ func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportO return &entities.ImageImportReport{Id: imageID}, nil } +// Search for images using term and filters func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { filter, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { @@ -427,7 +428,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im Authfile: opts.Authfile, Filter: *filter, Limit: opts.Limit, - NoTrunc: opts.NoTrunc, + NoTrunc: true, InsecureSkipTLSVerify: opts.SkipTLSVerify, ListTags: opts.ListTags, } @@ -454,7 +455,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im return reports, nil } -// GetConfig returns a copy of the configuration used by the runtime +// Config returns a copy of the configuration used by the runtime func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { return ir.Libpod.GetConfig() } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index d41a20348..b8af2de68 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -12,7 +12,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" - images "github.com/containers/podman/v3/pkg/bindings/images" + "github.com/containers/podman/v3/pkg/bindings/images" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" "github.com/containers/podman/v3/pkg/domain/utils" @@ -323,7 +323,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im options := new(images.SearchOptions) options.WithAuthfile(opts.Authfile).WithFilters(mappedFilters).WithLimit(opts.Limit) - options.WithListTags(opts.ListTags).WithNoTrunc(opts.NoTrunc) + options.WithListTags(opts.ListTags) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { options.WithSkipTLSVerify(true) 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/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index e93462008..6eebc6376 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -257,7 +257,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener // Environment Variables envs := map[string]string{} for _, env := range imageData.Config.Env { - keyval := strings.Split(env, "=") + keyval := strings.SplitN(env, "=", 2) envs[keyval[0]] = keyval[1] } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index dbb669291..07995b2df 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -552,10 +552,10 @@ func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { if rootfs { csc.Rootfs = arg // check if rootfs is actually overlayed - parts := strings.SplitN(csc.Rootfs, ":", 2) - if len(parts) > 1 && parts[1] == "O" { + lastColonIndex := strings.LastIndex(csc.Rootfs, ":") + if lastColonIndex != -1 && lastColonIndex+1 < len(csc.Rootfs) && csc.Rootfs[lastColonIndex+1:] == "O" { csc.RootfsOverlay = true - csc.Rootfs = parts[0] + csc.Rootfs = csc.Rootfs[:lastColonIndex] } } else { csc.Image = arg diff --git a/pkg/specgen/specgen_test.go b/pkg/specgen/specgen_test.go new file mode 100644 index 000000000..b838d9d30 --- /dev/null +++ b/pkg/specgen/specgen_test.go @@ -0,0 +1,25 @@ +package specgen + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewSpecGeneratorWithRootfs(t *testing.T) { + tests := []struct { + rootfs string + expectedRootfsOverlay bool + expectedRootfs string + }{ + {"/root/a:b:O", true, "/root/a:b"}, + {"/root/a:b/c:O", true, "/root/a:b/c"}, + {"/root/a:b/c:", false, "/root/a:b/c:"}, + {"/root/a/b", false, "/root/a/b"}, + } + for _, args := range tests { + val := NewSpecGenerator(args.rootfs, true) + assert.Equal(t, val.RootfsOverlay, args.expectedRootfsOverlay) + assert.Equal(t, val.Rootfs, args.expectedRootfs) + } +} diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index fac200c3c..2faad8d91 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -445,7 +445,7 @@ var _ = Describe("Podman run", func() { Expect(session.ErrorToString()).To(ContainSubstring("invalid image_copy_tmp_dir")) }) - It("podman system sevice --help shows (default 20)", func() { + It("podman system service --help shows (default 20)", func() { SkipIfRemote("this test is only for local") result := podmanTest.Podman([]string{"system", "service", "--help"}) result.WaitWithDefaultTimeout() diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index b4ec7447e..56af64f04 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -446,4 +446,25 @@ RUN > file2 }) + It("podman builder prune", func() { + dockerfile := `FROM quay.io/libpod/alpine:latest +RUN > file +` + dockerfile2 := `FROM quay.io/libpod/alpine:latest +RUN > file2 +` + podmanTest.BuildImageWithLabel(dockerfile, "foobar.com/workdir:latest", "false", "abc") + podmanTest.BuildImageWithLabel(dockerfile2, "foobar.com/workdir:latest", "false", "xyz") + // --force used to to avoid y/n question + result := podmanTest.Podman([]string{"builder", "prune", "--filter", "label=abc", "--force"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(Exit(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + + //check if really abc is removed + result = podmanTest.Podman([]string{"image", "list", "--filter", "label=abc"}) + Expect(len(result.OutputToStringArray())).To(Equal(0)) + + }) + }) diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index ae9f112b5..c9e13e7d2 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -43,7 +43,7 @@ var _ = Describe("Podman network create", func() { It("podman network create with name and subnet", func() { netName := "subnet-" + stringid.GenerateNonCryptoID() - nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", netName}) + nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", "--ip-range", "10.11.12.0/26", netName}) nc.WaitWithDefaultTimeout() defer podmanTest.removeCNINetwork(netName) Expect(nc).Should(Exit(0)) @@ -61,7 +61,11 @@ var _ = Describe("Podman network create", func() { result := results[0] Expect(result.Name).To(Equal(netName)) Expect(result.Subnets).To(HaveLen(1)) + Expect(result.Subnets[0].Subnet.String()).To(Equal("10.11.12.0/24")) Expect(result.Subnets[0].Gateway.String()).To(Equal("10.11.12.1")) + Expect(result.Subnets[0].LeaseRange).ToNot(BeNil()) + Expect(result.Subnets[0].LeaseRange.StartIP.String()).To(Equal("10.11.12.1")) + Expect(result.Subnets[0].LeaseRange.EndIP.String()).To(Equal("10.11.12.63")) // Once a container executes a new network, the nic will be created. We should clean those up // best we can diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 079bb53b5..8aeba9d75 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -18,6 +18,7 @@ import ( "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/format" . "github.com/onsi/gomega/gexec" "github.com/opencontainers/selinux/go-selinux" ) @@ -2852,4 +2853,45 @@ invalid kube kind Expect(ls).Should(Exit(0)) Expect(len(ls.OutputToStringArray())).To(Equal(1)) }) + + Describe("verify environment variables", func() { + var maxLength int + BeforeEach(func() { + maxLength = format.MaxLength + format.MaxLength = 0 + }) + AfterEach(func() { + format.MaxLength = maxLength + }) + + It("values containing equal sign", func() { + javaToolOptions := `-XX:+IgnoreUnrecognizedVMOptions -XX:+IdleTuningGcOnIdle -Xshareclasses:name=openj9_system_scc,cacheDir=/opt/java/.scc,readonly,nonFatal` + openj9JavaOptions := `-XX:+IgnoreUnrecognizedVMOptions -XX:+IdleTuningGcOnIdle -Xshareclasses:name=openj9_system_scc,cacheDir=/opt/java/.scc,readonly,nonFatal -Dosgi.checkConfiguration=false` + + containerfile := fmt.Sprintf(`FROM %s +ENV JAVA_TOOL_OPTIONS=%q +ENV OPENJ9_JAVA_OPTIONS=%q +`, + ALPINE, javaToolOptions, openj9JavaOptions) + + image := "podman-kube-test:env" + podmanTest.BuildImage(containerfile, image, "false") + ctnr := getCtr(withImage(image)) + pod := getPod(withCtr(ctnr)) + Expect(generateKubeYaml("pod", pod, kubeYaml)).Should(Succeed()) + + play := podmanTest.Podman([]string{"play", "kube", "--start", kubeYaml}) + play.WaitWithDefaultTimeout() + Expect(play).Should(Exit(0)) + + inspect := podmanTest.Podman([]string{"container", "inspect", "--format=json", getCtrNameInPod(pod)}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + + contents := string(inspect.Out.Contents()) + Expect(contents).To(ContainSubstring(javaToolOptions)) + Expect(contents).To(ContainSubstring(openj9JavaOptions)) + }) + }) + }) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index f82c3d9d1..10e991d9f 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -2,6 +2,7 @@ package integration import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "os" @@ -9,6 +10,7 @@ import ( "strconv" "text/template" + "github.com/containers/podman/v3/pkg/domain/entities" . "github.com/containers/podman/v3/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -105,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()) }) @@ -123,6 +136,15 @@ registries = ['{{.Host}}:{{.Port}}']` Expect(search).Should(Exit(0)) Expect(search.IsJSONOutputValid()).To(BeTrue()) Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/alpine")) + + // Test for https://github.com/containers/podman/issues/11894 + contents := make([]entities.ImageSearchReport, 0) + err := json.Unmarshal(search.Out.Contents(), &contents) + Expect(err).ToNot(HaveOccurred()) + Expect(len(contents)).To(BeNumerically(">", 0), "No results from image search") + for _, element := range contents { + Expect(element.Description).ToNot(HaveSuffix("...")) + } }) It("podman search format json list tags", func() { @@ -135,13 +157,17 @@ registries = ['{{.Host}}:{{.Port}}']` Expect(search.OutputToString()).To(ContainSubstring("2.7")) }) - It("podman search no-trunc flag", func() { - search := podmanTest.Podman([]string{"search", "--no-trunc", "alpine"}) + // Test for https://github.com/containers/podman/issues/11894 + It("podman search no-trunc=false flag", func() { + search := podmanTest.Podman([]string{"search", "--no-trunc=false", "alpine", "--format={{.Description}}"}) search.WaitWithDefaultTimeout() Expect(search).Should(Exit(0)) - Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1)) - Expect(search.LineInOutputContains("docker.io/library/alpine")).To(BeTrue()) - Expect(search.LineInOutputContains("...")).To(BeFalse()) + + for _, line := range search.OutputToStringArray() { + if len(line) > 44 { + Expect(line).To(HaveSuffix("..."), line+" should have been truncated") + } + } }) It("podman search limit flag", func() { diff --git a/test/e2e/system_dial_stdio_test.go b/test/e2e/system_dial_stdio_test.go new file mode 100644 index 000000000..afe3d5acd --- /dev/null +++ b/test/e2e/system_dial_stdio_test.go @@ -0,0 +1,53 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/podman/v3/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("podman system dial-stdio", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("podman system dial-stdio help", func() { + session := podmanTest.Podman([]string{"system", "dial-stdio", "--help"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("Examples: podman system dial-stdio")) + }) + + It("podman system dial-stdio while service is not running", func() { + if IsRemote() { + Skip("this test is only for non-remote") + } + session := podmanTest.Podman([]string{"system", "dial-stdio"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("Error: failed to open connection to podman")) + }) +}) diff --git a/test/e2e/system_service_test.go b/test/e2e/system_service_test.go index 684ac56b4..9a4d687c3 100644 --- a/test/e2e/system_service_test.go +++ b/test/e2e/system_service_test.go @@ -65,7 +65,7 @@ var _ = Describe("podman system service", func() { pprofPort := randomPort() session := podmanTest.Podman([]string{ - "system", "service", "--log-level=info", "--time=0", + "system", "service", "--log-level=debug", "--time=0", "--pprof-address=localhost:" + pprofPort, address.String(), }) defer session.Kill() @@ -91,7 +91,7 @@ var _ = Describe("podman system service", func() { Expect(body).ShouldNot(BeEmpty()) session.Interrupt().Wait(2 * time.Second) - Eventually(session, 2).Should(Exit(1)) + Eventually(session).Should(Exit(1)) }) It("are not available", func() { @@ -103,7 +103,7 @@ var _ = Describe("podman system service", func() { } session := podmanTest.Podman([]string{ - "system", "service", "--log-level=info", "--time=0", address.String(), + "system", "service", "--log-level=debug", "--time=0", address.String(), }) defer session.Kill() @@ -113,7 +113,7 @@ var _ = Describe("podman system service", func() { Expect(session.Err.Contents()).ShouldNot(ContainSubstring(magicComment)) session.Interrupt().Wait(2 * time.Second) - Eventually(session, 2).Should(Exit(1)) + Eventually(session).Should(Exit(1)) }) }) }) diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 490d635e5..7128f1b65 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -97,6 +97,14 @@ Labels.l | $mylabel run_podman volume rm $myvolume } +# Removing volumes with --force +@test "podman volume rm --force" { + run_podman run -d --volume myvol:/myvol $IMAGE top + cid=$output + run_podman 2 volume rm myvol + is "$output" "Error: volume myvol is being used by the following container(s): $cid: volume is being used" "should error since container is running" + run_podman volume rm myvol --force +} # Running scripts (executables) from a volume @test "podman volume: exec/noexec" { diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats index 3607c1028..98241c309 100644 --- a/test/system/250-systemd.bats +++ b/test/system/250-systemd.bats @@ -153,7 +153,7 @@ function service_cleanup() { cname3=$(random_string) run_podman create --restart=on-failure:42 --name $cname3 $IMAGE run_podman generate systemd --new $cname3 - is "$output" ".*Restart=on-failure.*" "on-failure:xx is parsed correclty" + is "$output" ".*Restart=on-failure.*" "on-failure:xx is parsed correctly" is "$output" ".*StartLimitBurst=42.*" "on-failure:xx is parsed correctly" run_podman rm -t 0 -f $cname $cname2 $cname3 diff --git a/vendor/github.com/onsi/ginkgo/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/CHANGELOG.md index 494abdbfb..a26bc530f 100644 --- a/vendor/github.com/onsi/ginkgo/CHANGELOG.md +++ b/vendor/github.com/onsi/ginkgo/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.16.5 + +Ginkgo 2.0 now has a Release Candidate. 1.16.5 advertises the existence of the RC. +1.16.5 deprecates GinkgoParallelNode in favor of GinkgoParallelProcess + +You can silence the RC advertisement by setting an `ACK_GINKG_RC=true` environment variable or creating a file in your home directory called `.ack-ginkgo-rc` + ## 1.16.4 ### Fixes diff --git a/vendor/github.com/onsi/ginkgo/README.md b/vendor/github.com/onsi/ginkgo/README.md index 05321e6ea..a25ca5e03 100644 --- a/vendor/github.com/onsi/ginkgo/README.md +++ b/vendor/github.com/onsi/ginkgo/README.md @@ -1,23 +1,18 @@ ![Ginkgo: A Go BDD Testing Framework](https://onsi.github.io/ginkgo/images/ginkgo.png) -[![Build Status](https://travis-ci.org/onsi/ginkgo.svg?branch=master)](https://travis-ci.org/onsi/ginkgo) [![test](https://github.com/onsi/ginkgo/workflows/test/badge.svg?branch=master)](https://github.com/onsi/ginkgo/actions?query=workflow%3Atest+branch%3Amaster) Jump to the [docs](https://onsi.github.io/ginkgo/) | [中文文档](https://ke-chain.github.io/ginkgodoc) to learn more. To start rolling your Ginkgo tests *now* [keep reading](#set-me-up)! If you have a question, comment, bug report, feature request, etc. please open a GitHub issue, or visit the [Ginkgo Slack channel](https://app.slack.com/client/T029RQSE6/CQQ50BBNW). -# Ginkgo 2.0 is coming soon! +# Ginkgo 2.0 Release Candidate is available! -An effort is underway to develop and deliver Ginkgo 2.0. The work is happening in the [v2](https://github.com/onsi/ginkgo/tree/v2) branch and a changelog and migration guide is being maintained on that branch [here](https://github.com/onsi/ginkgo/blob/v2/docs/MIGRATING_TO_V2.md). Issue [#711](https://github.com/onsi/ginkgo/issues/711) is the central place for discussion and links to the original [proposal doc](https://docs.google.com/document/d/1h28ZknXRsTLPNNiOjdHIO-F2toCzq4xoZDXbfYaBdoQ/edit#). +An effort is underway to develop and deliver Ginkgo 2.0. The work is happening in the [ver2](https://github.com/onsi/ginkgo/tree/ver2) branch and a changelog and migration guide is being maintained on that branch [here](https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md). Issue [#711](https://github.com/onsi/ginkgo/issues/711) is the central place for discussion. -As described in the [changelog](https://github.com/onsi/ginkgo/blob/v2/docs/MIGRATING_TO_V2.md) and [proposal](https://docs.google.com/document/d/1h28ZknXRsTLPNNiOjdHIO-F2toCzq4xoZDXbfYaBdoQ/edit#), Ginkgo 2.0 will clean up the Ginkgo codebase, deprecate and remove some v1 functionality, and add several new much-requested features. To help users get ready for the migration, Ginkgo v1 has started emitting deprecation warnings for features that will no longer be supported with links to documentation for how to migrate away from these features. If you have concerns or comments please chime in on [#711](https://github.com/onsi/ginkgo/issues/711). +As described in the [changelog](https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md) and [proposal](https://docs.google.com/document/d/1h28ZknXRsTLPNNiOjdHIO-F2toCzq4xoZDXbfYaBdoQ/edit#), Ginkgo 2.0 will clean up the Ginkgo codebase, deprecate and remove some v1 functionality, and add several new much-requested features. To help users get ready for the migration, Ginkgo v1 has started emitting deprecation warnings for features that will no longer be supported with links to documentation for how to migrate away from these features. If you have concerns or comments please chime in on [#711](https://github.com/onsi/ginkgo/issues/711). -The current timeline for completion of 2.0 looks like: - -- Early April 2021: first public release of 2.0, deprecation warnings land in v1. -- May 2021: first beta/rc of 2.0 with most new functionality in place. -- June/July 2021: 2.0 ships and fully replaces the 1.x codebase on master. +Please start exploring and using the V2 release! To get started follow the [Using the Release Candidate](https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md#using-the-beta) directions in the migration guide. ## TLDR Ginkgo builds on Go's `testing` package, allowing expressive [Behavior-Driven Development](https://en.wikipedia.org/wiki/Behavior-driven_development) ("BDD") style tests. diff --git a/vendor/github.com/onsi/ginkgo/config/config.go b/vendor/github.com/onsi/ginkgo/config/config.go index 5f3f43969..3130c7789 100644 --- a/vendor/github.com/onsi/ginkgo/config/config.go +++ b/vendor/github.com/onsi/ginkgo/config/config.go @@ -20,7 +20,7 @@ import ( "fmt" ) -const VERSION = "1.16.4" +const VERSION = "1.16.5" type GinkgoConfigType struct { RandomSeed int64 diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go index 6f5af3913..ea10e9796 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/bootstrap_command.go @@ -37,6 +37,7 @@ func BuildBootstrapCommand() *Command { }, Command: func(args []string, additionalArgs []string) { generateBootstrap(agouti, noDot, internal, customBootstrapFile) + emitRCAdvertisement() }, } } diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go index 27758beba..f79271676 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/generate_command.go @@ -36,6 +36,7 @@ func BuildGenerateCommand() *Command { }, Command: func(args []string, additionalArgs []string) { generateSpec(args, agouti, noDot, internal, customTestFile) + emitRCAdvertisement() }, } } diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go index 23b1d2f11..db3f40406 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/help_command.go @@ -20,6 +20,7 @@ func BuildHelpCommand() *Command { func printHelp(args []string, additionalArgs []string) { if len(args) == 0 { usage() + emitRCAdvertisement() } else { command, found := commandMatching(args[0]) if !found { @@ -27,5 +28,6 @@ func printHelp(args []string, additionalArgs []string) { } usageForCommand(command, true) + emitRCAdvertisement() } } diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/main.go b/vendor/github.com/onsi/ginkgo/ginkgo/main.go index ac725bf40..ae0e1daf6 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/main.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/main.go @@ -131,9 +131,11 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strings" "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/formatter" "github.com/onsi/ginkgo/ginkgo/testsuite" ) @@ -243,6 +245,7 @@ func usageForCommand(command *Command, longForm bool) { func complainAndQuit(complaint string) { fmt.Fprintf(os.Stderr, "%s\nFor usage instructions:\n\tginkgo help\n", complaint) + emitRCAdvertisement() os.Exit(1) } @@ -306,3 +309,29 @@ func pluralizedWord(singular, plural string, count int) string { } return plural } + +func emitRCAdvertisement() { + ackRC := os.Getenv("ACK_GINKGO_RC") + if ackRC != "" { + return + } + home, err := os.UserHomeDir() + if err == nil { + _, err := os.Stat(filepath.Join(home, ".ack-ginkgo-rc")) + if err == nil { + return + } + } + + out := formatter.F("\n{{light-yellow}}Ginkgo 2.0 is coming soon!{{/}}\n") + out += formatter.F("{{light-yellow}}=========================={{/}}\n") + out += formatter.F("{{bold}}{{green}}Ginkgo 2.0{{/}} is under active development and will introduce several new features, improvements, and a small handful of breaking changes.\n") + out += formatter.F("A release candidate for 2.0 is now available and 2.0 should GA in Fall 2021. {{bold}}Please give the RC a try and send us feedback!{{/}}\n") + out += formatter.F(" - To learn more, view the migration guide at {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md{{/}}\n") + out += formatter.F(" - For instructions on using the Release Candidate visit {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md#using-the-beta{{/}}\n") + out += formatter.F(" - To comment, chime in at {{cyan}}{{underline}}https://github.com/onsi/ginkgo/issues/711{{/}}\n\n") + out += formatter.F("To {{bold}}{{coral}}silence this notice{{/}}, set the environment variable: {{bold}}ACK_GINKGO_RC=true{{/}}\n") + out += formatter.F("Alternatively you can: {{bold}}touch $HOME/.ack-ginkgo-rc{{/}}") + + fmt.Println(out) +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go index c7f80d143..f3d4e99a5 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/run_command.go @@ -161,6 +161,7 @@ func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { } } else { fmt.Printf("Test Suite Failed\n") + emitRCAdvertisement() os.Exit(1) } } diff --git a/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go b/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go index f586908e8..a5b68c216 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo/version_command.go @@ -21,4 +21,5 @@ func BuildVersionCommand() *Command { func printVersion([]string, []string) { fmt.Printf("Ginkgo Version %s\n", config.VERSION) + emitRCAdvertisement() } diff --git a/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go b/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go index 4a6e1e1ee..ccd7685e3 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go @@ -73,9 +73,15 @@ func GinkgoRandomSeed() int64 { return config.GinkgoConfig.RandomSeed } -//GinkgoParallelNode returns the parallel node number for the current ginkgo process -//The node number is 1-indexed +//GinkgoParallelNode is deprecated, use GinkgoParallelProcess instead func GinkgoParallelNode() int { + deprecationTracker.TrackDeprecation(types.Deprecations.ParallelNode(), codelocation.New(1)) + return GinkgoParallelProcess() +} + +//GinkgoParallelProcess returns the parallel process number for the current ginkgo process +//The process number is 1-indexed +func GinkgoParallelProcess() int { return config.GinkgoConfig.ParallelNode } @@ -109,6 +115,7 @@ func GinkgoT(optionalOffset ...int) GinkgoTInterface { //in the testing package's T. type GinkgoTInterface interface { Cleanup(func()) + Setenv(key, value string) Error(args ...interface{}) Errorf(format string, args ...interface{}) Fail() diff --git a/vendor/github.com/onsi/ginkgo/go.mod b/vendor/github.com/onsi/ginkgo/go.mod index 86a5a97be..171144326 100644 --- a/vendor/github.com/onsi/ginkgo/go.mod +++ b/vendor/github.com/onsi/ginkgo/go.mod @@ -1,6 +1,6 @@ module github.com/onsi/ginkgo -go 1.15 +go 1.16 require ( github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 diff --git a/vendor/github.com/onsi/ginkgo/internal/testingtproxy/testing_t_proxy.go b/vendor/github.com/onsi/ginkgo/internal/testingtproxy/testing_t_proxy.go index d7bbb7a96..4dcfaf4cd 100644 --- a/vendor/github.com/onsi/ginkgo/internal/testingtproxy/testing_t_proxy.go +++ b/vendor/github.com/onsi/ginkgo/internal/testingtproxy/testing_t_proxy.go @@ -34,6 +34,11 @@ func (t *ginkgoTestingTProxy) Cleanup(func()) { // No-op } +func (t *ginkgoTestingTProxy) Setenv(kev, value string) { + fmt.Println("Setenv is a noop for Ginkgo at the moment but will be implemented in V2") + // No-op until Cleanup is implemented +} + func (t *ginkgoTestingTProxy) Error(args ...interface{}) { t.fail(fmt.Sprintln(args...), t.offset) } diff --git a/vendor/github.com/onsi/ginkgo/types/deprecation_support.go b/vendor/github.com/onsi/ginkgo/types/deprecation_support.go index 305c134b7..d5a6658f3 100644 --- a/vendor/github.com/onsi/ginkgo/types/deprecation_support.go +++ b/vendor/github.com/onsi/ginkgo/types/deprecation_support.go @@ -52,6 +52,14 @@ func (d deprecations) Measure() Deprecation { } } +func (d deprecations) ParallelNode() Deprecation { + return Deprecation{ + Message: "GinkgoParallelNode is deprecated and will be removed in Ginkgo V2. Please use GinkgoParallelProcess instead.", + DocLink: "renamed-ginkgoparallelnode", + Version: "1.16.5", + } +} + func (d deprecations) Convert() Deprecation { return Deprecation{ Message: "The convert command is deprecated in Ginkgo V2", @@ -99,16 +107,18 @@ func (d *DeprecationTracker) DidTrackDeprecations() bool { } func (d *DeprecationTracker) DeprecationsReport() string { - out := formatter.F("{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}\n") + out := formatter.F("\n{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}\n") out += formatter.F("{{light-yellow}}============================================={{/}}\n") - out += formatter.F("Ginkgo 2.0 is under active development and will introduce (a small number of) breaking changes.\n") - out += formatter.F("To learn more, view the migration guide at {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/v2/docs/MIGRATING_TO_V2.md{{/}}\n") - out += formatter.F("To comment, chime in at {{cyan}}{{underline}}https://github.com/onsi/ginkgo/issues/711{{/}}\n\n") + out += formatter.F("{{bold}}{{green}}Ginkgo 2.0{{/}} is under active development and will introduce several new features, improvements, and a small handful of breaking changes.\n") + out += formatter.F("A release candidate for 2.0 is now available and 2.0 should GA in Fall 2021. {{bold}}Please give the RC a try and send us feedback!{{/}}\n") + out += formatter.F(" - To learn more, view the migration guide at {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md{{/}}\n") + out += formatter.F(" - For instructions on using the Release Candidate visit {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md#using-the-beta{{/}}\n") + out += formatter.F(" - To comment, chime in at {{cyan}}{{underline}}https://github.com/onsi/ginkgo/issues/711{{/}}\n\n") for deprecation, locations := range d.deprecations { out += formatter.Fi(1, "{{yellow}}"+deprecation.Message+"{{/}}\n") if deprecation.DocLink != "" { - out += formatter.Fi(1, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/v2/docs/MIGRATING_TO_V2.md#%s{{/}}\n", deprecation.DocLink) + out += formatter.Fi(1, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}https://github.com/onsi/ginkgo/blob/ver2/docs/MIGRATING_TO_V2.md#%s{{/}}\n", deprecation.DocLink) } for _, location := range locations { out += formatter.Fi(2, "{{gray}}%s{{/}}\n", location) diff --git a/vendor/modules.txt b/vendor/modules.txt index ae28bf9ee..830de276b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -458,7 +458,7 @@ github.com/nxadm/tail/ratelimiter github.com/nxadm/tail/util github.com/nxadm/tail/watch github.com/nxadm/tail/winfile -# github.com/onsi/ginkgo v1.16.4 +# github.com/onsi/ginkgo v1.16.5 github.com/onsi/ginkgo github.com/onsi/ginkgo/config github.com/onsi/ginkgo/extensions/table |