github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/artifactory/commands/npm/install.go (about)

     1  package npm
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/buger/jsonparser"
    17  	gofrogcmd "github.com/jfrog/gofrog/io"
    18  	"github.com/jfrog/gofrog/parallel"
    19  	"github.com/jfrog/jfrog-cli-go/artifactory/utils"
    20  	"github.com/jfrog/jfrog-cli-go/artifactory/utils/npm"
    21  	"github.com/jfrog/jfrog-cli-go/utils/config"
    22  	"github.com/jfrog/jfrog-cli-go/utils/ioutils"
    23  	"github.com/jfrog/jfrog-client-go/artifactory"
    24  	"github.com/jfrog/jfrog-client-go/artifactory/buildinfo"
    25  	serviceutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
    26  	"github.com/jfrog/jfrog-client-go/auth"
    27  	"github.com/jfrog/jfrog-client-go/httpclient"
    28  	cliutils "github.com/jfrog/jfrog-client-go/utils"
    29  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    30  	"github.com/jfrog/jfrog-client-go/utils/log"
    31  	"github.com/jfrog/jfrog-client-go/utils/version"
    32  )
    33  
    34  const npmrcFileName = ".npmrc"
    35  const npmrcBackupFileName = "jfrog.npmrc.backup"
    36  const minSupportedArtifactoryVersion = "5.5.2"
    37  const minSupportedNpmVersion = "5.4.0"
    38  
    39  type NpmCommandArgs struct {
    40  	command          string
    41  	threads          int
    42  	jsonOutput       bool
    43  	executablePath   string
    44  	npmrcFileMode    os.FileMode
    45  	workingDirectory string
    46  	registry         string
    47  	npmAuth          string
    48  	collectBuildInfo bool
    49  	dependencies     map[string]*dependency
    50  	typeRestriction  string
    51  	artDetails       auth.CommonDetails
    52  	packageInfo      *npm.PackageInfo
    53  	NpmCommand
    54  }
    55  
    56  type NpmInstallCommand struct {
    57  	configFilePath string
    58  	commandName    string
    59  	*NpmCommandArgs
    60  }
    61  
    62  func NewNpmInstallCommand() *NpmInstallCommand {
    63  	return &NpmInstallCommand{NpmCommandArgs: NewNpmCommandArgs("install"), commandName: "rt_npm_install"}
    64  }
    65  
    66  func NewNpmCiCommand() *NpmInstallCommand {
    67  	return &NpmInstallCommand{NpmCommandArgs: NewNpmCommandArgs("ci"), commandName: "rt_npm_ci"}
    68  }
    69  
    70  func (nic *NpmInstallCommand) CommandName() string {
    71  	return nic.commandName
    72  }
    73  
    74  func (nic *NpmInstallCommand) SetConfigFilePath(configFilePath string) *NpmInstallCommand {
    75  	nic.configFilePath = configFilePath
    76  	return nic
    77  }
    78  
    79  func (nic *NpmInstallCommand) SetArgs(args []string) *NpmInstallCommand {
    80  	nic.NpmCommandArgs.npmArgs = args
    81  	return nic
    82  }
    83  
    84  func (nic *NpmInstallCommand) SetRepoConfig(conf *utils.RepositoryConfig) *NpmInstallCommand {
    85  	rtDetails, _ := conf.RtDetails()
    86  	nic.NpmCommandArgs.SetRepo(conf.TargetRepo()).SetRtDetails(rtDetails)
    87  	return nic
    88  }
    89  
    90  func (nic *NpmInstallCommand) Run() error {
    91  	log.Info("Running npm Install.")
    92  	// Read config file.
    93  	log.Debug("Preparing to read the config file", nic.configFilePath)
    94  	vConfig, err := utils.ReadConfigFile(nic.configFilePath, utils.YAML)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	// Extract resolution params.
    99  	resolverParams, err := utils.GetRepoConfigByPrefix(nic.configFilePath, utils.ProjectConfigResolverPrefix, vConfig)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	threads, jsonOutput, filteredNpmArgs, buildConfiguration, err := npm.ExtractNpmOptionsFromArgs(nic.npmArgs)
   104  	nic.SetRepoConfig(resolverParams).SetArgs(filteredNpmArgs).SetThreads(threads).SetJsonOutput(jsonOutput).SetBuildConfiguration(buildConfiguration)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	return nic.run()
   109  }
   110  
   111  func (nca *NpmCommandArgs) SetThreads(threads int) *NpmCommandArgs {
   112  	nca.threads = threads
   113  	return nca
   114  }
   115  
   116  func (nca *NpmCommandArgs) SetJsonOutput(jsonOutput bool) *NpmCommandArgs {
   117  	nca.jsonOutput = jsonOutput
   118  	return nca
   119  }
   120  
   121  func NewNpmCommandArgs(npmCommand string) *NpmCommandArgs {
   122  	return &NpmCommandArgs{command: npmCommand}
   123  }
   124  
   125  func (nca *NpmCommandArgs) RtDetails() (*config.ArtifactoryDetails, error) {
   126  	return nca.rtDetails, nil
   127  }
   128  
   129  func (nca *NpmCommandArgs) run() error {
   130  	if err := nca.preparePrerequisites(nca.repo); err != nil {
   131  		return err
   132  	}
   133  
   134  	if err := nca.createTempNpmrc(); err != nil {
   135  		return nca.restoreNpmrcAndError(err)
   136  	}
   137  
   138  	if err := nca.runInstall(); err != nil {
   139  		return nca.restoreNpmrcAndError(err)
   140  	}
   141  
   142  	if err := nca.restoreNpmrc(); err != nil {
   143  		return err
   144  	}
   145  
   146  	if !nca.collectBuildInfo {
   147  		log.Info("npm install finished successfully.")
   148  		return nil
   149  	}
   150  
   151  	if err := nca.setDependenciesList(); err != nil {
   152  		return err
   153  	}
   154  
   155  	if err := nca.collectDependenciesChecksums(); err != nil {
   156  		return err
   157  	}
   158  
   159  	if err := nca.saveDependenciesData(); err != nil {
   160  		return err
   161  	}
   162  
   163  	log.Info("npm install finished successfully.")
   164  	return nil
   165  }
   166  
   167  func (nca *NpmCommandArgs) preparePrerequisites(repo string) error {
   168  	log.Debug("Preparing prerequisites.")
   169  	if err := nca.setNpmExecutable(); err != nil {
   170  		return err
   171  	}
   172  
   173  	if err := nca.validateNpmVersion(); err != nil {
   174  		return err
   175  	}
   176  
   177  	if err := nca.setWorkingDirectory(); err != nil {
   178  		return err
   179  	}
   180  
   181  	if err := nca.prepareArtifactoryPrerequisites(repo); err != nil {
   182  		return err
   183  	}
   184  
   185  	if err := nca.prepareBuildInfo(); err != nil {
   186  		return err
   187  	}
   188  
   189  	return nca.backupProjectNpmrc()
   190  }
   191  
   192  func (nca *NpmCommandArgs) prepareArtifactoryPrerequisites(repo string) (err error) {
   193  	npmAuth, err := getArtifactoryDetails(nca.artDetails)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	nca.npmAuth = npmAuth
   198  
   199  	if err = utils.CheckIfRepoExists(repo, nca.artDetails); err != nil {
   200  		return err
   201  	}
   202  
   203  	nca.registry = getNpmRepositoryUrl(repo, nca.artDetails.GetUrl())
   204  	return nil
   205  }
   206  
   207  func (nca *NpmCommandArgs) prepareBuildInfo() error {
   208  	var err error
   209  	if len(nca.buildConfiguration.BuildName) > 0 && len(nca.buildConfiguration.BuildNumber) > 0 {
   210  		nca.collectBuildInfo = true
   211  		if err = utils.SaveBuildGeneralDetails(nca.buildConfiguration.BuildName, nca.buildConfiguration.BuildNumber); err != nil {
   212  			return err
   213  		}
   214  
   215  		if nca.packageInfo, err = npm.ReadPackageInfoFromPackageJson(nca.workingDirectory); err != nil {
   216  			return err
   217  		}
   218  	}
   219  	return err
   220  }
   221  
   222  func (nca *NpmCommandArgs) setWorkingDirectory() error {
   223  	currentDir, err := os.Getwd()
   224  	if err != nil {
   225  		return errorutils.CheckError(err)
   226  	}
   227  
   228  	if currentDir, err = filepath.Abs(currentDir); err != nil {
   229  		return errorutils.CheckError(err)
   230  	}
   231  
   232  	nca.workingDirectory = currentDir
   233  	log.Debug("Working directory set to:", nca.workingDirectory)
   234  	if err = nca.setArtifactoryAuth(); err != nil {
   235  		return errorutils.CheckError(err)
   236  	}
   237  	return nil
   238  }
   239  
   240  // In order to make sure the install downloads the dependencies from Artifactory, we are creating a.npmrc file in the project's root directory.
   241  // If such a file already exists, we are copying it aside.
   242  // This method restores the backed up file and deletes the one created by the command.
   243  func (nca *NpmCommandArgs) restoreNpmrc() (err error) {
   244  	log.Debug("Restoring project .npmrc file")
   245  	if err = os.Remove(filepath.Join(nca.workingDirectory, npmrcFileName)); err != nil {
   246  		return errorutils.CheckError(errors.New(createRestoreErrorPrefix(nca.workingDirectory) + err.Error()))
   247  	}
   248  	log.Debug("Deleted the temporary .npmrc file successfully")
   249  
   250  	if _, err = os.Stat(filepath.Join(nca.workingDirectory, npmrcBackupFileName)); err != nil {
   251  		if os.IsNotExist(err) {
   252  			return nil
   253  		}
   254  		return errorutils.CheckError(errors.New(createRestoreErrorPrefix(nca.workingDirectory) + err.Error()))
   255  	}
   256  
   257  	if err = ioutils.CopyFile(
   258  		filepath.Join(nca.workingDirectory, npmrcBackupFileName),
   259  		filepath.Join(nca.workingDirectory, npmrcFileName), nca.npmrcFileMode); err != nil {
   260  		return errorutils.CheckError(err)
   261  	}
   262  	log.Debug("Restored project .npmrc file successfully")
   263  
   264  	if err = os.Remove(filepath.Join(nca.workingDirectory, npmrcBackupFileName)); err != nil {
   265  		return errorutils.CheckError(errors.New(createRestoreErrorPrefix(nca.workingDirectory) + err.Error()))
   266  	}
   267  	log.Debug("Deleted project", npmrcBackupFileName, "file successfully")
   268  	return nil
   269  }
   270  
   271  func createRestoreErrorPrefix(workingDirectory string) string {
   272  	return fmt.Sprintf("Error occurred while restoring project .npmrc file. "+
   273  		"Delete '%s' and move '%s' (if exists) to '%s' in order to restore the project. Failure cause: \n",
   274  		filepath.Join(workingDirectory, npmrcFileName),
   275  		filepath.Join(workingDirectory, npmrcBackupFileName),
   276  		filepath.Join(workingDirectory, npmrcFileName))
   277  }
   278  
   279  // In order to make sure the install downloads the artifacts from Artifactory we create a .npmrc file in the project dir.
   280  // If such a file exists we back it up as npmrcBackupFileName.
   281  func (nca *NpmCommandArgs) createTempNpmrc() error {
   282  	log.Debug("Creating project .npmrc file.")
   283  	data, err := npm.GetConfigList(nca.npmArgs, nca.executablePath)
   284  	configData, err := nca.prepareConfigData(data)
   285  	if err != nil {
   286  		return errorutils.CheckError(err)
   287  	}
   288  
   289  	if err = removeNpmrcIfExists(nca.workingDirectory); err != nil {
   290  		return err
   291  	}
   292  
   293  	return errorutils.CheckError(ioutil.WriteFile(filepath.Join(nca.workingDirectory, npmrcFileName), configData, nca.npmrcFileMode))
   294  }
   295  
   296  func (nca *NpmCommandArgs) runInstall() error {
   297  	log.Debug(fmt.Sprintf("Running npm %s command.", nca.command))
   298  	filteredArgs := filterFlags(nca.npmArgs)
   299  	installCmdConfig := &npm.NpmConfig{
   300  		Npm:          nca.executablePath,
   301  		Command:      append([]string{nca.command}, filteredArgs...),
   302  		CommandFlags: nil,
   303  		StrWriter:    nil,
   304  		ErrWriter:    nil,
   305  	}
   306  
   307  	if nca.collectBuildInfo && len(filteredArgs) > 0 {
   308  		log.Warn("Build info dependencies collection with npm arguments is not supported. Build info creation will be skipped.")
   309  		nca.collectBuildInfo = false
   310  	}
   311  
   312  	return errorutils.CheckError(gofrogcmd.RunCmd(installCmdConfig))
   313  }
   314  
   315  func (nca *NpmCommandArgs) setDependenciesList() (err error) {
   316  	nca.dependencies = make(map[string]*dependency)
   317  	// nca.scope can be empty, "production" or "development" in case of empty both of the functions should run
   318  	if nca.typeRestriction != "production" {
   319  		if err = nca.prepareDependencies("development"); err != nil {
   320  			return
   321  		}
   322  	}
   323  	if nca.typeRestriction != "development" {
   324  		err = nca.prepareDependencies("production")
   325  	}
   326  	return
   327  }
   328  
   329  func (nca *NpmCommandArgs) collectDependenciesChecksums() error {
   330  	log.Info("Collecting dependencies information... This may take a few minutes...")
   331  	servicesManager, err := utils.CreateServiceManager(nca.rtDetails, false)
   332  	if err != nil {
   333  		return err
   334  	}
   335  
   336  	producerConsumer := parallel.NewBounedRunner(nca.threads, false)
   337  	errorsQueue := serviceutils.NewErrorsQueue(1)
   338  	handlerFunc := nca.createGetDependencyInfoFunc(servicesManager)
   339  	go func() {
   340  		defer producerConsumer.Done()
   341  		for i := range nca.dependencies {
   342  			producerConsumer.AddTaskWithError(handlerFunc(i), errorsQueue.AddError)
   343  		}
   344  	}()
   345  	producerConsumer.Run()
   346  	return errorsQueue.GetError()
   347  }
   348  
   349  func (nca *NpmCommandArgs) saveDependenciesData() error {
   350  	log.Debug("Saving data.")
   351  	dependencies, missingDependencies := nca.transformDependencies()
   352  	populateFunc := func(partial *buildinfo.Partial) {
   353  		partial.Dependencies = dependencies
   354  		if nca.buildConfiguration.Module == "" {
   355  			nca.buildConfiguration.Module = nca.packageInfo.BuildInfoModuleId()
   356  		}
   357  		partial.ModuleId = nca.buildConfiguration.Module
   358  	}
   359  
   360  	if err := utils.SavePartialBuildInfo(nca.buildConfiguration.BuildName, nca.buildConfiguration.BuildNumber, populateFunc); err != nil {
   361  		return err
   362  	}
   363  
   364  	if len(missingDependencies) > 0 {
   365  		var missingDependenciesText []string
   366  		for _, dependency := range missingDependencies {
   367  			missingDependenciesText = append(missingDependenciesText, dependency.name+"-"+dependency.version)
   368  		}
   369  		log.Warn(strings.Join(missingDependenciesText, "\n"))
   370  		log.Warn("The npm dependencies above could not be found in Artifactory and therefore are not included in the build-info.\n" +
   371  			"Make sure the dependencies are available in Artifactory for this build.\n" +
   372  			"Deleting the local cache will force populating Artifactory with these dependencies.")
   373  	}
   374  	return nil
   375  }
   376  
   377  func (nca *NpmCommandArgs) validateNpmVersion() error {
   378  	npmVersion, err := npm.Version(nca.executablePath)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	rtVersion := version.NewVersion(string(npmVersion))
   383  	if rtVersion.Compare(minSupportedNpmVersion) > 0 {
   384  		return errorutils.CheckError(errors.New("JFrog cli npm-install command requires npm client version " + minSupportedNpmVersion + " or higher."))
   385  	}
   386  	return nil
   387  }
   388  
   389  // To make npm do the resolution from Artifactory we are creating .npmrc file in the project dir.
   390  // If a .npmrc file already exists we will backup it and override while running the command
   391  func (nca *NpmCommandArgs) backupProjectNpmrc() error {
   392  	fileInfo, err := os.Stat(filepath.Join(nca.workingDirectory, npmrcFileName))
   393  	if err != nil {
   394  		if os.IsNotExist(err) {
   395  			nca.npmrcFileMode = 0644
   396  			return nil
   397  		}
   398  		return errorutils.CheckError(err)
   399  	}
   400  
   401  	nca.npmrcFileMode = fileInfo.Mode()
   402  	src := filepath.Join(nca.workingDirectory, npmrcFileName)
   403  	dst := filepath.Join(nca.workingDirectory, npmrcBackupFileName)
   404  	if err = ioutils.CopyFile(src, dst, nca.npmrcFileMode); err != nil {
   405  		return err
   406  	}
   407  	log.Debug("Project .npmrc file backed up successfully to", filepath.Join(nca.workingDirectory, npmrcBackupFileName))
   408  	return nil
   409  }
   410  
   411  // This func transforms "npm config list --json" result to key=val list of values that can be set to .npmrc file.
   412  // it filters any nil values key, changes registry and scope registries to Artifactory url and adds Artifactory authentication to the list
   413  func (nca *NpmCommandArgs) prepareConfigData(data []byte) ([]byte, error) {
   414  	var collectedConfig map[string]interface{}
   415  	var filteredConf []string
   416  	if err := json.Unmarshal(data, &collectedConfig); err != nil {
   417  		return nil, errorutils.CheckError(err)
   418  	}
   419  
   420  	for i := range collectedConfig {
   421  		if isValidKeyVal(i, collectedConfig[i]) {
   422  			filteredConf = append(filteredConf, i, " = ", fmt.Sprint(collectedConfig[i]), "\n")
   423  		} else if strings.HasPrefix(i, "@") {
   424  			// Override scoped registries (@scope = xyz)
   425  			filteredConf = append(filteredConf, i, " = ", nca.registry, "\n")
   426  		}
   427  		nca.setTypeRestriction(i, collectedConfig[i])
   428  	}
   429  	filteredConf = append(filteredConf, "json = ", strconv.FormatBool(nca.jsonOutput), "\n")
   430  	filteredConf = append(filteredConf, "registry = ", nca.registry, "\n")
   431  	filteredConf = append(filteredConf, nca.npmAuth)
   432  	return []byte(strings.Join(filteredConf, "")), nil
   433  }
   434  
   435  // npm install type restriction can be set by "--production" or "-only={prod[uction]|dev[elopment]}" flags
   436  func (nca *NpmCommandArgs) setTypeRestriction(key string, val interface{}) {
   437  	if key == "production" && val != nil && (val == true || val == "true") {
   438  		nca.typeRestriction = "production"
   439  	} else if key == "only" && val != nil {
   440  		if strings.Contains(val.(string), "prod") {
   441  			nca.typeRestriction = "production"
   442  		} else if strings.Contains(val.(string), "dev") {
   443  			nca.typeRestriction = "development"
   444  		}
   445  	}
   446  }
   447  
   448  // Run npm list and parse the returned json
   449  func (nca *NpmCommandArgs) prepareDependencies(typeRestriction string) error {
   450  	// Run npm list
   451  	data, errData, err := npm.RunList(strings.Join(append(nca.npmArgs, " -only="+typeRestriction), " "), nca.executablePath)
   452  	if err != nil {
   453  		log.Warn("npm list command failed with error:", err.Error())
   454  	}
   455  	if len(errData) > 0 {
   456  		log.Warn("Some errors occurred while collecting dependencies info:\n" + string(errData))
   457  	}
   458  
   459  	// Parse the dependencies json object
   460  	return jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
   461  		if string(key) == "dependencies" {
   462  			err := nca.parseDependencies(value, typeRestriction)
   463  			if err != nil {
   464  				return err
   465  			}
   466  		}
   467  		return nil
   468  	})
   469  }
   470  
   471  // Parses npm dependencies recursively and adds the collected dependencies to nca.dependencies
   472  func (nca *NpmCommandArgs) parseDependencies(data []byte, scope string) error {
   473  	var transitiveDependencies [][]byte
   474  	err := jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
   475  		ver, _, _, err := jsonparser.Get(data, string(key), "version")
   476  		if err != nil && err != jsonparser.KeyPathNotFoundError {
   477  			return errorutils.CheckError(err)
   478  		} else if err == jsonparser.KeyPathNotFoundError {
   479  			log.Warn(fmt.Sprintf("npm dependencies list contains the package '%s' without version information. The dependency will not be added to build-info.", string(key)))
   480  		} else {
   481  			nca.appendDependency(key, ver, scope)
   482  		}
   483  		transitive, _, _, err := jsonparser.Get(data, string(key), "dependencies")
   484  		if err != nil && err.Error() != "Key path not found" {
   485  			return errorutils.CheckError(err)
   486  		}
   487  
   488  		if len(transitive) > 0 {
   489  			transitiveDependencies = append(transitiveDependencies, transitive)
   490  		}
   491  		return nil
   492  	})
   493  
   494  	if err != nil {
   495  		return err
   496  	}
   497  
   498  	for _, element := range transitiveDependencies {
   499  		err := nca.parseDependencies(element, scope)
   500  		if err != nil {
   501  			return err
   502  		}
   503  	}
   504  	return nil
   505  }
   506  
   507  func (nca *NpmCommandArgs) appendDependency(key []byte, ver []byte, scope string) {
   508  	dependencyKey := string(key) + "-" + string(ver)
   509  	if nca.dependencies[dependencyKey] == nil {
   510  		nca.dependencies[dependencyKey] = &dependency{name: string(key), version: string(ver), scopes: []string{scope}}
   511  	} else if !scopeAlreadyExists(scope, nca.dependencies[dependencyKey].scopes) {
   512  		nca.dependencies[dependencyKey].scopes = append(nca.dependencies[dependencyKey].scopes, scope)
   513  	}
   514  }
   515  
   516  // Creates a function that fetches dependency data from Artifactory. Can be applied from a producer-consumer mechanism
   517  func (nca *NpmCommandArgs) createGetDependencyInfoFunc(servicesManager *artifactory.ArtifactoryServicesManager) getDependencyInfoFunc {
   518  	return func(dependencyIndex string) parallel.TaskFunc {
   519  		return func(threadId int) error {
   520  			name := nca.dependencies[dependencyIndex].name
   521  			ver := nca.dependencies[dependencyIndex].version
   522  			log.Debug(cliutils.GetLogMsgPrefix(threadId, false), "Fetching checksums for", name, "-", ver)
   523  			result, err := servicesManager.Aql(serviceutils.CreateAqlQueryForNpm(name, ver))
   524  			if err != nil {
   525  				return err
   526  			}
   527  
   528  			parsedResult := new(aqlResult)
   529  			if err = json.Unmarshal(result, parsedResult); err != nil {
   530  				return errorutils.CheckError(err)
   531  			}
   532  			if len(parsedResult.Results) == 0 {
   533  				log.Debug(cliutils.GetLogMsgPrefix(threadId, false), name, "-", ver, "could not be found in Artifactory.")
   534  				return nil
   535  			}
   536  			nca.dependencies[dependencyIndex].artifactName = parsedResult.Results[0].Name
   537  			nca.dependencies[dependencyIndex].checksum =
   538  				&buildinfo.Checksum{Sha1: parsedResult.Results[0].Actual_sha1, Md5: parsedResult.Results[0].Actual_md5}
   539  			log.Debug(cliutils.GetLogMsgPrefix(threadId, false), "Found", parsedResult.Results[0].Name,
   540  				"sha1:", parsedResult.Results[0].Actual_sha1,
   541  				"md5", parsedResult.Results[0].Actual_md5)
   542  			return nil
   543  		}
   544  	}
   545  }
   546  
   547  // Transforms the list of dependencies to buildinfo.Dependencies list and creates a list of dependencies that are missing in Artifactory.
   548  func (nca *NpmCommandArgs) transformDependencies() (dependencies []buildinfo.Dependency, missingDependencies []dependency) {
   549  	for _, dependency := range nca.dependencies {
   550  		if dependency.artifactName != "" {
   551  			fileType := ""
   552  			if i := strings.LastIndex(dependency.artifactName, "."); i != -1 {
   553  				fileType = dependency.artifactName[i+1:]
   554  			}
   555  			dependencies = append(dependencies,
   556  				buildinfo.Dependency{Id: dependency.artifactName, Type: fileType, Scopes: dependency.scopes, Checksum: dependency.checksum})
   557  		} else {
   558  			missingDependencies = append(missingDependencies, *dependency)
   559  		}
   560  	}
   561  	return
   562  }
   563  
   564  func (nca *NpmCommandArgs) restoreNpmrcAndError(err error) error {
   565  	if restoreErr := nca.restoreNpmrc(); restoreErr != nil {
   566  		return errors.New(fmt.Sprintf("Two errors occurred:\n %s\n %s", restoreErr.Error(), err.Error()))
   567  	}
   568  	return err
   569  }
   570  
   571  func (nca *NpmCommandArgs) setArtifactoryAuth() error {
   572  	authArtDetails, err := nca.rtDetails.CreateArtAuthConfig()
   573  	if err != nil {
   574  		return err
   575  	}
   576  	if authArtDetails.GetSshAuthHeaders() != nil {
   577  		return errorutils.CheckError(errors.New("SSH authentication is not supported in this command."))
   578  	}
   579  	nca.artDetails = authArtDetails
   580  	return nil
   581  }
   582  
   583  func removeNpmrcIfExists(workingDirectory string) error {
   584  	if _, err := os.Stat(filepath.Join(workingDirectory, npmrcFileName)); err != nil {
   585  		if os.IsNotExist(err) { // The file dose not exist, nothing to do.
   586  			return nil
   587  		}
   588  		return errorutils.CheckError(err)
   589  	}
   590  
   591  	log.Debug("Removing Existing .npmrc file")
   592  	return errorutils.CheckError(os.Remove(filepath.Join(workingDirectory, npmrcFileName)))
   593  }
   594  
   595  func (nca *NpmCommandArgs) setNpmExecutable() error {
   596  	npmExecPath, err := exec.LookPath("npm")
   597  	if err != nil {
   598  		return errorutils.CheckError(err)
   599  	}
   600  
   601  	if npmExecPath == "" {
   602  		return errorutils.CheckError(errors.New("Could not find 'npm' executable"))
   603  	}
   604  	nca.executablePath = npmExecPath
   605  	log.Debug("Found npm executable at:", nca.executablePath)
   606  	return nil
   607  }
   608  
   609  func getArtifactoryDetails(artDetails auth.CommonDetails) (npmAuth string, err error) {
   610  	// Check Artifactory version.
   611  	err = validateArtifactoryVersion(artDetails)
   612  	if err != nil {
   613  		return "", err
   614  	}
   615  
   616  	// Get npm tokem from Artifactory.
   617  	if artDetails.GetAccessToken() == "" {
   618  		return getDetailsUsingBasicAuth(artDetails)
   619  	}
   620  	return getDetailsUsingAccessToken(artDetails)
   621  }
   622  
   623  func validateArtifactoryVersion(artDetails auth.CommonDetails) error {
   624  	// Get Artifactory version.
   625  	versionStr, err := artDetails.GetVersion()
   626  	if err != nil {
   627  		return err
   628  	}
   629  
   630  	// Validate version.
   631  	rtVersion := version.NewVersion(versionStr)
   632  	if !rtVersion.AtLeast(minSupportedArtifactoryVersion) {
   633  		return errorutils.CheckError(errors.New("This operation requires Artifactory version " + minSupportedArtifactoryVersion + " or higher."))
   634  	}
   635  
   636  	return nil
   637  }
   638  
   639  func getDetailsUsingAccessToken(artDetails auth.CommonDetails) (npmAuth string, err error) {
   640  	npmAuthString := "_auth = %s\nalways-auth = true"
   641  	// Build npm token, consists of <username:password> encoded.
   642  	// Use Artifactory's access-token as username and password to create npm token.
   643  	username, err := auth.ExtractUsernameFromAccessToken(artDetails.GetAccessToken())
   644  	if err != nil {
   645  		return "", err
   646  	}
   647  	encodedNpmToken := base64.StdEncoding.EncodeToString([]byte(username + ":" + artDetails.GetAccessToken()))
   648  	npmAuth = fmt.Sprintf(npmAuthString, encodedNpmToken)
   649  
   650  	return npmAuth, err
   651  }
   652  
   653  func getDetailsUsingBasicAuth(artDetails auth.CommonDetails) (npmAuth string, err error) {
   654  	authApiUrl := artDetails.GetUrl() + "api/npm/auth"
   655  	log.Debug("Sending npm auth request")
   656  
   657  	// Get npm token from Artifactory.
   658  	client, err := httpclient.ClientBuilder().Build()
   659  	if err != nil {
   660  		return "", err
   661  	}
   662  	resp, body, _, err := client.SendGet(authApiUrl, true, artDetails.CreateHttpClientDetails())
   663  	if err != nil {
   664  		return "", err
   665  	}
   666  	if resp.StatusCode != http.StatusOK {
   667  		return "", errorutils.CheckError(errors.New("Artifactory response: " + resp.Status + "\n" + cliutils.IndentJson(body)))
   668  	}
   669  
   670  	return string(body), nil
   671  }
   672  
   673  func getNpmRepositoryUrl(repo, url string) string {
   674  	if !strings.HasSuffix(url, "/") {
   675  		url += "/"
   676  	}
   677  	url += "api/npm/" + repo
   678  	return url
   679  }
   680  
   681  func scopeAlreadyExists(scope string, existingScopes []string) bool {
   682  	for _, existingScope := range existingScopes {
   683  		if existingScope == scope {
   684  			return true
   685  		}
   686  	}
   687  	return false
   688  }
   689  
   690  // Valid configs keys are not related to registry (registry = xyz) or scoped registry (@scope = xyz)) and have data in their value
   691  // We want to avoid writing "json=true" because we ran the the configuration list command with "--json". We will add it explicitly if necessary.
   692  func isValidKeyVal(key string, val interface{}) bool {
   693  	return !strings.HasPrefix(key, "//") &&
   694  		!strings.HasPrefix(key, "@") &&
   695  		key != "registry" &&
   696  		key != "metrics-registry" &&
   697  		key != "json" &&
   698  		val != nil &&
   699  		val != ""
   700  }
   701  
   702  func filterFlags(splitArgs []string) []string {
   703  	var filteredArgs []string
   704  	for _, arg := range splitArgs {
   705  		if !strings.HasPrefix(arg, "-") {
   706  			filteredArgs = append(filteredArgs, arg)
   707  		}
   708  	}
   709  	return filteredArgs
   710  }
   711  
   712  type getDependencyInfoFunc func(string) parallel.TaskFunc
   713  
   714  type dependency struct {
   715  	name         string
   716  	version      string
   717  	scopes       []string
   718  	artifactName string
   719  	checksum     *buildinfo.Checksum
   720  }
   721  
   722  type aqlResult struct {
   723  	Results []*results `json:"results,omitempty"`
   724  }
   725  
   726  type results struct {
   727  	Name        string `json:"name,omitempty"`
   728  	Actual_md5  string `json:"actual_md5,omitempty"`
   729  	Actual_sha1 string `json:"actual_sha1,omitempty"`
   730  }