github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/docker/apk.go (about) 1 /* 2 * Copyright (c) 2021 CodeNotary, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package docker 10 11 import ( 12 "bufio" 13 "bytes" 14 "encoding/base64" 15 "encoding/hex" 16 "fmt" 17 "strings" 18 19 "github.com/vchain-us/vcn/pkg/bom/artifact" 20 "github.com/vchain-us/vcn/pkg/bom/executor" 21 ) 22 23 // apk implements packageManager interface 24 type apk struct { 25 cache []artifact.Dependency 26 index map[string]*artifact.Dependency 27 } 28 29 const ( 30 checksumTag = 'C' 31 packageTag = 'P' 32 versionTag = 'V' 33 licenseTag = 'L' 34 ) 35 36 func (pkg apk) Type() string { 37 return APK 38 } 39 40 // PackageByFile finds a package by the file it includes 41 func (pkg *apk) PackageByFile(e executor.Executor, file string, output artifact.OutputOptions) (artifact.Dependency, error) { 42 var err error 43 if pkg.cache == nil { 44 err := pkg.buildCache(e) 45 if err != nil { 46 return artifact.Dependency{}, err 47 } 48 } 49 50 stdOut, _, exitCode, err := e.Exec([]string{"apk", "info", "--who-owns", file}) 51 if err != nil { 52 return artifact.Dependency{}, fmt.Errorf("error starting 'apk' command: %w", err) 53 } 54 if exitCode != 0 { 55 return artifact.Dependency{}, ErrNotFound 56 } 57 58 lines := bytes.SplitN(stdOut, []byte{'\n'}, 2) 59 // expect format "<file> is owned by <package>' 60 fields := strings.Split(string(lines[0]), " is owned by ") 61 if len(fields) != 2 { 62 return artifact.Dependency{}, fmt.Errorf("got unexpected result from 'apk' command: %s", stdOut) 63 } 64 65 // package has a structure '<name>-<version>-<release>', strip '-<version>-<release>', name may contain '-' too 66 pos := strings.LastIndexByte(fields[1], '-') 67 if pos < 0 { 68 return artifact.Dependency{}, fmt.Errorf("malformed package name %s", fields[1]) 69 } 70 pos = strings.LastIndexByte(fields[1][:pos], '-') 71 if pos < 0 { 72 return artifact.Dependency{}, fmt.Errorf("malformed package name %s", fields[1]) 73 } 74 75 p, ok := pkg.index[fields[1][:pos]] 76 if !ok { 77 return artifact.Dependency{}, fmt.Errorf("cannot find package %s in cache", fields[0]) 78 } 79 80 return *p, nil 81 } 82 83 // AllPackages finds all installed packages 84 func (pkg *apk) AllPackages(e executor.Executor, output artifact.OutputOptions) ([]artifact.Dependency, error) { 85 if pkg.cache == nil { 86 err := pkg.buildCache(e) 87 if err != nil { 88 return nil, err 89 } 90 } 91 92 return pkg.cache, nil 93 } 94 95 // build package cache and index - see https://wiki.alpinelinux.org/wiki/Apk_spec 96 func (pkg *apk) buildCache(e executor.Executor) error { 97 buf, err := e.ReadFile("/lib/apk/db/installed") 98 if err != nil { 99 return fmt.Errorf("error reading file from container: %w", err) 100 } 101 102 pkg.cache = make([]artifact.Dependency, 0) 103 scanner := bufio.NewScanner(bytes.NewBuffer(buf)) 104 curPkg := artifact.Dependency{} 105 for scanner.Scan() { 106 line := scanner.Text() 107 if line == "" { 108 // end of package section 109 pkg.cache = append(pkg.cache, curPkg) 110 curPkg = artifact.Dependency{} 111 continue 112 } 113 tag := line[0] 114 switch tag { 115 case packageTag: 116 curPkg.Name = line[2:] 117 case versionTag: 118 curPkg.Version = line[2:] 119 case checksumTag: 120 hash, err := base64.StdEncoding.DecodeString(line[4:]) // first two bytes contain Q1 to indicate SHA1 121 if err != nil { 122 return fmt.Errorf("malformed package checksum") 123 } 124 curPkg.Hash = hex.EncodeToString(hash) 125 curPkg.HashType = artifact.HashSHA1 126 case licenseTag: 127 curPkg.License = line[2:] 128 } 129 } 130 // last line in 'installed' file is empty, therefore last created curPkg just discarded 131 132 pkg.index = make(map[string]*artifact.Dependency, len(pkg.cache)) 133 for i := range pkg.cache { 134 pkg.index[pkg.cache[i].Name] = &pkg.cache[i] 135 } 136 137 return nil 138 }