github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/java/java.go (about) 1 /* 2 * Copyright (c) 2018-2020 vChain, 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 java 10 11 import ( 12 "archive/zip" 13 "bufio" 14 "bytes" 15 "crypto/sha256" 16 "encoding/base64" 17 "encoding/hex" 18 "encoding/xml" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "strings" 27 28 "golang.org/x/net/html/charset" 29 30 "github.com/schollz/progressbar/v3" 31 "github.com/sirupsen/logrus" 32 33 "github.com/vchain-us/vcn/pkg/bom/artifact" 34 ) 35 36 const AssetType = "java" 37 38 const mvn_version = 3 39 const mvn_artid = 0 40 const mvn_pkg_name = 1 41 42 const pom = "pom.xml" 43 const maven = "mvn" 44 45 type task struct { 46 path string 47 name string 48 version string 49 } 50 type result struct { 51 comp artifact.Dependency 52 err error 53 } 54 55 // javaMavenArtifact implements Artifact interface 56 type javaMavenArtifact struct { 57 artifact.GenericArtifact 58 folder string 59 dirName string 60 } 61 62 func (p *javaMavenArtifact) Path() string { 63 return p.dirName 64 } 65 66 // New returns new JavaMavenPackage object 67 func New(path string) artifact.Artifact { 68 f, err := GetPOM(path) 69 if err != nil { 70 return nil 71 } 72 73 return &javaMavenArtifact{folder: f, dirName: filepath.Dir(path)} 74 } 75 76 func (p *javaMavenArtifact) Type() string { 77 return AssetType 78 } 79 80 // Dependencies returns list of java packages used during the build 81 func (a *javaMavenArtifact) ResolveDependencies(output artifact.OutputOptions) ([]artifact.Dependency, error) { 82 if a.Deps != nil { 83 return a.Deps, nil 84 } 85 86 xmlDepFn, err := ioutil.TempFile(os.TempDir(), "xml") 87 if err != nil { 88 return nil, err 89 } 90 defer xmlDepFn.Close() 91 92 fname, err := exec.LookPath("mvn") 93 if err != nil { 94 return nil, fmt.Errorf("please install mvn tool follwing this link: https://maven.apache.org/install.html. Error reported: %w", err) 95 } 96 97 command := exec.Command(fname, "dependency:tree", "-DoutputType=graphml", "-f="+a.folder, "-DappendOutput=true", "-DoutputFile="+xmlDepFn.Name()) 98 _, err = command.Output() 99 if err != nil { 100 exit, ok := err.(*exec.ExitError) 101 if ok { 102 return nil, fmt.Errorf("cannot get Java dependencies:\n%s", string(exit.Stderr)) 103 } 104 return nil, fmt.Errorf("cannot get Java dependencies: %w", err) 105 } 106 107 xmlDep, err := ioutil.ReadFile(xmlDepFn.Name()) 108 if err != nil { 109 return nil, err 110 } 111 graph := GraphML{} 112 err = xml.Unmarshal(xmlDep, &graph) 113 if err != nil { 114 return nil, err 115 } 116 117 cacheDir, err := MavenCacheDir() 118 if err != nil { 119 return nil, err 120 } 121 122 // worker pool 123 tasks := make(chan task, len(graph.Graph.Nodes)) 124 results := make(chan result, len(graph.Graph.Nodes)) 125 for i := 0; i < artifact.MaxGoroutines; i++ { 126 go worker(cacheDir, tasks, results) 127 } 128 129 for _, n := range graph.Graph.Nodes { 130 fields := strings.Split(n.Data.ShapeNode.NodeLabel, ":") 131 if fields[mvn_pkg_name] == "" { 132 logrus.Errorf("unable to retrieve package name of component %s", n.Data.ShapeNode.NodeLabel) 133 continue 134 } 135 if fields[mvn_version] == "" { 136 logrus.Errorf("unable to retrieve package version of component %s", n.Data.ShapeNode.NodeLabel) 137 continue 138 } 139 if fields[mvn_artid] == "" { 140 logrus.Errorf("unable to retrieve package artifact ID of component %s", n.Data.ShapeNode.NodeLabel) 141 continue 142 } 143 144 tasks <- task{ 145 path: strings.Replace(fields[mvn_artid], ".", "/", -1), 146 name: fields[mvn_pkg_name], 147 version: fields[mvn_version], 148 } 149 } 150 151 res := make([]artifact.Dependency, 0) 152 bar := progressbar.Default(int64(len(graph.Graph.Nodes))) 153 for done := 0; done < len(graph.Graph.Nodes); done++ { 154 result := <-results 155 if result.err != nil { 156 // it happens sometimes when dependency specified, but it doesn't exist 157 continue 158 } 159 if result.comp.Hash != "" { 160 res = append(res, result.comp) 161 } 162 switch output { 163 case artifact.Progress: 164 bar.Add(1) 165 case artifact.Debug: 166 fmt.Printf("%s@%s (%s)\n", result.comp.Name, result.comp.Version, result.comp.Hash) 167 } 168 } 169 close(tasks) 170 close(results) 171 172 a.Deps = res 173 return res, nil 174 } 175 176 type MavenPOM struct { 177 License string `xml:"licenses>license>name"` 178 } 179 180 func worker(cacheDir string, tasks <-chan task, results chan<- result) { 181 for task := range tasks { 182 comp := artifact.Dependency{} 183 184 basePath := filepath.Join(cacheDir, task.path, task.name, task.version, task.name+"-"+task.version) 185 186 buf, err := ioutil.ReadFile(basePath + ".jar") 187 if err != nil { 188 results <- result{comp: comp, err: err} 189 continue 190 } 191 rawHash := sha256.Sum256(buf) 192 hash := hex.EncodeToString(rawHash[:]) 193 194 buf, err = ioutil.ReadFile(basePath + ".pom") 195 if err != nil { 196 results <- result{comp: comp, err: err} 197 continue 198 } 199 200 var pom MavenPOM 201 decoder := xml.NewDecoder(bytes.NewBuffer(buf)) 202 decoder.CharsetReader = charset.NewReaderLabel 203 err = decoder.Decode(&pom) 204 if err != nil { 205 results <- result{comp: comp, err: err} 206 continue 207 } 208 209 comp.Hash = hash 210 comp.Version = task.version 211 comp.Name = task.name 212 comp.HashType = artifact.HashSHA256 213 comp.License = pom.License 214 215 results <- result{comp: comp} 216 } 217 } 218 219 func GetPOM(path string) (string, error) { 220 221 if strings.ToLower(filepath.Base(path)) == pom { 222 return path, nil 223 } 224 225 fm := strings.ToLower(filepath.Join(path, pom)) 226 if _, err := os.Stat(fm); err == nil { 227 return fm, nil 228 } 229 230 r, err := zip.OpenReader(path) 231 232 if err != nil { 233 return "", err 234 } 235 236 defer r.Close() 237 for _, f := range r.File { 238 if filepath.Base(f.Name) != "pom.xml" { 239 continue 240 } 241 hash := sha256.Sum256([]byte(f.Name)) 242 tmpDirName := base64.RawURLEncoding.EncodeToString(hash[:]) 243 tempDir := filepath.Join(os.TempDir(), "vcn", tmpDirName) 244 err := os.MkdirAll(tempDir, 0755) 245 if err != nil { 246 return "", err 247 } 248 tmp, err := ioutil.TempFile(tempDir, POM_name) 249 if err != nil { 250 return "", err 251 } 252 defer tmp.Close() 253 rc, err := f.Open() 254 if err != nil { 255 return "", err 256 } 257 defer rc.Close() 258 259 _, err = io.Copy(tmp, rc) 260 if err != nil { 261 return "", err 262 } 263 return tmp.Name(), nil 264 } 265 return "", errors.New("no pom is founded") 266 } 267 268 type MavenConfig struct { 269 LocalRepo string `xml:"settings>localRepository"` 270 } 271 272 // local Maven package cache dir 273 func MavenCacheDir() (string, error) { 274 command := exec.Command(maven, "-v") 275 out, err := command.Output() 276 if err != nil { 277 exit, ok := err.(*exec.ExitError) 278 if ok { 279 return "", fmt.Errorf("cannot get Maven config:\n%s", string(exit.Stderr)) 280 } 281 return "", fmt.Errorf("cannot get Maven config: %w", err) 282 } 283 284 var configDir string 285 scanner := bufio.NewScanner(bytes.NewBuffer(out)) 286 for scanner.Scan() { 287 fields := strings.SplitN(scanner.Text(), ": ", 2) 288 if fields[0] == "Maven home" { 289 configDir = fields[1] 290 break 291 } 292 } 293 294 if configDir == "" { 295 return "", fmt.Errorf("cannot find Maven home dir") 296 } 297 298 buf, err := ioutil.ReadFile(filepath.Join(configDir, "conf", "settings.xml")) 299 if err != nil { 300 return "", fmt.Errorf("cannot read Maven config: %w", err) 301 } 302 303 var mvnConfig MavenConfig 304 err = xml.Unmarshal(buf, &mvnConfig) 305 if err != nil { 306 return "", fmt.Errorf("cannot parse Maven config: %w", err) 307 } 308 309 if mvnConfig.LocalRepo != "" { 310 return mvnConfig.LocalRepo, nil 311 } 312 313 // local repo path is not overridden in Maven config - use default path 314 home, err := os.UserHomeDir() 315 if err != nil { 316 return "", fmt.Errorf("cannot get user home dir: %w", err) 317 } 318 319 return filepath.Join(home, ".m2", "repository"), nil 320 }