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  }