github.com/YousefHaggyHeroku/pack@v1.5.5/internal/builder/lifecycle.go (about) 1 package builder 2 3 import ( 4 "archive/tar" 5 "fmt" 6 "io" 7 "path" 8 "regexp" 9 10 "github.com/pkg/errors" 11 12 "github.com/YousefHaggyHeroku/pack/internal/archive" 13 ) 14 15 // A snapshot of the latest tested lifecycle version values 16 const ( 17 DefaultLifecycleVersion = "0.9.3" 18 DefaultBuildpackAPIVersion = "0.2" 19 ) 20 21 // Blob is an interface to wrap opening blobs 22 type Blob interface { 23 Open() (io.ReadCloser, error) 24 } 25 26 // Lifecycle is an implementation of the CNB Lifecycle spec 27 //go:generate mockgen -package testmocks -destination testmocks/mock_lifecycle.go github.com/YousefHaggyHeroku/pack/internal/builder Lifecycle 28 type Lifecycle interface { 29 Blob 30 Descriptor() LifecycleDescriptor 31 } 32 33 type lifecycle struct { 34 descriptor LifecycleDescriptor 35 Blob 36 } 37 38 // NewLifecycle creates a Lifecycle from a Blob 39 func NewLifecycle(blob Blob) (Lifecycle, error) { 40 var err error 41 42 br, err := blob.Open() 43 if err != nil { 44 return nil, errors.Wrap(err, "open lifecycle blob") 45 } 46 defer br.Close() 47 48 _, buf, err := archive.ReadTarEntry(br, "lifecycle.toml") 49 if err != nil && errors.Cause(err) == archive.ErrEntryNotExist { 50 return nil, err 51 } else if err != nil { 52 return nil, errors.Wrap(err, "reading lifecycle descriptor") 53 } 54 55 lifecycleDescriptor, err := ParseDescriptor(string(buf)) 56 if err != nil { 57 return nil, err 58 } 59 60 lifecycle := &lifecycle{Blob: blob, descriptor: CompatDescriptor(lifecycleDescriptor)} 61 62 if err = lifecycle.validateBinaries(); err != nil { 63 return nil, errors.Wrap(err, "validating binaries") 64 } 65 66 return lifecycle, nil 67 } 68 69 // Descriptor returns the LifecycleDescriptor 70 func (l *lifecycle) Descriptor() LifecycleDescriptor { 71 return l.descriptor 72 } 73 74 func (l *lifecycle) validateBinaries() error { 75 rc, err := l.Open() 76 if err != nil { 77 return errors.Wrap(err, "create lifecycle blob reader") 78 } 79 defer rc.Close() 80 regex := regexp.MustCompile(`^[^/]+/([^/]+)$`) 81 headers := map[string]bool{} 82 tr := tar.NewReader(rc) 83 for { 84 header, err := tr.Next() 85 if err == io.EOF { 86 break 87 } 88 if err != nil { 89 return errors.Wrap(err, "failed to get next tar entry") 90 } 91 92 pathMatches := regex.FindStringSubmatch(path.Clean(header.Name)) 93 if pathMatches != nil { 94 headers[pathMatches[1]] = true 95 } 96 } 97 for _, p := range l.binaries() { 98 _, found := headers[p] 99 if !found { 100 _, found = headers[p+".exe"] 101 if !found { 102 return fmt.Errorf("did not find '%s' in tar", p) 103 } 104 } 105 } 106 return nil 107 } 108 109 // Binaries returns a list of all binaries contained in the lifecycle. 110 func (l *lifecycle) binaries() []string { 111 binaries := []string{ 112 "detector", 113 "restorer", 114 "analyzer", 115 "builder", 116 "exporter", 117 "launcher", 118 "creator", 119 } 120 return binaries 121 }