github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/python/pipenv.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 python
    10  
    11  import (
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io/ioutil"
    16  	"os"
    17  	"path/filepath"
    18  	"strings"
    19  
    20  	"github.com/schollz/progressbar/v3"
    21  
    22  	"github.com/vchain-us/vcn/pkg/bom/artifact"
    23  )
    24  
    25  // pythonArtifactFromPipEnv implements Artifact interface
    26  type pythonArtifactFromPipEnv struct {
    27  	pythonArtifact
    28  }
    29  
    30  // Dependencies returns list of Python dependencies for the artifact
    31  // for pipenv Pipfile.lock JSON file contains all needed information in "default" section
    32  func (a *pythonArtifactFromPipEnv) ResolveDependencies(output artifact.OutputOptions) ([]artifact.Dependency, error) {
    33  	if a.Deps != nil {
    34  		return a.Deps, nil
    35  	}
    36  	file, err := os.Open(filepath.Join(a.path, pipenvFileName))
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	buf, err := ioutil.ReadAll(file)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	var content map[string]interface{}
    47  	err = json.Unmarshal(buf, &content)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	deflt, ok := content["default"]
    53  	if !ok {
    54  		return nil, errors.New("malformed Pipfile.lock - missing \"default\"")
    55  	}
    56  	packages, ok := deflt.(map[string]interface{})
    57  	if !ok {
    58  		return nil, errors.New("malformed Pipfile.lock - \"default\" has a wrong data type")
    59  	}
    60  
    61  	var bar *progressbar.ProgressBar
    62  	if output == artifact.Progress {
    63  		bar = progressbar.Default(int64(len(packages)))
    64  	}
    65  
    66  	// init goroutine throttling - channels, start goroutines.
    67  	// We can be sure that there will be no more in-flight messages in channels than known modules
    68  	tasks := make(chan task, len(packages))
    69  	results := make(chan result, len(packages))
    70  	for i := 0; i < artifact.MaxGoroutines; i++ {
    71  		go poetryWorker(tasks, results, output, bar) // pipenv and poetry use the same worker, only hash and license
    72  	}
    73  	defer close(results)
    74  	defer close(tasks) // signal workers to stop
    75  
    76  	taskCount := 0
    77  
    78  	for name, pkg := range packages {
    79  		pkgContent, ok := pkg.(map[string]interface{})
    80  		if !ok {
    81  			return nil, fmt.Errorf("malformed \"%s\" section", name)
    82  		}
    83  
    84  		field, ok := pkgContent["version"]
    85  		if !ok {
    86  			return nil, fmt.Errorf("malformed \"%s\" section", name)
    87  		}
    88  		version, ok := field.(string)
    89  		if !ok {
    90  			return nil, fmt.Errorf("malformed \"%s\" section", name)
    91  		}
    92  		version = strings.TrimPrefix(version, "==")
    93  
    94  		tasks <- task{name: name, version: version}
    95  		taskCount++
    96  	}
    97  
    98  	res := make([]artifact.Dependency, 0, len(packages))
    99  	for done := 0; taskCount == 0 || done < taskCount; done++ {
   100  		result := <-results
   101  		if result.err != nil {
   102  			close(tasks) // signal workers to stop
   103  			return nil, err
   104  		}
   105  		res = append(res, artifact.Dependency{
   106  			Name:     result.name,
   107  			Version:  result.version,
   108  			Hash:     result.hash,
   109  			HashType: result.hashType,
   110  			License:  result.license})
   111  	}
   112  
   113  	a.Deps = res
   114  	return res, nil
   115  }