diff --git a/core/gallery/backends.go b/core/gallery/backends.go index bdfeb027..a2df466d 100644 --- a/core/gallery/backends.go +++ b/core/gallery/backends.go @@ -44,7 +44,7 @@ func InstallBackend(basePath string, config *GalleryBackend, downloadStatus func return fmt.Errorf("failed to create backend path %q: %v", backendPath, err) } - if err := oci.ExtractOCIImage(img, backendPath); err != nil { + if err := oci.ExtractOCIImage(img, backendPath, downloadStatus); err != nil { return fmt.Errorf("failed to extract image %q: %v", config.URI, err) } diff --git a/pkg/downloader/uri.go b/pkg/downloader/uri.go index 54b8eb10..94c2e13a 100644 --- a/pkg/downloader/uri.go +++ b/pkg/downloader/uri.go @@ -256,7 +256,7 @@ func (uri URI) DownloadFile(filePath, sha string, fileN, total int, downloadStat return fmt.Errorf("failed to get image %q: %v", url, err) } - return oci.ExtractOCIImage(img, filepath.Dir(filePath)) + return oci.ExtractOCIImage(img, filepath.Dir(filePath), downloadStatus) } // Check if the file already exists diff --git a/pkg/oci/image.go b/pkg/oci/image.go index 40e91122..3efbe189 100644 --- a/pkg/oci/image.go +++ b/pkg/oci/image.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "runtime" + "strconv" "strings" "syscall" "time" @@ -59,11 +60,64 @@ var defaultRetryPredicate = func(err error) bool { return false } -// ExtractOCIImage will extract a given targetImage into a given targetDestination -func ExtractOCIImage(img v1.Image, targetDestination string) error { - reader := mutate.Extract(img) +type progressWriter struct { + written int64 + total int64 + fileName string + downloadStatus func(string, string, string, float64) +} - _, err := archive.Apply(context.Background(), targetDestination, reader, archive.WithNoSameOwner()) +func formatBytes(bytes int64) string { + const unit = 1024 + if bytes < unit { + return strconv.FormatInt(bytes, 10) + " B" + } + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} + +func (pw *progressWriter) Write(p []byte) (int, error) { + n := len(p) + pw.written += int64(n) + if pw.total > 0 { + percentage := float64(pw.written) / float64(pw.total) * 100 + //log.Debug().Msgf("Downloading %s: %s/%s (%.2f%%)", pw.fileName, formatBytes(pw.written), formatBytes(pw.total), percentage) + pw.downloadStatus(pw.fileName, formatBytes(pw.written), formatBytes(pw.total), percentage) + } else { + pw.downloadStatus(pw.fileName, formatBytes(pw.written), "", 0) + } + + return n, nil +} + +// ExtractOCIImage will extract a given targetImage into a given targetDestination +func ExtractOCIImage(img v1.Image, targetDestination string, downloadStatus func(string, string, string, float64)) error { + var reader io.Reader + reader = mutate.Extract(img) + + if downloadStatus != nil { + var totalSize int64 + layers, err := img.Layers() + if err != nil { + return err + } + for _, layer := range layers { + size, err := layer.Size() + if err != nil { + return err + } + totalSize += size + } + reader = io.TeeReader(reader, &progressWriter{total: totalSize, downloadStatus: downloadStatus}) + } + + _, err := archive.Apply(context.Background(), + targetDestination, reader, + archive.WithNoSameOwner()) return err } diff --git a/pkg/oci/image_test.go b/pkg/oci/image_test.go index c5b09334..3fc31c20 100644 --- a/pkg/oci/image_test.go +++ b/pkg/oci/image_test.go @@ -30,7 +30,7 @@ var _ = Describe("OCI", func() { Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(dir) - err = ExtractOCIImage(img, dir) + err = ExtractOCIImage(img, dir, nil) Expect(err).NotTo(HaveOccurred()) }) })