diff --git a/.dockerignore b/.dockerignore index df9a8584..3ac0ee37 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,4 @@ ** # Allow binary -!/codeowners-validator +!/codeowners diff --git a/.gitignore b/.gitignore index 210df0fd..62e6c4b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ scripts/ .idea -codeowners-validator +/codeowners dist/ tmp/ bin/ diff --git a/.goreleaser.yml b/.goreleaser.yml index 8bf525c6..97801da5 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,5 +1,5 @@ builds: - - id: codeowners-validator + - id: codeowners env: - CGO_ENABLED=0 goos: @@ -35,15 +35,15 @@ archives: dockers: - dockerfile: Dockerfile ids: - - codeowners-validator + - codeowners image_templates: - - "ghcr.io/mszostok/codeowners-validator:stable" - - "ghcr.io/mszostok/codeowners-validator:{{ .Tag }}" - - "ghcr.io/mszostok/codeowners-validator:v{{ .Major }}.{{ .Minor }}" - - "ghcr.io/mszostok/codeowners-validator:v{{ .Major }}" - - "mszostok/codeowners-validator:latest" - - "mszostok/codeowners-validator:{{ .Tag }}" - - "mszostok/codeowners-validator:v{{ .Major }}.{{ .Minor }}" + - "ghcr.io/mszostok/codeowners:stable" + - "ghcr.io/mszostok/codeowners:{{ .Tag }}" + - "ghcr.io/mszostok/codeowners:v{{ .Major }}.{{ .Minor }}" + - "ghcr.io/mszostok/codeowners:v{{ .Major }}" + - "mszostok/codeowners:latest" + - "mszostok/codeowners:{{ .Tag }}" + - "mszostok/codeowners:v{{ .Major }}.{{ .Minor }}" checksum: name_template: 'checksums.txt' @@ -68,11 +68,11 @@ release: # Default is extracted from the origin remote URL or empty if its private hosted. github: owner: mszostok - name: codeowners-validator + name: codeowners brews: - - name: codeowners-validator - homepage: https://github.com/mszostok/codeowners-validator + - name: codeowners + homepage: https://github.com/mszostok/codeowners description: Ensures the correctness of your CODEOWNERS file. license: "Apache License 2.0" tap: @@ -82,4 +82,4 @@ brews: name: Mateusz Szostok email: szostok.mateusz@gmail.com test: | - system "#{bin}/codeowners-validator -v --short" + system "#{bin}/codeowners -v --short" diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 00000000..11c3ad33 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,6 @@ +- id: codeowners + name: Validate CODEOWNERS file + description: Ensures the correctness of your CODEOWNERS file. + language: golang + # TODO: types: hardcode expected file location? + entry: codeowners validate diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b47ebec1..ed6d734b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,26 +9,26 @@ This document contains contribution guidelines for this repository. Read it befo ## Contributing -Before proposing or adding changes, check the [existing issues](https://github.com/mszostok/codeowners-validator/issues) and make sure the discussion/work has not already been started to avoid duplication. +Before proposing or adding changes, check the [existing issues](https://github.com/mszostok/codeowners/issues) and make sure the discussion/work has not already been started to avoid duplication. -If you'd like to see a new feature implemented, use this [feature request template](https://github.com/mszostok/codeowners-validator/issues/new?assignees=&labels=&template=feature_request.md) to create an issue. +If you'd like to see a new feature implemented, use this [feature request template](https://github.com/mszostok/codeowners/issues/new?assignees=&labels=&template=feature_request.md) to create an issue. -Similarly, if you spot a bug, use this [bug report template](https://github.com/mszostok/codeowners-validator/issues/new?assignees=mszostok&labels=bug&template=bug_report.md) to let us know! +Similarly, if you spot a bug, use this [bug report template](https://github.com/mszostok/codeowners/issues/new?assignees=mszostok&labels=bug&template=bug_report.md) to let us know! ### Ready for action? Start developing! To start contributing, follow these steps: -1. Fork the `codeowners-validator` repository. +1. Fork the `codeowners` repository. 2. Clone the repository locally. > **TIP:** This project uses Go modules, so you can check it out locally wherever you want. It doesn't need to be checked out in `$GOPATH`. -3. Set the `codeowners-validator` repository as upstream: +3. Set the `codeowners` repository as upstream: ```bash - git remote add upstream git@github.com:mszostok/codeowners-validator.git + git remote add upstream git@github.com:mszostok/codeowners.git ``` 4. Fetch all the remote branches for this repository: diff --git a/Dockerfile b/Dockerfile index 97ee0fd1..26e26f6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ RUN apk --no-cache add ca-certificates git FROM scratch -LABEL org.opencontainers.image.source=https://github.com/mszostok/codeowners-validator +LABEL org.opencontainers.image.source=https://github.com/mszostok/codeowners -COPY ./codeowners-validator /codeowners-validator +COPY ./codeowners /codeowners COPY --from=deps /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=deps /usr/bin/git /usr/bin/git @@ -16,4 +16,4 @@ COPY --from=deps /usr/bin/xargs /usr/bin/xargs COPY --from=deps /lib /lib COPY --from=deps /usr/lib /usr/lib -ENTRYPOINT ["/codeowners-validator"] +ENTRYPOINT ["/codeowners"] diff --git a/Makefile b/Makefile index 01284640..ab5db449 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ all: build-race test-unit test-integration test-lint # When running integration tests on windows machine # it cannot execute binary without extension. # It needs to be parametrized, so we can override it on CI. -export BINARY_PATH = $(ROOT_DIR)/codeowners-validator$(BINARY_EXT) +export BINARY_PATH = $(ROOT_DIR)/codeowners$(BINARY_EXT) ############ # Building # diff --git a/README.md b/README.md index cc5fd98d..971e114a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ## Codeowners Validator Software License -Go Report Card +Go Report Card Twitter Follow The Codeowners Validator project validates the GitHub [CODEOWNERS](https://help.github.com/articles/about-code-owners/) file based on [specified checks](#checks). It supports public and private GitHub repositories and also GitHub Enterprise installations. @@ -29,7 +29,7 @@ docker run --rm -v $(pwd):/repo -w /repo \ -e GITHUB_ACCESS_TOKEN="$GH_TOKEN" \ -e EXPERIMENTAL_CHECKS="notowned" \ -e OWNER_CHECKER_REPOSITORY="org-name/rep-name" \ - mszostok/codeowners-validator:v0.7.4 + mszostok/codeowners:v0.7.4 ``` #### Command line @@ -40,13 +40,13 @@ env REPOSITORY_PATH="." \ GITHUB_ACCESS_TOKEN="$GH_TOKEN" \ EXPERIMENTAL_CHECKS="notowned" \ OWNER_CHECKER_REPOSITORY="org-name/rep-name" \ - codeowners-validator + codeowners ``` #### GitHub Action ```yaml -- uses: mszostok/codeowners-validator@v0.7.4 +- uses: mszostok/codeowners@v0.7.4 with: checks: "files,owners,duppatterns,syntax" experimental_checks: "notowned,avoid-shadowing" @@ -62,44 +62,44 @@ Check the [Configuration](#configuration) section for more info on how to enable ## Installation -It's highly recommended to install a fixed version of `codeowners-validator`. Releases are available on the [releases page](https://github.com/mszostok/codeowners-validator/releases). +It's highly recommended to install a fixed version of `codeowners`. Releases are available on the [releases page](https://github.com/mszostok/codeowners/releases). ### macOS & Linux -`codeowners-validator` is available via [Homebrew](https://brew.sh/index_pl). +`codeowners` is available via [Homebrew](https://brew.sh/index_pl). #### Homebrew | Install | Upgrade | |--------------------------------------------------|--------------------------------------------------| -| `brew install mszostok/tap/codeowners-validator` | `brew upgrade mszostok/tap/codeowners-validator` | +| `brew install mszostok/tap/codeowners` | `brew upgrade mszostok/tap/codeowners` | #### Install script ```bash # binary installed into ./bin/ -curl -sfL https://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh | sh -s v0.7.4 +curl -sfL https://raw.githubusercontent.com/mszostok/codeowners/main/install.sh | sh -s v0.7.4 -# binary installed into $(go env GOPATH)/bin/codeowners-validator -curl -sfL https://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh | sh -s -- -b $(go env GOPATH)/bin v0.7.4 +# binary installed into $(go env GOPATH)/bin/codeowners +curl -sfL https://raw.githubusercontent.com/mszostok/codeowners/main/install.sh | sh -s -- -b $(go env GOPATH)/bin v0.7.4 # In alpine linux (as it does not come with curl by default) -wget -O - -q https://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh | sh -s v0.7.4 +wget -O - -q https://raw.githubusercontent.com/mszostok/codeowners/main/install.sh | sh -s v0.7.4 # Print version. Add `--oshort` to print just the version number. -codeowners-validator version +codeowners version ``` -You can also download [latest version](https://github.com/mszostok/codeowners-validator/releases/latest) from release page manually. +You can also download [latest version](https://github.com/mszostok/codeowners/releases/latest) from release page manually. #### From Sources - -You can install `codeowners-validator` with `go install github.com/mszostok/codeowners-validator@v0.7.4`. + +You can install `codeowners` with `go install github.com/mszostok/codeowners@v0.7.4`. > NOTE: please use Go 1.16 or greater. -This will put `codeowners-validator` in `$(go env GOPATH)/bin`. +This will put `codeowners` in `$(go env GOPATH)/bin`. ## Checks @@ -165,6 +165,6 @@ Contributions are greatly appreciated! The project follows the typical GitHub pu ## Roadmap -The [codeowners-validator roadmap uses GitHub milestones](https://github.com/mszostok/codeowners-validator/milestone/1) to track the progress of the project. +The [codeowners roadmap uses GitHub milestones](https://github.com/mszostok/codeowners/milestone/1) to track the progress of the project. They are sorted with priority. First are most important. diff --git a/action.yml b/action.yml index 2dae83de..5ac83993 100644 --- a/action.yml +++ b/action.yml @@ -1,4 +1,4 @@ -name: "GitHub CODEOWNERS Validator" +name: "GitHub CODEOWNERS" description: "GitHub action to ensure the correctness of your CODEOWNERS file." author: "szostok.mateusz@gmail.com" @@ -8,7 +8,7 @@ inputs: required: false github_app_id: - description: "Github App ID for authentication. This replaces the GITHUB_ACCESS_TOKEN. Instruction for creating a Github App can be found here: https://github.com/mszostok/codeowners-validator/blob/main/docs/gh-token.md" + description: "Github App ID for authentication. This replaces the GITHUB_ACCESS_TOKEN. Instruction for creating a Github App can be found here: https://github.com/mszostok/codeowners/blob/main/docs/gh-token.md" required: false github_app_installation_id: @@ -80,7 +80,7 @@ inputs: runs: using: 'docker' - image: 'docker://ghcr.io/mszostok/codeowners-validator:v0.7.4' + image: 'docker://ghcr.io/mszostok/codeowners:v0.7.4' env: ENVS_PREFIX: "INPUT" diff --git a/cmd/codeowners/codeowners.go b/cmd/codeowners/codeowners.go new file mode 100644 index 00000000..f7a2ff30 --- /dev/null +++ b/cmd/codeowners/codeowners.go @@ -0,0 +1,145 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/config" + "go.szostok.io/codeowners/internal/load" + "go.szostok.io/codeowners/internal/runner" + "go.szostok.io/codeowners/pkg/codeowners" + "go.szostok.io/version/extension" +) + +//var severity api.SeverityType + +// NewRoot returns a root cobra.Command for the whole Agent utility. +func RootCmd() *cobra.Command { + cfg := &config.Config{} + + rootCmd := &cobra.Command{ + Use: "codeowners", + Short: "Ensures the correctness of your CODEOWNERS file.", + SilenceUsage: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return InitializeConfig(cmd, cfg, args) + }, + } + + rootCmd.AddCommand( + extension.NewVersionCobraCmd(), + validateCmd(cfg), + ) + + return rootCmd +} + +func validateCmd(cfg *config.Config) *cobra.Command { + + var validateCmd = &cobra.Command{ + Use: "validate", + Short: "Validate a CODEOWNERS file", + + Run: func(cmd *cobra.Command, args []string) { + log := logrus.New() + + // init checks + checks, err := load.Checks(cmd.Context(), cfg) + exitOnError(err) + + // init codeowners entries + codeownersEntries, err := codeowners.NewFromPath(cfg.RepositoryPath) + exitOnError(err) + + // run check runner + absRepoPath, err := filepath.Abs(cfg.RepositoryPath) + exitOnError(err) + + checkRunner := runner.NewCheckRunner(log, codeownersEntries, absRepoPath, cfg.CheckFailureLevel, checks...) + checkRunner.Run(cmd.Context()) + + if cmd.Context().Err() != nil { + log.Error("Application was interrupted by operating system") + os.Exit(2) + } + if checkRunner.ShouldExitWithCheckFailure() { + os.Exit(3) + } + }, + } + addValidateFlags(validateCmd) + return validateCmd +} + +func addValidateFlags(cmd *cobra.Command) { + cmd.Flags().StringSlice("checks", nil, "List of checks to be executed") + //cmd.Flags().Var(&severity, "check-failure-level", "Defines the level on which the application should treat check issues as failures") + cmd.Flags().String("experimental-checks", "", "The comma-separated list of experimental checks that should be executed") + cmd.Flags().String("github-access-token", "", "GitHub access token") + cmd.Flags().String("github-base-url", "https://api.github.com/", "GitHub base URL for API requests") + cmd.Flags().String("github-upload-url", "https://uploads.github.com/", "GitHub upload URL for uploading files") + cmd.Flags().String("github-app-id", "", "Github App ID for authentication") + cmd.Flags().String("github-app-installation-id", "", "Github App Installation ID") + cmd.Flags().String("github-app-private-key", "", "Github App private key in PEM format") + cmd.Flags().StringSlice("not-owned-checker-skip-patterns", nil, "The comma-separated list of patterns that should be ignored by not-owned-checker") + cmd.Flags().StringSlice("not-owned-checker-subdirectories", nil, "The comma-separated list of subdirectories to check in not-owned-checker") + cmd.Flags().Bool("not-owned-checker-trust-workspace", false, "Specifies whether the repository path should be marked as safe") + cmd.Flags().String("repository-path", "", "Path to your repository on your local machine") + cmd.Flags().String("owner-checker-repository", "", "The owner and repository name separated by slash") + cmd.Flags().StringSlice("owner-checker-ignored-owners", []string{"@ghost"}, "The comma-separated list of owners that should not be validated") + cmd.Flags().Bool("owner-checker-allow-unowned-patterns", true, "Specifies whether CODEOWNERS may have unowned files") + cmd.Flags().Bool("owner-checker-owners-must-be-teams", false, "Specifies whether only teams are allowed as owners of files") +} + +func exitOnError(err error) { + if err != nil { + logrus.Fatal(err) + } +} + +func InitializeConfig(cmd *cobra.Command, cfg *config.Config, args []string) error { + v := viper.New() + + // Look for config file, ignore if missing + v.SetConfigName(config.DefaultConfigFilename) + v.AddConfigPath(".") + if err := v.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return err + } + } + // Look for environment variables + v.SetEnvPrefix(config.EnvPrefix) + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + v.AutomaticEnv() + + // Bind flags to the configuration struct + bindFlags(cmd, v) + + // Unmarshal the configuration into the struct + if err := v.Unmarshal(cfg); err != nil { + return err + } + cfg.CheckFailureLevel = api.Warning + + return nil +} + +// Bind each cobra flag to its associated viper configuration environment variable +func bindFlags(cmd *cobra.Command, v *viper.Viper) { + cmd.Flags().VisitAll(func(f *pflag.Flag) { + configName := f.Name + if !f.Changed && v.IsSet(configName) { + val := v.Get(configName) + cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) + } + v.BindPFlag(configName, f) + }) +} diff --git a/docs/assets/usage.svg b/docs/assets/usage.svg index 687ed617..8ae9393b 100644 --- a/docs/assets/usage.svg +++ b/docs/assets/usage.svg @@ -1 +1 @@ -OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\#Downloadcodeowners-validatorbinary/binv0.2.0curl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/local/binv0.2.0mszostok/codeowners-validatorinfocheckingGitHubfortag'v0.2.0'mszostok/codeowners-validatorinfofoundversion:0.2.0forv0.2.0/Darwin/x86_64mszostok/codeowners-validatorinfoinstalled/usr/local/bin/codeowners-validator#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERSgitclonegit@github.com:gh-codeowners/codeowners-samples.gitCloninginto'codeowners-samples'...remote:Enumeratingobjects:8,done.remote:Countingobjects:100%(8/8),done.remote:Compressingobjects:100%(3/3),done.remote:Total8(delta0),reused8(delta0),pack-reused0Receivingobjects:100%(8/8),done.#Executecodeowners-validatoragainstclonedrepository#Executecodeowners-validatoragainstclonedrepositoryEXPERIMENTAL_CHECKS="notowned"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\#Downloadcodeowners-validatorbinarycodeowners-validatorenvREPOSITORY_PATH="./codeowners-samples"\EXPERIMENTAL_CHECKS="notowned"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\codeowners-validator==>ExecutingFileExistChecker(1.240501ms)[err]line13:"/scripts/"doesnotmatchanyfilesinrepository==>ExecutingDuplicatedPatternChecker(1.820841ms)[err]Pattern"/some/awesome/dir"isdefined2timesinlines:*10:withowners:[@mszostok@owner-a]*11:withowners:[@octocat]==>Executing[Experimental]NotOwnedFileChecker(38.920431ms)CheckOK==>ExecutingValidOwnerChecker(1.238375347s)[err]line8:User"@mszostok"isnotamemberoftheorganization[err]line10:User"@owner-a"doesnothavegithubaccount[err]line11:User"@octocat"isnotamemberoftheorganization4check(s)executed,3failure(s)#Downloadcodeowners-validatorbinary#Downloadcodeowners-validatorbinary#Downloadcodeowners-validatorbinarycurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/local/binv0.2.0/binv0.2.0/binv0.2.0/binv0.2.0#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERSgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitReceivingobjects:12%(1/8)Receivingobjects:100%(8/8)#Executecodeowners-validatoragainstclonedrepository#Executecodeowners-validatoragainstclonedrepository#Executecodeowners-validatoragainstclonedrepository#Executecodeowners-validatoragainstclonedrepository#Executecodeowners-validatoragainstclonedrepositoryenvREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\EXPERIMENTAL_CHECKS="notowned"\EXPERIMENTAL_CHECKS="notowned"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\codeowners-validator[err] +OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\#Downloadcodeownersbinary/binv0.2.0curl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/local/binv0.2.0mszostok/codeownersinfocheckingGitHubfortag'v0.2.0'mszostok/codeownersinfofoundversion:0.2.0forv0.2.0/Darwin/x86_64mszostok/codeownersinfoinstalled/usr/local/bin/codeowners#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERSgitclonegit@github.com:gh-codeowners/codeowners-samples.gitCloninginto'codeowners-samples'...remote:Enumeratingobjects:8,done.remote:Countingobjects:100%(8/8),done.remote:Compressingobjects:100%(3/3),done.remote:Total8(delta0),reused8(delta0),pack-reused0Receivingobjects:100%(8/8),done.#Executecodeownersagainstclonedrepository#ExecutecodeownersagainstclonedrepositoryEXPERIMENTAL_CHECKS="notowned"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\#DownloadcodeownersbinarycodeownersenvREPOSITORY_PATH="./codeowners-samples"\EXPERIMENTAL_CHECKS="notowned"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\codeowners==>ExecutingFileExistChecker(1.240501ms)[err]line13:"/scripts/"doesnotmatchanyfilesinrepository==>ExecutingDuplicatedPatternChecker(1.820841ms)[err]Pattern"/some/awesome/dir"isdefined2timesinlines:*10:withowners:[@mszostok@owner-a]*11:withowners:[@octocat]==>Executing[Experimental]NotOwnedFileChecker(38.920431ms)CheckOK==>ExecutingValidOwnerChecker(1.238375347s)[err]line8:User"@mszostok"isnotamemberoftheorganization[err]line10:User"@owner-a"doesnothavegithubaccount[err]line11:User"@octocat"isnotamemberoftheorganization4check(s)executed,3failure(s)#Downloadcodeownersbinary#Downloadcodeowners-validatorbinary#Downloadcodeownersbinarycurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners-validator/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/localcurl-sfLhttps://raw.githubusercontent.com/mszostok/codeowners/main/install.sh|sh-s---b/usr/local/binv0.2.0/binv0.2.0/binv0.2.0/binv0.2.0#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERS#CloneexamplerepositorywithsampleCODEOWNERSgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitgitclonegit@github.com:gh-codeowners/codeowners-samples.gitReceivingobjects:12%(1/8)Receivingobjects:100%(8/8)#Executecodeownersagainstclonedrepository#Executecodeowners-validatoragainstclonedrepository#Executecodeownersagainstclonedrepository#Executecodeownersagainstclonedrepository#ExecutecodeownersagainstclonedrepositoryenvREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\envREPOSITORY_PATH="./codeowners-samples"\EXPERIMENTAL_CHECKS="notowned"\EXPERIMENTAL_CHECKS="notowned"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\GITHUB_ACCESS_TOKEN="${GH_TOKEN}"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\OWNER_CHECKER_ORGANIZATION_NAME="gh-codeowners"\codeowners-validator[err] diff --git a/docs/development.md b/docs/development.md index 6a8e968e..832a43f2 100644 --- a/docs/development.md +++ b/docs/development.md @@ -74,7 +74,7 @@ make fix-lint-issues This project supports the integration tests that are defined in the [tests](../tests) package. The tests are executed against [`gh-codeowners/codeowners-samples`](https://github.com/gh-codeowners/codeowners-samples). > **CAUTION:** Currently, running the integration tests both on external PRs and locally by external contributors is not supported, as the teams used for testing are visible only to the organization members. -> At the moment, the `codeowners-validator` repository owner is responsible for running these tests. +> At the moment, the `codeowners` repository owner is responsible for running these tests. ## Build a binary @@ -83,6 +83,6 @@ To generate a binary for this project, execute: make build ``` -This command generates a binary named `codeowners-validator` in the root directory. +This command generates a binary named `codeowners` in the root directory. [↑ Back to top](#table-of-contents) diff --git a/docs/gh-action.md b/docs/gh-action.md index a57d8c26..ad2b71ba 100644 --- a/docs/gh-action.md +++ b/docs/gh-action.md @@ -9,10 +9,10 @@

