diff --git a/artifactory/commands/npm/npmcommand.go b/artifactory/commands/npm/npmcommand.go index 9832b6db3..f3d477c97 100644 --- a/artifactory/commands/npm/npmcommand.go +++ b/artifactory/commands/npm/npmcommand.go @@ -390,3 +390,17 @@ func filterFlags(splitArgs []string) []string { func (nc *NpmCommand) GetRepo() string { return nc.repo } + +// Creates an .npmrc file in the project's directory in order to configure the provided Artifactory server as a resolution server +func SetArtifactoryAsResolutionServer(serverDetails *config.ServerDetails, depsRepo string) (clearResolutionServerFunc func() error, err error) { + npmCmd := NewNpmInstallCommand().SetServerDetails(serverDetails) + if err = npmCmd.PreparePrerequisites(depsRepo); err != nil { + return + } + if err = npmCmd.CreateTempNpmrc(); err != nil { + return + } + clearResolutionServerFunc = npmCmd.RestoreNpmrcFunc() + log.Info(fmt.Sprintf("Resolving dependencies from '%s' from repo '%s'", serverDetails.Url, depsRepo)) + return +} diff --git a/artifactory/commands/npm/npmcommand_test.go b/artifactory/commands/npm/npmcommand_test.go index 170e78904..57898f2b9 100644 --- a/artifactory/commands/npm/npmcommand_test.go +++ b/artifactory/commands/npm/npmcommand_test.go @@ -2,10 +2,15 @@ package npm import ( "fmt" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/gofrog/version" + commonTests "github.com/jfrog/jfrog-cli-core/v2/common/tests" + "github.com/jfrog/jfrog-cli-core/v2/utils/tests" testsUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" + "net/http" "os" + "path/filepath" "strings" "testing" ) @@ -97,3 +102,38 @@ func TestSetNpmConfigAuthEnv(t *testing.T) { }) } } + +func TestSetArtifactoryAsResolutionServer(t *testing.T) { + tmpDir, createTempDirCallback := tests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + + npmProjectPath := filepath.Join("..", "..", "..", "tests", "testdata", "npm-project") + err := biutils.CopyDir(npmProjectPath, tmpDir, false, nil) + assert.NoError(t, err) + + cwd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := testsUtils.ChangeDirWithCallback(t, cwd, tmpDir) + defer chdirCallback() + + // Prepare mock server + testServer, serverDetails, _ := commonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/api/system/version" { + w.WriteHeader(http.StatusOK) + _, err = w.Write([]byte("{\"version\" : \"7.75.4\"}")) + assert.NoError(t, err) + } + }) + defer testServer.Close() + + depsRepo := "my-rt-resolution-repo" + + clearResolutionServerFunc, err := SetArtifactoryAsResolutionServer(serverDetails, depsRepo) + assert.NoError(t, err) + assert.NotNil(t, clearResolutionServerFunc) + defer func() { + assert.NoError(t, clearResolutionServerFunc()) + }() + + assert.FileExists(t, filepath.Join(tmpDir, ".npmrc")) +} diff --git a/common/project/projectconfig.go b/common/project/projectconfig.go index c6bf9d129..89bc25309 100644 --- a/common/project/projectconfig.go +++ b/common/project/projectconfig.go @@ -1,6 +1,7 @@ package project import ( + "errors" "fmt" "os" "path/filepath" @@ -128,11 +129,7 @@ func GetProjectConfFilePath(projectType ProjectType) (confFilePath string, exist func GetRepoConfigByPrefix(configFilePath, prefix string, vConfig *viper.Viper) (repoConfig *RepositoryConfig, err error) { defer func() { if err != nil { - err = fmt.Errorf("%s\nPlease run 'jf %s-config' with your %s repository information", - err.Error(), - vConfig.GetString("type"), - prefix, - ) + err = errors.Join(err, fmt.Errorf("please run 'jf %s-config' with your %s repository information", vConfig.GetString("type"), prefix)) } }() if !vConfig.IsSet(prefix) { diff --git a/tests/testdata/npm-project/helloworld.js b/tests/testdata/npm-project/helloworld.js new file mode 100644 index 000000000..858d2c9c9 --- /dev/null +++ b/tests/testdata/npm-project/helloworld.js @@ -0,0 +1,6 @@ +var http = require('http'); + +http.createServer(function(req, res) { + res.writeHead(200, {'Content-Type' : 'text/plain'}); + res.end('Hello World\n'); +}).listen(1337, '127.0.0.1'); diff --git a/tests/testdata/npm-project/package.json b/tests/testdata/npm-project/package.json new file mode 100644 index 000000000..742cd470e --- /dev/null +++ b/tests/testdata/npm-project/package.json @@ -0,0 +1,14 @@ +{ + "name": "npm-example", + "version": "0.0.3", + "scripts": { + "start": "node helloworld" + }, + "dependencies": { + "chokidar": "^2.0.3", + "send": "^0.16.2" + }, + "devDependencies": { + "debug": "^4.1.1" + } +} diff --git a/utils/java/deptreemanager.go b/utils/java/deptreemanager.go index ef8fa0724..071e2192d 100644 --- a/utils/java/deptreemanager.go +++ b/utils/java/deptreemanager.go @@ -125,3 +125,7 @@ func getArtifactoryAuthFromServer(server *config.ServerDetails) (string, string, } return username, password, nil } + +func (dtm *DepTreeManager) GetDepsRepo() string { + return dtm.depsRepo +} diff --git a/utils/java/mvn.go b/utils/java/mvn.go index 5431a4408..56deee17c 100644 --- a/utils/java/mvn.go +++ b/utils/java/mvn.go @@ -61,39 +61,39 @@ func NewMavenDepTreeManager(params *DepTreeParams, cmdName MavenDepTreeCmd, isDe func buildMavenDependencyTree(params *DepTreeParams, isDepTreeInstalled bool) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps []string, err error) { manager := NewMavenDepTreeManager(params, Tree, isDepTreeInstalled) - outputFilePaths, err := manager.RunMavenDepTree() + outputFilePaths, clearMavenDepTreeRun, err := manager.RunMavenDepTree() if err != nil { + if clearMavenDepTreeRun != nil { + err = errors.Join(err, clearMavenDepTreeRun()) + } return } + + defer func() { + err = errors.Join(err, clearMavenDepTreeRun()) + }() + dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFilePaths) return } -func (mdt *MavenDepTreeManager) RunMavenDepTree() (string, error) { - // Create a temp directory for all the files that are required for the maven-dep-tree run - depTreeExecDir, err := fileutils.CreateTempDir() +// Runs maven-dep-tree according to cmdName. Returns the plugin output along with a function pointer to revert the plugin side effects. +// If a non-nil clearMavenDepTreeRun pointer is returnes it means we had no error during the entire function execution +func (mdt *MavenDepTreeManager) RunMavenDepTree() (depTreeOutput string, clearMavenDepTreeRun func() error, err error) { + // depTreeExecDir is a temp directory for all the files that are required for the maven-dep-tree run + depTreeExecDir, clearMavenDepTreeRun, err := mdt.CreateTempDirWithSettingsXmlIfNeeded() if err != nil { - return "", err - } - defer func() { - err = errors.Join(err, fileutils.RemoveTempDir(depTreeExecDir)) - }() - - // Create a settings.xml file that sets the dependency resolution from the given server and repository - if mdt.depsRepo != "" { - if err = mdt.createSettingsXmlWithConfiguredArtifactory(depTreeExecDir); err != nil { - return "", err - } + return } if err = mdt.installMavenDepTreePlugin(depTreeExecDir); err != nil { - return "", err + return } - depTreeOutput, err := mdt.execMavenDepTree(depTreeExecDir) + depTreeOutput, err = mdt.execMavenDepTree(depTreeExecDir) if err != nil { - return "", err + return } - return depTreeOutput, nil + return } func (mdt *MavenDepTreeManager) installMavenDepTreePlugin(depTreeExecDir string) error { @@ -170,6 +170,14 @@ func (mdt *MavenDepTreeManager) RunMvnCmd(goals []string) (cmdOutput []byte, err return } +func (mdt *MavenDepTreeManager) GetSettingsXmlPath() string { + return mdt.settingsXmlPath +} + +func (mdt *MavenDepTreeManager) SetSettingsXmlPath(settingsXmlPath string) { + mdt.settingsXmlPath = settingsXmlPath +} + func removeMavenConfig() (func() error, error) { mavenConfigExists, err := fileutils.IsFileExists(mavenConfigPath, false) if err != nil { @@ -205,3 +213,24 @@ func (mdt *MavenDepTreeManager) createSettingsXmlWithConfiguredArtifactory(path return errorutils.CheckError(os.WriteFile(mdt.settingsXmlPath, []byte(settingsXmlContent), 0600)) } + +// Creates a temporary directory. +// If Artifactory resolution repo is provided, a settings.xml file with the provided server and repository will be created inside the temprary directory. +func (mdt *MavenDepTreeManager) CreateTempDirWithSettingsXmlIfNeeded() (tempDirPath string, clearMavenDepTreeRun func() error, err error) { + tempDirPath, err = fileutils.CreateTempDir() + if err != nil { + return + } + + clearMavenDepTreeRun = func() error { return fileutils.RemoveTempDir(tempDirPath) } + + // Create a settings.xml file that sets the dependency resolution from the given server and repository + if mdt.depsRepo != "" { + err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDirPath) + } + if err != nil { + err = errors.Join(err, clearMavenDepTreeRun()) + clearMavenDepTreeRun = nil + } + return +} diff --git a/utils/java/mvn_test.go b/utils/java/mvn_test.go index ea3fe1f76..fb141a3d9 100644 --- a/utils/java/mvn_test.go +++ b/utils/java/mvn_test.go @@ -224,10 +224,13 @@ func TestRunProjectsCmd(t *testing.T) { _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "tests", "testdata", "maven-example")) defer cleanUp() mvnDepTreeManager := NewMavenDepTreeManager(&DepTreeParams{}, Projects, false) - output, err := mvnDepTreeManager.RunMavenDepTree() + output, clearMavenDepTreeRun, err := mvnDepTreeManager.RunMavenDepTree() assert.NoError(t, err) + assert.NotNil(t, clearMavenDepTreeRun) + pomPathOccurrences := strings.Count(output, "pomPath") assert.Equal(t, 4, pomPathOccurrences) + assert.NoError(t, clearMavenDepTreeRun()) } func TestRemoveMavenConfig(t *testing.T) {