diff options
31 files changed, 339 insertions, 196 deletions
@@ -267,7 +267,7 @@ test/version/version: version/version.go .PHONY: codespell codespell: - codespell -S bin,vendor,.git,go.sum,.cirrus.yml,"RELEASE_NOTES.md,*.xz,*.gz,*.ps1,*.tar,swagger.yaml,*.tgz,bin2img,*ico,*.png,*.1,*.5,copyimg,*.orig,apidoc.go" -L pullrequest,uint,iff,od,seeked,splitted,marge,erro,hist,ether -w + codespell -S bin,vendor,.git,go.sum,.cirrus.yml,"RELEASE_NOTES.md,*.xz,*.gz,*.ps1,*.tar,swagger.yaml,*.tgz,bin2img,*ico,*.png,*.1,*.5,copyimg,*.orig,apidoc.go" -L clos,ans,pullrequest,uint,iff,od,seeked,splitted,marge,erro,hist,ether -w .PHONY: validate validate: lint .gitvalidation validate.completions man-page-check swagger-check tests-included tests-expect-exit pr-removes-fixed-skips diff --git a/cmd/podman/syslog_linux.go b/cmd/podman/syslog_common.go index ac7bbfe0f..e035e6365 100644 --- a/cmd/podman/syslog_linux.go +++ b/cmd/podman/syslog_common.go @@ -1,3 +1,6 @@ +//go:build linux || freebsd +// +build linux freebsd + package main import ( diff --git a/cmd/podman/syslog_unsupported.go b/cmd/podman/syslog_unsupported.go index 42a7851ab..365e5b2b4 100644 --- a/cmd/podman/syslog_unsupported.go +++ b/cmd/podman/syslog_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package main diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 8d0240a8d..68ac8902b 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -105,7 +105,9 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities } if err := utils.MaybeMoveToSubCgroup(); err != nil { - return err + // it is a best effort operation, so just print the + // error for debugging purposes. + logrus.Debugf("Could not move to subcgroup: %v", err) } servicereaper.Start() diff --git a/docs/source/markdown/options/systemd.md b/docs/source/markdown/options/systemd.md new file mode 100644 index 000000000..a341edbc2 --- /dev/null +++ b/docs/source/markdown/options/systemd.md @@ -0,0 +1,29 @@ +#### **--systemd**=*true* | *false* | *always* + +Run container in systemd mode. The default is **true**. + +The value *always* enforces the systemd mode is enforced without +looking at the executable name. Otherwise, if set to true and the +command you are running inside the container is **systemd**, **/usr/sbin/init**, +**/sbin/init** or **/usr/local/sbin/init**. + +Running the container in systemd mode causes the following changes: + +* Podman mounts tmpfs file systems on the following directories + * _/run_ + * _/run/lock_ + * _/tmp_ + * _/sys/fs/cgroup/systemd_ + * _/var/lib/journal_ +* Podman sets the default stop signal to **SIGRTMIN+3**. +* Podman sets **container_uuid** environment variable in the container to the +first 32 characters of the container id. + +This allows systemd to run in a confined container without any modifications. + +Note that on **SELinux** systems, systemd attempts to write to the cgroup +file system. Containers writing to the cgroup file system are denied by default. +The **container_manage_cgroup** boolean must be enabled for this to be allowed on an SELinux separated system. +``` +setsebool -P container_manage_cgroup true +``` diff --git a/docs/source/markdown/podman-create.1.md.in b/docs/source/markdown/podman-create.1.md.in index 4dbc75551..1ff7429c7 100644 --- a/docs/source/markdown/podman-create.1.md.in +++ b/docs/source/markdown/podman-create.1.md.in @@ -632,34 +632,7 @@ Network Namespace - current sysctls allowed: Note: if you use the --network=host option these sysctls will not be allowed. -#### **--systemd**=*true* | *false* | *always* - -Run container in systemd mode. The default is *true*. - -The value *always* enforces the systemd mode is enforced without -looking at the executable name. Otherwise, if set to true and the -command you are running inside the container is **systemd**, **/usr/sbin/init**, -**/sbin/init** or **/usr/local/sbin/init**. - -Running the container in systemd mode causes the following changes: - -* Podman mounts tmpfs file systems on the following directories - * _/run_ - * _/run/lock_ - * _/tmp_ - * _/sys/fs/cgroup/systemd_ - * _/var/lib/journal_ -* Podman sets the default stop signal to **SIGRTMIN+3**. -* Podman sets **container_uuid** environment variable in the container to the -first 32 characters of the container id. - -This allows systemd to run in a confined container without any modifications. - -Note: On `SELinux` systems, systemd attempts to write to the cgroup -file system. Containers writing to the cgroup file system are denied by default. -The `container_manage_cgroup` boolean must be enabled for this to be allowed on an SELinux separated system. - -`setsebool -P container_manage_cgroup true` +@@option systemd @@option timeout diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md index fc2ce171e..88dff2a45 100644 --- a/docs/source/markdown/podman-generate-systemd.1.md +++ b/docs/source/markdown/podman-generate-systemd.1.md @@ -26,7 +26,7 @@ therefore the overridden default value._ A Kubernetes YAML can be executed in systemd via the `podman-kube@.service` systemd template. The template's argument is the path to the YAML file. Given a `workload.yaml` file in the home directory, it can be executed as follows: ``` -$ escaped=$(systemd-escape ~/sysadmin.yaml) +$ escaped=$(systemd-escape ~/workload.yaml) $ systemctl --user start podman-kube@$escaped.service $ systemctl --user is-active podman-kube@$escaped.service active diff --git a/docs/source/markdown/podman-run.1.md.in b/docs/source/markdown/podman-run.1.md.in index c7985d7e1..f172ffc9e 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -682,35 +682,7 @@ For the network namespace, the following sysctls are allowed: Note: if you use the **--network=host** option, these sysctls will not be allowed. -#### **--systemd**=*true* | *false* | *always* - -Run container in systemd mode. The default is **true**. - -The value *always* enforces the systemd mode is enforced without -looking at the executable name. Otherwise, if set to true and the -command you are running inside the container is **systemd**, **/usr/sbin/init**, -**/sbin/init** or **/usr/local/sbin/init**. - -Running the container in systemd mode causes the following changes: - -* Podman mounts tmpfs file systems on the following directories - * _/run_ - * _/run/lock_ - * _/tmp_ - * _/sys/fs/cgroup/systemd_ - * _/var/lib/journal_ -* Podman sets the default stop signal to **SIGRTMIN+3**. -* Podman sets **container_uuid** environment variable in the container to the -first 32 characters of the container id. - -This allows systemd to run in a confined container without any modifications. - -Note that on **SELinux** systems, systemd attempts to write to the cgroup -file system. Containers writing to the cgroup file system are denied by default. -The **container_manage_cgroup** boolean must be enabled for this to be allowed on an SELinux separated system. -``` -setsebool -P container_manage_cgroup true -``` +@@option systemd @@option timeout @@ -58,7 +58,7 @@ require ( github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/ulikunitz/xz v0.5.10 - github.com/vbauerster/mpb/v7 v7.4.2 + github.com/vbauerster/mpb/v7 v7.5.2 github.com/vishvananda/netlink v1.1.1-0.20220115184804-dd687eb2f2d4 go.etcd.io/bbolt v1.3.6 golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f @@ -1593,8 +1593,9 @@ github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/V github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= -github.com/vbauerster/mpb/v7 v7.4.2 h1:n917F4d8EWdUKc9c81wFkksyG6P6Mg7IETfKCE1Xqng= github.com/vbauerster/mpb/v7 v7.4.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= +github.com/vbauerster/mpb/v7 v7.5.2 h1:Ph3JvpBcoIwzIG1QwbUq97KQifrTRbKcMXN9rN5BYAs= +github.com/vbauerster/mpb/v7 v7.5.2/go.mod h1:UmOiIUI8aPqWXIps0ciik3RKMdzx7+ooQpq+fBcXwBA= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index de92ff260..074aeee47 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -69,21 +69,21 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // getHostsEntries returns the container ip host entries for the correct netmode func (c *Container) getHostsEntries() (etchosts.HostEntries, error) { - return nil, errors.New("unspported (*Container) getHostsEntries") + return nil, errors.New("unsupported (*Container) getHostsEntries") } // Fix ownership and permissions of the specified volume if necessary. func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error { - return errors.New("unspported (*Container) fixVolumePermissions") + return errors.New("unsupported (*Container) fixVolumePermissions") } func (c *Container) expectPodCgroup() (bool, error) { - return false, errors.New("unspported (*Container) expectPodCgroup") + return false, errors.New("unsupported (*Container) expectPodCgroup") } // Get cgroup path in a format suitable for the OCI spec func (c *Container) getOCICgroupPath() (string, error) { - return "", errors.New("unspported (*Container) getOCICgroupPath") + return "", errors.New("unsupported (*Container) getOCICgroupPath") } func getLocalhostHostEntry(c *Container) etchosts.HostEntries { diff --git a/libpod/define/exec_codes.go b/libpod/define/exec_codes.go index 3f2da4910..a84730e72 100644 --- a/libpod/define/exec_codes.go +++ b/libpod/define/exec_codes.go @@ -11,8 +11,8 @@ const ( // ExecErrorCodeGeneric is the default error code to return from an exec session if libpod failed // prior to calling the runtime ExecErrorCodeGeneric = 125 - // ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command - // an example of this can be found by trying to execute a directory: + // ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command. + // An example of this can be found by trying to execute a directory: // `podman exec -l /etc` ExecErrorCodeCannotInvoke = 126 // ExecErrorCodeNotFound is the error code to return when a command cannot be found diff --git a/libpod/kube.go b/libpod/kube.go index 8c09a6bb5..a0fb52973 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -267,6 +267,8 @@ func GenerateKubeServiceFromV1Pod(pod *v1.Pod, servicePorts []v1.ServicePort) (Y } service.Spec = serviceSpec service.ObjectMeta = pod.ObjectMeta + // Reset the annotations for the service as the pod annotations are not needed for the service + service.ObjectMeta.Annotations = nil tm := v12.TypeMeta{ Kind: "Service", APIVersion: pod.TypeMeta.APIVersion, diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 18fb35966..105404a0d 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -89,6 +89,12 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { } e := entities.ConvertToEntitiesEvent(*evt) + // Some events differ between Libpod and Docker endpoints. + // Handle these differences for Docker-compat. + if !utils.IsLibpodRequest(r) && e.Type == "image" && e.Status == "remove" { + e.Status = "delete" + e.Action = "delete" + } if !utils.IsLibpodRequest(r) && e.Status == "died" { e.Status = "die" e.Action = "die" diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 020991cc7..7ba1029a7 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -101,6 +101,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { ForceRm bool `schema:"forcerm"` From string `schema:"from"` HTTPProxy bool `schema:"httpproxy"` + IDMappingOptions string `schema:"idmappingoptions"` IdentityLabel bool `schema:"identitylabel"` Ignore bool `schema:"ignore"` Isolation string `schema:"isolation"` @@ -389,6 +390,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } } + var idMappingOptions buildahDefine.IDMappingOptions + if _, found := r.URL.Query()["idmappingoptions"]; found { + if err := json.Unmarshal([]byte(query.IDMappingOptions), &idMappingOptions); err != nil { + utils.BadRequest(w, "idmappingoptions", query.IDMappingOptions, err) + return + } + } + var cacheFrom reference.Named if _, found := r.URL.Query()["cachefrom"]; found { cacheFrom, err = parse.RepoNameToNamedReference(query.CacheFrom) @@ -644,6 +653,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Excludes: excludes, ForceRmIntermediateCtrs: query.ForceRm, From: fromImage, + IDMappingOptions: &idMappingOptions, IgnoreUnrecognizedInstructions: query.Ignore, Isolation: isolation, Jobs: &jobs, diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 2615bc516..8348ac54b 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -88,6 +88,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } params.Set("additionalbuildcontexts", string(additionalBuildContextMap)) } + if options.IDMappingOptions != nil { + idmappingsOptions, err := jsoniter.Marshal(options.IDMappingOptions) + if err != nil { + return nil, err + } + params.Set("idmappingoptions", string(idmappingsOptions)) + } if buildArgs := options.Args; len(buildArgs) > 0 { bArgs, err := jsoniter.MarshalToString(buildArgs) if err != nil { diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 5162006db..54aa9e1b7 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -175,7 +175,7 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url return uri } -// GetCacheDir returns the dir where VM images are downladed into when pulled +// GetCacheDir returns the dir where VM images are downloaded into when pulled func GetCacheDir(vmType string) (string, error) { dataDir, err := GetDataDir(vmType) if err != nil { diff --git a/pkg/systemd/notifyproxy/notifyproxy_test.go b/pkg/systemd/notifyproxy/notifyproxy_test.go index edad95659..ce63fc9cd 100644 --- a/pkg/systemd/notifyproxy/notifyproxy_test.go +++ b/pkg/systemd/notifyproxy/notifyproxy_test.go @@ -37,7 +37,7 @@ func TestWaitAndClose(t *testing.T) { time.Sleep(250 * time.Millisecond) select { case err := <-ch: - t.Fatalf("Should stil be waiting but received %v", err) + t.Fatalf("Should still be waiting but received %v", err) default: } diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index 4fd954e37..86ee2a1f5 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -239,4 +239,23 @@ fi cleanBuildTest +# compat API vs libpod API event differences: +# on image removal, libpod produces 'remove' events. +# compat produces 'delete' events. +podman image build -t test:test -<<EOF +from $IMAGE +EOF + +START=$(date +%s) + +t DELETE libpod/images/test:test 200 +# HACK HACK HACK There is a race around events being added to the journal +# This sleep seems to avoid the race. +# If it fails and begins to flake, investigate a retry loop. +sleep 1 +t GET "libpod/events?stream=false&since=$START" 200 \ + 'select(.status | contains("remove")).Action=remove' +t GET "events?stream=false&since=$START" 200 \ + 'select(.status | contains("delete")).Action=delete' + # vim: filetype=sh diff --git a/test/e2e/build/Containerfile.userns-auto b/test/e2e/build/Containerfile.userns-auto new file mode 100644 index 000000000..921610982 --- /dev/null +++ b/test/e2e/build/Containerfile.userns-auto @@ -0,0 +1,2 @@ +FROM alpine +RUN cat /proc/self/uid_map diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index dd0070f54..9df884292 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -228,7 +228,7 @@ var _ = Describe("Podman restart", func() { Expect(beforeRestart.OutputToString()).To(Equal(afterRestart.OutputToString())) }) - It("podman restart all stoped containers with --all", func() { + It("podman restart all stopped containers with --all", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index f247b2dac..62e512d3a 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -8,6 +8,7 @@ import ( "strings" . "github.com/containers/podman/v4/test/utils" + "github.com/containers/storage" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -42,6 +43,33 @@ var _ = Describe("Podman UserNS support", func() { }) + // Note: Lot of tests for build with --userns=auto are already there in buildah + // but they are skipped in podman CI because bud tests are executed in rootfull + // environment ( where mappings for the `containers` user is not present in /etc/subuid ) + // causing them to skip hence this is a redundant test for sanity to make sure + // we don't break this feature for podman-remote. + It("podman build with --userns=auto", func() { + u, err := user.Current() + Expect(err).To(BeNil()) + name := u.Name + if name == "root" { + name = "containers" + } + content, err := ioutil.ReadFile("/etc/subuid") + if err != nil { + Skip("cannot read /etc/subuid") + } + if !strings.Contains(string(content), name) { + Skip("cannot find mappings for the current user") + } + session := podmanTest.Podman([]string{"build", "-f", "build/Containerfile.userns-auto", "-t", "test", "--userns=auto"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + // `1024` is the default size or length of the range of user IDs + // that is mapped between the two user namespaces by --userns=auto. + Expect(session.OutputToString()).To(ContainSubstring(fmt.Sprintf("%d", storage.AutoUserNsMinSize))) + }) + It("podman uidmapping and gidmapping", func() { session := podmanTest.Podman([]string{"run", "--uidmap=0:100:5000", "--gidmap=0:200:5000", "alpine", "echo", "hello"}) session.WaitWithDefaultTimeout() @@ -157,6 +185,8 @@ var _ = Describe("Podman UserNS support", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) l := session.OutputToString() + // `1024` is the default size or length of the range of user IDs + // that is mapped between the two user namespaces by --userns=auto. Expect(l).To(ContainSubstring("1024")) m[l] = l } diff --git a/vendor/github.com/vbauerster/mpb/v7/bar.go b/vendor/github.com/vbauerster/mpb/v7/bar.go index 4991f4f15..7db860e30 100644 --- a/vendor/github.com/vbauerster/mpb/v7/bar.go +++ b/vendor/github.com/vbauerster/mpb/v7/bar.go @@ -29,7 +29,7 @@ type Bar struct { recoveredPanic interface{} } -type extenderFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int) +type extenderFunc func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader // bState is actual bar's state. type bState struct { @@ -57,14 +57,15 @@ type bState struct { extender extenderFunc debugOut io.Writer - afterBar *Bar // key for (*pState).queueBars - sync bool + wait struct { + bar *Bar // key for (*pState).queueBars + sync bool + } } type renderFrame struct { - reader io.Reader - lines int - shutdown bool + rows []io.Reader + shutdown int } func newBar(container *Progress, bs *bState) *Bar { @@ -339,8 +340,8 @@ func (b *Bar) Wait() { func (b *Bar) serve(ctx context.Context, bs *bState) { defer b.container.bwg.Done() - if bs.afterBar != nil && bs.sync { - bs.afterBar.Wait() + if bs.wait.bar != nil && bs.wait.sync { + bs.wait.bar.Wait() } for { select { @@ -359,48 +360,58 @@ func (b *Bar) serve(ctx context.Context, bs *bState) { func (b *Bar) render(tw int) { select { case b.operateState <- func(s *bState) { - var reader io.Reader - var lines int + var rows []io.Reader stat := newStatistics(tw, s) defer func() { // recovering if user defined decorator panics for example if p := recover(); p != nil { if s.debugOut != nil { - fmt.Fprintln(s.debugOut, p) - _, _ = s.debugOut.Write(debug.Stack()) + for _, fn := range []func() (int, error){ + func() (int, error) { + return fmt.Fprintln(s.debugOut, p) + }, + func() (int, error) { + return s.debugOut.Write(debug.Stack()) + }, + } { + if _, err := fn(); err != nil { + panic(err) + } + } } s.aborted = !s.completed s.extender = makePanicExtender(p) - reader, lines = s.extender(nil, s.reqWidth, stat) b.recoveredPanic = p } - frame := renderFrame{ - reader: reader, - lines: lines + 1, - shutdown: s.completed || s.aborted, + if fn := s.extender; fn != nil { + rows = fn(rows, s.reqWidth, stat) + } + frame := &renderFrame{ + rows: rows, } - if frame.shutdown { + if s.completed || s.aborted { b.cancel() + frame.shutdown++ } - b.frameCh <- &frame + b.frameCh <- frame }() if b.recoveredPanic == nil { - reader = s.draw(stat) + rows = append(rows, s.draw(stat)) } - reader, lines = s.extender(reader, s.reqWidth, stat) }: case <-b.done: - var reader io.Reader - var lines int - stat, s := newStatistics(tw, b.bs), b.bs + var rows []io.Reader + s, stat := b.bs, newStatistics(tw, b.bs) if b.recoveredPanic == nil { - reader = s.draw(stat) + rows = append(rows, s.draw(stat)) + } + if fn := s.extender; fn != nil { + rows = fn(rows, s.reqWidth, stat) } - reader, lines = s.extender(reader, s.reqWidth, stat) - b.frameCh <- &renderFrame{ - reader: reader, - lines: lines + 1, + frame := &renderFrame{ + rows: rows, } + b.frameCh <- frame } } @@ -446,7 +457,7 @@ func (b *Bar) wSyncTable() [][]chan int { func (s *bState) draw(stat decor.Statistics) io.Reader { bufP, bufB, bufA := s.buffers[0], s.buffers[1], s.buffers[2] - nlr := strings.NewReader("\n") + nlr := bytes.NewReader([]byte("\n")) tw := stat.AvailableWidth for _, d := range s.pDecorators { str := d.Decor(stat) @@ -596,11 +607,11 @@ func extractBaseDecorator(d decor.Decorator) decor.Decorator { func makePanicExtender(p interface{}) extenderFunc { pstr := fmt.Sprint(p) - return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) { - mr := io.MultiReader( - strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")), - strings.NewReader("\n"), + return func(rows []io.Reader, _ int, stat decor.Statistics) []io.Reader { + r := io.MultiReader( + strings.NewReader(runewidth.Truncate(pstr, stat.AvailableWidth, "…")), + bytes.NewReader([]byte("\n")), ) - return mr, 0 + return append(rows, r) } } diff --git a/vendor/github.com/vbauerster/mpb/v7/bar_option.go b/vendor/github.com/vbauerster/mpb/v7/bar_option.go index 8599f0a57..3506ed2f1 100644 --- a/vendor/github.com/vbauerster/mpb/v7/bar_option.go +++ b/vendor/github.com/vbauerster/mpb/v7/bar_option.go @@ -60,6 +60,7 @@ func BarWidth(width int) BarOption { } // BarQueueAfter puts this (being constructed) bar into the queue. +// BarPriority will be inherited from the argument bar. // When argument bar completes or aborts queued bar replaces its place. // If sync is true queued bar is suspended until argument bar completes // or aborts. @@ -68,8 +69,8 @@ func BarQueueAfter(bar *Bar, sync bool) BarOption { return nil } return func(s *bState) { - s.afterBar = bar - s.sync = sync + s.wait.bar = bar + s.wait.sync = sync } } @@ -111,29 +112,61 @@ func BarFillerMiddleware(middle func(BarFiller) BarFiller) BarOption { } // BarPriority sets bar's priority. Zero is highest priority, i.e. bar -// will be on top. If `BarReplaceOnComplete` option is supplied, this -// option is ignored. +// will be on top. This option isn't effective with `BarQueueAfter` option. func BarPriority(priority int) BarOption { return func(s *bState) { s.priority = priority } } -// BarExtender provides a way to extend bar to the next new line. +// BarExtender extends bar with arbitrary lines. Provided BarFiller will be +// called at each render/flush cycle. Any lines written to the underlying +// io.Writer will be printed after the bar itself. func BarExtender(filler BarFiller) BarOption { + return barExtender(filler, false) +} + +// BarExtenderRev extends bar with arbitrary lines in reverse order. Provided +// BarFiller will be called at each render/flush cycle. Any lines written +// to the underlying io.Writer will be printed before the bar itself. +func BarExtenderRev(filler BarFiller) BarOption { + return barExtender(filler, true) +} + +func barExtender(filler BarFiller, rev bool) BarOption { if filler == nil { return nil } return func(s *bState) { - s.extender = makeExtenderFunc(filler) + s.extender = makeExtenderFunc(filler, rev) } } -func makeExtenderFunc(filler BarFiller) extenderFunc { +func makeExtenderFunc(filler BarFiller, rev bool) extenderFunc { buf := new(bytes.Buffer) - return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) { - filler.Fill(buf, reqWidth, st) - return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n")) + base := func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader { + buf.Reset() + filler.Fill(buf, width, stat) + for { + b, err := buf.ReadBytes('\n') + if err != nil { + break + } + rows = append(rows, bytes.NewReader(b)) + } + return rows + } + + if !rev { + return base + } else { + return func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader { + rows = base(rows, width, stat) + for left, right := 0, len(rows)-1; left < right; left, right = left+1, right-1 { + rows[left], rows[right] = rows[right], rows[left] + } + return rows + } } } diff --git a/vendor/github.com/vbauerster/mpb/v7/container_option.go b/vendor/github.com/vbauerster/mpb/v7/container_option.go index e523a1759..bfaa3286a 100644 --- a/vendor/github.com/vbauerster/mpb/v7/container_option.go +++ b/vendor/github.com/vbauerster/mpb/v7/container_option.go @@ -31,7 +31,7 @@ func WithWidth(width int) ContainerOption { } } -// WithRefreshRate overrides default 120ms refresh rate. +// WithRefreshRate overrides default 150ms refresh rate. func WithRefreshRate(d time.Duration) ContainerOption { return func(s *pState) { s.rr = d diff --git a/vendor/github.com/vbauerster/mpb/v7/cwriter/writer.go b/vendor/github.com/vbauerster/mpb/v7/cwriter/writer.go index fac15b3bc..19fd90e94 100644 --- a/vendor/github.com/vbauerster/mpb/v7/cwriter/writer.go +++ b/vendor/github.com/vbauerster/mpb/v7/cwriter/writer.go @@ -20,19 +20,30 @@ const ( // Writer is a buffered the writer that updates the terminal. The // contents of writer will be flushed when Flush is called. type Writer struct { - out io.Writer - buf bytes.Buffer - lines int - fd int - isTerminal bool + out io.Writer + buf bytes.Buffer + lines int // how much lines to clear before flushing new ones + fd int + terminal bool + termSize func(int) (int, int, error) } // New returns a new Writer with defaults. func New(out io.Writer) *Writer { - w := &Writer{out: out} + w := &Writer{ + out: out, + termSize: func(_ int) (int, int, error) { + return -1, -1, ErrNotTTY + }, + } if f, ok := out.(*os.File); ok { w.fd = int(f.Fd()) - w.isTerminal = IsTerminal(w.fd) + if IsTerminal(w.fd) { + w.terminal = true + w.termSize = func(fd int) (int, int, error) { + return GetSize(fd) + } + } } return w } @@ -67,13 +78,9 @@ func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) { return w.buf.ReadFrom(r) } -// GetWidth returns width of underlying terminal. -func (w *Writer) GetWidth() (int, error) { - if !w.isTerminal { - return -1, ErrNotTTY - } - tw, _, err := GetSize(w.fd) - return tw, err +// GetTermSize returns WxH of underlying terminal. +func (w *Writer) GetTermSize() (width, height int, err error) { + return w.termSize(w.fd) } func (w *Writer) ansiCuuAndEd() error { diff --git a/vendor/github.com/vbauerster/mpb/v7/cwriter/writer_windows.go b/vendor/github.com/vbauerster/mpb/v7/cwriter/writer_windows.go index 8f99dbe32..2c4c3707b 100644 --- a/vendor/github.com/vbauerster/mpb/v7/cwriter/writer_windows.go +++ b/vendor/github.com/vbauerster/mpb/v7/cwriter/writer_windows.go @@ -16,7 +16,7 @@ var ( ) func (w *Writer) clearLines() error { - if !w.isTerminal { + if !w.terminal { // hope it's cygwin or similar return w.ansiCuuAndEd() } diff --git a/vendor/github.com/vbauerster/mpb/v7/decor/on_condition.go b/vendor/github.com/vbauerster/mpb/v7/decor/on_condition.go index a9db0653a..74a3d9667 100644 --- a/vendor/github.com/vbauerster/mpb/v7/decor/on_condition.go +++ b/vendor/github.com/vbauerster/mpb/v7/decor/on_condition.go @@ -1,27 +1,55 @@ package decor -// OnPredicate returns decorator if predicate evaluates to true. +// OnCondition applies decorator only if a condition is true. // // `decorator` Decorator // -// `predicate` func() bool +// `cond` bool // -func OnPredicate(decorator Decorator, predicate func() bool) Decorator { - if predicate() { - return decorator - } - return nil +func OnCondition(decorator Decorator, cond bool) Decorator { + return Conditional(cond, decorator, nil) } -// OnCondition returns decorator if condition is true. +// OnPredicate applies decorator only if a predicate evaluates to true. // // `decorator` Decorator // +// `predicate` func() bool +// +func OnPredicate(decorator Decorator, predicate func() bool) Decorator { + return Predicative(predicate, decorator, nil) +} + +// Conditional returns decorator `a` if condition is true, otherwise +// decorator `b`. +// // `cond` bool // -func OnCondition(decorator Decorator, cond bool) Decorator { +// `a` Decorator +// +// `b` Decorator +// +func Conditional(cond bool, a, b Decorator) Decorator { if cond { - return decorator + return a + } else { + return b + } +} + +// Predicative returns decorator `a` if predicate evaluates to true, +// otherwise decorator `b`. +// +// `predicate` func() bool +// +// `a` Decorator +// +// `b` Decorator +// +func Predicative(predicate func() bool, a, b Decorator) Decorator { + if predicate() { + return a + } else { + return b } - return nil } diff --git a/vendor/github.com/vbauerster/mpb/v7/priority_queue.go b/vendor/github.com/vbauerster/mpb/v7/priority_queue.go index 29d9bd5a8..152482e9a 100644 --- a/vendor/github.com/vbauerster/mpb/v7/priority_queue.go +++ b/vendor/github.com/vbauerster/mpb/v7/priority_queue.go @@ -6,7 +6,8 @@ type priorityQueue []*Bar func (pq priorityQueue) Len() int { return len(pq) } func (pq priorityQueue) Less(i, j int) bool { - return pq[i].priority < pq[j].priority + // less priority pops first + return pq[i].priority > pq[j].priority } func (pq priorityQueue) Swap(i, j int) { diff --git a/vendor/github.com/vbauerster/mpb/v7/progress.go b/vendor/github.com/vbauerster/mpb/v7/progress.go index 1d9a53e5c..ea5a0c15e 100644 --- a/vendor/github.com/vbauerster/mpb/v7/progress.go +++ b/vendor/github.com/vbauerster/mpb/v7/progress.go @@ -12,7 +12,6 @@ import ( "time" "github.com/vbauerster/mpb/v7/cwriter" - "github.com/vbauerster/mpb/v7/decor" ) const ( @@ -41,6 +40,7 @@ type pState struct { // following are provided/overrided by user idCount int reqWidth int + popPriority int popCompleted bool outputDiscarded bool rr time.Duration @@ -64,10 +64,11 @@ func New(options ...ContainerOption) *Progress { // method has been called. func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { s := &pState{ - bHeap: priorityQueue{}, - rr: prr, - queueBars: make(map[*Bar]*Bar), - output: os.Stdout, + bHeap: priorityQueue{}, + rr: prr, + queueBars: make(map[*Bar]*Bar), + output: os.Stdout, + popPriority: math.MinInt32, } for _, opt := range options { @@ -118,8 +119,8 @@ func (p *Progress) Add(total int64, filler BarFiller, options ...BarOption) *Bar case p.operateState <- func(ps *pState) { bs := ps.makeBarState(total, filler, options...) bar := newBar(p, bs) - if bs.afterBar != nil { - ps.queueBars[bs.afterBar] = bar + if bs.wait.bar != nil { + ps.queueBars[bs.wait.bar] = bar } else { heap.Push(&ps.bHeap, bar) ps.heapUpdated = true @@ -204,33 +205,27 @@ func (p *Progress) serve(s *pState, cw *cwriter.Writer) { p.refreshCh = s.newTicker(p.done) + render := func(debugOut io.Writer) { + err := s.render(cw) + for err != nil { + if debugOut != nil { + _, err = fmt.Fprintln(debugOut, err) + } else { + panic(err) + } + debugOut = nil + } + } + for { select { case op := <-p.operateState: op(s) case <-p.refreshCh: - if err := s.render(cw); err != nil { - if s.debugOut != nil { - _, e := fmt.Fprintln(s.debugOut, err) - if e != nil { - panic(err) - } - } else { - panic(err) - } - } + render(s.debugOut) case <-s.shutdownNotifier: for s.heapUpdated { - if err := s.render(cw); err != nil { - if s.debugOut != nil { - _, e := fmt.Fprintln(s.debugOut, err) - if e != nil { - panic(err) - } - } else { - panic(err) - } - } + render(s.debugOut) } return } @@ -245,42 +240,52 @@ func (s *pState) render(cw *cwriter.Writer) error { syncWidth(s.pMatrix) syncWidth(s.aMatrix) - tw, err := cw.GetWidth() + width, height, err := cw.GetTermSize() if err != nil { - tw = s.reqWidth + width = s.reqWidth + height = s.bHeap.Len() } for i := 0; i < s.bHeap.Len(); i++ { bar := s.bHeap[i] - go bar.render(tw) + go bar.render(width) } - return s.flush(cw) + return s.flush(cw, height) } -func (s *pState) flush(cw *cwriter.Writer) error { - var lines int +func (s *pState) flush(cw *cwriter.Writer, height int) error { + var popCount int + rows := make([]io.Reader, 0, height) pool := make([]*Bar, 0, s.bHeap.Len()) for s.bHeap.Len() > 0 { + var frameRowsUsed int b := heap.Pop(&s.bHeap).(*Bar) frame := <-b.frameCh - lines += frame.lines - _, err := cw.ReadFrom(frame.reader) - if err != nil { - return err + for i := len(frame.rows) - 1; i >= 0; i-- { + if len(rows) == height { + break + } + rows = append(rows, frame.rows[i]) + frameRowsUsed++ } - if frame.shutdown { + if frame.shutdown != 0 { b.Wait() // waiting for b.done, so it's safe to read b.bs - var toDrop bool + drop := b.bs.dropOnComplete if qb, ok := s.queueBars[b]; ok { delete(s.queueBars, b) qb.priority = b.priority pool = append(pool, qb) - toDrop = true + drop = true } else if s.popCompleted && !b.bs.noPop { - lines -= frame.lines - toDrop = true + if frame.shutdown > 1 { + popCount += frameRowsUsed + drop = true + } else { + s.popPriority++ + b.priority = s.popPriority + } } - if toDrop || b.bs.dropOnComplete { + if drop { s.heapUpdated = true continue } @@ -292,7 +297,14 @@ func (s *pState) flush(cw *cwriter.Writer) error { heap.Push(&s.bHeap, b) } - return cw.Flush(lines) + for i := len(rows) - 1; i >= 0; i-- { + _, err := cw.ReadFrom(rows[i]) + if err != nil { + return err + } + } + + return cw.Flush(len(rows) - popCount) } func (s *pState) newTicker(done <-chan struct{}) chan time.Time { @@ -358,7 +370,6 @@ func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOptio reqWidth: s.reqWidth, total: total, filler: filler, - extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { return r, 0 }, debugOut: s.debugOut, } @@ -377,10 +388,6 @@ func (s *pState) makeBarState(total int64, filler BarFiller, options ...BarOptio bs.middleware = nil } - if s.popCompleted && !bs.noPop { - bs.priority = -(math.MaxInt32 - s.idCount) - } - for i := 0; i < len(bs.buffers); i++ { bs.buffers[i] = bytes.NewBuffer(make([]byte, 0, 512)) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 20f781318..db7e9b5bb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -720,7 +720,7 @@ github.com/ulikunitz/xz/lzma github.com/vbatts/tar-split/archive/tar github.com/vbatts/tar-split/tar/asm github.com/vbatts/tar-split/tar/storage -# github.com/vbauerster/mpb/v7 v7.4.2 +# github.com/vbauerster/mpb/v7 v7.5.2 ## explicit github.com/vbauerster/mpb/v7 github.com/vbauerster/mpb/v7/cwriter |