## -The [Codeowners Validator](https://github.com/mszostok/codeowners-validator) is available as a GitHub Action. +The [Codeowners Validator](https://github.com/mszostok/codeowners) is available as a GitHub Action.

- demo + demo

@@ -35,7 +35,7 @@ jobs: # Checks-out your repository, which is validated in the next step - uses: actions/checkout@v2 - name: GitHub CODEOWNERS Validator - uses: mszostok/codeowners-validator@v0.7.4 + uses: mszostok/codeowners@v0.7.4 # input parameters with: # ==== GitHub Auth ==== diff --git a/docs/gh-auth.md b/docs/gh-auth.md index f41f1b3b..49eb31ae 100644 --- a/docs/gh-auth.md +++ b/docs/gh-auth.md @@ -57,7 +57,7 @@ Here are the steps to create a GitHub App and use it for this tool: ```yaml - name: GitHub CODEOWNERS Validator - uses: mszostok/codeowners-validator@v0.7.4 + uses: mszostok/codeowners@v0.7.4 with: # ... github_app_id: ${{ secrets.APP_ID }} diff --git a/docs/investigation/file_exists_checker/glob.md b/docs/investigation/file_exists_checker/glob.md index 3fd3ed7e..4e8279e1 100644 --- a/docs/investigation/file_exists_checker/glob.md +++ b/docs/investigation/file_exists_checker/glob.md @@ -7,7 +7,7 @@ This document describes investigation about [`file exists`](../../../internal/ch A [CODEOWNERS](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax) file uses a pattern that follows the same rules used in [gitignore](https://git-scm.com/docs/gitignore#_pattern_format) files. The gitignore files support two consecutive asterisks ("**") in patterns that match against the full path name. Unfortunately the core Go library `filepath.Glob` does not support [`**`](https://github.com/golang/go/issues/11862) at all. -This caused that for some patterns the [`file exists`](../../../internal/check/file_exists.go) checker didn't work properly, see [issue#22](https://github.com/mszostok/codeowners-validator/issues/22). +This caused that for some patterns the [`file exists`](../../../internal/check/file_exists.go) checker didn't work properly, see [issue#22](https://github.com/mszostok/codeowners/issues/22). Additionally, we need to support a single asterisk at the beginning of the pattern. For example, `*.js` should check for all JS files in the whole git repository. To achieve that we need to detect that and change from `*.js` to `**/*.js`. diff --git a/docs/investigation/file_exists_checker/go.mod b/docs/investigation/file_exists_checker/go.mod index e95d277d..46bab2c8 100644 --- a/docs/investigation/file_exists_checker/go.mod +++ b/docs/investigation/file_exists_checker/go.mod @@ -1,4 +1,4 @@ -module github.com/mszostok/codeowners-validator/docs/investigation/file_exists_checker +module github.com/mszostok/codeowners/docs/investigation/file_exists_checker go 1.15 diff --git a/docs/release.md b/docs/release.md index c8725e1d..923f9342 100644 --- a/docs/release.md +++ b/docs/release.md @@ -2,7 +2,7 @@ # Release process -The release of the codeowners-validator tool is performed by the [GoReleaser](https://github.com/goreleaser/goreleaser) which builds Go binaries for several platforms and then creates a GitHub release. +The release of the codeowners tool is performed by the [GoReleaser](https://github.com/goreleaser/goreleaser) which builds Go binaries for several platforms and then creates a GitHub release. **Process** diff --git a/go.mod b/go.mod index 3550b18f..d7c35fe7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module go.szostok.io/codeowners-validator +module go.szostok.io/codeowners go 1.18 @@ -13,22 +13,32 @@ require ( github.com/sebdah/goldie/v2 v2.5.3 github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.9.0 - github.com/spf13/afero v1.9.2 - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.8.0 + github.com/spf13/afero v1.9.3 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.2 github.com/vrischmann/envconfig v1.3.0 go.szostok.io/version v1.1.0 - golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect - golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99 - golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/oauth2 v0.7.0 + golang.org/x/sys v0.7.0 // indirect gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 gopkg.in/src-d/go-git.v4 v4.13.1 - gotest.tools v2.2.0+incompatible ) require github.com/spf13/cobra v1.5.0 +require ( + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) + require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect @@ -39,8 +49,7 @@ require ( github.com/emirpasic/gods v1.12.0 // indirect github.com/goccy/go-yaml v1.9.5 // indirect github.com/golang-jwt/jwt/v4 v4.4.1 // indirect - github.com/golang/protobuf v1.4.3 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-github/v45 v45.2.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect @@ -52,7 +61,6 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect - github.com/kr/pretty v0.2.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect @@ -64,13 +72,14 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect - github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/viper v1.15.0 github.com/src-d/gcfg v1.4.0 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect - golang.org/x/text v0.3.4 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 65d273ca..64fbd923 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,9 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -121,8 +124,10 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -134,9 +139,10 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= @@ -171,6 +177,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -192,8 +200,7 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -202,6 +209,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -219,12 +228,16 @@ github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMK github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 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= @@ -236,6 +249,7 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= @@ -247,19 +261,25 @@ github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXY github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= 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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -267,8 +287,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk= github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= @@ -295,8 +319,9 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -360,8 +385,9 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -370,8 +396,9 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99 h1:5vD4XjIc0X5+kHZjx4UecYdjA6mJo+XXNoaW0EjU5Os= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -421,17 +448,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= -golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -577,14 +606,19 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 h1:WJH1qsOB4/zb/li+zLMn0vaAUJ5FqPv6HYLI3aQVg1k= gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544/go.mod h1:UhTeH/yXCK/KY7TX24mqPkaQ7gZeqmWd/8SSS8B3aHw= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= @@ -604,8 +638,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/install.sh b/install.sh index 1182a542..0c94bfa5 100755 --- a/install.sh +++ b/install.sh @@ -6,13 +6,13 @@ set -e usage() { this=$1 cat < 0 { msg := fmt.Sprintf("Pattern %q shadows the following patterns:\n%s\nEntries should go from least-specific to most-specific.", entry.Pattern, c.listFormatFunc(shadowed)) - bldr.ReportIssue(msg, WithEntry(entry)) + bldr.ReportIssue(msg, api.WithEntry(entry)) } previousEntries = append(previousEntries, entry) } @@ -45,7 +46,7 @@ func (c *AvoidShadowing) Check(ctx context.Context, in Input) (output Output, er return bldr.Output(), nil } -// listFormatFunc is a basic formatter that outputs a bullet point list of the pattern. +// listFormatFunc is a basic formatter that api.Outputs a bullet point list of the pattern. func (c *AvoidShadowing) listFormatFunc(es []codeowners.Entry) string { points := make([]string, len(es)) for i, err := range es { diff --git a/internal/check/avoid_shadowing_test.go b/internal/check/avoid_shadowing_test.go index 31e05f0b..49a583ff 100644 --- a/internal/check/avoid_shadowing_test.go +++ b/internal/check/avoid_shadowing_test.go @@ -4,8 +4,9 @@ import ( "context" "testing" - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/ptr" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" + "go.szostok.io/codeowners/internal/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,7 +15,7 @@ import ( func TestAvoidShadowing(t *testing.T) { tests := map[string]struct { codeownersInput string - expectedIssues []check.Issue + expectedIssues []api.Issue }{ "Should report info about shadowed entries": { codeownersInput: ` @@ -32,9 +33,9 @@ func TestAvoidShadowing(t *testing.T) { /b*/other @o1 /script/* @o2 `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ { - Severity: check.Error, + Severity: api.Error, LineNo: ptr.Uint64Ptr(6), Message: `Pattern "*" shadows the following patterns: * 2: "/build/logs/" @@ -42,14 +43,14 @@ func TestAvoidShadowing(t *testing.T) { Entries should go from least-specific to most-specific.`, }, { - Severity: check.Error, + Severity: api.Error, LineNo: ptr.Uint64Ptr(7), Message: `Pattern "/s*/" shadows the following patterns: * 3: "/script" Entries should go from least-specific to most-specific.`, }, { - Severity: check.Error, + Severity: api.Error, LineNo: ptr.Uint64Ptr(8), Message: `Pattern "/s*" shadows the following patterns: * 3: "/script" @@ -57,14 +58,14 @@ Entries should go from least-specific to most-specific.`, Entries should go from least-specific to most-specific.`, }, { - Severity: check.Error, + Severity: api.Error, LineNo: ptr.Uint64Ptr(9), Message: `Pattern "/b*" shadows the following patterns: * 2: "/build/logs/" Entries should go from least-specific to most-specific.`, }, { - Severity: check.Error, + Severity: api.Error, LineNo: ptr.Uint64Ptr(10), Message: `Pattern "/b*/logs" shadows the following patterns: * 2: "/build/logs/" diff --git a/internal/check/duplicated_pattern.go b/internal/check/duplicated_pattern.go index 183a2568..4e5fe461 100644 --- a/internal/check/duplicated_pattern.go +++ b/internal/check/duplicated_pattern.go @@ -5,8 +5,9 @@ import ( "fmt" "strings" - "go.szostok.io/codeowners-validator/internal/ctxutil" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/ctxutil" + "go.szostok.io/codeowners/pkg/codeowners" ) // DuplicatedPattern validates if CODEOWNERS file does not contain @@ -19,8 +20,8 @@ func NewDuplicatedPattern() *DuplicatedPattern { } // Check searches for doubles paths(patterns) in CODEOWNERS file. -func (d *DuplicatedPattern) Check(ctx context.Context, in Input) (Output, error) { - var bldr OutputBuilder +func (d *DuplicatedPattern) Check(ctx context.Context, in api.Input) (api.Output, error) { + var bldr api.OutputBuilder // TODO(mszostok): decide if the `CodeownersEntries` entry by default should be // indexed by pattern (`map[string][]codeowners.Entry{}`) @@ -28,7 +29,7 @@ func (d *DuplicatedPattern) Check(ctx context.Context, in Input) (Output, error) patterns := map[string][]codeowners.Entry{} for _, entry := range in.CodeownersEntries { if ctxutil.ShouldExit(ctx) { - return Output{}, ctx.Err() + return api.Output{}, ctx.Err() } patterns[entry.Pattern] = append(patterns[entry.Pattern], entry) diff --git a/internal/check/duplicated_pattern_test.go b/internal/check/duplicated_pattern_test.go index 975c293a..f92b787f 100644 --- a/internal/check/duplicated_pattern_test.go +++ b/internal/check/duplicated_pattern_test.go @@ -4,7 +4,8 @@ import ( "context" "testing" - "go.szostok.io/codeowners-validator/internal/check" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,7 +14,7 @@ import ( func TestDuplicatedPattern(t *testing.T) { tests := map[string]struct { codeownersInput string - expectedIssues []check.Issue + expectedIssues []api.Issue }{ "Should report info about duplicated entries": { codeownersInput: ` @@ -25,16 +26,16 @@ func TestDuplicatedPattern(t *testing.T) { /script @mszostok /script m.t@g.com `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ { - Severity: check.Error, + Severity: api.Error, LineNo: nil, Message: `Pattern "/build/logs/" is defined 2 times in lines: * 4: with owners: [@doctocat] * 5: with owners: [@doctocat]`, }, { - Severity: check.Error, + Severity: api.Error, LineNo: nil, Message: `Pattern "/script" is defined 2 times in lines: * 7: with owners: [@mszostok] diff --git a/internal/check/file_exists.go b/internal/check/file_exists.go index 16944d2d..afbdcf38 100644 --- a/internal/check/file_exists.go +++ b/internal/check/file_exists.go @@ -6,7 +6,8 @@ import ( "os" "path/filepath" - "go.szostok.io/codeowners-validator/internal/ctxutil" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/ctxutil" "github.com/mattn/go-zglob" "github.com/pkg/errors" @@ -18,12 +19,12 @@ func NewFileExist() *FileExist { return &FileExist{} } -func (f *FileExist) Check(ctx context.Context, in Input) (Output, error) { - var bldr OutputBuilder +func (f *FileExist) Check(ctx context.Context, in api.Input) (api.Output, error) { + var bldr api.OutputBuilder for _, entry := range in.CodeownersEntries { if ctxutil.ShouldExit(ctx) { - return Output{}, ctx.Err() + return api.Output{}, ctx.Err() } fullPath := filepath.Join(in.RepoDir, f.fnmatchPattern(entry.Pattern)) @@ -32,15 +33,15 @@ func (f *FileExist) Check(ctx context.Context, in Input) (Output, error) { case err == nil: case errors.Is(err, os.ErrNotExist): msg := fmt.Sprintf("%q does not match any files in repository", entry.Pattern) - bldr.ReportIssue(msg, WithEntry(entry)) + bldr.ReportIssue(msg, api.WithEntry(entry)) continue default: - return Output{}, errors.Wrapf(err, "while checking if there is any file in %s matching pattern %s", in.RepoDir, entry.Pattern) + return api.Output{}, errors.Wrapf(err, "while checking if there is any file in %s matching pattern %s", in.RepoDir, entry.Pattern) } if len(matches) == 0 { msg := fmt.Sprintf("%q does not match any files in repository", entry.Pattern) - bldr.ReportIssue(msg, WithEntry(entry)) + bldr.ReportIssue(msg, api.WithEntry(entry)) } } diff --git a/internal/check/file_exists_test.go b/internal/check/file_exists_test.go index c137726a..88b2bd73 100644 --- a/internal/check/file_exists_test.go +++ b/internal/check/file_exists_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/ptr" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" + "go.szostok.io/codeowners/internal/ptr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,7 +23,7 @@ import ( func TestFileExists(t *testing.T) { tests := map[string]struct { codeownersInput string - expectedIssues []check.Issue + expectedIssues []api.Issue paths []string }{ "Should found JS file": { @@ -85,20 +86,20 @@ func TestFileExists(t *testing.T) { // https://github.community/t/codeowners-file-with-a-not-file-type-condition/1423 "Should not match with negation pattern": { codeownersInput: ` - !/codeowners-validator @pico + !/codeowners @pico `, paths: []string{ "/somewhere/over/the/rainbow/here/it/is.js", }, - expectedIssues: []check.Issue{ - newErrIssue(`"!/codeowners-validator" does not match any files in repository`), + expectedIssues: []api.Issue{ + newErrIssue(`"!/codeowners" does not match any files in repository`), }, }, "Should not found JS file": { codeownersInput: ` *.js @pico `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"*.js" does not match any files in repository`), }, }, @@ -106,7 +107,7 @@ func TestFileExists(t *testing.T) { codeownersInput: ` **/foo @pico `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"**/foo" does not match any files in repository`), }, }, @@ -114,7 +115,7 @@ func TestFileExists(t *testing.T) { codeownersInput: ` **/foo.js @pico `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"**/foo.js" does not match any files in repository`), }, }, @@ -122,7 +123,7 @@ func TestFileExists(t *testing.T) { codeownersInput: ` **/foo/bar @bello `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"**/foo/bar" does not match any files in repository`), }, }, @@ -130,7 +131,7 @@ func TestFileExists(t *testing.T) { codeownersInput: ` **/foo/bar.js @bello `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"**/foo/bar.js" does not match any files in repository`), }, }, @@ -138,7 +139,7 @@ func TestFileExists(t *testing.T) { codeownersInput: ` abc/** @bello `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"abc/**" does not match any files in repository`), }, }, @@ -146,7 +147,7 @@ func TestFileExists(t *testing.T) { codeownersInput: ` a/**/b @bello `, - expectedIssues: []check.Issue{ + expectedIssues: []api.Issue{ newErrIssue(`"a/**/b" does not match any files in repository`), }, }, @@ -205,9 +206,9 @@ func TestFileExistCheckFileSystemFailure(t *testing.T) { assert.Empty(t, out) } -func newErrIssue(msg string) check.Issue { - return check.Issue{ - Severity: check.Error, +func newErrIssue(msg string) api.Issue { + return api.Issue{ + Severity: api.Error, LineNo: ptr.Uint64Ptr(2), Message: msg, } diff --git a/internal/check/helpers_test.go b/internal/check/helpers_test.go index 87dd0e99..c217664a 100644 --- a/internal/check/helpers_test.go +++ b/internal/check/helpers_test.go @@ -6,9 +6,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.szostok.io/codeowners-validator/internal/check" + "go.szostok.io/codeowners/internal/api" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/pkg/codeowners" ) var FixtureValidCODEOWNERS = ` @@ -25,15 +25,15 @@ var FixtureValidCODEOWNERS = ` /script m.t@g.com ` -func LoadInput(in string) check.Input { +func LoadInput(in string) api.Input { r := strings.NewReader(in) - return check.Input{ + return api.Input{ CodeownersEntries: codeowners.ParseCodeowners(r), } } -func assertIssue(t *testing.T, expIssue *check.Issue, gotIssues []check.Issue) { +func assertIssue(t *testing.T, expIssue *api.Issue, gotIssues []api.Issue) { t.Helper() if expIssue != nil { diff --git a/internal/check/not_owned_file.go b/internal/check/not_owned_file.go index b8be0315..59d091ca 100644 --- a/internal/check/not_owned_file.go +++ b/internal/check/not_owned_file.go @@ -7,8 +7,9 @@ import ( "path" "strings" - "go.szostok.io/codeowners-validator/internal/ctxutil" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/ctxutil" + "go.szostok.io/codeowners/pkg/codeowners" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" @@ -43,12 +44,12 @@ func NewNotOwnedFile(cfg NotOwnedFileConfig) *NotOwnedFile { } } -func (c *NotOwnedFile) Check(ctx context.Context, in Input) (output Output, err error) { +func (c *NotOwnedFile) Check(ctx context.Context, in api.Input) (output api.Output, err error) { if ctxutil.ShouldExit(ctx) { - return Output{}, ctx.Err() + return api.Output{}, ctx.Err() } - var bldr OutputBuilder + var bldr api.OutputBuilder if len(in.CodeownersEntries) == 0 { bldr.ReportIssue("The CODEOWNERS file is empty. The files in the repository don't have any owner.") @@ -58,12 +59,12 @@ func (c *NotOwnedFile) Check(ctx context.Context, in Input) (output Output, err patterns := c.patternsToBeIgnored(in.CodeownersEntries) if err := c.trustWorkspaceIfNeeded(in.RepoDir); err != nil { - return Output{}, err + return api.Output{}, err } statusOut, err := c.GitCheckStatus(in.RepoDir) if err != nil { - return Output{}, err + return api.Output{}, err } if len(statusOut) != 0 { bldr.ReportIssue("git state is dirty: commit all changes before executing this check") @@ -73,24 +74,24 @@ func (c *NotOwnedFile) Check(ctx context.Context, in Input) (output Output, err defer func() { errReset := c.GitResetCurrentBranch(in.RepoDir) if err != nil { - output = Output{} + output = api.Output{} err = multierror.Append(err, errReset).ErrorOrNil() } }() err = c.AppendToGitignoreFile(in.RepoDir, patterns) if err != nil { - return Output{}, err + return api.Output{}, err } err = c.GitRemoveIgnoredFiles(in.RepoDir) if err != nil { - return Output{}, err + return api.Output{}, err } out, err := c.GitListFiles(in.RepoDir) if err != nil { - return Output{}, err + return api.Output{}, err } lsOut := strings.TrimSpace(out) diff --git a/internal/check/package_test.go b/internal/check/package_test.go index 98077b72..00b5537a 100644 --- a/internal/check/package_test.go +++ b/internal/check/package_test.go @@ -5,24 +5,26 @@ import ( "errors" "testing" - "go.szostok.io/codeowners-validator/internal/check" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" + "go.szostok.io/codeowners/internal/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRespectingCanceledContext(t *testing.T) { - must := func(checker check.Checker, err error) check.Checker { + must := func(checker api.Checker, err error) api.Checker { require.NoError(t, err) return checker } - checkers := []check.Checker{ + checkers := []api.Checker{ check.NewDuplicatedPattern(), check.NewFileExist(), check.NewValidSyntax(), check.NewNotOwnedFile(check.NotOwnedFileConfig{}), - must(check.NewValidOwner(check.ValidOwnerConfig{Repository: "org/repo"}, nil, true)), + must(check.NewValidOwner(&config.Config{OwnerCheckerRepository: "org/repo"}, nil, true)), } for _, checker := range checkers { diff --git a/internal/check/valid_owner.go b/internal/check/valid_owner.go index cfc15122..5b7544f5 100644 --- a/internal/check/valid_owner.go +++ b/internal/check/valid_owner.go @@ -7,7 +7,9 @@ import ( "net/mail" "strings" - "go.szostok.io/codeowners-validator/internal/ctxutil" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/config" + "go.szostok.io/codeowners/internal/ctxutil" "github.com/google/go-github/v41/github" "github.com/pkg/errors" @@ -48,20 +50,21 @@ type ValidOwner struct { orgName string orgTeams []*github.Team orgRepoName string + outsideCollaborators *map[string]struct{} ignOwners map[string]struct{} allowUnownedPatterns bool ownersMustBeTeams bool } // NewValidOwner returns new instance of the ValidOwner -func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client, checkScopes bool) (*ValidOwner, error) { - split := strings.Split(cfg.Repository, "/") +func NewValidOwner(cfg *config.Config, ghClient *github.Client, checkScopes bool) (*ValidOwner, error) { + split := strings.Split(cfg.OwnerCheckerRepository, "/") if len(split) != 2 { - return nil, errors.Errorf("Wrong repository name. Expected pattern 'owner/repository', got '%s'", cfg.Repository) + return nil, errors.Errorf("Wrong repository name. Expected pattern 'owner/repository', got '%s'", cfg.OwnerCheckerRepository) } ignOwners := map[string]struct{}{} - for _, n := range cfg.IgnoredOwners { + for _, n := range cfg.OwnerCheckerIgnoredOwners { ignOwners[n] = struct{}{} } @@ -71,8 +74,8 @@ func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client, checkScopes bo orgName: split[0], orgRepoName: split[1], ignOwners: ignOwners, - allowUnownedPatterns: cfg.AllowUnownedPatterns, - ownersMustBeTeams: cfg.OwnersMustBeTeams, + allowUnownedPatterns: cfg.OwnerCheckerAllowUnownedPatterns, + ownersMustBeTeams: cfg.OwnerCheckerOwnersMustBeTeams, }, nil } @@ -88,20 +91,20 @@ func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client, checkScopes bo // - if GitHub user then check if have GitHub account // - if GitHub user then check if he/she is in organization // - if org team then check if exists in organization -func (v *ValidOwner) Check(ctx context.Context, in Input) (Output, error) { - var bldr OutputBuilder +func (v *ValidOwner) Check(ctx context.Context, in api.Input) (api.Output, error) { + var bldr api.OutputBuilder checkedOwners := map[string]struct{}{} for _, entry := range in.CodeownersEntries { if len(entry.Owners) == 0 && !v.allowUnownedPatterns { - bldr.ReportIssue("Missing owner, at least one owner is required", WithEntry(entry), WithSeverity(Warning)) + bldr.ReportIssue("Missing owner, at least one owner is required", api.WithEntry(entry), api.WithSeverity(api.Warning)) continue } for _, ownerName := range entry.Owners { if ctxutil.ShouldExit(ctx) { - return Output{}, ctx.Err() + return api.Output{}, ctx.Err() } if v.isIgnoredOwner(ownerName) { @@ -114,7 +117,7 @@ func (v *ValidOwner) Check(ctx context.Context, in Input) (Output, error) { validFn := v.selectValidateFn(ownerName) if err := validFn(ctx, ownerName); err != nil { - bldr.ReportIssue(err.msg, WithEntry(entry)) + bldr.ReportIssue(err.msg, api.WithEntry(entry)) if err.permanent { // Doesn't make sense to process further return bldr.Output(), nil } @@ -236,7 +239,7 @@ func (v *ValidOwner) validateTeam(ctx context.Context, name string) *validateErr // repo contains the permissions for the team slug given // TODO(mszostok): Switch to GraphQL API, see: - // https://github.com/mszostok/codeowners-validator/pull/62#discussion_r561273525 + // https://github.com/mszostok/codeowners/pull/62#discussion_r561273525 repo, _, err := v.ghClient.Teams.IsTeamRepoBySlug(ctx, v.orgName, team, org, v.orgRepoName) if err != nil { // TODO(mszostok): implement retry? switch err := err.(type) { @@ -297,6 +300,12 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid } } + if v.outsideCollaborators == nil { // TODO(mszostok): lazy init, make it more robust. + if err := v.initOutsideCollaboratorsList(ctx); err != nil { + return newValidateError("Cannot initialize outside collaborators list: %v", err).AsPermanent() + } + } + userName := strings.TrimPrefix(name, "@") _, _, err := v.ghClient.Users.Get(ctx, userName) if err != nil { // TODO(mszostok): implement retry? @@ -314,15 +323,18 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid } _, isMember := (*v.orgMembers)[userName] - if !isMember { - return newValidateError("User %q is not a member of the organization", name) + _, isOutsideCollaborator := (*v.outsideCollaborators)[userName] + if !(isMember || isOutsideCollaborator) { + return newValidateError("User %q is not an owner of the repository", name) } return nil } // There is a method to check if user is a org member -// client.Organizations.IsMember(context.Background(), "org-name", "user-name") +// +// client.Organizations.IsMember(context.Background(), "org-name", "user-name") +// // But latency is too huge for checking each single user independent // better and faster is to ask for all members and cache them. func (v *ValidOwner) initOrgListMembers(ctx context.Context) error { @@ -351,6 +363,36 @@ func (v *ValidOwner) initOrgListMembers(ctx context.Context) error { return nil } +// Add all outside collaborators who are part of the repository to +// +// outsideCollaborators *map[string]struct{} +func (v *ValidOwner) initOutsideCollaboratorsList(ctx context.Context) error { + opt := &github.ListCollaboratorsOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + Affiliation: "outside", + } + + var allMembers []*github.User + for { + collaborators, resp, err := v.ghClient.Repositories.ListCollaborators(ctx, v.orgName, v.orgRepoName, opt) + if err != nil { + return err + } + allMembers = append(allMembers, collaborators...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + + v.outsideCollaborators = &map[string]struct{}{} + for _, u := range allMembers { + (*v.outsideCollaborators)[u.GetLogin()] = struct{}{} + } + + return nil +} + // Name returns human-readable name of the validator func (ValidOwner) Name() string { return "Valid Owner Checker" diff --git a/internal/check/valid_owner_test.go b/internal/check/valid_owner_test.go index b42ee651..a683d05c 100644 --- a/internal/check/valid_owner_test.go +++ b/internal/check/valid_owner_test.go @@ -4,11 +4,13 @@ import ( "context" "testing" - "go.szostok.io/codeowners-validator/internal/check" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" + "go.szostok.io/codeowners/internal/config" "github.com/stretchr/testify/require" - "go.szostok.io/codeowners-validator/internal/ptr" + "go.szostok.io/codeowners/internal/ptr" "github.com/stretchr/testify/assert" ) @@ -55,9 +57,9 @@ func TestValidOwnerChecker(t *testing.T) { func TestValidOwnerCheckerIgnoredOwner(t *testing.T) { t.Run("Should ignore owner", func(t *testing.T) { // given - ownerCheck, err := check.NewValidOwner(check.ValidOwnerConfig{ - Repository: "org/repo", - IgnoredOwners: []string{"@owner1"}, + ownerCheck, err := check.NewValidOwner(&config.Config{ + OwnerCheckerRepository: "org/repo", + OwnerCheckerIgnoredOwners: []string{"@owner1"}, }, nil, true) require.NoError(t, err) @@ -74,21 +76,21 @@ func TestValidOwnerCheckerIgnoredOwner(t *testing.T) { t.Run("Should ignore user only and check the remaining owners", func(t *testing.T) { tests := map[string]struct { codeowners string - issue *check.Issue + issue *api.Issue allowUnownedPatterns bool }{ "No owners": { codeowners: `*`, - issue: &check.Issue{ - Severity: check.Warning, + issue: &api.Issue{ + Severity: api.Warning, LineNo: ptr.Uint64Ptr(1), Message: "Missing owner, at least one owner is required", }, }, "Bad owner definition": { codeowners: `* badOwner @owner1`, - issue: &check.Issue{ - Severity: check.Error, + issue: &api.Issue{ + Severity: api.Error, LineNo: ptr.Uint64Ptr(1), Message: `Not valid owner definition "badOwner"`, }, @@ -102,10 +104,10 @@ func TestValidOwnerCheckerIgnoredOwner(t *testing.T) { for tn, tc := range tests { t.Run(tn, func(t *testing.T) { // given - ownerCheck, err := check.NewValidOwner(check.ValidOwnerConfig{ - Repository: "org/repo", - AllowUnownedPatterns: tc.allowUnownedPatterns, - IgnoredOwners: []string{"@owner1"}, + ownerCheck, err := check.NewValidOwner(&config.Config{ + OwnerCheckerRepository: "org/repo", + OwnerCheckerAllowUnownedPatterns: tc.allowUnownedPatterns, + OwnerCheckerIgnoredOwners: []string{"@owner1"}, }, nil, true) require.NoError(t, err) @@ -123,13 +125,13 @@ func TestValidOwnerCheckerIgnoredOwner(t *testing.T) { func TestValidOwnerCheckerOwnersMustBeTeams(t *testing.T) { tests := map[string]struct { codeowners string - issue *check.Issue + issue *api.Issue allowUnownedPatterns bool }{ "Bad owner definition": { codeowners: `* @owner1`, - issue: &check.Issue{ - Severity: check.Error, + issue: &api.Issue{ + Severity: api.Error, LineNo: ptr.Uint64Ptr(1), Message: `Only team owners allowed and "@owner1" is not a team`, }, @@ -143,10 +145,10 @@ func TestValidOwnerCheckerOwnersMustBeTeams(t *testing.T) { for tn, tc := range tests { t.Run(tn, func(t *testing.T) { // given - ownerCheck, err := check.NewValidOwner(check.ValidOwnerConfig{ - Repository: "org/repo", - AllowUnownedPatterns: tc.allowUnownedPatterns, - OwnersMustBeTeams: true, + ownerCheck, err := check.NewValidOwner(&config.Config{ + OwnerCheckerRepository: "org/repo", + OwnerCheckerAllowUnownedPatterns: tc.allowUnownedPatterns, + OwnerCheckerOwnersMustBeTeams: true, }, nil, true) require.NoError(t, err) diff --git a/internal/check/valid_syntax.go b/internal/check/valid_syntax.go index f851e50d..16ffe460 100644 --- a/internal/check/valid_syntax.go +++ b/internal/check/valid_syntax.go @@ -6,7 +6,8 @@ import ( "regexp" "strings" - "go.szostok.io/codeowners-validator/internal/ctxutil" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/ctxutil" ) var ( @@ -32,16 +33,16 @@ func NewValidSyntax() *ValidSyntax { } // Check for syntax issues in your CODEOWNERS file. -func (v *ValidSyntax) Check(ctx context.Context, in Input) (Output, error) { - var bldr OutputBuilder +func (v *ValidSyntax) Check(ctx context.Context, in api.Input) (api.Output, error) { + var bldr api.OutputBuilder for _, entry := range in.CodeownersEntries { if ctxutil.ShouldExit(ctx) { - return Output{}, ctx.Err() + return api.Output{}, ctx.Err() } if entry.Pattern == "" { - bldr.ReportIssue("Missing pattern", WithEntry(entry)) + bldr.ReportIssue("Missing pattern", api.WithEntry(entry)) } ownersLoop: @@ -52,12 +53,12 @@ func (v *ValidSyntax) Check(ctx context.Context, in Input) (Output, error) { case strings.HasPrefix(item, "@"): if !usernameOrTeamRegexp.MatchString(item) { msg := fmt.Sprintf("Owner '%s' does not look like a GitHub username or team name", item) - bldr.ReportIssue(msg, WithEntry(entry), WithSeverity(Warning)) + bldr.ReportIssue(msg, api.WithEntry(entry), api.WithSeverity(api.Warning)) } default: if !emailRegexp.MatchString(item) { msg := fmt.Sprintf("Owner '%s' does not look like an email", item) - bldr.ReportIssue(msg, WithEntry(entry)) + bldr.ReportIssue(msg, api.WithEntry(entry)) } } } diff --git a/internal/check/valid_syntax_test.go b/internal/check/valid_syntax_test.go index e8a05d37..e7e7aea9 100644 --- a/internal/check/valid_syntax_test.go +++ b/internal/check/valid_syntax_test.go @@ -4,9 +4,10 @@ import ( "context" "testing" - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/ptr" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" + "go.szostok.io/codeowners/internal/ptr" + "go.szostok.io/codeowners/pkg/codeowners" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,7 +16,7 @@ import ( func TestValidSyntaxChecker(t *testing.T) { tests := map[string]struct { codeowners string - issue *check.Issue + issue *api.Issue }{ "No owners": { codeowners: `*`, @@ -23,40 +24,40 @@ func TestValidSyntaxChecker(t *testing.T) { }, "Bad username": { codeowners: `pkg/github.com/** @-`, - issue: &check.Issue{ - Severity: check.Warning, + issue: &api.Issue{ + Severity: api.Warning, LineNo: ptr.Uint64Ptr(1), Message: "Owner '@-' does not look like a GitHub username or team name", }, }, "Bad org": { codeowners: `* @bad+org`, - issue: &check.Issue{ - Severity: check.Warning, + issue: &api.Issue{ + Severity: api.Warning, LineNo: ptr.Uint64Ptr(1), Message: "Owner '@bad+org' does not look like a GitHub username or team name", }, }, "Bad team name on first place": { codeowners: `* @org/+not+a+good+name`, - issue: &check.Issue{ - Severity: check.Warning, + issue: &api.Issue{ + Severity: api.Warning, LineNo: ptr.Uint64Ptr(1), Message: "Owner '@org/+not+a+good+name' does not look like a GitHub username or team name", }, }, "Bad team name on second place": { codeowners: `* @org/hakuna-matata @org/-a-team`, - issue: &check.Issue{ - Severity: check.Warning, + issue: &api.Issue{ + Severity: api.Warning, LineNo: ptr.Uint64Ptr(1), Message: "Owner '@org/-a-team' does not look like a GitHub username or team name", }, }, "Doesn't look like username, team name, nor email": { codeowners: `* something_weird`, - issue: &check.Issue{ - Severity: check.Error, + issue: &api.Issue{ + Severity: api.Error, LineNo: ptr.Uint64Ptr(1), Message: "Owner 'something_weird' does not look like an email", }, @@ -81,7 +82,7 @@ func TestValidSyntaxChecker(t *testing.T) { func TestValidSyntaxZeroValueEntry(t *testing.T) { // given - zeroValueInput := check.Input{ + zeroValueInput := api.Input{ CodeownersEntries: []codeowners.Entry{ { LineNo: 0, @@ -90,10 +91,10 @@ func TestValidSyntaxZeroValueEntry(t *testing.T) { }, }, } - expIssues := []check.Issue{ + expIssues := []api.Issue{ { LineNo: ptr.Uint64Ptr(0), - Severity: check.Error, + Severity: api.Error, Message: "Missing pattern", }, } diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..a8d3ad3c --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,29 @@ +package config + +import "go.szostok.io/codeowners/internal/api" + +const ( + DefaultConfigFilename = "codeowners-config.yaml" + EnvPrefix = "CODEOWNERS" +) + +// Config holds the application configuration +type Config struct { + Checks []string `mapstructure:"checks"` + CheckFailureLevel api.SeverityType `mapstructure:"check-failure-level"` + ExperimentalChecks []string `mapstructure:"experimental-checks"` + GithubAccessToken string `mapstructure:"github-access-token"` + GithubBaseURL string `mapstructure:"github-base-url"` + GithubUploadURL string `mapstructure:"github-upload-url"` + GithubAppID int64 `mapstructure:"github-app-id"` + GithubAppInstallationID int64 `mapstructure:"github-app-installation-id"` + GithubAppPrivateKey string `mapstructure:"github-app-private-key"` + NotOwnedCheckerSkipPatterns []string `mapstructure:"not-owned-checker-skip-patterns"` + NotOwnedCheckerSubdirectories []string `mapstructure:"not-owned-checker-subdirectories"` + NotOwnedCheckerTrustWorkspace bool `mapstructure:"not-owned-checker-trust-workspace"` + OwnerCheckerRepository string `mapstructure:"owner-checker-repository"` + OwnerCheckerIgnoredOwners []string `mapstructure:"owner-checker-ignored-owners"` + OwnerCheckerAllowUnownedPatterns bool `mapstructure:"owner-checker-allow-unowned-patterns"` + OwnerCheckerOwnersMustBeTeams bool `mapstructure:"owner-checker-owners-must-be-teams"` + RepositoryPath string `mapstructure:"repository-path"` +} diff --git a/internal/ctxutil/check_test.go b/internal/ctxutil/check_test.go index 2ed83856..eb115404 100644 --- a/internal/ctxutil/check_test.go +++ b/internal/ctxutil/check_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - contextutil "go.szostok.io/codeowners-validator/internal/ctxutil" + contextutil "go.szostok.io/codeowners/internal/ctxutil" ) func TestShouldExit(t *testing.T) { diff --git a/internal/envconfig/envconfig.go b/internal/envconfig/envconfig.go deleted file mode 100644 index 625a7ce9..00000000 --- a/internal/envconfig/envconfig.go +++ /dev/null @@ -1,13 +0,0 @@ -package envconfig - -import ( - "os" - - "github.com/vrischmann/envconfig" -) - -// Init the given config. Supports also envs prefix if set. -func Init(conf interface{}) error { - envPrefix := os.Getenv("ENVS_PREFIX") - return envconfig.InitWithPrefix(conf, envPrefix) -} diff --git a/internal/envconfig/envconfig_test.go b/internal/envconfig/envconfig_test.go deleted file mode 100644 index 2a2b7c77..00000000 --- a/internal/envconfig/envconfig_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package envconfig_test - -import ( - "os" - "testing" - - "go.szostok.io/codeowners-validator/internal/envconfig" - - "github.com/stretchr/testify/require" - "gotest.tools/assert" -) - -type testConfig struct { - Key1 string -} - -func TestInit(t *testing.T) { - t.Run("Should read env variable without prefix", func(t *testing.T) { - // given - var cfg testConfig - - require.NoError(t, os.Setenv("KEY1", "test-value")) - - // when - err := envconfig.Init(&cfg) - - // then - require.NoError(t, err) - assert.Equal(t, "test-value", cfg.Key1) - }) - - t.Run("Should read env variable with prefix", func(t *testing.T) { - // given - var cfg testConfig - - require.NoError(t, os.Setenv("ENVS_PREFIX", "TEST_PREFIX")) - require.NoError(t, os.Setenv("TEST_PREFIX_KEY1", "test-value")) - - // when - err := envconfig.Init(&cfg) - - // then - require.NoError(t, err) - assert.Equal(t, "test-value", cfg.Key1) - }) -} diff --git a/internal/github/client.go b/internal/github/client.go index 6abc2bf6..2e1cdbb9 100644 --- a/internal/github/client.go +++ b/internal/github/client.go @@ -8,39 +8,30 @@ import ( "github.com/bradleyfalzon/ghinstallation/v2" - "go.szostok.io/codeowners-validator/pkg/url" + "go.szostok.io/codeowners/internal/config" + "go.szostok.io/codeowners/pkg/url" "github.com/google/go-github/v41/github" "golang.org/x/oauth2" ) -type ClientConfig struct { - AccessToken string `envconfig:"optional"` - - AppID int64 `envconfig:"optional"` - AppPrivateKey string `envconfig:"optional"` - AppInstallationID int64 `envconfig:"optional"` - - BaseURL string `envconfig:"optional"` - UploadURL string `envconfig:"optional"` - HTTPRequestTimeout time.Duration `envconfig:"default=30s"` -} +var httpRequestTimeout int = 30 // Validate validates if provided client options are valid. -func (c *ClientConfig) Validate() error { - if c.AccessToken == "" && c.AppID == 0 { +func Validate(cfg *config.Config) error { + if cfg.GithubAccessToken == "" && cfg.GithubAppID == 0 { return errors.New("GitHub authorization is required, provide ACCESS_TOKEN or APP_ID") } - if c.AccessToken != "" && c.AppID != 0 { + if cfg.GithubAccessToken != "" && cfg.GithubAppID != 0 { return errors.New("GitHub ACCESS_TOKEN cannot be provided when APP_ID is specified") } - if c.AppID != 0 { - if c.AppInstallationID == 0 { + if cfg.GithubAppID != 0 { + if cfg.GithubAppInstallationID == 0 { return errors.New("GitHub APP_INSTALLATION_ID is required with APP_ID") } - if c.AppPrivateKey == "" { + if cfg.GithubAppPrivateKey == "" { return errors.New("GitHub APP_PRIVATE_KEY is required with APP_ID") } } @@ -48,27 +39,31 @@ func (c *ClientConfig) Validate() error { return nil } -func NewClient(ctx context.Context, cfg *ClientConfig) (ghClient *github.Client, isApp bool, err error) { - if err := cfg.Validate(); err != nil { +func NewClient(ctx context.Context, cfg *config.Config) (ghClient *github.Client, isApp bool, err error) { + if err := Validate(cfg); err != nil { return nil, false, err } - httpClient := http.DefaultClient + httpClient := &http.Client{ + Transport: http.DefaultClient.Transport, + CheckRedirect: http.DefaultClient.CheckRedirect, + Jar: http.DefaultClient.Jar, + Timeout: time.Duration(httpRequestTimeout) * time.Second, + } - if cfg.AccessToken != "" { + if cfg.GithubAccessToken != "" { httpClient = oauth2.NewClient(ctx, oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: cfg.AccessToken}, + &oauth2.Token{AccessToken: cfg.GithubAccessToken}, )) - } else if cfg.AppID != 0 { + } else if cfg.GithubAppID != 0 { httpClient, err = createAppInstallationHTTPClient(cfg) isApp = true if err != nil { return } } - httpClient.Timeout = cfg.HTTPRequestTimeout - baseURL, uploadURL := cfg.BaseURL, cfg.UploadURL + baseURL, uploadURL := cfg.GithubBaseURL, cfg.GithubUploadURL if baseURL == "" { ghClient = github.NewClient(httpClient) @@ -84,9 +79,9 @@ func NewClient(ctx context.Context, cfg *ClientConfig) (ghClient *github.Client, return } -func createAppInstallationHTTPClient(cfg *ClientConfig) (client *http.Client, err error) { +func createAppInstallationHTTPClient(cfg *config.Config) (client *http.Client, err error) { tr := http.DefaultTransport - itr, err := ghinstallation.New(tr, cfg.AppID, cfg.AppInstallationID, []byte(cfg.AppPrivateKey)) + itr, err := ghinstallation.New(tr, cfg.GithubAppID, cfg.GithubAppInstallationID, []byte(cfg.GithubAppPrivateKey)) if err != nil { return nil, err } diff --git a/internal/load/load.go b/internal/load/load.go index 37172cdd..e27fa96c 100644 --- a/internal/load/load.go +++ b/internal/load/load.go @@ -3,47 +3,41 @@ package load import ( "context" - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/envconfig" - "go.szostok.io/codeowners-validator/internal/github" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/check" + "go.szostok.io/codeowners/internal/config" + "go.szostok.io/codeowners/internal/github" "github.com/pkg/errors" + "github.com/vrischmann/envconfig" ) // For now, it is a good enough solution to init checks. Important thing is to do not require env variables // and do not create clients which will not be used because of the given checker. // // MAYBE in the future the https://github.com/uber-go/dig will be used. -func Checks(ctx context.Context, enabledChecks, experimentalChecks []string) ([]check.Checker, error) { - var checks []check.Checker +func Checks(ctx context.Context, cfg *config.Config) ([]api.Checker, error) { + var checks []api.Checker - if isEnabled(enabledChecks, "syntax") { + if isEnabled(cfg.Checks, "syntax") { checks = append(checks, check.NewValidSyntax()) } - if isEnabled(enabledChecks, "duppatterns") { + if isEnabled(cfg.Checks, "duppatterns") { checks = append(checks, check.NewDuplicatedPattern()) } - if isEnabled(enabledChecks, "files") { + if isEnabled(cfg.Checks, "files") { checks = append(checks, check.NewFileExist()) } - if isEnabled(enabledChecks, "owners") { - var cfg struct { - OwnerChecker check.ValidOwnerConfig - Github github.ClientConfig - } - if err := envconfig.Init(&cfg); err != nil { - return nil, errors.Wrapf(err, "while loading config for %s", "owners") - } - - ghClient, isApp, err := github.NewClient(ctx, &cfg.Github) + if isEnabled(cfg.Checks, "owners") { + ghClient, isApp, err := github.NewClient(ctx, cfg) if err != nil { return nil, errors.Wrap(err, "while creating GitHub client") } - owners, err := check.NewValidOwner(cfg.OwnerChecker, ghClient, !isApp) + owners, err := check.NewValidOwner(cfg, ghClient, !isApp) if err != nil { return nil, errors.Wrap(err, "while enabling 'owners' checker") } @@ -55,7 +49,7 @@ func Checks(ctx context.Context, enabledChecks, experimentalChecks []string) ([] checks = append(checks, owners) } - expChecks, err := loadExperimentalChecks(experimentalChecks) + expChecks, err := loadExperimentalChecks(cfg.ExperimentalChecks) if err != nil { return nil, errors.Wrap(err, "while loading experimental checks") } @@ -63,8 +57,8 @@ func Checks(ctx context.Context, enabledChecks, experimentalChecks []string) ([] return append(checks, expChecks...), nil } -func loadExperimentalChecks(experimentalChecks []string) ([]check.Checker, error) { - var checks []check.Checker +func loadExperimentalChecks(experimentalChecks []string) ([]api.Checker, error) { + var checks []api.Checker if contains(experimentalChecks, "notowned") { var cfg struct { diff --git a/internal/printer/tty.go b/internal/printer/tty.go index 8bd7fc17..f6b1b2df 100644 --- a/internal/printer/tty.go +++ b/internal/printer/tty.go @@ -9,7 +9,7 @@ import ( "time" "github.com/fatih/color" - "go.szostok.io/codeowners-validator/internal/check" + "go.szostok.io/codeowners/internal/api" ) // writer used for test purpose @@ -19,7 +19,7 @@ type TTYPrinter struct { m sync.RWMutex } -func (tty *TTYPrinter) PrintCheckResult(checkName string, duration time.Duration, checkOut check.Output, checkErr error) { +func (tty *TTYPrinter) PrintCheckResult(checkName string, duration time.Duration, checkOut api.Output, checkErr error) { tty.m.Lock() defer tty.m.Unlock() @@ -48,12 +48,12 @@ func (tty *TTYPrinter) PrintCheckResult(checkName string, duration time.Duration } } -func (*TTYPrinter) severityPrintfFunc(severity check.SeverityType) func(w io.Writer, format string, a ...interface{}) { +func (*TTYPrinter) severityPrintfFunc(severity api.SeverityType) func(w io.Writer, format string, a ...interface{}) { p := color.New() switch severity { - case check.Warning: + case api.Warning: p.Add(color.FgYellow) - case check.Error: + case api.Error: p.Add(color.FgRed) } diff --git a/internal/printer/tty_test.go b/internal/printer/tty_test.go index 4eedb7e2..6754146f 100644 --- a/internal/printer/tty_test.go +++ b/internal/printer/tty_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/ptr" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/ptr" "github.com/sebdah/goldie/v2" ) @@ -23,24 +23,24 @@ func TestTTYPrinterPrintCheckResult(t *testing.T) { defer restore() // when - tty.PrintCheckResult("Foo Checker", time.Second, check.Output{ - Issues: []check.Issue{ + tty.PrintCheckResult("Foo Checker", time.Second, api.Output{ + Issues: []api.Issue{ { - Severity: check.Error, + Severity: api.Error, LineNo: ptr.Uint64Ptr(42), Message: "Simulate error in line 42", }, { - Severity: check.Warning, + Severity: api.Warning, LineNo: ptr.Uint64Ptr(2020), Message: "Simulate warning in line 2020", }, { - Severity: check.Error, + Severity: api.Error, Message: "Error without line number", }, { - Severity: check.Warning, + Severity: api.Warning, Message: "Warning without line number", }, }, @@ -59,7 +59,7 @@ func TestTTYPrinterPrintCheckResult(t *testing.T) { defer restore() // when - tty.PrintCheckResult("Foo Checker", time.Second, check.Output{ + tty.PrintCheckResult("Foo Checker", time.Second, api.Output{ Issues: nil, }, nil) diff --git a/internal/runner/runner_worker.go b/internal/runner/runner_worker.go index 5e2f6a11..c52bcc45 100644 --- a/internal/runner/runner_worker.go +++ b/internal/runner/runner_worker.go @@ -5,9 +5,9 @@ import ( "sync" "time" - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/printer" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/internal/api" + "go.szostok.io/codeowners/internal/printer" + "go.szostok.io/codeowners/pkg/codeowners" "github.com/sirupsen/logrus" ) @@ -21,7 +21,7 @@ const ( // Printer prints the checks results type Printer interface { - PrintCheckResult(checkName string, duration time.Duration, checkOut check.Output, err error) + PrintCheckResult(checkName string, duration time.Duration, checkOut api.Output, err error) PrintSummary(allCheck int, failedChecks int) } @@ -32,15 +32,15 @@ type CheckRunner struct { log logrus.FieldLogger codeowners []codeowners.Entry repoPath string - treatedAsFailure check.SeverityType - checks []check.Checker + treatedAsFailure api.SeverityType + checks []api.Checker printer Printer - allFoundIssues map[check.SeverityType]uint32 + allFoundIssues map[api.SeverityType]uint32 notPassedChecksCnt int } // NewCheckRunner is a constructor for CheckRunner -func NewCheckRunner(log logrus.FieldLogger, co []codeowners.Entry, repoPath string, treatedAsFailure check.SeverityType, checks ...check.Checker) *CheckRunner { +func NewCheckRunner(log logrus.FieldLogger, co []codeowners.Entry, repoPath string, treatedAsFailure api.SeverityType, checks ...api.Checker) *CheckRunner { return &CheckRunner{ log: log.WithField("service", "check:runner"), repoPath: repoPath, @@ -49,7 +49,7 @@ func NewCheckRunner(log logrus.FieldLogger, co []codeowners.Entry, repoPath stri checks: checks, printer: &printer.TTYPrinter{}, - allFoundIssues: map[check.SeverityType]uint32{}, + allFoundIssues: map[api.SeverityType]uint32{}, } } @@ -60,10 +60,10 @@ func (r *CheckRunner) Run(ctx context.Context) { // TODO(mszostok): timeout per check? wg.Add(len(r.checks)) for _, c := range r.checks { - go func(c check.Checker) { + go func(c api.Checker) { defer wg.Done() startTime := time.Now() - out, err := c.Check(ctx, check.Input{ + out, err := c.Check(ctx, api.Input{ CodeownersEntries: r.codeowners, RepoDir: r.repoPath, }) @@ -78,7 +78,7 @@ func (r *CheckRunner) Run(ctx context.Context) { } func (r *CheckRunner) ShouldExitWithCheckFailure() bool { - higherOccurredIssue := check.SeverityType(MaxInt) + higherOccurredIssue := api.SeverityType(MaxInt) for key := range r.allFoundIssues { if higherOccurredIssue > key { higherOccurredIssue = key @@ -88,7 +88,7 @@ func (r *CheckRunner) ShouldExitWithCheckFailure() bool { return higherOccurredIssue <= r.treatedAsFailure } -func (r *CheckRunner) collectMetrics(checkOut check.Output, err error) { +func (r *CheckRunner) collectMetrics(checkOut api.Output, err error) { r.m.Lock() defer r.m.Unlock() for _, i := range checkOut.Issues { @@ -96,7 +96,7 @@ func (r *CheckRunner) collectMetrics(checkOut check.Output, err error) { } if err != nil { - r.allFoundIssues[check.Error]++ + r.allFoundIssues[api.Error]++ } if len(checkOut.Issues) > 0 || err != nil { diff --git a/main.go b/main.go index 7987ca8c..a1feca7a 100644 --- a/main.go +++ b/main.go @@ -4,33 +4,16 @@ import ( "context" "os" "os/signal" - "path/filepath" "syscall" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "go.szostok.io/version/extension" - - "go.szostok.io/codeowners-validator/internal/check" - "go.szostok.io/codeowners-validator/internal/envconfig" - "go.szostok.io/codeowners-validator/internal/load" - "go.szostok.io/codeowners-validator/internal/runner" - "go.szostok.io/codeowners-validator/pkg/codeowners" + cmd "go.szostok.io/codeowners/cmd/codeowners" ) -// Config holds the application configuration -type Config struct { - RepositoryPath string - CheckFailureLevel check.SeverityType `envconfig:"default=warning"` - Checks []string `envconfig:"optional"` - ExperimentalChecks []string `envconfig:"optional"` -} - func main() { ctx, cancelFunc := WithStopContext(context.Background()) defer cancelFunc() - if err := NewRoot().ExecuteContext(ctx); err != nil { + if err := cmd.RootCmd().ExecuteContext(ctx); err != nil { // error is already handled by `cobra`, we don't want to log it here as we will duplicate the message. // If needed, based on error type we can exit with different codes. //nolint:gocritic @@ -38,12 +21,6 @@ func main() { } } -func exitOnError(err error) { - if err != nil { - logrus.Fatal(err) - } -} - // WithStopContext returns a copy of parent with a new Done channel. The returned // context's Done channel is closed on of SIGINT or SIGTERM signals. func WithStopContext(parent context.Context) (context.Context, context.CancelFunc) { @@ -61,48 +38,3 @@ func WithStopContext(parent context.Context) (context.Context, context.CancelFun return ctx, cancel } - -// NewRoot returns a root cobra.Command for the whole Agent utility. -func NewRoot() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "codeowners-validator", - Short: "Ensures the correctness of your CODEOWNERS file.", - SilenceUsage: true, - Run: func(cmd *cobra.Command, args []string) { - var cfg Config - err := envconfig.Init(&cfg) - exitOnError(err) - - log := logrus.New() - - // init checks - checks, err := load.Checks(cmd.Context(), cfg.Checks, cfg.ExperimentalChecks) - exitOnError(err) - - // init codeowners entries - codeownersEntries, err := codeowners.NewFromPath(cfg.RepositoryPath) - exitOnError(err) - - // run check runner - absRepoPath, err := filepath.Abs(cfg.RepositoryPath) - exitOnError(err) - - checkRunner := runner.NewCheckRunner(log, codeownersEntries, absRepoPath, cfg.CheckFailureLevel, checks...) - checkRunner.Run(cmd.Context()) - - if cmd.Context().Err() != nil { - log.Error("Application was interrupted by operating system") - os.Exit(2) - } - if checkRunner.ShouldExitWithCheckFailure() { - os.Exit(3) - } - }, - } - - rootCmd.AddCommand( - extension.NewVersionCobraCmd(), - ) - - return rootCmd -} diff --git a/pkg/codeowners/owners_example_test.go b/pkg/codeowners/owners_example_test.go index 65532f81..3468b672 100644 --- a/pkg/codeowners/owners_example_test.go +++ b/pkg/codeowners/owners_example_test.go @@ -3,7 +3,7 @@ package codeowners_test import ( "fmt" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/pkg/codeowners" ) func ExampleNewFromPath() { diff --git a/pkg/codeowners/owners_test.go b/pkg/codeowners/owners_test.go index 4b4ae799..0fa54e42 100644 --- a/pkg/codeowners/owners_test.go +++ b/pkg/codeowners/owners_test.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.szostok.io/codeowners-validator/pkg/codeowners" + "go.szostok.io/codeowners/pkg/codeowners" ) const sampleCodeownerFile = ` diff --git a/pkg/url/canonical_test.go b/pkg/url/canonical_test.go index 7d05e190..b1e08cd5 100644 --- a/pkg/url/canonical_test.go +++ b/pkg/url/canonical_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.szostok.io/codeowners-validator/pkg/url" + "go.szostok.io/codeowners/pkg/url" ) func TestCanonicalURLPath(t *testing.T) { diff --git a/tests/integration/helpers_test.go b/tests/integration/helpers_test.go index 60d6578e..cd2385bd 100644 --- a/tests/integration/helpers_test.go +++ b/tests/integration/helpers_test.go @@ -54,19 +54,30 @@ func CloneRepo(t *testing.T, url string, branch string) (string, func()) { type Executor struct { envs map[string]string + arguments []string timeout time.Duration binaryPath string } func Exec() *Executor { return &Executor{ - envs: map[string]string{}, + arguments: make([]string, 0), + envs: map[string]string{}, } } // WithEnv adds given env. Overrides if previously existed func (s *Executor) WithEnv(key string, value string) *Executor { - s.envs[key] = value + if key == "PATH" { + s.envs[key] = value + } else { + s.envs[envPrefix+key] = value + } + return s +} + +func (s *Executor) WithArg(argument string) *Executor { + s.arguments = append(s.arguments, argument) return s } @@ -83,7 +94,7 @@ func (s *Executor) AwaitResultAtMost(timeout time.Duration) *ExecuteOutput { var stdout, stderr bytes.Buffer - cmd := exec.CommandContext(ctx, s.binaryPath) + cmd := exec.CommandContext(ctx, s.binaryPath, s.arguments...) cmd.Stderr = &stderr cmd.Stdout = &stdout diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 1c56d540..a4bd0112 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -15,6 +15,7 @@ import ( ) const ( + envPrefix = "CODEOWNERS_" binaryPathEnvName = "BINARY_PATH" codeownersSamplesRepo = "https://github.com/gh-codeowners/codeowners-samples.git" caseInsensitiveOrgCodeownersSamplesRepo = "https://github.com/GitHubCODEOWNERS/codeowners-samples.git" @@ -38,7 +39,7 @@ var repositories = []struct { }, } -// TestCheckHappyPath tests that codeowners-validator reports no issues for valid CODEOWNERS file. +// TestCheckHappyPath tests that codeowners reports no issues for valid CODEOWNERS file. // // This test is based on golden file. // If the `-test.update-golden` flag is set then the actual content is written @@ -100,7 +101,8 @@ func TestCheckSuccess(t *testing.T) { binaryPath := os.Getenv(binaryPathEnvName) codeownersCmd := Exec(). Binary(binaryPath). - // codeowners-validator basic config + // codeowners basic config + WithArg("validate"). WithEnv("REPOSITORY_PATH", repoDir) for k, v := range tc.envs { @@ -159,7 +161,8 @@ func TestCheckSuccess(t *testing.T) { binaryPath := os.Getenv(binaryPathEnvName) codeownersCmd := Exec(). Binary(binaryPath). - // codeowners-validator basic config + // codeowners basic config + WithArg("validate"). WithEnv("REPOSITORY_PATH", repoDir) for k, v := range tc.envs { @@ -180,14 +183,15 @@ func TestCheckSuccess(t *testing.T) { }) } -// TestCheckFailures tests that codeowners-validator reports issues for not valid CODEOWNERS file. +// TestCheckFailures tests that codeowners reports issues for not valid CODEOWNERS file. // // This test is based on golden file. // If the `-test.update-golden` flag is set then the actual content is written // to the golden file. // // To update golden file, run: -// TEST=TestCheckFailures UPDATE_GOLDEN=true make test-integration +// +// TEST=TestCheckFailures UPDATE_GOLDEN=true make test-integration func TestCheckFailures(t *testing.T) { type Envs map[string]string tests := []struct { @@ -252,7 +256,8 @@ func TestCheckFailures(t *testing.T) { codeownersCmd := Exec(). Binary(binaryPath). - // codeowners-validator basic config + // codeowners basic config + WithArg("validate"). WithEnv("REPOSITORY_PATH", repoDir) for k, v := range tc.envs { @@ -274,7 +279,8 @@ func TestCheckFailures(t *testing.T) { } // To update golden file, run: -// TEST=TestOwnerCheckAuthZAndAuthN TOKEN_WITH_NO_SCOPES= UPDATE_GOLDEN=true make test-integration +// +// TEST=TestOwnerCheckAuthZAndAuthN TOKEN_WITH_NO_SCOPES= UPDATE_GOLDEN=true make test-integration func TestOwnerCheckAuthZAndAuthN(t *testing.T) { t.Parallel() @@ -284,6 +290,7 @@ func TestOwnerCheckAuthZAndAuthN(t *testing.T) { // given codeownersCmd := Exec(). Binary(os.Getenv(binaryPathEnvName)). + WithArg("validate"). WithEnv("REPOSITORY_PATH", "not-needed"). WithEnv("CHECKS", "owners"). WithEnv("OWNER_CHECKER_REPOSITORY", "gh-codeowners/codeowners-samples") @@ -306,6 +313,7 @@ func TestOwnerCheckAuthZAndAuthN(t *testing.T) { // given codeownersCmd := Exec(). Binary(os.Getenv(binaryPathEnvName)). + WithArg("validate"). WithEnv("REPOSITORY_PATH", "not-needed"). WithEnv("CHECKS", "owners"). WithEnv("OWNER_CHECKER_REPOSITORY", "gh-codeowners/codeowners-samples"). @@ -328,6 +336,7 @@ func TestOwnerCheckAuthZAndAuthN(t *testing.T) { // given codeownersCmd := Exec(). Binary(os.Getenv(binaryPathEnvName)). + WithArg("validate"). WithEnv("REPOSITORY_PATH", "not-needed"). WithEnv("CHECKS", "owners"). WithEnv("OWNER_CHECKER_REPOSITORY", "gh-codeowners/codeowners-samples"). @@ -350,6 +359,7 @@ func TestOwnerCheckAuthZAndAuthN(t *testing.T) { // given codeownersCmd := Exec(). Binary(os.Getenv(binaryPathEnvName)). + WithArg("validate"). WithEnv("REPOSITORY_PATH", "not-needed"). WithEnv("CHECKS", "owners"). WithEnv("OWNER_CHECKER_REPOSITORY", "gh-codeowners/private-repo"). @@ -378,6 +388,7 @@ func TestGitHubAppAuth(t *testing.T) { codeownersCmd := Exec(). Binary(os.Getenv(binaryPathEnvName)). + WithArg("validate"). WithEnv("REPOSITORY_PATH", repoDir). WithEnv("CHECKS", "owners"). WithEnv("OWNER_CHECKER_REPOSITORY", "GitHubCODEOWNERS/codeowners-samples").