From a1c309541f521a447f039ea29802f64fb3d8dfe5 Mon Sep 17 00:00:00 2001 From: Nieves Montero Date: Mon, 9 Jan 2023 14:28:56 +0100 Subject: [PATCH] Report the size of the image that will be downloaded from a registry This uses 'skopeo inspect' to get the size of the image on the registry, which is usually less than the size of the image in a local containers/storage image store after download (eg., 'podman images'), because they are kept compressed on the registry. Skopeo >= 1.10.0 is needed to retrieve the sizes [1]. However, this doesn't add a hard dependency on Skopeo to accommodate size-constrained operating systems like Fedora CoreOS. If skopeo(1) is missing or too old, then the size of the image won't be shown, but everything else would continue to work as before. Some changes by Debarshi Ray. [1] Skopeo commit d9dfc44888ff71a6 https://github.com/containers/skopeo/commit/d9dfc44888ff71a6 https://github.com/containers/skopeo/issues/641 https://github.com/containers/toolbox/issues/752 Signed-off-by: Nieves Montero --- meson.build | 4 -- playbooks/dependencies-centos-9-stream.yaml | 2 +- playbooks/dependencies-fedora-restricted.yaml | 8 +++ playbooks/dependencies-fedora.yaml | 2 +- src/cmd/create.go | 36 ++++++++++++- src/meson.build | 1 + src/pkg/skopeo/skopeo.go | 50 +++++++++++++++++++ 7 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/pkg/skopeo/skopeo.go diff --git a/meson.build b/meson.build index 653a3d3..21d3599 100644 --- a/meson.build +++ b/meson.build @@ -84,10 +84,6 @@ if shellcheck.found() test('shellcheck toolbox (deprecated)', shellcheck, args: [toolbox_sh]) endif -if not skopeo.found() - message('Running system tests requires Skopeo for OCI image manipulation.') -endif - install_subdir( 'test', install_dir: get_option('datadir') / meson.project_name(), diff --git a/playbooks/dependencies-centos-9-stream.yaml b/playbooks/dependencies-centos-9-stream.yaml index e0e46e9..4c19db3 100644 --- a/playbooks/dependencies-centos-9-stream.yaml +++ b/playbooks/dependencies-centos-9-stream.yaml @@ -52,7 +52,7 @@ chdir: '{{ zuul.project.src_dir }}' - name: Check versions of crucial packages - command: rpm -qa ShellCheck codespell *kernel* gcc *glibc* golang shadow-utils-subid-devel podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper + command: rpm -qa ShellCheck codespell *kernel* gcc *glibc* golang shadow-utils-subid-devel podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper skopeo - name: Show podman versions command: podman version diff --git a/playbooks/dependencies-fedora-restricted.yaml b/playbooks/dependencies-fedora-restricted.yaml index f8f0cf0..b347d7a 100644 --- a/playbooks/dependencies-fedora-restricted.yaml +++ b/playbooks/dependencies-fedora-restricted.yaml @@ -54,6 +54,14 @@ state: absent update_cache: "{{ true if zuul.attempts > 1 else false }}" +- name: Ensure that skopeo(1) is absent + become: yes + package: + name: + - skopeo + state: absent + update_cache: "{{ true if zuul.attempts > 1 else false }}" + - name: Download Go modules shell: | go mod download -x diff --git a/playbooks/dependencies-fedora.yaml b/playbooks/dependencies-fedora.yaml index f5a28d9..abd4ccc 100644 --- a/playbooks/dependencies-fedora.yaml +++ b/playbooks/dependencies-fedora.yaml @@ -52,7 +52,7 @@ chdir: '{{ zuul.project.src_dir }}' - name: Check versions of crucial packages - command: rpm -qa ShellCheck codespell *kernel* gcc *glibc* shadow-utils-subid-devel golang podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper + command: rpm -qa ShellCheck codespell *kernel* gcc *glibc* shadow-utils-subid-devel golang podman conmon containernetworking-plugins containers-common container-selinux crun fuse-overlayfs flatpak-session-helper skopeo - name: Show podman versions command: podman version diff --git a/src/cmd/create.go b/src/cmd/create.go index b0e666c..c50460a 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -27,7 +27,9 @@ import ( "github.com/briandowns/spinner" "github.com/containers/toolbox/pkg/podman" "github.com/containers/toolbox/pkg/shell" + "github.com/containers/toolbox/pkg/skopeo" "github.com/containers/toolbox/pkg/utils" + "github.com/docker/go-units" "github.com/godbus/dbus/v5" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -573,6 +575,30 @@ func getFullyQualifiedImageFromRepoTags(image string) (string, error) { return imageFull, nil } +func getImageSizeFromRegistry(imageFull string) (string, error) { + image, err := skopeo.Inspect(imageFull) + if err != nil { + return "", err + } + + if image.LayersData == nil { + return "", errors.New("'skopeo inspect' did not have LayersData") + } + + var imageSizeFloat float64 + + for _, layer := range image.LayersData { + if layerSize, err := layer.Size.Float64(); err != nil { + return "", err + } else { + imageSizeFloat += layerSize + } + } + + imageSizeHuman := units.HumanSize(imageSizeFloat) + return imageSizeHuman, nil +} + func getServiceSocket(serviceName string, unitName string) (string, error) { logrus.Debugf("Resolving path to the %s socket", serviceName) @@ -687,7 +713,15 @@ func pullImage(image, release, authFile string) (bool, error) { if promptForDownload { fmt.Println("Image required to create toolbox container.") - prompt := fmt.Sprintf("Download %s (500MB)? [y/N]:", imageFull) + var prompt string + + if imageSize, err := getImageSizeFromRegistry(imageFull); err != nil { + logrus.Debugf("Getting image size failed: %s", err) + prompt = fmt.Sprintf("Download %s? [y/N]:", imageFull) + } else { + prompt = fmt.Sprintf("Download %s (%s)? [y/N]:", imageFull, imageSize) + } + shouldPullImage = askForConfirmation(prompt) } diff --git a/src/meson.build b/src/meson.build index f4e85b3..3e00e4c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,7 @@ sources = files( 'cmd/utils.go', 'pkg/podman/podman.go', 'pkg/shell/shell.go', + 'pkg/skopeo/skopeo.go', 'pkg/utils/libsubid-wrappers.c', 'pkg/utils/errors.go', 'pkg/utils/utils.go', diff --git a/src/pkg/skopeo/skopeo.go b/src/pkg/skopeo/skopeo.go new file mode 100644 index 0000000..e49f715 --- /dev/null +++ b/src/pkg/skopeo/skopeo.go @@ -0,0 +1,50 @@ +/* + * Copyright © 2023 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package skopeo + +import ( + "bytes" + "encoding/json" + + "github.com/containers/toolbox/pkg/shell" +) + +type Layer struct { + Size json.Number +} +type Image struct { + LayersData []Layer +} + +func Inspect(target string) (*Image, error) { + var stdout bytes.Buffer + + targetWithTransport := "docker://" + target + args := []string{"inspect", "--format", "json", targetWithTransport} + + if err := shell.Run("skopeo", nil, &stdout, nil, args...); err != nil { + return nil, err + } + + output := stdout.Bytes() + var image Image + if err := json.Unmarshal(output, &image); err != nil { + return nil, err + } + + return &image, nil +}