func copyBlobFile(src io.Reader, dstPath string) error {
	logrus.Debugf("copying blob to %s", dstPath)
	if err := os.MkdirAll(filepath.Dir(dstPath), os.ModePerm); err != nil {
		return err
	}
	dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		return fmt.Errorf("error creating blob file: %v", err)
	}
	defer dst.Close()
	if _, err := io.Copy(dst, src); err != nil {
		return fmt.Errorf("error copying blob %q: %v", filepath.Base(dstPath), err)
	}
	return nil
}
TODO(estroz): symlink blobs instead of copying them to avoid data duplication.
`oc` mirror libs should be able to follow these symlinks.
package publish
import (
	"context"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"
	"github.com/google/uuid"
	"github.com/mholt/archiver/v3"
	"github.com/opencontainers/go-digest"
	operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1"
	"github.com/openshift/library-go/pkg/image/reference"
	"github.com/openshift/library-go/pkg/image/registryclient"
	"github.com/openshift/oc/pkg/cli/admin/release"
	"github.com/openshift/oc/pkg/cli/image/imagesource"
	imagemanifest "github.com/openshift/oc/pkg/cli/image/manifest"
	imgmirror "github.com/openshift/oc/pkg/cli/image/mirror"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	kcmdutil "k8s.io/kubectl/pkg/cmd/util"
	"github.com/RedHatGov/bundle/pkg/archive"
	"github.com/RedHatGov/bundle/pkg/config"
	"github.com/RedHatGov/bundle/pkg/config/v1alpha1"
	"github.com/RedHatGov/bundle/pkg/image"
	"github.com/RedHatGov/bundle/pkg/metadata/storage"
)
const (
	icspSizeLimit = 250000
	icspScope     = "repository"
)
type UuidError struct {
	InUuid   uuid.UUID
	CurrUuid uuid.UUID
}
func (u *UuidError) Error() string {
	return fmt.Sprintf("mismatched uuids, want %v, got %v", u.CurrUuid, u.InUuid)
}
type SequenceError struct {
	wantSeq int
	gotSeq  int
}
func (s *SequenceError) Error() string {
	return fmt.Sprintf("invalid bundle sequence order, want %v, got %v", s.wantSeq, s.gotSeq)
}
type ErrArchiveFileNotFound struct {
	filename string
}
func (e *ErrArchiveFileNotFound) Error() string {
	return fmt.Sprintf("file %s not found in archive", e.filename)
}
func (o *Options) Run(ctx context.Context, cmd *cobra.Command, f kcmdutil.Factory) error {
	logrus.Infof("Publishing image set from archive %q to registry %q", o.ArchivePath, o.ToMirror)
	var currentMeta v1alpha1.Metadata
	var incomingMeta v1alpha1.Metadata
	a := archive.NewArchiver()
	if err := o.ValidatePaths(); err != nil {
		return err
	}
	if o.OutputDir == "" {
		dir, err := o.createResultsDir()
		o.OutputDir = dir
		if err != nil {
			return err
		}
	}
	cleanup, tmpdir, err := mktempDir(o.Dir)
	if err != nil {
		return err
	}
	if !o.SkipCleanup {
		defer cleanup()
	}
	logrus.Debugf("Unarchiving metadata into %s", tmpdir)
	filesInArchive, err := o.readImageSet(a)
	if err != nil {
		return err
	}
	if err := o.unpackImageSet(a, tmpdir); err != nil {
		return err
	}
	backend, err := storage.NewLocalBackend(o.Dir)
	if err != nil {
		return fmt.Errorf("error opening local backend: %v", err)
	}
	workspace, err := storage.NewLocalBackend(tmpdir)
	if err != nil {
		return fmt.Errorf("error opening local backend: %v", err)
	}
	existingMeta := filepath.Join(o.Dir, config.MetadataBasePath)
	if _, err := os.Stat(existingMeta); err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			return err
		}
		logrus.Infof("No existing metadata found. Setting up new workspace")
		if err := os.MkdirAll(filepath.Join(o.Dir, config.PublishDir), os.ModePerm); err != nil {
			return err
		}
		if err := workspace.ReadMetadata(ctx, &incomingMeta, config.MetadataBasePath); err != nil {
			return fmt.Errorf("error reading incoming metadata: %v", err)
		}
		incomingRun := incomingMeta.PastMirrors[len(incomingMeta.PastMirrors)-1]
		if incomingRun.Sequence != 1 {
			return &SequenceError{1, incomingRun.Sequence}
		}
	} else {
		if err := backend.ReadMetadata(ctx, &currentMeta, config.MetadataBasePath); err != nil {
			return fmt.Errorf("error reading current metadata: %v", err)
		}
		if err := workspace.ReadMetadata(ctx, &incomingMeta, config.MetadataBasePath); err != nil {
			return fmt.Errorf("error reading incoming metadata: %v", err)
		}
		logrus.Debug("Checking metadata UID")
		if incomingMeta.MetadataSpec.Uid != currentMeta.MetadataSpec.Uid {
			return &UuidError{currentMeta.MetadataSpec.Uid, incomingMeta.MetadataSpec.Uid}
		}
		logrus.Debug("Check metadata sequence number")
		currRun := currentMeta.PastMirrors[len(currentMeta.PastMirrors)-1]
		incomingRun := incomingMeta.PastMirrors[len(incomingMeta.PastMirrors)-1]
		if incomingRun.Sequence != (currRun.Sequence + 1) {
			return &SequenceError{currRun.Sequence + 1, incomingRun.Sequence}
		}
	}
	logrus.Debugf("Unpacking any provided Helm charts to %s", o.OutputDir)
	if err := unpack(config.HelmDir, o.OutputDir, filesInArchive); err != nil {
		return err
	}
	assocs, err := readAssociations(filepath.Join(tmpdir, config.AssociationsBasePath))
	if err != nil {
		return err
	}
	toMirrorRef, err := imagesource.ParseReference(o.ToMirror)
	if err != nil {
		return fmt.Errorf("error parsing mirror registry %q: %v", o.ToMirror, err)
	}
	logrus.Debugf("mirror reference: %#v", toMirrorRef)
	if toMirrorRef.Type != imagesource.DestinationRegistry {
		return fmt.Errorf("destination %q must be a registry reference", o.ToMirror)
	}
	var (
		errs     []error
		allICSPs []operatorv1alpha1.ImageContentSourcePolicy
	)
	namedICSPMappings := map[string]map[reference.DockerImageReference]reference.DockerImageReference{}
	for _, imageName := range assocs.Keys() {
		newImg, err := reference.Parse(imageName)
		if err != nil {
			return err
		}
		newImg.Registry = o.ToMirror
		genericMappings := []imgmirror.Mapping{}
		releaseMapping := imgmirror.Mapping{}
		imageRef, err := reference.Parse(imageName)
		if err != nil {
			errs = append(errs, fmt.Errorf("error parsing image name %q for ICSP generation: %v", imageName, err))
			continue
		}
		dstRef := imageRef
		dstRef.Registry = toMirrorRef.Ref.Registry
		namedICSPMappings[imageRef.Name] = map[reference.DockerImageReference]reference.DockerImageReference{
			imageRef: dstRef,
		}
		values, _ := assocs.Search(imageName)
		cleanUnpackDir, unpackDir, err := mktempDir(tmpdir)
		if err != nil {
			return err
		}
		for _, assoc := range values {
			missingLayers := map[string][]string{}
			manifestPath := filepath.Join("v2", assoc.Path, "manifests")
			logrus.Debugf("reading assoc: %s", assoc.Name)
			if len(assoc.ManifestDigests) != 0 {
				for _, manifestDigest := range assoc.ManifestDigests {
					if hasManifest := assocs.SetContainsKey(imageName, manifestDigest); !hasManifest {
						errs = append(errs, fmt.Errorf("image %q: expected associations to have manifest %s but was not found", imageName, manifestDigest))
						continue
					}
					manifestArchivePath := filepath.Join(manifestPath, manifestDigest)
					switch _, err := os.Stat(manifestArchivePath); {
					case err == nil:
						logrus.Debugf("Manifest found %s found in %s", manifestDigest, assoc.Path)
					case errors.Is(err, os.ErrNotExist):
						if err := unpack(manifestArchivePath, unpackDir, filesInArchive); err != nil {
							errs = append(errs, err)
						}
					default:
						errs = append(errs, fmt.Errorf("accessing image %q manifest %q: %v", imageName, manifestDigest, err))
					}
				}
			}
			if err := unpack(filepath.Join(manifestPath, assoc.ID), unpackDir, filesInArchive); err != nil {
				errs = append(errs, err)
				continue
			}
			for _, layerDigest := range assoc.LayerDigests {
				logrus.Debugf("Found layer %v for image %s", layerDigest, imageName)
				blobPath := filepath.Join("blobs", layerDigest)
				imagePath := filepath.Join(unpackDir, "v2", assoc.Path)
				imageBlobPath := filepath.Join(imagePath, blobPath)
				aerr := &ErrArchiveFileNotFound{}
				switch err := unpack(blobPath, imagePath, filesInArchive); {
				case err == nil:
					logrus.Debugf("Blob %s found in %s", layerDigest, assoc.Path)
				case errors.Is(err, os.ErrNotExist) || errors.As(err, &aerr):
					missingLayers[layerDigest] = append(missingLayers[layerDigest], imageBlobPath)
				default:
					errs = append(errs, fmt.Errorf("accessing image %q blob %q at %s: %v", imageName, layerDigest, blobPath, err))
				}
			}
			m := imgmirror.Mapping{Name: assoc.Name}
			if m.Source, err = imagesource.ParseReference("file://" + assoc.Path); err != nil {
				errs = append(errs, fmt.Errorf("error parsing source ref %q: %v", assoc.Path, err))
				continue
			}
			if assoc.TagSymlink == "" {
				m.Source.Ref.ID = assoc.ID
			} else {
				if err := unpack(filepath.Join(manifestPath, assoc.TagSymlink), unpackDir, filesInArchive); err != nil {
					errs = append(errs, err)
					continue
				}
				m.Source.Ref.Tag = assoc.TagSymlink
			}
			m.Destination = toMirrorRef
			m.Destination.Ref.Namespace = m.Source.Ref.Namespace
			m.Destination.Ref.Name = m.Source.Ref.Name
			m.Destination.Ref.Tag = m.Source.Ref.Tag
			m.Destination.Ref.ID = m.Source.Ref.ID
			if len(missingLayers) != 0 {
				if err := o.fetchBlobs(ctx, incomingMeta, m, missingLayers, newImg); err != nil {
					return err
				}
			}
			switch assoc.Type {
			case image.TypeGeneric:
				genericMappings = append(genericMappings, m)
			case image.TypeOCPRelease:
				m.Destination.Ref.Tag = ""
				m.Destination.Ref.ID = ""
				if strings.Contains(assoc.Name, "ocp-release") {
					releaseMapping = m
				}
			case image.TypeOperatorCatalog:
				mapping := map[imagesource.TypedImageReference]imagesource.TypedImageReference{m.Source: m.Destination}
				if err := o.writeCatalogSource(o.OutputDir, mapping); err != nil {
					errs = append(errs, fmt.Errorf("image %q: error writing catalog source: %v", imageName, err))
					continue
				}
				genericMappings = append(genericMappings, m)
			case image.TypeOperatorBundle, image.TypeOperatorRelatedImage:
				genericMappings = append(genericMappings, m)
			case image.TypeInvalid:
				errs = append(errs, fmt.Errorf("image %q: image type is not set", imageName))
			default:
				errs = append(errs, fmt.Errorf("image %q: invalid image type %v", imageName, assoc.Type))
			}
		}
		if len(genericMappings) != 0 {
			if err := o.mirrorImage(genericMappings, unpackDir); err != nil {
				errs = append(errs, err)
			}
		}
		if releaseMapping.Source.String() != "" {
			if err := o.mirrorRelease(releaseMapping, cmd, f, unpackDir); err != nil {
				errs = append(errs, err)
			}
		}
		if !o.SkipCleanup {
			cleanUnpackDir()
		}
	}
	if len(errs) != 0 {
		return utilerrors.NewAggregate(errs)
	}
	logrus.Debug("rebuilding catalog images")
	ctlgRefs, err := o.rebuildCatalogs(ctx, tmpdir, filesInArchive)
	if err != nil {
		return fmt.Errorf("error rebuilding catalog images from file-based catalogs: %v", err)
	}
	for _, ref := range ctlgRefs {
		namedICSPMappings[ref.Ref.Name] = map[reference.DockerImageReference]reference.DockerImageReference{ref.Ref: ref.Ref}
		if err := writeCatalogSource(ref, ref, o.OutputDir); err != nil {
			return fmt.Errorf("error writing CatalogSource for catalog image %q: %v", ref.Ref.Exact(), err)
		}
	}
	for imageName, icspMapping := range namedICSPMappings {
		icsps, err := GenerateICSPs(imageName, icspSizeLimit, icspScope, icspMapping)
		if err != nil {
			return fmt.Errorf("error generating ICSP for image name %q: %v", imageName, err)
		}
		allICSPs = append(allICSPs, icsps...)
	}
	if err := WriteICSPs(o.OutputDir, allICSPs); err != nil {
		return fmt.Errorf("error writing ICSPs: %v", err)
	}
	logrus.Info("CatalogSource and ICSP install not implemented")
	if err := backend.WriteMetadata(ctx, &incomingMeta, config.MetadataBasePath); err != nil {
		return err
	}
	return nil
}
func readAssociations(assocPath string) (assocs image.AssociationSet, err error) {
	f, err := os.Open(assocPath)
	if err != nil {
		return assocs, fmt.Errorf("error opening image associations file: %v", err)
	}
	defer f.Close()
	return assocs, assocs.Decode(f)
}
func (o *Options) unpackImageSet(a archive.Archiver, dest string) error {
	exclude := []string{"blobs", "v2", config.HelmDir}
	file, err := os.Stat(o.ArchivePath)
	if err != nil {
		return err
	}
	if file.IsDir() {
		err = filepath.Walk(o.ArchivePath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return fmt.Errorf("traversing %s: %v", path, err)
			}
			if info == nil {
				return fmt.Errorf("no file info")
			}
			extension := filepath.Ext(path)
			extension = strings.TrimPrefix(extension, ".")
			if extension == a.String() {
				logrus.Debugf("Extracting archive %s", path)
				if err := archive.Unarchive(a, path, dest, exclude); err != nil {
					return err
				}
			}
			return nil
		})
	} else {
		logrus.Infof("Extracting archive %s", o.ArchivePath)
		if err := archive.Unarchive(a, o.ArchivePath, dest, exclude); err != nil {
			return err
		}
	}
	return err
}
func (o *Options) readImageSet(a archive.Archiver) (map[string]string, error) {
	filesinArchive := make(map[string]string)
	file, err := os.Stat(o.ArchivePath)
	if err != nil {
		return nil, err
	}
	if file.IsDir() {
		logrus.Infoln("Detected multiple archive files")
		err = filepath.Walk(o.ArchivePath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return fmt.Errorf("traversing %s: %v", path, err)
			}
			if info == nil {
				return fmt.Errorf("no file info")
			}
			extension := filepath.Ext(path)
			extension = strings.TrimPrefix(extension, ".")
			if extension == a.String() {
				logrus.Debugf("Found archive %s", path)
				return a.Walk(path, func(f archiver.File) error {
					filesinArchive[f.Name()] = path
					return nil
				})
			}
			return nil
		})
	} else {
		err = a.Walk(o.ArchivePath, func(f archiver.File) error {
			filesinArchive[f.Name()] = o.ArchivePath
			return nil
		})
	}
	return filesinArchive, err
}