diff --git a/artifactory/commands/gradle/gradle.go b/artifactory/commands/gradle/gradle.go index 042214b79..45593ff9b 100644 --- a/artifactory/commands/gradle/gradle.go +++ b/artifactory/commands/gradle/gradle.go @@ -1,6 +1,7 @@ package gradle import ( + _ "embed" "fmt" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/generic" commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" @@ -15,9 +16,15 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/spf13/viper" + "os" "path/filepath" + "strings" + "text/template" ) +//go:embed resources/init.gradle +var gradleInitScript string + const ( usePlugin = "useplugin" useWrapper = "usewrapper" @@ -231,6 +238,80 @@ func (gc *GradleCommand) setResult(result *commandsutils.Result) *GradleCommand return gc } +type InitScriptAuthConfig struct { + ArtifactoryURL string + ArtifactoryRepositoryKey string + ArtifactoryUsername string + ArtifactoryAccessToken string +} + +// GenerateInitScript generates a Gradle init script with the provided authentication configuration. +func GenerateInitScript(config InitScriptAuthConfig) (string, error) { + tmpl, err := template.New("gradleTemplate").Parse(gradleInitScript) + if err != nil { + return "", fmt.Errorf("failed to parse Gradle init script template: %s", err) + } + + var result strings.Builder + err = tmpl.Execute(&result, config) + if err != nil { + return "", fmt.Errorf("failed to execute Gradle init script template: %s", err) + } + + return result.String(), nil +} + +// WriteInitScriptWithBackup write the Gradle init script to the Gradle user home directory. +// If init scripts already exists, they will be backed up. +// Allows the user to interactively decide whether to overwrite existing init scripts. +func WriteInitScriptWithBackup(initScript string, interactUser bool) error { + gradleHome := os.Getenv("GRADLE_USER_HOME") + if gradleHome == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get user home directory: %w", err) + } + gradleHome = filepath.Join(homeDir, ".gradle") + } + initScripts, err := getExistingGradleInitScripts(gradleHome) + if err != nil { + return err + } + if len(initScripts) > 0 && interactUser { + toContinue := coreutils.AskYesNo("Existing Gradle init scripts have been found. Do you want to overwrite them?", false) + if !toContinue { + return nil + } + } + if err = backupExistingGradleInitScripts(initScripts); err != nil { + return err + } + initScriptPath := filepath.Join(gradleHome, "init.gradle") + if err = os.WriteFile(initScriptPath, []byte(initScript), 0644); err != nil { + return fmt.Errorf("failed to write Gradle init script to %s: %w", initScriptPath, err) + } + return nil +} + +func getExistingGradleInitScripts(gradleHome string) ([]string, error) { + gradleInitScripts, err := filepath.Glob(filepath.Join(gradleHome, "init.gradle*")) + if err != nil { + return nil, fmt.Errorf("failed while searching for Gradle init scripts: %w", err) + } + return gradleInitScripts, nil +} + +// backupExistingGradleInitScripts backup existing Gradle init scripts in the Gradle user home directory. +func backupExistingGradleInitScripts(gradleInitScripts []string) error { + for _, script := range gradleInitScripts { + backupPath := script + ".bak" + if err := os.Rename(script, backupPath); err != nil { + return fmt.Errorf("failed to backup Gradle init script %s: %w", script, err) + } + } + return nil +} + func runGradle(vConfig *viper.Viper, tasks []string, deployableArtifactsFile string, configuration *build.BuildConfiguration, threads int, disableDeploy bool) error { buildInfoService := build.CreateBuildInfoService() buildName, err := configuration.GetBuildName() diff --git a/artifactory/commands/gradle/gradle_test.go b/artifactory/commands/gradle/gradle_test.go new file mode 100644 index 000000000..2697953dc --- /dev/null +++ b/artifactory/commands/gradle/gradle_test.go @@ -0,0 +1,73 @@ +package gradle + +import ( + "github.com/stretchr/testify/require" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateInitScript(t *testing.T) { + config := InitScriptAuthConfig{ + ArtifactoryURL: "http://example.com/artifactory", + ArtifactoryRepositoryKey: "example-repo", + ArtifactoryUsername: "user", + ArtifactoryAccessToken: "token", + } + script, err := GenerateInitScript(config) + assert.NoError(t, err) + assert.Contains(t, script, "http://example.com/artifactory") + assert.Contains(t, script, "example-repo") + assert.Contains(t, script, "user") + assert.Contains(t, script, "token") +} + +func TestWriteInitScriptWithBackup(t *testing.T) { + tests := []struct { + name string + existingScript bool + expectedBackup bool + }{ + {name: "No existing init.gradle", existingScript: false, expectedBackup: false}, + {name: "Existing init.gradle", existingScript: true, expectedBackup: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up a temporary directory to act as the Gradle user home + tempDir := t.TempDir() + require.NoError(t, os.Setenv("GRADLE_USER_HOME", tempDir)) + defer func() { + assert.NoError(t, os.Unsetenv("GRADLE_USER_HOME")) + }() + + // Create a dummy init script + initScript := "init script content" + + if tt.existingScript { + existingScriptPath := filepath.Join(tempDir, "init.gradle") + require.NoError(t, os.WriteFile(existingScriptPath, []byte("existing content"), 0644)) + } + + // Call the function + err := WriteInitScriptWithBackup(initScript, false) + assert.NoError(t, err) + + // Verify the init script was written to the correct location + initScriptPath := filepath.Join(tempDir, "init.gradle") + content, err := os.ReadFile(initScriptPath) + assert.NoError(t, err) + assert.Equal(t, initScript, string(content)) + + // Verify backup if there was an existing script + if tt.expectedBackup { + backupScriptPath := initScriptPath + ".bak" + backupContent, err := os.ReadFile(backupScriptPath) + assert.NoError(t, err) + assert.Equal(t, "existing content", string(backupContent)) + } + }) + } +} diff --git a/artifactory/commands/gradle/resources/init.gradle b/artifactory/commands/gradle/resources/init.gradle new file mode 100644 index 000000000..ddb05770a --- /dev/null +++ b/artifactory/commands/gradle/resources/init.gradle @@ -0,0 +1,31 @@ +def artifactoryUrl = '{{ .ArtifactoryURL }}' +def artifactoryRepoKey = '{{ .ArtifactoryRepositoryKey }}' +def artifactoryUsername = '{{ .ArtifactoryUsername }}' +def artifactoryAccessToken = '{{ .ArtifactoryAccessToken }}' + +gradle.settingsEvaluated { settings -> + settings.pluginManagement { + repositories { + maven { + url "${artifactoryUrl}/${artifactoryRepoKey}" + credentials { + username = artifactoryUsername + password = artifactoryAccessToken + } + } + gradlePluginPortal() // Fallback to Gradle Plugin Portal + } + } +} + +allprojects { project -> + project.repositories { + maven { + url "${artifactoryUrl}/${artifactoryRepoKey}" + credentials { + username = artifactoryUsername + password = artifactoryAccessToken + } + } + } +} \ No newline at end of file diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index 213d8f62f..c64cd652b 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -1,11 +1,13 @@ package setup import ( + _ "embed" "fmt" bidotnet "github.com/jfrog/build-info-go/build/utils/dotnet" biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/dotnet" gocommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/golang" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/gradle" pythoncommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" @@ -17,12 +19,14 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" "golang.org/x/exp/maps" "net/url" "os" "slices" + "strings" ) // packageManagerToRepositoryPackageType maps project types to corresponding Artifactory repository package types. @@ -46,6 +50,8 @@ var packageManagerToRepositoryPackageType = map[project.ProjectType]string{ project.Podman: repository.Docker, project.Go: repository.Go, + + project.Gradle: repository.Gradle, } // SetupCommand configures registries and authentication for various package manager (npm, Yarn, Pip, Pipenv, Poetry, Go) @@ -147,6 +153,8 @@ func (sc *SetupCommand) Run() (err error) { err = sc.configureDotnetNuget() case project.Docker, project.Podman: err = sc.configureContainer() + case project.Gradle: + err = sc.configureGradle() default: err = errorutils.CheckErrorf("unsupported package manager: %s", sc.packageManager) } @@ -349,3 +357,25 @@ func (sc *SetupCommand) configureContainer() error { containerManagerType, ) } + +// configureGradle configures Gradle to use the specified Artifactory repository. +func (sc *SetupCommand) configureGradle() error { + password := sc.serverDetails.GetPassword() + username := sc.serverDetails.GetUser() + if sc.serverDetails.GetAccessToken() != "" { + password = sc.serverDetails.GetAccessToken() + username = auth.ExtractUsernameFromAccessToken(password) + } + initScriptAuthConfig := gradle.InitScriptAuthConfig{ + ArtifactoryURL: strings.TrimSuffix(sc.serverDetails.GetArtifactoryUrl(), "/"), + ArtifactoryRepositoryKey: sc.repoName, + ArtifactoryAccessToken: password, + ArtifactoryUsername: username, + } + initScript, err := gradle.GenerateInitScript(initScriptAuthConfig) + if err != nil { + return err + } + + return gradle.WriteInitScriptWithBackup(initScript, true) +}