github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/utils/pip/installer.go (about) 1 package pip 2 3 import ( 4 "fmt" 5 "net/url" 6 "strings" 7 8 gofrogcmd "github.com/jfrog/gofrog/io" 9 "github.com/jfrog/jfrog-cli-core/utils/config" 10 "github.com/jfrog/jfrog-client-go/auth" 11 clientutils "github.com/jfrog/jfrog-client-go/utils" 12 "github.com/jfrog/jfrog-client-go/utils/errorutils" 13 "github.com/jfrog/jfrog-client-go/utils/log" 14 ) 15 16 type PipInstaller struct { 17 ServerDetails *config.ServerDetails 18 Args []string 19 Repository string 20 ShouldParseLogs bool 21 DependencyToFileMap map[string]string 22 } 23 24 func (pi *PipInstaller) Install() error { 25 // Prepare for running. 26 pipExecutablePath, pipIndexUrl, err := pi.prepare() 27 if err != nil { 28 return err 29 } 30 31 // Run pip install. 32 err = pi.runPipInstall(pipExecutablePath, pipIndexUrl) 33 if err != nil { 34 return err 35 } 36 37 return nil 38 } 39 40 func (pi *PipInstaller) prepare() (pipExecutablePath, pipIndexUrl string, err error) { 41 log.Debug("Preparing prerequisites.") 42 43 pipExecutablePath, err = GetExecutablePath("pip") 44 if err != nil { 45 return 46 } 47 48 pipIndexUrl, err = getArtifactoryUrlWithCredentials(pi.ServerDetails, pi.Repository) 49 if err != nil { 50 return 51 } 52 53 return 54 } 55 56 func getArtifactoryUrlWithCredentials(serverDetails *config.ServerDetails, repository string) (string, error) { 57 rtUrl, err := url.Parse(serverDetails.GetArtifactoryUrl()) 58 if err != nil { 59 return "", errorutils.CheckError(err) 60 } 61 62 username := serverDetails.GetUser() 63 password := serverDetails.GetPassword() 64 65 // Get credentials from access-token if exists. 66 if serverDetails.GetAccessToken() != "" { 67 username, err = auth.ExtractUsernameFromAccessToken(serverDetails.GetAccessToken()) 68 if err != nil { 69 return "", err 70 } 71 password = serverDetails.GetAccessToken() 72 } 73 74 if username != "" && password != "" { 75 rtUrl.User = url.UserPassword(username, password) 76 } 77 rtUrl.Path += "api/pypi/" + repository + "/simple" 78 79 return rtUrl.String(), nil 80 } 81 82 func (pi *PipInstaller) runPipInstall(pipExecutablePath, pipIndexUrl string) error { 83 pipInstallCmd := &PipCmd{ 84 Executable: pipExecutablePath, 85 Command: "install", 86 CommandArgs: append(pi.Args, "-i", pipIndexUrl), 87 } 88 89 // Check if need to run with log parsing. 90 if pi.ShouldParseLogs { 91 return pi.runPipInstallWithLogParsing(pipInstallCmd) 92 } 93 94 // Run without log parsing. 95 return gofrogcmd.RunCmd(pipInstallCmd) 96 } 97 98 // Run pip-install command while parsing the logs for downloaded packages. 99 // Supports running pip either in non-verbose and verbose mode. 100 // Populates 'dependencyToFileMap' with downloaded package-name and its actual downloaded file (wheel/egg/zip...). 101 func (pi *PipInstaller) runPipInstallWithLogParsing(pipInstallCmd *PipCmd) error { 102 // Create regular expressions for log parsing. 103 collectingPackageRegexp, err := clientutils.GetRegExp(`^Collecting\s(\w[\w-\.]+)`) 104 if err != nil { 105 return err 106 } 107 downloadFileRegexp, err := clientutils.GetRegExp(`^\s\sDownloading\s[^\s]*\/([^\s]*)`) 108 if err != nil { 109 return err 110 } 111 installedPackagesRegexp, err := clientutils.GetRegExp(`^Requirement\salready\ssatisfied\:\s(\w[\w-\.]+)`) 112 if err != nil { 113 return err 114 } 115 116 downloadedDependencies := make(map[string]string) 117 var packageName string 118 expectingPackageFilePath := false 119 120 // Extract downloaded package name. 121 dependencyNameParser := gofrogcmd.CmdOutputPattern{ 122 RegExp: collectingPackageRegexp, 123 ExecFunc: func(pattern *gofrogcmd.CmdOutputPattern) (string, error) { 124 // If this pattern matched a second time before downloaded-file-name was found, prompt a message. 125 if expectingPackageFilePath { 126 // This may occur when a package-installation file is saved in pip-cache-dir, thus not being downloaded during the installation. 127 // Re-running pip-install with 'no-cache-dir' fixes this issue. 128 log.Debug(fmt.Sprintf("Could not resolve download path for package: %s, continuing...", packageName)) 129 130 // Save package with empty file path. 131 downloadedDependencies[strings.ToLower(packageName)] = "" 132 } 133 134 // Check for out of bound results. 135 if len(pattern.MatchedResults)-1 < 0 { 136 log.Debug(fmt.Sprintf("Failed extracting package name from line: %s", pattern.Line)) 137 return pattern.Line, nil 138 } 139 140 // Save dependency information. 141 expectingPackageFilePath = true 142 packageName = pattern.MatchedResults[1] 143 144 return pattern.Line, nil 145 }, 146 } 147 148 // Extract downloaded file, stored in Artifactory. 149 dependencyFileParser := gofrogcmd.CmdOutputPattern{ 150 RegExp: downloadFileRegexp, 151 ExecFunc: func(pattern *gofrogcmd.CmdOutputPattern) (string, error) { 152 // Check for out of bound results. 153 if len(pattern.MatchedResults)-1 < 0 { 154 log.Debug(fmt.Sprintf("Failed extracting download path from line: %s", pattern.Line)) 155 return pattern.Line, nil 156 } 157 158 // If this pattern matched before package-name was found, do not collect this path. 159 if !expectingPackageFilePath { 160 log.Debug(fmt.Sprintf("Could not resolve package name for download path: %s , continuing...", packageName)) 161 return pattern.Line, nil 162 } 163 164 // Save dependency information. 165 filePath := pattern.MatchedResults[1] 166 downloadedDependencies[strings.ToLower(packageName)] = filePath 167 expectingPackageFilePath = false 168 169 log.Debug(fmt.Sprintf("Found package: %s installed with: %s", packageName, filePath)) 170 return pattern.Line, nil 171 }, 172 } 173 174 // Extract already installed packages names. 175 installedPackagesParser := gofrogcmd.CmdOutputPattern{ 176 RegExp: installedPackagesRegexp, 177 ExecFunc: func(pattern *gofrogcmd.CmdOutputPattern) (string, error) { 178 // Check for out of bound results. 179 if len(pattern.MatchedResults)-1 < 0 { 180 log.Debug(fmt.Sprintf("Failed extracting package name from line: %s", pattern.Line)) 181 return pattern.Line, nil 182 } 183 184 // Save dependency with empty file name. 185 downloadedDependencies[strings.ToLower(pattern.MatchedResults[1])] = "" 186 187 log.Debug(fmt.Sprintf("Found package: %s already installed", pattern.MatchedResults[1])) 188 return pattern.Line, nil 189 }, 190 } 191 192 // Execute command. 193 _, _, _, err = gofrogcmd.RunCmdWithOutputParser(pipInstallCmd, true, &dependencyNameParser, &dependencyFileParser, &installedPackagesParser) 194 if errorutils.CheckError(err) != nil { 195 return err 196 } 197 198 // Update dependencyToFileMap. 199 pi.DependencyToFileMap = downloadedDependencies 200 201 return nil 202 }