Skip to content

Commit f12dfb4

Browse files
authored
Enhance Gradle init script handling by querying Java's user home (#343)
1 parent 12090b4 commit f12dfb4

File tree

2 files changed

+180
-1
lines changed

2 files changed

+180
-1
lines changed

artifactory/commands/gradle/gradle.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838

3939
UserHomeEnv = "GRADLE_USER_HOME"
4040
InitScriptName = "jfrog.init.gradle"
41+
javaUserHome = "user.home"
4142
)
4243

4344
type GradleCommand struct {
@@ -383,7 +384,14 @@ func GenerateInitScript(config InitScriptAuthConfig) (string, error) {
383384
func WriteInitScript(initScript string) error {
384385
gradleHome := os.Getenv(UserHomeEnv)
385386
if gradleHome == "" {
386-
gradleHome = filepath.Join(clientutils.GetUserHomeDir(), ".gradle")
387+
// Try Java's user.home first (fixes container issue where $HOME != user.home)
388+
if javaHome, err := getJavaUserHome(); err == nil && javaHome != "" {
389+
log.Debug("Using Java user.home for Gradle:", javaHome)
390+
gradleHome = filepath.Join(javaHome, ".gradle")
391+
} else {
392+
// Fall back to $HOME if Java is not available
393+
gradleHome = filepath.Join(clientutils.GetUserHomeDir(), ".gradle")
394+
}
387395
}
388396
// Sanitize the path to prevent directory traversal attacks
389397
gradleHome = filepath.Clean(gradleHome)
@@ -399,6 +407,32 @@ func WriteInitScript(initScript string) error {
399407
return nil
400408
}
401409

410+
// getJavaUserHome queries Java for its user.home system property.
411+
// Gradle uses this property (not $HOME) to determine where to look for init scripts.
412+
// This fixes issues in containers where $HOME and Java's user.home can differ.
413+
func getJavaUserHome() (string, error) {
414+
cmd := exec.Command("java", "-XshowSettings:properties", "-version")
415+
output, err := cmd.CombinedOutput()
416+
if err != nil {
417+
return "", fmt.Errorf("failed to run java: %w", err)
418+
}
419+
return parseUserHomeFromJavaOutput(string(output))
420+
}
421+
422+
// parseUserHomeFromJavaOutput extracts the user.home property from Java's -XshowSettings:properties output.
423+
// This is separated from getJavaUserHome for unit testing purposes.
424+
func parseUserHomeFromJavaOutput(output string) (string, error) {
425+
for _, line := range strings.Split(output, "\n") {
426+
if strings.Contains(line, javaUserHome) {
427+
parts := strings.SplitN(line, "=", 2)
428+
if len(parts) == 2 {
429+
return strings.TrimSpace(parts[1]), nil
430+
}
431+
}
432+
}
433+
return "", fmt.Errorf("user.home not found in java output")
434+
}
435+
402436
func runGradle(vConfig *viper.Viper, tasks []string, deployableArtifactsFile string, configuration *build.BuildConfiguration, threads int, disableDeploy bool) error {
403437
buildInfoService := build.CreateBuildInfoService()
404438
buildName, err := configuration.GetBuildName()

artifactory/commands/gradle/gradle_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,151 @@ func TestExtractBuildFilePath(t *testing.T) {
170170
}
171171
}
172172

173+
// TestParseUserHomeFromJavaOutput tests the parsing of user.home from Java output
174+
func TestParseUserHomeFromJavaOutput(t *testing.T) {
175+
tests := []struct {
176+
name string
177+
input string
178+
expected string
179+
expectError bool
180+
}{
181+
{
182+
name: "valid output with user.home",
183+
input: `Property settings:
184+
file.encoding = UTF-8
185+
java.home = /opt/java
186+
user.dir = /home/user/project
187+
user.home = /home/user
188+
user.name = testuser`,
189+
expected: "/home/user",
190+
expectError: false,
191+
},
192+
{
193+
name: "valid output with spaces around equals",
194+
input: `Property settings:
195+
user.home = /Users/developer
196+
user.name = dev`,
197+
expected: "/Users/developer",
198+
expectError: false,
199+
},
200+
{
201+
name: "valid output with Windows path",
202+
input: "Property settings:\n user.home = C:\\Users\\Developer\n user.name = dev",
203+
expected: "C:\\Users\\Developer",
204+
expectError: false,
205+
},
206+
{
207+
name: "valid output with root user (container scenario)",
208+
input: `Property settings:
209+
user.home = /root
210+
user.name = root`,
211+
expected: "/root",
212+
expectError: false,
213+
},
214+
{
215+
name: "empty output",
216+
input: "",
217+
expected: "",
218+
expectError: true,
219+
},
220+
{
221+
name: "output without user.home",
222+
input: `Property settings:
223+
java.home = /opt/java
224+
user.name = testuser`,
225+
expected: "",
226+
expectError: true,
227+
},
228+
{
229+
name: "malformed line without equals",
230+
input: "user.home /home/user",
231+
expected: "",
232+
expectError: true,
233+
},
234+
}
235+
236+
for _, tt := range tests {
237+
t.Run(tt.name, func(t *testing.T) {
238+
result, err := parseUserHomeFromJavaOutput(tt.input)
239+
if tt.expectError {
240+
assert.Error(t, err)
241+
} else {
242+
assert.NoError(t, err)
243+
assert.Equal(t, tt.expected, result)
244+
}
245+
})
246+
}
247+
}
248+
249+
// TestWriteInitScriptUsesJavaUserHome specifically tests the new case where
250+
// Java's user.home is used instead of $HOME when GRADLE_USER_HOME is not set.
251+
// This is the fix for container environments where $HOME differs from Java's user.home.
252+
func TestWriteInitScriptUsesJavaUserHome(t *testing.T) {
253+
// Get Java's user.home - skip if Java is not available
254+
javaHome, err := getJavaUserHome()
255+
if err != nil {
256+
t.Skip("Java not available, skipping test")
257+
}
258+
259+
// Ensure GRADLE_USER_HOME is NOT set so we exercise the Java user.home path
260+
t.Setenv(UserHomeEnv, "")
261+
262+
// Set $HOME to a DIFFERENT temp directory to simulate container environment
263+
// where $HOME differs from Java's user.home
264+
fakeHome := t.TempDir()
265+
t.Setenv("HOME", fakeHome)
266+
267+
initScript := "test init script for java user.home case"
268+
269+
err = WriteInitScript(initScript)
270+
assert.NoError(t, err)
271+
272+
// Verify: init script should be in Java's user.home, NOT in fake $HOME
273+
expectedPath := filepath.Join(javaHome, ".gradle", "init.d", InitScriptName)
274+
wrongPath := filepath.Join(fakeHome, ".gradle", "init.d", InitScriptName)
275+
276+
// Should exist in Java user.home (the correct location)
277+
content, err := os.ReadFile(expectedPath)
278+
assert.NoError(t, err, "Init script should be written to Java user.home: %s", expectedPath)
279+
assert.Equal(t, initScript, string(content))
280+
281+
// Should NOT exist in fake $HOME (this was the bug!)
282+
_, err = os.Stat(wrongPath)
283+
assert.True(t, os.IsNotExist(err), "Init script should NOT be written to $HOME: %s", wrongPath)
284+
285+
// Cleanup - use t.Cleanup for deferred cleanup
286+
t.Cleanup(func() {
287+
_ = os.Remove(expectedPath)
288+
})
289+
}
290+
291+
// TestWriteInitScriptFallsBackToHome tests that WriteInitScript falls back to $HOME
292+
// when both GRADLE_USER_HOME is not set AND Java is not available
293+
func TestWriteInitScriptFallsBackToHome(t *testing.T) {
294+
// Ensure GRADLE_USER_HOME is NOT set
295+
t.Setenv(UserHomeEnv, "")
296+
297+
// Set a known HOME for the fallback case
298+
tempDir := t.TempDir()
299+
t.Setenv("HOME", tempDir)
300+
301+
// Temporarily modify PATH to ensure Java is not found
302+
// This simulates an environment where Java is not installed
303+
// Note: t.Setenv automatically restores the original value after the test
304+
t.Setenv("PATH", "/nonexistent")
305+
306+
initScript := "test init script for HOME fallback case"
307+
308+
err := WriteInitScript(initScript)
309+
assert.NoError(t, err)
310+
311+
// Verify the init script was written to $HOME fallback location
312+
fallbackPath := filepath.Join(tempDir, ".gradle", "init.d", InitScriptName)
313+
content, err := os.ReadFile(fallbackPath)
314+
assert.NoError(t, err, "Init script should be written to $HOME fallback: %s", fallbackPath)
315+
assert.Equal(t, initScript, string(content))
316+
}
317+
173318
// TestExtractBuildFilePathWindowsPaths tests Windows-style paths if on Windows
174319
func TestExtractBuildFilePathWindowsPaths(t *testing.T) {
175320
if runtime.GOOS != "windows" {

0 commit comments

Comments
 (0)