diff --git a/add.go b/add.go index c523dbf28b0..036a15958e1 100644 --- a/add.go +++ b/add.go @@ -27,6 +27,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/fileutils" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/regexp" "github.com/docker/go-connections/tlsconfig" "github.com/hashicorp/go-multierror" digest "github.com/opencontainers/go-digest" @@ -94,14 +95,27 @@ type AddAndCopyOptions struct { RetryDelay time.Duration } +// gitURLFragmentSuffix matches fragments to use as Git reference and build +// context from the Git repository e.g. +// +// github.com/containers/buildah.git +// github.com/containers/buildah.git#main +// github.com/containers/buildah.git#v1.35.0 +var gitURLFragmentSuffix = regexp.Delayed(`\.git(?:#.+)?$`) + // sourceIsGit returns true if "source" is a git location. func sourceIsGit(source string) bool { - return strings.HasPrefix(source, "git://") || strings.Contains(source, ".git") + return isURL(source) && gitURLFragmentSuffix.MatchString(source) +} + +func isURL(url string) bool { + return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") } -// sourceIsRemote returns true if "source" is a remote location. +// sourceIsRemote returns true if "source" is a remote location +// and *not* a git repo. Certain github urls such as raw.github.* are allowed. func sourceIsRemote(source string) bool { - return (strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://")) && !strings.Contains(source, ".git") + return isURL(source) && !gitURLFragmentSuffix.MatchString(source) } // getURL writes a tar archive containing the named content @@ -478,28 +492,10 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption } wg.Add(1) - - go func() { - getErr = retry.IfNecessary(context.TODO(), func() error { - return getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, chmodDirsFiles, srcDigest, options.CertPath, options.InsecureSkipTLSVerify) - }, &retry.Options{ - MaxRetry: options.MaxRetries, - Delay: options.RetryDelay, - }) - pipeWriter.Close() - wg.Done() - }() - if sourceIsGit(src) { go func() { var cloneDir string cloneDir, _, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src) - - renamedItems := 0 - writer := io.WriteCloser(pipeWriter) - writer = newTarFilterer(writer, func(hdr *tar.Header) (bool, bool, io.Reader) { - return false, false, nil - }) getOptions := copier.GetOptions{ UIDMap: srcUIDMap, GIDMap: srcGIDMap, @@ -513,16 +509,19 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption StripSetgidBit: options.StripSetgidBit, StripStickyBit: options.StripStickyBit, } + writer := io.WriteCloser(pipeWriter) getErr = copier.Get(cloneDir, cloneDir, getOptions, []string{"."}, writer) - closeErr = writer.Close() - if renameTarget != "" && renamedItems > 1 { - renameErr = fmt.Errorf("internal error: renamed %d items when we expected to only rename 1", renamedItems) - } + pipeWriter.Close() wg.Done() }() } else { go func() { - getErr = getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, chmodDirsFiles, srcDigest) + getErr = retry.IfNecessary(context.TODO(), func() error { + return getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, chmodDirsFiles, srcDigest, options.CertPath, options.InsecureSkipTLSVerify) + }, &retry.Options{ + MaxRetry: options.MaxRetries, + Delay: options.RetryDelay, + }) pipeWriter.Close() wg.Done() }() diff --git a/define/types.go b/define/types.go index 3f53c1c1426..4f917b7db4a 100644 --- a/define/types.go +++ b/define/types.go @@ -254,11 +254,11 @@ func parseGitBuildContext(url string) (string, string, string) { return gitBranchPart[0], gitSubdir, gitBranch } -func isGitTag(remote, ref, dir string) (bool, error) { +func isGitTag(remote, ref string) bool { if _, err := exec.Command("git", "ls-remote", "--exit-code", remote, ref).Output(); err != nil { - return true, nil + return true } - return false, nil + return false } func cloneToDirectory(url, dir string) ([]byte, string, error) { @@ -279,7 +279,7 @@ func cloneToDirectory(url, dir string) ([]byte, string, error) { } if gitRef != "" { - if ok, _ := isGitTag(url, gitRef, dir); ok { + if ok := isGitTag(url, gitRef); ok { gitRef += ":refs/tags/" + gitRef } } diff --git a/tests/bud.bats b/tests/bud.bats index 36a271e2d8a..dea9ccb21a0 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -6875,3 +6875,30 @@ _EOF run_buildah 125 build --retry-delay=0.142857s --retry=14 --cert-dir ${TEST_SCRATCH_DIR} $cid ${TEST_SCRATCH_DIR} assert "$output" =~ "retrying in 142.*ms .*14/14.*" } + +@test "bud with ADD with git repository source" { + _prefetch alpine + + local contextdir=${TEST_SCRATCH_DIR}/add-git + mkdir -p $contextdir + cat > $contextdir/Dockerfile << _EOF +FROM alpine +RUN apk add git + +ADD https://github.com/containers/podman.git#v5.0 /podman-branch +ADD https://github.com/containers/podman.git#v5.0.0 /podman-tag +_EOF + + run_buildah build -f $contextdir/Dockerfile -t git-image $contextdir + run_buildah from --quiet $WITH_POLICY_JSON --name testctr git-image + + run_buildah run testctr -- sh -c 'cd podman-branch && git rev-parse HEAD' + local_head_hash=$output + run_buildah run testctr -- sh -c 'cd podman-branch && git ls-remote origin v5.0 | cut -f1' + assert "$output" = "$local_head_hash" + + run_buildah run testctr -- sh -c 'cd podman-tag && git rev-parse HEAD' + local_head_hash=$output + run_buildah run testctr -- sh -c 'cd podman-tag && git ls-remote --tags origin v5.0.0^{} | cut -f1' + assert "$output" = "$local_head_hash" +}