From 3568fe9e52a810ed130ef025165663176b14a693 Mon Sep 17 00:00:00 2001 From: dbuduev Date: Mon, 24 May 2021 17:27:07 +1200 Subject: [PATCH] feat(git) git clone improvements [EE-451] (#5070) --- .gitignore | 2 + api/archive/testdata/sample_archive.zip | Bin 0 -> 1054 bytes api/archive/zip.go | 60 +++++ api/archive/zip_test.go | 32 +++ api/git/azure.go | 219 +++++++++++++++++ api/git/azure_integration_test.go | 92 +++++++ api/git/azure_test.go | 250 ++++++++++++++++++++ api/git/git.go | 99 ++++++-- api/git/git_integration_test.go | 26 ++ api/git/git_test.go | 173 ++++++++++++++ api/git/testdata/azure-repo.zip | Bin 0 -> 163 bytes api/git/testdata/test-clone-git-repo.tar.gz | Bin 0 -> 11003 bytes api/go.mod | 11 +- api/go.sum | 101 ++++---- 14 files changed, 980 insertions(+), 85 deletions(-) create mode 100644 api/archive/testdata/sample_archive.zip create mode 100644 api/archive/zip_test.go create mode 100644 api/git/azure.go create mode 100644 api/git/azure_integration_test.go create mode 100644 api/git/azure_test.go create mode 100644 api/git/git_integration_test.go create mode 100644 api/git/git_test.go create mode 100644 api/git/testdata/azure-repo.zip create mode 100644 api/git/testdata/test-clone-git-repo.tar.gz 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 0000000000000000000000000000000000000000..de289247aebc33f2b13961a6cd98a4d24f1b5356 GIT binary patch literal 1054 zcmWIWW@h1H00F_!RY70|l;CHOVJJ?_EyziYPb^B#$Sh0M4-MgDV1Ae~BLRd&GZJ!u7^hJsXhsQRGs+NR*leIdEAbh2Gk8^y zyW!nhkas~?0=r>GdLaoa46E62 zlgoi7hhv(Yk(@kd2*! z%~(*f02&NR7I+NCOcW4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d4b53d0b83a53ff87773844da7a48545815a39bf GIT binary patch literal 163 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 literal 0 HcmV?d00001 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=