Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) 🐛 Fix: Prevent nil errors in setupLog.Error to ensure proper logging and add sanity check #1599

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,13 @@ help-extended: #HELP Display extended help.
#SECTION Development

.PHONY: lint
lint: $(GOLANGCI_LINT) #HELP Run golangci linter.
lint: lint-custom $(GOLANGCI_LINT) #HELP Run golangci linter.
$(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS)

.PHONY: lint-custom
lint-custom: custom-linter-build
go vet -vettool=./bin/custom-linter ./...
Comment on lines +101 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

golangci-lint should already be running go vet (assuming the govet linter is enabled). So I think we get this part automatically if we add the directory where we build the linter binary to the path for the golangci-lint run command.


.PHONY: tidy
tidy: #HELP Update dependencies.
# Force tidy to use the version already in go.mod
Expand Down Expand Up @@ -285,6 +289,11 @@ BINARIES=operator-controller
$(BINARIES):
go build $(GO_BUILD_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/$@ ./cmd/$@

.PHONY: custom-linter-build
custom-linter-build:
cd ./hack/ci/custom-linters/cmd/ && go build -o ../../../../bin/custom-linter


.PHONY: build-deps
build-deps: manifests generate fmt vet

Expand Down
4 changes: 2 additions & 2 deletions catalogd/cmd/catalogd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ func main() {
}

if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {
setupLog.Error(nil, "unable to configure TLS certificates: tls-cert and tls-key flags must be used together")
setupLog.Error(fmt.Errorf("unable to configure TLS certificates"), "tls-cert and tls-key flags must be used together")
os.Exit(1)
}

if metricsAddr != "" && certFile == "" && keyFile == "" {
setupLog.Error(nil, "metrics-bind-address requires tls-cert and tls-key flags to be set")
setupLog.Error(fmt.Errorf("unable to configure metrics-bind-address"), "metrics-bind-address requires tls-cert and tls-key flags to be set")
os.Exit(1)
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/operator-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ func main() {
}

if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {
setupLog.Error(nil, "unable to configure TLS certificates: tls-cert and tls-key flags must be used together")
setupLog.Error(fmt.Errorf("unable to configure TLS certificates"), "tls-cert and tls-key flags must be used together")
os.Exit(1)
}

if metricsAddr != "" && certFile == "" && keyFile == "" {
setupLog.Error(nil, "metrics-bind-address requires tls-cert and tls-key flags to be set")
setupLog.Error(fmt.Errorf("unable to configure metrics-bind-address"), "metrics-bind-address requires tls-cert and tls-key flags to be set")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use errors.New since we aren't using the format string capabilities of fmt.Errorf.

os.Exit(1)
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.10.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/tools v0.28.0
gopkg.in/yaml.v2 v2.4.0
helm.sh/helm/v3 v3.16.4
k8s.io/api v0.31.4
Expand Down Expand Up @@ -230,7 +231,6 @@ require (
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.28.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
Expand Down
16 changes: 16 additions & 0 deletions hack/ci/custom-linters/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"github.com/github.com/operator-framework/operator-controller/custom-linters/setuplognilerrorcheck"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/unitchecker"
)

// Define the custom Linters implemented in the project
var customLinters = []*analysis.Analyzer{
setuplognilerrorcheck.SetupLogNilErrorCheck,
}

func main() {
unitchecker.Main(customLinters...)
}
5 changes: 5 additions & 0 deletions hack/ci/custom-linters/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/github.com/operator-framework/operator-controller/custom-linters

go 1.23.4

require golang.org/x/tools v0.29.0
8 changes: 8 additions & 0 deletions hack/ci/custom-linters/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package setuplognilerrorcheck

import (
"go/ast"
"golang.org/x/tools/go/analysis"
)

var SetupLogNilErrorCheck = &analysis.Analyzer{
Name: "setuplognilerrorcheck",
Doc: "check for setupLog.Error(nil, ...) calls with nil as the first argument",
Run: runSetupLogNilErrorCheck,
}

func runSetupLogNilErrorCheck(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
ast.Inspect(f, func(n ast.Node) bool {
expr, ok := n.(*ast.CallExpr)
if !ok {
return true
}

selectorExpr, ok := expr.Fun.(*ast.SelectorExpr)
if !ok || selectorExpr.Sel.Name != "Error" {
return true
}

i, ok := selectorExpr.X.(*ast.Ident)
if !ok || i.Name != "setupLog" {
return true
}
Comment on lines +28 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check should:

  1. Look for all instances of logr.Logger (not just setupLog)
  2. Of those, find when Error is called.
  3. Of those, find when the first argument is nil.

That way, we'll find all cases where this is an issue, and we'll protect ourselves in the case that we decide to change the name of setupLog.


if len(expr.Args) > 0 {
if arg, ok := expr.Args[0].(*ast.Ident); ok && arg.Name == "nil" {
pass.Reportf(expr.Pos(), "Avoid using 'setupLog.Error(nil, ...)'. Instead, use 'errors.New()' "+
"or 'fmt.Errorf()' to ensure logs are created. Using 'nil' for errors can result in silent "+
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely, we should just say "pass a non-nil error". If we suggest fmt.Errorf(), that may incorrect lead to a contributor burying useful structured log context inside the error message, when what they should do is:

l.Error(errors.New("could not do thing"), "thing", foo, "otherThing", bar)

"failures, making bugs harder to detect.")
}
}
return true
})
}
return nil, nil
}
Loading