mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add support for host port PublishMode in services
Add api/cli support for adding host port PublishMode in services. Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
parent
750d634d62
commit
14ac9f60d0
15 changed files with 362 additions and 44 deletions
|
@ -31,8 +31,23 @@ type PortConfig struct {
|
||||||
TargetPort uint32 `json:",omitempty"`
|
TargetPort uint32 `json:",omitempty"`
|
||||||
// PublishedPort is the port on the swarm hosts
|
// PublishedPort is the port on the swarm hosts
|
||||||
PublishedPort uint32 `json:",omitempty"`
|
PublishedPort uint32 `json:",omitempty"`
|
||||||
|
// PublishMode is the mode in which port is published
|
||||||
|
PublishMode PortConfigPublishMode `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PortConfigPublishMode represents the mode in which the port is to
|
||||||
|
// be published.
|
||||||
|
type PortConfigPublishMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PortConfigPublishModeIngress is used for ports published
|
||||||
|
// for ingress load balancing using routing mesh.
|
||||||
|
PortConfigPublishModeIngress PortConfigPublishMode = "ingress"
|
||||||
|
// PortConfigPublishModeHost is used for ports published
|
||||||
|
// for direct host level access on the host where the task is running.
|
||||||
|
PortConfigPublishModeHost PortConfigPublishMode = "host"
|
||||||
|
)
|
||||||
|
|
||||||
// PortConfigProtocol represents the protocol of a port.
|
// PortConfigProtocol represents the protocol of a port.
|
||||||
type PortConfigProtocol string
|
type PortConfigProtocol string
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,7 @@ type TaskStatus struct {
|
||||||
Message string `json:",omitempty"`
|
Message string `json:",omitempty"`
|
||||||
Err string `json:",omitempty"`
|
Err string `json:",omitempty"`
|
||||||
ContainerStatus ContainerStatus `json:",omitempty"`
|
ContainerStatus ContainerStatus `json:",omitempty"`
|
||||||
|
PortStatus PortStatus `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerStatus represents the status of a container.
|
// ContainerStatus represents the status of a container.
|
||||||
|
@ -119,3 +120,9 @@ type ContainerStatus struct {
|
||||||
PID int `json:",omitempty"`
|
PID int `json:",omitempty"`
|
||||||
ExitCode int `json:",omitempty"`
|
ExitCode int `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PortStatus represents the port status of a task's host ports whose
|
||||||
|
// service has published host ports
|
||||||
|
type PortStatus struct {
|
||||||
|
Ports []PortConfig `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
|
@ -40,12 +40,14 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
flags.Var(&opts.constraints, flagConstraint, "Placement constraints")
|
flags.Var(&opts.constraints, flagConstraint, "Placement constraints")
|
||||||
flags.Var(&opts.networks, flagNetwork, "Network attachments")
|
flags.Var(&opts.networks, flagNetwork, "Network attachments")
|
||||||
flags.Var(&opts.secrets, flagSecret, "Specify secrets to expose to the service")
|
flags.Var(&opts.secrets, flagSecret, "Specify secrets to expose to the service")
|
||||||
flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
|
flags.VarP(&opts.endpoint.publishPorts, flagPublish, "p", "Publish a port as a node port")
|
||||||
|
flags.MarkHidden(flagPublish)
|
||||||
flags.Var(&opts.groups, flagGroup, "Set one or more supplementary user groups for the container")
|
flags.Var(&opts.groups, flagGroup, "Set one or more supplementary user groups for the container")
|
||||||
flags.Var(&opts.dns, flagDNS, "Set custom DNS servers")
|
flags.Var(&opts.dns, flagDNS, "Set custom DNS servers")
|
||||||
flags.Var(&opts.dnsOption, flagDNSOption, "Set DNS options")
|
flags.Var(&opts.dnsOption, flagDNSOption, "Set DNS options")
|
||||||
flags.Var(&opts.dnsSearch, flagDNSSearch, "Set custom DNS search domains")
|
flags.Var(&opts.dnsSearch, flagDNSSearch, "Set custom DNS search domains")
|
||||||
flags.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)")
|
flags.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)")
|
||||||
|
flags.Var(&opts.endpoint.expandedPorts, flagPort, "Publish a port")
|
||||||
|
|
||||||
flags.SetInterspersed(false)
|
flags.SetInterspersed(false)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -287,14 +287,15 @@ func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
type endpointOptions struct {
|
type endpointOptions struct {
|
||||||
mode string
|
mode string
|
||||||
ports opts.ListOpts
|
publishPorts opts.ListOpts
|
||||||
|
expandedPorts opts.PortOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
|
func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
|
||||||
portConfigs := []swarm.PortConfig{}
|
portConfigs := []swarm.PortConfig{}
|
||||||
// We can ignore errors because the format was already validated by ValidatePort
|
// We can ignore errors because the format was already validated by ValidatePort
|
||||||
ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
|
ports, portBindings, _ := nat.ParsePortSpecs(e.publishPorts.GetAll())
|
||||||
|
|
||||||
for port := range ports {
|
for port := range ports {
|
||||||
portConfigs = append(portConfigs, ConvertPortToPortConfig(port, portBindings)...)
|
portConfigs = append(portConfigs, ConvertPortToPortConfig(port, portBindings)...)
|
||||||
|
@ -302,7 +303,7 @@ func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
|
||||||
|
|
||||||
return &swarm.EndpointSpec{
|
return &swarm.EndpointSpec{
|
||||||
Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
|
Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
|
||||||
Ports: portConfigs,
|
Ports: append(portConfigs, e.expandedPorts.Value()...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,7 +460,7 @@ func newServiceOptions() *serviceOptions {
|
||||||
env: opts.NewListOpts(runconfigopts.ValidateEnv),
|
env: opts.NewListOpts(runconfigopts.ValidateEnv),
|
||||||
envFile: opts.NewListOpts(nil),
|
envFile: opts.NewListOpts(nil),
|
||||||
endpoint: endpointOptions{
|
endpoint: endpointOptions{
|
||||||
ports: opts.NewListOpts(ValidatePort),
|
publishPorts: opts.NewListOpts(ValidatePort),
|
||||||
},
|
},
|
||||||
groups: opts.NewListOpts(nil),
|
groups: opts.NewListOpts(nil),
|
||||||
logDriver: newLogDriverOptions(),
|
logDriver: newLogDriverOptions(),
|
||||||
|
@ -647,6 +648,9 @@ const (
|
||||||
flagPublish = "publish"
|
flagPublish = "publish"
|
||||||
flagPublishRemove = "publish-rm"
|
flagPublishRemove = "publish-rm"
|
||||||
flagPublishAdd = "publish-add"
|
flagPublishAdd = "publish-add"
|
||||||
|
flagPort = "port"
|
||||||
|
flagPortAdd = "port-add"
|
||||||
|
flagPortRemove = "port-rm"
|
||||||
flagReplicas = "replicas"
|
flagReplicas = "replicas"
|
||||||
flagReserveCPU = "reserve-cpu"
|
flagReserveCPU = "reserve-cpu"
|
||||||
flagReserveMemory = "reserve-memory"
|
flagReserveMemory = "reserve-memory"
|
||||||
|
|
|
@ -48,6 +48,8 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
|
flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
|
||||||
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
|
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
|
||||||
flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
|
flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
|
||||||
|
flags.MarkHidden(flagPublishRemove)
|
||||||
|
flags.Var(newListOptsVar(), flagPortRemove, "Remove a port(target-port mandatory)")
|
||||||
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
|
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
|
||||||
flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
|
flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
|
||||||
flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
|
flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
|
||||||
|
@ -60,7 +62,9 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
flags.Var(&opts.secrets, flagSecretAdd, "Add or update a secret on a service")
|
flags.Var(&opts.secrets, flagSecretAdd, "Add or update a secret on a service")
|
||||||
flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service")
|
flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service")
|
||||||
flags.Var(&opts.constraints, flagConstraintAdd, "Add or update a placement constraint")
|
flags.Var(&opts.constraints, flagConstraintAdd, "Add or update a placement constraint")
|
||||||
flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
|
flags.Var(&opts.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
|
||||||
|
flags.MarkHidden(flagPublishAdd)
|
||||||
|
flags.Var(&opts.endpoint.expandedPorts, flagPortAdd, "Add or update a port")
|
||||||
flags.Var(&opts.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
|
flags.Var(&opts.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
|
||||||
flags.Var(&opts.dns, flagDNSAdd, "Add or update a custom DNS server")
|
flags.Var(&opts.dns, flagDNSAdd, "Add or update a custom DNS server")
|
||||||
flags.Var(&opts.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
|
flags.Var(&opts.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
|
||||||
|
@ -267,7 +271,7 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
|
if anyChanged(flags, flagPublishAdd, flagPublishRemove, flagPortAdd, flagPortRemove) {
|
||||||
if spec.EndpointSpec == nil {
|
if spec.EndpointSpec == nil {
|
||||||
spec.EndpointSpec = &swarm.EndpointSpec{}
|
spec.EndpointSpec = &swarm.EndpointSpec{}
|
||||||
}
|
}
|
||||||
|
@ -627,7 +631,13 @@ func portConfigToString(portConfig *swarm.PortConfig) string {
|
||||||
if protocol == "" {
|
if protocol == "" {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v/%s", portConfig.PublishedPort, protocol)
|
|
||||||
|
mode := portConfig.PublishMode
|
||||||
|
if mode == "" {
|
||||||
|
mode = "ingress"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v:%v/%s/%s", portConfig.PublishedPort, portConfig.TargetPort, protocol, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
||||||
|
@ -649,6 +659,15 @@ func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.Changed(flagPortAdd) {
|
||||||
|
for _, entry := range flags.Lookup(flagPortAdd).Value.(*opts.PortOpt).Value() {
|
||||||
|
if v, ok := portSet[portConfigToString(&entry)]; ok && v != entry {
|
||||||
|
return fmt.Errorf("conflicting port mapping between %v:%v/%s and %v:%v/%s", entry.PublishedPort, entry.TargetPort, entry.Protocol, v.PublishedPort, v.TargetPort, v.Protocol)
|
||||||
|
}
|
||||||
|
portSet[portConfigToString(&entry)] = entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Override previous PortConfig in service if there is any duplicate
|
// Override previous PortConfig in service if there is any duplicate
|
||||||
for _, entry := range *portConfig {
|
for _, entry := range *portConfig {
|
||||||
if _, ok := portSet[portConfigToString(&entry)]; !ok {
|
if _, ok := portSet[portConfigToString(&entry)]; !ok {
|
||||||
|
@ -657,6 +676,14 @@ func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
|
toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
|
||||||
|
removePortCSV := flags.Lookup(flagPortRemove).Value.(*opts.ListOpts).GetAll()
|
||||||
|
removePortOpts := &opts.PortOpt{}
|
||||||
|
for _, portCSV := range removePortCSV {
|
||||||
|
if err := removePortOpts.Set(portCSV); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newPorts := []swarm.PortConfig{}
|
newPorts := []swarm.PortConfig{}
|
||||||
portLoop:
|
portLoop:
|
||||||
for _, port := range portSet {
|
for _, port := range portSet {
|
||||||
|
@ -666,14 +693,36 @@ portLoop:
|
||||||
continue portLoop
|
continue portLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, pConfig := range removePortOpts.Value() {
|
||||||
|
if equalProtocol(port.Protocol, pConfig.Protocol) &&
|
||||||
|
port.TargetPort == pConfig.TargetPort &&
|
||||||
|
equalPublishMode(port.PublishMode, pConfig.PublishMode) {
|
||||||
|
continue portLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newPorts = append(newPorts, port)
|
newPorts = append(newPorts, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the PortConfig to avoid unnecessary updates
|
// Sort the PortConfig to avoid unnecessary updates
|
||||||
sort.Sort(byPortConfig(newPorts))
|
sort.Sort(byPortConfig(newPorts))
|
||||||
*portConfig = newPorts
|
*portConfig = newPorts
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func equalProtocol(prot1, prot2 swarm.PortConfigProtocol) bool {
|
||||||
|
return prot1 == prot2 ||
|
||||||
|
(prot1 == swarm.PortConfigProtocol("") && prot2 == swarm.PortConfigProtocolTCP) ||
|
||||||
|
(prot2 == swarm.PortConfigProtocol("") && prot1 == swarm.PortConfigProtocolTCP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalPublishMode(mode1, mode2 swarm.PortConfigPublishMode) bool {
|
||||||
|
return mode1 == mode2 ||
|
||||||
|
(mode1 == swarm.PortConfigPublishMode("") && mode2 == swarm.PortConfigPublishModeIngress) ||
|
||||||
|
(mode2 == swarm.PortConfigPublishMode("") && mode1 == swarm.PortConfigPublishModeIngress)
|
||||||
|
}
|
||||||
|
|
||||||
func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
|
func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
|
||||||
return (string(port.Protocol) == targetPort.Proto() &&
|
return (string(port.Protocol) == targetPort.Proto() &&
|
||||||
port.TargetPort == uint32(targetPort.Int()))
|
port.TargetPort == uint32(targetPort.Int()))
|
||||||
|
|
|
@ -238,7 +238,7 @@ func TestUpdatePortsDuplicateEntries(t *testing.T) {
|
||||||
func TestUpdatePortsDuplicateKeys(t *testing.T) {
|
func TestUpdatePortsDuplicateKeys(t *testing.T) {
|
||||||
// Test case for #25375
|
// Test case for #25375
|
||||||
flags := newUpdateCommand(nil).Flags()
|
flags := newUpdateCommand(nil).Flags()
|
||||||
flags.Set("publish-add", "80:20")
|
flags.Set("publish-add", "80:80")
|
||||||
|
|
||||||
portConfigs := []swarm.PortConfig{
|
portConfigs := []swarm.PortConfig{
|
||||||
{TargetPort: 80, PublishedPort: 80},
|
{TargetPort: 80, PublishedPort: 80},
|
||||||
|
@ -247,21 +247,7 @@ func TestUpdatePortsDuplicateKeys(t *testing.T) {
|
||||||
err := updatePorts(flags, &portConfigs)
|
err := updatePorts(flags, &portConfigs)
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
assert.Equal(t, len(portConfigs), 1)
|
assert.Equal(t, len(portConfigs), 1)
|
||||||
assert.Equal(t, portConfigs[0].TargetPort, uint32(20))
|
assert.Equal(t, portConfigs[0].TargetPort, uint32(80))
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdatePortsConflictingFlags(t *testing.T) {
|
|
||||||
// Test case for #25375
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
|
||||||
flags.Set("publish-add", "80:80")
|
|
||||||
flags.Set("publish-add", "80:20")
|
|
||||||
|
|
||||||
portConfigs := []swarm.PortConfig{
|
|
||||||
{TargetPort: 80, PublishedPort: 80},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := updatePorts(flags, &portConfigs)
|
|
||||||
assert.Error(t, err, "conflicting port mapping")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateHealthcheckTable(t *testing.T) {
|
func TestUpdateHealthcheckTable(t *testing.T) {
|
||||||
|
|
|
@ -17,10 +17,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s ago\t%s\n"
|
psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s ago\t%s\t%s\n"
|
||||||
maxErrLength = 30
|
maxErrLength = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type portStatus swarm.PortStatus
|
||||||
|
|
||||||
|
func (ps portStatus) String() string {
|
||||||
|
if len(ps.Ports) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("*:%d->%d/%s", ps.Ports[0].PublishedPort, ps.Ports[0].TargetPort, ps.Ports[0].Protocol)
|
||||||
|
for _, pConfig := range ps.Ports[1:] {
|
||||||
|
str += fmt.Sprintf(",*:%d->%d/%s", pConfig.PublishedPort, pConfig.TargetPort, pConfig.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
type tasksBySlot []swarm.Task
|
type tasksBySlot []swarm.Task
|
||||||
|
|
||||||
func (t tasksBySlot) Len() int {
|
func (t tasksBySlot) Len() int {
|
||||||
|
@ -51,7 +66,7 @@ func Print(dockerCli *command.DockerCli, ctx context.Context, tasks []swarm.Task
|
||||||
|
|
||||||
// Ignore flushing errors
|
// Ignore flushing errors
|
||||||
defer writer.Flush()
|
defer writer.Flush()
|
||||||
fmt.Fprintln(writer, strings.Join([]string{"NAME", "IMAGE", "NODE", "DESIRED STATE", "CURRENT STATE", "ERROR"}, "\t"))
|
fmt.Fprintln(writer, strings.Join([]string{"NAME", "IMAGE", "NODE", "DESIRED STATE", "CURRENT STATE", "ERROR", "PORTS"}, "\t"))
|
||||||
|
|
||||||
if err := print(writer, ctx, tasks, resolver, noTrunc); err != nil {
|
if err := print(writer, ctx, tasks, resolver, noTrunc); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -113,6 +128,7 @@ func print(out io.Writer, ctx context.Context, tasks []swarm.Task, resolver *idr
|
||||||
command.PrettyPrint(task.Status.State),
|
command.PrettyPrint(task.Status.State),
|
||||||
strings.ToLower(units.HumanDuration(time.Since(task.Status.Timestamp))),
|
strings.ToLower(units.HumanDuration(time.Since(task.Status.Timestamp))),
|
||||||
taskErr,
|
taskErr,
|
||||||
|
portStatus(task.Status.PortStatus),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -93,6 +93,7 @@ func endpointSpecFromGRPC(es *swarmapi.EndpointSpec) *types.EndpointSpec {
|
||||||
endpointSpec.Ports = append(endpointSpec.Ports, types.PortConfig{
|
endpointSpec.Ports = append(endpointSpec.Ports, types.PortConfig{
|
||||||
Name: portState.Name,
|
Name: portState.Name,
|
||||||
Protocol: types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(portState.Protocol)])),
|
Protocol: types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(portState.Protocol)])),
|
||||||
|
PublishMode: types.PortConfigPublishMode(strings.ToLower(swarmapi.PortConfig_PublishMode_name[int32(portState.PublishMode)])),
|
||||||
TargetPort: portState.TargetPort,
|
TargetPort: portState.TargetPort,
|
||||||
PublishedPort: portState.PublishedPort,
|
PublishedPort: portState.PublishedPort,
|
||||||
})
|
})
|
||||||
|
@ -112,6 +113,7 @@ func endpointFromGRPC(e *swarmapi.Endpoint) types.Endpoint {
|
||||||
endpoint.Ports = append(endpoint.Ports, types.PortConfig{
|
endpoint.Ports = append(endpoint.Ports, types.PortConfig{
|
||||||
Name: portState.Name,
|
Name: portState.Name,
|
||||||
Protocol: types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(portState.Protocol)])),
|
Protocol: types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(portState.Protocol)])),
|
||||||
|
PublishMode: types.PortConfigPublishMode(strings.ToLower(swarmapi.PortConfig_PublishMode_name[int32(portState.PublishMode)])),
|
||||||
TargetPort: portState.TargetPort,
|
TargetPort: portState.TargetPort,
|
||||||
PublishedPort: portState.PublishedPort,
|
PublishedPort: portState.PublishedPort,
|
||||||
})
|
})
|
||||||
|
|
|
@ -199,6 +199,7 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
|
||||||
spec.Endpoint.Ports = append(spec.Endpoint.Ports, &swarmapi.PortConfig{
|
spec.Endpoint.Ports = append(spec.Endpoint.Ports, &swarmapi.PortConfig{
|
||||||
Name: portConfig.Name,
|
Name: portConfig.Name,
|
||||||
Protocol: swarmapi.PortConfig_Protocol(swarmapi.PortConfig_Protocol_value[strings.ToUpper(string(portConfig.Protocol))]),
|
Protocol: swarmapi.PortConfig_Protocol(swarmapi.PortConfig_Protocol_value[strings.ToUpper(string(portConfig.Protocol))]),
|
||||||
|
PublishMode: swarmapi.PortConfig_PublishMode(swarmapi.PortConfig_PublishMode_value[strings.ToUpper(string(portConfig.PublishMode))]),
|
||||||
TargetPort: portConfig.TargetPort,
|
TargetPort: portConfig.TargetPort,
|
||||||
PublishedPort: portConfig.PublishedPort,
|
PublishedPort: portConfig.PublishedPort,
|
||||||
})
|
})
|
||||||
|
|
|
@ -63,5 +63,19 @@ func TaskFromGRPC(t swarmapi.Task) types.Task {
|
||||||
task.NetworksAttachments = append(task.NetworksAttachments, networkAttachementFromGRPC(na))
|
task.NetworksAttachments = append(task.NetworksAttachments, networkAttachementFromGRPC(na))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.Status.PortStatus == nil {
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range t.Status.PortStatus.Ports {
|
||||||
|
task.Status.PortStatus.Ports = append(task.Status.PortStatus.Ports, types.PortConfig{
|
||||||
|
Name: p.Name,
|
||||||
|
Protocol: types.PortConfigProtocol(strings.ToLower(swarmapi.PortConfig_Protocol_name[int32(p.Protocol)])),
|
||||||
|
PublishMode: types.PortConfigPublishMode(strings.ToLower(swarmapi.PortConfig_PublishMode_name[int32(p.PublishMode)])),
|
||||||
|
TargetPort: p.TargetPort,
|
||||||
|
PublishedPort: p.PublishedPort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
volumetypes "github.com/docker/docker/api/types/volume"
|
volumetypes "github.com/docker/docker/api/types/volume"
|
||||||
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/swarmkit/agent/exec"
|
"github.com/docker/swarmkit/agent/exec"
|
||||||
"github.com/docker/swarmkit/api"
|
"github.com/docker/swarmkit/api"
|
||||||
"github.com/docker/swarmkit/protobuf/ptypes"
|
"github.com/docker/swarmkit/protobuf/ptypes"
|
||||||
|
@ -136,17 +138,61 @@ func (c *containerConfig) image() string {
|
||||||
return reference.WithDefaultTag(ref).String()
|
return reference.WithDefaultTag(ref).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *containerConfig) portBindings() nat.PortMap {
|
||||||
|
portBindings := nat.PortMap{}
|
||||||
|
if c.task.Endpoint == nil {
|
||||||
|
return portBindings
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, portConfig := range c.task.Endpoint.Ports {
|
||||||
|
if portConfig.PublishMode != api.PublishModeHost {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
port := nat.Port(fmt.Sprintf("%d/%s", portConfig.TargetPort, strings.ToLower(portConfig.Protocol.String())))
|
||||||
|
binding := []nat.PortBinding{
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if portConfig.PublishedPort != 0 {
|
||||||
|
binding[0].HostPort = strconv.Itoa(int(portConfig.PublishedPort))
|
||||||
|
}
|
||||||
|
portBindings[port] = binding
|
||||||
|
}
|
||||||
|
|
||||||
|
return portBindings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerConfig) exposedPorts() map[nat.Port]struct{} {
|
||||||
|
exposedPorts := make(map[nat.Port]struct{})
|
||||||
|
if c.task.Endpoint == nil {
|
||||||
|
return exposedPorts
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, portConfig := range c.task.Endpoint.Ports {
|
||||||
|
if portConfig.PublishMode != api.PublishModeHost {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
port := nat.Port(fmt.Sprintf("%d/%s", portConfig.TargetPort, strings.ToLower(portConfig.Protocol.String())))
|
||||||
|
exposedPorts[port] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exposedPorts
|
||||||
|
}
|
||||||
|
|
||||||
func (c *containerConfig) config() *enginecontainer.Config {
|
func (c *containerConfig) config() *enginecontainer.Config {
|
||||||
config := &enginecontainer.Config{
|
config := &enginecontainer.Config{
|
||||||
Labels: c.labels(),
|
Labels: c.labels(),
|
||||||
Tty: c.spec().TTY,
|
Tty: c.spec().TTY,
|
||||||
User: c.spec().User,
|
User: c.spec().User,
|
||||||
Env: c.spec().Env,
|
Env: c.spec().Env,
|
||||||
Hostname: c.spec().Hostname,
|
Hostname: c.spec().Hostname,
|
||||||
WorkingDir: c.spec().Dir,
|
WorkingDir: c.spec().Dir,
|
||||||
Image: c.image(),
|
Image: c.image(),
|
||||||
Volumes: c.volumes(),
|
Volumes: c.volumes(),
|
||||||
Healthcheck: c.healthcheck(),
|
ExposedPorts: c.exposedPorts(),
|
||||||
|
Healthcheck: c.healthcheck(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.spec().Command) > 0 {
|
if len(c.spec().Command) > 0 {
|
||||||
|
@ -333,10 +379,11 @@ func getMountMask(m *api.Mount) string {
|
||||||
|
|
||||||
func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
||||||
hc := &enginecontainer.HostConfig{
|
hc := &enginecontainer.HostConfig{
|
||||||
Resources: c.resources(),
|
Resources: c.resources(),
|
||||||
Binds: c.binds(),
|
Binds: c.binds(),
|
||||||
Tmpfs: c.tmpfs(),
|
Tmpfs: c.tmpfs(),
|
||||||
GroupAdd: c.spec().Groups,
|
GroupAdd: c.spec().Groups,
|
||||||
|
PortBindings: c.portBindings(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.spec().DNSConfig != nil {
|
if c.spec().DNSConfig != nil {
|
||||||
|
@ -525,6 +572,10 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
|
||||||
|
|
||||||
if c.task.Endpoint != nil {
|
if c.task.Endpoint != nil {
|
||||||
for _, ePort := range c.task.Endpoint.Ports {
|
for _, ePort := range c.task.Endpoint.Ports {
|
||||||
|
if ePort.PublishMode != api.PublishModeIngress {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
svcCfg.ExposedPorts = append(svcCfg.ExposedPorts, &clustertypes.PortConfig{
|
svcCfg.ExposedPorts = append(svcCfg.ExposedPorts, &clustertypes.PortConfig{
|
||||||
Name: ePort.Name,
|
Name: ePort.Name,
|
||||||
Protocol: int32(ePort.Protocol),
|
Protocol: int32(ePort.Protocol),
|
||||||
|
|
|
@ -7,11 +7,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/events"
|
"github.com/docker/docker/api/types/events"
|
||||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/libnetwork"
|
"github.com/docker/libnetwork"
|
||||||
"github.com/docker/swarmkit/agent/exec"
|
"github.com/docker/swarmkit/agent/exec"
|
||||||
"github.com/docker/swarmkit/api"
|
"github.com/docker/swarmkit/api"
|
||||||
|
@ -69,6 +72,19 @@ func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus,
|
||||||
return parseContainerStatus(ctnr)
|
return parseContainerStatus(ctnr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *controller) PortStatus(ctx context.Context) (*api.PortStatus, error) {
|
||||||
|
ctnr, err := r.adapter.inspect(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if isUnknownContainer(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsePortStatus(ctnr)
|
||||||
|
}
|
||||||
|
|
||||||
// Update tasks a recent task update and applies it to the container.
|
// Update tasks a recent task update and applies it to the container.
|
||||||
func (r *controller) Update(ctx context.Context, t *api.Task) error {
|
func (r *controller) Update(ctx context.Context, t *api.Task) error {
|
||||||
// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
|
// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
|
||||||
|
@ -553,6 +569,64 @@ func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePortStatus(ctnr types.ContainerJSON) (*api.PortStatus, error) {
|
||||||
|
status := &api.PortStatus{}
|
||||||
|
|
||||||
|
if ctnr.NetworkSettings != nil && len(ctnr.NetworkSettings.Ports) > 0 {
|
||||||
|
exposedPorts, err := parsePortMap(ctnr.NetworkSettings.Ports)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
status.Ports = exposedPorts
|
||||||
|
}
|
||||||
|
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePortMap(portMap nat.PortMap) ([]*api.PortConfig, error) {
|
||||||
|
exposedPorts := make([]*api.PortConfig, 0, len(portMap))
|
||||||
|
|
||||||
|
for portProtocol, mapping := range portMap {
|
||||||
|
parts := strings.SplitN(string(portProtocol), "/", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid port mapping: %s", portProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.ParseUint(parts[0], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := api.ProtocolTCP
|
||||||
|
switch strings.ToLower(parts[1]) {
|
||||||
|
case "tcp":
|
||||||
|
protocol = api.ProtocolTCP
|
||||||
|
case "udp":
|
||||||
|
protocol = api.ProtocolUDP
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid protocol: %s", parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, binding := range mapping {
|
||||||
|
hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(aluzzardi): We're losing the port `name` here since
|
||||||
|
// there's no way to retrieve it back from the Engine.
|
||||||
|
exposedPorts = append(exposedPorts, &api.PortConfig{
|
||||||
|
PublishMode: api.PublishModeHost,
|
||||||
|
Protocol: protocol,
|
||||||
|
TargetPort: uint32(port),
|
||||||
|
PublishedPort: uint32(hostPort),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exposedPorts, nil
|
||||||
|
}
|
||||||
|
|
||||||
type exitError struct {
|
type exitError struct {
|
||||||
code int
|
code int
|
||||||
cause error
|
cause error
|
||||||
|
|
|
@ -32,6 +32,7 @@ func (s *DockerSwarmSuite) TestServiceUpdatePort(c *check.C) {
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
PublishedPort: 8082,
|
PublishedPort: 8082,
|
||||||
TargetPort: 8083,
|
TargetPort: 8083,
|
||||||
|
PublishMode: "ingress",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -321,12 +321,9 @@ func (s *DockerSwarmSuite) TestSwarmPublishAdd(c *check.C) {
|
||||||
out, err = d.cmdRetryOutOfSequence("service", "update", "--publish-add", "80:80", "--publish-add", "80:20", name)
|
out, err = d.cmdRetryOutOfSequence("service", "update", "--publish-add", "80:80", "--publish-add", "80:20", name)
|
||||||
c.Assert(err, checker.NotNil)
|
c.Assert(err, checker.NotNil)
|
||||||
|
|
||||||
out, err = d.cmdRetryOutOfSequence("service", "update", "--publish-add", "80:20", name)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.EndpointSpec.Ports }}", name)
|
out, err = d.Cmd("service", "inspect", "--format", "{{ .Spec.EndpointSpec.Ports }}", name)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(strings.TrimSpace(out), checker.Equals, "[{ tcp 20 80}]")
|
c.Assert(strings.TrimSpace(out), checker.Equals, "[{ tcp 80 80 ingress}]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSwarmSuite) TestSwarmServiceWithGroup(c *check.C) {
|
func (s *DockerSwarmSuite) TestSwarmServiceWithGroup(c *check.C) {
|
||||||
|
|
99
opts/port.go
Normal file
99
opts/port.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package opts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
portOptTargetPort = "target"
|
||||||
|
portOptPublishedPort = "published"
|
||||||
|
portOptProtocol = "protocol"
|
||||||
|
portOptMode = "mode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PortOpt struct {
|
||||||
|
ports []swarm.PortConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a new port value
|
||||||
|
func (p *PortOpt) Set(value string) error {
|
||||||
|
csvReader := csv.NewReader(strings.NewReader(value))
|
||||||
|
fields, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pConfig := swarm.PortConfig{}
|
||||||
|
for _, field := range fields {
|
||||||
|
parts := strings.SplitN(field, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid field %s", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.ToLower(parts[0])
|
||||||
|
value := strings.ToLower(parts[1])
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case portOptProtocol:
|
||||||
|
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
|
||||||
|
return fmt.Errorf("invalid protocol value %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pConfig.Protocol = swarm.PortConfigProtocol(value)
|
||||||
|
case portOptMode:
|
||||||
|
if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
|
||||||
|
return fmt.Errorf("invalid publish mode value %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pConfig.PublishMode = swarm.PortConfigPublishMode(value)
|
||||||
|
case portOptTargetPort:
|
||||||
|
tPort, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pConfig.TargetPort = uint32(tPort)
|
||||||
|
case portOptPublishedPort:
|
||||||
|
pPort, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pConfig.PublishedPort = uint32(pPort)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid field key %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pConfig.TargetPort == 0 {
|
||||||
|
return fmt.Errorf("missing mandatory field %q", portOptTargetPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ports = append(p.ports, pConfig)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the type of this option
|
||||||
|
func (p *PortOpt) Type() string {
|
||||||
|
return "port"
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string repr of this option
|
||||||
|
func (p *PortOpt) String() string {
|
||||||
|
ports := []string{}
|
||||||
|
for _, port := range p.ports {
|
||||||
|
repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
|
||||||
|
ports = append(ports, repr)
|
||||||
|
}
|
||||||
|
return strings.Join(ports, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the ports
|
||||||
|
func (p *PortOpt) Value() []swarm.PortConfig {
|
||||||
|
return p.ports
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue