diff --git a/README.md b/README.md index caf23e1d9641..64fc69f0307c 100644 --- a/README.md +++ b/README.md @@ -406,7 +406,7 @@ RESULTS ##### Using a Package manager -For projects in the `--npm`, `--pypi`, or `--rubygems` ecosystems, you have the +For projects in the `--npm`, `--pypi`, `--rubygems`, or `--nuget` ecosystems, you have the option to run Scorecard using a package manager. Provide the package name to run the checks on the corresponding GitHub source code. diff --git a/cmd/package_managers.go b/cmd/package_managers.go index a18ecbd25a8b..b267c3dc7432 100644 --- a/cmd/package_managers.go +++ b/cmd/package_managers.go @@ -17,8 +17,11 @@ package cmd import ( "encoding/json" + "encoding/xml" "fmt" + "golang.org/x/exp/slices" + sce "github.com/ossf/scorecard/v4/errors" ) @@ -27,7 +30,7 @@ type packageMangerResponse struct { exists bool } -func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems string, +func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems, nuget string, manager packageManagerClient, ) (packageMangerResponse, error) { if npm != "" { @@ -51,6 +54,13 @@ func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems string, associatedRepo: gitRepo, }, err } + if nuget != "" { + gitRepo, err := fetchGitRepositoryFromNuget(nuget, manager) + return packageMangerResponse{ + exists: true, + associatedRepo: gitRepo, + }, err + } return packageMangerResponse{}, nil } @@ -77,6 +87,34 @@ type rubyGemsSearchResults struct { SourceCodeURI string `json:"source_code_uri"` } +type nugetIndexResult struct { + ID string `json:"@id"` + Type string `json:"@type"` +} + +type nugetIndexResults struct { + Resources []nugetIndexResult `json:"resources"` +} + +type nugetpackageIndexResults struct { + Versions []string `json:"versions"` +} + +type nugetNuspec struct { + XMLName xml.Name `xml:"package"` + Metadata nuspecMetadata `xml:"metadata"` +} + +type nuspecMetadata struct { + XMLName xml.Name `xml:"metadata"` + Repository nuspecRepository `xml:"repository"` +} + +type nuspecRepository struct { + XMLName xml.Name `xml:"repository"` + URL string `xml:"url,attr"` +} + // Gets the GitHub repository URL for the npm package. func fetchGitRepositoryFromNPM(packageName string, packageManager packageManagerClient) (string, error) { npmSearchURL := "https://registry.npmjs.org/-/v1/search?text=%s&size=1" @@ -138,3 +176,60 @@ func fetchGitRepositoryFromRubyGems(packageName string, manager packageManagerCl } return v.SourceCodeURI, nil } + +// Gets the GitHub repository URL for the nuget package. +func fetchGitRepositoryFromNuget(packageName string, manager packageManagerClient) (string, error) { + nugetIndexURL := "https://api.nuget.org/v3/index.json" + respIndex, err := manager.GetURI(nugetIndexURL) + if err != nil { + return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to get nuget index json: %v", err)) + } + defer respIndex.Body.Close() + nugetIndexResults := &nugetIndexResults{} + err = json.NewDecoder(respIndex.Body).Decode(nugetIndexResults) + + if err != nil { + return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to parse nuget index json: %v", err)) + } + packageBaseAddressIndex := slices.IndexFunc(nugetIndexResults.Resources, + func(n nugetIndexResult) bool { return n.Type == "PackageBaseAddress/3.0.0" }) + if packageBaseAddressIndex == -1 { + return "", sce.WithMessage(sce.ErrScorecardInternal, "failed to find package base URI at nuget index json") + } + + nugetPackageBaseURL := nugetIndexResults.Resources[packageBaseAddressIndex].ID + + respPackageIndex, err := manager.Get(nugetPackageBaseURL+"%s/index.json", packageName) + if err != nil { + return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to get nuget package index json: %v", err)) + } + defer respPackageIndex.Body.Close() + packageIndexResults := &nugetpackageIndexResults{} + err = json.NewDecoder(respPackageIndex.Body).Decode(packageIndexResults) + + if err != nil { + return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to parse nuget package index json: %v", err)) + } + + lastVersion := packageIndexResults.Versions[len(packageIndexResults.Versions)-1] + + respPackageSpec, err := manager.Get(nugetPackageBaseURL+"%[1]v/"+lastVersion+"/%[1]v.nuspec", packageName) + if err != nil { + return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to get nuget package spec json: %v", err)) + } + defer respPackageSpec.Body.Close() + + packageSpecResults := &nugetNuspec{} + err = xml.NewDecoder(respPackageSpec.Body).Decode(packageSpecResults) + + if err != nil { + return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("failed to parse nuget nuspec xml: %v", err)) + } + if packageSpecResults.Metadata == (nuspecMetadata{}) || + packageSpecResults.Metadata.Repository == (nuspecRepository{}) || + packageSpecResults.Metadata.Repository.URL == "" { + return "", sce.WithMessage(sce.ErrScorecardInternal, + fmt.Sprintf("source repo is not defined for nuget package %v", packageName)) + } + return packageSpecResults.Metadata.Repository.URL, nil +} diff --git a/cmd/package_managers_test.go b/cmd/package_managers_test.go index b4e398109f4b..6a07810f565d 100644 --- a/cmd/package_managers_test.go +++ b/cmd/package_managers_test.go @@ -20,6 +20,7 @@ import ( "errors" "io" "net/http" + "strings" "testing" "github.com/golang/mock/gomock" @@ -706,3 +707,541 @@ func Test_fetchGitRepositoryFromRubyGems(t *testing.T) { }) } } + +func Test_fetchGitRepositoryFromNuget(t *testing.T) { + t.Parallel() + type args struct { + packageName string + resultIndex string + resultPackageIndex string + resultPackageSpec string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "fetchGitRepositoryFromNuget", + + args: args{ + packageName: "nuget-package", + resultIndex: ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://azuresearch-usnc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://azuresearch-ussc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (secondary)" + }, + { + "@id": "https://azuresearch-usnc.nuget.org/autocomplete", + "@type": "SearchAutocompleteService", + "comment": "Autocomplete endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://api.nuget.org/v3/registration5-semver1/", + "@type": "RegistrationsBaseUrl", + "comment": "Base URL of Azure storage where NuGet package registration info is stored" + }, + { + "@id": "https://api.nuget.org/v3-flatcontainer/", + "@type": "PackageBaseAddress/3.0.0", + "comment": "Base URL of where NuGet packages are stored, in the format ..." + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery" + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery/2.0.0" + }, + { + "@id": "https://www.nuget.org/api/v2/package", + "@type": "PackagePublish/2.0.0" + } + ] + } + `, + resultPackageIndex: ` + { + "versions": [ + "13.0.2", + "13.0.3-beta1", + "13.0.3" + ] + } + `, + resultPackageSpec: ` + + + Foo + 13.0.3 + Foo.NET + Foo Foo + Foo Foo + false + MIT + https://licenses.nuget.org/MIT + https://www.newtonsoft.com/json + https://www.foo.com/content/images/nugeticon.png + Foo.NET is a popular foo framework for .NET + Copyright ©Foo Foo 2008 + foo + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + }, + want: "foo", + wantErr: false, + }, + { + name: "fetchGitRepositoryFromNuget_error_index", + + args: args{ + packageName: "nuget-package", + resultIndex: "", + resultPackageIndex: "", + resultPackageSpec: "", + }, + want: "", + wantErr: true, + }, + { + name: "fetchGitRepositoryFromNuget_bad_index", + + args: args{ + packageName: "nuget-package", + resultIndex: "foo", + resultPackageIndex: "", + resultPackageSpec: "", + }, + want: "", + wantErr: true, + }, + { + name: "fetchGitRepositoryFromNuget_error_package_index", + + args: args{ + packageName: "nuget-package", + resultIndex: ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://azuresearch-usnc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://azuresearch-ussc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (secondary)" + }, + { + "@id": "https://azuresearch-usnc.nuget.org/autocomplete", + "@type": "SearchAutocompleteService", + "comment": "Autocomplete endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://api.nuget.org/v3/registration5-semver1/", + "@type": "RegistrationsBaseUrl", + "comment": "Base URL of Azure storage where NuGet package registration info is stored" + }, + { + "@id": "https://api.nuget.org/v3-flatcontainer/", + "@type": "PackageBaseAddress/3.0.0", + "comment": "Base URL of where NuGet packages are stored, in the format ..." + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery" + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery/2.0.0" + }, + { + "@id": "https://www.nuget.org/api/v2/package", + "@type": "PackagePublish/2.0.0" + } + ] + } + `, + resultPackageIndex: "", + resultPackageSpec: "", + }, + want: "", + wantErr: true, + }, + { + name: "fetchGitRepositoryFromNuget_bad_package_index", + + args: args{ + packageName: "nuget-package", + resultIndex: ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://azuresearch-usnc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://azuresearch-ussc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (secondary)" + }, + { + "@id": "https://azuresearch-usnc.nuget.org/autocomplete", + "@type": "SearchAutocompleteService", + "comment": "Autocomplete endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://api.nuget.org/v3/registration5-semver1/", + "@type": "RegistrationsBaseUrl", + "comment": "Base URL of Azure storage where NuGet package registration info is stored" + }, + { + "@id": "https://api.nuget.org/v3-flatcontainer/", + "@type": "PackageBaseAddress/3.0.0", + "comment": "Base URL of where NuGet packages are stored, in the format ..." + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery" + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery/2.0.0" + }, + { + "@id": "https://www.nuget.org/api/v2/package", + "@type": "PackagePublish/2.0.0" + } + ] + } + `, + resultPackageIndex: "foo", + resultPackageSpec: "", + }, + want: "", + wantErr: true, + }, + { + name: "fetchGitRepositoryFromNuget_error_package_spec", + + args: args{ + packageName: "nuget-package", + resultIndex: ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://azuresearch-usnc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://azuresearch-ussc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (secondary)" + }, + { + "@id": "https://azuresearch-usnc.nuget.org/autocomplete", + "@type": "SearchAutocompleteService", + "comment": "Autocomplete endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://api.nuget.org/v3/registration5-semver1/", + "@type": "RegistrationsBaseUrl", + "comment": "Base URL of Azure storage where NuGet package registration info is stored" + }, + { + "@id": "https://api.nuget.org/v3-flatcontainer/", + "@type": "PackageBaseAddress/3.0.0", + "comment": "Base URL of where NuGet packages are stored, in the format ..." + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery" + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery/2.0.0" + }, + { + "@id": "https://www.nuget.org/api/v2/package", + "@type": "PackagePublish/2.0.0" + } + ] + } + `, + resultPackageIndex: ` + { + "versions": [ + "13.0.2", + "13.0.3-beta1", + "13.0.3" + ] + } + `, + resultPackageSpec: "", + }, + want: "", + wantErr: true, + }, + { + name: "fetchGitRepositoryFromNuget_bad_package_spec", + + args: args{ + packageName: "nuget-package", + resultIndex: ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://azuresearch-usnc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://azuresearch-ussc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (secondary)" + }, + { + "@id": "https://azuresearch-usnc.nuget.org/autocomplete", + "@type": "SearchAutocompleteService", + "comment": "Autocomplete endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://api.nuget.org/v3/registration5-semver1/", + "@type": "RegistrationsBaseUrl", + "comment": "Base URL of Azure storage where NuGet package registration info is stored" + }, + { + "@id": "https://api.nuget.org/v3-flatcontainer/", + "@type": "PackageBaseAddress/3.0.0", + "comment": "Base URL of where NuGet packages are stored, in the format ..." + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery" + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery/2.0.0" + }, + { + "@id": "https://www.nuget.org/api/v2/package", + "@type": "PackagePublish/2.0.0" + } + ] + } + `, + resultPackageIndex: ` + { + "versions": [ + "13.0.2", + "13.0.3-beta1", + "13.0.3" + ] + } + `, + resultPackageSpec: "foo", + }, + want: "", + wantErr: true, + }, + { + name: "fetchGitRepositoryFromNuget_missing_repo", + + args: args{ + packageName: "nuget-package", + resultIndex: ` + { + "version": "3.0.0", + "resources": [ + { + "@id": "https://azuresearch-usnc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://azuresearch-ussc.nuget.org/query", + "@type": "SearchQueryService", + "comment": "Query endpoint of NuGet Search service (secondary)" + }, + { + "@id": "https://azuresearch-usnc.nuget.org/autocomplete", + "@type": "SearchAutocompleteService", + "comment": "Autocomplete endpoint of NuGet Search service (primary)" + }, + { + "@id": "https://api.nuget.org/v3/registration5-semver1/", + "@type": "RegistrationsBaseUrl", + "comment": "Base URL of Azure storage where NuGet package registration info is stored" + }, + { + "@id": "https://api.nuget.org/v3-flatcontainer/", + "@type": "PackageBaseAddress/3.0.0", + "comment": "Base URL of where NuGet packages are stored, in the format ..." + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery" + }, + { + "@id": "https://www.nuget.org/api/v2", + "@type": "LegacyGallery/2.0.0" + }, + { + "@id": "https://www.nuget.org/api/v2/package", + "@type": "PackagePublish/2.0.0" + } + ] + } + `, + resultPackageIndex: ` + { + "versions": [ + "13.0.2", + "13.0.3-beta1", + "13.0.3" + ] + } + `, + resultPackageSpec: ` + + + Foo + 13.0.3 + Foo.NET + Foo Foo + Foo Foo + false + MIT + https://licenses.nuget.org/MIT + https://www.newtonsoft.com/json + https://www.foo.com/content/images/nugeticon.png + Foo.NET is a popular foo framework for .NET + Copyright ©Foo Foo 2008 + foo + + + + + + + + + + + + + + + + + + + + + + + + + + `, + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + p := NewMockpackageManagerClient(ctrl) + p.EXPECT().GetURI(gomock.Any()). + DoAndReturn(func(url string) (*http.Response, error) { + if tt.wantErr && tt.args.resultIndex == "" { + //nolint + return nil, errors.New("error") + } + + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewBufferString(tt.args.resultIndex)), + }, nil + }).AnyTimes() + p.EXPECT().Get(gomock.Any(), tt.args.packageName). + DoAndReturn(func(url, packageName string) (*http.Response, error) { + if tt.wantErr && tt.args.resultPackageIndex == "" { + //nolint + return nil, errors.New("error") + } + + if tt.wantErr && tt.args.resultPackageSpec == "" { + //nolint + return nil, errors.New("error") + } + if strings.HasSuffix(url, "index.json") { + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewBufferString(tt.args.resultPackageIndex)), + }, nil + } else if strings.HasSuffix(url, ".nuspec") { + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewBufferString(tt.args.resultPackageSpec)), + }, nil + } + return nil, nil + }).AnyTimes() + got, err := fetchGitRepositoryFromNuget(tt.args.packageName, p) + if (err != nil) != tt.wantErr { + t.Errorf("fetchGitRepositoryFromNuget() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("fetchGitRepositoryFromNuget() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cmd/packagemanager_client.go b/cmd/packagemanager_client.go index f453d22cfd02..d98c15506a6e 100644 --- a/cmd/packagemanager_client.go +++ b/cmd/packagemanager_client.go @@ -22,16 +22,28 @@ import ( type packageManagerClient interface { Get(URI string, packagename string) (*http.Response, error) + + GetURI(URI string) (*http.Response, error) } type packageManager struct{} // nolint: noctx func (c *packageManager) Get(url, packageName string) (*http.Response, error) { + return c.getRemoteURL(fmt.Sprintf(url, packageName)) +} + +// nolint: noctx +func (c *packageManager) GetURI(url string) (*http.Response, error) { + return c.getRemoteURL(url) +} + +// nolint: noctx +func (c *packageManager) getRemoteURL(url string) (*http.Response, error) { const timeout = 10 client := &http.Client{ Timeout: timeout * time.Second, } //nolint - return client.Get(fmt.Sprintf(url, packageName)) + return client.Get(url) } diff --git a/cmd/packagemanager_mockclient.go b/cmd/packagemanager_mockclient.go index 40cc49d369fc..daecee250cd8 100644 --- a/cmd/packagemanager_mockclient.go +++ b/cmd/packagemanager_mockclient.go @@ -58,8 +58,23 @@ func (m *MockpackageManagerClient) Get(URI, packagename string) (*http.Response, return ret0, ret1 } +// GetURI mocks base method. +func (m *MockpackageManagerClient) GetURI(URI string) (*http.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetURI", URI) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + // Get indicates an expected call of Get. func (mr *MockpackageManagerClientMockRecorder) Get(URI, packagename interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockpackageManagerClient)(nil).Get), URI, packagename) } + +// GetURI indicates an expected call of GetURI. +func (mr *MockpackageManagerClientMockRecorder) GetURI(URI interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetURI", reflect.TypeOf((*MockpackageManagerClient)(nil).GetURI), URI) +} diff --git a/cmd/root.go b/cmd/root.go index da4cd0610d01..608b4935d7be 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,7 +37,7 @@ import ( const ( scorecardLong = "A program that shows the OpenSSF scorecard for an open source software." - scorecardUse = `./scorecard (--repo= | --local= | --{npm,pypi,rubygems}=) + scorecardUse = `./scorecard (--repo= | --local= | --{npm,pypi,rubygems,nuget}=) [--checks=check1,...] [--show-details]` scorecardShort = "OpenSSF Scorecard" ) @@ -74,7 +74,7 @@ func New(o *options.Options) *cobra.Command { func rootCmd(o *options.Options) error { p := &packageManager{} // Set `repo` from package managers. - pkgResp, err := fetchGitRepositoryFromPackageManagers(o.NPM, o.PyPI, o.RubyGems, p) + pkgResp, err := fetchGitRepositoryFromPackageManagers(o.NPM, o.PyPI, o.RubyGems, o.Nuget, p) if err != nil { return fmt.Errorf("fetchGitRepositoryFromPackageManagers: %w", err) } diff --git a/options/flags.go b/options/flags.go index 66a48c2b85af..1652c9fd18ae 100644 --- a/options/flags.go +++ b/options/flags.go @@ -45,6 +45,9 @@ const ( // FlagRubyGems is the flag name for specifying a RubyGems repository. FlagRubyGems = "rubygems" + // FlagNuget is the flag name for specifying a Nuget repository. + FlagNuget = "nuget" + // FlagMetadata is the flag name for specifying metadata for the project. FlagMetadata = "metadata" @@ -120,6 +123,13 @@ func (o *Options) AddFlags(cmd *cobra.Command) { "rubygems package to check, given that the rubygems package has a GitHub repository", ) + cmd.Flags().StringVar( + &o.Nuget, + FlagNuget, + o.Nuget, + "nuget package to check, given that the nuget package has a GitHub repository", + ) + cmd.Flags().StringSliceVar( &o.Metadata, FlagMetadata, diff --git a/options/options.go b/options/options.go index 164c356b8218..5be1fda1febe 100644 --- a/options/options.go +++ b/options/options.go @@ -37,6 +37,7 @@ type Options struct { NPM string PyPI string RubyGems string + Nuget string PolicyFile string // TODO(action): Add logic for writing results to file ResultsFile string @@ -113,7 +114,7 @@ var ( errPolicyFileNotSupported = errors.New("policy file is not supported yet") errRawOptionNotSupported = errors.New("raw option is not supported yet") errRepoOptionMustBeSet = errors.New( - "exactly one of `repo`, `npm`, `pypi`, `rubygems` or `local` must be set", + "exactly one of `repo`, `npm`, `pypi`, `rubygems`, `nuget` or `local` must be set", ) errSARIFNotSupported = errors.New("SARIF format is not supported yet") errValidate = errors.New("some options could not be validated") @@ -124,11 +125,12 @@ var ( func (o *Options) Validate() error { var errs []error - // Validate exactly one of `--repo`, `--npm`, `--pypi`, `--rubygems`, `--local` is enabled. + // Validate exactly one of `--repo`, `--npm`, `--pypi`, `--rubygems`, `--nuget`, `--local` is enabled. if boolSum(o.Repo != "", o.NPM != "", o.PyPI != "", o.RubyGems != "", + o.Nuget != "", o.Local != "") != 1 { errs = append( errs, diff --git a/options/options_test.go b/options/options_test.go index b69d5c35e07a..8098e8ebc907 100644 --- a/options/options_test.go +++ b/options/options_test.go @@ -21,7 +21,7 @@ import ( ) // Cannot run parallel tests because of the ENV variables. -//nolint +// nolint func TestOptions_Validate(t *testing.T) { type fields struct { Repo string @@ -32,6 +32,7 @@ func TestOptions_Validate(t *testing.T) { NPM string PyPI string RubyGems string + Nuget string PolicyFile string ResultsFile string ChecksToRun []string @@ -99,6 +100,7 @@ func TestOptions_Validate(t *testing.T) { NPM: tt.fields.NPM, PyPI: tt.fields.PyPI, RubyGems: tt.fields.RubyGems, + Nuget: tt.fields.Nuget, PolicyFile: tt.fields.PolicyFile, ResultsFile: tt.fields.ResultsFile, ChecksToRun: tt.fields.ChecksToRun,