1
0
mirror of https://github.com/docker/cli.git synced 2026-01-26 15:41:42 +03:00

add local fork of github.com/docker/docker/builder/remotecontext

Adds a local fork of this package for use in the classic builder.

Code was taken at commit [d33d46d01656e1d9ee26743f0c0d7779f685dd4e][1].

Migration was done using the following steps:

    # install filter-repo (https://github.com/newren/git-filter-repo/blob/main/INSTALL.md)
    brew install git-filter-repo

    # create a temporary clone of docker
    cd ~/Projects
    git clone https://github.com/docker/docker.git build_context_temp
    cd build_context_temp

    # commit taken from
    git rev-parse --verify HEAD
    d33d46d01656e1d9ee26743f0c0d7779f685dd4e

    git filter-repo --analyze

    # remove all code, except for the remotecontext packages, and move to build/internal docs and previous locations of it
    git filter-repo \
      --path 'builder/remotecontext/git' \
      --path 'builder/remotecontext/urlutil' \
      --path-rename builder/remotecontext:cli/command/image/build/internal

    # go to the target repository
    cd ~/go/src/github.com/docker/cli

    # create a branch to work with
    git checkout -b fork_remotecontext

    # add the temporary repository as an upstream and make sure it's up-to-date
    git remote add build_context_temp ~/Projects/build_context_temp
    git fetch build_context_temp

    # merge the upstream code
    git merge --allow-unrelated-histories --signoff -S build_context_temp/master

[1]: https://github.com/docker/docker/d33d46d01656e1d9ee26743f0c0d7779f685dd4e

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-07-16 15:59:00 +02:00
8 changed files with 427 additions and 6 deletions

View File

@@ -1,241 +0,0 @@
package git
import (
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/moby/sys/symlink"
"github.com/pkg/errors"
)
type gitRepo struct {
remote string
ref string
subdir string
isolateConfig bool
}
// CloneOption changes the behaviour of Clone().
type CloneOption func(*gitRepo)
// WithIsolatedConfig disables reading the user or system gitconfig files when
// performing Git operations.
func WithIsolatedConfig(v bool) CloneOption {
return func(gr *gitRepo) {
gr.isolateConfig = v
}
}
// Clone clones a repository into a newly created directory which
// will be under "docker-build-git"
func Clone(remoteURL string, opts ...CloneOption) (string, error) {
repo, err := parseRemoteURL(remoteURL)
if err != nil {
return "", err
}
for _, opt := range opts {
opt(&repo)
}
return repo.clone()
}
func (repo gitRepo) clone() (checkoutDir string, retErr error) {
fetch := fetchArgs(repo.remote, repo.ref)
root, err := os.MkdirTemp("", "docker-build-git")
if err != nil {
return "", err
}
defer func() {
if retErr != nil {
_ = os.RemoveAll(root)
}
}()
if out, err := repo.gitWithinDir(root, "init"); err != nil {
return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out)
}
// Add origin remote for compatibility with previous implementation that
// used "git clone" and also to make sure local refs are created for branches
if out, err := repo.gitWithinDir(root, "remote", "add", "origin", repo.remote); err != nil {
return "", errors.Wrapf(err, "failed add origin repo at %s: %s", repo.remote, out)
}
if output, err := repo.gitWithinDir(root, fetch...); err != nil {
return "", errors.Wrapf(err, "error fetching: %s", output)
}
checkoutDir, err = repo.checkout(root)
if err != nil {
return "", err
}
cmd := exec.Command("git", "submodule", "update", "--init", "--recursive", "--depth=1")
cmd.Dir = root
output, err := cmd.CombinedOutput()
if err != nil {
return "", errors.Wrapf(err, "error initializing submodules: %s", output)
}
return checkoutDir, nil
}
func parseRemoteURL(remoteURL string) (gitRepo, error) {
repo := gitRepo{}
if !isGitTransport(remoteURL) {
remoteURL = "https://" + remoteURL
}
if strings.HasPrefix(remoteURL, "git@") {
// git@.. is not an URL, so cannot be parsed as URL
var fragment string
repo.remote, fragment, _ = strings.Cut(remoteURL, "#")
repo.ref, repo.subdir = getRefAndSubdir(fragment)
} else {
u, err := url.Parse(remoteURL)
if err != nil {
return repo, err
}
repo.ref, repo.subdir = getRefAndSubdir(u.Fragment)
u.Fragment = ""
repo.remote = u.String()
}
if strings.HasPrefix(repo.ref, "-") {
return gitRepo{}, errors.Errorf("invalid refspec: %s", repo.ref)
}
return repo, nil
}
func getRefAndSubdir(fragment string) (ref string, subdir string) {
ref, subdir, _ = strings.Cut(fragment, ":")
if ref == "" {
ref = "master"
}
return ref, subdir
}
func fetchArgs(remoteURL string, ref string) []string {
args := []string{"fetch"}
if supportsShallowClone(remoteURL) {
args = append(args, "--depth", "1")
}
return append(args, "origin", "--", ref)
}
// Check if a given git URL supports a shallow git clone,
// i.e. it is a non-HTTP server or a smart HTTP server.
func supportsShallowClone(remoteURL string) bool {
if scheme := getScheme(remoteURL); scheme == "http" || scheme == "https" {
// Check if the HTTP server is smart
// Smart servers must correctly respond to a query for the git-upload-pack service
serviceURL := remoteURL + "/info/refs?service=git-upload-pack"
// Try a HEAD request and fallback to a Get request on error
res, err := http.Head(serviceURL) // #nosec G107
if err != nil || res.StatusCode != http.StatusOK {
res, err = http.Get(serviceURL) // #nosec G107
if err == nil {
res.Body.Close()
}
if err != nil || res.StatusCode != http.StatusOK {
// request failed
return false
}
}
if res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
// Fallback, not a smart server
return false
}
return true
}
// Non-HTTP protocols always support shallow clones
return true
}
func (repo gitRepo) checkout(root string) (string, error) {
// Try checking out by ref name first. This will work on branches and sets
// .git/HEAD to the current branch name
if output, err := repo.gitWithinDir(root, "checkout", repo.ref); err != nil {
// If checking out by branch name fails check out the last fetched ref
if _, err2 := repo.gitWithinDir(root, "checkout", "FETCH_HEAD"); err2 != nil {
return "", errors.Wrapf(err, "error checking out %s: %s", repo.ref, output)
}
}
if repo.subdir != "" {
newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, repo.subdir), root)
if err != nil {
return "", errors.Wrapf(err, "error setting git context, %q not within git root", repo.subdir)
}
fi, err := os.Stat(newCtx)
if err != nil {
return "", err
}
if !fi.IsDir() {
return "", errors.Errorf("error setting git context, not a directory: %s", newCtx)
}
root = newCtx
}
return root, nil
}
func (repo gitRepo) gitWithinDir(dir string, args ...string) ([]byte, error) {
args = append([]string{"-c", "protocol.file.allow=never"}, args...) // Block sneaky repositories from using repos from the filesystem as submodules.
cmd := exec.Command("git", args...)
cmd.Dir = dir
// Disable unsafe remote protocols.
cmd.Env = append(os.Environ(), "GIT_PROTOCOL_FROM_USER=0")
if repo.isolateConfig {
cmd.Env = append(cmd.Env,
"GIT_CONFIG_NOSYSTEM=1", // Disable reading from system gitconfig.
"HOME=/dev/null", // Disable reading from user gitconfig.
)
}
return cmd.CombinedOutput()
}
// isGitTransport returns true if the provided str is a git transport by inspecting
// the prefix of the string for known protocols used in git.
func isGitTransport(str string) bool {
if strings.HasPrefix(str, "git@") {
return true
}
switch getScheme(str) {
case "git", "http", "https", "ssh":
return true
}
return false
}
// getScheme returns addresses' scheme in lowercase, or an empty
// string in case address is an invalid URL.
func getScheme(address string) string {
u, err := url.Parse(address)
if err != nil {
return ""
}
return u.Scheme
}

