Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
caddyfile: Introduce basic linting and fmt check (#3923)
* caddyfile: Introduce basic linting and fmt check This will help encourage people to keep their Caddyfiles tidy. * Remove unrelated tests I am not sure that testing the output of warnings here is quite the right idea; these tests are just for syntax and parsing success.
Matt Holt committed
Jan 4, 2021 at 11:11 UTC
c8557dc00bd93ce8ecf0bb724856a320e129c71b
7 files changed
+40
-54
caddyconfig/caddyfile/adapter.go
+11
-4
@@ -15,6 +15,7 @@
15
package caddyfile
16
17
import (
18
+ "bytes"
19
"encoding/json"
20
"fmt"
21
@@ -51,11 +52,17 @@ func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []c
52
return nil, warnings, err
53
}
54
54
- marshalFunc := json.Marshal
55
- if options["pretty"] == "true" {
56
- marshalFunc = caddyconfig.JSONIndent
55
+ // lint check: see if input was properly formatted; sometimes messy files files parse
56
+ // successfully but result in logical errors because the Caddyfile is a bad format
57
+ // TODO: also perform this check on imported files
58
+ if !bytes.Equal(Format(body), body) {
59
+ warnings = append(warnings, caddyconfig.Warning{
60
+ File: filename,
61
+ Message: "file is not formatted with 'caddy fmt'",
62
+ })
63
}
58
- result, err := marshalFunc(cfg)
64
+
65
+ result, err := json.Marshal(cfg)
66
67
return result, warnings, err
68
}
caddyconfig/configadapters.go
-6
@@ -93,12 +93,6 @@ func JSONModuleObject(val interface{}, fieldName, fieldVal string, warnings *[]W
93
return result
94
}
95
96
-// JSONIndent is used to JSON-marshal the final resulting Caddy
97
-// configuration in a consistent, human-readable way.
98
-func JSONIndent(val interface{}) ([]byte, error) {
99
- return json.MarshalIndent(val, "", "\t")
100
-}
101
-
96
// RegisterAdapter registers a config adapter with the given name.
97
// This should usually be done at init-time. It panics if the
98
// adapter cannot be registered successfully.
caddyconfig/httpcaddyfile/builtins_test.go
+1
-10
@@ -10,7 +10,6 @@ import (
10
func TestLogDirectiveSyntax(t *testing.T) {
11
for i, tc := range []struct {
12
input string
13
- expectWarn bool
13
expectError bool
14
}{
15
{
@@ -18,7 +17,6 @@ func TestLogDirectiveSyntax(t *testing.T) {
17
log
18
}
19
`,
21
- expectWarn: false,
20
expectError: false,
21
},
22
{
@@ -28,7 +26,6 @@ func TestLogDirectiveSyntax(t *testing.T) {
26
}
27
}
28
`,
31
- expectWarn: false,
29
expectError: false,
30
},
31
{
@@ -38,7 +35,6 @@ func TestLogDirectiveSyntax(t *testing.T) {
35
}
36
}
37
`,
41
- expectWarn: false,
38
expectError: true,
39
},
40
} {
@@ -47,12 +43,7 @@ func TestLogDirectiveSyntax(t *testing.T) {
43
ServerType: ServerType{},
44
}
45
50
- _, warnings, err := adapter.Adapt([]byte(tc.input), nil)
51
-
52
- if len(warnings) > 0 != tc.expectWarn {
53
- t.Errorf("Test %d warning expectation failed Expected: %v, got %v", i, tc.expectWarn, warnings)
54
- continue
55
- }
46
+ _, _, err := adapter.Adapt([]byte(tc.input), nil)
47
48
if err != nil != tc.expectError {
49
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
caddyconfig/httpcaddyfile/httptype_test.go
+2
-28
@@ -9,7 +9,6 @@ import (
9
func TestMatcherSyntax(t *testing.T) {
10
for i, tc := range []struct {
11
input string
12
- expectWarn bool
12
expectError bool
13
}{
14
{
@@ -18,7 +17,6 @@ func TestMatcherSyntax(t *testing.T) {
17
query showdebug=1
18
}
19
`,
21
- expectWarn: false,
20
expectError: false,
21
},
22
{
@@ -27,7 +25,6 @@ func TestMatcherSyntax(t *testing.T) {
25
query bad format
26
}
27
`,
30
- expectWarn: false,
28
expectError: true,
29
},
30
{
@@ -38,7 +35,6 @@ func TestMatcherSyntax(t *testing.T) {
35
}
36
}
37
`,
41
- expectWarn: false,
38
expectError: false,
39
},
40
{
@@ -47,14 +43,12 @@ func TestMatcherSyntax(t *testing.T) {
43
not path /somepath*
44
}
45
`,
50
- expectWarn: false,
46
expectError: false,
47
},
48
{
49
input: `http://localhost
50
@debug not path /somepath*
51
`,
57
- expectWarn: false,
52
expectError: false,
53
},
54
{
@@ -63,7 +57,6 @@ func TestMatcherSyntax(t *testing.T) {
57
}
58
http://localhost
59
`,
66
- expectWarn: false,
60
expectError: true,
61
},
62
} {
@@ -72,12 +65,7 @@ func TestMatcherSyntax(t *testing.T) {
65
ServerType: ServerType{},
66
}
67
75
- _, warnings, err := adapter.Adapt([]byte(tc.input), nil)
76
-
77
- if len(warnings) > 0 != tc.expectWarn {
78
- t.Errorf("Test %d warning expectation failed Expected: %v, got %v", i, tc.expectWarn, warnings)
79
- continue
80
- }
68
+ _, _, err := adapter.Adapt([]byte(tc.input), nil)
69
70
if err != nil != tc.expectError {
71
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
@@ -119,7 +107,6 @@ func TestSpecificity(t *testing.T) {
107
func TestGlobalOptions(t *testing.T) {
108
for i, tc := range []struct {
109
input string
122
- expectWarn bool
110
expectError bool
111
}{
112
{
@@ -129,7 +116,6 @@ func TestGlobalOptions(t *testing.T) {
116
}
117
:80
118
`,
132
- expectWarn: false,
119
expectError: false,
120
},
121
{
@@ -139,7 +125,6 @@ func TestGlobalOptions(t *testing.T) {
125
}
126
:80
127
`,
142
- expectWarn: false,
128
expectError: false,
129
},
130
{
@@ -149,7 +134,6 @@ func TestGlobalOptions(t *testing.T) {
134
}
135
:80
136
`,
152
- expectWarn: false,
137
expectError: false,
138
},
139
{
@@ -161,7 +145,6 @@ func TestGlobalOptions(t *testing.T) {
145
}
146
:80
147
`,
164
- expectWarn: false,
148
expectError: true,
149
},
150
{
@@ -174,7 +157,6 @@ func TestGlobalOptions(t *testing.T) {
157
}
158
:80
159
`,
177
- expectWarn: false,
160
expectError: false,
161
},
162
{
@@ -187,7 +169,6 @@ func TestGlobalOptions(t *testing.T) {
169
}
170
:80
171
`,
190
- expectWarn: false,
172
expectError: false,
173
},
174
{
@@ -200,7 +181,6 @@ func TestGlobalOptions(t *testing.T) {
181
}
182
:80
183
`,
203
- expectWarn: false,
184
expectError: true,
185
},
186
{
@@ -213,7 +193,6 @@ func TestGlobalOptions(t *testing.T) {
193
}
194
:80
195
`,
216
- expectWarn: false,
196
expectError: true,
197
},
198
} {
@@ -222,12 +201,7 @@ func TestGlobalOptions(t *testing.T) {
201
ServerType: ServerType{},
202
}
203
225
- _, warnings, err := adapter.Adapt([]byte(tc.input), nil)
226
-
227
- if len(warnings) > 0 != tc.expectWarn {
228
- t.Errorf("Test %d warning expectation failed Expected: %v, got %v", i, tc.expectWarn, warnings)
229
- continue
230
- }
204
+ _, _, err := adapter.Adapt([]byte(tc.input), nil)
205
206
if err != nil != tc.expectError {
207
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
caddytest/caddytest.go
+8
-1
@@ -336,7 +336,6 @@ func CompareAdapt(t *testing.T, rawConfig string, adapterName string, expectedRe
336
}
337
338
options := make(map[string]interface{})
339
- options["pretty"] = "true"
339
340
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
341
if err != nil {
@@ -344,6 +343,14 @@ func CompareAdapt(t *testing.T, rawConfig string, adapterName string, expectedRe
343
return false
344
}
345
346
+ // prettify results to keep tests human-manageable
347
+ var prettyBuf bytes.Buffer
348
+ err = json.Indent(&prettyBuf, result, "", "\t")
349
+ if err != nil {
350
+ return false
351
+ }
352
+ result = prettyBuf.Bytes()
353
+
354
if len(warnings) > 0 {
355
for _, w := range warnings {
356
t.Logf("warning: directive: %s : %s", w.Directive, w.Message)
cmd/commandfuncs.go
+10
-5
@@ -463,17 +463,22 @@ func cmdAdaptConfig(fl Flags) (int, error) {
463
fmt.Errorf("reading input file: %v", err)
464
}
465
466
- opts := make(map[string]interface{})
467
- if adaptCmdPrettyFlag {
468
- opts["pretty"] = "true"
469
- }
470
- opts["filename"] = adaptCmdInputFlag
466
+ opts := map[string]interface{}{"filename": adaptCmdInputFlag}
467
468
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
469
if err != nil {
470
return caddy.ExitCodeFailedStartup, err
471
}
472
473
+ if adaptCmdPrettyFlag {
474
+ var prettyBuf bytes.Buffer
475
+ err = json.Indent(&prettyBuf, adaptedConfig, "", "\t")
476
+ if err != nil {
477
+ return caddy.ExitCodeFailedStartup, err
478
+ }
479
+ adaptedConfig = prettyBuf.Bytes()
480
+ }
481
+
482
// print warnings to stderr
483
for _, warn := range warnings {
484
msg := warn.Message
modules/caddyhttp/reverseproxy/caddyfile.go
+8
@@ -15,6 +15,7 @@
15
package reverseproxy
16
17
import (
18
+ "log"
19
"net"
20
"net/http"
21
"net/url"
@@ -497,6 +498,13 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
498
case 1:
499
err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], "", "")
500
case 2:
501
+ // some lint checks, I guess
502
+ if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
503
+ log.Printf("[WARNING] Unnecessary header_up ('Host' field): the reverse proxy's default behavior is to pass headers to the upstream")
504
+ }
505
+ if strings.EqualFold(args[0], "x-forwarded-proto") && (args[1] == "{scheme}" || args[1] == "{http.request.scheme}") {
506
+ log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-Proto' field): the reverse proxy's default behavior is to pass headers to the upstream")
507
+ }
508
err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], "")
509
case 3:
510
err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], args[2])