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