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
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ run:

build-tags:
- dfrunsecurity
- dfaddgit

linters:
enable:
Expand Down
45 changes: 15 additions & 30 deletions frontend/dockerfile/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb"
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
"github.com/moby/buildkit/util/gitutil"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
Expand Down Expand Up @@ -70,7 +71,6 @@ const (
)

var httpPrefix = regexp.MustCompile(`^https?://`)
var gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)

func Build(ctx context.Context, c client.Client) (*client.Result, error) {
opts := c.BuildOpts().Opts
Expand Down Expand Up @@ -181,7 +181,11 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {

var buildContext *llb.State
isNotLocalContext := false
if st, ok := detectGitContext(opts[localNameContext], opts[keyContextKeepGitDirArg]); ok {
keepGit := false
if v, err := strconv.ParseBool(opts[keyContextKeepGitDirArg]); err == nil {
keepGit = v
}
if st, ok := detectGitContext(opts[localNameContext], keepGit); ok {
if !forceLocalDockerfile {
src = *st
}
Expand Down Expand Up @@ -599,40 +603,21 @@ func filter(opt map[string]string, key string) map[string]string {
return m
}

func detectGitContext(ref, gitContext string) (*llb.State, bool) {
found := false
if httpPrefix.MatchString(ref) && gitURLPathWithFragmentSuffix.MatchString(ref) {
found = true
}

keepGit := false
if gitContext != "" {
if v, err := strconv.ParseBool(gitContext); err == nil {
keepGit = v
}
}

for _, prefix := range []string{"git://", "github.com/", "git@"} {
if strings.HasPrefix(ref, prefix) {
found = true
break
}
}
if !found {
func detectGitContext(ref string, keepGit bool) (*llb.State, bool) {
g, err := gitutil.ParseGitRef(ref)
if err != nil {
return nil, false
}

parts := strings.SplitN(ref, "#", 2)
branch := ""
if len(parts) > 1 {
branch = parts[1]
commit := g.Commit
if g.SubDir != "" {
commit += ":" + g.SubDir
}
gitOpts := []llb.GitOption{dockerfile2llb.WithInternalName("load git source " + ref)}
if keepGit {
gitOpts = append(gitOpts, llb.KeepGitDir())
}

st := llb.Git(parts[0], branch, gitOpts...)
st := llb.Git(g.Remote, commit, gitOpts...)
return &st, true
}

Expand Down Expand Up @@ -870,13 +855,13 @@ func contextByName(ctx context.Context, c client.Client, sessionID, name string,
}
return &st, &img, nil, nil
case "git":
st, ok := detectGitContext(v, "1")
st, ok := detectGitContext(v, true)
if !ok {
return nil, nil, nil, errors.Errorf("invalid git context %s", v)
}
return st, nil, nil, nil
case "http", "https":
st, ok := detectGitContext(v, "1")
st, ok := detectGitContext(v, true)
if !ok {
httpst := llb.HTTP(v, llb.WithCustomName("[context "+name+"] "+v))
st = &httpst
Expand Down
32 changes: 31 additions & 1 deletion frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
"github.com/moby/buildkit/util/gitutil"
"github.com/moby/buildkit/util/suggest"
"github.com/moby/buildkit/util/system"
"github.com/moby/sys/signal"
Expand Down Expand Up @@ -652,6 +653,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
chown: c.Chown,
chmod: c.Chmod,
link: c.Link,
keepGitDir: c.KeepGitDir,
location: c.Location(),
opt: opt,
})
Expand Down Expand Up @@ -1006,7 +1008,34 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {

for _, src := range cfg.params.SourcePaths {
commitMessage.WriteString(" " + src)
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
gitRef, gitRefErr := gitutil.ParseGitRef(src)
if gitRefErr == nil && !gitRef.IndistinguishableFromLocal {
if !cfg.isAddCommand {
return errors.New("source can't be a git ref for COPY")
}
if !addGitEnabled {
return errors.New("instruction ADD <git ref> requires the labs channel")
}
// TODO: print a warning (not an error) if gitRef.UnencryptedTCP is true
commit := gitRef.Commit
if gitRef.SubDir != "" {
commit += ":" + gitRef.SubDir
}
var gitOptions []llb.GitOption
if cfg.keepGitDir {
gitOptions = append(gitOptions, llb.KeepGitDir())
}
st := llb.Git(gitRef.Remote, commit, gitOptions...)
opts := append([]llb.CopyOption{&llb.CopyInfo{
Mode: mode,
CreateDestPath: true,
}}, copyOpt...)
if a == nil {
a = llb.Copy(st, "/", dest, opts...)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make sure that

FROM scratch
ADD git:// / (or .)

Is converted to direct llb.Git(), not llb.Scratch().File(llb.Copy(llb.Git(), "/", "/")) . Possibly this could be also optimized in the llb libarary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to work on this in this PR or a separate PR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be better in this PR. Separate commit is ok. If you want to do this in llb client level then it can be done in parallel on separate PR but we should avoid the possibility of having the git feature but not this optimization merged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have that optimization for http sources?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an optimizer in the second commit

} else {
a = a.Copy(st, "/", dest, opts...)
}
} else if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
if !cfg.isAddCommand {
return errors.New("source can't be a URL for COPY")
}
Expand Down Expand Up @@ -1129,6 +1158,7 @@ type copyConfig struct {
chown string
chmod string
link bool
keepGitDir bool
location []parser.Range
opt dispatchOpt
}
Expand Down
6 changes: 6 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert_addgit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build dfaddgit
// +build dfaddgit

package dockerfile2llb

const addGitEnabled = true
6 changes: 6 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert_noaddgit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build !dfaddgit
// +build !dfaddgit

package dockerfile2llb

const addGitEnabled = false
115 changes: 115 additions & 0 deletions frontend/dockerfile/dockerfile_addgit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//go:build dfaddgit
// +build dfaddgit

package dockerfile

import (
"bytes"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"text/template"

"github.com/containerd/continuity/fs/fstest"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/dockerfile/builder"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/stretchr/testify/require"
)

var addGitTests = integration.TestFuncs(
testAddGit,
)

func init() {
allTests = append(allTests, addGitTests...)
}

func testAddGit(t *testing.T, sb integration.Sandbox) {
f := getFrontend(t, sb)

gitDir, err := os.MkdirTemp("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(gitDir)
gitCommands := []string{
"git init",
"git config --local user.email test",
"git config --local user.name test",
}
makeCommit := func(tag string) []string {
return []string{
"echo foo of " + tag + " >foo",
"git add foo",
"git commit -m " + tag,
"git tag " + tag,
}
}
gitCommands = append(gitCommands, makeCommit("v0.0.1")...)
gitCommands = append(gitCommands, makeCommit("v0.0.2")...)
gitCommands = append(gitCommands, makeCommit("v0.0.3")...)
gitCommands = append(gitCommands, "git update-server-info")
err = runShell(gitDir, gitCommands...)
require.NoError(t, err)

server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join(gitDir))))
defer server.Close()
serverURL := server.URL
t.Logf("serverURL=%q", serverURL)

dockerfile, err := applyTemplate(`
FROM alpine

# Basic case
ADD {{.ServerURL}}/.git#v0.0.1 /x
RUN cd /x && \
[ "$(cat foo)" = "foo of v0.0.1" ]

# Complicated case
ARG REPO="{{.ServerURL}}/.git"
ARG TAG="v0.0.2"
ADD --keep-git-dir=true --chown=4242:8484 ${REPO}#${TAG} /buildkit-chowned
RUN apk add git
USER 4242
RUN cd /buildkit-chowned && \
[ "$(cat foo)" = "foo of v0.0.2" ] && \
[ "$(stat -c %u foo)" = "4242" ] && \
[ "$(stat -c %g foo)" = "8484" ] && \
[ -z "$(git status -s)" ]
`, map[string]string{
"ServerURL": serverURL,
})
require.NoError(t, err)
t.Logf("dockerfile=%s", dockerfile)

dir, err := integration.Tmpdir(t,
fstest.CreateFile("Dockerfile", []byte(dockerfile), 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)

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

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalDirs: map[string]string{
builder.DefaultLocalNameDockerfile: dir,
builder.DefaultLocalNameContext: dir,
},
}, nil)
require.NoError(t, err)
}

func applyTemplate(tmpl string, x interface{}) (string, error) {
var buf bytes.Buffer
parsed, err := template.New("").Parse(tmpl)
if err != nil {
return "", err
}
if err := parsed.Execute(&buf, x); err != nil {
return "", err
}
return buf.String(), nil
}
38 changes: 38 additions & 0 deletions frontend/dockerfile/docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,44 @@ guide – Leverage build cache](https://docs.docker.com/develop/develop-images/d
- If `<dest>` doesn't exist, it is created along with all missing directories
in its path.

### Adding a git repository `ADD <git ref> <dir>`

> **Note**
>
> Available in [`docker/dockerfile-upstream:master-labs`](#syntax).
> Will be included in `docker/dockerfile:1.5-labs`.

This form allows adding a git repository to an image directly, without using the `git` command inside the image:
```
ADD [--keep-git-dir=<boolean>] <git ref> <dir>
```

```dockerfile
# syntax=docker/dockerfile-upstream:master-labs
FROM alpine
ADD --keep-git-dir=true https://github.com/moby/buildkit.git#v0.10.1 /buildkit
```

The `--keep-git-dir=true` flag adds the `.git` directory. This flag defaults to false.

### Adding a private git repository
To add a private repo via SSH, create a Dockerfile with the following form:
```dockerfile
# syntax = docker/dockerfile-upstream:master-labs
FROM alpine
ADD git@git.example.com:foo/bar.git /bar
```

This Dockerfile can be built with `docker build --ssh` or `buildctl build --ssh`, e.g.,

```console
$ docker build --ssh default
```

```console
$ buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. --ssh default
```

## ADD --link

See [`COPY --link`](#copy---link).
Expand Down
7 changes: 4 additions & 3 deletions frontend/dockerfile/instructions/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,10 @@ func (s *SourcesAndDest) ExpandRaw(expander SingleWordExpander) error {
type AddCommand struct {
withNameAndCode
SourcesAndDest
Chown string
Chmod string
Link bool
Chown string
Chmod string
Link bool
KeepGitDir bool // whether to keep .git dir, only meaningful for git sources
}

// Expand variables
Expand Down
2 changes: 2 additions & 0 deletions frontend/dockerfile/instructions/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ func parseAdd(req parseRequest) (*AddCommand, error) {
flChown := req.flags.AddString("chown", "")
flChmod := req.flags.AddString("chmod", "")
flLink := req.flags.AddBool("link", false)
flKeepGitDir := req.flags.AddBool("keep-git-dir", false)
if err := req.flags.Parse(); err != nil {
return nil, err
}
Expand All @@ -296,6 +297,7 @@ func parseAdd(req parseRequest) (*AddCommand, error) {
Chown: flChown.Value,
Chmod: flChmod.Value,
Link: flLink.Value == "true",
KeepGitDir: flKeepGitDir.Value == "true",
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/dockerfile/release/labs/tags
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dfrunsecurity
dfrunsecurity dfaddgit
Loading