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 }