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  }