detached 51f9860

Add DNS resolver validation context

commit
51f98600e96ddd971ea693eb90143216f7529275
author
brice · · 2026-05-01 17:32 UTC
parent
ae10fc7
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +30 -4
M internal/adapters/out/acmelego/issuer.go +4 -4
@@ -123,18 +123,18 @@ func normalizeDNSResolvers(resolvers []string) ([]string, error) {
123123 for i, resolver := range resolvers {
124124 trimmed := strings.TrimSpace(resolver)
125125 if trimmed == "" {
126- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)
126+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q", domain.ErrDNSConfigInvalid, i, trimmed)
127127 }
128128 host, port, err := net.SplitHostPort(trimmed)
129129 if err != nil {
130- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)
130+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: %v", domain.ErrDNSConfigInvalid, i, trimmed, err)
131131 }
132132 if host == "" {
133- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)
133+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: host must not be empty", domain.ErrDNSConfigInvalid, i, trimmed)
134134 }
135135 portNumber, err := strconv.Atoi(port)
136136 if err != nil || portNumber < 1 || portNumber > 65535 {
137- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)
137+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: port must be a number between 1 and 65535", domain.ErrDNSConfigInvalid, i, trimmed)
138138 }
139139 normalized[i] = trimmed
140140 }
@@ -123,18 +123,18 @@ func normalizeDNSResolvers(resolvers []string) ([]string, error) {
123 for i, resolver := range resolvers {123 for i, resolver := range resolvers {
124 trimmed := strings.TrimSpace(resolver)124 trimmed := strings.TrimSpace(resolver)
125 if trimmed == "" {125 if trimmed == "" {
126- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)126+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q", domain.ErrDNSConfigInvalid, i, trimmed)
127 }127 }
128 host, port, err := net.SplitHostPort(trimmed)128 host, port, err := net.SplitHostPort(trimmed)
129 if err != nil {129 if err != nil {
130- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)130+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: %v", domain.ErrDNSConfigInvalid, i, trimmed, err)
131 }131 }
132 if host == "" {132 if host == "" {
133- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)133+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: host must not be empty", domain.ErrDNSConfigInvalid, i, trimmed)
134 }134 }
135 portNumber, err := strconv.Atoi(port)135 portNumber, err := strconv.Atoi(port)
136 if err != nil || portNumber < 1 || portNumber > 65535 {136 if err != nil || portNumber < 1 || portNumber > 65535 {
137- return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry", domain.ErrDNSConfigInvalid)137+ return nil, fmt.Errorf("acmelego: %w: invalid DNSResolvers entry at index %d value %q: port must be a number between 1 and 65535", domain.ErrDNSConfigInvalid, i, trimmed)
138 }138 }
139 normalized[i] = trimmed139 normalized[i] = trimmed
140 }140 }
M internal/adapters/out/acmelego/issuer_test.go +26 -0
@@ -95,6 +95,8 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) {
9595 require.Error(t, err)
9696 assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid)
9797 assert.Contains(t, err.Error(), "invalid DNSResolvers entry")
98+ assert.Contains(t, err.Error(), "index 0")
99+ assert.Contains(t, err.Error(), "1.1.1.1:abc")
98100
99101 _, err = NewIssuer(Config{
100102 Email: "test@example.com",
@@ -122,6 +124,30 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) {
122124 assert.Equal(t, []string{"1.1.1.1:53", "8.8.8.8:53"}, issuer.cfg.DNSResolvers)
123125 }
124126
127+func TestNormalizeDNSResolversErrorsIncludeEntryContext(t *testing.T) {
128+ tests := []struct {
129+ name string
130+ resolver string
131+ want string
132+ }{
133+ {name: "empty after trimming", resolver: " \t ", want: "value \"\""},
134+ {name: "missing port", resolver: "1.1.1.1", want: "value \"1.1.1.1\""},
135+ {name: "empty host", resolver: ":53", want: "value \":53\""},
136+ {name: "invalid port", resolver: "1.1.1.1:abc", want: "value \"1.1.1.1:abc\""},
137+ }
138+
139+ for _, tt := range tests {
140+ t.Run(tt.name, func(t *testing.T) {
141+ _, err := normalizeDNSResolvers([]string{"8.8.8.8:53", tt.resolver})
142+ require.Error(t, err)
143+ assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid)
144+ assert.Contains(t, err.Error(), "invalid DNSResolvers entry")
145+ assert.Contains(t, err.Error(), "index 1")
146+ assert.Contains(t, err.Error(), tt.want)
147+ })
148+ }
149+}
150+
125151 func TestNewIssuerRejectsNonHTTPSURL(t *testing.T) {
126152 _, err := NewIssuer(Config{
127153 Email: "test@example.com",
@@ -95,6 +95,8 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) {
95 require.Error(t, err)95 require.Error(t, err)
96 assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid)96 assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid)
97 assert.Contains(t, err.Error(), "invalid DNSResolvers entry")97 assert.Contains(t, err.Error(), "invalid DNSResolvers entry")
98+ assert.Contains(t, err.Error(), "index 0")
99+ assert.Contains(t, err.Error(), "1.1.1.1:abc")
98 100
99 _, err = NewIssuer(Config{101 _, err = NewIssuer(Config{
100 Email: "test@example.com",102 Email: "test@example.com",
@@ -122,6 +124,30 @@ func TestNewIssuerValidatesCloudflareDNSConfig(t *testing.T) {
122 assert.Equal(t, []string{"1.1.1.1:53", "8.8.8.8:53"}, issuer.cfg.DNSResolvers)124 assert.Equal(t, []string{"1.1.1.1:53", "8.8.8.8:53"}, issuer.cfg.DNSResolvers)
123 }125 }
124 126
127+func TestNormalizeDNSResolversErrorsIncludeEntryContext(t *testing.T) {
128+ tests := []struct {
129+ name string
130+ resolver string
131+ want string
132+ }{
133+ {name: "empty after trimming", resolver: " \t ", want: "value \"\""},
134+ {name: "missing port", resolver: "1.1.1.1", want: "value \"1.1.1.1\""},
135+ {name: "empty host", resolver: ":53", want: "value \":53\""},
136+ {name: "invalid port", resolver: "1.1.1.1:abc", want: "value \"1.1.1.1:abc\""},
137+ }
138+
139+ for _, tt := range tests {
140+ t.Run(tt.name, func(t *testing.T) {
141+ _, err := normalizeDNSResolvers([]string{"8.8.8.8:53", tt.resolver})
142+ require.Error(t, err)
143+ assert.ErrorIs(t, err, domain.ErrDNSConfigInvalid)
144+ assert.Contains(t, err.Error(), "invalid DNSResolvers entry")
145+ assert.Contains(t, err.Error(), "index 1")
146+ assert.Contains(t, err.Error(), tt.want)
147+ })
148+ }
149+}
150+
125 func TestNewIssuerRejectsNonHTTPSURL(t *testing.T) {151 func TestNewIssuerRejectsNonHTTPSURL(t *testing.T) {
126 _, err := NewIssuer(Config{152 _, err := NewIssuer(Config{
127 Email: "test@example.com",153 Email: "test@example.com",

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