From 02bc4e89928ec81733bc3a198b64ccefe8c43b6e Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:23:23 +0100 Subject: [PATCH] build: set local dirs as frontend attributes Set local dirs metadata if relative to VCS directory so dockerfile path is tracked accurately in case vcs information is not fulfilled. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- build/build.go | 5 +++- build/git.go | 66 +++++++++++++++++++++++++++++----------------- build/git_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/build/build.go b/build/build.go index 624ceb84441..2ae0fbd8e78 100644 --- a/build/build.go +++ b/build/build.go @@ -530,7 +530,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s for k, opt := range opt { multiDriver := len(drivers[k]) > 1 hasMobyDriver := false - gitattrs, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath) + gitattrs, addVCSLocalDir, err := getGitAttributes(ctx, opt.Inputs.ContextPath, opt.Inputs.DockerfilePath) if err != nil { logrus.WithError(err).Warn("current commit information was not captured by the build") } @@ -554,6 +554,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s for k, v := range gitattrs { so.FrontendAttrs[k] = v } + if addVCSLocalDir != nil { + addVCSLocalDir(so) + } defers = append(defers, release) reqn = append(reqn, &reqForNode{ resolvedNode: np, diff --git a/build/git.go b/build/git.go index 1e0a284de59..112ec363b31 100644 --- a/build/git.go +++ b/build/git.go @@ -9,16 +9,17 @@ import ( "strings" "github.com/docker/buildx/util/gitutil" + "github.com/moby/buildkit/client" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) const DockerfileLabel = "com.docker.image.source.entrypoint" -func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string, _ error) { - res = make(map[string]string) +func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (map[string]string, func(*client.SolveOpt), error) { + res := make(map[string]string) if contextPath == "" { - return + return nil, nil, nil } setGitLabels := false @@ -37,7 +38,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st } if !setGitLabels && !setGitInfo { - return + return nil, nil, nil } // figure out in which directory the git command needs to run in @@ -52,20 +53,25 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd)) if err != nil { if st, err1 := os.Stat(path.Join(wd, ".git")); err1 == nil && st.IsDir() { - return res, errors.Wrap(err, "git was not found in the system") + return res, nil, errors.Wrap(err, "git was not found in the system") } - return + return nil, nil, nil } if !gitc.IsInsideWorkTree() { if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { - return res, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree") + return res, nil, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree") } - return res, nil + return nil, nil, nil + } + + root, err := gitc.RootDir() + if err != nil { + return res, nil, errors.Wrap(err, "failed to get git root dir") } if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) { - return res, errors.Wrap(err, "failed to get git commit") + return res, nil, errors.Wrap(err, "failed to get git commit") } else if sha != "" { checkDirty := false if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok { @@ -93,23 +99,35 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st } } - if setGitLabels { - if root, err := gitc.RootDir(); err != nil { - return res, errors.Wrap(err, "failed to get git root dir") - } else if root != "" { - if dockerfilePath == "" { - dockerfilePath = filepath.Join(wd, "Dockerfile") + if setGitLabels && root != "" { + if dockerfilePath == "" { + dockerfilePath = filepath.Join(wd, "Dockerfile") + } + if !filepath.IsAbs(dockerfilePath) { + cwd, _ := os.Getwd() + dockerfilePath = filepath.Join(cwd, dockerfilePath) + } + if r, err := filepath.Rel(root, dockerfilePath); err == nil && !strings.HasPrefix(r, "..") { + res["label:"+DockerfileLabel] = r + } + } + + return res, func(so *client.SolveOpt) { + if !setGitInfo || root == "" { + return + } + for k, dir := range so.LocalDirs { + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + continue } - if !filepath.IsAbs(dockerfilePath) { - cwd, _ := os.Getwd() - dockerfilePath = filepath.Join(cwd, dockerfilePath) + dir, err = filepath.Abs(dir) + if err != nil { + continue } - dockerfilePath, _ = filepath.Rel(root, dockerfilePath) - if !strings.HasPrefix(dockerfilePath, "..") { - res["label:"+DockerfileLabel] = dockerfilePath + if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") { + so.FrontendAttrs["vcs:localdir:"+k] = r } } - } - - return + }, nil } diff --git a/build/git_test.go b/build/git_test.go index 5d65b64b7a6..da954ddbb30 100644 --- a/build/git_test.go +++ b/build/git_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/docker/buildx/util/gitutil" + "github.com/moby/buildkit/client" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +31,7 @@ func setupTest(tb testing.TB) { } func TestGetGitAttributesNotGitRepo(t *testing.T) { - _, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile") + _, _, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile") assert.NoError(t, err) } @@ -38,14 +39,14 @@ func TestGetGitAttributesBadGitRepo(t *testing.T) { tmp := t.TempDir() require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755)) - _, err := getGitAttributes(context.Background(), tmp, "Dockerfile") + _, _, err := getGitAttributes(context.Background(), tmp, "Dockerfile") assert.Error(t, err) } func TestGetGitAttributesNoContext(t *testing.T) { setupTest(t) - gitattrs, err := getGitAttributes(context.Background(), "", "Dockerfile") + gitattrs, _, err := getGitAttributes(context.Background(), "", "Dockerfile") assert.NoError(t, err) assert.Empty(t, gitattrs) } @@ -114,7 +115,7 @@ func TestGetGitAttributes(t *testing.T) { if tt.envGitInfo != "" { t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo) } - gitattrs, err := getGitAttributes(context.Background(), ".", "Dockerfile") + gitattrs, _, err := getGitAttributes(context.Background(), ".", "Dockerfile") require.NoError(t, err) for _, e := range tt.expected { assert.Contains(t, gitattrs, e) @@ -139,7 +140,7 @@ func TestGetGitAttributesDirty(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644)) t.Setenv("BUILDX_GIT_LABELS", "true") - gitattrs, _ := getGitAttributes(context.Background(), ".", "Dockerfile") + gitattrs, _, _ := getGitAttributes(context.Background(), ".", "Dockerfile") assert.Equal(t, 5, len(gitattrs)) assert.Contains(t, gitattrs, "label:"+DockerfileLabel) @@ -154,3 +155,59 @@ func TestGetGitAttributesDirty(t *testing.T) { assert.Contains(t, gitattrs, "vcs:revision") assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty")) } + +func TestLocalDirs(t *testing.T) { + setupTest(t) + + so := &client.SolveOpt{ + FrontendAttrs: map[string]string{}, + LocalDirs: map[string]string{ + "context": ".", + "dockerfile": ".", + }, + } + + _, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "Dockerfile") + require.NoError(t, err) + require.NotNil(t, addVCSLocalDir) + + addVCSLocalDir(so) + require.Contains(t, so.FrontendAttrs, "vcs:localdir:context") + assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"]) + require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile") + assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:dockerfile"]) +} + +func TestLocalDirsSub(t *testing.T) { + gitutil.Mktmp(t) + + c, err := gitutil.New() + require.NoError(t, err) + gitutil.GitInit(c, t) + + df := []byte("FROM alpine:latest\n") + assert.NoError(t, os.MkdirAll("app", 0755)) + assert.NoError(t, os.WriteFile("app/Dockerfile", df, 0644)) + + gitutil.GitAdd(c, t, "app/Dockerfile") + gitutil.GitCommit(c, t, "initial commit") + gitutil.GitSetRemote(c, t, "origin", "git@github.com:docker/buildx.git") + + so := &client.SolveOpt{ + FrontendAttrs: map[string]string{}, + LocalDirs: map[string]string{ + "context": ".", + "dockerfile": "app", + }, + } + + _, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "app/Dockerfile") + require.NoError(t, err) + require.NotNil(t, addVCSLocalDir) + + addVCSLocalDir(so) + require.Contains(t, so.FrontendAttrs, "vcs:localdir:context") + assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"]) + require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile") + assert.Equal(t, "app", so.FrontendAttrs["vcs:localdir:dockerfile"]) +}