diff --git a/.gitignore b/.gitignore index 6f3dc2d33..bec73737e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ api/cmd/portainer/portainer* __debug_bin api/docs +.idea +.env diff --git a/api/archive/testdata/sample_archive.zip b/api/archive/testdata/sample_archive.zip new file mode 100644 index 000000000..de289247a Binary files /dev/null and b/api/archive/testdata/sample_archive.zip differ diff --git a/api/archive/zip.go b/api/archive/zip.go index 5952e98d0..2aaee5a4f 100644 --- a/api/archive/zip.go +++ b/api/archive/zip.go @@ -3,10 +3,13 @@ package archive import ( "archive/zip" "bytes" + "fmt" + "github.com/pkg/errors" "io" "io/ioutil" "os" "path/filepath" + "strings" ) // UnzipArchive will unzip an archive from bytes into the dest destination folder on disk @@ -52,3 +55,60 @@ func extractFileFromArchive(file *zip.File, dest string) error { return outFile.Close() } + +// UnzipFile will decompress a zip archive, moving all files and folders +// within the zip file (parameter 1) to an output directory (parameter 2). +func UnzipFile(src string, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + p := filepath.Join(dest, f.Name) + + // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE + if !strings.HasPrefix(p, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("%s: illegal file path", p) + } + + if f.FileInfo().IsDir() { + // Make Folder + os.MkdirAll(p, os.ModePerm) + continue + } + + err = unzipFile(f, p) + if err != nil { + return err + } + } + + return nil +} + +func unzipFile(f *zip.File, p string) error { + // Make File + if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil { + return errors.Wrapf(err, "unzipFile: can't make a path %s", p) + } + outFile, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return errors.Wrapf(err, "unzipFile: can't create file %s", p) + } + defer outFile.Close() + rc, err := f.Open() + if err != nil { + return errors.Wrapf(err, "unzipFile: can't open zip file %s in the archive", f.Name) + } + defer rc.Close() + + _, err = io.Copy(outFile, rc) + + if err != nil { + return errors.Wrapf(err, "unzipFile: can't copy an archived file content") + } + + return nil +} diff --git a/api/archive/zip_test.go b/api/archive/zip_test.go new file mode 100644 index 000000000..e405f7945 --- /dev/null +++ b/api/archive/zip_test.go @@ -0,0 +1,32 @@ +package archive + +import ( + "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestUnzipFile(t *testing.T) { + dir, err := ioutil.TempDir("", "unzip-test-") + assert.NoError(t, err) + defer os.RemoveAll(dir) + /* + Archive structure. + ├── 0 + │ ├── 1 + │ │ └── 2.txt + │ └── 1.txt + └── 0.txt + */ + + err = UnzipFile("./testdata/sample_archive.zip", dir) + + assert.NoError(t, err) + archiveDir := dir + "/sample_archive" + assert.FileExists(t, filepath.Join(archiveDir, "0.txt")) + assert.FileExists(t, filepath.Join(archiveDir, "0", "1.txt")) + assert.FileExists(t, filepath.Join(archiveDir, "0", "1", "2.txt")) + +} diff --git a/api/git/azure.go b/api/git/azure.go new file mode 100644 index 000000000..78f10e52d --- /dev/null +++ b/api/git/azure.go @@ -0,0 +1,219 @@ +package git + +import ( + "context" + "fmt" + "github.com/pkg/errors" + "github.com/portainer/portainer/api/archive" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" +) + +const ( + azureDevOpsHost = "dev.azure.com" + visualStudioHostSuffix = ".visualstudio.com" +) + +func isAzureUrl(s string) bool { + return strings.Contains(s, azureDevOpsHost) || + strings.Contains(s, visualStudioHostSuffix) +} + +type azureOptions struct { + organisation, project, repository string + // a user may pass credentials in a repository URL, + // for example https://:@/ + username, password string +} + +type azureDownloader struct { + client *http.Client + baseUrl string +} + +func NewAzureDownloader(client *http.Client) *azureDownloader { + return &azureDownloader{ + client: client, + baseUrl: "https://dev.azure.com", + } +} + +func (a *azureDownloader) download(ctx context.Context, destination string, options cloneOptions) error { + zipFilepath, err := a.downloadZipFromAzureDevOps(ctx, options) + if err != nil { + return errors.Wrap(err, "failed to download a zip file from Azure DevOps") + } + defer os.Remove(zipFilepath) + + err = archive.UnzipFile(zipFilepath, destination) + if err != nil { + return errors.Wrap(err, "failed to unzip file") + } + + return nil +} + +func (a *azureDownloader) downloadZipFromAzureDevOps(ctx context.Context, options cloneOptions) (string, error) { + config, err := parseUrl(options.repositoryUrl) + if err != nil { + return "", errors.WithMessage(err, "failed to parse url") + } + downloadUrl, err := a.buildDownloadUrl(config, options.referenceName) + if err != nil { + return "", errors.WithMessage(err, "failed to build download url") + } + zipFile, err := ioutil.TempFile("", "azure-git-repo-*.zip") + if err != nil { + return "", errors.WithMessage(err, "failed to create temp file") + } + defer zipFile.Close() + + req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil) + if options.username != "" || options.password != "" { + req.SetBasicAuth(options.username, options.password) + } else if config.username != "" || config.password != "" { + req.SetBasicAuth(config.username, config.password) + } + + if err != nil { + return "", errors.WithMessage(err, "failed to create a new HTTP request") + } + + res, err := a.client.Do(req) + if err != nil { + return "", errors.WithMessage(err, "failed to make an HTTP request") + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to download zip with a status \"%v\"", res.Status) + } + + _, err = io.Copy(zipFile, res.Body) + if err != nil { + return "", errors.WithMessage(err, "failed to save HTTP response to a file") + } + return zipFile.Name(), nil +} + +func parseUrl(rawUrl string) (*azureOptions, error) { + if strings.HasPrefix(rawUrl, "https://") || strings.HasPrefix(rawUrl, "http://") { + return parseHttpUrl(rawUrl) + } + if strings.HasPrefix(rawUrl, "git@ssh") { + return parseSshUrl(rawUrl) + } + if strings.HasPrefix(rawUrl, "ssh://") { + r := []rune(rawUrl) + return parseSshUrl(string(r[6:])) // remove the prefix + } + + return nil, errors.Errorf("supported url schemes are https and ssh; recevied URL %s rawUrl", rawUrl) +} + +var expectedSshUrl = "git@ssh.dev.azure.com:v3/Organisation/Project/Repository" + +func parseSshUrl(rawUrl string) (*azureOptions, error) { + path := strings.Split(rawUrl, "/") + + unexpectedUrlErr := errors.Errorf("want url %s, got %s", expectedSshUrl, rawUrl) + if len(path) != 4 { + return nil, unexpectedUrlErr + } + return &azureOptions{ + organisation: path[1], + project: path[2], + repository: path[3], + }, nil +} + +const expectedAzureDevOpsHttpUrl = "https://Organisation@dev.azure.com/Organisation/Project/_git/Repository" +const expectedVisualStudioHttpUrl = "https://organisation.visualstudio.com/project/_git/repository" + +func parseHttpUrl(rawUrl string) (*azureOptions, error) { + u, err := url.Parse(rawUrl) + if err != nil { + return nil, errors.Wrap(err, "failed to parse HTTP url") + } + + opt := azureOptions{} + switch { + case u.Host == azureDevOpsHost: + path := strings.Split(u.Path, "/") + if len(path) != 5 { + return nil, errors.Errorf("want url %s, got %s", expectedAzureDevOpsHttpUrl, u) + } + opt.organisation = path[1] + opt.project = path[2] + opt.repository = path[4] + case strings.HasSuffix(u.Host, visualStudioHostSuffix): + path := strings.Split(u.Path, "/") + if len(path) != 4 { + return nil, errors.Errorf("want url %s, got %s", expectedVisualStudioHttpUrl, u) + } + opt.organisation = strings.TrimSuffix(u.Host, visualStudioHostSuffix) + opt.project = path[1] + opt.repository = path[3] + default: + return nil, errors.Errorf("unknown azure host in url \"%s\"", rawUrl) + } + + opt.username = u.User.Username() + opt.password, _ = u.User.Password() + + return &opt, nil +} + +func (a *azureDownloader) buildDownloadUrl(config *azureOptions, referenceName string) (string, error) { + rawUrl := fmt.Sprintf("%s/%s/%s/_apis/git/repositories/%s/items", + a.baseUrl, + url.PathEscape(config.organisation), + url.PathEscape(config.project), + url.PathEscape(config.repository)) + u, err := url.Parse(rawUrl) + + if err != nil { + return "", errors.Wrapf(err, "failed to parse download url path %s", rawUrl) + } + q := u.Query() + // scopePath=/&download=true&versionDescriptor.version=main&$format=zip&recursionLevel=full&api-version=6.0 + q.Set("scopePath", "/") + q.Set("download", "true") + q.Set("versionDescriptor.versionType", getVersionType(referenceName)) + q.Set("versionDescriptor.version", formatReferenceName(referenceName)) + q.Set("$format", "zip") + q.Set("recursionLevel", "full") + q.Set("api-version", "6.0") + u.RawQuery = q.Encode() + + return u.String(), nil +} + +const ( + branchPrefix = "refs/heads/" + tagPrefix = "refs/tags/" +) + +func formatReferenceName(name string) string { + if strings.HasPrefix(name, branchPrefix) { + return strings.TrimPrefix(name, branchPrefix) + } + if strings.HasPrefix(name, tagPrefix) { + return strings.TrimPrefix(name, tagPrefix) + } + return name +} + +func getVersionType(name string) string { + if strings.HasPrefix(name, branchPrefix) { + return "branch" + } + if strings.HasPrefix(name, tagPrefix) { + return "tag" + } + return "commit" +} diff --git a/api/git/azure_integration_test.go b/api/git/azure_integration_test.go new file mode 100644 index 000000000..85ee8f707 --- /dev/null +++ b/api/git/azure_integration_test.go @@ -0,0 +1,92 @@ +package git + +import ( + "fmt" + "github.com/docker/docker/pkg/ioutils" + _ "github.com/joho/godotenv/autoload" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestService_ClonePublicRepository_Azure(t *testing.T) { + ensureIntegrationTest(t) + + pat := getRequiredValue(t, "AZURE_DEVOPS_PAT") + service := NewService() + + type args struct { + repositoryURLFormat string + referenceName string + username string + password string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Clone Azure DevOps repo branch", + args: args{ + repositoryURLFormat: "https://:%s@portainer.visualstudio.com/Playground/_git/dev_integration", + referenceName: "refs/heads/main", + username: "", + password: pat, + }, + wantErr: false, + }, + { + name: "Clone Azure DevOps repo tag", + args: args{ + repositoryURLFormat: "https://:%s@portainer.visualstudio.com/Playground/_git/dev_integration", + referenceName: "refs/tags/v1.1", + username: "", + password: pat, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst, err := ioutils.TempDir("", "clone") + assert.NoError(t, err) + defer os.RemoveAll(dst) + repositoryUrl := fmt.Sprintf(tt.args.repositoryURLFormat, tt.args.password) + err = service.ClonePublicRepository(repositoryUrl, tt.args.referenceName, dst) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(dst, "README.md")) + }) + } +} + +func TestService_ClonePrivateRepository_Azure(t *testing.T) { + ensureIntegrationTest(t) + + pat := getRequiredValue(t, "AZURE_DEVOPS_PAT") + service := NewService() + + dst, err := ioutils.TempDir("", "clone") + assert.NoError(t, err) + defer os.RemoveAll(dst) + + repositoryUrl := "https://portainer.visualstudio.com/Playground/_git/dev_integration" + err = service.ClonePrivateRepositoryWithBasicAuth(repositoryUrl, "refs/heads/main", dst, "", pat) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(dst, "README.md")) +} + +func getRequiredValue(t *testing.T, name string) string { + value, ok := os.LookupEnv(name) + if !ok { + t.Fatalf("can't find required env var \"%s\"", name) + } + return value +} + +func ensureIntegrationTest(t *testing.T) { + if _, ok := os.LookupEnv("INTEGRATION_TEST"); !ok { + t.Skip("skip an integration test") + } +} diff --git a/api/git/azure_test.go b/api/git/azure_test.go new file mode 100644 index 000000000..18417e9e6 --- /dev/null +++ b/api/git/azure_test.go @@ -0,0 +1,250 @@ +package git + +import ( + "context" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +func Test_buildDownloadUrl(t *testing.T) { + a := NewAzureDownloader(nil) + u, err := a.buildDownloadUrl(&azureOptions{ + organisation: "organisation", + project: "project", + repository: "repository", + }, "refs/heads/main") + + expectedUrl, _ := url.Parse("https://dev.azure.com/organisation/project/_apis/git/repositories/repository/items?scopePath=/&download=true&versionDescriptor.version=main&$format=zip&recursionLevel=full&api-version=6.0&versionDescriptor.versionType=branch") + actualUrl, _ := url.Parse(u) + if assert.NoError(t, err) { + assert.Equal(t, expectedUrl.Host, actualUrl.Host) + assert.Equal(t, expectedUrl.Scheme, actualUrl.Scheme) + assert.Equal(t, expectedUrl.Path, actualUrl.Path) + assert.Equal(t, expectedUrl.Query(), actualUrl.Query()) + } +} + +func Test_parseAzureUrl(t *testing.T) { + type args struct { + url string + } + tests := []struct { + name string + args args + want *azureOptions + wantErr bool + }{ + { + name: "Expected SSH URL format starting with ssh://", + args: args{ + url: "ssh://git@ssh.dev.azure.com:v3/Organisation/Project/Repository", + }, + want: &azureOptions{ + organisation: "Organisation", + project: "Project", + repository: "Repository", + }, + wantErr: false, + }, + { + name: "Expected SSH URL format starting with git@ssh", + args: args{ + url: "git@ssh.dev.azure.com:v3/Organisation/Project/Repository", + }, + want: &azureOptions{ + organisation: "Organisation", + project: "Project", + repository: "Repository", + }, + wantErr: false, + }, + { + name: "Unexpected SSH URL format", + args: args{ + url: "git@ssh.dev.azure.com:v3/Organisation/Repository", + }, + wantErr: true, + }, + { + name: "Expected HTTPS URL format", + args: args{ + url: "https://Organisation@dev.azure.com/Organisation/Project/_git/Repository", + }, + want: &azureOptions{ + organisation: "Organisation", + project: "Project", + repository: "Repository", + username: "Organisation", + }, + wantErr: false, + }, + { + name: "HTTPS URL with credentials", + args: args{ + url: "https://username:password@dev.azure.com/Organisation/Project/_git/Repository", + }, + want: &azureOptions{ + organisation: "Organisation", + project: "Project", + repository: "Repository", + username: "username", + password: "password", + }, + wantErr: false, + }, + { + name: "HTTPS URL with password", + args: args{ + url: "https://:password@dev.azure.com/Organisation/Project/_git/Repository", + }, + want: &azureOptions{ + organisation: "Organisation", + project: "Project", + repository: "Repository", + password: "password", + }, + wantErr: false, + }, + { + name: "Visual Studio HTTPS URL with credentials", + args: args{ + url: "https://username:password@organisation.visualstudio.com/project/_git/repository", + }, + want: &azureOptions{ + organisation: "organisation", + project: "project", + repository: "repository", + username: "username", + password: "password", + }, + wantErr: false, + }, + { + name: "Unexpected HTTPS URL format", + args: args{ + url: "https://Organisation@dev.azure.com/Project/_git/Repository", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseUrl(tt.args.url) + if (err != nil) != tt.wantErr { + t.Errorf("parseUrl() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_isAzureUrl(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Is Azure url", + args: args{ + s: "https://Organisation@dev.azure.com/Organisation/Project/_git/Repository", + }, + want: true, + }, + { + name: "Is Azure url", + args: args{ + s: "https://portainer.visualstudio.com/project/_git/repository", + }, + want: true, + }, + { + name: "Is NOT Azure url", + args: args{ + s: "https://github.com/Organisation/Repository", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, isAzureUrl(tt.args.s)) + }) + } +} + +func Test_azureDownloader_downloadZipFromAzureDevOps(t *testing.T) { + type args struct { + options cloneOptions + } + type basicAuth struct { + username, password string + } + tests := []struct { + name string + args args + want *basicAuth + }{ + { + name: "username, password embedded", + args: args{ + options: cloneOptions{ + repositoryUrl: "https://username:password@dev.azure.com/Organisation/Project/_git/Repository", + }, + }, + want: &basicAuth{ + username: "username", + password: "password", + }, + }, + { + name: "username, password embedded, clone options take precedence", + args: args{ + options: cloneOptions{ + repositoryUrl: "https://username:password@dev.azure.com/Organisation/Project/_git/Repository", + username: "u", + password: "p", + }, + }, + want: &basicAuth{ + username: "u", + password: "p", + }, + }, + { + name: "no credentials", + args: args{ + options: cloneOptions{ + repositoryUrl: "https://dev.azure.com/Organisation/Project/_git/Repository", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var zipRequestAuth *basicAuth + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if username, password, ok := r.BasicAuth(); ok { + zipRequestAuth = &basicAuth{username, password} + } + w.WriteHeader(http.StatusNotFound) // this makes function under test to return an error + })) + defer server.Close() + + a := &azureDownloader{ + client: server.Client(), + baseUrl: server.URL, + } + _, err := a.downloadZipFromAzureDevOps(context.Background(), tt.args.options) + assert.Error(t, err) + assert.Equal(t, tt.want, zipRequestAuth) + }) + } +} diff --git a/api/git/git.go b/api/git/git.go index 116a5f113..51eeac898 100644 --- a/api/git/git.go +++ b/api/git/git.go @@ -1,21 +1,71 @@ package git import ( + "context" "crypto/tls" + "github.com/pkg/errors" "net/http" - "net/url" - "strings" + "os" + "path/filepath" "time" - "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/transport/client" - githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport/client" + githttp "github.com/go-git/go-git/v5/plumbing/transport/http" ) +type cloneOptions struct { + repositoryUrl string + username string + password string + referenceName string + depth int +} + +type downloader interface { + download(ctx context.Context, dst string, opt cloneOptions) error +} + +type gitClient struct{ + preserveGitDirectory bool +} + +func (c gitClient) download(ctx context.Context, dst string, opt cloneOptions) error { + gitOptions := git.CloneOptions{ + URL: opt.repositoryUrl, + Depth: opt.depth, + } + + if opt.password != "" || opt.username != "" { + gitOptions.Auth = &githttp.BasicAuth{ + Username: opt.username, + Password: opt.password, + } + } + + if opt.referenceName != "" { + gitOptions.ReferenceName = plumbing.ReferenceName(opt.referenceName) + } + + _, err := git.PlainCloneContext(ctx, dst, false, &gitOptions) + + if err != nil { + return errors.Wrap(err, "failed to clone git repository") + } + + if !c.preserveGitDirectory { + os.RemoveAll(filepath.Join(dst, ".git")) + } + + return nil +} + // Service represents a service for managing Git. type Service struct { httpsCli *http.Client + azure downloader + git downloader } // NewService initializes a new service. @@ -31,32 +81,37 @@ func NewService() *Service { return &Service{ httpsCli: httpsCli, + azure: NewAzureDownloader(httpsCli), + git: gitClient{}, } } // ClonePublicRepository clones a public git repository using the specified URL in the specified // destination folder. -func (service *Service) ClonePublicRepository(repositoryURL, referenceName string, destination string) error { - return cloneRepository(repositoryURL, referenceName, destination) +func (service *Service) ClonePublicRepository(repositoryURL, referenceName, destination string) error { + return service.cloneRepository(destination, cloneOptions{ + repositoryUrl: repositoryURL, + referenceName: referenceName, + depth: 1, + }) } // ClonePrivateRepositoryWithBasicAuth clones a private git repository using the specified URL in the specified -// destination folder. It will use the specified username and password for basic HTTP authentication. -func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error { - credentials := username + ":" + url.PathEscape(password) - repositoryURL = strings.Replace(repositoryURL, "://", "://"+credentials+"@", 1) - return cloneRepository(repositoryURL, referenceName, destination) +// destination folder. It will use the specified Username and Password for basic HTTP authentication. +func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName, destination, username, password string) error { + return service.cloneRepository(destination, cloneOptions{ + repositoryUrl: repositoryURL, + username: username, + password: password, + referenceName: referenceName, + depth: 1, + }) } -func cloneRepository(repositoryURL, referenceName, destination string) error { - options := &git.CloneOptions{ - URL: repositoryURL, +func (service *Service) cloneRepository(destination string, options cloneOptions) error { + if isAzureUrl(options.repositoryUrl) { + return service.azure.download(context.TODO(), destination, options) } - if referenceName != "" { - options.ReferenceName = plumbing.ReferenceName(referenceName) - } - - _, err := git.PlainClone(destination, false, options) - return err + return service.git.download(context.TODO(), destination, options) } diff --git a/api/git/git_integration_test.go b/api/git/git_integration_test.go new file mode 100644 index 000000000..0249a629a --- /dev/null +++ b/api/git/git_integration_test.go @@ -0,0 +1,26 @@ +package git + +import ( + "github.com/docker/docker/pkg/ioutils" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestService_ClonePrivateRepository_GitHub(t *testing.T) { + ensureIntegrationTest(t) + + pat := getRequiredValue(t, "GITHUB_PAT") + username := getRequiredValue(t, "GITHUB_USERNAME") + service := NewService() + + dst, err := ioutils.TempDir("", "clone") + assert.NoError(t, err) + defer os.RemoveAll(dst) + + repositoryUrl := "https://github.com/portainer/private-test-repository.git" + err = service.ClonePrivateRepositoryWithBasicAuth(repositoryUrl, "refs/heads/main", dst, username, pat) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(dst, "README.md")) +} diff --git a/api/git/git_test.go b/api/git/git_test.go new file mode 100644 index 000000000..8276a0925 --- /dev/null +++ b/api/git/git_test.go @@ -0,0 +1,173 @@ +package git + +import ( + "context" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/pkg/errors" + "github.com/portainer/portainer/api/archive" + "github.com/stretchr/testify/assert" + "io/ioutil" + "log" + "os" + "path/filepath" + "testing" +) + +var bareRepoDir string + +func TestMain(m *testing.M) { + if err := testMain(m); err != nil { + log.Fatal(err) + } +} + +// testMain does extra setup/teardown before/after testing. +// The function is separated from TestMain due to necessity to call os.Exit/log.Fatal in the latter. +func testMain(m *testing.M) error { + dir, err := ioutil.TempDir("", "git-repo-") + if err != nil { + return errors.Wrap(err, "failed to create a temp dir") + } + defer os.RemoveAll(dir) + + bareRepoDir = filepath.Join(dir, "test-clone.git") + + file, err := os.OpenFile("./testdata/test-clone-git-repo.tar.gz", os.O_RDONLY, 0755) + if err != nil { + return errors.Wrap(err, "failed to open an archive") + } + err = archive.ExtractTarGz(file, dir) + if err != nil { + return errors.Wrapf(err, "failed to extract file from the archive to a folder %s\n", dir) + } + + m.Run() + + return nil +} + +func Test_ClonePublicRepository_Shallow(t *testing.T) { + service := Service{git: gitClient{preserveGitDirectory: true}} // no need for http client since the test access the repo via file system. + repositoryURL := bareRepoDir + referenceName := "refs/heads/main" + destination := "shallow" + + dir, err := ioutil.TempDir("", destination) + if err != nil { + t.Fatalf("failed to create a temp dir") + } + defer os.RemoveAll(dir) + t.Logf("Cloning into %s", dir) + err = service.ClonePublicRepository(repositoryURL, referenceName, dir) + assert.NoError(t, err) + assert.Equal(t, 1, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth") +} + +func Test_ClonePublicRepository_NoGitDirectory(t *testing.T) { + service := Service{git: gitClient{preserveGitDirectory: false}} // no need for http client since the test access the repo via file system. + repositoryURL := bareRepoDir + referenceName := "refs/heads/main" + destination := "shallow" + + dir, err := ioutil.TempDir("", destination) + if err != nil { + t.Fatalf("failed to create a temp dir") + } + defer os.RemoveAll(dir) + t.Logf("Cloning into %s", dir) + err = service.ClonePublicRepository(repositoryURL, referenceName, dir) + assert.NoError(t, err) + assert.NoDirExists(t, filepath.Join(dir, ".git")) +} + +func Test_cloneRepository(t *testing.T) { + service := Service{git: gitClient{preserveGitDirectory: true}} // no need for http client since the test access the repo via file system. + + repositoryURL := bareRepoDir + referenceName := "refs/heads/main" + destination := "shallow" + + dir, err := ioutil.TempDir("", destination) + if err != nil { + t.Fatalf("failed to create a temp dir") + } + defer os.RemoveAll(dir) + t.Logf("Cloning into %s", dir) + + err = service.cloneRepository(dir, cloneOptions{ + repositoryUrl: repositoryURL, + referenceName: referenceName, + depth: 10, + }) + + assert.NoError(t, err) + assert.Equal(t, 3, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth") +} + +func getCommitHistoryLength(t *testing.T, err error, dir string) int { + repo, err := git.PlainOpen(dir) + if err != nil { + t.Fatalf("can't open a git repo at %s with error %v", dir, err) + } + iter, err := repo.Log(&git.LogOptions{All: true}) + if err != nil { + t.Fatalf("can't get a commit history iterator with error %v", err) + } + count := 0 + err = iter.ForEach(func(_ *object.Commit) error { + count++ + return nil + }) + if err != nil { + t.Fatalf("can't iterate over the commit history with error %v", err) + } + return count +} + +type testDownloader struct { + called bool +} + +func (t *testDownloader) download(_ context.Context, _ string, _ cloneOptions) error { + t.called = true + return nil +} + +func Test_cloneRepository_azure(t *testing.T) { + tests := []struct { + name string + url string + called bool + }{ + { + name: "Azure HTTP URL", + url: "https://Organisation@dev.azure.com/Organisation/Project/_git/Repository", + called: true, + }, + { + name: "Azure SSH URL", + url: "git@ssh.dev.azure.com:v3/Organisation/Project/Repository", + called: true, + }, + { + name: "Something else", + url: "https://example.com", + called: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + azure := &testDownloader{} + git := &testDownloader{} + + s := &Service{azure: azure, git: git} + s.cloneRepository("", cloneOptions{repositoryUrl: tt.url, depth: 1}) + + // if azure API is called, git isn't and vice versa + assert.Equal(t, tt.called, azure.called) + assert.Equal(t, tt.called, !git.called) + }) + } +} diff --git a/api/git/testdata/azure-repo.zip b/api/git/testdata/azure-repo.zip new file mode 100644 index 000000000..d4b53d0b8 Binary files /dev/null and b/api/git/testdata/azure-repo.zip differ diff --git a/api/git/testdata/test-clone-git-repo.tar.gz b/api/git/testdata/test-clone-git-repo.tar.gz new file mode 100644 index 000000000..ca63d337c Binary files /dev/null and b/api/git/testdata/test-clone-git-repo.tar.gz differ diff --git a/api/go.mod b/api/go.mod index 0b9a01bd7..82a130f3c 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/portainer/portainer/api go 1.13 require ( - github.com/Microsoft/go-winio v0.4.14 + github.com/Microsoft/go-winio v0.4.16 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a github.com/boltdb/bolt v1.3.1 github.com/containerd/containerd v1.3.1 // indirect @@ -13,12 +13,13 @@ require ( github.com/docker/cli v0.0.0-20191126203649-54d085b857e9 github.com/docker/docker v0.0.0-00010101000000-000000000000 github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814 + github.com/go-git/go-git/v5 v5.3.0 github.com/go-ldap/ldap/v3 v3.1.8 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/mux v1.7.3 github.com/gorilla/securecookie v1.1.1 github.com/gorilla/websocket v1.4.1 - github.com/imdario/mergo v0.3.8 // indirect + github.com/joho/godotenv v1.3.0 github.com/jpillora/chisel v0.0.0-20190724232113-f3a8df20e389 github.com/json-iterator/go v1.1.8 github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c @@ -30,14 +31,14 @@ require ( github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2 github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33 github.com/stretchr/testify v1.6.1 - golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 - golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 gopkg.in/alecthomas/kingpin.v2 v2.2.6 - gopkg.in/src-d/go-git.v4 v4.13.1 k8s.io/api v0.17.2 k8s.io/apimachinery v0.17.2 k8s.io/client-go v0.17.2 ) replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20200204220554-5f6d6f3f2203 + +replace golang.org/x/sys => golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 diff --git a/api/go.sum b/api/go.sum index 6c69c89d4..b7e4bb5c2 100644 --- a/api/go.sum +++ b/api/go.sum @@ -11,10 +11,10 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.3.8 h1:dvxbxtpTIjdAbx2OtL26p4eq0iEvys/U5yrsTJb3NZI= github.com/Microsoft/go-winio v0.3.8/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -47,7 +47,7 @@ github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVl github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -92,6 +92,15 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0 h1:4pl5BV4o7ZG/lterP4S6WzJ6xr49Ba5ET9ygheTYahk= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap/v3 v3.1.8 h1:5vU/2jOh9HqprwXp8aF915s9p6Z8wmbSEVF7/gdTFhM= github.com/go-ldap/ldap/v3 v3.1.8/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= @@ -105,7 +114,6 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -147,11 +155,13 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669 h1:l5rH/CnVVu+HPxjtxjM90nHrm4nov3j3RF9/62UjgLs= github.com/jpillora/ansi v0.0.0-20170202005112-f496b27cd669/go.mod h1:kOeLNvjNBGSV3uYtFjvb72+fnZCMFJF1XDvRIjdom0g= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= @@ -168,8 +178,8 @@ github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46O github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c h1:N7A4JCA2G+j5fuFxCsJqjFU/sZe0mj8H0sSoSwbaikw= @@ -177,13 +187,14 @@ github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c/go.mod h1:Nn github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -205,6 +216,7 @@ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -218,10 +230,8 @@ github.com/opencontainers/runc v0.0.0-20161109192122-51371867a01c h1:iOMba/KmaXg github.com/opencontainers/runc v0.0.0-20161109192122-51371867a01c/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw= github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= -github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -248,9 +258,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -258,15 +267,10 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= -github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= @@ -274,8 +278,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= -github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -288,10 +292,9 @@ golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8= -golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -308,12 +311,10 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= -golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -324,27 +325,16 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -356,13 +346,11 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7 h1:ZUjXAXmrAyrmmCPHgCA/vChHcpsX27MZ3yBonD/z1KE= @@ -373,25 +361,22 @@ google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= -gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= -gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=