From bc37c74b28bbfeeff37cc0c4067a597d02a03682 Mon Sep 17 00:00:00 2001 From: Azeem Shaikh Date: Fri, 10 Sep 2021 10:13:14 -0700 Subject: [PATCH] Remove Owner/Repo strings from CheckRequest (#997) Co-authored-by: Azeem Shaikh --- checker/check_request.go | 8 +- checker/check_runner.go | 2 +- checks/cii_best_practices.go | 2 +- checks/file_utils.go | 7 +- checks/fuzzing.go | 20 ++-- checks/security_policy.go | 14 +-- clients/githubrepo/client.go | 12 ++- clients/githubrepo/repo_url.go | 135 +++++++++++++++++++++++++ clients/githubrepo/repo_url_test.go | 102 +++++++++++++++++++ clients/mockrepo/client.go | 8 +- clients/mockrepo/repo.go | 150 ++++++++++++++++++++++++++++ clients/repo.go | 27 +++++ clients/repo_client.go | 2 +- e2e/binary_artifacts_test.go | 14 +-- e2e/branch_protection_test.go | 14 +-- e2e/ci_tests_test.go | 7 +- e2e/cii_best_practices_test.go | 7 +- e2e/code_review_test.go | 7 +- e2e/contributors_test.go | 14 +-- e2e/dependency_update_tool_test.go | 14 +-- e2e/fuzzing_test.go | 7 +- e2e/maintained_test.go | 7 +- e2e/packaging_test.go | 7 +- e2e/permissions_test.go | 7 +- e2e/pinned_dependencies_test.go | 7 +- e2e/sast_test.go | 7 +- e2e/security_policy_test.go | 14 +-- e2e/signedreleases_test.go | 7 +- e2e/vulnerabilities_test.go | 14 +-- errors/public.go | 4 + pkg/scorecard.go | 18 ++-- repos/repo_url.go | 3 +- 32 files changed, 566 insertions(+), 102 deletions(-) create mode 100644 clients/githubrepo/repo_url.go create mode 100644 clients/githubrepo/repo_url_test.go create mode 100644 clients/mockrepo/repo.go create mode 100644 clients/repo.go diff --git a/checker/check_request.go b/checker/check_request.go index 38dc9627..87da2b7f 100644 --- a/checker/check_request.go +++ b/checker/check_request.go @@ -22,8 +22,8 @@ import ( // CheckRequest struct encapsulates all data to be passed into a CheckFn. type CheckRequest struct { - Ctx context.Context - RepoClient clients.RepoClient - Dlogger DetailLogger - Owner, Repo string + Ctx context.Context + RepoClient clients.RepoClient + Dlogger DetailLogger + Repo clients.Repo } diff --git a/checker/check_runner.go b/checker/check_runner.go index e6a54982..6a957213 100644 --- a/checker/check_runner.go +++ b/checker/check_runner.go @@ -31,9 +31,9 @@ const checkRetries = 3 // Runner runs a check with retries. type Runner struct { + CheckRequest CheckRequest CheckName string Repo string - CheckRequest CheckRequest } // CheckFn defined for convenience. diff --git a/checks/cii_best_practices.go b/checks/cii_best_practices.go index f9e7e4a4..01fcf926 100644 --- a/checks/cii_best_practices.go +++ b/checks/cii_best_practices.go @@ -39,7 +39,7 @@ type response struct { // CIIBestPractices runs CII-Best-Practices check. func CIIBestPractices(c *checker.CheckRequest) checker.CheckResult { - repoURL := fmt.Sprintf("https://github.com/%s/%s", c.Owner, c.Repo) + repoURL := fmt.Sprintf("https://%s", c.Repo.URL()) url := fmt.Sprintf("https://bestpractices.coreinfrastructure.org/projects.json?url=%s", repoURL) req, err := http.NewRequestWithContext(c.Ctx, "GET", url, nil) if err != nil { diff --git a/checks/file_utils.go b/checks/file_utils.go index ae182ece..d200247e 100644 --- a/checks/file_utils.go +++ b/checks/file_utils.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/ossf/scorecard/v2/checker" + "github.com/ossf/scorecard/v2/clients" sce "github.com/ossf/scorecard/v2/errors" ) @@ -48,9 +49,9 @@ func isMatchingPath(pattern, fullpath string, caseSensitive bool) (bool, error) return match, nil } -func isScorecardTestFile(owner, repo, fullpath string) bool { +func isScorecardTestFile(repo clients.Repo, fullpath string) bool { // testdata/ or /some/dir/testdata/some/other - return owner == "ossf" && repo == "scorecard" && (strings.HasPrefix(fullpath, "testdata/") || + return repo.IsScorecardRepo() && (strings.HasPrefix(fullpath, "testdata/") || strings.Contains(fullpath, "/testdata/")) } @@ -78,7 +79,7 @@ func CheckFilesContent(shellPathFnPattern string, ) error { predicate := func(filepath string) (bool, error) { // Filter out Scorecard's own test files. - if isScorecardTestFile(c.Owner, c.Repo, filepath) { + if isScorecardTestFile(c.Repo, filepath) { return false, nil } // Filter out files based on path/names using the pattern. diff --git a/checks/fuzzing.go b/checks/fuzzing.go index 84fde978..d53fb53c 100644 --- a/checks/fuzzing.go +++ b/checks/fuzzing.go @@ -30,10 +30,11 @@ import ( const CheckFuzzing = "Fuzzing" var ( - ossFuzzRepo clients.RepoClient - errOssFuzzRepo error - logger *zap.Logger - once sync.Once + ossFuzzRepo clients.Repo + ossFuzzRepoClient clients.RepoClient + errOssFuzzRepo error + logger *zap.Logger + once sync.Once ) //nolint:gochecknoinits @@ -48,8 +49,13 @@ func Fuzzing(c *checker.CheckRequest) checker.CheckResult { if errOssFuzzRepo != nil { return } - ossFuzzRepo = githubrepo.CreateGithubRepoClient(c.Ctx, logger) - errOssFuzzRepo = ossFuzzRepo.InitRepo("google", "oss-fuzz") + ossFuzzRepo, errOssFuzzRepo = githubrepo.MakeGithubRepo("google/oss-fuzz") + if errOssFuzzRepo != nil { + return + } + + ossFuzzRepoClient = githubrepo.CreateGithubRepoClient(c.Ctx, logger) + errOssFuzzRepo = ossFuzzRepoClient.InitRepo(ossFuzzRepo) }) if errOssFuzzRepo != nil { e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("InitRepo: %v", errOssFuzzRepo)) @@ -60,7 +66,7 @@ func Fuzzing(c *checker.CheckRequest) checker.CheckResult { Query: c.RepoClient.URL(), Filename: "project.yaml", } - result, err := ossFuzzRepo.Search(req) + result, err := ossFuzzRepoClient.Search(req) if err != nil { e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("Client.Search.Code: %v", err)) return checker.CreateRuntimeErrorResult(CheckFuzzing, e) diff --git a/checks/security_policy.go b/checks/security_policy.go index 6f207761..a220a53f 100644 --- a/checks/security_policy.go +++ b/checks/security_policy.go @@ -72,19 +72,21 @@ func SecurityPolicy(c *checker.CheckRequest) checker.CheckResult { // checking for community default within the .github folder // https://docs.github.com/en/github/building-a-strong-community/creating-a-default-community-health-file - dotGitHub := c - dotGitHub.Repo = ".github" logger, err := githubrepo.NewLogger(zap.InfoLevel) if err != nil { return checker.CreateRuntimeErrorResult(CheckSecurityPolicy, err) } - dotGitHubClient := githubrepo.CreateGithubRepoClient(c.Ctx, logger) - err = dotGitHubClient.InitRepo(c.Owner, c.Repo) + dotGitHub := &checker.CheckRequest{ + Ctx: c.Ctx, + Dlogger: c.Dlogger, + RepoClient: githubrepo.CreateGithubRepoClient(c.Ctx, logger), + Repo: c.Repo.Org(), + } + err = dotGitHub.RepoClient.InitRepo(dotGitHub.Repo) switch { case err == nil: - defer dotGitHubClient.Close() - dotGitHub.RepoClient = dotGitHubClient + defer dotGitHub.RepoClient.Close() onFile = func(name string, dl checker.DetailLogger, data FileCbData) (bool, error) { pdata := FileGetCbDataAsBoolPointer(data) if strings.EqualFold(name, "security.md") || diff --git a/clients/githubrepo/client.go b/clients/githubrepo/client.go index e4aa774b..d5953ce5 100644 --- a/clients/githubrepo/client.go +++ b/clients/githubrepo/client.go @@ -17,6 +17,7 @@ package githubrepo import ( "context" + "errors" "fmt" "net/http" @@ -30,6 +31,8 @@ import ( sce "github.com/ossf/scorecard/v2/errors" ) +var errInputRepoType = errors.New("input repo should be of type repoURL") + // Client is GitHub-specific implementation of RepoClient. type Client struct { owner string @@ -49,9 +52,14 @@ type Client struct { } // InitRepo sets up the GitHub repo in local storage for improving performance and GitHub token usage efficiency. -func (client *Client) InitRepo(owner, repoName string) error { +func (client *Client) InitRepo(inputRepo clients.Repo) error { + ghRepo, ok := inputRepo.(*repoURL) + if !ok { + return fmt.Errorf("%w: %v", errInputRepoType, inputRepo) + } + // Sanity check. - repo, _, err := client.repoClient.Repositories.Get(client.ctx, owner, repoName) + repo, _, err := client.repoClient.Repositories.Get(client.ctx, ghRepo.owner, ghRepo.repo) if err != nil { return sce.WithMessage(sce.ErrRepoUnreachable, err.Error()) } diff --git a/clients/githubrepo/repo_url.go b/clients/githubrepo/repo_url.go new file mode 100644 index 00000000..96bff3dd --- /dev/null +++ b/clients/githubrepo/repo_url.go @@ -0,0 +1,135 @@ +// Copyright 2020 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package githubrepo + +import ( + "fmt" + "net/url" + "strings" + + "github.com/ossf/scorecard/v2/clients" + sce "github.com/ossf/scorecard/v2/errors" +) + +const ( + githubOrgRepo = ".github" + scorecardOwner = "ossf" + scorecardRepo = "scorecard" +) + +type repoURL struct { + host, owner, repo string + metadata []string +} + +// Parses input string into repoURL struct. +// Accepts "owner/repo" or "github.com/owner/repo". +func (r *repoURL) parse(input string) error { + var t string + + const two = 2 + const three = 3 + + c := strings.Split(input, "/") + + switch l := len(c); { + // This will takes care for repo/owner format. + // By default it will use github.com + case l == two: + t = "github.com/" + c[0] + "/" + c[1] + case l >= three: + t = input + } + + // Allow skipping scheme for ease-of-use, default to https. + if !strings.Contains(t, "://") { + t = "https://" + t + } + + u, e := url.Parse(t) + if e != nil { + return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("url.Parse: %v", e)) + } + + const splitLen = 2 + split := strings.SplitN(strings.Trim(u.Path, "/"), "/", splitLen) + if len(split) != splitLen { + return sce.WithMessage(sce.ErrorInvalidURL, fmt.Sprintf("%v. Exepted full repository url", input)) + } + + r.host, r.owner, r.repo = u.Host, split[0], split[1] + return nil +} + +// URL implements Repo.URL. +func (r *repoURL) URL() string { + return fmt.Sprintf("%s/%s/%s", r.host, r.owner, r.repo) +} + +// String implements Repo.String. +func (r *repoURL) String() string { + return fmt.Sprintf("%s-%s-%s", r.host, r.owner, r.repo) +} + +// Org implements Repo.Org. +func (r *repoURL) Org() clients.Repo { + return &repoURL{ + host: r.host, + owner: r.owner, + repo: githubOrgRepo, + } +} + +// IsValid implements Repo.IsValid. +func (r *repoURL) IsValid() error { + switch r.host { + case "github.com": + default: + return sce.WithMessage(sce.ErrorUnsupportedHost, r.host) + } + + if strings.TrimSpace(r.owner) == "" || strings.TrimSpace(r.repo) == "" { + return sce.WithMessage(sce.ErrorInvalidURL, + fmt.Sprintf("%v. Expected the full reposiroty url", r.URL())) + } + return nil +} + +func (r *repoURL) AppendMetadata(metadata ...string) { + r.metadata = append(r.metadata, metadata...) +} + +// Metadata implements Repo.Metadata. +func (r *repoURL) Metadata() []string { + return r.metadata +} + +// IsScorecardRepo implements Repo.IsScorecardRepo. +func (r *repoURL) IsScorecardRepo() bool { + return r.owner == scorecardOwner && r.repo == scorecardRepo +} + +// MakeGithubRepo takes input of form "owner/repo" or "github.com/owner/repo" +// and returns an implementation of clients.Repo interface. +func MakeGithubRepo(input string) (clients.Repo, error) { + var repo repoURL + if err := repo.parse(input); err != nil { + return nil, fmt.Errorf("error during parse: %w", err) + } + if err := repo.IsValid(); err != nil { + return nil, fmt.Errorf("error in IsValid: %w", err) + } + return &repo, nil +} diff --git a/clients/githubrepo/repo_url_test.go b/clients/githubrepo/repo_url_test.go new file mode 100644 index 00000000..dc9ed65e --- /dev/null +++ b/clients/githubrepo/repo_url_test.go @@ -0,0 +1,102 @@ +// Copyright 2020 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package githubrepo + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestRepoURL_IsValid(t *testing.T) { + t.Parallel() + tests := []struct { + name string + inputURL string + expected repoURL + wantErr bool + }{ + { + name: "Valid http address", + expected: repoURL{ + host: "github.com", + owner: "foo", + repo: "kubeflow", + }, + inputURL: "https://github.com/foo/kubeflow", + wantErr: false, + }, + { + name: "Valid http address with trailing slash", + expected: repoURL{ + host: "github.com", + owner: "foo", + repo: "kubeflow", + }, + inputURL: "https://github.com/foo/kubeflow/", + wantErr: false, + }, + { + name: "Non github repository", + expected: repoURL{ + host: "gitlab.com", + owner: "foo", + repo: "kubeflow", + }, + inputURL: "https://gitlab.com/foo/kubeflow", + wantErr: true, + }, + { + name: "github repository", + expected: repoURL{ + host: "github.com", + owner: "foo", + repo: "kubeflow", + }, + inputURL: "foo/kubeflow", + wantErr: false, + }, + { + name: "github repository", + expected: repoURL{ + host: "github.com", + owner: "foo", + repo: "kubeflow", + }, + inputURL: "https://github.com/foo/kubeflow", + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt // Re-initializing variable so it is not changed while executing the closure below + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + r := repoURL{ + host: tt.expected.host, + owner: tt.expected.owner, + repo: tt.expected.repo, + } + if err := r.parse(tt.inputURL); err != nil { + t.Errorf("repoURL.parse() error = %v", err) + } + if err := r.IsValid(); (err != nil) != tt.wantErr { + t.Errorf("repoURL.IsValid() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !cmp.Equal(tt.expected, r, cmp.AllowUnexported(repoURL{})) { + t.Errorf("Got diff: %s", cmp.Diff(tt.expected, r)) + } + }) + } +} diff --git a/clients/mockrepo/client.go b/clients/mockrepo/client.go index 6ba1c64c..ce596e5a 100644 --- a/clients/mockrepo/client.go +++ b/clients/mockrepo/client.go @@ -94,17 +94,17 @@ func (mr *MockRepoClientMockRecorder) GetFileContent(filename interface{}) *gomo } // InitRepo mocks base method. -func (m *MockRepoClient) InitRepo(owner, repo string) error { +func (m *MockRepoClient) InitRepo(repo clients.Repo) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InitRepo", owner, repo) + ret := m.ctrl.Call(m, "InitRepo", repo) ret0, _ := ret[0].(error) return ret0 } // InitRepo indicates an expected call of InitRepo. -func (mr *MockRepoClientMockRecorder) InitRepo(owner, repo interface{}) *gomock.Call { +func (mr *MockRepoClientMockRecorder) InitRepo(repo interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitRepo", reflect.TypeOf((*MockRepoClient)(nil).InitRepo), owner, repo) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitRepo", reflect.TypeOf((*MockRepoClient)(nil).InitRepo), repo) } // IsArchived mocks base method. diff --git a/clients/mockrepo/repo.go b/clients/mockrepo/repo.go new file mode 100644 index 00000000..6b14de62 --- /dev/null +++ b/clients/mockrepo/repo.go @@ -0,0 +1,150 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: clients/repo.go + +// Package mockrepo is a generated GoMock package. +package mockrepo + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + clients "github.com/ossf/scorecard/v2/clients" +) + +// MockRepo is a mock of Repo interface. +type MockRepo struct { + ctrl *gomock.Controller + recorder *MockRepoMockRecorder +} + +// MockRepoMockRecorder is the mock recorder for MockRepo. +type MockRepoMockRecorder struct { + mock *MockRepo +} + +// NewMockRepo creates a new mock instance. +func NewMockRepo(ctrl *gomock.Controller) *MockRepo { + mock := &MockRepo{ctrl: ctrl} + mock.recorder = &MockRepoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepo) EXPECT() *MockRepoMockRecorder { + return m.recorder +} + +// AppendMetadata mocks base method. +func (m *MockRepo) AppendMetadata(metadata ...string) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range metadata { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "AppendMetadata", varargs...) +} + +// AppendMetadata indicates an expected call of AppendMetadata. +func (mr *MockRepoMockRecorder) AppendMetadata(metadata ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendMetadata", reflect.TypeOf((*MockRepo)(nil).AppendMetadata), metadata...) +} + +// IsScorecardRepo mocks base method. +func (m *MockRepo) IsScorecardRepo() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsScorecardRepo") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsScorecardRepo indicates an expected call of IsScorecardRepo. +func (mr *MockRepoMockRecorder) IsScorecardRepo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsScorecardRepo", reflect.TypeOf((*MockRepo)(nil).IsScorecardRepo)) +} + +// IsValid mocks base method. +func (m *MockRepo) IsValid() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsValid") + ret0, _ := ret[0].(error) + return ret0 +} + +// IsValid indicates an expected call of IsValid. +func (mr *MockRepoMockRecorder) IsValid() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsValid", reflect.TypeOf((*MockRepo)(nil).IsValid)) +} + +// Metadata mocks base method. +func (m *MockRepo) Metadata() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Metadata") + ret0, _ := ret[0].([]string) + return ret0 +} + +// Metadata indicates an expected call of Metadata. +func (mr *MockRepoMockRecorder) Metadata() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Metadata", reflect.TypeOf((*MockRepo)(nil).Metadata)) +} + +// Org mocks base method. +func (m *MockRepo) Org() clients.Repo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Org") + ret0, _ := ret[0].(clients.Repo) + return ret0 +} + +// Org indicates an expected call of Org. +func (mr *MockRepoMockRecorder) Org() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Org", reflect.TypeOf((*MockRepo)(nil).Org)) +} + +// String mocks base method. +func (m *MockRepo) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockRepoMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockRepo)(nil).String)) +} + +// URL mocks base method. +func (m *MockRepo) URL() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "URL") + ret0, _ := ret[0].(string) + return ret0 +} + +// URL indicates an expected call of URL. +func (mr *MockRepoMockRecorder) URL() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "URL", reflect.TypeOf((*MockRepo)(nil).URL)) +} diff --git a/clients/repo.go b/clients/repo.go new file mode 100644 index 00000000..cf14ee86 --- /dev/null +++ b/clients/repo.go @@ -0,0 +1,27 @@ +// Copyright 2021 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package clients + +// Repo interface uniquely identifies a repo. +type Repo interface { + URL() string + String() string + Org() Repo + IsValid() error + Metadata() []string + AppendMetadata(metadata ...string) + // TODO: Find a better alterntive. + IsScorecardRepo() bool +} diff --git a/clients/repo_client.go b/clients/repo_client.go index b3b74910..d4aeb255 100644 --- a/clients/repo_client.go +++ b/clients/repo_client.go @@ -17,7 +17,7 @@ package clients // RepoClient interface is used by Scorecard checks to access a repo. type RepoClient interface { - InitRepo(owner, repo string) error + InitRepo(repo Repo) error URL() string IsArchived() (bool, error) ListFiles(predicate func(string) (bool, error)) ([]string, error) diff --git a/e2e/binary_artifacts_test.go b/e2e/binary_artifacts_test.go index da165ce5..9d959d16 100644 --- a/e2e/binary_artifacts_test.go +++ b/e2e/binary_artifacts_test.go @@ -32,15 +32,16 @@ var _ = Describe("E2E TEST:"+checks.CheckBinaryArtifacts, func() { Context("E2E TEST:Binary artifacts are not present in source code", func() { It("Should return not binary artifacts in source code", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf/scorecard") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf", "scorecard") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf", - Repo: "scorecard", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ @@ -61,15 +62,16 @@ var _ = Describe("E2E TEST:"+checks.CheckBinaryArtifacts, func() { }) It("Should return binary artifacts present in source code", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-binary-artifacts-e2e") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-binary-artifacts-e2e") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-binary-artifacts-e2e", + Repo: repo, Dlogger: &dl, } // TODO: upload real binaries to the repo as well. diff --git a/e2e/branch_protection_test.go b/e2e/branch_protection_test.go index c44cd7bb..2c9e1ec1 100644 --- a/e2e/branch_protection_test.go +++ b/e2e/branch_protection_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckBranchProtection, func() { Context("E2E TEST:Validating branch protection", func() { It("Should fail to return branch protection on other repositories", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("apache/airflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("apache", "airflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "apache", - Repo: "airflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ @@ -58,14 +59,15 @@ var _ = Describe("E2E TEST:"+checks.CheckBranchProtection, func() { Context("E2E TEST:Validating branch protection", func() { It("Should fail to return branch protection on other repositories", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-branch-protection-e2e") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-branch-protection-e2e") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-branch-protection-e2e", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/ci_tests_test.go b/e2e/ci_tests_test.go index 1a331a14..7c59c641 100644 --- a/e2e/ci_tests_test.go +++ b/e2e/ci_tests_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckCITests, func() { Context("E2E TEST:Validating use of CI tests", func() { It("Should return use of CI tests", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("apache/airflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("apache", "airflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "apache", - Repo: "airflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/cii_best_practices_test.go b/e2e/cii_best_practices_test.go index 8e58df1a..68fd8bef 100644 --- a/e2e/cii_best_practices_test.go +++ b/e2e/cii_best_practices_test.go @@ -22,6 +22,7 @@ import ( "github.com/ossf/scorecard/v2/checker" "github.com/ossf/scorecard/v2/checks" + "github.com/ossf/scorecard/v2/clients/githubrepo" scut "github.com/ossf/scorecard/v2/utests" ) @@ -29,11 +30,13 @@ var _ = Describe("E2E TEST:CIIBestPractices", func() { Context("E2E TEST:Validating use of CII Best Practices", func() { It("Should return use of CII Best Practices", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("tensorflow/tensorflow") + Expect(err).Should(BeNil()) + req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: nil, - Owner: "tensorflow", - Repo: "tensorflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/code_review_test.go b/e2e/code_review_test.go index 2c0339a4..5ae7f631 100644 --- a/e2e/code_review_test.go +++ b/e2e/code_review_test.go @@ -32,15 +32,16 @@ var _ = Describe("E2E TEST:CodeReview", func() { Context("E2E TEST:Validating use of code reviews", func() { It("Should return use of code reviews", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("apache/airflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("apache", "airflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "apache", - Repo: "airflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/contributors_test.go b/e2e/contributors_test.go index a9877350..86612142 100644 --- a/e2e/contributors_test.go +++ b/e2e/contributors_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckContributors, func() { Context("E2E TEST:Validating project contributors", func() { It("Should return valid project contributors", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf/scorecard") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf", "scorecard") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf", - Repo: "scorecard", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ @@ -57,14 +58,15 @@ var _ = Describe("E2E TEST:"+checks.CheckContributors, func() { }) It("Should return valid project contributors", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("apache/airflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("apache", "airflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) checkRequest := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "apache", - Repo: "airflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/dependency_update_tool_test.go b/e2e/dependency_update_tool_test.go index 961c6dc9..3d34d21a 100644 --- a/e2e/dependency_update_tool_test.go +++ b/e2e/dependency_update_tool_test.go @@ -32,15 +32,16 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() { Context("E2E TEST:Validating dependencies are updated with a tool", func() { It("Should return repo uses dependabot", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf/scorecard") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf", "scorecard") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf", - Repo: "scorecard", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ @@ -61,15 +62,16 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() { }) It("Should return repo uses renovatebot", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("netlify/netlify-cms") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("netlify", "netlify-cms") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "netlify", - Repo: "netlify-cms", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/fuzzing_test.go b/e2e/fuzzing_test.go index d74da908..fa5ed594 100644 --- a/e2e/fuzzing_test.go +++ b/e2e/fuzzing_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckFuzzing, func() { Context("E2E TEST:Validating use of fuzzing tools", func() { It("Should return use of fuzzing tools", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("tensorflow/tensorflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("tensorflow", "tensorflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "tensorflow", - Repo: "tensorflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/maintained_test.go b/e2e/maintained_test.go index 76fb0f3b..e73e546c 100644 --- a/e2e/maintained_test.go +++ b/e2e/maintained_test.go @@ -31,14 +31,15 @@ var _ = Describe("E2E TEST:"+checks.CheckMaintained, func() { Context("E2E TEST:Validating maintained status", func() { It("Should return valid maintained status", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("apache/airflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("apache", "airflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "apache", - Repo: "airflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/packaging_test.go b/e2e/packaging_test.go index 262befba..8cb9bfd5 100644 --- a/e2e/packaging_test.go +++ b/e2e/packaging_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckPackaging, func() { Context("E2E TEST:Validating use of packaging in CI/CD", func() { It("Should return use of packaging in CI/CD", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-packaging-e2e") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-packaging-e2e") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-packaging-e2e", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/permissions_test.go b/e2e/permissions_test.go index f9334e21..aa29b12b 100644 --- a/e2e/permissions_test.go +++ b/e2e/permissions_test.go @@ -29,14 +29,15 @@ var _ = Describe("E2E TEST:"+checks.CheckTokenPermissions, func() { Context("E2E TEST:Validating token permission check", func() { It("Should return token permission works", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-token-permissions-e2e") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-token-permissions-e2e") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-token-permissions-e2e", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/pinned_dependencies_test.go b/e2e/pinned_dependencies_test.go index 07e84c30..0a579ee8 100644 --- a/e2e/pinned_dependencies_test.go +++ b/e2e/pinned_dependencies_test.go @@ -31,15 +31,16 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() { Context("E2E TEST:Validating dependencies check is working", func() { It("Should return dependencies check is working", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-pinned-dependencies-e2e") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-pinned-dependencies-e2e") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-pinned-dependencies-e2e", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/sast_test.go b/e2e/sast_test.go index 651f00d1..3c227b10 100644 --- a/e2e/sast_test.go +++ b/e2e/sast_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckSAST, func() { Context("E2E TEST:Validating use of SAST tools", func() { It("Should return use of SAST tools", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("apache/airflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("apache", "airflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "apache", - Repo: "airflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/security_policy_test.go b/e2e/security_policy_test.go index b2a761eb..d150caef 100644 --- a/e2e/security_policy_test.go +++ b/e2e/security_policy_test.go @@ -29,15 +29,16 @@ var _ = Describe("E2E TEST:SecurityPolicy", func() { Context("E2E TEST:Validating security policy", func() { It("Should return valid security policy", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("tensorflow/tensorflow") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("tensorflow", "tensorflow") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "tensorflow", - Repo: "tensorflow", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ @@ -57,15 +58,16 @@ var _ = Describe("E2E TEST:SecurityPolicy", func() { }) It("Should return valid security policy for rust repositories", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("randombit/botan") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("randombit", "botan") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "randombit", - Repo: "botan", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/signedreleases_test.go b/e2e/signedreleases_test.go index 1afeb7d2..908828dc 100644 --- a/e2e/signedreleases_test.go +++ b/e2e/signedreleases_test.go @@ -30,14 +30,15 @@ var _ = Describe("E2E TEST:"+checks.CheckSignedReleases, func() { Context("E2E TEST:Validating signed releases", func() { It("Should return valid signed releases", func() { dl := scut.TestDetailLogger{} + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-signed-releases-e2e") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-signed-releases-e2e") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-signed-releases-e2e", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/e2e/vulnerabilities_test.go b/e2e/vulnerabilities_test.go index b9c8d029..d22e05d0 100644 --- a/e2e/vulnerabilities_test.go +++ b/e2e/vulnerabilities_test.go @@ -29,16 +29,17 @@ import ( var _ = Describe("E2E TEST:Vulnerabilities", func() { Context("E2E TEST:Validating vulnerabilities status", func() { It("Should return that there are no vulnerabilities", func() { + repo, err := githubrepo.MakeGithubRepo("ossf/scorecard") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf", "scorecard") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) dl := scut.TestDetailLogger{} req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf", - Repo: "scorecard", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ @@ -58,16 +59,17 @@ var _ = Describe("E2E TEST:Vulnerabilities", func() { }) It("Should return that there are vulnerabilities", func() { + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-vulnerabilities-open62541") + Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err := repoClient.InitRepo("ossf-tests", "scorecard-check-vulnerabilities-open62541") + err = repoClient.InitRepo(repo) Expect(err).Should(BeNil()) dl := scut.TestDetailLogger{} checkRequest := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, - Owner: "ossf-tests", - Repo: "scorecard-check-vulnerabilities-open62541", + Repo: repo, Dlogger: &dl, } expected := scut.TestReturn{ diff --git a/errors/public.go b/errors/public.go index 14fc3a86..3875398b 100644 --- a/errors/public.go +++ b/errors/public.go @@ -23,6 +23,10 @@ import ( var ( ErrScorecardInternal = errors.New("internal error") ErrRepoUnreachable = errors.New("repo unreachable") + // ErrorUnsupportedHost indicates the repo's host is unsupported. + ErrorUnsupportedHost = errors.New("unsupported host") + // ErrorInvalidURL indicates the repo's full URL was not passed. + ErrorInvalidURL = errors.New("invalid repo flag") ) // WithMessage wraps any of the errors listed above. diff --git a/pkg/scorecard.go b/pkg/scorecard.go index 9628c3cd..d2699de5 100644 --- a/pkg/scorecard.go +++ b/pkg/scorecard.go @@ -26,6 +26,7 @@ import ( "github.com/ossf/scorecard/v2/checker" "github.com/ossf/scorecard/v2/clients" + "github.com/ossf/scorecard/v2/clients/githubrepo" sce "github.com/ossf/scorecard/v2/errors" "github.com/ossf/scorecard/v2/repos" "github.com/ossf/scorecard/v2/stats" @@ -37,13 +38,12 @@ func logStats(ctx context.Context, startTime time.Time) { } func runEnabledChecks(ctx context.Context, - repo repos.RepoURL, checksToRun checker.CheckNameToFnMap, repoClient clients.RepoClient, + repo clients.Repo, checksToRun checker.CheckNameToFnMap, repoClient clients.RepoClient, resultsCh chan checker.CheckResult) { request := checker.CheckRequest{ Ctx: ctx, RepoClient: repoClient, - Owner: repo.Owner, - Repo: repo.Repo, + Repo: repo, } wg := sync.WaitGroup{} for checkName, checkFn := range checksToRun { @@ -66,18 +66,22 @@ func runEnabledChecks(ctx context.Context, // RunScorecards runs enabled Scorecard checks on a RepoURL. func RunScorecards(ctx context.Context, - repo repos.RepoURL, + repoURL repos.RepoURL, checksToRun checker.CheckNameToFnMap, repoClient clients.RepoClient) (ScorecardResult, error) { - ctx, err := tag.New(ctx, tag.Upsert(stats.Repo, repo.URL())) + ctx, err := tag.New(ctx, tag.Upsert(stats.Repo, repoURL.URL())) if err != nil { return ScorecardResult{}, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("tag.New: %v", err)) } defer logStats(ctx, time.Now()) - if err := repoClient.InitRepo(repo.Owner, repo.Repo); err != nil { + repo, err := githubrepo.MakeGithubRepo(repoURL.URL()) + if err != nil { + return ScorecardResult{}, sce.WithMessage(err, "") + } + if err := repoClient.InitRepo(repo); err != nil { // No need to call sce.WithMessage() since InitRepo will do that for us. - // nolint: wrapcheck + //nolint:wrapcheck return ScorecardResult{}, err } defer repoClient.Close() diff --git a/repos/repo_url.go b/repos/repo_url.go index 49af4d2e..a53e3d30 100644 --- a/repos/repo_url.go +++ b/repos/repo_url.go @@ -33,7 +33,8 @@ var ( ErrorInvalidURL = errors.New("invalid repo flag") ) -//nolint:revive +// nolint:revive +// TODO: Remove RepoURL and replace all instances with clients.Repo interface. type RepoURL struct { Host, Owner, Repo string Metadata []string