Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions client/llb/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ func TestGit(t *testing.T) {
"git.fullurl": "https://github.com/foo/bar.git",
},
},
{
name: "git advice",
st: Git("github.com/foo/bar.git", "ref", GitAdvice(true)),
identifier: "git://github.com/foo/bar.git#ref",
attrs: map[string]string{
"git.authheadersecret": "GIT_AUTH_HEADER",
"git.authtokensecret": "GIT_AUTH_TOKEN",
"git.fullurl": "https://github.com/foo/bar.git",
"git.advice": "true",
},
},
{
name: "git advice disabled",
st: Git("github.com/foo/bar.git", "ref", GitAdvice(false)),
identifier: "git://github.com/foo/bar.git#ref",
attrs: map[string]string{
"git.authheadersecret": "GIT_AUTH_HEADER",
"git.authtokensecret": "GIT_AUTH_TOKEN",
"git.fullurl": "https://github.com/foo/bar.git",
},
},
{
name: "bundle",
st: Git(
Expand Down
13 changes: 13 additions & 0 deletions client/llb/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ func Git(url, fragment string, opts ...GitOption) State {
addCap(&gi.Constraints, pb.CapSourceGitMTime)
}

if gi.Advice {
attrs[pb.AttrGitAdvice] = "true"
}

if gi.FetchByCommit {
attrs[pb.AttrGitFetchByCommit] = "true"
addCap(&gi.Constraints, pb.CapSourceGitFetchByCommit)
Expand Down Expand Up @@ -531,6 +535,7 @@ type GitInfo struct {
SubDir string
SkipSubmodules bool
MTime string
Advice bool
Bundle string
BundleOCISessionID string
BundleOCIStoreID string
Expand Down Expand Up @@ -570,6 +575,14 @@ func GitMTime(v string) GitOption {
})
}

// GitAdvice controls whether Git advice messages are emitted while resolving
// this git source.
func GitAdvice(enabled bool) GitOption {
return gitOptionFunc(func(gi *GitInfo) {
gi.Advice = enabled
})
}

func KeepGitDir() GitOption {
return gitOptionFunc(func(gi *GitInfo) {
gi.KeepGitDir = true
Expand Down
2 changes: 2 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,7 @@ func (dctx *dispatchContext) dispatchStages(ctx context.Context, allReachable ma
buildPlatforms: dctx.platformOpt.buildPlatforms,
targetPlatform: dctx.platformOpt.targetPlatform,
extraHosts: dctx.opt.ExtraHosts,
gitAdvice: dctx.opt.GitAdvice,
shmSize: dctx.opt.ShmSize,
ulimit: dctx.opt.Ulimits,
devices: dctx.opt.Devices,
Expand Down Expand Up @@ -989,6 +990,7 @@ type dispatchOpt struct {
targetPlatform ocispecs.Platform
buildPlatforms []ocispecs.Platform
extraHosts []llb.HostIP
gitAdvice bool
shmSize int64
ulimit []*pb.Ulimit
devices []*pb.CDIDevice
Expand Down
3 changes: 3 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
llb.WithCustomName(pgName),
llb.GitRef(gitRef.Ref),
}
if cfg.opt.gitAdvice {
gitOptions = append(gitOptions, llb.GitAdvice(true))
}
if cfg.keepGitDir != nil && gitRef.KeepGitDir != nil {
if *cfg.keepGitDir != *gitRef.KeepGitDir {
return errors.New("inconsistent keep-git-dir configuration")
Expand Down
50 changes: 48 additions & 2 deletions frontend/dockerfile/dockerfile2llb/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/buildkit/frontend/dockerfile/shell"
"github.com/moby/buildkit/frontend/dockerui"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/appcontext"
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
digest "github.com/opencontainers/go-digest"
Expand Down Expand Up @@ -355,7 +356,7 @@ ADD $URL /
require.NoError(t, err)
require.Len(t, stages, 1)

state, err := sourceDateEpochStageSource(stages[0], nil, &llb.EnvList{}, shell.NewLex('\\'))
state, err := sourceDateEpochStageSource(stages[0], nil, &llb.EnvList{}, shell.NewLex('\\'), false)
require.NoError(t, err)
require.NotNil(t, state)
sourceOp, err := sourceOpFromState(t.Context(), state)
Expand All @@ -364,6 +365,51 @@ ADD $URL /
assert.Equal(t, "src.tar", sourceOp.Attrs["http.filename"])
}

func TestDockerfileGitAdviceBuildArgADD(t *testing.T) {
t.Parallel()

df := []byte(`
FROM scratch
ADD https://github.com/moby/buildkit.git#master /
`)

for _, tc := range []struct {
name string
gitAdvice bool
wantAttr bool
}{
{
name: "default",
},
{
name: "enabled",
gitAdvice: true,
wantAttr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

res, err := Dockerfile2LLB(appcontext.Context(), df, ConvertOpt{
Config: dockerui.Config{
GitAdvice: tc.gitAdvice,
},
})
require.NoError(t, err)

sourceOp, err := sourceOpFromState(t.Context(), &res.State)
require.NoError(t, err)
require.NotNil(t, sourceOp)

if tc.wantAttr {
require.Equal(t, "true", sourceOp.Attrs[pb.AttrGitAdvice])
} else {
require.NotContains(t, sourceOp.Attrs, pb.AttrGitAdvice)
}
})
}
}

func TestSourceDateEpochStageSourceRequiresScratch(t *testing.T) {
t.Parallel()

Expand All @@ -379,7 +425,7 @@ ADD https://example.com/src.tar /
require.NoError(t, err)
require.Len(t, stages, 1)

_, err = sourceDateEpochStageSource(stages[0], nil, &llb.EnvList{}, shell.NewLex('\\'))
_, err = sourceDateEpochStageSource(stages[0], nil, &llb.EnvList{}, shell.NewLex('\\'), false)
require.ErrorContains(t, err, "SOURCE_DATE_EPOCH stage must use FROM scratch")
}

Expand Down
11 changes: 7 additions & 4 deletions frontend/dockerfile/dockerfile2llb/epoch.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func resolveSourceDateEpochState(ctx context.Context, value string, opt ConvertO
args = &updated
}

sourceState, err := sourceDateEpochStageSource(stages[i], opt.BuildArgs, args, shlex)
sourceState, err := sourceDateEpochStageSource(stages[i], opt.BuildArgs, args, shlex, opt.GitAdvice)
if err != nil {
return nil, sourceDateEpochStateOpt{}, parser.WithLocation(err, stages[i].Location)
}
Expand All @@ -110,7 +110,7 @@ func resolveSourceDateEpochState(ctx context.Context, value string, opt ConvertO
return nil, sourceDateEpochStateOpt{}, errors.Errorf("invalid SOURCE_DATE_EPOCH: %s", value)
}

func sourceDateEpochStageSource(stage instructions.Stage, buildArgs map[string]string, globalArgs *llb.EnvList, shlex *shell.Lex) (*llb.State, error) {
func sourceDateEpochStageSource(stage instructions.Stage, buildArgs map[string]string, globalArgs *llb.EnvList, shlex *shell.Lex, gitAdvice bool) (*llb.State, error) {
stageBaseName, _, err := shlex.ProcessWord(stage.BaseName, globalArgs)
if err != nil {
return nil, errors.Wrapf(err, "failed to process source stage base name %q", stage.BaseName)
Expand All @@ -133,7 +133,7 @@ func sourceDateEpochStageSource(stage instructions.Stage, buildArgs map[string]s
if sourceState != nil {
return nil, errors.New("SOURCE_DATE_EPOCH stage must contain exactly one remote ADD")
}
sourceState, err = sourceDateEpochAddSource(c, env, shlex)
sourceState, err = sourceDateEpochAddSource(c, env, shlex, gitAdvice)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -167,7 +167,7 @@ func applySourceDateEpochStageArgs(args []instructions.KeyValuePairOptional, env
return env, nil
}

func sourceDateEpochAddSource(cmd *instructions.AddCommand, env *llb.EnvList, shlex *shell.Lex) (*llb.State, error) {
func sourceDateEpochAddSource(cmd *instructions.AddCommand, env *llb.EnvList, shlex *shell.Lex, gitAdvice bool) (*llb.State, error) {
if len(cmd.SourceContents) != 0 || len(cmd.SourcePaths) != 1 {
return nil, errors.New("SOURCE_DATE_EPOCH stage must contain exactly one remote ADD source")
}
Expand Down Expand Up @@ -201,6 +201,9 @@ func sourceDateEpochAddSource(cmd *instructions.AddCommand, env *llb.EnvList, sh
gitOptions := []llb.GitOption{
llb.GitRef(gitRef.Ref),
}
if gitAdvice {
gitOptions = append(gitOptions, llb.GitAdvice(true))
}
if cmd.KeepGitDir != nil && *cmd.KeepGitDir {
gitOptions = append(gitOptions, llb.KeepGitDir())
}
Expand Down
147 changes: 147 additions & 0 deletions frontend/dockerfile/dockerfile_addgit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var addGitTests = integration.TestFuncs(
testAddGitSHA256,
testAddGitChecksumCache,
testGitQueryString,
testGitAdviceBuildArg,
)

func init() {
Expand Down Expand Up @@ -716,6 +717,152 @@ FROM main
}
}

func testGitAdviceBuildArg(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows", "Git source handler submodule update not supported on Windows")
f := getFrontend(t, sb)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

const detachedHeadAdvice = "detached HEAD"

for _, tc := range []struct {
name string
buildArg string
wantAdvice bool
}{
{
name: "default",
},
{
name: "enabled",
buildArg: "1",
wantAdvice: true,
},
} {
t.Run("context_"+tc.name, func(t *testing.T) {
serverURL, closeServer := newGitAdviceHTTPRepo(t, map[string]string{
"Dockerfile": "FROM scratch\nCOPY .git/HEAD /head\n",
"unique": "context " + tc.name,
})
defer closeServer()

dest := t.TempDir()
attrs := map[string]string{
"context": serverURL + "/.git?tag=v0.0.1&keep-git-dir=true",
}
if tc.buildArg != "" {
attrs["build-arg:BUILDKIT_GIT_ADVICE"] = tc.buildArg
}
logs := solveWithGitAdviceLogs(t, sb, f, c, client.SolveOpt{
FrontendAttrs: attrs,
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: dest,
},
},
})

_, err := os.ReadFile(filepath.Join(dest, "head"))
require.NoError(t, err)
if tc.wantAdvice {
require.Contains(t, logs, detachedHeadAdvice)
} else {
require.NotContains(t, logs, detachedHeadAdvice)
}
})

t.Run("add_"+tc.name, func(t *testing.T) {
serverURL, closeServer := newGitAdviceHTTPRepo(t, map[string]string{
"foo": "bar\n",
"unique": "add " + tc.name,
})
defer closeServer()

dockerfile := fmt.Appendf(nil, "FROM scratch\nADD --keep-git-dir=true %s/.git#v0.0.1 /repo\n", serverURL)
dir := integration.Tmpdir(t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)

dest := t.TempDir()
attrs := map[string]string{}
if tc.buildArg != "" {
attrs["build-arg:BUILDKIT_GIT_ADVICE"] = tc.buildArg
}
logs := solveWithGitAdviceLogs(t, sb, f, c, client.SolveOpt{
FrontendAttrs: attrs,
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: dest,
},
},
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
})

dt, err := os.ReadFile(filepath.Join(dest, "repo", "foo"))
require.NoError(t, err)
require.Equal(t, "bar\n", string(dt))
if tc.wantAdvice {
require.Contains(t, logs, detachedHeadAdvice)
} else {
require.NotContains(t, logs, detachedHeadAdvice)
}
})
}
}

func newGitAdviceHTTPRepo(t *testing.T, files map[string]string) (string, func()) {
t.Helper()

gitDir := t.TempDir()
for name, data := range files {
p := filepath.Join(gitDir, filepath.FromSlash(name))
require.NoError(t, os.MkdirAll(filepath.Dir(p), 0700))
require.NoError(t, os.WriteFile(p, []byte(data), 0600))
}

err := runShell(gitDir,
"git init",
"git config --local user.email test",
"git config --local user.name test",
"git add .",
"git commit -m initial",
"git tag v0.0.1",
"git update-server-info",
)
require.NoError(t, err)

server := httptest.NewServer(http.FileServer(http.Dir(filepath.Clean(gitDir))))
return server.URL, server.Close
}

func solveWithGitAdviceLogs(t *testing.T, sb integration.Sandbox, f frontend, c *client.Client, opt client.SolveOpt) string {
t.Helper()

statusCh := make(chan *client.SolveStatus)
logsCh := make(chan string, 1)
go func() {
var logs bytes.Buffer
for status := range statusCh {
for _, l := range status.Logs {
logs.Write(l.Data)
}
}
logsCh <- logs.String()
}()

_, err := f.Solve(sb.Context(), c, opt, statusCh)
logs := <-logsCh
require.NoError(t, err)
return logs
}

func applyTemplate(tmpl string, x any) (string, error) {
var buf bytes.Buffer
parsed, err := template.New("").Parse(tmpl)
Expand Down
1 change: 1 addition & 0 deletions frontend/dockerfile/docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2733,6 +2733,7 @@ RUN echo "I'm building for $TARGETPLATFORM"
| `BUILDKIT_BUILD_NAME` | String | Override the build name shown in [`buildx history` command](https://docs.docker.com/reference/cli/docker/buildx/history/) and [Docker Desktop Builds view](https://docs.docker.com/desktop/use-desktop/builds/). |
| `BUILDKIT_CACHE_MOUNT_NS` | String | Set optional cache ID namespace. |
| `BUILDKIT_CONTEXT_KEEP_GIT_DIR` | Bool | Trigger Git context to keep the `.git` directory. |
| `BUILDKIT_GIT_ADVICE` | Bool | Show Git advice messages from BuildKit-managed Git operations. Defaults to `false`. |
| `BUILDKIT_INLINE_CACHE`[^2] | Bool | Inline cache metadata to image config or not. |
| `BUILDKIT_MULTI_PLATFORM` | Bool | Opt into deterministic output regardless of multi-platform output or not. |
| `BUILDKIT_SANDBOX_HOSTNAME` | String | Set the hostname (default `buildkitsandbox`) |
Expand Down
Loading