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 }