github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/docker/docker.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  	"crypto/sha256"
    15  	"encoding/hex"
    16  	"fmt"
    17  	"strings"
    18  
    19  	"github.com/schollz/progressbar/v3"
    20  	"github.com/vchain-us/vcn/pkg/bom/artifact"
    21  	"github.com/vchain-us/vcn/pkg/bom/executor"
    22  )
    23  
    24  const AssetType = "docker"
    25  
    26  const (
    27  	APK  = "apk"
    28  	DPKG = "dpkg"
    29  	RPM  = "rpm"
    30  )
    31  
    32  type DockerArtifact struct {
    33  	artifact.GenericArtifact
    34  	image    string
    35  	binaries []string
    36  	ex       executor.Executor
    37  	pkg      pkgManager
    38  	pkgType  string
    39  }
    40  
    41  // New returns new DockerArtifact object
    42  // unlike other environments, this New() is not called from bom.New()
    43  func New(path string, binaries []string) (*DockerArtifact, error) {
    44  	executor, err := executor.NewDockerExecutor(path)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	pkg, err := probePackageManager(executor)
    50  	if err != nil {
    51  		return nil, fmt.Errorf("error identifying package manager for the image: %w", err)
    52  	}
    53  	if pkg == nil {
    54  		return nil, fmt.Errorf("cannot identify package manager for the container image")
    55  	}
    56  
    57  	ret := DockerArtifact{
    58  		image:    path,
    59  		binaries: binaries,
    60  		ex:       executor,
    61  		pkg:      pkg,
    62  		pkgType:  pkg.Type(),
    63  	}
    64  	return &ret, nil
    65  }
    66  
    67  func (p DockerArtifact) Type() string {
    68  	return p.pkgType
    69  }
    70  
    71  func (p DockerArtifact) Path() string {
    72  	return p.image
    73  }
    74  
    75  func (a *DockerArtifact) ResolveDependencies(output artifact.OutputOptions) ([]artifact.Dependency, error) {
    76  	if a.Deps != nil {
    77  		return a.Deps, nil
    78  	}
    79  
    80  	defer a.ex.Close()
    81  
    82  	var err error
    83  	result := make([]artifact.Dependency, 0)
    84  	if len(a.binaries) > 0 {
    85  		// for every binary find its dynamic libs, and find packages for these libs
    86  		var bar *progressbar.ProgressBar
    87  		if output == artifact.Progress {
    88  			// init progress indicator
    89  			bar = progressbar.Default(10)
    90  		}
    91  
    92  		packages := make(map[string]struct{})
    93  		binCount := 0.
    94  		fileCount := 0.
    95  		for _, bin := range a.binaries {
    96  			stdOut, stdErr, exitCode, err := a.ex.Exec([]string{"ldd", bin})
    97  			if err != nil {
    98  				return nil, fmt.Errorf("error starting 'ldd' command: %w", err)
    99  			}
   100  
   101  			if exitCode != 0 {
   102  				if stdErr == nil {
   103  					stdErr = stdOut
   104  				}
   105  				return nil, fmt.Errorf("error executing 'ldd' command: %s", stdErr)
   106  			}
   107  
   108  			if bar != nil {
   109  				scanner := bufio.NewScanner(bytes.NewBuffer(stdOut))
   110  				for scanner.Scan() {
   111  					fileCount++
   112  				}
   113  				binCount++
   114  				projected := int(fileCount * float64(len(a.binaries)) / binCount)
   115  				bar.ChangeMax(projected)
   116  			}
   117  
   118  			scanner := bufio.NewScanner(bytes.NewBuffer(stdOut))
   119  			for scanner.Scan() {
   120  				line := scanner.Text()
   121  				fields := strings.Split(line, " ")
   122  				var lib string
   123  				switch len(fields) {
   124  				case 2:
   125  					lib = strings.TrimSpace(fields[0])
   126  				case 4:
   127  					lib = strings.TrimSpace(fields[2])
   128  				default:
   129  					fmt.Printf("Ignoring malformed 'ldd' output: %s", line)
   130  					continue
   131  				}
   132  
   133  				if bar != nil {
   134  					bar.Add(1)
   135  				}
   136  				dep, err := a.pkg.PackageByFile(a.ex, lib, output)
   137  				if err == ErrNotFound {
   138  					// it is possible that lib is a symlink, in this case package manager can't find it, so resolve it and retry
   139  					stdOut, stdErr, exitCode, err = a.ex.Exec([]string{"readlink", "-f", lib})
   140  					if err != nil {
   141  						return nil, fmt.Errorf("error starting 'readlink' command: %w", err)
   142  					}
   143  					if exitCode != 0 {
   144  						if stdErr == nil {
   145  							stdErr = stdOut
   146  						}
   147  						return nil, fmt.Errorf("error executing 'readlink' command: %s", stdErr)
   148  					}
   149  					lib = strings.TrimSpace(string(stdOut))
   150  					dep, err = a.pkg.PackageByFile(a.ex, lib, output)
   151  					if err != nil {
   152  						// it is ok when library is not a part of the package - ignore
   153  						continue
   154  					}
   155  				} else if err != nil {
   156  					// it is ok when library is not a part of the package - ignore
   157  					continue
   158  				}
   159  				if _, ok := packages[dep.Name]; ok {
   160  					continue // package already processed
   161  				}
   162  				packages[dep.Name] = struct{}{}
   163  				result = append(result, dep)
   164  			}
   165  		}
   166  	} else {
   167  		result, err = a.pkg.AllPackages(a.ex, output)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  	}
   172  
   173  	a.Deps = result
   174  	return result, nil
   175  }
   176  
   177  func (a *DockerArtifact) FileHash(name string) (string, error) {
   178  	buf, err := a.ex.ReadFile(name)
   179  	if err != nil {
   180  		return "", nil
   181  	}
   182  
   183  	checksum := sha256.Sum256(buf)
   184  	return hex.EncodeToString(checksum[:]), nil
   185  }