Skip to content

Commit

Permalink
Setup command - added support for custom configuration and pnpm (#1326)
Browse files Browse the repository at this point in the history
  • Loading branch information
sverdlov93 authored Jan 9, 2025
1 parent 506d2fe commit 96fa7ce
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 220 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ jobs:
with:
dotnet-version: '6.x'

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9

# Install Mono on Ubuntu to run nuget.exe (due to this issue on Ubuntu 24 that hasn't been fixed yet - https://github.com/NuGet/setup-nuget/issues/168)
- name: Install Mono on Ubuntu
if: matrix.os == 'ubuntu'
Expand Down
60 changes: 53 additions & 7 deletions artifactory/commands/dotnet/dotnetcommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"net/url"
"os"
"path"
"path/filepath"
"strings"
)

Expand Down Expand Up @@ -168,7 +169,7 @@ func changeWorkingDir(newWorkingDir string) (string, error) {
}

// Runs nuget/dotnet source add command
func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, password string) error {
func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, password, customConfigPath string) error {
cmd, err := dotnet.CreateDotnetAddSourceCmd(cmdType, sourceUrl)
if err != nil {
return err
Expand All @@ -178,15 +179,21 @@ func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, passw
cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"name", SourceName)
cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"username", user)
cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"password", password)
stdOut, errorOut, _, err := frogio.RunCmdWithOutputParser(cmd, false)

if customConfigPath != "" {
addConfigFileFlag(cmd, customConfigPath)
}

_, _, _, err = frogio.RunCmdWithOutputParser(cmd, false)
if err != nil {
return fmt.Errorf("failed to add source: %w\n%s", err, strings.TrimSpace(stdOut+errorOut))
return fmt.Errorf("failed to add source: %w", err)
}
return nil
}

