-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cli/compose: implement the ports validation method #5524
cli/compose: implement the ports validation method #5524
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #5524 +/- ##
==========================================
+ Coverage 60.58% 60.63% +0.04%
==========================================
Files 345 345
Lines 23456 23462 +6
==========================================
+ Hits 14211 14226 +15
+ Misses 8273 8263 -10
- Partials 972 973 +1 |
at my computer right now (commenting from my phone), but wondering if (most of this?) would be doable using the utilities that are used for the corresponding command-line options;
|
This commit implements a validation method for the port mappings. Also, it removes the ports validation method from the expose property since they do not accept the same type of values. Signed-off-by: Stavros Panakakis <[email protected]>
d9ae83c
to
0319795
Compare
@thaJeztah Thank you for the review, I was not aware of this library. I implemented the changes, I did a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
@@ -37,7 +48,6 @@ func (checker durationFormatChecker) IsFormat(input any) bool { | |||
} | |||
|
|||
func init() { | |||
gojsonschema.FormatCheckers.Add("expose", portsFormatChecker{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not for this PR, but we probably would be able to use the same utilities for this case as well, which could be either using ParsePortSpec
and making sure the IP address is not set, or a combination of ParsePortRange
and SplitProtoPort
.
I see the latter is the approach taken for the --expose
flag, but .. I'm actually wondering if the first approach would be better, because it also validates the given proto
cli/cli/command/container/opts.go
Lines 443 to 466 in 305985c
// Merge in exposed ports to the map of published ports | |
for _, e := range copts.expose.GetAll() { | |
if strings.Contains(e, ":") { | |
return nil, errors.Errorf("invalid port format for --expose: %s", e) | |
} | |
// support two formats for expose, original format <portnum>/[<proto>] | |
// or <startport-endport>/[<proto>] | |
proto, port := nat.SplitProtoPort(e) | |
// parse the start and end port and create a sequence of ports to expose | |
// if expose a port, the start and end port are the same | |
start, end, err := nat.ParsePortRange(port) | |
if err != nil { | |
return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err) | |
} | |
for i := start; i <= end; i++ { | |
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) | |
if err != nil { | |
return nil, err | |
} | |
if _, exists := ports[p]; !exists { | |
ports[p] = struct{}{} | |
} | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, MAYBE validating the proto shouldn't belong on the client-side, as the list of supported protocols could depend on what the daemon supports.
But given that we already do it elsewhere, maybe that's not too problematic (and if we change, we should do so across the board).
We could possibly convert the code linked above to a utility if we don't want to duplicate the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In any case, none of the above is needed for the current PR; it's mostly outlining thoughts for a follow-up 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thaJeztah The ParsePortSpec
method returns PortMapping
while the expose
propery is a PortBinding
, so ParsePortSpec
might not be the best for this case.
For this reason, I believe that the combination of ParsePortRange
and SplitProtoPort
will work better .
Regarding creating a utility, I am not sure that it will help to avoid duplicate code. For example:
Using a utility:
// utils.go
func validateExposePortOption(ctx context.Context, portSpec string) error {
if strings.Contains(portSpec, ":") {
return fmt.Errorf("invalid port format for expose %s", portSpec)
}
// support two formats for expose, original format <portnum>/[<proto>]
// or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(portSpec)
// we could probably convert the validateProto method to public and validate the
// proto to the client
// https://github.com/docker/go-connections/blob/5df8d2b30ca886f2d94740ce3c54abd58a5bb2c9/nat/nat.go#L110
// ......
// parse the start and end port and create a sequence of ports to expose
// if expose a port, the start and end port are the same
_, _, err := nat.ParsePortRange(port)
if err != nil {
return fmt.Errorf("invalid range format for expose: %s, error: %s", portSpec, err)
}
return nil
}
// opts.go
// Merge in exposed ports to the map of published ports
for _, e := range copts.expose.GetAll() {
err := validateExposePortOption(ctx, e)
if err != nil {
return nil, errors.Errorf("invalid port format for --expose: %s, error: %s", e, err)
}
// DUPLICATE CODE WITH THE UTILITY
start, end, err := nat.ParsePortRange(port)
if err != nil {
return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return nil, err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
// schema.go
type exposeFormatChecker struct{}
func (checker exposeFormatChecker) IsFormat(input any) bool {
var port string
switch p := input.(type) {
case string:
port = p
case *big.Rat:
port = strings.Split(p.String(), "/")[0]
}
err := validateExposePortOption(context.Background(), port)
return err == nil
}
Not using a utility:
// opts.go
// Merge in exposed ports to the map of published ports
for _, e := range copts.expose.GetAll() {
if strings.Contains(e, ":") {
return nil, errors.Errorf("invalid port format for --expose: %s", e)
}
// support two formats for expose, original format <portnum>/[<proto>]
// or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
// we could probably convert the validateProto method to public and validate the proto to the client
// https://github.com/docker/go-connections/blob/5df8d2b30ca886f2d94740ce3c54abd58a5bb2c9/nat/nat.go#L110
// ......
// parse the start and end port and create a sequence of ports to expose
// if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return nil, err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
// schema.go
type exposeFormatChecker struct{}
func (checker exposeFormatChecker) IsFormat(input any) bool {
var port string
switch p := input.(type) {
case string:
port = p
case *big.Rat:
port = strings.Split(p.String(), "/")[0]
}
if strings.Contains(portSpec, ":") {
return false
}
// support two formats for expose, original format <portnum>/[<proto>]
// or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(portSpec)
// we could probably convert the validateProto method to public and validate the
// proto to the client
// https://github.com/docker/go-connections/blob/5df8d2b30ca886f2d94740ce3c54abd58a5bb2c9/nat/nat.go#L110
// ......
// parse the start and end port and create a sequence of ports to expose
// if expose a port, the start and end port are the same
_, _, err := nat.ParsePortRange(port)
if err != nil {
return false
}
return true
}
Both cases with or without a utility contain duplicate code.
What do you think?
I will be happy to open a new pull request for this.
- What I did
This commit implements a validation method for the port mappings of compose. Also, it removes the ports validation method from the
expose
property since they do not always accept the same type of values. For example,ports
property accepts the value8000:8000
but theexpose
property not.- How I did it
After reading the API Reference of Docker Compose syntax, I wrote a test that covers all the cases and then implemented the validation method until the tests pass.
- How to verify it
Run
make test
- Description for the changelog