From 0652123bc7a28dd6a895aa56987328624a5a7625 Mon Sep 17 00:00:00 2001 From: Pavel Sviderski Date: Tue, 10 Dec 2024 19:21:52 +1000 Subject: [PATCH] encode and decode published ports using container labels --- internal/api/port.go | 4 +-- internal/cli/client/service.go | 12 ++++++++ test/e2e/service_test.go | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/internal/api/port.go b/internal/api/port.go index 984439b8..7965b86d 100644 --- a/internal/api/port.go +++ b/internal/api/port.go @@ -27,9 +27,9 @@ type PortSpec struct { PublishedPort uint16 // ContainerPort is the port inside the container that the service listens on. ContainerPort uint16 - // Protocol specifies the network protocol. Default is ProtocolHTTPS if Hostname is set, ProtocolTCP otherwise. + // Protocol specifies the network protocol. Protocol string - // Mode specifies how the port is published. Default is PortModeIngress. + // Mode specifies how the port is published. Mode string } diff --git a/internal/cli/client/service.go b/internal/cli/client/service.go index 16ccadb6..5095bb0a 100644 --- a/internal/cli/client/service.go +++ b/internal/cli/client/service.go @@ -238,6 +238,18 @@ func (cli *Client) runContainer( config.Labels[api.LabelServiceMode] = api.ServiceModeGlobal } + if len(spec.Ports) > 0 { + encodedPorts := make([]string, len(spec.Ports)) + for i, p := range spec.Ports { + encodedPorts[i], err = p.String() + if err != nil { + return resp, fmt.Errorf("encode service port spec: %w", err) + } + } + + config.Labels[api.LabelServicePorts] = strings.Join(encodedPorts, ",") + } + hostConfig := &container.HostConfig{ Init: spec.Container.Init, } diff --git a/test/e2e/service_test.go b/test/e2e/service_test.go index f802d98c..15f7a185 100644 --- a/test/e2e/service_test.go +++ b/test/e2e/service_test.go @@ -73,6 +73,61 @@ func TestRunService(t *testing.T) { assert.True(t, found) }) + t.Run("1 replica with ports", func(t *testing.T) { + t.Parallel() + + name := "busybox-1-replica-ports" + t.Cleanup(func() { + err := cli.RemoveService(ctx, name) + if !dockerclient.IsErrNotFound(err) { + require.NoError(t, err) + } + + _, err = cli.InspectService(ctx, name) + require.ErrorIs(t, err, client.ErrNotFound) + }) + + spec := api.ServiceSpec{ + Name: name, + Mode: api.ServiceModeReplicated, + Container: api.ContainerSpec{ + Command: []string{"sleep", "infinity"}, + Image: "busybox:latest", + }, + Ports: []api.PortSpec{ + { + Hostname: "https.example.com", + ContainerPort: 8080, + Protocol: api.ProtocolHTTPS, + Mode: api.PortModeIngress, + }, + { + PublishedPort: 8000, + ContainerPort: 8080, + Protocol: api.ProtocolTCP, + Mode: api.PortModeIngress, + }, + { + PublishedPort: 8000, + ContainerPort: 8000, + Protocol: api.ProtocolUDP, + Mode: api.PortModeHost, + }, + }, + } + resp, err := cli.RunService(ctx, spec) + require.NoError(t, err) + + svc, err := cli.InspectService(ctx, resp.ID) + require.NoError(t, err) + require.Len(t, svc.Containers, 1) + ctr := svc.Containers[0].Container + + ports, err := ctr.ServicePorts() + require.NoError(t, err) + assert.Equal(t, spec.Ports, ports) + }) + t.Run("global mode", func(t *testing.T) { t.Parallel()