Skip to content

Commit dc9e4c2

Browse files
committed
dockerfile2llb: check GitURLOpts error
Prior to this commit, `ADD https://github.com/moby/example.git?invalid=42` was fetching a single HTML file without causing an error. Now it returns an error `unexpected query "invalid"`. Hint: use `git show --ignore-all-space` to review this commit. Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
1 parent 7331897 commit dc9e4c2

2 files changed

Lines changed: 115 additions & 83 deletions

File tree

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 103 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/moby/buildkit/identity"
3737
"github.com/moby/buildkit/solver/pb"
3838
"github.com/moby/buildkit/util/apicaps"
39+
"github.com/moby/buildkit/util/gitutil"
3940
"github.com/moby/buildkit/util/suggest"
4041
"github.com/moby/buildkit/util/system"
4142
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
@@ -1472,7 +1473,15 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
14721473
if len(cfg.params.SourcePaths) != 1 {
14731474
return errors.New("checksum can't be specified for multiple sources")
14741475
}
1475-
if !isHTTPSource(cfg.params.SourcePaths[0]) && !isGitSource(cfg.params.SourcePaths[0]) {
1476+
isHTTPSourceV, err := isHTTPSource(cfg.params.SourcePaths[0])
1477+
if err != nil {
1478+
return err
1479+
}
1480+
isGitSourceV, err := isGitSource(cfg.params.SourcePaths[0])
1481+
if err != nil {
1482+
return err
1483+
}
1484+
if !isHTTPSourceV && !isGitSourceV {
14761485
return errors.New("checksum requires HTTP(S) or Git sources")
14771486
}
14781487
}
@@ -1537,96 +1546,102 @@ func dispatchCopy(d *dispatchState, cfg copyConfig) error {
15371546
} else {
15381547
a = a.Copy(st, "/", dest, opts...)
15391548
}
1540-
} else if isHTTPSource(src) {
1541-
if !cfg.isAddCommand {
1542-
return errors.New("source can't be a URL for COPY")
1549+
} else {
1550+
isHTTPSourceV, err := isHTTPSource(src)
1551+
if err != nil {
1552+
return err
15431553
}
1554+
if isHTTPSourceV {
1555+
if !cfg.isAddCommand {
1556+
return errors.New("source can't be a URL for COPY")
1557+
}
15441558

1545-
// Resources from remote URLs are not decompressed.
1546-
// https://docs.docker.com/engine/reference/builder/#add
1547-
//
1548-
// Note: mixing up remote archives and local archives in a single ADD instruction
1549-
// would result in undefined behavior: https://github.com/moby/buildkit/pull/387#discussion_r189494717
1550-
u, err := url.Parse(src)
1551-
f := "__unnamed__"
1552-
if err == nil {
1553-
if base := path.Base(u.Path); base != "." && base != "/" {
1554-
f = base
1559+
// Resources from remote URLs are not decompressed.
1560+
// https://docs.docker.com/engine/reference/builder/#add
1561+
//
1562+
// Note: mixing up remote archives and local archives in a single ADD instruction
1563+
// would result in undefined behavior: https://github.com/moby/buildkit/pull/387#discussion_r189494717
1564+
u, err := url.Parse(src)
1565+
f := "__unnamed__"
1566+
if err == nil {
1567+
if base := path.Base(u.Path); base != "." && base != "/" {
1568+
f = base
1569+
}
15551570
}
1556-
}
15571571

1558-
var checksum digest.Digest
1559-
if cfg.checksum != "" {
1560-
checksum, err = digest.Parse(cfg.checksum)
1561-
if err != nil {
1562-
return err
1572+
var checksum digest.Digest
1573+
if cfg.checksum != "" {
1574+
checksum, err = digest.Parse(cfg.checksum)
1575+
if err != nil {
1576+
return err
1577+
}
15631578
}
1564-
}
15651579

1566-
st := llb.HTTP(src, llb.Filename(f), llb.WithCustomName(pgName), llb.Checksum(checksum), dfCmd(cfg.params))
1580+
st := llb.HTTP(src, llb.Filename(f), llb.WithCustomName(pgName), llb.Checksum(checksum), dfCmd(cfg.params))
15671581

1568-
var unpack bool
1569-
if cfg.unpack != nil {
1570-
unpack = *cfg.unpack
1571-
}
1582+
var unpack bool
1583+
if cfg.unpack != nil {
1584+
unpack = *cfg.unpack
1585+
}
15721586

1573-
opts := append([]llb.CopyOption{&llb.CopyInfo{
1574-
Mode: chopt,
1575-
CreateDestPath: true,
1576-
AttemptUnpack: unpack,
1577-
}}, copyOpt...)
1587+
opts := append([]llb.CopyOption{&llb.CopyInfo{
1588+
Mode: chopt,
1589+
CreateDestPath: true,
1590+
AttemptUnpack: unpack,
1591+
}}, copyOpt...)
15781592

1579-
if a == nil {
1580-
a = llb.Copy(st, f, dest, opts...)
1581-
} else {
1582-
a = a.Copy(st, f, dest, opts...)
1583-
}
1584-
} else {
1585-
validateCopySourcePath(src, &cfg)
1586-
var patterns []string
1587-
if cfg.parents {
1588-
// detect optional pivot point
1589-
parent, pattern, ok := strings.Cut(src, "/./")
1590-
if !ok {
1591-
pattern = src
1592-
src = "/"
1593+
if a == nil {
1594+
a = llb.Copy(st, f, dest, opts...)
15931595
} else {
1594-
src = parent
1596+
a = a.Copy(st, f, dest, opts...)
15951597
}
1598+
} else {
1599+
validateCopySourcePath(src, &cfg)
1600+
var patterns []string
1601+
if cfg.parents {
1602+
// detect optional pivot point
1603+
parent, pattern, ok := strings.Cut(src, "/./")
1604+
if !ok {
1605+
pattern = src
1606+
src = "/"
1607+
} else {
1608+
src = parent
1609+
}
15961610

1597-
pattern, err = system.NormalizePath("/", pattern, d.platform.OS, false)
1611+
pattern, err = system.NormalizePath("/", pattern, d.platform.OS, false)
1612+
if err != nil {
1613+
return errors.Wrap(err, "removing drive letter")
1614+
}
1615+
1616+
patterns = []string{strings.TrimPrefix(pattern, "/")}
1617+
}
1618+
1619+
src, err = system.NormalizePath("/", src, d.platform.OS, false)
15981620
if err != nil {
15991621
return errors.Wrap(err, "removing drive letter")
16001622
}
16011623

1602-
patterns = []string{strings.TrimPrefix(pattern, "/")}
1603-
}
1604-
1605-
src, err = system.NormalizePath("/", src, d.platform.OS, false)
1606-
if err != nil {
1607-
return errors.Wrap(err, "removing drive letter")
1608-
}
1609-
1610-
unpack := cfg.isAddCommand
1611-
if cfg.unpack != nil {
1612-
unpack = *cfg.unpack
1613-
}
1614-
1615-
opts := append([]llb.CopyOption{&llb.CopyInfo{
1616-
Mode: chopt,
1617-
FollowSymlinks: true,
1618-
CopyDirContentsOnly: true,
1619-
IncludePatterns: patterns,
1620-
AttemptUnpack: unpack,
1621-
CreateDestPath: true,
1622-
AllowWildcard: true,
1623-
AllowEmptyWildcard: true,
1624-
}}, copyOpt...)
1624+
unpack := cfg.isAddCommand
1625+
if cfg.unpack != nil {
1626+
unpack = *cfg.unpack
1627+
}
16251628

1626-
if a == nil {
1627-
a = llb.Copy(cfg.source, src, dest, opts...)
1628-
} else {
1629-
a = a.Copy(cfg.source, src, dest, opts...)
1629+
opts := append([]llb.CopyOption{&llb.CopyInfo{
1630+
Mode: chopt,
1631+
FollowSymlinks: true,
1632+
CopyDirContentsOnly: true,
1633+
IncludePatterns: patterns,
1634+
AttemptUnpack: unpack,
1635+
CreateDestPath: true,
1636+
AllowWildcard: true,
1637+
AllowEmptyWildcard: true,
1638+
}}, copyOpt...)
1639+
1640+
if a == nil {
1641+
a = llb.Copy(cfg.source, src, dest, opts...)
1642+
} else {
1643+
a = a.Copy(cfg.source, src, dest, opts...)
1644+
}
16301645
}
16311646
}
16321647
}
@@ -2262,19 +2277,26 @@ func commonImageNames() []string {
22622277
return out
22632278
}
22642279

2265-
func isHTTPSource(src string) bool {
2280+
func isHTTPSource(src string) (bool, error) {
22662281
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
2267-
return false
2282+
return false, nil
22682283
}
2269-
return !isGitSource(src)
2284+
b, err := isGitSource(src)
2285+
return !b, err
22702286
}
22712287

2272-
func isGitSource(src string) bool {
2288+
func isGitSource(src string) (bool, error) {
22732289
// https://github.com/ORG/REPO.git is a git source, not an http source
2274-
if gitRef, gitErr := dfgitutil.ParseGitRef(src); gitRef != nil && gitErr == nil {
2275-
return true
2290+
gitRef, gitErr := dfgitutil.ParseGitRef(src)
2291+
var guoe *gitutil.GitURLOptsError
2292+
if errors.As(gitErr, &guoe) {
2293+
// this is a git source, and it has invalid URL opts
2294+
return true, gitErr
2295+
}
2296+
if gitRef != nil && gitErr == nil {
2297+
return true, nil
22762298
}
2277-
return false
2299+
return false, nil
22782300
}
22792301

22802302
func isEnabledForStage(stage string, value string) bool {

util/gitutil/git_url.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ type GitURLOpts struct {
6666
Subdir string
6767
}
6868

69+
// GitURLOptsError is returned for invalid GitURLOpts.
70+
type GitURLOptsError struct {
71+
error
72+
}
73+
6974
// parseOpts splits a git URL fragment into its respective git
7075
// reference and subdirectory components.
7176
func parseOpts(fragment string, query url.Values) (*GitURLOpts, error) {
@@ -169,7 +174,9 @@ func FromURL(url *url.URL) (*GitURL, error) {
169174
withoutOpts.RawQuery = ""
170175
opts, err := parseOpts(url.Fragment, url.Query())
171176
if err != nil {
172-
return nil, err
177+
return nil, &GitURLOptsError{
178+
error: errors.Wrapf(err, "failed to parse git URL opts %q", url.Redacted()),
179+
}
173180
}
174181
return &GitURL{
175182
Scheme: url.Scheme,
@@ -187,7 +194,10 @@ func fromSCPStyleURL(url *sshutil.SCPStyleURL) (*GitURL, error) {
187194
withoutOpts.Query = nil
188195
opts, err := parseOpts(url.Fragment, url.Query)
189196
if err != nil {
190-
return nil, err
197+
return nil, &GitURLOptsError{
198+
// *sshutil.SCPStyleURL.String() does not contain password
199+
error: errors.Wrapf(err, "failed to parse git URL opts %q", url.String()),
200+
}
191201
}
192202
return &GitURL{
193203
Scheme: SSHProtocol,

0 commit comments

Comments
 (0)