Skip to content

Commit b0f1a69

Browse files
committed
Support nested path for docker build
1 parent 5aff2b2 commit b0f1a69

File tree

3 files changed

+173
-2
lines changed

3 files changed

+173
-2
lines changed

docker_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,175 @@ func TestPushFatManifestImage(t *testing.T) {
396396
assert.True(t, totalResults > 1)
397397
}
398398

399+
// runNestedPathDockerBuildTest is a helper function for testing docker build --push with nested paths.
400+
// It handles common setup, build execution, validation, and cleanup.
401+
// platforms: empty string for single platform, or comma-separated platforms like "linux/amd64,linux/arm64"
402+
func runNestedPathDockerBuildTest(t *testing.T, buildNameSuffix, imageSuffix, nestedPath, platforms string) {
403+
buildName := buildNameSuffix + tests.DockerBuildName
404+
buildNumber := "1"
405+
406+
// Extract hostname from ContainerRegistry (remove protocol if present)
407+
registryHost := *tests.ContainerRegistry
408+
if parsedURL, err := url.Parse(registryHost); err == nil && parsedURL.Host != "" {
409+
registryHost = parsedURL.Host
410+
}
411+
412+
// Construct image name with nested path: repo/nestedPath/image
413+
nestedImageName := path.Join(registryHost, tests.OciLocalRepo, nestedPath, imageSuffix)
414+
imageTag := nestedImageName + ":v1"
415+
416+
// Create test workspace
417+
workspace, err := filepath.Abs(tests.Out)
418+
assert.NoError(t, err)
419+
assert.NoError(t, fileutils.CreateDirIfNotExist(workspace))
420+
421+
// Construct base image with hostname
422+
baseImage := path.Join(registryHost, tests.OciRemoteRepo, "alpine:latest")
423+
424+
// Create Dockerfile
425+
dockerfileContent := fmt.Sprintf(`FROM %s
426+
RUN echo "Built for nested path test"
427+
CMD ["echo", "Hello from nested path"]`, baseImage)
428+
429+
dockerfilePath := filepath.Join(workspace, "Dockerfile")
430+
assert.NoError(t, os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0644))
431+
432+
// Cleanup old build
433+
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
434+
defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
435+
436+
// Clean build before test
437+
runJfrogCli(t, "rt", "bc", buildName, buildNumber)
438+
439+
// Run docker build --push (single or multiplatform based on platforms parameter)
440+
if platforms != "" {
441+
runJfrogCli(t, "docker", "buildx", "build", "--platform", platforms,
442+
"-t", imageTag, "-f", dockerfilePath, "--push", "--build-name="+buildName, "--build-number="+buildNumber, workspace)
443+
} else {
444+
runJfrogCli(t, "docker", "build", "-t", imageTag, "-f", dockerfilePath, "--push",
445+
"--build-name="+buildName, "--build-number="+buildNumber, workspace)
446+
}
447+
448+
// Publish build info
449+
runJfrogCli(t, "rt", "build-publish", buildName, buildNumber)
450+
451+
// Validate the published build-info exists
452+
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber)
453+
assert.NoError(t, err)
454+
assert.True(t, found, "build info was expected to be found")
455+
assert.True(t, len(publishedBuildInfo.BuildInfo.Modules) >= 1, "Expected at least 1 module in build info")
456+
457+
// Validate build-name & build-number properties in all image layers at nested path
458+
searchSpec := spec.NewBuilder().Pattern(tests.OciLocalRepo + "/" + nestedPath + "/*").Build(buildName).Recursive(true).BuildSpec()
459+
searchCmd := generic.NewSearchCommand()
460+
searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpec)
461+
reader, err := searchCmd.Search()
462+
assert.NoError(t, err)
463+
totalResults, err := reader.Length()
464+
assert.NoError(t, err)
465+
assert.True(t, totalResults > 1, "Expected layers to be found at nested path "+nestedPath+"/")
466+
467+
// Cleanup image from Artifactory
468+
inttestutils.ContainerTestCleanup(t, serverDetails, artHttpDetails, nestedPath+"/"+imageSuffix, buildName, tests.OciLocalRepo)
469+
}
470+
471+
// TestDockerBuildPushWithNestedPath tests docker build --push with nested paths like repo/myorg/image.
472+
// This validates that layer fetching works correctly for single platform images with nested paths.
473+
func TestDockerBuildPushWithNestedPath(t *testing.T) {
474+
cleanup := initDockerBuildTest(t)
475+
defer cleanup()
476+
runNestedPathDockerBuildTest(t, "docker-build-nested", "test-single-nested", "myorg", "")
477+
}
478+
479+
// TestPushFatManifestImageWithNestedPath tests pushing fat-manifest (multi-platform) images with nested paths.
480+
// This validates that layer fetching works correctly for paths like <repository>/myorg/image
481+
// which was failing before the fix to FatManifestHandler.createSearchablePathForDockerManifestContents.
482+
func TestPushFatManifestImageWithNestedPath(t *testing.T) {
483+
cleanup := initDockerBuildTest(t)
484+
defer cleanup()
485+
runNestedPathDockerBuildTest(t, "push-fat-manifest-nested", "test-multiarch-nested", "myorg", "linux/amd64,linux/arm64")
486+
}
487+
488+
// TestDockerBuildWithNestedPathBaseImage tests that dependencies are correctly collected
489+
// when using a nested path image as a base layer in a Dockerfile.
490+
func TestDockerBuildWithNestedPathBaseImage(t *testing.T) {
491+
cleanup := initDockerBuildTest(t)
492+
defer cleanup()
493+
494+
// Extract hostname from ContainerRegistry
495+
registryHost := *tests.ContainerRegistry
496+
if parsedURL, err := url.Parse(registryHost); err == nil && parsedURL.Host != "" {
497+
registryHost = parsedURL.Host
498+
}
499+
500+
// Step 1: Push a base image to a nested path (myorg/base-image)
501+
baseImageBuildName := "base-nested" + tests.DockerBuildName
502+
baseImageBuildNumber := "1"
503+
nestedBasePath := "myorg"
504+
baseImageName := path.Join(registryHost, tests.OciLocalRepo, nestedBasePath, "base-image")
505+
baseImageTag := baseImageName + ":v1"
506+
507+
workspace, err := filepath.Abs(tests.Out)
508+
assert.NoError(t, err)
509+
assert.NoError(t, fileutils.CreateDirIfNotExist(workspace))
510+
511+
// Create base Dockerfile
512+
alpineBase := path.Join(registryHost, tests.OciRemoteRepo, "alpine:latest")
513+
baseDockerfile := fmt.Sprintf(`FROM %s
514+
RUN echo "This is the nested base image"
515+
CMD ["echo", "base"]`, alpineBase)
516+
517+
baseDockerfilePath := filepath.Join(workspace, "Dockerfile.base")
518+
assert.NoError(t, os.WriteFile(baseDockerfilePath, []byte(baseDockerfile), 0644))
519+
520+
// Push base image to nested path
521+
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, baseImageBuildName, artHttpDetails)
522+
defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, baseImageBuildName, artHttpDetails)
523+
524+
runJfrogCli(t, "rt", "bc", baseImageBuildName, baseImageBuildNumber)
525+
runJfrogCli(t, "docker", "build", "-t", baseImageTag, "-f", baseDockerfilePath, "--push",
526+
"--build-name="+baseImageBuildName, "--build-number="+baseImageBuildNumber, workspace)
527+
runJfrogCli(t, "rt", "build-publish", baseImageBuildName, baseImageBuildNumber)
528+
529+
// Step 2: Build a new image using the nested path base image
530+
childBuildName := "child-nested" + tests.DockerBuildName
531+
childBuildNumber := "1"
532+
childImageName := path.Join(registryHost, tests.OciLocalRepo, "child-image")
533+
childImageTag := childImageName + ":v1"
534+
535+
// Create child Dockerfile that uses the nested path base image
536+
childDockerfile := fmt.Sprintf(`FROM %s
537+
RUN echo "This is the child image using nested base"
538+
CMD ["echo", "child"]`, baseImageTag)
539+
540+
childDockerfilePath := filepath.Join(workspace, "Dockerfile.child")
541+
assert.NoError(t, os.WriteFile(childDockerfilePath, []byte(childDockerfile), 0644))
542+
543+
// Build child image
544+
inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, childBuildName, artHttpDetails)
545+
defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, childBuildName, artHttpDetails)
546+
547+
runJfrogCli(t, "rt", "bc", childBuildName, childBuildNumber)
548+
runJfrogCli(t, "docker", "build", "-t", childImageTag, "-f", childDockerfilePath, "--push",
549+
"--build-name="+childBuildName, "--build-number="+childBuildNumber, workspace)
550+
runJfrogCli(t, "rt", "build-publish", childBuildName, childBuildNumber)
551+
552+
// Step 3: Validate build info has dependencies from the nested path base image
553+
publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, childBuildName, childBuildNumber)
554+
assert.NoError(t, err)
555+
assert.True(t, found, "build info was expected to be found")
556+
assert.True(t, len(publishedBuildInfo.BuildInfo.Modules) >= 1, "Expected at least 1 module in build info")
557+
558+
// Check that dependencies exist (these come from the base image layers)
559+
module := publishedBuildInfo.BuildInfo.Modules[0]
560+
assert.True(t, len(module.Dependencies) > 0,
561+
"Expected dependencies from nested path base image (myorg/base-image). ")
562+
563+
// Cleanup
564+
inttestutils.ContainerTestCleanup(t, serverDetails, artHttpDetails, nestedBasePath+"/base-image", baseImageBuildName, tests.OciLocalRepo)
565+
inttestutils.ContainerTestCleanup(t, serverDetails, artHttpDetails, "child-image", childBuildName, tests.OciLocalRepo)
566+
}
567+
399568
func TestPushMultiTaggedImage(t *testing.T) {
400569
if !*tests.TestDocker {
401570
t.Skip("Skipping test. To run it, add the '-test.docker=true' option.")

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ replace github.com/gfleury/go-bitbucket-v1 => github.com/gfleury/go-bitbucket-v1
284284

285285
//replace github.com/jfrog/jfrog-cli-core/v2 => ../jfrog-cli-core
286286

287+
replace github.com/jfrog/jfrog-cli-artifactory => github.com/fluxxBot/jfrog-cli-artifactory v0.0.0-20260130044429-464a5025d08a
288+
287289
//replace github.com/jfrog/build-info-go => github.com/fluxxBot/build-info-go v1.10.10-0.20260105070825-d3f36f619ba5
288290

289291
//replace github.com/jfrog/jfrog-cli-core/v2 => github.com/fluxxBot/jfrog-cli-core/v2 v2.58.1-0.20260105065921-c6488910f44c

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
885885
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
886886
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
887887
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
888+
github.com/fluxxBot/jfrog-cli-artifactory v0.0.0-20260130044429-464a5025d08a h1:QpKxvaVVlIKA/4EPg2RnN+o9UfTVWs3gw/mb5V2uCbo=
889+
github.com/fluxxBot/jfrog-cli-artifactory v0.0.0-20260130044429-464a5025d08a/go.mod h1:ANFZOB4AX+Voo24l1BO8bVvN76m3ZViR1QaK5u3QDgE=
888890
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
889891
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
890892
github.com/forPelevin/gomoji v1.4.1 h1:7U+Bl8o6RV/dOQz7coQFWj/jX6Ram6/cWFOuFDEPEUo=
@@ -1216,8 +1218,6 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL
12161218
github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w=
12171219
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260107143435-b30ede954432 h1:6eOOs+326IrWLVA+ghDDudJmN3Nt8lxPl11eOu8vtPE=
12181220
github.com/jfrog/jfrog-cli-application v1.0.2-0.20260107143435-b30ede954432/go.mod h1:xum2HquWO5uExa/A7MQs3TgJJVEeoqTR+6Z4mfBr1Xw=
1219-
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260129054930-035e3ed7e30d h1:jyBD9kqAL8eHerZvGmxiTZECA+sYuH3TpodGnnpnPSs=
1220-
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260129054930-035e3ed7e30d/go.mod h1:ANFZOB4AX+Voo24l1BO8bVvN76m3ZViR1QaK5u3QDgE=
12211221
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260112010739-87fc7275623c h1:K9anqOZ7ASxlsijsl9u4jh92wqqIvJA4kTYfXrcOmJA=
12221222
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260112010739-87fc7275623c/go.mod h1:+Hnaikp/xCSPD/q7txxRy4Zc0wzjW/usrCSf+6uONSQ=
12231223
github.com/jfrog/jfrog-cli-evidence v0.8.3-0.20251225153025-9d8ac181d615 h1:y5an0bojHL00ipHP1QuBUrVcP+XK+yZHHOJ/r1I0RUM=

0 commit comments

Comments
 (0)