github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/python/dependencies/dependencies.go (about)

     1  package dependencies
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	ioutils "github.com/jfrog/gofrog/io"
     7  	"io"
     8  	"strings"
     9  
    10  	buildinfo "github.com/jfrog/build-info-go/entities"
    11  
    12  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
    13  	"github.com/jfrog/jfrog-client-go/artifactory"
    14  	serviceutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
    15  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    16  	"github.com/jfrog/jfrog-client-go/utils/log"
    17  )
    18  
    19  // Populate project's dependencies with checksums and file names.
    20  // If the dependency was downloaded in this pip-install execution, checksum will be fetched from Artifactory.
    21  // Otherwise, check if exists in cache.
    22  // Return dependency-names of all dependencies which its information could not be obtained.
    23  func UpdateDepsChecksumInfo(dependenciesMap map[string]buildinfo.Dependency, srcPath string, servicesManager artifactory.ArtifactoryServicesManager, repository string) error {
    24  	dependenciesCache, err := GetProjectDependenciesCache(srcPath)
    25  	if err != nil {
    26  		return err
    27  	}
    28  
    29  	var missingDeps []string
    30  	// Iterate dependencies map to update info.
    31  	for depName, depInfo := range dependenciesMap {
    32  		// Get dependency info.
    33  		depFileName, depChecksum, err := getDependencyInfo(depName, repository, dependenciesCache, depInfo.Id, servicesManager)
    34  		if err != nil {
    35  			return err
    36  		}
    37  
    38  		// Check if info not found.
    39  		if depFileName == "" || depChecksum.IsEmpty() {
    40  			// Dependency either wasn't downloaded in this run nor stored in cache.
    41  			missingDeps = append(missingDeps, depName)
    42  			// dependenciesMap should contain only dependencies with checksums.
    43  			delete(dependenciesMap, depName)
    44  
    45  			continue
    46  		}
    47  		depInfo.Checksum = depChecksum
    48  		dependenciesMap[depName] = depInfo
    49  	}
    50  
    51  	promptMissingDependencies(missingDeps)
    52  
    53  	err = UpdateDependenciesCache(dependenciesMap, srcPath)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	return nil
    58  }
    59  
    60  // Get dependency information.
    61  // If dependency was downloaded in this pip-install execution, fetch info from Artifactory.
    62  // Otherwise, fetch info from cache.
    63  func getDependencyInfo(depName, repository string, dependenciesCache *DependenciesCache, depFileName string, servicesManager artifactory.ArtifactoryServicesManager) (string, buildinfo.Checksum, error) {
    64  	// Check if this dependency was updated during this pip-install execution, and we have its file-name.
    65  	// If updated - fetch checksum from Artifactory, regardless of what was previously stored in cache.
    66  
    67  	if dependenciesCache != nil {
    68  		depFromCache := dependenciesCache.GetDependency(depName)
    69  		if depFromCache.Id != "" {
    70  			// Cached dependencies are used in the following cases:
    71  			// 	1. When file name is empty and therefore the dependency is cached
    72  			// 	2. When file name is identical to the cached file name
    73  			if depFileName == "" || depFileName == depFromCache.Id {
    74  				// The checksum was found in cache - the info is returned.
    75  				return depFromCache.Id, depFromCache.Checksum, nil
    76  			}
    77  		}
    78  	}
    79  
    80  	if depFileName != "" {
    81  		checksum, err := getDependencyChecksumFromArtifactory(servicesManager, repository, depFileName)
    82  		return depFileName, checksum, err
    83  	}
    84  
    85  	return "", buildinfo.Checksum{}, nil
    86  }
    87  
    88  // Fetch checksum for file from Artifactory.
    89  // If the file isn't found, or md5 or sha1 are missing, return nil.
    90  func getDependencyChecksumFromArtifactory(servicesManager artifactory.ArtifactoryServicesManager, repository, dependencyFile string) (checksum buildinfo.Checksum, err error) {
    91  	log.Debug(fmt.Sprintf("Fetching checksums for: %s", dependencyFile))
    92  	repository, err = utils.GetRepoNameForDependenciesSearch(repository, servicesManager)
    93  	if err != nil {
    94  		return
    95  	}
    96  	stream, err := servicesManager.Aql(serviceutils.CreateAqlQueryForPypi(repository, dependencyFile))
    97  	if err != nil {
    98  		return
    99  	}
   100  	defer ioutils.Close(stream, &err)
   101  	result, err := io.ReadAll(stream)
   102  	if err != nil {
   103  		return
   104  	}
   105  	parsedResult := new(aqlResult)
   106  	err = json.Unmarshal(result, parsedResult)
   107  	if err = errorutils.CheckError(err); err != nil {
   108  		return
   109  	}
   110  	if len(parsedResult.Results) == 0 {
   111  		log.Debug(fmt.Sprintf("File: %s could not be found in repository: %s", dependencyFile, repository))
   112  		return
   113  	}
   114  
   115  	// Verify checksum exist.
   116  	sha256 := parsedResult.Results[0].Sha256
   117  	sha1 := parsedResult.Results[0].Actual_Sha1
   118  	md5 := parsedResult.Results[0].Actual_Md5
   119  	if sha1 == "" || md5 == "" {
   120  		// Missing checksum.
   121  		log.Debug(fmt.Sprintf("Missing checksums for file: %s, sha256: '%s', sha1: '%s', md5: '%s'", dependencyFile, sha256, sha1, md5))
   122  		return
   123  	}
   124  
   125  	// Update checksum.
   126  	checksum = buildinfo.Checksum{Sha256: sha256, Sha1: sha1, Md5: md5}
   127  	log.Debug(fmt.Sprintf("Found checksums for file: %s, sha256: '%s', sha1: '%s', md5: '%s'", dependencyFile, sha256, sha1, md5))
   128  
   129  	return
   130  }
   131  
   132  func promptMissingDependencies(missingDeps []string) {
   133  	if len(missingDeps) > 0 {
   134  		log.Warn(strings.Join(missingDeps, "\n"))
   135  		log.Warn("The pypi packages above could not be found in Artifactory or were not downloaded in this execution, therefore they are not included in the build-info.\n" +
   136  			"Reinstalling in clean environment or using '--no-cache-dir' and '--force-reinstall' flags (in one execution only), will force downloading and populating Artifactory with these packages, and therefore resolve the issue.")
   137  	}
   138  }
   139  
   140  type aqlResult struct {
   141  	Results []*serviceutils.ResultItem `json:"results,omitempty"`
   142  }