detached be42624

fix: address current review findings

commit
be4262411966cf9d22b553605ab533b7375ce4e8
author
brice · · 2026-04-08 17:59 UTC
parent
18a333b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +27 -20
M internal/adapters/in/cli/remote/config.go +6 -2
@@ -97,7 +97,9 @@ func migrateClientConfig(config *ClientConfig) {
9797 return
9898 }
9999
100- fmt.Fprintf(os.Stderr, "Notice: migrated [client] config from gordon.toml to 'default' remote. The [client] section is deprecated; use 'gordon remotes' instead.\n")
100+ if _, err := fmt.Fprintf(os.Stderr, "Notice: migrated [client] config from gordon.toml to 'default' remote. The [client] section is deprecated; use 'gordon remotes' instead.\n"); err != nil {
101+ _, _ = os.Stderr.WriteString("Warning: failed to write remote migration notice\n")
102+ }
101103
102104 config.Remotes["default"] = RemoteEntry{
103105 URL: legacy.Client.Remote,
@@ -111,7 +113,9 @@ func migrateClientConfig(config *ClientConfig) {
111113 }
112114
113115 if err := SaveRemotes("", config); err != nil {
114- fmt.Fprintf(os.Stderr, "Warning: failed to save migrated remotes: %v\n", err)
116+ if _, writeErr := fmt.Fprintf(os.Stderr, "Warning: failed to save migrated remotes: %v\n", err); writeErr != nil {
117+ _, _ = os.Stderr.WriteString("Warning: failed to write remote migration warning\n")
118+ }
115119 }
116120 }
117121
@@ -97,7 +97,9 @@ func migrateClientConfig(config *ClientConfig) {
97 return97 return
98 }98 }
99 99
100- fmt.Fprintf(os.Stderr, "Notice: migrated [client] config from gordon.toml to 'default' remote. The [client] section is deprecated; use 'gordon remotes' instead.\n")100+ if _, err := fmt.Fprintf(os.Stderr, "Notice: migrated [client] config from gordon.toml to 'default' remote. The [client] section is deprecated; use 'gordon remotes' instead.\n"); err != nil {
101+ _, _ = os.Stderr.WriteString("Warning: failed to write remote migration notice\n")
102+ }
101 103
102 config.Remotes["default"] = RemoteEntry{104 config.Remotes["default"] = RemoteEntry{
103 URL: legacy.Client.Remote,105 URL: legacy.Client.Remote,
@@ -111,7 +113,9 @@ func migrateClientConfig(config *ClientConfig) {
111 }113 }
112 114
113 if err := SaveRemotes("", config); err != nil {115 if err := SaveRemotes("", config); err != nil {
114- fmt.Fprintf(os.Stderr, "Warning: failed to save migrated remotes: %v\n", err)116+ if _, writeErr := fmt.Fprintf(os.Stderr, "Warning: failed to save migrated remotes: %v\n", err); writeErr != nil {
117+ _, _ = os.Stderr.WriteString("Warning: failed to write remote migration warning\n")
118+ }
115 }119 }
116 }120 }
117 121
M internal/adapters/in/http/httphelper/localhost_test.go +4 -3
@@ -5,6 +5,8 @@ import (
55 "net/http/httptest"
66 "testing"
77
8+ "github.com/stretchr/testify/assert"
9+
810 "github.com/bnema/gordon/internal/adapters/in/http/httphelper"
911 )
1012
@@ -25,9 +27,8 @@ func TestIsLocalhostRequest(t *testing.T) {
2527 t.Run(tt.name, func(t *testing.T) {
2628 req := httptest.NewRequest(http.MethodGet, "/", nil)
2729 req.RemoteAddr = tt.remoteAddr
28- if got := httphelper.IsLocalhostRequest(req); got != tt.want {
29- t.Errorf("IsLocalhostRequest(%q) = %v, want %v", tt.remoteAddr, got, tt.want)
30- }
30+ got := httphelper.IsLocalhostRequest(req)
31+ assert.Equal(t, tt.want, got, "IsLocalhostRequest(%q)", tt.remoteAddr)
3132 })
3233 }
3334 }
@@ -5,6 +5,8 @@ import (
5 "net/http/httptest"5 "net/http/httptest"
6 "testing"6 "testing"
7 7
8+ "github.com/stretchr/testify/assert"
9+
8 "github.com/bnema/gordon/internal/adapters/in/http/httphelper"10 "github.com/bnema/gordon/internal/adapters/in/http/httphelper"
9 )11 )
10 12
@@ -25,9 +27,8 @@ func TestIsLocalhostRequest(t *testing.T) {
25 t.Run(tt.name, func(t *testing.T) {27 t.Run(tt.name, func(t *testing.T) {
26 req := httptest.NewRequest(http.MethodGet, "/", nil)28 req := httptest.NewRequest(http.MethodGet, "/", nil)
27 req.RemoteAddr = tt.remoteAddr29 req.RemoteAddr = tt.remoteAddr
28- if got := httphelper.IsLocalhostRequest(req); got != tt.want {30+ got := httphelper.IsLocalhostRequest(req)
29- t.Errorf("IsLocalhostRequest(%q) = %v, want %v", tt.remoteAddr, got, tt.want)31+ assert.Equal(t, tt.want, got, "IsLocalhostRequest(%q)", tt.remoteAddr)
30- }
31 })32 })
32 }33 }
33 }34 }
M internal/adapters/in/http/httphelper/trust.go +5 -0
@@ -16,6 +16,11 @@ var localhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
1616 func ParseTrustedProxies(proxies []string) []*net.IPNet {
1717 var nets []*net.IPNet
1818 for _, proxy := range proxies {
19+ proxy = strings.TrimSpace(proxy)
20+ if proxy == "" {
21+ continue
22+ }
23+
1924 // Try parsing as CIDR
2025 _, ipNet, err := net.ParseCIDR(proxy)
2126 if err == nil {
@@ -16,6 +16,11 @@ var localhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
16 func ParseTrustedProxies(proxies []string) []*net.IPNet {16 func ParseTrustedProxies(proxies []string) []*net.IPNet {
17 var nets []*net.IPNet17 var nets []*net.IPNet
18 for _, proxy := range proxies {18 for _, proxy := range proxies {
19+ proxy = strings.TrimSpace(proxy)
20+ if proxy == "" {
21+ continue
22+ }
23+
19 // Try parsing as CIDR24 // Try parsing as CIDR
20 _, ipNet, err := net.ParseCIDR(proxy)25 _, ipNet, err := net.ParseCIDR(proxy)
21 if err == nil {26 if err == nil {
M internal/adapters/in/http/middleware/accesslog_test.go +11 -5
@@ -2,6 +2,7 @@ package middleware
22
33 import (
44 "fmt"
5+ "io"
56 "net/http"
67 "net/http/httptest"
78 "testing"
@@ -65,11 +66,16 @@ func TestAccessLogger_RedactsSensitiveQueryParams(t *testing.T) {
6566 handler := AccessLogger(mock, false, log, nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
6667 w.WriteHeader(http.StatusOK)
6768 }))
68-
69- req := httptest.NewRequest(http.MethodGet, "/oauth/callback?code=secret-code&state=ok&token=secret-token", nil)
70- rw := httptest.NewRecorder()
71-
72- handler.ServeHTTP(rw, req)
69+ server := httptest.NewServer(handler)
70+ defer server.Close()
71+
72+ resp, err := http.Get(server.URL + "/oauth/callback?code=secret-code&state=ok&token=secret-token")
73+ require.NoError(t, err)
74+ defer func() {
75+ assert.NoError(t, resp.Body.Close())
76+ }()
77+ _, err = io.Copy(io.Discard, resp.Body)
78+ require.NoError(t, err)
7379
7480 require.Len(t, mock.entries, 1)
7581 assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]", mock.entries[0].Query)
@@ -2,6 +2,7 @@ package middleware
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5+ "io"
5 "net/http"6 "net/http"
6 "net/http/httptest"7 "net/http/httptest"
7 "testing"8 "testing"
@@ -65,11 +66,16 @@ func TestAccessLogger_RedactsSensitiveQueryParams(t *testing.T) {
65 handler := AccessLogger(mock, false, log, nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {66 handler := AccessLogger(mock, false, log, nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66 w.WriteHeader(http.StatusOK)67 w.WriteHeader(http.StatusOK)
67 }))68 }))
68-69+ server := httptest.NewServer(handler)
69- req := httptest.NewRequest(http.MethodGet, "/oauth/callback?code=secret-code&state=ok&token=secret-token", nil)70+ defer server.Close()
70- rw := httptest.NewRecorder()71+
71-72+ resp, err := http.Get(server.URL + "/oauth/callback?code=secret-code&state=ok&token=secret-token")
72- handler.ServeHTTP(rw, req)73+ require.NoError(t, err)
74+ defer func() {
75+ assert.NoError(t, resp.Body.Close())
76+ }()
77+ _, err = io.Copy(io.Discard, resp.Body)
78+ require.NoError(t, err)
73 79
74 require.Len(t, mock.entries, 1)80 require.Len(t, mock.entries, 1)
75 assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]", mock.entries[0].Query)81 assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]", mock.entries[0].Query)
M internal/adapters/in/http/proxy/forwarder.go +1 -6
@@ -202,7 +202,7 @@ func newReverseProxy(targetURL *url.URL, originalHost string, transport http.Rou
202202 // SetXForwarded() unconditionally sets it based on the incoming scheme
203203 // (HTTP between proxies), but when a trusted proxy already sent the
204204 // real client proto, we must honor it.
205- if existingProto != "" && isTrustedSource(incomingReq, trustedNets) {
205+ if existingProto != "" && gordonhttp.IsTrustedSource(incomingReq, trustedNets) {
206206 pr.Out.Header.Set("X-Forwarded-Proto", existingProto)
207207 }
208208 },
@@ -212,11 +212,6 @@ func newReverseProxy(targetURL *url.URL, originalHost string, transport http.Rou
212212 }
213213 }
214214
215-// isTrustedSource reports whether the request's remote address is in trustedNets.
216-func isTrustedSource(r *http.Request, trustedNets []*net.IPNet) bool {
217- return gordonhttp.IsTrustedSource(r, trustedNets)
218-}
219-
220215 // modifyResponse returns a function that adds proxy headers and enforces response size limits.
221216 func modifyResponse(maxResponseSize int64) func(*http.Response) error {
222217 return func(resp *http.Response) error {
@@ -202,7 +202,7 @@ func newReverseProxy(targetURL *url.URL, originalHost string, transport http.Rou
202 // SetXForwarded() unconditionally sets it based on the incoming scheme202 // SetXForwarded() unconditionally sets it based on the incoming scheme
203 // (HTTP between proxies), but when a trusted proxy already sent the203 // (HTTP between proxies), but when a trusted proxy already sent the
204 // real client proto, we must honor it.204 // real client proto, we must honor it.
205- if existingProto != "" && isTrustedSource(incomingReq, trustedNets) {205+ if existingProto != "" && gordonhttp.IsTrustedSource(incomingReq, trustedNets) {
206 pr.Out.Header.Set("X-Forwarded-Proto", existingProto)206 pr.Out.Header.Set("X-Forwarded-Proto", existingProto)
207 }207 }
208 },208 },
@@ -212,11 +212,6 @@ func newReverseProxy(targetURL *url.URL, originalHost string, transport http.Rou
212 }212 }
213 }213 }
214 214
215-// isTrustedSource reports whether the request's remote address is in trustedNets.
216-func isTrustedSource(r *http.Request, trustedNets []*net.IPNet) bool {
217- return gordonhttp.IsTrustedSource(r, trustedNets)
218-}
219-
220 // modifyResponse returns a function that adds proxy headers and enforces response size limits.215 // modifyResponse returns a function that adds proxy headers and enforces response size limits.
221 func modifyResponse(maxResponseSize int64) func(*http.Response) error {216 func modifyResponse(maxResponseSize int64) func(*http.Response) error {
222 return func(resp *http.Response) error {217 return func(resp *http.Response) error {
M internal/usecase/container/service.go +0 -4
@@ -835,10 +835,6 @@ func isPullFailure(err error) bool {
835835 return false
836836 }
837837
838- if errors.Is(err, domain.ErrImagePullFailed) {
839- return true
840- }
841-
842838 msg := strings.ToLower(err.Error())
843839 return strings.Contains(msg, "failed to pull image") ||
844840 strings.Contains(msg, "pull access denied")
@@ -835,10 +835,6 @@ func isPullFailure(err error) bool {
835 return false835 return false
836 }836 }
837 837
838- if errors.Is(err, domain.ErrImagePullFailed) {
839- return true
840- }
841-
842 msg := strings.ToLower(err.Error())838 msg := strings.ToLower(err.Error())
843 return strings.Contains(msg, "failed to pull image") ||839 return strings.Contains(msg, "failed to pull image") ||
844 strings.Contains(msg, "pull access denied")840 strings.Contains(msg, "pull access denied")

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close