@@ -7,93 +7,100 @@ import (
7
7
"strings"
8
8
9
9
"github.com/moby/buildkit/frontend/dockerfile/instructions"
10
- "github.com/moby/buildkit/frontend/dockerfile/shell"
10
+ "github.com/moby/buildkit/frontend/dockerfile/linter"
11
+ "github.com/moby/buildkit/frontend/dockerfile/parser"
11
12
"github.com/pkg/errors"
12
13
)
13
14
14
- func dispatchExpose (d * dispatchState , c * instructions.ExposeCommand , shlex * shell. Lex ) error {
15
+ func dispatchExpose (d * dispatchState , c * instructions.ExposeCommand , opt * dispatchOpt ) error {
15
16
ports := []string {}
16
17
env := getEnv (d .state )
17
18
for _ , p := range c .Ports {
18
- ps , err := shlex .ProcessWords (p , env )
19
+ ps , err := opt . shlex .ProcessWords (p , env )
19
20
if err != nil {
20
21
return err
21
22
}
22
23
ports = append (ports , ps ... )
23
24
}
24
25
c .Ports = ports
25
26
26
- ps , err := parsePortSpecs (c .Ports )
27
+ ps := newPortSpecs (
28
+ withLocation (c .Location ()),
29
+ withLint (opt .lint ),
30
+ )
31
+
32
+ psp , err := ps .parsePorts (c .Ports )
27
33
if err != nil {
28
34
return err
29
35
}
30
36
31
37
if d .image .Config .ExposedPorts == nil {
32
38
d .image .Config .ExposedPorts = make (map [string ]struct {})
33
39
}
34
- for _ , p := range ps {
40
+ for _ , p := range psp {
35
41
d .image .Config .ExposedPorts [p ] = struct {}{}
36
42
}
37
43
38
44
return commitToHistory (& d .image , fmt .Sprintf ("EXPOSE %v" , ps ), false , nil , d .epoch )
39
45
}
40
46
41
- // parsePortSpecs receives port specs in the format of [ip:]public:private/proto
42
- // and returns them as a list of "port/proto".
43
- func parsePortSpecs (ports []string ) (exposedPorts []string , _ error ) {
44
- for _ , p := range ports {
45
- portProtos , err := parsePortSpec (p )
46
- if err != nil {
47
- return nil , err
48
- }
49
- exposedPorts = append (exposedPorts , portProtos ... )
50
- }
51
- return exposedPorts , nil
47
+ type portSpecs struct {
48
+ location []parser.Range
49
+ lint * linter.Linter
52
50
}
53
51
54
- // splitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
55
- // "<startport-endport>/[<proto>]". It returns an error if no port(range) or
56
- // an invalid proto is provided. If no protocol is provided, the default ("tcp")
57
- // protocol is returned.
58
- func splitProtoPort (rawPort string ) (proto string , port string , _ error ) {
59
- port , proto , _ = strings .Cut (rawPort , "/" )
60
- if port == "" {
61
- return "" , "" , errors .New ("no port specified" )
52
+ type portSpecsOption func (ps * portSpecs )
53
+
54
+ func withLocation (location []parser.Range ) portSpecsOption {
55
+ return func (ps * portSpecs ) {
56
+ ps .location = location
62
57
}
63
- proto = strings .ToLower (proto )
64
- switch proto {
65
- case "" :
66
- return "tcp" , port , nil
67
- case "tcp" , "udp" , "sctp" :
68
- return proto , port , nil
69
- default :
70
- return "" , "" , errors .New ("invalid proto: " + proto )
58
+ }
59
+
60
+ func withLint (lint * linter.Linter ) portSpecsOption {
61
+ return func (ps * portSpecs ) {
62
+ ps .lint = lint
71
63
}
72
64
}
73
65
74
- func splitParts (rawport string ) (hostIP , hostPort , containerPort string ) {
75
- parts := strings .Split (rawport , ":" )
66
+ func newPortSpecs (opts ... portSpecsOption ) * portSpecs {
67
+ ps := & portSpecs {}
68
+ for _ , opt := range opts {
69
+ opt (ps )
70
+ }
71
+ return ps
72
+ }
76
73
77
- switch len (parts ) {
78
- case 1 :
79
- return "" , "" , parts [0 ]
80
- case 2 :
81
- return "" , parts [0 ], parts [1 ]
82
- case 3 :
83
- return parts [0 ], parts [1 ], parts [2 ]
84
- default :
85
- n := len (parts )
86
- return strings .Join (parts [:n - 2 ], ":" ), parts [n - 2 ], parts [n - 1 ]
74
+ // parsePorts receives port specs in the format of [ip:]public:private/proto
75
+ // and returns them as a list of "port/proto".
76
+ func (ps * portSpecs ) parsePorts (ports []string ) (exposedPorts []string , _ error ) {
77
+ for _ , p := range ports {
78
+ portProtos , err := ps .parsePort (p )
79
+ if err != nil {
80
+ return nil , err
81
+ }
82
+ exposedPorts = append (exposedPorts , portProtos ... )
87
83
}
84
+ return exposedPorts , nil
88
85
}
89
86
90
- // parsePortSpec parses a port specification string into a slice of "<portnum>/[<proto>]"
91
- func parsePortSpec (rawPort string ) (portProto []string , _ error ) {
92
- ip , hostPort , containerPort := splitParts (rawPort )
93
- proto , containerPort , err := splitProtoPort (containerPort )
87
+ // parsePort parses a port specification string into a slice of "<portnum>/[<proto>]"
88
+ func ( ps * portSpecs ) parsePort (rawPort string ) (portProto []string , _ error ) {
89
+ ip , hostPort , containerPort := ps . splitParts (rawPort )
90
+ proto , containerPort , err := ps . splitProtoPort (containerPort )
94
91
if err != nil {
95
92
return nil , errors .Wrapf (err , "invalid port: %q" , rawPort )
96
93
}
94
+ if ps .lint != nil {
95
+ if proto != strings .ToLower (proto ) {
96
+ msg := linter .RuleExposeProtoCasing .Format (rawPort )
97
+ ps .lint .Run (& linter .RuleExposeProtoCasing , ps .location , msg )
98
+ }
99
+ if ip != "" || hostPort != "" {
100
+ msg := linter .RuleExposeInvalidFormat .Format (rawPort )
101
+ ps .lint .Run (& linter .RuleExposeInvalidFormat , ps .location , msg )
102
+ }
103
+ }
97
104
98
105
// TODO(thaJeztah): mapping IP-addresses should not be allowed for EXPOSE; see https://github.com/moby/buildkit/issues/2173
99
106
if ip != "" && ip [0 ] == '[' {
@@ -108,14 +115,14 @@ func parsePortSpec(rawPort string) (portProto []string, _ error) {
108
115
return nil , errors .New ("invalid IP address: " + ip )
109
116
}
110
117
111
- startPort , endPort , err := parsePortRange (containerPort )
118
+ startPort , endPort , err := ps . parsePortRange (containerPort )
112
119
if err != nil {
113
120
return nil , errors .New ("invalid containerPort: " + containerPort )
114
121
}
115
122
116
123
// TODO(thaJeztah): mapping host-ports should not be allowed for EXPOSE; see https://github.com/moby/buildkit/issues/2173
117
124
if hostPort != "" {
118
- startHostPort , endHostPort , err := parsePortRange (hostPort )
125
+ startHostPort , endHostPort , err := ps . parsePortRange (hostPort )
119
126
if err != nil {
120
127
return nil , errors .New ("invalid hostPort: " + hostPort )
121
128
}
@@ -133,27 +140,27 @@ func parsePortSpec(rawPort string) (portProto []string, _ error) {
133
140
ports := make ([]string , 0 , count )
134
141
135
142
for i := range count {
136
- ports = append (ports , strconv .Itoa (startPort + i )+ "/" + proto )
143
+ ports = append (ports , strconv .Itoa (startPort + i )+ "/" + strings . ToLower ( proto ) )
137
144
}
138
145
return ports , nil
139
146
}
140
147
141
148
// parsePortRange parses and validates the specified string as a port range (e.g., "8000-9000").
142
- func parsePortRange (ports string ) (startPort , endPort int , _ error ) {
149
+ func ( ps * portSpecs ) parsePortRange (ports string ) (startPort , endPort int , _ error ) {
143
150
if ports == "" {
144
151
return 0 , 0 , errors .New ("empty string specified for ports" )
145
152
}
146
153
start , end , ok := strings .Cut (ports , "-" )
147
154
148
- startPort , err := parsePortNumber (start )
155
+ startPort , err := ps . parsePortNumber (start )
149
156
if err != nil {
150
157
return 0 , 0 , errors .Wrapf (err , "invalid start port '%s'" , start )
151
158
}
152
159
if ! ok || start == end {
153
160
return startPort , startPort , nil
154
161
}
155
162
156
- endPort , err = parsePortNumber (end )
163
+ endPort , err = ps . parsePortNumber (end )
157
164
if err != nil {
158
165
return 0 , 0 , errors .Wrapf (err , "invalid end port '%s'" , end )
159
166
}
@@ -165,7 +172,7 @@ func parsePortRange(ports string) (startPort, endPort int, _ error) {
165
172
166
173
// parsePortNumber parses rawPort into an int, unwrapping strconv errors
167
174
// and returning a single "out of range" error for any value outside 0–65535.
168
- func parsePortNumber (rawPort string ) (int , error ) {
175
+ func ( ps * portSpecs ) parsePortNumber (rawPort string ) (int , error ) {
169
176
if rawPort == "" {
170
177
return 0 , errors .New ("value is empty" )
171
178
}
@@ -183,3 +190,38 @@ func parsePortNumber(rawPort string) (int, error) {
183
190
184
191
return int (port ), nil
185
192
}
193
+
194
+ // splitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
195
+ // "<startport-endport>/[<proto>]". It returns an error if no port(range) or
196
+ // an invalid proto is provided. If no protocol is provided, the default ("tcp")
197
+ // protocol is returned.
198
+ func (ps * portSpecs ) splitProtoPort (rawPort string ) (proto string , port string , _ error ) {
199
+ port , proto , _ = strings .Cut (rawPort , "/" )
200
+ if port == "" {
201
+ return "" , "" , errors .New ("no port specified" )
202
+ }
203
+ switch strings .ToLower (proto ) {
204
+ case "" :
205
+ return "tcp" , port , nil
206
+ case "tcp" , "udp" , "sctp" :
207
+ return proto , port , nil
208
+ default :
209
+ return "" , "" , errors .New ("invalid proto: " + proto )
210
+ }
211
+ }
212
+
213
+ func (ps * portSpecs ) splitParts (rawport string ) (hostIP , hostPort , containerPort string ) {
214
+ parts := strings .Split (rawport , ":" )
215
+
216
+ switch len (parts ) {
217
+ case 1 :
218
+ return "" , "" , parts [0 ]
219
+ case 2 :
220
+ return "" , parts [0 ], parts [1 ]
221
+ case 3 :
222
+ return parts [0 ], parts [1 ], parts [2 ]
223
+ default :
224
+ n := len (parts )
225
+ return strings .Join (parts [:n - 2 ], ":" ), parts [n - 2 ], parts [n - 1 ]
226
+ }
227
+ }
0 commit comments