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 }