View File

@@ -1,87 +0,0 @@
// Package urlutil provides helper function to check if a given build-context
// location should be considered a URL or a remote Git repository.
//
// This package is specifically written for use with docker build contexts, and
// should not be used as a general-purpose utility.
package urlutil
import (
"strings"
"github.com/docker/docker/internal/lazyregexp"
)
// urlPathWithFragmentSuffix matches fragments to use as Git reference and build
// context from the Git repository. See IsGitURL for details.
var urlPathWithFragmentSuffix = lazyregexp.New(`\.git(?:#.+)?$`)
// IsURL returns true if the provided str is an HTTP(S) URL by checking if it
// has a http:// or https:// scheme. No validation is performed to verify if the
// URL is well-formed.
func IsURL(str string) bool {
return strings.HasPrefix(str, "https://") || strings.HasPrefix(str, "http://")
}
// IsGitURL returns true if the provided str is a remote git repository "URL".
//
// This function only performs a rudimentary check (no validation is performed
// to ensure the URL is well-formed), and is written specifically for use with
// docker build, with some logic for backward compatibility with older versions
// of docker: do not use this function as a general-purpose utility.
//
// The following patterns are considered to be a Git URL:
//
// - https://(.*).git(?:#.+)?$ git repository URL with optional fragment, as known to be used by GitHub and GitLab.
// - http://(.*).git(?:#.+)?$ same, but non-TLS
// - git://(.*) URLs using git:// scheme
// - git@(.*)
// - github.com/ see description below
//
// The github.com/ prefix is a special case used to treat context-paths
// starting with "github.com/" as a git URL if the given path does not
// exist locally. The "github.com/" prefix is kept for backward compatibility,
// and is a legacy feature.
//
// Going forward, no additional prefixes should be added, and users should
// be encouraged to use explicit URLs (https://github.com/user/repo.git) instead.
//
// Note that IsGitURL does not check if "github.com/" prefixes exist as a local
// path. Code using this function should check if the path exists locally before
// using it as a URL.
//
// # Fragments
//
// Git URLs accept context configuration in their fragment section, separated by
// a colon (`:`). The first part represents the reference to check out, and can
// be either a branch, a tag, or a remote reference. The second part represents
// a subdirectory inside the repository to use as the build context.
//
// For example,the following URL uses a directory named "docker" in the branch
// "container" in the https://github.com/myorg/my-repo.git repository:
//
// https://github.com/myorg/my-repo.git#container:docker
//
// The following table represents all the valid suffixes with their build
// contexts:
//
// | Build Syntax Suffix | Git reference used | Build Context Used |
// |--------------------------------|----------------------|--------------------|
// | my-repo.git | refs/heads/master | / |
// | my-repo.git#mytag | refs/tags/my-tag | / |
// | my-repo.git#mybranch | refs/heads/my-branch | / |
// | my-repo.git#pull/42/head | refs/pull/42/head | / |
// | my-repo.git#:directory | refs/heads/master | /directory |
// | my-repo.git#master:directory | refs/heads/master | /directory |
// | my-repo.git#mytag:directory | refs/tags/my-tag | /directory |
// | my-repo.git#mybranch:directory | refs/heads/my-branch | /directory |
func IsGitURL(str string) bool {
if IsURL(str) && urlPathWithFragmentSuffix.MatchString(str) {
return true
}
for _, prefix := range []string{"git://", "github.com/", "git@"} {
if strings.HasPrefix(str, prefix) {
return true
}
}
return false
}