github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/builder/remotecontext/git/gitutils_test.go (about)

     1  package git // import "github.com/demonoid81/moby/builder/remotecontext/git"
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  	"gotest.tools/v3/assert"
    18  	is "gotest.tools/v3/assert/cmp"
    19  )
    20  
    21  func TestParseRemoteURL(t *testing.T) {
    22  	tests := []struct {
    23  		doc      string
    24  		url      string
    25  		expected gitRepo
    26  	}{
    27  		{
    28  			doc: "git scheme uppercase, no url-fragment",
    29  			url: "GIT://github.com/user/repo.git",
    30  			expected: gitRepo{
    31  				remote: "git://github.com/user/repo.git",
    32  				ref:    "master",
    33  			},
    34  		},
    35  		{
    36  			doc: "git scheme, no url-fragment",
    37  			url: "git://github.com/user/repo.git",
    38  			expected: gitRepo{
    39  				remote: "git://github.com/user/repo.git",
    40  				ref:    "master",
    41  			},
    42  		},
    43  		{
    44  			doc: "git scheme, with url-fragment",
    45  			url: "git://github.com/user/repo.git#mybranch:mydir/mysubdir/",
    46  			expected: gitRepo{
    47  				remote: "git://github.com/user/repo.git",
    48  				ref:    "mybranch",
    49  				subdir: "mydir/mysubdir/",
    50  			},
    51  		},
    52  		{
    53  			doc: "https scheme, no url-fragment",
    54  			url: "https://github.com/user/repo.git",
    55  			expected: gitRepo{
    56  				remote: "https://github.com/user/repo.git",
    57  				ref:    "master",
    58  			},
    59  		},
    60  		{
    61  			doc: "https scheme, with url-fragment",
    62  			url: "https://github.com/user/repo.git#mybranch:mydir/mysubdir/",
    63  			expected: gitRepo{
    64  				remote: "https://github.com/user/repo.git",
    65  				ref:    "mybranch",
    66  				subdir: "mydir/mysubdir/",
    67  			},
    68  		},
    69  		{
    70  			doc: "git@, no url-fragment",
    71  			url: "git@github.com:user/repo.git",
    72  			expected: gitRepo{
    73  				remote: "git@github.com:user/repo.git",
    74  				ref:    "master",
    75  			},
    76  		},
    77  		{
    78  			doc: "git@, with url-fragment",
    79  			url: "git@github.com:user/repo.git#mybranch:mydir/mysubdir/",
    80  			expected: gitRepo{
    81  				remote: "git@github.com:user/repo.git",
    82  				ref:    "mybranch",
    83  				subdir: "mydir/mysubdir/",
    84  			},
    85  		},
    86  		{
    87  			doc: "ssh, no url-fragment",
    88  			url: "ssh://github.com/user/repo.git",
    89  			expected: gitRepo{
    90  				remote: "ssh://github.com/user/repo.git",
    91  				ref:    "master",
    92  			},
    93  		},
    94  		{
    95  			doc: "ssh, with url-fragment",
    96  			url: "ssh://github.com/user/repo.git#mybranch:mydir/mysubdir/",
    97  			expected: gitRepo{
    98  				remote: "ssh://github.com/user/repo.git",
    99  				ref:    "mybranch",
   100  				subdir: "mydir/mysubdir/",
   101  			},
   102  		},
   103  		{
   104  			doc: "ssh, with url-fragment and user",
   105  			url: "ssh://foo%40barcorp.com@github.com/user/repo.git#mybranch:mydir/mysubdir/",
   106  			expected: gitRepo{
   107  				remote: "ssh://foo%40barcorp.com@github.com/user/repo.git",
   108  				ref:    "mybranch",
   109  				subdir: "mydir/mysubdir/",
   110  			},
   111  		},
   112  	}
   113  
   114  	for _, tc := range tests {
   115  		tc := tc
   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/demonoid81/moby", "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 := git([]string{"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, err := ioutil.TempDir("", "docker-build-git-checkout")
   175  	assert.NilError(t, err)
   176  	defer os.RemoveAll(root)
   177  
   178  	autocrlf := gitGetConfig("core.autocrlf")
   179  	if !(autocrlf == "true" || autocrlf == "false" ||
   180  		autocrlf == "input" || autocrlf == "") {
   181  		t.Logf("unknown core.autocrlf value: \"%s\"", autocrlf)
   182  	}
   183  	eol := "\n"
   184  	if autocrlf == "true" {
   185  		eol = "\r\n"
   186  	}
   187  
   188  	gitDir := filepath.Join(root, "repo")
   189  	_, err = git("init", gitDir)
   190  	assert.NilError(t, err)
   191  
   192  	_, err = gitWithinDir(gitDir, "config", "user.email", "test@docker.com")
   193  	assert.NilError(t, err)
   194  
   195  	_, err = gitWithinDir(gitDir, "config", "user.name", "Docker test")
   196  	assert.NilError(t, err)
   197  
   198  	err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch"), 0644)
   199  	assert.NilError(t, err)
   200  
   201  	subDir := filepath.Join(gitDir, "subdir")
   202  	assert.NilError(t, os.Mkdir(subDir, 0755))
   203  
   204  	err = ioutil.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 5000"), 0644)
   205  	assert.NilError(t, err)
   206  
   207  	if runtime.GOOS != "windows" {
   208  		if err = os.Symlink("../subdir", filepath.Join(gitDir, "parentlink")); err != nil {
   209  			t.Fatal(err)
   210  		}
   211  
   212  		if err = os.Symlink("/subdir", filepath.Join(gitDir, "absolutelink")); err != nil {
   213  			t.Fatal(err)
   214  		}
   215  	}
   216  
   217  	_, err = gitWithinDir(gitDir, "add", "-A")
   218  	assert.NilError(t, err)
   219  
   220  	_, err = gitWithinDir(gitDir, "commit", "-am", "First commit")
   221  	assert.NilError(t, err)
   222  
   223  	_, err = gitWithinDir(gitDir, "checkout", "-b", "test")
   224  	assert.NilError(t, err)
   225  
   226  	err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 3000"), 0644)
   227  	assert.NilError(t, err)
   228  
   229  	err = ioutil.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM busybox\nEXPOSE 5000"), 0644)
   230  	assert.NilError(t, err)
   231  
   232  	_, err = gitWithinDir(gitDir, "add", "-A")
   233  	assert.NilError(t, err)
   234  
   235  	_, err = gitWithinDir(gitDir, "commit", "-am", "Branch commit")
   236  	assert.NilError(t, err)
   237  
   238  	_, err = gitWithinDir(gitDir, "checkout", "master")
   239  	assert.NilError(t, err)
   240  
   241  	// set up submodule
   242  	subrepoDir := filepath.Join(root, "subrepo")
   243  	_, err = git("init", subrepoDir)
   244  	assert.NilError(t, err)
   245  
   246  	_, err = gitWithinDir(subrepoDir, "config", "user.email", "test@docker.com")
   247  	assert.NilError(t, err)
   248  
   249  	_, err = gitWithinDir(subrepoDir, "config", "user.name", "Docker test")
   250  	assert.NilError(t, err)
   251  
   252  	err = ioutil.WriteFile(filepath.Join(subrepoDir, "subfile"), []byte("subcontents"), 0644)
   253  	assert.NilError(t, err)
   254  
   255  	_, err = gitWithinDir(subrepoDir, "add", "-A")
   256  	assert.NilError(t, err)
   257  
   258  	_, err = gitWithinDir(subrepoDir, "commit", "-am", "Subrepo initial")
   259  	assert.NilError(t, err)
   260  
   261  	cmd := exec.Command("git", "submodule", "add", subrepoDir, "sub") // this command doesn't work with --work-tree
   262  	cmd.Dir = gitDir
   263  	assert.NilError(t, cmd.Run())
   264  
   265  	_, err = gitWithinDir(gitDir, "add", "-A")
   266  	assert.NilError(t, err)
   267  
   268  	_, err = gitWithinDir(gitDir, "commit", "-am", "With submodule")
   269  	assert.NilError(t, err)
   270  
   271  	type singleCase struct {
   272  		frag      string
   273  		exp       string
   274  		fail      bool
   275  		submodule bool
   276  	}
   277  
   278  	cases := []singleCase{
   279  		{"", "FROM scratch", false, true},
   280  		{"master", "FROM scratch", false, true},
   281  		{":subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false},
   282  		{":nosubdir", "", true, false},   // missing directory error
   283  		{":Dockerfile", "", true, false}, // not a directory error
   284  		{"master:nosubdir", "", true, false},
   285  		{"master:subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false},
   286  		{"master:../subdir", "", true, false},
   287  		{"test", "FROM scratch" + eol + "EXPOSE 3000", false, false},
   288  		{"test:", "FROM scratch" + eol + "EXPOSE 3000", false, false},
   289  		{"test:subdir", "FROM busybox" + eol + "EXPOSE 5000", false, false},
   290  	}
   291  
   292  	if runtime.GOOS != "windows" {
   293  		// Windows GIT (2.7.1 x64) does not support parentlink/absolutelink. Sample output below
   294  		// 	git --work-tree .\repo --git-dir .\repo\.git add -A
   295  		//	error: readlink("absolutelink"): Function not implemented
   296  		// 	error: unable to index file absolutelink
   297  		// 	fatal: adding files failed
   298  		cases = append(cases, singleCase{frag: "master:absolutelink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false})
   299  		cases = append(cases, singleCase{frag: "master:parentlink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false})
   300  	}
   301  
   302  	for _, c := range cases {
   303  		ref, subdir := getRefAndSubdir(c.frag)
   304  		r, err := cloneGitRepo(gitRepo{remote: gitDir, ref: ref, subdir: subdir})
   305  
   306  		if c.fail {
   307  			assert.Check(t, is.ErrorContains(err, ""))
   308  			continue
   309  		}
   310  		assert.NilError(t, err)
   311  		defer os.RemoveAll(r)
   312  		if c.submodule {
   313  			b, err := ioutil.ReadFile(filepath.Join(r, "sub/subfile"))
   314  			assert.NilError(t, err)
   315  			assert.Check(t, is.Equal("subcontents", string(b)))
   316  		} else {
   317  			_, err := os.Stat(filepath.Join(r, "sub/subfile"))
   318  			assert.Assert(t, is.ErrorContains(err, ""))
   319  			assert.Assert(t, os.IsNotExist(err))
   320  		}
   321  
   322  		b, err := ioutil.ReadFile(filepath.Join(r, "Dockerfile"))
   323  		assert.NilError(t, err)
   324  		assert.Check(t, is.Equal(c.exp, string(b)))
   325  	}
   326  }
   327  
   328  func TestValidGitTransport(t *testing.T) {
   329  	gitUrls := []string{
   330  		"git://github.com/demonoid81/moby",
   331  		"git@github.com:docker/docker.git",
   332  		"git@bitbucket.org:atlassianlabs/atlassian-docker.git",
   333  		"https://github.com/demonoid81/moby.git",
   334  		"http://github.com/demonoid81/moby.git",
   335  		"http://github.com/demonoid81/moby.git#branch",
   336  		"http://github.com/demonoid81/moby.git#:dir",
   337  	}
   338  	incompleteGitUrls := []string{
   339  		"github.com/demonoid81/moby",
   340  	}
   341  
   342  	for _, url := range gitUrls {
   343  		if !isGitTransport(url) {
   344  			t.Fatalf("%q should be detected as valid Git prefix", url)
   345  		}
   346  	}
   347  
   348  	for _, url := range incompleteGitUrls {
   349  		if isGitTransport(url) {
   350  			t.Fatalf("%q should not be detected as valid Git prefix", url)
   351  		}
   352  	}
   353  }
   354  
   355  func TestGitInvalidRef(t *testing.T) {
   356  	gitUrls := []string{
   357  		"git://github.com/moby/moby#--foo bar",
   358  		"git@github.com/moby/moby#--upload-pack=sleep;:",
   359  		"git@g.com:a/b.git#-B",
   360  		"git@g.com:a/b.git#with space",
   361  	}
   362  
   363  	for _, url := range gitUrls {
   364  		_, err := Clone(url)
   365  		assert.Assert(t, err != nil)
   366  		assert.Check(t, is.Contains(strings.ToLower(err.Error()), "invalid refspec"))
   367  	}
   368  }