// Runs nuget/dotnet source remove command
func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType) error {
// RemoveSourceFromNugetConfigIfExists runs the nuget/dotnet source remove command.
// Removes the source if it exists in the configuration.
func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType, customConfigPath string) error {
cmd, err := dotnet.NewToolchainCmd(cmdType)
if err != nil {
return err
Expand All @@ -197,16 +204,55 @@ func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType) error {
cmd.Command = append(cmd.Command, "sources", "remove")
cmd.CommandFlags = append(cmd.CommandFlags, "-name", SourceName)
}

if customConfigPath != "" {
addConfigFileFlag(cmd, customConfigPath)
}

stdOut, stdErr, _, err := frogio.RunCmdWithOutputParser(cmd, false)
if err != nil {
if strings.Contains(stdOut+stdErr, "Unable to find") {
if strings.Contains(stdOut+stdErr, "Unable to find") || strings.Contains(stdOut+stdErr, "does not exist") {
return nil
}
return fmt.Errorf("failed to remove source: %w\n%s", err, strings.TrimSpace(stdOut+stdErr))
return errorutils.CheckErrorf("failed to remove source: %s", err.Error())
}
return nil
}

// GetConfigPathFromEnvIfProvided returns the path to the custom NuGet.Config file if it was provided by the user.
func GetConfigPathFromEnvIfProvided(cmdType dotnet.ToolchainType) string {
if cmdType == dotnet.DotnetCore {
if customDotnetDir := os.Getenv("DOTNET_CLI_HOME"); customDotnetDir != "" {
return filepath.Join(customDotnetDir, "NuGet.Config")
}
}
return os.Getenv("NUGET_CONFIG_FILE")
}

// CreateConfigFileIfNeeded creates a new config file if it does not exist.
func CreateConfigFileIfNeeded(customConfigPath string) error {
// Ensure the file exists
exists, err := fileutils.IsFileExists(customConfigPath, false)
if err != nil || exists {
return err
}
// If the file does not exist, create it
if err = os.MkdirAll(filepath.Dir(customConfigPath), 0755); err != nil {
return err
}
// Write the default config content to the file
return os.WriteFile(customConfigPath, []byte("<configuration></configuration>"), 0644)
}

func addConfigFileFlag(cmd *dotnet.Cmd, configFilePath string) {
// Add the config file flag if needed.
if cmd.GetToolchain() == dotnet.DotnetCore {
cmd.CommandFlags = append(cmd.CommandFlags, "--configfile", configFilePath)
} else {
cmd.CommandFlags = append(cmd.CommandFlags, "-ConfigFile", configFilePath)
}
}

// Checks if the user provided input such as -configfile flag or -Source flag.
// If those flags were provided, NuGet will use the provided configs (default config file or the one with -configfile)
// If neither provided, we are initializing our own config.
Expand Down
122 changes: 122 additions & 0 deletions artifactory/commands/dotnet/dotnetcommand_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dotnet

import (
"github.com/jfrog/jfrog-cli-core/v2/utils/ioutils"
"os"
"path/filepath"
"reflect"
Expand Down Expand Up @@ -223,3 +224,124 @@ func createNewDotnetModule(t *testing.T, tmpDir string) *build.DotnetModule {
assert.NoError(t, err)
return module
}

func TestGetConfigPathFromEnvIfProvided(t *testing.T) {
testCases := []struct {
name string
mockEnv map[string]string
cmdType dotnet.ToolchainType
expectedPath string
}{
{
name: "DotnetCore with DOTNET_CLI_HOME",
mockEnv: map[string]string{
"DOTNET_CLI_HOME": "/custom/dotnet",
},
cmdType: dotnet.DotnetCore,
expectedPath: "/custom/dotnet/NuGet.Config",
},
{
name: "NuGet with NUGET_CONFIG_FILE",
mockEnv: map[string]string{
"NUGET_CONFIG_FILE": "/custom/nuget.config",
},
cmdType: dotnet.Nuget,
expectedPath: "/custom/nuget.config",
},
{
name: "No env variable",
mockEnv: map[string]string{},
cmdType: dotnet.Nuget,
expectedPath: "",
},
}

// Test the function with different environment variable settings
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Setenv("DOTNET_CLI_HOME", testCase.mockEnv["DOTNET_CLI_HOME"])

// Set other environment variables if needed
if testCase.mockEnv["NUGET_CONFIG_FILE"] != "" {
t.Setenv("NUGET_CONFIG_FILE", testCase.mockEnv["NUGET_CONFIG_FILE"])
}
result := GetConfigPathFromEnvIfProvided(testCase.cmdType)
assert.Equal(t, testCase.expectedPath, ioutils.WinToUnixPathSeparator(result))
})
}
}

func TestCreateConfigFileIfNeeded(t *testing.T) {
testCases := []struct {
name string
configPath string
fileExists bool
expectedError error
}{
{
name: "File does not exist, create file with default content",
configPath: "/custom/path/NuGet.Config",
fileExists: false,
},
{
name: "File exists, no changes",
configPath: "/custom/path/NuGet.Config",
fileExists: true,
},
}

// Setup for testing file existence and creation
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
configPath := filepath.Join(t.TempDir(), testCase.configPath)
if testCase.fileExists {
assert.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0777))
assert.NoError(t, os.WriteFile(configPath, []byte{}, 0644))
}
err := CreateConfigFileIfNeeded(configPath)
assert.NoError(t, err)

if !testCase.fileExists {
// Read the content of the file
content, err := os.ReadFile(configPath)
assert.NoError(t, err)

// Assert the content is the default config content
assert.Equal(t, "<configuration></configuration>", string(content))
}
})
}
}

func TestAddConfigFileFlag(t *testing.T) {
testCases := []struct {
name string
toolchainType dotnet.ToolchainType
expectedFlags []string
}{
{
name: "DotnetCore toolchain",
toolchainType: dotnet.DotnetCore,
expectedFlags: []string{"--configfile", "/path/to/NuGet.Config"},
},
{
name: "NuGet toolchain",
toolchainType: dotnet.Nuget,
expectedFlags: []string{"-ConfigFile", "/path/to/NuGet.Config"},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Create a mock command object
cmd, err := dotnet.NewToolchainCmd(testCase.toolchainType)
assert.NoError(t, err)

// Call the function
addConfigFileFlag(cmd, "/path/to/NuGet.Config")

// Assert that the flags are as expected
assert.Equal(t, testCase.expectedFlags, cmd.CommandFlags)
})
}
}
12 changes: 6 additions & 6 deletions artifactory/commands/golang/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func TestGetArtifactoryApiUrl(t *testing.T) {
}

