Merge pull request #20538 from baude/ocipull

Consume OCI images for machine image
This commit is contained in:
openshift-ci[bot]
2023-11-02 20:37:35 +00:00
committed by GitHub
11 changed files with 741 additions and 13 deletions

View File

@ -38,6 +38,7 @@ func stop(cmd *cobra.Command, args []string) error {
err error
vm machine.VM
)
vmName := defaultMachineName
if len(args) > 0 && len(args[0]) > 0 {
vmName = args[0]

View File

@ -585,6 +585,7 @@ func (dl Download) AcquireVMImage(imagePath string) (*VMFile, FCOSStream, error)
imageLocation *VMFile
fcosStream FCOSStream
)
switch imagePath {
// TODO these need to be re-typed as FCOSStreams
case Testing.String(), Next.String(), Stable.String(), "":

154
pkg/machine/default.go Normal file
View File

@ -0,0 +1,154 @@
package machine
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v4/pkg/machine/ocipull"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
type Versioned struct {
blob *types.BlobInfo
blobDirPath string
cacheDir string
ctx context.Context
imageFormat ImageFormat
imageName string
machineImageDir string
machineVersion *OSVersion
vmName string
}
func newVersioned(ctx context.Context, machineImageDir, vmName string) (*Versioned, error) {
imageCacheDir := filepath.Join(machineImageDir, "cache")
if err := os.MkdirAll(imageCacheDir, 0777); err != nil {
return nil, err
}
o := getVersion()
return &Versioned{ctx: ctx, cacheDir: imageCacheDir, machineImageDir: machineImageDir, machineVersion: o, vmName: vmName}, nil
}
func (d *Versioned) LocalBlob() *types.BlobInfo {
return d.blob
}
func (d *Versioned) DiskEndpoint() string {
return d.machineVersion.diskImage(d.imageFormat)
}
func (d *Versioned) versionedOCICacheDir() string {
return filepath.Join(d.cacheDir, d.machineVersion.majorMinor())
}
func (d *Versioned) identifyImageNameFromOCIDir() (string, error) {
imageManifest, err := ocipull.ReadImageManifestFromOCIPath(d.ctx, d.versionedOCICacheDir())
if err != nil {
return "", err
}
if len(imageManifest.Layers) > 1 {
return "", fmt.Errorf("podman machine images can have only one layer: %d found", len(imageManifest.Layers))
}
path := filepath.Join(d.versionedOCICacheDir(), "blobs", "sha256", imageManifest.Layers[0].Digest.Hex())
return findTarComponent(path)
}
func (d *Versioned) pull(path string) error {
fmt.Printf("Pulling %s\n", d.DiskEndpoint())
logrus.Debugf("pulling %s to %s", d.DiskEndpoint(), path)
return ocipull.Pull(d.ctx, d.DiskEndpoint(), path, ocipull.PullOptions{})
}
func (d *Versioned) Pull() error {
var (
err error
isUpdatable bool
localBlob *types.BlobInfo
remoteDescriptor *v1.Descriptor
)
remoteDiskImage := d.machineVersion.diskImage(Qcow)
logrus.Debugf("podman disk image name: %s", remoteDiskImage)
// is there a valid oci dir in our cache
hasCache := d.localOCIDirExists()
if hasCache {
logrus.Debug("checking remote registry")
remoteDescriptor, err = ocipull.GetRemoteDescriptor(d.ctx, remoteDiskImage)
if err != nil {
return err
}
logrus.Debugf("working with local cache: %s", d.versionedOCICacheDir())
localBlob, err = ocipull.GetLocalBlob(d.ctx, d.versionedOCICacheDir())
if err != nil {
return err
}
// determine if the local is same as remote
if remoteDescriptor.Digest.Hex() != localBlob.Digest.Hex() {
logrus.Debugf("new image is available: %s", remoteDescriptor.Digest.Hex())
isUpdatable = true
}
}
if !hasCache || isUpdatable {
if hasCache {
if err := GuardedRemoveAll(d.versionedOCICacheDir()); err != nil {
return err
}
}
if err := d.pull(d.versionedOCICacheDir()); err != nil {
return err
}
}
imageName, err := d.identifyImageNameFromOCIDir()
if err != nil {
return err
}
logrus.Debugf("image name: %s", imageName)
d.imageName = imageName
if localBlob == nil {
localBlob, err = ocipull.GetLocalBlob(d.ctx, d.versionedOCICacheDir())
if err != nil {
return err
}
}
d.blob = localBlob
d.blobDirPath = d.versionedOCICacheDir()
logrus.Debugf("local oci disk image blob: %s", d.localOCIDiskImageDir(localBlob))
return nil
}
func (d *Versioned) Unpack() (*VMFile, error) {
tbPath := localOCIDiskImageDir(d.blobDirPath, d.blob)
unpackedFile, err := unpackOCIDir(tbPath, d.machineImageDir)
if err != nil {
return nil, err
}
d.imageName = unpackedFile.GetPath()
return unpackedFile, nil
}
func (d *Versioned) Decompress(compressedFile *VMFile) (*VMFile, error) {
imageCompression := compressionFromFile(d.imageName)
strippedImageName := strings.TrimSuffix(d.imageName, fmt.Sprintf(".%s", imageCompression.String()))
finalName := finalFQImagePathName(d.vmName, strippedImageName)
if err := Decompress(compressedFile, finalName); err != nil {
return nil, err
}
return NewMachineFile(finalName, nil)
}
func (d *Versioned) localOCIDiskImageDir(localBlob *types.BlobInfo) string {
return filepath.Join(d.versionedOCICacheDir(), "blobs", "sha256", localBlob.Digest.Hex())
}
func (d *Versioned) localOCIDirExists() bool {
_, indexErr := os.Stat(filepath.Join(d.versionedOCICacheDir(), "index.json"))
return indexErr == nil
}

View File

@ -77,7 +77,11 @@ var _ = BeforeSuite(func() {
Fail(fmt.Sprintf("unable to download machine image: %q", err))
}
GinkgoWriter.Println("Download took: ", time.Since(now).String())
if err := machine.Decompress(fqImageName+compressionExtension, fqImageName); err != nil {
diskImage, err := machine.NewMachineFile(fqImageName+compressionExtension, nil)
if err != nil {
Fail(fmt.Sprintf("unable to create vmfile %q: %v", fqImageName+compressionExtension, err))
}
if err := machine.Decompress(diskImage, fqImageName); err != nil {
Fail(fmt.Sprintf("unable to decompress image file: %q", err))
}
} else {

View File

@ -78,6 +78,18 @@ func (imf ImageFormat) String() string {
return "qcow2.xz"
}
func (imf ImageFormat) string() string {
switch imf {
case Vhdx:
return "vhdx"
case Tar:
return "tar"
case Raw:
return "raw"
}
return "qcow2"
}
func (c ImageCompression) String() string {
switch c {
case Gz:

136
pkg/machine/oci.go Normal file
View File

@ -0,0 +1,136 @@
package machine
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/image/v5/types"
"github.com/containers/image/v5/pkg/compression"
"github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus"
"github.com/blang/semver/v4"
"github.com/containers/podman/v4/version"
)
// quay.io/libpod/podman-machine-images:4.6
const (
diskImages = "podman-machine-images"
registry = "quay.io"
repo = "libpod"
)
type OSVersion struct {
*semver.Version
}
type Disker interface {
Pull() error
Decompress(compressedFile *VMFile) (*VMFile, error)
DiskEndpoint() string
Unpack() (*VMFile, error)
}
type OCIOpts struct {
Scheme *OCIKind
Dir *string
}
type OCIKind string
var (
OCIDir OCIKind = "oci-dir"
OCIRegistry OCIKind = "docker"
OCIUnknown OCIKind = "unknown"
)
func (o OCIKind) String() string {
switch o {
case OCIDir:
return string(OCIDir)
case OCIRegistry:
return string(OCIRegistry)
}
return string(OCIUnknown)
}
func (o OCIKind) IsOCIDir() bool {
return o == OCIDir
}
func StripOCIReference(input string) string {
return strings.TrimPrefix(input, "docker://")
}
func getVersion() *OSVersion {
v := version.Version
// OVERRIDES FOR DEV ONLY
v.Minor = 6
v.Pre = nil
// OVERRIDES FOR DEV ONLY
return &OSVersion{&v}
}
func (o *OSVersion) majorMinor() string {
return fmt.Sprintf("%d.%d", o.Major, o.Minor)
}
func (o *OSVersion) diskImage(diskFlavor ImageFormat) string {
return fmt.Sprintf("%s/%s/%s:%s-%s", registry, repo, diskImages, o.majorMinor(), diskFlavor.string())
}
func unpackOCIDir(ociTb, machineImageDir string) (*VMFile, error) {
imageFileName, err := findTarComponent(ociTb)
if err != nil {
return nil, err
}
unpackedFileName := filepath.Join(machineImageDir, imageFileName)
f, err := os.Open(ociTb)
if err != nil {
return nil, err
}
defer func() {
if err := f.Close(); err != nil {
logrus.Error(err)
}
}()
uncompressedReader, _, err := compression.AutoDecompress(f)
if err != nil {
return nil, err
}
defer func() {
if err := uncompressedReader.Close(); err != nil {
logrus.Error(err)
}
}()
logrus.Debugf("untarring %q to %q", ociTb, machineImageDir)
if err := archive.Untar(uncompressedReader, machineImageDir, &archive.TarOptions{
NoLchown: true,
}); err != nil {
return nil, err
}
return NewMachineFile(unpackedFileName, nil)
}
func localOCIDiskImageDir(blobDirPath string, localBlob *types.BlobInfo) string {
return filepath.Join(blobDirPath, "blobs", "sha256", localBlob.Digest.Hex())
}
func finalFQImagePathName(vmName, imageName string) string {
// imageName here is fully qualified. we need to break
// it apart and add the vmname
baseDir, filename := filepath.Split(imageName)
return filepath.Join(baseDir, fmt.Sprintf("%s-%s", vmName, filename))
}

116
pkg/machine/ocidir.go Normal file
View File

@ -0,0 +1,116 @@
package machine
import (
"archive/tar"
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/containers/image/v5/pkg/compression"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v4/pkg/machine/ocipull"
"github.com/sirupsen/logrus"
)
type LocalBlobDir struct {
blob *types.BlobInfo
blobDirPath string
ctx context.Context
imageName string
machineImageDir string
vmName string
}
func NewOCIDir(ctx context.Context, inputDir, machineImageDir, vmName string) *LocalBlobDir {
strippedInputDir := strings.TrimPrefix(inputDir, fmt.Sprintf("%s:/", OCIDir.String()))
l := LocalBlobDir{
blob: nil,
blobDirPath: strippedInputDir,
ctx: ctx,
imageName: "",
machineImageDir: machineImageDir,
vmName: vmName,
}
return &l
}
func (l *LocalBlobDir) Pull() error {
localBlob, err := ocipull.GetLocalBlob(l.ctx, l.DiskEndpoint())
if err != nil {
return err
}
l.blob = localBlob
return nil
}
func (l *LocalBlobDir) Decompress(compressedFile *VMFile) (*VMFile, error) {
finalName := finalFQImagePathName(l.vmName, l.imageName)
if err := Decompress(compressedFile, finalName); err != nil {
return nil, err
}
return NewMachineFile(finalName, nil)
}
func (l *LocalBlobDir) Unpack() (*VMFile, error) {
tbPath := localOCIDiskImageDir(l.blobDirPath, l.blob)
unPackedFile, err := unpackOCIDir(tbPath, l.machineImageDir)
if err != nil {
return nil, err
}
l.imageName = unPackedFile.GetPath()
return unPackedFile, err
}
func (l *LocalBlobDir) DiskEndpoint() string {
return l.blobDirPath
}
func (l *LocalBlobDir) LocalBlob() *types.BlobInfo {
return l.blob
}
// findTarComponent returns a header and a reader matching componentPath within inputFile,
// or (nil, nil, nil) if not found.
func findTarComponent(pathToTar string) (string, error) {
f, err := os.Open(pathToTar)
if err != nil {
return "", err
}
defer func() {
if err := f.Close(); err != nil {
logrus.Error(err)
}
}()
uncompressedReader, _, err := compression.AutoDecompress(f)
if err != nil {
return "", err
}
defer func() {
if err := uncompressedReader.Close(); err != nil {
logrus.Error(err)
}
}()
var (
filename string
headerCount uint
)
t := tar.NewReader(uncompressedReader)
for {
h, err := t.Next()
if err == io.EOF {
break
}
if err != nil {
return "", err
}
filename = h.Name
headerCount++
}
if headerCount != 1 {
return "", errors.New("invalid oci machine image")
}
return filename, nil
}

110
pkg/machine/ocipull/pull.go Normal file
View File

@ -0,0 +1,110 @@
package ocipull
import (
"context"
"fmt"
"os"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/pkg/shortnames"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
specV1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// PullOptions includes data to alter certain knobs when pulling a source
// image.
type PullOptions struct {
// Require HTTPS and verify certificates when accessing the registry.
TLSVerify bool
// [username[:password] to use when connecting to the registry.
Credentials string
// Quiet the progress bars when pushing.
Quiet bool
}
// Pull `imageInput` from a container registry to `sourcePath`.
func Pull(ctx context.Context, imageInput string, sourcePath string, options PullOptions) error {
if _, err := os.Stat(sourcePath); err == nil {
return fmt.Errorf("%q already exists", sourcePath)
}
srcRef, err := stringToImageReference(imageInput)
if err != nil {
return err
}
destRef, err := layout.ParseReference(sourcePath)
if err != nil {
return err
}
sysCtx := &types.SystemContext{
DockerInsecureSkipTLSVerify: types.NewOptionalBool(!options.TLSVerify),
}
if options.Credentials != "" {
authConf, err := parse.AuthConfig(options.Credentials)
if err != nil {
return err
}
sysCtx.DockerAuthConfig = authConf
}
if err := validateSourceImageReference(ctx, srcRef, sysCtx); err != nil {
return err
}
policy, err := signature.DefaultPolicy(sysCtx)
if err != nil {
return fmt.Errorf("obtaining default signature policy: %w", err)
}
policyContext, err := signature.NewPolicyContext(policy)
if err != nil {
return fmt.Errorf("creating new signature policy context: %w", err)
}
copyOpts := copy.Options{
SourceCtx: sysCtx,
}
if !options.Quiet {
copyOpts.ReportWriter = os.Stderr
}
if _, err := copy.Image(ctx, policyContext, destRef, srcRef, &copyOpts); err != nil {
return fmt.Errorf("pulling source image: %w", err)
}
return nil
}
func stringToImageReference(imageInput string) (types.ImageReference, error) {
if shortnames.IsShortName(imageInput) {
return nil, fmt.Errorf("pulling source images by short name (%q) is not supported, please use a fully-qualified name", imageInput)
}
ref, err := alltransports.ParseImageName("docker://" + imageInput)
if err != nil {
return nil, fmt.Errorf("parsing image name: %w", err)
}
return ref, nil
}
func validateSourceImageReference(ctx context.Context, ref types.ImageReference, sysCtx *types.SystemContext) error {
src, err := ref.NewImageSource(ctx, sysCtx)
if err != nil {
return fmt.Errorf("creating image source from reference: %w", err)
}
defer src.Close()
ociManifest, _, _, err := readManifestFromImageSource(ctx, src)
if err != nil {
return err
}
if ociManifest.Config.MediaType != specV1.MediaTypeImageConfig {
return fmt.Errorf("invalid media type of image config %q (expected: %q)", ociManifest.Config.MediaType, specV1.MediaTypeImageConfig)
}
return nil
}

View File

@ -0,0 +1,120 @@
package ocipull
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
specV1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// readManifestFromImageSource reads the manifest from the specified image
// source. Note that the manifest is expected to be an OCI v1 manifest.
func readManifestFromImageSource(ctx context.Context, src types.ImageSource) (*specV1.Manifest, *digest.Digest, int64, error) {
rawData, mimeType, err := src.GetManifest(ctx, nil)
if err != nil {
return nil, nil, -1, err
}
if mimeType != specV1.MediaTypeImageManifest {
return nil, nil, -1, fmt.Errorf("image %q is of type %q (expected: %q)", strings.TrimPrefix(src.Reference().StringWithinTransport(), "//"), mimeType, specV1.MediaTypeImageManifest)
}
manifest := specV1.Manifest{}
if err := json.Unmarshal(rawData, &manifest); err != nil {
return nil, nil, -1, fmt.Errorf("reading manifest: %w", err)
}
manifestDigest := digest.FromBytes(rawData)
return &manifest, &manifestDigest, int64(len(rawData)), nil
}
// readManifestFromOCIPath returns the manifest of the specified source image
// at `sourcePath` along with its digest. The digest can later on be used to
// locate the manifest on the file system.
func readManifestFromOCIPath(ctx context.Context, sourcePath string) (*specV1.Manifest, *digest.Digest, int64, error) {
ociRef, err := layout.ParseReference(sourcePath)
if err != nil {
return nil, nil, -1, err
}
ociSource, err := ociRef.NewImageSource(ctx, &types.SystemContext{})
if err != nil {
return nil, nil, -1, err
}
defer ociSource.Close()
return readManifestFromImageSource(ctx, ociSource)
}
func GetLocalBlob(ctx context.Context, path string) (*types.BlobInfo, error) {
ociRef, err := layout.ParseReference(path)
if err != nil {
return nil, err
}
img, err := ociRef.NewImage(ctx, &types.SystemContext{})
if err != nil {
return nil, err
}
b, _, err := img.Manifest(ctx)
if err != nil {
return nil, err
}
localManifest := specV1.Manifest{}
if err := json.Unmarshal(b, &localManifest); err != nil {
return nil, err
}
blobs := img.LayerInfos()
if err != nil {
return nil, err
}
if len(blobs) != 1 {
return nil, errors.New("invalid disk image")
}
fmt.Println(blobs[0].Digest.Hex())
return &blobs[0], nil
}
func GetRemoteManifest(ctx context.Context, dest string) (*specV1.Manifest, error) {
ref, err := docker.ParseReference(fmt.Sprintf("//%s", dest))
if err != nil {
return nil, err
}
imgSrc, err := ref.NewImage(ctx, &types.SystemContext{})
if err != nil {
return nil, err
}
b, _, err := imgSrc.Manifest(ctx)
if err != nil {
return nil, err
}
remoteManifest := specV1.Manifest{}
err = json.Unmarshal(b, &remoteManifest)
return &remoteManifest, err
}
func GetRemoteDescriptor(ctx context.Context, dest string) (*specV1.Descriptor, error) {
remoteManifest, err := GetRemoteManifest(ctx, dest)
if err != nil {
return nil, err
}
if len(remoteManifest.Layers) != 1 {
return nil, errors.New("invalid remote disk image")
}
return &remoteManifest.Layers[0], nil
}
func ReadImageManifestFromOCIPath(ctx context.Context, ociImagePath string) (*specV1.Manifest, error) {
imageManifest, _, _, err := readManifestFromOCIPath(ctx, ociImagePath)
return imageManifest, err
}

View File

@ -6,6 +6,7 @@ package machine
import (
"archive/zip"
"bufio"
"context"
"errors"
"fmt"
"io"
@ -133,7 +134,11 @@ func DownloadImage(d DistributionDownload) error {
}
}()
}
return Decompress(d.Get().LocalPath, d.Get().LocalUncompressedFile)
localPath, err := NewMachineFile(d.Get().LocalPath, nil)
if err != nil {
return err
}
return Decompress(localPath, d.Get().LocalUncompressedFile)
}
func progressBar(prefix string, size int64, onComplete string) (*mpb.Progress, *mpb.Bar) {
@ -205,17 +210,17 @@ func DownloadVMImage(downloadURL *url2.URL, imageName string, localImagePath str
return nil
}
func Decompress(localPath, uncompressedPath string) error {
func Decompress(localPath *VMFile, uncompressedPath string) error {
var isZip bool
uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return err
}
sourceFile, err := os.ReadFile(localPath)
sourceFile, err := localPath.Read()
if err != nil {
return err
}
if strings.HasSuffix(localPath, ".zip") {
if strings.HasSuffix(localPath.GetPath(), ".zip") {
isZip = true
}
prefix := "Copying uncompressed file"
@ -225,12 +230,12 @@ func Decompress(localPath, uncompressedPath string) error {
}
prefix += ": " + filepath.Base(uncompressedPath)
if compressionType == archive.Xz {
return decompressXZ(prefix, localPath, uncompressedFileWriter)
return decompressXZ(prefix, localPath.GetPath(), uncompressedFileWriter)
}
if isZip && runtime.GOOS == "windows" {
return decompressZip(prefix, localPath, uncompressedFileWriter)
return decompressZip(prefix, localPath.GetPath(), uncompressedFileWriter)
}
return decompressEverythingElse(prefix, localPath, uncompressedFileWriter)
return decompressEverythingElse(prefix, localPath.GetPath(), uncompressedFileWriter)
}
// Will error out if file without .Xz already exists
@ -405,3 +410,75 @@ func (dl Download) AcquireAlternateImage(inputPath string) (*VMFile, error) {
return imagePath, nil
}
func isOci(input string) (bool, *OCIKind, error) {
inputURL, err := url2.Parse(input)
if err != nil {
return false, nil, err
}
switch inputURL.Scheme {
case OCIDir.String():
return true, &OCIDir, nil
case OCIRegistry.String():
return true, &OCIRegistry, nil
}
return false, nil, nil
}
func Pull(input, machineName string, vp VirtProvider) (*VMFile, FCOSStream, error) {
var (
disk Disker
)
ociBased, ociScheme, err := isOci(input)
if err != nil {
return nil, 0, err
}
if !ociBased {
// Business as usual
dl, err := vp.NewDownload(machineName)
if err != nil {
return nil, 0, err
}
return dl.AcquireVMImage(input)
}
oopts := OCIOpts{
Scheme: ociScheme,
}
dataDir, err := GetDataDir(vp.VMType())
if err != nil {
return nil, 0, err
}
if ociScheme.IsOCIDir() {
strippedOCIDir := StripOCIReference(input)
oopts.Dir = &strippedOCIDir
disk = NewOCIDir(context.Background(), input, dataDir, machineName)
} else {
// a use of a containers image type here might be
// tighter
strippedInput := strings.TrimPrefix(input, "docker://")
// this is the next piece of work
if len(strippedInput) > 0 {
return nil, 0, errors.New("image names are not supported yet")
}
disk, err = newVersioned(context.Background(), dataDir, machineName)
if err != nil {
return nil, 0, err
}
}
if err := disk.Pull(); err != nil {
return nil, 0, err
}
unpacked, err := disk.Unpack()
if err != nil {
return nil, 0, err
}
defer func() {
logrus.Debugf("cleaning up %q", unpacked.GetPath())
if err := unpacked.Delete(); err != nil {
logrus.Errorf("unable to delete local compressed file %q:%v", unpacked.GetPath(), err)
}
}()
imagePath, err := disk.Decompress(unpacked)
return imagePath, UnknownStream, err
}

View File

@ -260,15 +260,12 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
v.IdentityPath = util.GetIdentityPath(v.Name)
v.Rootful = opts.Rootful
dl, err := VirtualizationProvider().NewDownload(v.Name)
imagePath, strm, err := machine.Pull(opts.ImagePath, opts.Name, VirtualizationProvider())
if err != nil {
return false, err
}
imagePath, strm, err := dl.AcquireVMImage(opts.ImagePath)
if err != nil {
return false, err
}
// By this time, image should be had and uncompressed
callbackFuncs.Add(imagePath.Delete)
// Assign values about the download