Currently `--publish-rm` only accepts `<TargetPort>` or `<TargetPort>[/Protocol]`
though there are some confusions.
Since `--publish-add` accepts `<PublishedPort>:<TargetPort>[/Protocol]`, some user
may provide `--publish-rm 80:80`. However, there is no error checking so the incorrect
provided argument is ignored silently.
This fix adds the check to make sure `--publish-rm` only accepts `<TargetPort>[/Protocol]`
and returns error if the format is invalid.
The `--publish-rm` itself may needs to be revisited to have a better UI/UX experience,
see discussions on:
https://github.com/docker/swarmkit/issues/1396
https://github.com/docker/docker/issues/25200#issuecomment-236213242
https://github.com/docker/docker/issues/25338#issuecomment-240787002
This fix is short term measure so that end users are not misled by the silently ignored error
of `--publish-rm`.
This fix is related to (but is not a complete fix):
https://github.com/docker/swarmkit/issues/1396
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
| ... | ... |
@@ -47,7 +47,7 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 47 | 47 |
flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key") |
| 48 | 48 |
flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key") |
| 49 | 49 |
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path") |
| 50 |
- flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port") |
|
| 50 |
+ flags.Var(newListOptsVar().WithValidator(validatePublishRemove), flagPublishRemove, "Remove a published port by its target port") |
|
| 51 | 51 |
flags.MarkHidden(flagPublishRemove) |
| 52 | 52 |
flags.Var(newListOptsVar(), flagPortRemove, "Remove a port(target-port mandatory)") |
| 53 | 53 |
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint") |
| ... | ... |
@@ -645,6 +645,23 @@ func portConfigToString(portConfig *swarm.PortConfig) string {
|
| 645 | 645 |
return fmt.Sprintf("%v:%v/%s/%s", portConfig.PublishedPort, portConfig.TargetPort, protocol, mode)
|
| 646 | 646 |
} |
| 647 | 647 |
|
| 648 |
+// This validation is only used for `--publish-rm`. |
|
| 649 |
+// The `--publish-rm` takes: |
|
| 650 |
+// <TargetPort>[/<Protocol>] (e.g., 80, 80/tcp, 53/udp) |
|
| 651 |
+func validatePublishRemove(val string) (string, error) {
|
|
| 652 |
+ proto, port := nat.SplitProtoPort(val) |
|
| 653 |
+ if proto != "tcp" && proto != "udp" {
|
|
| 654 |
+ return "", fmt.Errorf("invalid protocol '%s' for %s", proto, val)
|
|
| 655 |
+ } |
|
| 656 |
+ if strings.Contains(port, ":") {
|
|
| 657 |
+ return "", fmt.Errorf("invalid port format: '%s', should be <TargetPort>[/<Protocol>] (e.g., 80, 80/tcp, 53/udp)", port)
|
|
| 658 |
+ } |
|
| 659 |
+ if _, err := nat.ParsePort(port); err != nil {
|
|
| 660 |
+ return "", err |
|
| 661 |
+ } |
|
| 662 |
+ return val, nil |
|
| 663 |
+} |
|
| 664 |
+ |
|
| 648 | 665 |
func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
| 649 | 666 |
// The key of the map is `port/protocol`, e.g., `80/tcp` |
| 650 | 667 |
portSet := map[string]swarm.PortConfig{}
|
| ... | ... |
@@ -345,3 +345,43 @@ func TestUpdateHosts(t *testing.T) {
|
| 345 | 345 |
assert.Equal(t, hosts[1], "2001:db8:abc8::1 ipv6.net") |
| 346 | 346 |
assert.Equal(t, hosts[2], "4.3.2.1 example.org") |
| 347 | 347 |
} |
| 348 |
+ |
|
| 349 |
+func TestUpdatePortsRmWithProtocol(t *testing.T) {
|
|
| 350 |
+ flags := newUpdateCommand(nil).Flags() |
|
| 351 |
+ flags.Set("publish-add", "8081:81")
|
|
| 352 |
+ flags.Set("publish-add", "8082:82")
|
|
| 353 |
+ flags.Set("publish-rm", "80")
|
|
| 354 |
+ flags.Set("publish-rm", "81/tcp")
|
|
| 355 |
+ flags.Set("publish-rm", "82/udp")
|
|
| 356 |
+ |
|
| 357 |
+ portConfigs := []swarm.PortConfig{
|
|
| 358 |
+ {TargetPort: 80, PublishedPort: 8080, Protocol: swarm.PortConfigProtocolTCP},
|
|
| 359 |
+ } |
|
| 360 |
+ |
|
| 361 |
+ err := updatePorts(flags, &portConfigs) |
|
| 362 |
+ assert.Equal(t, err, nil) |
|
| 363 |
+ assert.Equal(t, len(portConfigs), 1) |
|
| 364 |
+ assert.Equal(t, portConfigs[0].TargetPort, uint32(82)) |
|
| 365 |
+} |
|
| 366 |
+ |
|
| 367 |
+func TestValidatePort(t *testing.T) {
|
|
| 368 |
+ validPorts := []string{"80/tcp", "80", "80/udp"}
|
|
| 369 |
+ invalidPorts := map[string]string{
|
|
| 370 |
+ "9999999": "out of range", |
|
| 371 |
+ "80:80/tcp": "invalid port format", |
|
| 372 |
+ "53:53/udp": "invalid port format", |
|
| 373 |
+ "80:80": "invalid port format", |
|
| 374 |
+ "80/xyz": "invalid protocol", |
|
| 375 |
+ "tcp": "invalid syntax", |
|
| 376 |
+ "udp": "invalid syntax", |
|
| 377 |
+ "": "invalid protocol", |
|
| 378 |
+ } |
|
| 379 |
+ for _, port := range validPorts {
|
|
| 380 |
+ _, err := validatePublishRemove(port) |
|
| 381 |
+ assert.Equal(t, err, nil) |
|
| 382 |
+ } |
|
| 383 |
+ for port, e := range invalidPorts {
|
|
| 384 |
+ _, err := validatePublishRemove(port) |
|
| 385 |
+ assert.Error(t, err, e) |
|
| 386 |
+ } |
|
| 387 |
+} |
| ... | ... |
@@ -108,6 +108,12 @@ func (opts *ListOpts) Type() string {
|
| 108 | 108 |
return "list" |
| 109 | 109 |
} |
| 110 | 110 |
|
| 111 |
+// WithValidator returns the ListOpts with validator set. |
|
| 112 |
+func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts {
|
|
| 113 |
+ opts.validator = validator |
|
| 114 |
+ return opts |
|
| 115 |
+} |
|
| 116 |
+ |
|
| 111 | 117 |
// NamedOption is an interface that list and map options |
| 112 | 118 |
// with names implement. |
| 113 | 119 |
type NamedOption interface {
|