func TestGoProxyUrlParams_BuildUrl(t *testing.T) {
tests := []struct {
testCases := []struct {
name string
RepoName string
Direct bool
Expand All @@ -153,15 +153,15 @@ func TestGoProxyUrlParams_BuildUrl(t *testing.T) {
ExpectedUrl: "https://test/prefix/api/go/go",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
remoteUrl, err := url.Parse("https://test")
require.NoError(t, err)
gdu := &GoProxyUrlParams{
Direct: tt.Direct,
EndpointPrefix: tt.EndpointPrefix,
Direct: testCase.Direct,
EndpointPrefix: testCase.EndpointPrefix,
}
assert.Equalf(t, tt.ExpectedUrl, gdu.BuildUrl(remoteUrl, tt.RepoName), "BuildUrl(%v, %v)", remoteUrl, tt.RepoName)
assert.Equalf(t, testCase.ExpectedUrl, gdu.BuildUrl(remoteUrl, testCase.RepoName), "BuildUrl(%v, %v)", remoteUrl, testCase.RepoName)
})
}
}
12 changes: 12 additions & 0 deletions artifactory/commands/python/pip.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package python

import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"

"github.com/jfrog/build-info-go/entities"
"github.com/jfrog/build-info-go/utils/pythonutils"
Expand Down Expand Up @@ -46,6 +49,15 @@ func (pc *PipCommand) SetCommandName(commandName string) *PipCommand {
return pc
}

func CreatePipConfigManually(customPipConfigPath, repoWithCredsUrl string) error {
if err := os.MkdirAll(filepath.Dir(customPipConfigPath), os.ModePerm); err != nil {
return err
}
// Write the configuration to pip.conf.
configContent := fmt.Sprintf("[global]\nindex-url = %s\n", repoWithCredsUrl)
return os.WriteFile(customPipConfigPath, []byte(configContent), 0644)
}

func (pc *PipCommand) CommandName() string {
return "rt_python_pip"
}
Expand Down
27 changes: 27 additions & 0 deletions artifactory/commands/python/pip_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package python

import (
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)

func TestCreatePipConfigManually(t *testing.T) {
// Define the test parameters
customConfigPath := filepath.Join(t.TempDir(), "/tmp/test/pip.conf")
// #nosec G101 -- False positive - no hardcoded credentials.
repoWithCredsUrl := "https://example.com/simple/"
expectedContent := "[global]\nindex-url = https://example.com/simple/\n"

// Call the function under test
err := CreatePipConfigManually(customConfigPath, repoWithCredsUrl)

// Assert no error occurred
assert.NoError(t, err)

// Verify the file exists and has the correct content
fileContent, err := os.ReadFile(customConfigPath)
assert.NoError(t, err)
assert.Equal(t, expectedContent, string(fileContent))
}
10 changes: 5 additions & 5 deletions artifactory/commands/python/python_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestGetPypiRepoUrlWithCredentials(t *testing.T) {
tests := []struct {
testCases := []struct {
name string
curationCmd bool
}{
Expand All @@ -24,11 +24,11 @@ func TestGetPypiRepoUrlWithCredentials(t *testing.T) {
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
url, _, _, err := GetPypiRepoUrlWithCredentials(&config.ServerDetails{}, "test", tt.curationCmd)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
url, _, _, err := GetPypiRepoUrlWithCredentials(&config.ServerDetails{}, "test", testCase.curationCmd)
require.NoError(t, err)
assert.Equal(t, tt.curationCmd, strings.Contains(url.Path, coreutils.CurationPassThroughApi))
assert.Equal(t, testCase.curationCmd, strings.Contains(url.Path, coreutils.CurationPassThroughApi))
})
}
}
Loading

0 comments on commit 96fa7ce

Please sign in to comment.