mirror of
https://github.com/moby/buildkit.git
synced 2026-11-06 05:16:45 +00:00
Updated Coverage (provenance,mount,outline,exclude_patterns,and jobs)
Signed-off-by: Natnael Gebremariam <ngebremariam@microsoft.com>
This commit is contained in:
1 parent
c411f0ac55
commit
9ce4915231
5 files changed
+159
-60
No files matched your search
@@ -644,14 +644,22 @@ COPY --from=base /combined.txt /
|
||||
test("updated\n")
|
||||
}
|
||||
|
||||
// moby/buildkit#5566
|
||||
// testCacheMountParallel checks that two build stages sharing nested cache
|
||||
// mounts (target=/foo/bar and target=/foo/bar/baz) can run side-by-side. It
|
||||
// re-runs the build 20 times to surface the runc mkdir bug on nested cache
|
||||
// mounts fixed in runc 1.2.3. Regression test for moby/buildkit#5566.
|
||||
func testCacheMountParallel(t *testing.T, sb integration.Sandbox) {
|
||||
// flaky on WS2025
|
||||
integration.SkipOnPlatform(t, "windows")
|
||||
// Skipped on Windows (seen on Windows Server 2025). Starting many containers
|
||||
// in quick succession overloads the host, which fails with "Insufficient
|
||||
// system resources". That's transient host overload, not the #5566 bug this
|
||||
// test guards against (a deterministic mkdir failure setting up the nested
|
||||
// cache mounts). The WCOW daemon should throttle here but doesn't yet, so we
|
||||
// skip rather than mask the gap with in-test retries.
|
||||
integration.SkipOnPlatform(t, "windows", "Insufficient system resources error from creating many containers in quick succession")
|
||||
|
||||
f := getFrontend(t, sb)
|
||||
|
||||
dockerfile := []byte(integration.UnixOrWindows(
|
||||
`
|
||||
dockerfile := []byte(`
|
||||
FROM alpine AS b1
|
||||
RUN --mount=type=cache,target=/foo/bar --mount=type=cache,target=/foo/bar/baz echo 1
|
||||
|
||||
@@ -661,21 +669,7 @@ RUN --mount=type=cache,target=/foo/bar --mount=type=cache,target=/foo/bar/baz ec
|
||||
FROM scratch
|
||||
COPY --from=b1 /etc/passwd p1
|
||||
COPY --from=b2 /etc/passwd p2
|
||||
`,
|
||||
`
|
||||
FROM nanoserver AS b1
|
||||
USER ContainerAdministrator
|
||||
RUN --mount=type=cache,target=/foo/bar --mount=type=cache,target=/foo/bar/baz echo 1
|
||||
|
||||
FROM nanoserver AS b2
|
||||
USER ContainerAdministrator
|
||||
RUN --mount=type=cache,target=/foo/bar --mount=type=cache,target=/foo/bar/baz echo 2
|
||||
|
||||
FROM nanoserver
|
||||
COPY --from=b1 /License.txt p1
|
||||
COPY --from=b2 /License.txt p2
|
||||
`,
|
||||
))
|
||||
`)
|
||||
|
||||
dir := integration.Tmpdir(
|
||||
t,
|
||||
|
||||
@@ -191,15 +191,28 @@ FROM second
|
||||
require.True(t, called)
|
||||
}
|
||||
|
||||
// testOutlineSecrets checks that BuildKit can look at a Dockerfile and correctly
|
||||
// list the secrets and SSH mounts that a given build target needs.
|
||||
//
|
||||
// The outline feature only reads the Dockerfile — it never actually runs the build.
|
||||
// So this test makes sure that:
|
||||
// - Only secrets/SSH used by the target stage (and its dependencies) are listed.
|
||||
// Secrets and SSH declared in unrelated stages are ignored.
|
||||
// - Each secret and SSH entry has the right name, required flag, and line number.
|
||||
// - Build args inside mount ids (like id=second${BAR}) get expanded correctly.
|
||||
//
|
||||
// Because nothing is executed, the Linux and Windows versions of the Dockerfile
|
||||
// only differ in cosmetic ways (base images and the dummy command). The line
|
||||
// numbers are kept identical so the same assertions work on both platforms.
|
||||
func testOutlineSecrets(t *testing.T, sb integration.Sandbox) {
|
||||
integration.SkipOnPlatform(t, "windows")
|
||||
workers.CheckFeatureCompat(t, sb, workers.FeatureFrontendOutline)
|
||||
f := getFrontend(t, sb)
|
||||
if _, ok := f.(*clientFrontend); !ok {
|
||||
t.Skip("only test with client frontend")
|
||||
}
|
||||
|
||||
dockerfile := []byte(`
|
||||
dockerfile := []byte(integration.UnixOrWindows(
|
||||
`
|
||||
FROM busybox AS first
|
||||
RUN --mount=type=secret,target=/etc/passwd,required=true --mount=type=ssh true
|
||||
|
||||
@@ -215,7 +228,25 @@ COPY --from=first /foo /
|
||||
RUN --mount=type=ssh,id=ssh3,required true
|
||||
|
||||
FROM second
|
||||
`)
|
||||
`,
|
||||
`
|
||||
FROM nanoserver AS first
|
||||
RUN --mount=type=secret,target=/etc/passwd,required=true --mount=type=ssh exit 0
|
||||
|
||||
FROM nanoserver AS second
|
||||
RUN --mount=type=secret,id=unused --mount=type=ssh,id=ssh2 exit 0
|
||||
|
||||
FROM nanoserver AS third
|
||||
ARG BAR
|
||||
RUN --mount=type=secret,id=second${BAR} exit 0
|
||||
|
||||
FROM third AS target
|
||||
COPY --from=first /License.txt /
|
||||
RUN --mount=type=ssh,id=ssh3,required exit 0
|
||||
|
||||
FROM second
|
||||
`,
|
||||
))
|
||||
|
||||
dir := integration.Tmpdir(
|
||||
t,
|
||||
|
||||
@@ -438,8 +438,17 @@ func testGitProvenanceAttestationSHA256(t *testing.T, sb integration.Sandbox) {
|
||||
testGitProvenanceAttestation(t, sb, "sha256")
|
||||
}
|
||||
|
||||
// testGitProvenanceAttestation checks that when a build is sourced from a Git
|
||||
// repository, the resulting provenance attestation records the Git URL, branch,
|
||||
// and commit SHA correctly, masks any credentials in the URL, and includes the
|
||||
// base image and Git source in the resolved dependencies.
|
||||
func testGitProvenanceAttestation(t *testing.T, sb integration.Sandbox, format string) {
|
||||
integration.SkipOnPlatform(t, "windows")
|
||||
// Skipped on Windows:
|
||||
// BuildKit's Git source handler fails to update submodules on Windows even when
|
||||
// the repo has no submodules. The error "failed to update submodules ... git stderr:"
|
||||
// occurs with empty stderr, indicating the submodule update command cannot execute
|
||||
// properly on Windows. Same root cause as testDockerfileFromGit.
|
||||
integration.SkipOnPlatform(t, "windows", "Git source handler submodule update not supported on Windows")
|
||||
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush, workers.FeatureProvenance)
|
||||
ctx := sb.Context()
|
||||
|
||||
@@ -786,13 +795,12 @@ RUN echo "ok-$TARGETARCH" > /foo
|
||||
}
|
||||
}
|
||||
|
||||
// testClientFrontendProvenance verifies that frontend provenance metadata is
|
||||
// correctly captured when a Dockerfile is built inside a client-side frontend.
|
||||
// Normally client frontends skip provenance because they run in the client, not
|
||||
// in BuildKit; this test ensures provenance is still recorded in that scenario.
|
||||
func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
integration.SkipOnPlatform(t, "windows")
|
||||
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush, workers.FeatureProvenance)
|
||||
// Building with client frontend does not capture frontend provenance
|
||||
// because frontend runs in client, not in BuildKit.
|
||||
// This test builds Dockerfile inside a client frontend ensuring that
|
||||
// in that case frontend provenance is captured.
|
||||
ctx := sb.Context()
|
||||
|
||||
c, err := client.New(ctx, sb.Address())
|
||||
@@ -800,6 +808,7 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
defer c.Close()
|
||||
|
||||
registry, err := sb.NewRegistry()
|
||||
|
||||
if errors.Is(err, integration.ErrRequirements) {
|
||||
t.Skip(err.Error())
|
||||
}
|
||||
@@ -807,6 +816,12 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
|
||||
target := registry + "/buildkit/clientprovenance:latest"
|
||||
|
||||
// On Windows, use windows/{arm64,amd64} platform IDs; the worker only
|
||||
// executes one platform natively but the IDs flow through provenance.
|
||||
armPlatformID := integration.UnixOrWindows("linux/arm64", "windows/arm64")
|
||||
amdPlatformID := integration.UnixOrWindows("linux/amd64", "windows/amd64")
|
||||
platformOS := integration.UnixOrWindows("linux", "windows")
|
||||
|
||||
f := getFrontend(t, sb)
|
||||
|
||||
_, isClient := f.(*clientFrontend)
|
||||
@@ -814,13 +829,26 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
t.Skip("not a client frontend")
|
||||
}
|
||||
|
||||
dockerfile := []byte(`
|
||||
// Windows uses nanoserver for both stages (no busybox/alpine equivalents);
|
||||
// USER ContainerAdministrator is required so cmd.exe can write to C:\.
|
||||
dockerfile := []byte(integration.UnixOrWindows(
|
||||
`
|
||||
FROM alpine as x86target
|
||||
RUN echo "alpine" > /foo
|
||||
|
||||
FROM busybox:latest AS armtarget
|
||||
RUN --network=none echo "bbox" > /foo
|
||||
`)
|
||||
`,
|
||||
`
|
||||
FROM nanoserver AS x86target
|
||||
USER ContainerAdministrator
|
||||
RUN echo alpine> /foo
|
||||
|
||||
FROM nanoserver AS armtarget
|
||||
USER ContainerAdministrator
|
||||
RUN --network=none echo bbox> /foo
|
||||
`,
|
||||
))
|
||||
dir := integration.Tmpdir(
|
||||
t,
|
||||
fstest.CreateFile("Dockerfile", dockerfile, 0600),
|
||||
@@ -868,19 +896,22 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Platform IDs (armPlatformID, amdPlatformID, platformOS) are captured
|
||||
// from the enclosing testClientFrontendProvenance scope so the result
|
||||
// assembly and the later assertions stay in sync.
|
||||
res := gateway.NewResult()
|
||||
res.AddRef("linux/arm64", res1.Ref)
|
||||
res.AddRef("linux/amd64", res2.Ref)
|
||||
res.AddRef(armPlatformID, res1.Ref)
|
||||
res.AddRef(amdPlatformID, res2.Ref)
|
||||
|
||||
pl, err := json.Marshal(exptypes.Platforms{
|
||||
Platforms: []exptypes.Platform{
|
||||
{
|
||||
ID: "linux/arm64",
|
||||
Platform: ocispecs.Platform{OS: "linux", Architecture: "arm64"},
|
||||
ID: armPlatformID,
|
||||
Platform: ocispecs.Platform{OS: platformOS, Architecture: "arm64"},
|
||||
},
|
||||
{
|
||||
ID: "linux/amd64",
|
||||
Platform: ocispecs.Platform{OS: "linux", Architecture: "amd64"},
|
||||
ID: amdPlatformID,
|
||||
Platform: ocispecs.Platform{OS: platformOS, Architecture: "amd64"},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -918,11 +949,20 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 4, len(imgs.Images))
|
||||
|
||||
img := imgs.Find("linux/arm64")
|
||||
require.NotNil(t, img)
|
||||
require.Equal(t, []byte("bbox\n"), img.Layers[1]["foo"].Data)
|
||||
// nanoserver layer entries are under Files/; cmd echo writes CRLF.
|
||||
outFile := integration.UnixOrWindows("foo", "Files/foo")
|
||||
armData := integration.UnixOrWindows([]byte("bbox\n"), []byte("bbox\r\n"))
|
||||
amdData := integration.UnixOrWindows([]byte("alpine\n"), []byte("alpine\r\n"))
|
||||
// Both Windows stages are based on nanoserver, so the resolved base URI is
|
||||
// nanoserver for both platforms; Linux uses the original busybox/alpine.
|
||||
armBase := integration.UnixOrWindows("docker/busybox", "docker/nanoserver")
|
||||
amdBase := integration.UnixOrWindows("docker/alpine", "docker/nanoserver")
|
||||
|
||||
att := imgs.FindAttestation("linux/arm64")
|
||||
img := imgs.Find(armPlatformID)
|
||||
require.NotNil(t, img)
|
||||
require.Equal(t, armData, img.Layers[1][outFile].Data)
|
||||
|
||||
att := imgs.FindAttestation(armPlatformID)
|
||||
require.NotNil(t, att)
|
||||
require.Equal(t, "attestation-manifest", att.Desc.Annotations["vnd.docker.reference.type"])
|
||||
var attest intoto.Statement
|
||||
@@ -947,14 +987,14 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
|
||||
require.Equal(t, 2, len(pred.BuildDefinition.ExternalParameters.Request.Locals))
|
||||
require.Equal(t, 1, len(pred.BuildDefinition.ResolvedDependencies))
|
||||
require.Contains(t, pred.BuildDefinition.ResolvedDependencies[0].URI, "docker/busybox")
|
||||
require.Contains(t, pred.BuildDefinition.ResolvedDependencies[0].URI, armBase)
|
||||
|
||||
// amd64
|
||||
img = imgs.Find("linux/amd64")
|
||||
img = imgs.Find(amdPlatformID)
|
||||
require.NotNil(t, img)
|
||||
require.Equal(t, []byte("alpine\n"), img.Layers[1]["foo"].Data)
|
||||
require.Equal(t, amdData, img.Layers[1][outFile].Data)
|
||||
|
||||
att = imgs.FindAttestation("linux/amd64")
|
||||
att = imgs.FindAttestation(amdPlatformID)
|
||||
require.NotNil(t, att)
|
||||
require.Equal(t, "attestation-manifest", att.Desc.Annotations["vnd.docker.reference.type"])
|
||||
attest = intoto.Statement{}
|
||||
@@ -976,7 +1016,7 @@ func testClientFrontendProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
|
||||
require.Equal(t, 2, len(pred.BuildDefinition.ExternalParameters.Request.Locals))
|
||||
require.Equal(t, 1, len(pred.BuildDefinition.ResolvedDependencies))
|
||||
require.Contains(t, pred.BuildDefinition.ResolvedDependencies[0].URI, "docker/alpine")
|
||||
require.Contains(t, pred.BuildDefinition.ResolvedDependencies[0].URI, amdBase)
|
||||
}
|
||||
|
||||
func testGatewayBuiltinSyntaxSourceProvenance(t *testing.T, sb integration.Sandbox) {
|
||||
|
||||
@@ -25,9 +25,19 @@ func init() {
|
||||
allTests = append(allTests, excludedFilesTests...)
|
||||
}
|
||||
|
||||
// testExcludedFilesOnCopy verifies that COPY --exclude and ADD --exclude glob
|
||||
// patterns correctly filter out matching files during a Dockerfile build, while
|
||||
// preserving non-matching files with their expected content.
|
||||
// See #4439
|
||||
func testExcludedFilesOnCopy(t *testing.T, sb integration.Sandbox) {
|
||||
integration.SkipOnPlatform(t, "windows")
|
||||
// Skipped on Windows:
|
||||
// This test builds 8 stages at once (1 base + 6 builders + 1 final), and
|
||||
// every builder copies from the same base stage. On Windows, mounting that
|
||||
// shared parent layer from many stages in parallel hits a known container
|
||||
// bug (hcsshim::ActivateLayer fails with "file is being used by another
|
||||
// process"). That's unrelated to --exclude, so we skip the whole test rather
|
||||
// than weaken it.
|
||||
integration.SkipOnPlatform(t, "windows", "Windows snapshotter cannot mount the same parent layer from many stages in parallel (hcsshim::ActivateLayer race)")
|
||||
ctx := sb.Context()
|
||||
|
||||
c, err := client.New(ctx, sb.Address())
|
||||
|
||||
+37
-13
@@ -1,6 +1,7 @@
|
||||
package solver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -19,7 +20,9 @@ func init() {
|
||||
}
|
||||
|
||||
func TestJobsIntegration(t *testing.T) {
|
||||
mirrors := integration.WithMirroredImages(integration.OfficialImages("busybox:latest"))
|
||||
mirrors := integration.WithMirroredImages(integration.OfficialImages(
|
||||
integration.UnixOrWindows("busybox:latest", "nanoserver:latest"),
|
||||
))
|
||||
integration.Run(t, integration.TestFuncs(
|
||||
testParallelism,
|
||||
),
|
||||
@@ -31,31 +34,52 @@ func TestJobsIntegration(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
// testParallelism verifies that two independent builds can run concurrently by
|
||||
// using a shared cache mount for signaling. Each build creates a signal file and
|
||||
// polls for the other's signal. With unlimited parallelism both finish under 10s;
|
||||
// with single parallelism they run sequentially and exceed 10s.
|
||||
func testParallelism(t *testing.T, sb integration.Sandbox) {
|
||||
integration.SkipOnPlatform(t, "windows")
|
||||
ctx := sb.Context()
|
||||
|
||||
c, err := client.New(ctx, sb.Address())
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
||||
cacheMountSrc := integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
|
||||
// sharedDir is the single source of truth for the cache mount target.
|
||||
// Using it for both llb.AddMount's destination and the command strings
|
||||
// avoids relying on any implicit "/shared" -> "C:\shared" mapping in WCOW.
|
||||
sharedDir := integration.UnixOrWindows("/shared", `C:\shared`)
|
||||
cacheMount := llb.AddMount(
|
||||
"/shared", llb.Scratch(),
|
||||
sharedDir, cacheMountSrc,
|
||||
llb.AsPersistentCacheDir("shared", llb.CacheMountShared))
|
||||
run1 := llb.Image("busybox:latest").Run(
|
||||
llb.Args([]string{
|
||||
"/bin/sh", "-c",
|
||||
"touch /shared/signal1 && i=0; while [ ! -f /shared/signal2 ] && [ $i -lt 10 ]; do i=$((i+1)); sleep 1; done",
|
||||
}),
|
||||
|
||||
// On Linux, use sh to touch a signal file and poll for the peer's signal.
|
||||
// On Windows (nanoserver), use cmd with copy+ping to achieve the same
|
||||
// signaling: "copy nul" creates an empty file, and "ping -n 2 127.0.0.1"
|
||||
// provides a ~1-second delay per iteration.
|
||||
cmd1 := integration.UnixOrWindows(
|
||||
[]string{"/bin/sh", "-c",
|
||||
fmt.Sprintf("touch %[1]s/signal1 && i=0; while [ ! -f %[1]s/signal2 ] && [ $i -lt 10 ]; do i=$((i+1)); sleep 1; done", sharedDir)},
|
||||
[]string{"cmd", "/S", "/C",
|
||||
fmt.Sprintf(`copy nul %[1]s\signal1 >nul & for /L %%i in (1,1,10) do @if not exist %[1]s\signal2 ping -n 2 127.0.0.1 >nul`, sharedDir)},
|
||||
)
|
||||
run1 := llb.Image(imgName).Run(
|
||||
llb.Args(cmd1),
|
||||
cacheMount,
|
||||
).Root()
|
||||
d1, err := run1.Marshal(ctx)
|
||||
require.NoError(t, err)
|
||||
run2 := llb.Image("busybox:latest").Run(
|
||||
llb.Args([]string{
|
||||
"/bin/sh", "-c",
|
||||
"touch /shared/signal2 && i=0; while [ ! -f /shared/signal1 ] && [ $i -lt 10 ]; do i=$((i+1)); sleep 1; done",
|
||||
}),
|
||||
|
||||
cmd2 := integration.UnixOrWindows(
|
||||
[]string{"/bin/sh", "-c",
|
||||
fmt.Sprintf("touch %[1]s/signal2 && i=0; while [ ! -f %[1]s/signal1 ] && [ $i -lt 10 ]; do i=$((i+1)); sleep 1; done", sharedDir)},
|
||||
[]string{"cmd", "/S", "/C",
|
||||
fmt.Sprintf(`copy nul %[1]s\signal2 >nul & for /L %%i in (1,1,10) do @if not exist %[1]s\signal1 ping -n 2 127.0.0.1 >nul`, sharedDir)},
|
||||
)
|
||||
run2 := llb.Image(imgName).Run(
|
||||
llb.Args(cmd2),
|
||||
cacheMount,
|
||||
).Root()
|
||||
d2, err := run2.Marshal(ctx)
|
||||
|
||||
Reference in new issue
Block a user