Skip to content

Commit ed9ea2c

Browse files
committed
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>
2 parents b5a9392 + 3b25977 commit ed9ea2c

8 files changed

Lines changed: 427 additions & 6 deletions

File tree

cli/command/image/build/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"strings"
1717
"time"
1818

19-
"github.com/docker/docker/builder/remotecontext/git"
19+
"github.com/docker/cli/cli/command/image/build/internal/git"
2020
"github.com/docker/docker/pkg/progress"
2121
"github.com/docker/docker/pkg/streamformatter"
2222
"github.com/moby/go-archive"

cli/command/image/build/context_detect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"fmt"
55
"os"
66

7-
"github.com/docker/docker/builder/remotecontext/urlutil"
7+
"github.com/docker/cli/cli/command/image/build/internal/urlutil"
88
)
99

1010
// ContextType describes the type (source) of build-context specified.

vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go renamed to cli/command/image/build/internal/git/gitutils.go

File renamed without changes.
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
package git
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net/http"
7+
"net/http/cgi"
8+
"net/http/httptest"
9+
"net/url"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
13+
"runtime"
14+
"strings"
15+
"testing"
16+
17+
"github.com/google/go-cmp/cmp"
18+
"gotest.tools/v3/assert"
19+
is "gotest.tools/v3/assert/cmp"
20+
)
21+
22+
func TestParseRemoteURL(t *testing.T) {
23+
tests := []struct {
24+
doc string
25+
url string
26+
expected gitRepo
27+
}{
28+
{
29+
doc: "git scheme uppercase, no url-fragment",
30+
url: "GIT://github.com/user/repo.git",
31+
expected: gitRepo{
32+
remote: "git://github.com/user/repo.git",
33+
ref: "master",
34+
},
35+
},
36+
{
37+
doc: "git scheme, no url-fragment",
38+
url: "git://github.com/user/repo.git",
39+
expected: gitRepo{
40+
remote: "git://github.com/user/repo.git",
41+
ref: "master",
42+
},
43+
},
44+
{
45+
doc: "git scheme, with url-fragment",
46+
url: "git://github.com/user/repo.git#mybranch:mydir/mysubdir/",
47+
expected: gitRepo{
48+
remote: "git://github.com/user/repo.git",
49+
ref: "mybranch",
50+
subdir: "mydir/mysubdir/",
51+
},
52+
},
53+
{
54+
doc: "https scheme, no url-fragment",
55+
url: "https://github.com/user/repo.git",
56+
expected: gitRepo{
57+
remote: "https://github.com/user/repo.git",
58+
ref: "master",
59+
},
60+
},
61+
{
62+
doc: "https scheme, with url-fragment",
63+
url: "https://github.com/user/repo.git#mybranch:mydir/mysubdir/",
64+
expected: gitRepo{
65+
remote: "https://github.com/user/repo.git",
66+
ref: "mybranch",
67+
subdir: "mydir/mysubdir/",
68+
},
69+
},
70+
{
71+
doc: "git@, no url-fragment",
72+
url: "git@github.com:user/repo.git",
73+
expected: gitRepo{
74+
remote: "git@github.com:user/repo.git",
75+
ref: "master",
76+
},
77+
},
78+
{
79+
doc: "git@, with url-fragment",
80+
url: "git@github.com:user/repo.git#mybranch:mydir/mysubdir/",
81+
expected: gitRepo{
82+
remote: "git@github.com:user/repo.git",
83+
ref: "mybranch",
84+
subdir: "mydir/mysubdir/",
85+
},
86+
},
87+
{
88+
doc: "ssh, no url-fragment",
89+
url: "ssh://github.com/user/repo.git",
90+
expected: gitRepo{
91+
remote: "ssh://github.com/user/repo.git",
92+
ref: "master",
93+
},
94+
},
95+
{
96+
doc: "ssh, with url-fragment",
97+
url: "ssh://github.com/user/repo.git#mybranch:mydir/mysubdir/",
98+
expected: gitRepo{
99+
remote: "ssh://github.com/user/repo.git",
100+
ref: "mybranch",
101+
subdir: "mydir/mysubdir/",
102+
},
103+
},
104+
{
105+
doc: "ssh, with url-fragment and user",
106+
url: "ssh://foo%40barcorp.com@github.com/user/repo.git#mybranch:mydir/mysubdir/",
107+
expected: gitRepo{
108+
remote: "ssh://foo%40barcorp.com@github.com/user/repo.git",
109+
ref: "mybranch",
110+
subdir: "mydir/mysubdir/",
111+
},
112+
},
113+
}
114+
115+
for _, tc := range tests {
116+
t.Run(tc.doc, func(t *testing.T) {
117+
repo, err := parseRemoteURL(tc.url)
118+
assert.NilError(t, err)
119+
assert.Check(t, is.DeepEqual(tc.expected, repo, cmp.AllowUnexported(gitRepo{})))
120+
})
121+
}
122+
}
123+
124+
func TestCloneArgsSmartHttp(t *testing.T) {
125+
mux := http.NewServeMux()
126+
server := httptest.NewServer(mux)
127+
serverURL, _ := url.Parse(server.URL)
128+
129+
serverURL.Path = "/repo.git"
130+
131+
mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) {
132+
q := r.URL.Query().Get("service")
133+
w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q))
134+
})
135+
136+
args := fetchArgs(serverURL.String(), "master")
137+
exp := []string{"fetch", "--depth", "1", "origin", "--", "master"}
138+
assert.Check(t, is.DeepEqual(exp, args))
139+
}
140+
141+
func TestCloneArgsDumbHttp(t *testing.T) {
142+
mux := http.NewServeMux()
143+
server := httptest.NewServer(mux)
144+
serverURL, _ := url.Parse(server.URL)
145+
146+
serverURL.Path = "/repo.git"
147+
148+
mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) {
149+
w.Header().Set("Content-Type", "text/plain")
150+
})
151+
152+
args := fetchArgs(serverURL.String(), "master")
153+
exp := []string{"fetch", "origin", "--", "master"}
154+
assert.Check(t, is.DeepEqual(exp, args))
155+
}
156+
157+
func TestCloneArgsGit(t *testing.T) {
158+
args := fetchArgs("git://github.com/docker/docker", "master")
159+
exp := []string{"fetch", "--depth", "1", "origin", "--", "master"}
160+
assert.Check(t, is.DeepEqual(exp, args))
161+
}
162+
163+
func gitGetConfig(name string) string {
164+
b, err := gitRepo{}.gitWithinDir("", "config", "--get", name)
165+
if err != nil {
166+
// since we are interested in empty or non empty string,
167+
// we can safely ignore the err here.
168+
return ""
169+
}
170+
return strings.TrimSpace(string(b))
171+
}
172+
173+
func TestCheckoutGit(t *testing.T) {
174+
root := t.TempDir()
175+
176+
gitpath, err := exec.LookPath("git")
177+
assert.NilError(t, err)
178+
gitversion, _ := exec.Command(gitpath, "version").CombinedOutput()
179+
t.Logf("%s", gitversion) // E.g. "git version 2.30.2"
180+
181+
// Serve all repositories under root using the Smart HTTP protocol so
182+
// they can be cloned. The Dumb HTTP protocol is incompatible with
183+
// shallow cloning but we unconditionally shallow-clone submodules, and
184+
// we explicitly disable the file protocol.
185+
// (Another option would be to use `git daemon` and the Git protocol,
186+
// but that listens on a fixed port number which is a recipe for
187+
// disaster in CI. Funnily enough, `git daemon --port=0` works but there
188+
// is no easy way to discover which port got picked!)
189+
190+
// Associate git-http-backend logs with the current (sub)test.
191+
// Incompatible with parallel subtests.
192+
currentSubtest := t
193+
githttp := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
194+
var logs bytes.Buffer
195+
(&cgi.Handler{
196+
Path: gitpath,
197+
Args: []string{"http-backend"},
198+
Dir: root,
199+
Env: []string{
200+
"GIT_PROJECT_ROOT=" + root,
201+
"GIT_HTTP_EXPORT_ALL=1",
202+
},
203+
Stderr: &logs,
204+
}).ServeHTTP(w, r)
205+
if logs.Len() == 0 {
206+
return
207+
}
208+
for {
209+
line, err := logs.ReadString('\n')
210+
currentSubtest.Log("git-http-backend: " + line)
211+
if err != nil {
212+
break
213+
}
214+
}
215+
})
216+
server := httptest.NewServer(&githttp)
217+
defer server.Close()
218+
219+
eol := "\n"
220+
autocrlf := gitGetConfig("core.autocrlf")
221+
switch autocrlf {
222+
case "true":
223+
eol = "\r\n"
224+
case "false", "input", "":
225+
// accepted values
226+
default:
227+
t.Logf(`unknown core.autocrlf value: "%s"`, autocrlf)
228+
}
229+
230+
must := func(out []byte, err error) {
231+
t.Helper()
232+
if len(out) > 0 {
233+
t.Logf("%s", out)
234+
}
235+
assert.NilError(t, err)
236+
}
237+
238+
gitDir := filepath.Join(root, "repo")
239+
must(gitRepo{}.gitWithinDir(root, "-c", "init.defaultBranch=master", "init", gitDir))
240+
must(gitRepo{}.gitWithinDir(gitDir, "config", "user.email", "test@docker.com"))
241+
must(gitRepo{}.gitWithinDir(gitDir, "config", "user.name", "Docker test"))
242+
assert.NilError(t, os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch"), 0o644))
243+
244+
subDir := filepath.Join(gitDir, "subdir")
245+
assert.NilError(t, os.Mkdir(subDir, 0o755))
246+
assert.NilError(t, os.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 5000"), 0o644))
247+
248+
if runtime.GOOS != "windows" {
249+
assert.NilError(t, os.Symlink("../subdir", filepath.Join(gitDir, "parentlink")))
250+
assert.NilError(t, os.Symlink("/subdir", filepath.Join(gitDir, "absolutelink")))
251+
}
252+
253+
must(gitRepo{}.gitWithinDir(gitDir, "add", "-A"))
254+
must(gitRepo{}.gitWithinDir(gitDir, "commit", "-am", "First commit"))
255+
must(gitRepo{}.gitWithinDir(gitDir, "checkout", "-b", "test"))
256+
257+
assert.NilError(t, os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 3000"), 0o644))
258+
assert.NilError(t, os.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM busybox\nEXPOSE 5000"), 0o644))
259+
260+
must(gitRepo{}.gitWithinDir(gitDir, "add", "-A"))
261+
must(gitRepo{}.gitWithinDir(gitDir, "commit", "-am", "Branch commit"))
262+
must(gitRepo{}.gitWithinDir(gitDir, "checkout", "master"))
263+
264+
// set up submodule
265+
subrepoDir := filepath.Join(root, "subrepo")
266+
must(gitRepo{}.gitWithinDir(root, "-c", "init.defaultBranch=master", "init", subrepoDir))
267+
must(gitRepo{}.gitWithinDir(subrepoDir, "config", "user.email", "test@docker.com"))
268+
must(gitRepo{}.gitWithinDir(subrepoDir, "config", "user.name", "Docker test"))
269+
270+
assert.NilError(t, os.WriteFile(filepath.Join(subrepoDir, "subfile"), []byte("subcontents"), 0o644))
271+
272+
must(gitRepo{}.gitWithinDir(subrepoDir, "add", "-A"))
273+
must(gitRepo{}.gitWithinDir(subrepoDir, "commit", "-am", "Subrepo initial"))
274+
275+
must(gitRepo{}.gitWithinDir(gitDir, "submodule", "add", server.URL+"/subrepo", "sub"))
276+
must(gitRepo{}.gitWithinDir(gitDir, "add", "-A"))
277+
must(gitRepo{}.gitWithinDir(gitDir, "commit", "-am", "With submodule"))
278+
279+
type singleCase struct {
280+
frag string
281+
exp string
282+
fail bool
283+
submodule bool
284+
}
285+
286+
cases := []singleCase{
287+
{"", "FROM scratch", false, true},
288+
{"master", "FROM scratch", false, true},
289+
{":subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false},
290+
{":nosubdir", "", true, false}, // missing directory error
291+
{":Dockerfile", "", true, false}, // not a directory error
292+
{"master:nosubdir", "", true, false},
293+
{"master:subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false},
294+
{"master:../subdir", "", true, false},
295+
{"test", "FROM scratch" + eol + "EXPOSE 3000", false, false},
296+
{"test:", "FROM scratch" + eol + "EXPOSE 3000", false, false},
297+
{"test:subdir", "FROM busybox" + eol + "EXPOSE 5000", false, false},
298+
}
299+
300+
if runtime.GOOS != "windows" {
301+
// Windows GIT (2.7.1 x64) does not support parentlink/absolutelink. Sample output below
302+
// git --work-tree .\repo --git-dir .\repo\.git add -A
303+
// error: readlink("absolutelink"): Function not implemented
304+
// error: unable to index file absolutelink
305+
// fatal: adding files failed
306+
cases = append(cases, singleCase{frag: "master:absolutelink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false})
307+
cases = append(cases, singleCase{frag: "master:parentlink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false})
308+
}
309+
310+
for _, c := range cases {
311+
t.Run(c.frag, func(t *testing.T) {
312+
currentSubtest = t
313+
ref, subdir := getRefAndSubdir(c.frag)
314+
r, err := gitRepo{remote: server.URL + "/repo", ref: ref, subdir: subdir}.clone()
315+
316+
if c.fail {
317+
assert.Check(t, is.ErrorContains(err, ""))
318+
return
319+
}
320+
assert.NilError(t, err)
321+
defer os.RemoveAll(r)
322+
if c.submodule {
323+
b, err := os.ReadFile(filepath.Join(r, "sub/subfile"))
324+
assert.NilError(t, err)
325+
assert.Check(t, is.Equal("subcontents", string(b)))
326+
} else {
327+
_, err := os.Stat(filepath.Join(r, "sub/subfile"))
328+
assert.ErrorContains(t, err, "")
329+
assert.Assert(t, os.IsNotExist(err))
330+
}
331+
332+
b, err := os.ReadFile(filepath.Join(r, "Dockerfile"))
333+
assert.NilError(t, err)
334+
assert.Check(t, is.Equal(c.exp, string(b)))
335+
})
336+
}
337+
}
338+
339+
func TestValidGitTransport(t *testing.T) {
340+
gitUrls := []string{
341+
"git://github.com/docker/docker",
342+
"git@github.com:docker/docker.git",
343+
"git@bitbucket.org:atlassianlabs/atlassian-docker.git",
344+
"https://github.com/docker/docker.git",
345+
"http://github.com/docker/docker.git",
346+
"http://github.com/docker/docker.git#branch",
347+
"http://github.com/docker/docker.git#:dir",
348+
}
349+
incompleteGitUrls := []string{
350+
"github.com/docker/docker",
351+
}
352+
353+
for _, u := range gitUrls {
354+
if !isGitTransport(u) {
355+
t.Fatalf("%q should be detected as valid Git prefix", u)
356+
}
357+
}
358+
359+
for _, u := range incompleteGitUrls {
360+
if isGitTransport(u) {
361+
t.Fatalf("%q should not be detected as valid Git prefix", u)
362+
}
363+
}
364+
}
365+
366+
func TestGitInvalidRef(t *testing.T) {
367+
gitUrls := []string{
368+
"git://github.com/moby/moby#--foo bar",
369+
"git@github.com/moby/moby#--upload-pack=sleep;:",
370+
"git@g.com:a/b.git#-B",
371+
"git@g.com:a/b.git#with space",
372+
}
373+
374+
for _, u := range gitUrls {
375+
_, err := Clone(u)
376+
assert.Assert(t, err != nil)
377+
// On Windows, git has different case for the "invalid refspec" error,
378+
// so we can't use ErrorContains.
379+
assert.Check(t, is.Contains(strings.ToLower(err.Error()), "invalid refspec"))
380+
}
381+
}

0 commit comments

Comments
 (0)