github.com/Datadog/cnab-go@v0.3.3-beta1.0.20191007143216-bba4b7e723d0/packager/export.go (about)

     1  package packager
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/deislabs/cnab-go/bundle"
    12  	"github.com/deislabs/cnab-go/bundle/loader"
    13  	"github.com/deislabs/cnab-go/imagestore"
    14  	"github.com/docker/docker/pkg/archive"
    15  )
    16  
    17  type Exporter struct {
    18  	source                string
    19  	destination           string
    20  	imageStoreConstructor imagestore.Constructor
    21  	imageStore            imagestore.Store
    22  	logs                  string
    23  	loader                loader.BundleLoader
    24  }
    25  
    26  // NewExporter returns an *Exporter given information about where a bundle
    27  //  lives, where the compressed bundle should be exported to. It also
    28  //  sets up a docker client to work with images.
    29  func NewExporter(source, dest, logsDir string, l loader.BundleLoader, c imagestore.Constructor) (*Exporter, error) {
    30  	logs := filepath.Join(logsDir, "export-"+time.Now().Format("20060102150405"))
    31  
    32  	return &Exporter{
    33  		source:                source,
    34  		destination:           dest,
    35  		imageStoreConstructor: c,
    36  		logs:                  logs,
    37  		loader:                l,
    38  	}, nil
    39  }
    40  
    41  // Export prepares an artifacts directory containing all of the necessary
    42  //  images, packages the bundle along with the artifacts in a gzipped tar
    43  //  file, and saves that file to the file path specified as destination.
    44  //  If the any part of the destination path doesn't, it will be created.
    45  //  exist
    46  func (ex *Exporter) Export() error {
    47  	//prepare log file for this export
    48  	logsf, err := os.Create(ex.logs)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	defer logsf.Close()
    53  
    54  	fi, err := os.Stat(ex.source)
    55  	if os.IsNotExist(err) {
    56  		return err
    57  	}
    58  	if fi.IsDir() {
    59  		return fmt.Errorf("Bundle manifest %s is a directory, should be a file", ex.source)
    60  	}
    61  
    62  	bun, err := ex.loader.Load(ex.source)
    63  	if err != nil {
    64  		return fmt.Errorf("Error loading bundle: %s", err)
    65  	}
    66  	name := bun.Name + "-" + bun.Version
    67  	archiveDir, err := ioutil.TempDir("", name)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if err := os.MkdirAll(archiveDir, 0755); err != nil {
    72  		return err
    73  	}
    74  	defer os.RemoveAll(archiveDir)
    75  
    76  	from, err := os.Open(ex.source)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	defer from.Close()
    81  
    82  	bundlefile := "bundle.json"
    83  	to, err := os.OpenFile(filepath.Join(archiveDir, bundlefile), os.O_RDWR|os.O_CREATE, 0666)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	defer to.Close()
    88  
    89  	_, err = io.Copy(to, from)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	ex.imageStore, err = ex.imageStoreConstructor(imagestore.WithArchiveDir(archiveDir), imagestore.WithLogs(logsf))
    95  	if err != nil {
    96  		return fmt.Errorf("Error creating artifacts: %s", err)
    97  	}
    98  
    99  	if err := ex.prepareArtifacts(bun); err != nil {
   100  		return fmt.Errorf("Error preparing artifacts: %s", err)
   101  	}
   102  
   103  	dest := name + ".tgz"
   104  	if ex.destination != "" {
   105  		dest = ex.destination
   106  	}
   107  
   108  	writer, err := os.Create(dest)
   109  	if err != nil {
   110  		return fmt.Errorf("Error creating archive file: %s", err)
   111  	}
   112  
   113  	defer writer.Close()
   114  
   115  	tarOptions := &archive.TarOptions{
   116  		Compression:      archive.Gzip,
   117  		IncludeFiles:     []string{"."},
   118  		IncludeSourceDir: true,
   119  	}
   120  	rc, err := archive.TarWithOptions(archiveDir, tarOptions)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	defer rc.Close()
   125  
   126  	_, err = io.Copy(writer, rc)
   127  	return err
   128  }
   129  
   130  // prepareArtifacts pulls all images, verifies their digests and
   131  // saves them to a directory called artifacts/ in the bundle directory
   132  func (ex *Exporter) prepareArtifacts(bun *bundle.Bundle) error {
   133  	for _, image := range bun.Images {
   134  		if err := ex.addImage(image.BaseImage); err != nil {
   135  			return err
   136  		}
   137  	}
   138  
   139  	for _, in := range bun.InvocationImages {
   140  		if err := ex.addImage(in.BaseImage); err != nil {
   141  			return err
   142  		}
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  // addImage pulls an image, adds it to the artifacts/ directory, and verifies its digest
   149  func (ex *Exporter) addImage(image bundle.BaseImage) error {
   150  	dig, err := ex.imageStore.Add(image.Image)
   151  	if err != nil {
   152  		return err
   153  	}
   154  	return checkDigest(image, dig)
   155  }
   156  
   157  // checkDigest compares the content digest of the given image to the given content digest and returns an error if they
   158  // are both non-empty and do not match
   159  func checkDigest(image bundle.BaseImage, dig string) error {
   160  	digestFromManifest := image.Digest
   161  	if dig == "" || digestFromManifest == "" {
   162  		return nil
   163  	}
   164  	if digestFromManifest != dig {
   165  		return fmt.Errorf("content digest mismatch: image %s has digest %s but the digest should be %s according to the bundle manifest", image.Image, dig, digestFromManifest)
   166  	}
   167  	return nil
   168  }
   169  
   170  func (ex *Exporter) Logs() string {
   171  	return ex.logs
   172  }