github.com/jdolitsky/cnab-go@v0.7.1-beta1/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 }