m/cli/metroctl: use one progress bar for all files
Previously, there was a separate progress bar for each uploaded file,
now there is just one which shows the total number of bytes transferred.
Change-Id: Id4cba63889077a076cb63d437e3fe4b17cfc3786
Reviewed-on: https://review.monogon.dev/c/monogon/+/4049
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/metroctl/cmd_install_ssh.go b/metropolis/cli/metroctl/cmd_install_ssh.go
index 410a88d..b52c353 100644
--- a/metropolis/cli/metroctl/cmd_install_ssh.go
+++ b/metropolis/cli/metroctl/cmd_install_ssh.go
@@ -24,9 +24,72 @@
"google.golang.org/protobuf/proto"
"source.monogon.dev/osbase/net/sshtakeover"
- "source.monogon.dev/osbase/structfs"
)
+// progressbarUpdater wraps a [progressbar.ProgressBar] with an improved
+// interface for updating progress. It updates the progress bar in a separate
+// goroutine and at most 60 times per second. The stop function stops the
+// updates and can be safely called multiple times.
+type progressbarUpdater struct {
+ bar *progressbar.ProgressBar
+ update chan int64
+ close chan struct{}
+}
+
+func startProgressbarUpdater(bar *progressbar.ProgressBar) *progressbarUpdater {
+ updater := &progressbarUpdater{
+ bar: bar,
+ update: make(chan int64, 1),
+ close: make(chan struct{}),
+ }
+ go updater.run()
+ return updater
+}
+
+func (p *progressbarUpdater) add(num int64) {
+ for {
+ select {
+ case p.update <- num:
+ return
+ case oldNum := <-p.update:
+ num += oldNum
+ }
+ }
+}
+
+func (p *progressbarUpdater) run() {
+ for {
+ select {
+ case num := <-p.update:
+ p.bar.Add64(num)
+ case <-p.close:
+ return
+ }
+ select {
+ case <-time.After(time.Second / 60):
+ case <-p.close:
+ return
+ }
+ }
+}
+
+func (p *progressbarUpdater) stop() {
+ if p.close == nil {
+ return
+ }
+ p.close <- struct{}{}
+ p.close = nil
+ select {
+ case num := <-p.update:
+ // Do one last update to make the bar reach 100%.
+ p.bar.Add64(num)
+ default:
+ }
+ if !p.bar.IsFinished() {
+ p.bar.Exit()
+ }
+}
+
var sshCmd = &cobra.Command{
Use: "ssh --disk=<disk> <target>",
Short: "Installs Metropolis on a Linux system accessible via SSH.",
@@ -136,30 +199,33 @@
return err
}
- barUploader := func(blob structfs.Blob, targetPath string) {
- content, err := blob.Open()
- if err != nil {
- log.Fatalf("error while uploading %q: %v", targetPath, err)
- }
- defer content.Close()
+ log.Println("Uploading files to target host.")
+ totalSize := takeover.Size() + bundle.Size()
+ barUpdater := startProgressbarUpdater(progressbar.DefaultBytes(totalSize))
+ defer barUpdater.stop()
+ conn.SetProgress(barUpdater.add)
- bar := progressbar.DefaultBytes(
- blob.Size(),
- targetPath,
- )
- defer bar.Close()
-
- proxyReader := progressbar.NewReader(content, bar)
- defer proxyReader.Close()
-
- if err := conn.UploadExecutable(ctx, targetPath, &proxyReader); err != nil {
- log.Fatalf("error while uploading %q: %v", targetPath, err)
- }
+ takeoverContent, err := takeover.Open()
+ if err != nil {
+ return err
+ }
+ err = conn.UploadExecutable(ctx, takeoverTargetPath, takeoverContent)
+ takeoverContent.Close()
+ if err != nil {
+ return fmt.Errorf("error while uploading %q: %w", takeoverTargetPath, err)
}
- log.Println("Uploading required binaries to target host.")
- barUploader(takeover, takeoverTargetPath)
- barUploader(bundle, bundleTargetPath)
+ bundleContent, err := bundle.Open()
+ if err != nil {
+ return err
+ }
+ err = conn.Upload(ctx, bundleTargetPath, bundleContent)
+ bundleContent.Close()
+ if err != nil {
+ return fmt.Errorf("error while uploading %q: %w", bundleTargetPath, err)
+ }
+
+ barUpdater.stop()
// Start the agent and wait for the agent's output to arrive.
log.Printf("Starting the takeover executable at path %q.", takeoverTargetPath)