github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/node/node.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 node
    10  
    11  import (
    12  	"encoding/json"
    13  	"fmt"
    14  	"os"
    15  	"os/exec"
    16  	"path"
    17  	"path/filepath"
    18  
    19  	"github.com/opencontainers/go-digest"
    20  	"github.com/vchain-us/vcn/pkg/bom/artifact"
    21  	"github.com/vchain-us/vcn/pkg/bundle"
    22  	"github.com/vchain-us/vcn/pkg/extractor/dir"
    23  )
    24  
    25  const packageLockJsonFN = "package-lock.json"
    26  const packageJsonFN = "package.json"
    27  
    28  const AssetType = "node"
    29  
    30  type NodePackage struct {
    31  	artifact.GenericArtifact
    32  	pj string
    33  }
    34  
    35  func New(path string) artifact.Artifact {
    36  	pj, err := GetPackageJsonPath(path)
    37  	if err != nil {
    38  		return nil
    39  	}
    40  
    41  	return &NodePackage{pj: pj}
    42  }
    43  
    44  func (p *NodePackage) ResolveDependencies(output artifact.OutputOptions) ([]artifact.Dependency, error) {
    45  	if p.Deps != nil {
    46  		return p.Deps, nil
    47  	}
    48  
    49  	if p.pj == "" {
    50  		return nil, fmt.Errorf("package.json not found")
    51  	}
    52  
    53  	plj, err := getPackageLock(path.Dir(p.pj))
    54  	if err != nil {
    55  		return nil, fmt.Errorf("package-lock.json not found. Please run npm install")
    56  	}
    57  
    58  	comps := make([]artifact.Dependency, 0)
    59  	fname, err := exec.LookPath("npm")
    60  	if err != nil {
    61  		return nil, fmt.Errorf("please install npm tool (7 version) follwing this link: https://docs.npmjs.com/getting-started. Error reported: %w", err)
    62  	}
    63  
    64  	ws := path.Dir(plj)
    65  	command := exec.Command(fname, "ls", "-a", "-l", "-p", "--json")
    66  	command.Dir = ws
    67  	o, err := command.Output()
    68  	if err != nil {
    69  		switch e := err.(type) {
    70  		case *exec.ExitError:
    71  			if output == artifact.Debug {
    72  				fmt.Printf("vcn encountered some problems while generating node BoM list. Result could be incomplete.\n%s\n", e.Stderr)
    73  			}
    74  		default:
    75  			return nil, fmt.Errorf("%s: %w", ErrNpmParsingMsg, err)
    76  		}
    77  	}
    78  
    79  	if o == nil {
    80  		return nil, nil
    81  	}
    82  
    83  	var plJson map[string]interface{}
    84  
    85  	err = json.Unmarshal(o, &plJson)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	dep, err := extractDependencies(plJson["dependencies"].(map[string]interface{}), output)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	comps = append(comps, dep...)
    96  
    97  	p.Deps = comps
    98  	return comps, nil
    99  }
   100  
   101  func extractDependencies(deps map[string]interface{}, output artifact.OutputOptions) ([]artifact.Dependency, error) {
   102  	comps := make([]artifact.Dependency, 0)
   103  
   104  	for _, dep := range deps {
   105  		comp := artifact.Dependency{}
   106  		d, ok := dep.(map[string]interface{})
   107  		if !ok {
   108  			warning(fmt.Sprintf("WARNING: impossible to calculate digest of %v dependency\n", dep), output)
   109  			continue
   110  		}
   111  		if len(d) == 0 {
   112  			continue
   113  		}
   114  		name, ok := d["name"].(string)
   115  		if !ok {
   116  			warning(fmt.Sprintf("WARNING: impossible to calculate digest of %v dependency\n", d), output)
   117  			continue
   118  		}
   119  		version, ok := d["version"].(string)
   120  		if !ok {
   121  			warning(fmt.Sprintf("WARNING: impossible to calculate digest of %s dependency\n", name), output)
   122  			continue
   123  		}
   124  		license, ok := d["license"].(string)
   125  		if !ok {
   126  			license = ""
   127  		}
   128  		nodeComPath, ok := d["path"].(string)
   129  		if !ok {
   130  			warning(fmt.Sprintf("WARNING: impossible to calculate digest of %s-%s dependency\n", name, version), output)
   131  			continue
   132  		}
   133  		hash, err := GetNodeComDigest(nodeComPath)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  		comp.Hash = hash.Encoded()
   138  		comp.Version = version
   139  		comp.Name = name
   140  		comp.HashType = artifact.HashSHA256
   141  		comp.License = license
   142  		comps = append(comps, comp)
   143  		if output == artifact.Debug {
   144  			fmt.Printf("%s@%s (%s)\n", comp.Name, comp.Version, comp.Hash)
   145  		}
   146  	}
   147  	return comps, nil
   148  }
   149  
   150  func getPackageLock(p string) (string, error) {
   151  	fp := path.Join(p, packageLockJsonFN)
   152  	_, err := os.Stat(fp)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  	return fp, nil
   157  }
   158  
   159  func (p *NodePackage) Type() string {
   160  	return AssetType
   161  }
   162  
   163  func (p *NodePackage) Path() string {
   164  	return p.pj
   165  }
   166  
   167  func (p *NodePackage) Close() {}
   168  
   169  func GetPackageJsonPath(p string) (string, error) {
   170  	fp := path.Join(p, packageJsonFN)
   171  	_, err := os.Stat(fp)
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  	return fp, nil
   176  }
   177  
   178  func GetNodeComDigest(componentFolder string) (digest.Digest, error) {
   179  	path, err := filepath.Abs(componentFolder)
   180  	if err != nil {
   181  		return "", err
   182  	}
   183  
   184  	d, err := os.Open(path)
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  	defer d.Close()
   189  
   190  	stat, err := d.Stat()
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  	if !stat.IsDir() {
   195  		return "", fmt.Errorf("read node component %s: is not a directory", path)
   196  	}
   197  
   198  	files, err := dir.Walk(path)
   199  	if err != nil {
   200  		return "", err
   201  	}
   202  
   203  	manifest := bundle.NewManifest(files...)
   204  	return manifest.Digest()
   205  }
   206  
   207  func warning(msg string, output artifact.OutputOptions) {
   208  	if output != artifact.Silent {
   209  		fmt.Print(msg)
   210  	}
   211  }