func (o *copyFileOp) String() string {
	return fmt.Sprintf(
		"COPY %q to %q",
		o.Source.AbsolutePath, o.Dest.AbsolutePath)
}
String is the pretty-printer for an operation, as used by dry-run.
package filepromoter
import (
	"context"
	"fmt"
	"io"
	"os"
	"github.com/sirupsen/logrus"
	api "sigs.k8s.io/promo-tools/v3/api/files"
	"sigs.k8s.io/release-utils/hash"
)
type SyncFileInfo struct {
	RelativePath string
	AbsolutePath string
	MD5 string
	Size int64
	filestore syncFilestore
}
type copyFileOp struct {
	Source *SyncFileInfo
	Dest   *SyncFileInfo
	ManifestFile *api.File
}
func (o *copyFileOp) Run(ctx context.Context) error {
	f, err := os.CreateTemp("", "promoter")
	if err != nil {
		return fmt.Errorf("error creating temp file: %v", err)
	}
	tempFilename := f.Name()
	defer func() {
		if f != nil {
			if err := f.Close(); err != nil {
				logrus.Warnf(
					"error closing temp file %q: %v",
					tempFilename, err)
			}
		}
		if err := os.Remove(tempFilename); err != nil {
			logrus.Warnf(
				"unable to remove temp file %q: %v",
				tempFilename, err)
		}
	}()
	in, err := o.Source.filestore.OpenReader(ctx, o.Source.RelativePath)
	if err != nil {
		return fmt.Errorf("error reading %q: %v", o.Source.AbsolutePath, err)
	}
	defer in.Close()
	if _, err := io.Copy(f, in); err != nil {
		return fmt.Errorf(
			"error downloading %s: %v",
			o.Source.AbsolutePath, err)
	}
	if err := f.Close(); err != nil {
		return fmt.Errorf("error writing temp file %q: %v", tempFilename, err)
	}
	f = nil
	sha256, err := hash.SHA256ForFile(tempFilename)
	if err != nil {
		return err
	}
	if sha256 != o.ManifestFile.SHA256 {
		return fmt.Errorf(
			"sha256 did not match for file %q: actual=%q expected=%q",
			o.Source.AbsolutePath, sha256, o.ManifestFile.SHA256)
	}
	return o.Dest.filestore.UploadFile(ctx, o.Dest.RelativePath, tempFilename)
}