diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aebc09568..4c9c5cacc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - name: Set up go uses: actions/setup-go@v5 with: - go-version: '1.22.4' + go-version: '1.24.1' - uses: actions/checkout@v4 with: @@ -36,7 +36,7 @@ jobs: - name: Set up go uses: actions/setup-go@v5 with: - go-version: '1.22.4' + go-version: '1.24.1' - uses: actions/checkout@v4 with: @@ -61,7 +61,7 @@ jobs: - name: Set up go uses: actions/setup-go@v5 with: - go-version: '1.22.4' + go-version: '1.24.1' - uses: actions/checkout@v4 with: @@ -72,10 +72,10 @@ jobs: # There is a risk of drift between the two, but this is only linting, # not runtime correctness! - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: working-directory: git-sync - version: v1.59.0 + version: v2.0.0 - name: make lint working-directory: git-sync diff --git a/.golangci.yaml b/.golangci.yaml index cd1a03c17..89bbffc9d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -2,25 +2,55 @@ # pass, in contrast to .golangci.yaml which defines checks that also the # existing code passes. +version: "2" + run: timeout: 30m linters: - disable-all: false + default: all + enable: # please keep this alphabetized - - ginkgolinter - - gocritic - govet - ineffassign - # Should we add logcheck, for consistency with kubernetes/kubernetes? - # - logcheck - staticcheck - - stylecheck - unused + - gocritic + - asasalint + - copyloopvar + + disable: + - errcheck + - cyclop + - depguard + - dupl + - err113 + - exhaustruct + - funlen + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst # TODO: turn this one on + - gocyclo + - gosec # TODO: turn this one on + - lll + - maintidx + - mnd + - musttag + - nestif + - nlreturn + - paralleltest + - perfsprint + - promlinter + - recvcheck + - revive + - tagliatelle + - testpackage + - varnamelen + - wrapcheck + - wsl -linters-settings: # please keep this alphabetized - gocritic: - staticcheck: - checks: - - "all" - stylecheck: + settings: # please keep this alphabetized + staticcheck: + checks: + - "all" diff --git a/Makefile b/Makefile index ed03f7259..05ea8cdc4 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ IMAGE := $(REGISTRY)/$(BIN) TAG := $(VERSION) OS_ARCH_TAG := $(TAG)__$(OS)_$(ARCH) -BUILD_IMAGE ?= golang:1.22 +BUILD_IMAGE ?= golang:1.24 DBG_MAKEFILE ?= ifneq ($(DBG_MAKEFILE),1) @@ -277,11 +277,8 @@ container-clean: bin-clean: rm -rf .go bin -lint-staticcheck: - go run honnef.co/go/tools/cmd/staticcheck@2023.1.3 - lint-golangci-lint: - go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0 run + go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.0 run -v lint-shellcheck: docker run \ @@ -292,4 +289,4 @@ lint-shellcheck: shellcheck \ $$(git ls-files ':!:vendor' '*.sh') -lint: lint-staticcheck lint-golangci-lint lint-shellcheck +lint: lint-golangci-lint lint-shellcheck diff --git a/credential.go b/credential.go index 523aff330..adf9cf1aa 100644 --- a/credential.go +++ b/credential.go @@ -48,7 +48,7 @@ type credentialSliceValue struct { var _ pflag.Value = &credentialSliceValue{} var _ pflag.SliceValue = &credentialSliceValue{} -// pflagCredentialSlice is like pflag.StringSlice() +// pflagCredentialSlice is like pflag.StringSlice(). func pflagCredentialSlice(name, def, usage string) *[]credential { p := &credentialSliceValue{} _ = p.Set(def) diff --git a/env.go b/env.go index 9b401cf66..6515d234d 100644 --- a/env.go +++ b/env.go @@ -296,7 +296,7 @@ type explicitFlag[T comparable] struct { flagPtr *T } -// newExplicitFlag allocates an explicitFlag +// newExplicitFlag allocates an explicitFlag. func newExplicitFlag[T comparable](ptr *T, name, usage string, fn func(name string, value T, usage string) *T) { h := &explicitFlag[T]{ realPtr: ptr, diff --git a/go.mod b/go.mod index 3df9edfdd..7988c2ff6 100644 --- a/go.mod +++ b/go.mod @@ -20,4 +20,4 @@ require ( google.golang.org/protobuf v1.33.0 // indirect ) -go 1.22 +go 1.23 diff --git a/main.go b/main.go index 65480f7a2..ae0a5bb04 100644 --- a/main.go +++ b/main.go @@ -378,7 +378,7 @@ func main() { // Handle print-and-exit cases. if *flVersion { - fmt.Println(version.VERSION) + fmt.Fprintln(os.Stdout, version.VERSION) os.Exit(0) } if *flHelp { @@ -415,7 +415,7 @@ func main() { cmdRunner := cmd.NewRunner(log) if *flRepo == "" { - fatalConfigError(log, true, "required flag: --repo must be specified") + fatalConfigErrorf(log, true, "required flag: --repo must be specified") } switch { @@ -428,27 +428,27 @@ func main() { log.V(0).Info("setting --ref from deprecated --rev") *flRef = *flDeprecatedRev case *flDeprecatedBranch != "" && *flDeprecatedRev != "": - fatalConfigError(log, true, "deprecated flag combo: can't set --ref from deprecated --branch and --rev (one or the other is OK)") + fatalConfigErrorf(log, true, "deprecated flag combo: can't set --ref from deprecated --branch and --rev (one or the other is OK)") } if *flRef == "" { - fatalConfigError(log, true, "required flag: --ref must be specified") + fatalConfigErrorf(log, true, "required flag: --ref must be specified") } if *flDepth < 0 { // 0 means "no limit" - fatalConfigError(log, true, "invalid flag: --depth must be greater than or equal to 0") + fatalConfigErrorf(log, true, "invalid flag: --depth must be greater than or equal to 0") } switch submodulesMode(*flSubmodules) { case submodulesRecursive, submodulesShallow, submodulesOff: default: - fatalConfigError(log, true, "invalid flag: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff) + fatalConfigErrorf(log, true, "invalid flag: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff) } switch *flGitGC { case gcAuto, gcAlways, gcAggressive, gcOff: default: - fatalConfigError(log, true, "invalid flag: --git-gc must be one of %q, %q, %q, or %q", gcAuto, gcAlways, gcAggressive, gcOff) + fatalConfigErrorf(log, true, "invalid flag: --git-gc must be one of %q, %q, %q, or %q", gcAuto, gcAlways, gcAggressive, gcOff) } if *flDeprecatedDest != "" { @@ -467,11 +467,11 @@ func main() { *flPeriod = time.Duration(int(*flDeprecatedWait*1000)) * time.Millisecond } if *flPeriod < 10*time.Millisecond { - fatalConfigError(log, true, "invalid flag: --period must be at least 10ms") + fatalConfigErrorf(log, true, "invalid flag: --period must be at least 10ms") } if *flDeprecatedChmod != 0 { - fatalConfigError(log, true, "deprecated flag: --change-permissions is no longer supported") + fatalConfigErrorf(log, true, "deprecated flag: --change-permissions is no longer supported") } var syncSig syscall.Signal @@ -488,7 +488,7 @@ func main() { } } if syncSig == 0 { - fatalConfigError(log, true, "invalid flag: --sync-on-signal must be a valid signal name or number") + fatalConfigErrorf(log, true, "invalid flag: --sync-on-signal must be a valid signal name or number") } } @@ -498,7 +498,7 @@ func main() { *flSyncTimeout = time.Duration(*flDeprecatedTimeout) * time.Second } if *flSyncTimeout < 10*time.Millisecond { - fatalConfigError(log, true, "invalid flag: --sync-timeout must be at least 10ms") + fatalConfigErrorf(log, true, "invalid flag: --sync-timeout must be at least 10ms") } if *flDeprecatedMaxSyncFailures != 0 { @@ -514,10 +514,10 @@ func main() { } if *flExechookCommand != "" { if *flExechookTimeout < time.Second { - fatalConfigError(log, true, "invalid flag: --exechook-timeout must be at least 1s") + fatalConfigErrorf(log, true, "invalid flag: --exechook-timeout must be at least 1s") } if *flExechookBackoff < time.Second { - fatalConfigError(log, true, "invalid flag: --exechook-backoff must be at least 1s") + fatalConfigErrorf(log, true, "invalid flag: --exechook-backoff must be at least 1s") } } @@ -527,13 +527,13 @@ func main() { *flWebhookStatusSuccess = 0 } if *flWebhookStatusSuccess < 0 { - fatalConfigError(log, true, "invalid flag: --webhook-success-status must be a valid HTTP code or 0") + fatalConfigErrorf(log, true, "invalid flag: --webhook-success-status must be a valid HTTP code or 0") } if *flWebhookTimeout < time.Second { - fatalConfigError(log, true, "invalid flag: --webhook-timeout must be at least 1s") + fatalConfigErrorf(log, true, "invalid flag: --webhook-timeout must be at least 1s") } if *flWebhookBackoff < time.Second { - fatalConfigError(log, true, "invalid flag: --webhook-backoff must be at least 1s") + fatalConfigErrorf(log, true, "invalid flag: --webhook-backoff must be at least 1s") } } @@ -543,79 +543,79 @@ func main() { } if *flUsername != "" { if *flPassword == "" && *flPasswordFile == "" { - fatalConfigError(log, true, "required flag: $GITSYNC_PASSWORD or --password-file must be specified when --username is specified") + fatalConfigErrorf(log, true, "required flag: $GITSYNC_PASSWORD or --password-file must be specified when --username is specified") } if *flPassword != "" && *flPasswordFile != "" { - fatalConfigError(log, true, "invalid flag: only one of $GITSYNC_PASSWORD and --password-file may be specified") + fatalConfigErrorf(log, true, "invalid flag: only one of $GITSYNC_PASSWORD and --password-file may be specified") } if u, err := url.Parse(*flRepo); err == nil { // it may not even parse as a URL, that's OK if u.User != nil { - fatalConfigError(log, true, "invalid flag: credentials may not be specified in --repo when --username is specified") + fatalConfigErrorf(log, true, "invalid flag: credentials may not be specified in --repo when --username is specified") } } } else { if *flPassword != "" { - fatalConfigError(log, true, "invalid flag: $GITSYNC_PASSWORD may only be specified when --username is specified") + fatalConfigErrorf(log, true, "invalid flag: $GITSYNC_PASSWORD may only be specified when --username is specified") } if *flPasswordFile != "" { - fatalConfigError(log, true, "invalid flag: --password-file may only be specified when --username is specified") + fatalConfigErrorf(log, true, "invalid flag: --password-file may only be specified when --username is specified") } } if *flGithubAppApplicationID != 0 || *flGithubAppClientID != "" { if *flGithubAppApplicationID != 0 && *flGithubAppClientID != "" { - fatalConfigError(log, true, "invalid flag: only one of --github-app-application-id or --github-app-client-id may be specified") + fatalConfigErrorf(log, true, "invalid flag: only one of --github-app-application-id or --github-app-client-id may be specified") } if *flGithubAppInstallationID == 0 { - fatalConfigError(log, true, "invalid flag: --github-app-installation-id must be specified when --github-app-application-id or --github-app-client-id are specified") + fatalConfigErrorf(log, true, "invalid flag: --github-app-installation-id must be specified when --github-app-application-id or --github-app-client-id are specified") } if *flGithubAppPrivateKey == "" && *flGithubAppPrivateKeyFile == "" { - fatalConfigError(log, true, "invalid flag: $GITSYNC_GITHUB_APP_PRIVATE_KEY or --github-app-private-key-file must be specified when --github-app-application-id or --github-app-client-id are specified") + fatalConfigErrorf(log, true, "invalid flag: $GITSYNC_GITHUB_APP_PRIVATE_KEY or --github-app-private-key-file must be specified when --github-app-application-id or --github-app-client-id are specified") } if *flGithubAppPrivateKey != "" && *flGithubAppPrivateKeyFile != "" { - fatalConfigError(log, true, "invalid flag: only one of $GITSYNC_GITHUB_APP_PRIVATE_KEY or --github-app-private-key-file may be specified") + fatalConfigErrorf(log, true, "invalid flag: only one of $GITSYNC_GITHUB_APP_PRIVATE_KEY or --github-app-private-key-file may be specified") } if *flUsername != "" { - fatalConfigError(log, true, "invalid flag: --username may not be specified when --github-app-private-key-file is specified") + fatalConfigErrorf(log, true, "invalid flag: --username may not be specified when --github-app-private-key-file is specified") } if *flPassword != "" { - fatalConfigError(log, true, "invalid flag: --password may not be specified when --github-app-private-key-file is specified") + fatalConfigErrorf(log, true, "invalid flag: --password may not be specified when --github-app-private-key-file is specified") } if *flPasswordFile != "" { - fatalConfigError(log, true, "invalid flag: --password-file may not be specified when --github-app-private-key-file is specified") + fatalConfigErrorf(log, true, "invalid flag: --password-file may not be specified when --github-app-private-key-file is specified") } } else { if *flGithubAppApplicationID != 0 { - fatalConfigError(log, true, "invalid flag: --github-app-application-id may only be specified when --github-app-private-key-file is specified") + fatalConfigErrorf(log, true, "invalid flag: --github-app-application-id may only be specified when --github-app-private-key-file is specified") } if *flGithubAppInstallationID != 0 { - fatalConfigError(log, true, "invalid flag: --github-app-installation-id may only be specified when --github-app-private-key-file is specified") + fatalConfigErrorf(log, true, "invalid flag: --github-app-installation-id may only be specified when --github-app-private-key-file is specified") } } if len(*flCredentials) > 0 { for _, cred := range *flCredentials { if cred.URL == "" { - fatalConfigError(log, true, "invalid flag: --credential URL must be specified") + fatalConfigErrorf(log, true, "invalid flag: --credential URL must be specified") } if cred.Username == "" { - fatalConfigError(log, true, "invalid flag: --credential username must be specified") + fatalConfigErrorf(log, true, "invalid flag: --credential username must be specified") } if cred.Password == "" && cred.PasswordFile == "" { - fatalConfigError(log, true, "invalid flag: --credential password or password-file must be specified") + fatalConfigErrorf(log, true, "invalid flag: --credential password or password-file must be specified") } if cred.Password != "" && cred.PasswordFile != "" { - fatalConfigError(log, true, "invalid flag: only one of --credential password and password-file may be specified") + fatalConfigErrorf(log, true, "invalid flag: only one of --credential password and password-file may be specified") } } } if *flHTTPBind == "" { if *flHTTPMetrics { - fatalConfigError(log, true, "required flag: --http-bind must be specified when --http-metrics is set") + fatalConfigErrorf(log, true, "required flag: --http-bind must be specified when --http-metrics is set") } if *flHTTPprof { - fatalConfigError(log, true, "required flag: --http-bind must be specified when --http-pprof is set") + fatalConfigErrorf(log, true, "required flag: --http-bind must be specified when --http-pprof is set") } } @@ -1105,8 +1105,8 @@ func logSafeFlags(v int) []string { } // Handle --credential if arg == "credential" { - orig := fl.Value.(*credentialSliceValue) - sl := []credential{} // make a copy of the slice so we can mutate it + orig := fl.Value.(*credentialSliceValue) //nolint:forcetypeassert + sl := []credential{} // make a copy of the slice so we can mutate it for _, cred := range orig.value { if cred.Password != "" { cred.Password = redactedString @@ -1153,10 +1153,12 @@ func sleepForever() { os.Exit(0) } -// fatalConfigError prints the error to the standard error, prints the usage +// fatalConfigErrorf prints the error to the standard error, prints the usage // if the `printUsage` flag is true, exports the error to the error file and // exits the process with the exit code. -func fatalConfigError(log *logging.Logger, printUsage bool, format string, a ...interface{}) { +// +//nolint:unparam +func fatalConfigErrorf(log *logging.Logger, printUsage bool, format string, a ...interface{}) { s := fmt.Sprintf(format, a...) fmt.Fprintln(os.Stderr, s) if printUsage { @@ -1399,7 +1401,7 @@ func dirIsEmpty(dir absPath) (bool, error) { return len(dirents) == 0, nil } -// removeDirContents iterated the specified dir and removes all contents +// removeDirContents iterated the specified dir and removes all contents. func removeDirContents(dir absPath, log *logging.Logger) error { return removeDirContentsIf(dir, log, func(fi os.FileInfo) (bool, error) { return true, nil @@ -1445,7 +1447,7 @@ func removeDirContentsIf(dir absPath, log *logging.Logger, fn func(fi os.FileInf // publishSymlink atomically sets link to point at the specified target. If the // link existed, this returns the previous target. -func (git *repoSync) publishSymlink(ctx context.Context, worktree worktree) error { +func (git *repoSync) publishSymlink(worktree worktree) error { targetPath := worktree.Path() linkDir, linkFile := git.link.Split() @@ -1475,7 +1477,7 @@ func (git *repoSync) publishSymlink(ctx context.Context, worktree worktree) erro return nil } -// removeWorktree is used to remove a worktree and its folder +// removeWorktree is used to remove a worktree and its folder. func (git *repoSync) removeWorktree(ctx context.Context, worktree worktree) error { // Clean up worktree, if needed. _, err := os.Stat(worktree.Path().String()) @@ -1527,7 +1529,7 @@ func (git *repoSync) configureWorktree(ctx context.Context, worktree worktree) e // /git/.git/worktrees/. Replace it with a reference // using relative paths, so that other containers can use a different volume // mount name. - rootDotGit := "" + var rootDotGit string if rel, err := filepath.Rel(worktree.Path().String(), git.root.String()); err != nil { return err } else { @@ -1745,7 +1747,7 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con // Figure out what we got. The ^{} syntax "peels" annotated tags to // their underlying commit hashes, but has no effect if we fetched a // branch, plain tag, or hash. - remoteHash := "" + var remoteHash string if output, _, err := git.Run(ctx, git.root, "rev-parse", "FETCH_HEAD^{}"); err != nil { return false, "", err } else { @@ -1802,7 +1804,7 @@ func (git *repoSync) SyncRepo(ctx context.Context, refreshCreds func(context.Con // If we have a new hash, update the symlink to point to the new worktree. if changed { - err := git.publishSymlink(ctx, newWorktree) + err := git.publishSymlink(newWorktree) if err != nil { return false, "", err } @@ -1976,7 +1978,7 @@ func (git *repoSync) CallAskPassURL(ctx context.Context) error { return http.ErrUseLastResponse }, } - httpReq, err := http.NewRequestWithContext(ctx, "GET", git.authURL, nil) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, git.authURL, nil) if err != nil { return fmt.Errorf("can't create auth request: %w", err) } @@ -1987,7 +1989,7 @@ func (git *repoSync) CallAskPassURL(ctx context.Context) error { defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { errMessage, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("auth URL returned status %d, failed to read body: %w", resp.StatusCode, err) @@ -2021,7 +2023,8 @@ func (git *repoSync) CallAskPassURL(ctx context.Context) error { return nil } -// RefreshGitHubAppToken generates a new installation token for a GitHub app and stores it as a credential +// RefreshGitHubAppToken generates a new installation token for a GitHub app +// and stores it as a credential. func (git *repoSync) RefreshGitHubAppToken(ctx context.Context, githubBaseURL, privateKey, privateKeyFile, clientID string, appID, installationID int) error { git.log.V(3).Info("refreshing GitHub app token") @@ -2080,7 +2083,7 @@ func (git *repoSync) RefreshGitHubAppToken(ctx context.Context, githubBaseURL, p defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != 201 { + if resp.StatusCode != http.StatusCreated { errMessage, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("GitHub app installation endpoint returned status %d, failed to read body: %w", resp.StatusCode, err) @@ -2247,8 +2250,8 @@ func parseGitConfigKey(r rune, ch <-chan rune) (string, error) { buf = append(buf, r) for r := range ch { - switch { - case r == ':': + switch r { + case ':': return string(buf), nil default: buf = append(buf, r) @@ -2777,5 +2780,5 @@ HOOKS ` func printManPage() { - fmt.Print(manual) + fmt.Fprint(os.Stdout, manual) } diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index c0894cf54..996d6c2c1 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cmd provides an API to run commands and log them in a consistent +// way. package cmd import ( @@ -40,7 +42,7 @@ type logintf interface { WithCallDepth(depth int) logr.Logger } -// NewRunner returns a new CommandRunner +// NewRunner returns a new CommandRunner. func NewRunner(log logintf) Runner { return Runner{log: log} } diff --git a/pkg/hook/exechook.go b/pkg/hook/exechook.go index 9bd13e289..e31d99642 100644 --- a/pkg/hook/exechook.go +++ b/pkg/hook/exechook.go @@ -25,7 +25,7 @@ import ( "k8s.io/git-sync/pkg/cmd" ) -// Exechook structure, implements Hook +// Exechook implements Hook in terms of executing a command. type Exechook struct { // Runner cmdrunner cmd.Runner @@ -41,7 +41,7 @@ type Exechook struct { log logintf } -// NewExechook returns a new Exechook +// NewExechook returns a new Exechook. func NewExechook(cmdrunner cmd.Runner, command string, getWorktree func(string) string, args []string, timeout time.Duration, log logintf) *Exechook { return &Exechook{ cmdrunner: cmdrunner, @@ -53,12 +53,12 @@ func NewExechook(cmdrunner cmd.Runner, command string, getWorktree func(string) } } -// Name describes hook, implements Hook.Name +// Name describes hook, implements Hook.Name. func (h *Exechook) Name() string { return "exechook" } -// Do runs exechook.command, implements Hook.Do +// Do runs exechook.command, implements Hook.Do. func (h *Exechook) Do(ctx context.Context, hash string) error { ctx, cancel := context.WithTimeout(ctx, h.timeout) defer cancel() diff --git a/pkg/hook/hook.go b/pkg/hook/hook.go index 9432e3873..5ba5a5cf2 100644 --- a/pkg/hook/hook.go +++ b/pkg/hook/hook.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package hook provides a way to run hooks in a controlled way. package hook import ( @@ -38,7 +39,7 @@ func init() { prometheus.MustRegister(hookRunCount) } -// Describes what a Hook needs to implement, run by HookRunner +// Hook describes a single hook of some sort, which can be run by HookRunner. type Hook interface { // Describes hook Name() string @@ -52,7 +53,7 @@ type hookData struct { hash string } -// NewHookData returns a new HookData +// NewHookData returns a new HookData. func NewHookData() *hookData { return &hookData{ ch: make(chan struct{}, 1), @@ -87,7 +88,7 @@ func (d *hookData) send(newHash string) { } } -// NewHookRunner returns a new HookRunner +// NewHookRunner returns a new HookRunner. func NewHookRunner(hook Hook, backoff time.Duration, data *hookData, log logintf, oneTime bool) *HookRunner { hr := &HookRunner{hook: hook, backoff: backoff, data: data, log: log} if oneTime { @@ -96,7 +97,7 @@ func NewHookRunner(hook Hook, backoff time.Duration, data *hookData, log logintf return hr } -// HookRunner struct +// HookRunner struct. type HookRunner struct { // Hook to run and check hook Hook @@ -118,12 +119,12 @@ type logintf interface { V(level int) logr.Logger } -// Send sends hash to hookdata +// Send sends hash to hookdata. func (r *HookRunner) Send(hash string) { r.data.send(hash) } -// Run waits for trigger events from the channel, and run hook when triggered +// Run waits for trigger events from the channel, and run hook when triggered. func (r *HookRunner) Run(ctx context.Context) { var lastHash string @@ -171,7 +172,7 @@ func (r *HookRunner) sendOneTimeResultAndTerminate(completedSuccessfully bool) { // WaitForCompletion waits for HookRunner to send completion message to // calling thread and returns either true if HookRunner executed successfully // and some error otherwise. -// Assumes that r.oneTimeResult is not nil, but if it is, returns an error +// Assumes that r.oneTimeResult is not nil, but if it is, returns an error. func (r *HookRunner) WaitForCompletion() error { // Make sure function should be called if r.oneTimeResult == nil { diff --git a/pkg/hook/hook_test.go b/pkg/hook/hook_test.go index 2c6666e13..584763075 100644 --- a/pkg/hook/hook_test.go +++ b/pkg/hook/hook_test.go @@ -43,7 +43,7 @@ func TestHookData(t *testing.T) { t.Run("last update wins when channel buffer is full", func(t *testing.T) { hd := NewHookData() - for i := 0; i < 10; i++ { + for i := range 10 { h := fmt.Sprintf("111111111111111111111111111111111111111%d", i) hd.send(h) } diff --git a/pkg/hook/webhook.go b/pkg/hook/webhook.go index 42a662c1a..673866961 100644 --- a/pkg/hook/webhook.go +++ b/pkg/hook/webhook.go @@ -24,7 +24,7 @@ import ( "time" ) -// WebHook structure, implements Hook +// Webhook implements Hook for HTTP requests. type Webhook struct { // Url for the http/s request url string @@ -39,7 +39,7 @@ type Webhook struct { log logintf } -// NewWebhook returns a new WebHook +// NewWebhook returns a new WebHook. func NewWebhook(url, method string, success int, timeout time.Duration, log logintf) *Webhook { return &Webhook{ url: url, @@ -50,12 +50,12 @@ func NewWebhook(url, method string, success int, timeout time.Duration, log logi } } -// Name describes hook, implements Hook.Name +// Name describes hook, implements Hook.Name. func (w *Webhook) Name() string { return "webhook" } -// Do calls webhook.url, implements Hook.Do +// Do calls webhook.url, implements Hook.Do. func (w *Webhook) Do(ctx context.Context, hash string) error { req, err := http.NewRequest(w.method, w.url, nil) if err != nil { diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go index 218098cf6..72c385b94 100644 --- a/pkg/logging/logging.go +++ b/pkg/logging/logging.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package logging provides a logging interface. package logging import ( diff --git a/pkg/pid1/pid1.go b/pkg/pid1/pid1.go index c84633d12..2996da71f 100644 --- a/pkg/pid1/pid1.go +++ b/pkg/pid1/pid1.go @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package pid1 provides a simple way to convert the current process into an +// init-like process. This is useful for containerized applications that need +// to manage child processes. package pid1 import ( @@ -53,6 +56,7 @@ func runInit(firstborn int) (int, error) { for sig := range sigs { if sig != syscall.SIGCHLD { // Pass it on to the real process. + //nolint:forcetypeassert if err := syscall.Kill(firstborn, sig.(syscall.Signal)); err != nil { return 0, err } diff --git a/pkg/pid1/test/fast-exit/main.go b/pkg/pid1/test/fast-exit/main.go index e317e9c0a..053c02ae8 100644 --- a/pkg/pid1/test/fast-exit/main.go +++ b/pkg/pid1/test/fast-exit/main.go @@ -27,14 +27,14 @@ import ( func main() { // In case we come up as pid 1, act as init. if os.Getpid() == 1 { - fmt.Printf("detected pid 1, running as init\n") + fmt.Fprintf(os.Stdout, "detected pid 1, running as init\n") code, err := pid1.ReRun() if err == nil { os.Exit(code) } - fmt.Printf("unhandled pid1 error: %v\n", err) + fmt.Fprintf(os.Stdout, "unhandled pid1 error: %v\n", err) os.Exit(127) } - fmt.Printf("main app\n") + fmt.Fprintf(os.Stdout, "main app\n") os.Exit(42) } diff --git a/pkg/version/version.go b/pkg/version/version.go index 33adb591c..1d011a5b5 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package version contains version information for the current binary. package version +// VERSION is the version of the binary. This can be set by the linker at +// build time. var VERSION = "UNKNOWN" diff --git a/vendor/github.com/golang-jwt/jwt/v4/parser.go b/vendor/github.com/golang-jwt/jwt/v4/parser.go index c0a6f6927..0fc510a0a 100644 --- a/vendor/github.com/golang-jwt/jwt/v4/parser.go +++ b/vendor/github.com/golang-jwt/jwt/v4/parser.go @@ -7,6 +7,8 @@ import ( "strings" ) +const tokenDelimiter = "." + type Parser struct { // If populated, only these methods will be considered valid. // @@ -36,19 +38,21 @@ func NewParser(options ...ParserOption) *Parser { return p } -// Parse parses, validates, verifies the signature and returns the parsed token. -// keyFunc will receive the parsed token and should return the key for validating. +// Parse parses, validates, verifies the signature and returns the parsed token. keyFunc will +// receive the parsed token and should return the key for validating. func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) } -// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims -// interface. This provides default values which can be overridden and allows a caller to use their own type, rather -// than the default MapClaims implementation of Claims. +// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object +// implementing the Claims interface. This provides default values which can be overridden and +// allows a caller to use their own type, rather than the default MapClaims implementation of +// Claims. // -// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims), -// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the -// proper memory for it before passing in the overall claims, otherwise you might run into a panic. +// Note: If you provide a custom claim implementation that embeds one of the standard claims (such +// as RegisteredClaims), make sure that a) you either embed a non-pointer version of the claims or +// b) if you are using a pointer, allocate the proper memory for it before passing in the overall +// claims, otherwise you might run into a panic. func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { token, parts, err := p.ParseUnverified(tokenString, claims) if err != nil { @@ -85,12 +89,17 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} } + // Perform validation + token.Signature = parts[2] + if err := token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { + return token, &ValidationError{Inner: err, Errors: ValidationErrorSignatureInvalid} + } + vErr := &ValidationError{} // Validate Claims if !p.SkipClaimsValidation { if err := token.Claims.Valid(); err != nil { - // If the Claims Valid returned an error, check if it is a validation error, // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set if e, ok := err.(*ValidationError); !ok { @@ -98,22 +107,14 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf } else { vErr = e } + return token, vErr } } - // Perform validation - token.Signature = parts[2] - if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { - vErr.Inner = err - vErr.Errors |= ValidationErrorSignatureInvalid - } - - if vErr.valid() { - token.Valid = true - return token, nil - } + // No errors so far, token is valid. + token.Valid = true - return token, vErr + return token, nil } // ParseUnverified parses the token but doesn't validate the signature. @@ -123,9 +124,10 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf // It's only ever useful in cases where you know the signature is valid (because it has // been checked previously in the stack) and you want to extract values from it. func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) { - parts = strings.Split(tokenString, ".") - if len(parts) != 3 { - return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) + var ok bool + parts, ok = splitToken(tokenString) + if !ok { + return nil, nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) } token = &Token{Raw: tokenString} @@ -175,3 +177,30 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke return token, parts, nil } + +// splitToken splits a token string into three parts: header, claims, and signature. It will only +// return true if the token contains exactly two delimiters and three parts. In all other cases, it +// will return nil parts and false. +func splitToken(token string) ([]string, bool) { + parts := make([]string, 3) + header, remain, ok := strings.Cut(token, tokenDelimiter) + if !ok { + return nil, false + } + parts[0] = header + claims, remain, ok := strings.Cut(remain, tokenDelimiter) + if !ok { + return nil, false + } + parts[1] = claims + // One more cut to ensure the signature is the last part of the token and there are no more + // delimiters. This avoids an issue where malicious input could contain additional delimiters + // causing unecessary overhead parsing tokens. + signature, _, unexpected := strings.Cut(remain, tokenDelimiter) + if unexpected { + return nil, false + } + parts[2] = signature + + return parts, true +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3924192bd..f7421294a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,7 +8,7 @@ github.com/cespare/xxhash/v2 ## explicit; go 1.16 github.com/go-logr/logr github.com/go-logr/logr/funcr -# github.com/golang-jwt/jwt/v4 v4.5.0 +# github.com/golang-jwt/jwt/v4 v4.5.2 ## explicit; go 1.16 github.com/golang-jwt/jwt/v4 # github.com/golang/protobuf v1.5.2