diff --git a/.golangci.yml b/.golangci.yml index feb6fc62..d541999f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -36,6 +36,7 @@ linters-settings: arguments: - fmt.Printf - fmt.Println + - fmt.Fprint - fmt.Fprintf - fmt.Fprintln - os.Stderr.Sync diff --git a/flake.lock b/flake.lock index 9dfbe6f5..2c6b402b 100644 --- a/flake.lock +++ b/flake.lock @@ -18,12 +18,12 @@ }, "flake-schemas": { "locked": { - "lastModified": 1697467827, - "narHash": "sha256-j8SR19V1SRysyJwpOBF4TLuAvAjF5t+gMiboN4gYQDU=", - "rev": "764932025c817d4e500a8d2a4d8c565563923d29", - "revCount": 29, + "lastModified": 1721999734, + "narHash": "sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw=", + "rev": "0a5c42297d870156d9c57d8f99e476b738dcd982", + "revCount": 75, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.2/018b3da8-4cc3-7fbb-8ff7-1588413c53e2/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz?rev=0a5c42297d870156d9c57d8f99e476b738dcd982&revCount=75" }, "original": { "type": "tarball", @@ -53,12 +53,12 @@ }, "nixpkgs": { "locked": { - "lastModified": 1717952948, - "narHash": "sha256-mJi4/gjiwQlSaxjA6AusXBN/6rQRaPCycR7bd8fydnQ=", - "rev": "2819fffa7fa42156680f0d282c60d81e8fb185b7", - "revCount": 631440, + "lastModified": 1735669367, + "narHash": "sha256-tfYRbFhMOnYaM4ippqqid3BaLOXoFNdImrfBfCp4zn0=", + "rev": "edf04b75c13c2ac0e54df5ec5c543e300f76f1c9", + "revCount": 712148, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.631440%2Brev-2819fffa7fa42156680f0d282c60d81e8fb185b7/0190034c-678d-7039-b45c-fa38168f2500/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2411.712148%2Brev-edf04b75c13c2ac0e54df5ec5c543e300f76f1c9/0194235a-7100-7180-896b-1aa5e516625b/source.tar.gz?rev=edf04b75c13c2ac0e54df5ec5c543e300f76f1c9&revCount=712148" }, "original": { "type": "tarball", @@ -67,27 +67,27 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1718811006, - "narHash": "sha256-0Y8IrGhRmBmT7HHXlxxepg2t8j1X90++qRN3lukGaIk=", + "lastModified": 1730741070, + "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "03d771e513ce90147b65fe922d87d3a0356fc125", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1719082008, - "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", + "lastModified": 1730768919, + "narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9693852a2070b398ee123a329e68f0dab5526681", + "rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc", "type": "github" }, "original": { @@ -105,11 +105,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1719259945, - "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", + "lastModified": 1734797603, + "narHash": "sha256-ulZN7ps8nBV31SE+dwkDvKIzvN6hroRY8sYOT0w+E28=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", + "rev": "f0f0dc4920a903c3e08f5bdb9246bb572fcae498", "type": "github" }, "original": { diff --git a/pkg/sync/syncer.go b/pkg/sync/syncer.go index d8c0ea67..cdcbfd2e 100644 --- a/pkg/sync/syncer.go +++ b/pkg/sync/syncer.go @@ -33,9 +33,7 @@ const maxDepth = 8 var dontFixCycles, _ = strconv.ParseBool(os.Getenv("BATON_DONT_FIX_CYCLES")) -var ( - ErrSyncNotComplete = fmt.Errorf("sync exited without finishing") -) +var ErrSyncNotComplete = fmt.Errorf("sync exited without finishing") type Syncer interface { Sync(context.Context) error @@ -971,8 +969,7 @@ func (s *syncer) SyncAssets(ctx context.Context) error { return nil } -// SyncGrantExpansion -// TODO(morgabra) Docs. +// SyncGrantExpansion documentation pending. func (s *syncer) SyncGrantExpansion(ctx context.Context) error { l := ctxzap.Extract(ctx) entitlementGraph := s.state.EntitlementGraph(ctx) diff --git a/pkg/uhttp/body_print.go b/pkg/uhttp/body_print.go new file mode 100644 index 00000000..861e084b --- /dev/null +++ b/pkg/uhttp/body_print.go @@ -0,0 +1,40 @@ +package uhttp + +// Implements a debugging facility for request responses. This changes +// the behavior of `BaseHttpClient` with an unexported flag. +// +// IMPORTANT: This feature is intended for development and debugging purposes only. +// Do not enable in production as it may expose sensitive information in logs. +// +// Usage: +// client := uhttp.NewBaseHttpClient( +// httpClient, +// uhttp.WithPrintBody(true), // Enable response body printing +// ) + +import ( + "errors" + "fmt" + "io" + "os" +) + +type printReader struct { + reader io.Reader +} + +func (pr *printReader) Read(p []byte) (int, error) { + n, err := pr.reader.Read(p) + if n > 0 { + _, merr := fmt.Fprint(os.Stdout, string(p[:n])) + if merr != nil { + return -1, errors.Join(err, merr) + } + } + + return n, err +} + +func wrapPrintBody(body io.Reader) io.Reader { + return &printReader{reader: body} +} diff --git a/pkg/uhttp/wrapper.go b/pkg/uhttp/wrapper.go index 47f4c074..c1a61de0 100644 --- a/pkg/uhttp/wrapper.go +++ b/pkg/uhttp/wrapper.go @@ -10,6 +10,7 @@ import ( "io" "net/http" "net/url" + "os" "syscall" "time" @@ -33,8 +34,6 @@ const ( authorizationHeader = "Authorization" ) -const maxBodySize = 4096 - type WrapperResponse struct { Header http.Header Body []byte @@ -140,8 +139,8 @@ func WithJSONResponse(response interface{}) DoOption { if !IsJSONContentType(contentHeader) { if len(resp.Body) != 0 { - // we want to see the body regardless - return fmt.Errorf("unexpected content type for JSON response: %s. status code: %d. body: «%s»", contentHeader, resp.StatusCode, logBody(resp.Body, maxBodySize)) + // to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead + return fmt.Errorf("unexpected content type for JSON response: %s. status code: %d", contentHeader, resp.StatusCode) } return fmt.Errorf("unexpected content type for JSON response: %s. status code: %d", contentHeader, resp.StatusCode) } @@ -150,7 +149,8 @@ func WithJSONResponse(response interface{}) DoOption { } err := json.Unmarshal(resp.Body, response) if err != nil { - return fmt.Errorf("failed to unmarshal json response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize)) + // to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead + return fmt.Errorf("failed to unmarshal json response: %w. status code: %d", err, resp.StatusCode) } return nil } @@ -164,19 +164,13 @@ func WithAlwaysJSONResponse(response interface{}) DoOption { } err := json.Unmarshal(resp.Body, response) if err != nil { - return fmt.Errorf("failed to unmarshal json response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize)) + // to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead + return fmt.Errorf("failed to unmarshal json response: %w. status code: %d", err, resp.StatusCode) } return nil } } -func logBody(body []byte, size int) string { - if len(body) > size { - return string(body[:size]) + " ..." - } - return string(body) -} - type ErrorResponse interface { Message() string } @@ -190,12 +184,14 @@ func WithErrorResponse(resource ErrorResponse) DoOption { contentHeader := resp.Header.Get(ContentType) if !IsJSONContentType(contentHeader) { - return fmt.Errorf("unexpected content type for JSON error response: %s. status code: %d. body: «%s»", contentHeader, resp.StatusCode, logBody(resp.Body, maxBodySize)) + // to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead + return fmt.Errorf("unexpected content type for JSON error response: %s. status code: %d", contentHeader, resp.StatusCode) } // Decode the JSON response body into the ErrorResponse if err := json.Unmarshal(resp.Body, &resource); err != nil { - return fmt.Errorf("failed to unmarshal JSON error response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize)) + // to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead + return fmt.Errorf("failed to unmarshal JSON error response: %w. status code: %d", err, resp.StatusCode) } // Construct a more detailed error message @@ -341,7 +337,12 @@ func (c *BaseHttpClient) Do(req *http.Request, options ...DoOption) (*http.Respo } // Replace resp.Body with a no-op closer so nobody has to worry about closing the reader. - resp.Body = io.NopCloser(bytes.NewBuffer(body)) + shouldPrint := os.Getenv("BATON_DEBUG_PRINT_RESPONSE_BODY") + if shouldPrint != "" { + resp.Body = io.NopCloser(wrapPrintBody(bytes.NewBuffer(body))) + } else { + resp.Body = io.NopCloser(bytes.NewBuffer(body)) + } wresp := WrapperResponse{ Header: resp.Header,