From bd5d3b20daeb120b8fecb02d214e4bf3dae9bbcb Mon Sep 17 00:00:00 2001 From: Frederic Branczyk Date: Fri, 5 Dec 2025 14:57:05 +0100 Subject: [PATCH 1/2] Add ability to attach arbitrary headers --- cmd/parca-debuginfo/main.go | 57 ++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/cmd/parca-debuginfo/main.go b/cmd/parca-debuginfo/main.go index 3198b58..c3f2cf2 100644 --- a/cmd/parca-debuginfo/main.go +++ b/cmd/parca-debuginfo/main.go @@ -43,6 +43,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" ) const ( @@ -53,16 +54,17 @@ type flags struct { LogLevel string `kong:"enum='error,warn,info,debug',help='Log level.',default='info'"` Upload struct { - StoreAddress string `kong:"required,help='gRPC address to sends symbols to.'"` - BearerToken string `kong:"help='Bearer token to authenticate with store.',env='PARCA_DEBUGINFO_BEARER_TOKEN'"` - BearerTokenFile string `kong:"help='File to read bearer token from to authenticate with store.'"` - Insecure bool `kong:"help='Send gRPC requests via plaintext instead of TLS.'"` - InsecureSkipVerify bool `kong:"help='Skip TLS certificate verification.'"` - NoExtract bool `kong:"help='Do not extract debug information from binaries, just upload the binary as is.'"` - NoInitiate bool `kong:"help='Do not initiate the upload, just check if it should be initiated.'"` - Force bool `kong:"help='Force upload even if the Build ID is already uploaded.'"` - Type string `kong:"enum='debuginfo,executable,sources',help='Type of the debug information to upload.',default='debuginfo'"` - BuildID string `kong:"help='Build ID of the binary to upload.'"` + StoreAddress string `kong:"required,help='gRPC address to sends symbols to.'"` + BearerToken string `kong:"help='Bearer token to authenticate with store.',env='PARCA_DEBUGINFO_BEARER_TOKEN'"` + BearerTokenFile string `kong:"help='File to read bearer token from to authenticate with store.'"` + Insecure bool `kong:"help='Send gRPC requests via plaintext instead of TLS.'"` + InsecureSkipVerify bool `kong:"help='Skip TLS certificate verification.'"` + GRPCHeaders map[string]string `kong:"help='Additional gRPC headers to send with each request (key=value pairs).'"` + NoExtract bool `kong:"help='Do not extract debug information from binaries, just upload the binary as is.'"` + NoInitiate bool `kong:"help='Do not initiate the upload, just check if it should be initiated.'"` + Force bool `kong:"help='Force upload even if the Build ID is already uploaded.'"` + Type string `kong:"enum='debuginfo,executable,sources',help='Type of the debug information to upload.',default='debuginfo'"` + BuildID string `kong:"help='Build ID of the binary to upload.'"` Path string `kong:"required,arg,name='path',help='Paths to upload.',type:'path'"` } `cmd:"" help:"Upload debug information files."` @@ -429,10 +431,21 @@ func grpcConn(reg prometheus.Registerer, flags flags) (*grpc.ClientConn, error) met.EnableClientHandlingTimeHistogram() reg.MustRegister(met) + unaryInterceptors := []grpc.UnaryClientInterceptor{ + met.UnaryClientInterceptor(), + } + streamInterceptors := []grpc.StreamClientInterceptor{} + + if len(flags.Upload.GRPCHeaders) > 0 { + unaryInterceptors = append([]grpc.UnaryClientInterceptor{ + customHeadersUnaryInterceptor(flags.Upload.GRPCHeaders)}, unaryInterceptors...) + streamInterceptors = append([]grpc.StreamClientInterceptor{ + customHeadersStreamInterceptor(flags.Upload.GRPCHeaders)}, streamInterceptors...) + } + opts := []grpc.DialOption{ - grpc.WithUnaryInterceptor( - met.UnaryClientInterceptor(), - ), + grpc.WithChainUnaryInterceptor(unaryInterceptors...), + grpc.WithChainStreamInterceptor(streamInterceptors...), } if flags.Upload.Insecure { opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -480,6 +493,24 @@ func (t *perRequestBearerToken) RequireTransportSecurity() bool { return !t.insecure } +func customHeadersUnaryInterceptor(headers map[string]string) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + for key, value := range headers { + ctx = metadata.AppendToOutgoingContext(ctx, key, value) + } + return invoker(ctx, method, req, reply, cc, opts...) + } +} + +func customHeadersStreamInterceptor(headers map[string]string) grpc.StreamClientInterceptor { + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + for key, value := range headers { + ctx = metadata.AppendToOutgoingContext(ctx, key, value) + } + return streamer(ctx, desc, cc, method, opts...) + } +} + func uploadViaSignedURL(ctx context.Context, url string, r io.Reader) error { req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, r) if err != nil { From 05af7bb438e16d8ef4714cd8eb11c9dda68c61aa Mon Sep 17 00:00:00 2001 From: Frederic Branczyk Date: Fri, 5 Dec 2025 16:06:17 +0100 Subject: [PATCH 2/2] *: Upgrade golangci-lint --- .github/workflows/build.yml | 2 +- .golangci.yml | 187 ++++++++++++++++++++++++++---------- cmd/parca-debuginfo/main.go | 29 ++++-- 3 files changed, 156 insertions(+), 62 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6747a94..bb41032 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,4 +63,4 @@ jobs: run: make vet - name: Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 diff --git a/.golangci.yml b/.golangci.yml index 1d582a4..42d05c0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,21 +1,87 @@ -run: - deadline: 5m - +version: "2" linters: - presets: - - bugs - - comment - - error - - format - - import - - metalinter - - performance - - style - - unused + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - containedctx + - contextcheck + - copyloopvar + - decorder + - depguard + - dogsled + - dupl + - dupword + - durationcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exptostd + - fatcontext + - forbidigo + - forcetypeassert + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoinits + - gochecksumtype + - goconst + - gocritic + - godot + - goheader + - gomodguard + - goprintffuncname + - gosec + - gosmopolitan + - grouper + - iface + - importas + - inamedparam + - interfacebloat + - intrange + - loggercheck + - makezero + - mirror + - misspell + - mnd + - musttag + - nakedret + - nilerr + - nilnesserr + - nilnil + - noctx + - nolintlint + - nonamedreturns + - nosprintfhostport + - perfsprint + - prealloc + - predeclared + - promlinter + - protogetter + - reassign + - recvcheck + - revive + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - staticcheck + - tagalign + - tagliatelle + - testifylint + - tparallel + - unconvert + - unparam + - usestdlibvars + - wastedassign + - whitespace + - zerologlint disable: + - err113 - exhaustruct - funlen - - gci - gochecknoglobals - godox - gomoddirectives @@ -27,41 +93,60 @@ linters: - varnamelen - wrapcheck - wsl - - err113 - -issues: - exclude-rules: - - path: _test.go - linters: - - errcheck - -linters-settings: - depguard: - rules: - Main: - deny: - - pkg: sync/atomic - desc: Use go.uber.org/atomic instead of sync/atomic - - pkg: github.com/stretchr/testify/assert - desc: Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert - - pkg: github.com/go-kit/kit/log - desc: Use github.com/go-kit/log instead of github.com/go-kit/kit/log - - pkg: github.com/pkg/errors - desc: Use fmt.Errorf instead - errcheck: - exclude-functions: ./.errcheck_excludes.txt - goimports: - local-prefixes: github.com/parca-dev/parca-debuginfo - gofumpt: - extra-rules: true - misspell: - locale: US - revive: + settings: + cyclop: + max-complexity: 15 + depguard: + rules: + Main: + deny: + - pkg: sync/atomic + desc: Use go.uber.org/atomic instead of sync/atomic + - pkg: github.com/stretchr/testify/assert + desc: Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert + - pkg: github.com/go-kit/kit/log + desc: Use github.com/go-kit/log instead of github.com/go-kit/kit/log + - pkg: github.com/pkg/errors + desc: Use fmt.Errorf instead + errcheck: + exclude-functions: + - (github.com/go-kit/log.Logger).Log + misspell: + locale: US + revive: + rules: + - name: unexported-return + severity: warning + disabled: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling rules: - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return - - name: unexported-return - severity: warning - disabled: true - cyclop: - # The maximal code complexity to report. - max-complexity: 15 + - linters: + - errcheck + path: _test.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - gofumpt + - goimports + settings: + gofumpt: + extra-rules: true + goimports: + local-prefixes: + - github.com/parca-dev/parca-debuginfo + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/cmd/parca-debuginfo/main.go b/cmd/parca-debuginfo/main.go index c3f2cf2..1cb52ad 100644 --- a/cmd/parca-debuginfo/main.go +++ b/cmd/parca-debuginfo/main.go @@ -47,7 +47,8 @@ import ( ) const ( - LogLevelDebug = "debug" + LogLevelDebug = "debug" + headerKeyValuePairElements = 2 ) type flags struct { @@ -437,10 +438,14 @@ func grpcConn(reg prometheus.Registerer, flags flags) (*grpc.ClientConn, error) streamInterceptors := []grpc.StreamClientInterceptor{} if len(flags.Upload.GRPCHeaders) > 0 { - unaryInterceptors = append([]grpc.UnaryClientInterceptor{ - customHeadersUnaryInterceptor(flags.Upload.GRPCHeaders)}, unaryInterceptors...) - streamInterceptors = append([]grpc.StreamClientInterceptor{ - customHeadersStreamInterceptor(flags.Upload.GRPCHeaders)}, streamInterceptors...) + unaryInterceptors = append( + []grpc.UnaryClientInterceptor{customHeadersUnaryInterceptor(flags.Upload.GRPCHeaders)}, + unaryInterceptors..., + ) + streamInterceptors = append( + []grpc.StreamClientInterceptor{customHeadersStreamInterceptor(flags.Upload.GRPCHeaders)}, + streamInterceptors..., + ) } opts := []grpc.DialOption{ @@ -495,19 +500,23 @@ func (t *perRequestBearerToken) RequireTransportSecurity() bool { func customHeadersUnaryInterceptor(headers map[string]string) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + kvPairs := make([]string, 0, len(headers)*headerKeyValuePairElements) for key, value := range headers { - ctx = metadata.AppendToOutgoingContext(ctx, key, value) + kvPairs = append(kvPairs, key, value) } - return invoker(ctx, method, req, reply, cc, opts...) + newCtx := metadata.AppendToOutgoingContext(ctx, kvPairs...) + return invoker(newCtx, method, req, reply, cc, opts...) } } func customHeadersStreamInterceptor(headers map[string]string) grpc.StreamClientInterceptor { return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + kvPairs := make([]string, 0, len(headers)*headerKeyValuePairElements) for key, value := range headers { - ctx = metadata.AppendToOutgoingContext(ctx, key, value) + kvPairs = append(kvPairs, key, value) } - return streamer(ctx, desc, cc, method, opts...) + newCtx := metadata.AppendToOutgoingContext(ctx, kvPairs...) + return streamer(newCtx, desc, cc, method, opts...) } } @@ -618,7 +627,7 @@ func getNoteHexString(sectionBytes []byte, name string, noteType uint32) (string // read descsz and compute the last index of the note data dataSize := binary.LittleEndian.Uint32(sectionBytes[idx-4 : idx]) - idxDataEnd := uint64(idxDataStart) + uint64(dataSize) //nolint:gosec + idxDataEnd := uint64(idxDataStart) + uint64(dataSize) // Check sanity (64 is totally arbitrary, as we only use it for Linux ID and Build ID) if idxDataEnd > uint64(len(sectionBytes)) || dataSize > 64 {