mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Earlier versions of this code allowed "ref" to take any value that would be accepted by "git checkout" as a valid target of a symbolic ref. We inadvertently accepted a breaking change to upstream go-getter that broke that as part of introducing a shallow clone optimization, because shallow clone requires selecting a single branch. To restore the previous capabilities while retaining the "depth" argument, here we accept a compromise where "ref" has the stronger requirement of being a valid named ref in the remote repository if and only if "depth" is set to a value greater than zero. If depth isn't set or is less than one, we will do the old behavior of just cloning all of the refs in the remote repository in full and then switching to refer to the selected branch, tag, or naked commit ID as a separate step. This includes a heuristic to generate an additional error message hint if we get an error from "git clone" and it looks like the user might've been trying to use "depth" and "ref=COMMIT" together. We can't recognize that error accurately because it's only reported as human-oriented git command output, but this heuristic should hopefully minimize situations where we show it inappropriately. For now this is a change in the Terraform repository directly, so that we can expedite the fix to an already-reported regression. After this is released I tend to also submit a similar set of changes to upstream go-getter, at which point we can revert Terraform to using the upstream getter.GitGetter instead of our own local fork.
828 lines
20 KiB
Go
828 lines
20 KiB
Go
package getmodules
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
getter "github.com/hashicorp/go-getter"
|
|
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
|
)
|
|
|
|
var testHasGit bool
|
|
|
|
func init() {
|
|
if _, err := exec.LookPath("git"); err == nil {
|
|
testHasGit = true
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_impl(t *testing.T) {
|
|
var _ getter.Getter = new(gitGetter)
|
|
}
|
|
|
|
func TestGitGetter(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "basic")
|
|
repo.commitFile("foo.txt", "hello")
|
|
|
|
// With a dir that doesn't exist
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath := filepath.Join(dst, "foo.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_branch(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "branch")
|
|
repo.git("checkout", "-b", "test-branch")
|
|
repo.commitFile("branch.txt", "branch")
|
|
|
|
q := repo.url.Query()
|
|
q.Add("ref", "test-branch")
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath := filepath.Join(dst, "branch.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Get again should work
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath = filepath.Join(dst, "branch.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_commitID(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
// We're going to create different content on the main branch vs.
|
|
// another branch here, so that below we can recognize if we
|
|
// correctly cloned the commit actually requested (from the
|
|
// "other branch"), not the one at HEAD.
|
|
repo := testGitRepo(t, "commit_id")
|
|
repo.git("checkout", "-b", "main-branch")
|
|
repo.commitFile("wrong.txt", "Nope")
|
|
repo.git("checkout", "-b", "other-branch")
|
|
repo.commitFile("hello.txt", "Yep")
|
|
commitID, err := repo.latestCommit()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Return to the main branch so that HEAD of this repository
|
|
// will be that, rather than "test-branch".
|
|
repo.git("checkout", "main-branch")
|
|
|
|
q := repo.url.Query()
|
|
q.Add("ref", commitID)
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
t.Logf("Getting %s", repo.url)
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath := filepath.Join(dst, "hello.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Get again should work
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath = filepath.Join(dst, "hello.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_remoteWithoutMaster(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Log("git not found, skipping")
|
|
t.Skip()
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "branch")
|
|
repo.git("checkout", "-b", "test-branch")
|
|
repo.commitFile("branch.txt", "branch")
|
|
|
|
q := repo.url.Query()
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath := filepath.Join(dst, "branch.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Get again should work
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath = filepath.Join(dst, "branch.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_shallowClone(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Log("git not found, skipping")
|
|
t.Skip()
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "upstream")
|
|
repo.commitFile("upstream.txt", "0")
|
|
repo.commitFile("upstream.txt", "1")
|
|
|
|
// Specifiy a clone depth of 1
|
|
q := repo.url.Query()
|
|
q.Add("depth", "1")
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Assert rev-list count is '1'
|
|
cmd := exec.Command("git", "rev-list", "HEAD", "--count")
|
|
cmd.Dir = dst
|
|
b, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
out := strings.TrimSpace(string(b))
|
|
if out != "1" {
|
|
t.Fatalf("expected rev-list count to be '1' but got %v", out)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_shallowCloneWithTag(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Log("git not found, skipping")
|
|
t.Skip()
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "upstream")
|
|
repo.commitFile("v1.0.txt", "0")
|
|
repo.git("tag", "v1.0")
|
|
repo.commitFile("v1.1.txt", "1")
|
|
|
|
// Specifiy a clone depth of 1 with a tag
|
|
q := repo.url.Query()
|
|
q.Add("ref", "v1.0")
|
|
q.Add("depth", "1")
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Assert rev-list count is '1'
|
|
cmd := exec.Command("git", "rev-list", "HEAD", "--count")
|
|
cmd.Dir = dst
|
|
b, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
out := strings.TrimSpace(string(b))
|
|
if out != "1" {
|
|
t.Fatalf("expected rev-list count to be '1' but got %v", out)
|
|
}
|
|
|
|
// Verify the v1.0 file exists
|
|
mainPath := filepath.Join(dst, "v1.0.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the v1.1 file does not exists
|
|
mainPath = filepath.Join(dst, "v1.1.txt")
|
|
if _, err := os.Stat(mainPath); err == nil {
|
|
t.Fatalf("expected v1.1 file to not exist")
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_shallowCloneWithCommitID(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Log("git not found, skipping")
|
|
t.Skip()
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "upstream")
|
|
repo.commitFile("v1.0.txt", "0")
|
|
repo.git("tag", "v1.0")
|
|
repo.commitFile("v1.1.txt", "1")
|
|
|
|
commitID, err := repo.latestCommit()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Specify a clone depth of 1 with a naked commit ID
|
|
// This is intentionally invalid: shallow clone always requires a named ref.
|
|
q := repo.url.Query()
|
|
q.Add("ref", commitID[:8])
|
|
q.Add("depth", "1")
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
t.Logf("Getting %s", repo.url)
|
|
err = g.Get(dst, repo.url)
|
|
if err == nil {
|
|
t.Fatalf("success; want error")
|
|
}
|
|
// We use a heuristic to generate an extra hint in the error message if
|
|
// it looks like the user was trying to combine ref=COMMIT with depth.
|
|
if got, want := err.Error(), "(note that setting 'depth' requires 'ref' to be a branch or tag name)"; !strings.Contains(got, want) {
|
|
t.Errorf("missing error message hint\ngot: %s\nwant substring: %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_branchUpdate(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
// First setup the state with a fresh branch
|
|
repo := testGitRepo(t, "branch-update")
|
|
repo.git("checkout", "-b", "test-branch")
|
|
repo.commitFile("branch.txt", "branch")
|
|
|
|
// Get the "test-branch" branch
|
|
q := repo.url.Query()
|
|
q.Add("ref", "test-branch")
|
|
repo.url.RawQuery = q.Encode()
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath := filepath.Join(dst, "branch.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Commit an update to the branch
|
|
repo.commitFile("branch-update.txt", "branch-update")
|
|
|
|
// Get again should work
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath = filepath.Join(dst, "branch-update.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_tag(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
repo := testGitRepo(t, "tag")
|
|
repo.commitFile("tag.txt", "tag")
|
|
repo.git("tag", "v1.0")
|
|
|
|
q := repo.url.Query()
|
|
q.Add("ref", "v1.0")
|
|
repo.url.RawQuery = q.Encode()
|
|
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath := filepath.Join(dst, "tag.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Get again should work
|
|
if err := g.Get(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
mainPath = filepath.Join(dst, "tag.txt")
|
|
if _, err := os.Stat(mainPath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_GetFile(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempTestFile(t)
|
|
defer os.RemoveAll(filepath.Dir(dst))
|
|
|
|
repo := testGitRepo(t, "file")
|
|
repo.commitFile("file.txt", "hello")
|
|
|
|
// Download the file
|
|
repo.url.Path = filepath.Join(repo.url.Path, "file.txt")
|
|
if err := g.GetFile(dst, repo.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Verify the main file exists
|
|
if _, err := os.Stat(dst); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
assertContents(t, dst, "hello")
|
|
}
|
|
|
|
func TestGitGetter_gitVersion(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping on windows since the test requires sh")
|
|
}
|
|
dir, err := ioutil.TempDir("", "go-getter")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
script := filepath.Join(dir, "git")
|
|
err = ioutil.WriteFile(
|
|
script,
|
|
[]byte("#!/bin/sh\necho \"git version 2.0 (Some Metadata Here)\n\""),
|
|
0700)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func(v string) {
|
|
os.Setenv("PATH", v)
|
|
}(os.Getenv("PATH"))
|
|
|
|
os.Setenv("PATH", dir)
|
|
|
|
// Asking for a higher version throws an error
|
|
if err := checkGitVersion("2.3"); err == nil {
|
|
t.Fatal("expect git version error")
|
|
}
|
|
|
|
// Passes when version is satisfied
|
|
if err := checkGitVersion("1.9"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_sshKey(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken))
|
|
|
|
// avoid getting locked by a github authenticity validation prompt
|
|
os.Setenv("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes")
|
|
defer os.Setenv("GIT_SSH_COMMAND", "")
|
|
|
|
u, err := urlhelper.Parse("ssh://git@github.com/hashicorp/test-private-repo" +
|
|
"?sshkey=" + encodedKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := g.Get(dst, u); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
readmePath := filepath.Join(dst, "README.md")
|
|
if _, err := os.Stat(readmePath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_sshSCPStyle(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken))
|
|
|
|
// avoid getting locked by a github authenticity validation prompt
|
|
os.Setenv("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes")
|
|
defer os.Setenv("GIT_SSH_COMMAND", "")
|
|
|
|
// This test exercises the combination of the git detector and the
|
|
// git getter, to make sure that together they make scp-style URLs work.
|
|
client := &getter.Client{
|
|
Src: "git@github.com:hashicorp/test-private-repo?sshkey=" + encodedKey,
|
|
Dst: dst,
|
|
Pwd: ".",
|
|
|
|
Mode: getter.ClientModeDir,
|
|
|
|
Detectors: []getter.Detector{
|
|
new(getter.GitDetector),
|
|
},
|
|
Getters: map[string]getter.Getter{
|
|
"git": g,
|
|
},
|
|
}
|
|
|
|
if err := client.Get(); err != nil {
|
|
t.Fatalf("client.Get failed: %s", err)
|
|
}
|
|
|
|
readmePath := filepath.Join(dst, "README.md")
|
|
if _, err := os.Stat(readmePath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_sshExplicitPort(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken))
|
|
|
|
// avoid getting locked by a github authenticity validation prompt
|
|
os.Setenv("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes")
|
|
defer os.Setenv("GIT_SSH_COMMAND", "")
|
|
|
|
// This test exercises the combination of the git detector and the
|
|
// git getter, to make sure that together they make scp-style URLs work.
|
|
client := &getter.Client{
|
|
Src: "git::ssh://git@github.com:22/hashicorp/test-private-repo?sshkey=" + encodedKey,
|
|
Dst: dst,
|
|
Pwd: ".",
|
|
|
|
Mode: getter.ClientModeDir,
|
|
|
|
Detectors: []getter.Detector{
|
|
new(getter.GitDetector),
|
|
},
|
|
Getters: map[string]getter.Getter{
|
|
"git": g,
|
|
},
|
|
}
|
|
|
|
if err := client.Get(); err != nil {
|
|
t.Fatalf("client.Get failed: %s", err)
|
|
}
|
|
|
|
readmePath := filepath.Join(dst, "README.md")
|
|
if _, err := os.Stat(readmePath); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_sshSCPStyleInvalidScheme(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken))
|
|
|
|
// avoid getting locked by a github authenticity validation prompt
|
|
os.Setenv("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes")
|
|
defer os.Setenv("GIT_SSH_COMMAND", "")
|
|
|
|
// This test exercises the combination of the git detector and the
|
|
// git getter, to make sure that together they make scp-style URLs work.
|
|
client := &getter.Client{
|
|
Src: "git::ssh://git@github.com:hashicorp/test-private-repo?sshkey=" + encodedKey,
|
|
Dst: dst,
|
|
Pwd: ".",
|
|
|
|
Mode: getter.ClientModeDir,
|
|
|
|
Detectors: []getter.Detector{
|
|
new(getter.GitDetector),
|
|
},
|
|
Getters: map[string]getter.Getter{
|
|
"git": g,
|
|
},
|
|
}
|
|
|
|
err := client.Get()
|
|
if err == nil {
|
|
t.Fatalf("get succeeded; want error")
|
|
}
|
|
|
|
got := err.Error()
|
|
want1, want2 := `invalid source string`, `invalid port number "hashicorp"`
|
|
if !(strings.Contains(got, want1) || strings.Contains(got, want2)) {
|
|
t.Fatalf("wrong error\ngot: %s\nwant: %q or %q", got, want1, want2)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_submodule(t *testing.T) {
|
|
if !testHasGit {
|
|
t.Skip("git not found, skipping")
|
|
}
|
|
|
|
g := new(gitGetter)
|
|
dst := tempDir(t)
|
|
|
|
relpath := func(basepath, targpath string) string {
|
|
relpath, err := filepath.Rel(basepath, targpath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return strings.Replace(relpath, `\`, `/`, -1)
|
|
// on windows git still prefers relatives paths
|
|
// containing `/` for submodules
|
|
}
|
|
|
|
// Set up the grandchild
|
|
gc := testGitRepo(t, "grandchild")
|
|
gc.commitFile("grandchild.txt", "grandchild")
|
|
|
|
// Set up the child
|
|
c := testGitRepo(t, "child")
|
|
c.commitFile("child.txt", "child")
|
|
c.git("submodule", "add", "-f", relpath(c.dir, gc.dir))
|
|
c.git("commit", "-m", "Add grandchild submodule")
|
|
|
|
// Set up the parent
|
|
p := testGitRepo(t, "parent")
|
|
p.commitFile("parent.txt", "parent")
|
|
p.git("submodule", "add", "-f", relpath(p.dir, c.dir))
|
|
p.git("commit", "-m", "Add child submodule")
|
|
|
|
// Clone the root repository
|
|
if err := g.Get(dst, p.url); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check that the files exist
|
|
for _, path := range []string{
|
|
filepath.Join(dst, "parent.txt"),
|
|
filepath.Join(dst, "child", "child.txt"),
|
|
filepath.Join(dst, "child", "grandchild", "grandchild.txt"),
|
|
} {
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_setupGitEnv_sshKey(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping on windows since the test requires sh")
|
|
}
|
|
|
|
cmd := exec.Command("/bin/sh", "-c", "echo $GIT_SSH_COMMAND")
|
|
setupGitEnv(cmd, "/tmp/foo.pem")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(string(out))
|
|
if actual != "ssh -i /tmp/foo.pem" {
|
|
t.Fatalf("unexpected GIT_SSH_COMMAND: %q", actual)
|
|
}
|
|
}
|
|
|
|
func TestGitGetter_setupGitEnvWithExisting_sshKey(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skipf("skipping on windows since the test requires sh")
|
|
return
|
|
}
|
|
|
|
// start with an existing ssh command configuration
|
|
os.Setenv("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes")
|
|
defer os.Setenv("GIT_SSH_COMMAND", "")
|
|
|
|
cmd := exec.Command("/bin/sh", "-c", "echo $GIT_SSH_COMMAND")
|
|
setupGitEnv(cmd, "/tmp/foo.pem")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(string(out))
|
|
if actual != "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i /tmp/foo.pem" {
|
|
t.Fatalf("unexpected GIT_SSH_COMMAND: %q", actual)
|
|
}
|
|
}
|
|
|
|
// gitRepo is a helper struct which controls a single temp git repo.
|
|
type gitRepo struct {
|
|
t *testing.T
|
|
url *url.URL
|
|
dir string
|
|
}
|
|
|
|
// testGitRepo creates a new test git repository.
|
|
func testGitRepo(t *testing.T, name string) *gitRepo {
|
|
t.Helper()
|
|
dir, err := ioutil.TempDir("", "go-getter")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir = filepath.Join(dir, name)
|
|
if err := os.Mkdir(dir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := &gitRepo{
|
|
t: t,
|
|
dir: dir,
|
|
}
|
|
|
|
url, err := urlhelper.Parse("file://" + r.dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.url = url
|
|
|
|
t.Logf("initializing git repo in %s", dir)
|
|
r.git("init")
|
|
r.git("config", "user.name", "go-getter")
|
|
r.git("config", "user.email", "go-getter@hashicorp.com")
|
|
|
|
return r
|
|
}
|
|
|
|
// git runs a git command against the repo.
|
|
func (r *gitRepo) git(args ...string) {
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = r.dir
|
|
bfr := bytes.NewBuffer(nil)
|
|
cmd.Stderr = bfr
|
|
if err := cmd.Run(); err != nil {
|
|
r.t.Fatal(err, bfr.String())
|
|
}
|
|
}
|
|
|
|
// commitFile writes and commits a text file to the repo.
|
|
func (r *gitRepo) commitFile(file, content string) {
|
|
path := filepath.Join(r.dir, file)
|
|
if err := ioutil.WriteFile(path, []byte(content), 0600); err != nil {
|
|
r.t.Fatal(err)
|
|
}
|
|
r.git("add", file)
|
|
r.git("commit", "-m", "Adding "+file)
|
|
}
|
|
|
|
// latestCommit returns the full commit id of the latest commit on the current
|
|
// branch.
|
|
func (r *gitRepo) latestCommit() (string, error) {
|
|
cmd := exec.Command("git", "rev-parse", "HEAD")
|
|
cmd.Dir = r.dir
|
|
rawOut, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
rawOut = bytes.TrimSpace(rawOut)
|
|
return string(rawOut), nil
|
|
}
|
|
|
|
// This is a read-only deploy key for an empty test repository.
|
|
// Note: This is split over multiple lines to avoid being disabled by key
|
|
// scanners automatically.
|
|
var testGitToken = `-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEpAIBAAKCAQEA9cHsxCl3Jjgu9DHpwvmfFOl1XEdY+ShHDR/cMnzJ5ddk5/oV
|
|
Wy6EWatvyHZfRSZMwzv4PtKeUPm6iXjqWp4xdWU9khlPzozyj+U9Fq70TRVUW9E5
|
|
T1XdQVwJE421yffr4VMMwu60wBqjI1epapH2i2inYvw9Zl9X2MXq0+jTvFvDerbT
|
|
mDtfStDPljenELAIZtWVETSvbI46gALwbxbM2292ZUIL4D6jRz0aZMmyy/twYv8r
|
|
9WGJLwmYzU518Ie7zqKW/mCTdTrV0WRiDj0MeRaPgrGY9amuHE4r9iG/cJkwpKAO
|
|
Ccz0Hs6i89u9vZnTqZU9V7weJqRAQcMjXXR6yQIDAQABAoIBAQDBzICKnGxiTlHw
|
|
rd+6qqChnAy5jWYDbZjCJ8q8YZ3RS08+g/8NXZxvHftTqM0uOaq1FviHig3gq15H
|
|
hHvCpBc6jXDFYoKFzq6FfO/0kFkE5HoWweIgxwRow0xBCDJAJ+ryUEyy+Ay/pQHb
|
|
IAjwilRS0V+WdnVw4mTjBAhPvb4jPOo97Yfy3PYUyx2F3newkqXOZy+zx3G/ANoa
|
|
ncypfMGyy76sfCWKqw4J1gVkVQLwbB6gQkXUFGYwY9sRrxbG93kQw76Flc/E/s52
|
|
62j4v1IM0fq0t/St+Y/+s6Lkw` + `aqt3ft1nsqWcRaVDdqvMfkzgJGXlw0bGzJG5MEQ
|
|
AIBq3dHRAoGBAP8OeG/DKG2Z1VmSfzuz1pas1fbZ+F7venOBrjez3sKlb3Pyl2aH
|
|
mt2wjaTUi5v10VrHgYtOEdqyhQeUSYydWXIBKNMag0NLLrfFUKZK+57wrHWFdFjn
|
|
VgpsdkLSNTOZpC8gA5OaJ+36IcOPfGqyyP9wuuRoaYnVT1KEzqLa9FEFAoGBAPaq
|
|
pglwhil2rxjJE4zq0afQLNpAfi7Xqcrepij+xvJIcIj7nawxXuPxqRFxONE/h3yX
|
|
zkybO8wLdbHX9Iw/wc1j50Uf1Z5gHdLf7/hQJoWKpz1RnkWRy6CYON8v1tpVp0tb
|
|
OAajR/kZnzebq2mfa7pyy5zDCX++2kp/dcFwHf31AoGAE8oupBVTZLWj7TBFuP8q
|
|
LkS40U92Sv9v09iDCQVmylmFvUxcXPM2m+7f/qMTNgWrucxzC7kB/6MMWVszHbrz
|
|
vrnCTibnemgx9sZTjKOSxHFOIEw7i85fSa3Cu0qOIDPSnmlwfZpfcMKQrhjLAYhf
|
|
uhooFiLX1X78iZ2OXup4PHUCgYEAsmBrm83sp1V1gAYBBlnVbXakyNv0pCk/Vz61
|
|
iFXeRt1NzDGxLxGw3kQnED8BaIh5kQcyn8Fud7sdzJMv/LAqlT4Ww60mzNYTGyjo
|
|
H3jOsqm3ESfRvduWFreeAQBWbiOczGjV1i8D4EbAFfWT+tjXjchwKBf+6Yt5zn/o
|
|
Bw/uEHUCgYAFs+JPOR25oRyBs7ujrMo/OY1z/eXTVVgZxY+tYGe1FJqDeFyR7ytK
|
|
+JBB1MuDwQKGm2wSIXdCzTNoIx2B9zTseiPTwT8G7vqNFhXoIaTBp4P2xIQb45mJ
|
|
7GkTsMBHwpSMOXgX9Weq3v5xOJ2WxVtjENmd6qzxcYCO5lP15O17hA==
|
|
-----END RSA PRIVATE KEY-----`
|
|
|
|
func assertContents(t *testing.T, path string, contents string) {
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(data, []byte(contents)) {
|
|
t.Fatalf("bad. expected:\n\n%s\n\nGot:\n\n%s", contents, string(data))
|
|
}
|
|
}
|
|
|
|
func tempDir(t *testing.T) string {
|
|
dir, err := ioutil.TempDir("", "tf")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
return dir
|
|
}
|
|
|
|
func tempTestFile(t *testing.T) string {
|
|
dir := tempDir(t)
|
|
return filepath.Join(dir, "foo")
|
|
}
|