Skip to content

Commit

Permalink
feat(container): allow specifying preferred local port for port-forwards
Browse files Browse the repository at this point in the history
This adds a `localPort` field to the `services[].ports` field, to make
it easier to control the local port used for port forwards. This is
handy for a variety of reasons, one of which is to have a predictable
port for debuggers.
  • Loading branch information
edvald authored and thsig committed Jun 8, 2021
1 parent 26d4d12 commit ba9838b
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 12 deletions.
10 changes: 10 additions & 0 deletions core/src/plugins/container/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface ServicePortSpec {
name: string
protocol: ServicePortProtocol
containerPort: number
localPort?: number
// Defaults to containerPort
servicePort: number
hostPort?: number
Expand Down Expand Up @@ -387,6 +388,15 @@ export const portSchema = () =>
The service port maps to the container port:
\`servicePort:80 -> containerPort:8080 -> process:8080\``),
localPort: joi
.number()
.example(10080)
.description(
dedent`
Specify a preferred local port to attach to when creating a port-forward to the service port. If this port is
busy, a warning will be shown and an alternative port chosen.
`
),
servicePort: joi
.number()
.default((context) => context.containerPort)
Expand Down
1 change: 1 addition & 0 deletions core/src/plugins/kubernetes/container/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export async function getContainerServiceStatus({
name: p.name,
protocol: "TCP",
targetPort: p.servicePort,
preferredLocalPort: p.localPort,
// TODO: this needs to be configurable
// urlProtocol: "http",
}
Expand Down
37 changes: 25 additions & 12 deletions core/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ interface PortProxy {
spec: ForwardablePort
}

const defaultLocalIp = "127.0.0.1"

const activeProxies: { [key: string]: PortProxy } = {}

registerCleanupFunction("kill-service-port-proxies", () => {
Expand Down Expand Up @@ -77,14 +79,6 @@ async function createProxy(
const key = getPortKey(service, spec)
let fwd: GetPortForwardResult | null = null

// get preferred IP from db
const localAddress = await LocalAddress.resolve({
projectName: garden.projectName,
moduleName: service.module.name,
serviceName: service.name,
hostname: getHostname(service, spec),
})

const getPortForward = async () => {
if (fwd) {
return fwd
Expand Down Expand Up @@ -203,18 +197,31 @@ async function createProxy(
})
}

let localIp = localAddress.getIp()
let localIp = defaultLocalIp
let localPort: number | undefined
const preferredLocalPort = spec.preferredLocalPort || spec.targetPort

if (!spec.preferredLocalPort) {
// Only try a non-default IP if a preferred port isn't set
const preferredLocalAddress = await LocalAddress.resolve({
projectName: garden.projectName,
moduleName: service.module.name,
serviceName: service.name,
hostname: getHostname(service, spec),
})

localIp = preferredLocalAddress.getIp()
}

while (true) {
try {
localPort = await getPort({ host: localIp, port: spec.targetPort })
localPort = await getPort({ host: localIp, port: preferredLocalPort })
} catch (err) {
if (err.errno === "EADDRNOTAVAIL") {
// If we're not allowed to bind to other 127.x.x.x addresses, we fall back to localhost. This will almost always
// be the case on Mac, until we come up with something more clever (that doesn't require sudo).
localIp = "127.0.0.1"
localPort = await getPort({ host: localIp, port: spec.targetPort })
localIp = defaultLocalIp
localPort = await getPort({ host: localIp, port: preferredLocalPort })
} else {
throw err
}
Expand Down Expand Up @@ -244,6 +251,12 @@ async function createProxy(
})

if (started) {
if (spec.preferredLocalPort && (localIp !== defaultLocalIp || localPort !== spec.preferredLocalPort)) {
log.warn(
chalk.yellow(`→ Unable to bind port forward ${key} to preferred local port ${spec.preferredLocalPort}`)
)
}

return { key, server, service, spec, localPort, localUrl }
} else {
// Need to retry on different port
Expand Down
2 changes: 2 additions & 0 deletions core/src/types/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const serviceIngressSchema = () =>
export interface ForwardablePort {
name?: string
// TODO: support other protocols
preferredLocalPort?: number
protocol: "TCP"
targetName?: string
targetPort: number
Expand All @@ -167,6 +168,7 @@ export const forwardablePortKeys = () => ({
name: joiIdentifier().description(
"A descriptive name for the port. Should correspond to user-configured ports where applicable."
),
preferredLocalPort: joi.number().integer().description("The preferred local port to use for forwarding."),
protocol: joi.string().allow("TCP").default("TCP").description("The protocol of the port."),
targetName: joi.string().description("The target name/hostname to forward to (defaults to the service name)."),
targetPort: joi.number().integer().required().description("The target port on the service."),
Expand Down
21 changes: 21 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ deployments:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:

# The preferred local port to use for forwarding.
preferredLocalPort:

# The protocol of the port.
protocol:

Expand Down Expand Up @@ -485,6 +488,9 @@ serviceStatuses:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:
# The preferred local port to use for forwarding.
preferredLocalPort:
# The protocol of the port.
protocol:
Expand Down Expand Up @@ -593,6 +599,9 @@ Examples:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:
# The preferred local port to use for forwarding.
preferredLocalPort:
# The protocol of the port.
protocol:
Expand Down Expand Up @@ -752,6 +761,9 @@ deployments:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:
# The preferred local port to use for forwarding.
preferredLocalPort:
# The protocol of the port.
protocol:
Expand Down Expand Up @@ -2351,6 +2363,9 @@ services:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:

# The preferred local port to use for forwarding.
preferredLocalPort:

# The protocol of the port.
protocol:

Expand Down Expand Up @@ -2945,6 +2960,9 @@ deployments:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:
# The preferred local port to use for forwarding.
preferredLocalPort:
# The protocol of the port.
protocol:
Expand Down Expand Up @@ -3510,6 +3528,9 @@ deployments:
- # A descriptive name for the port. Should correspond to user-configured ports where applicable.
name:
# The preferred local port to use for forwarding.
preferredLocalPort:
# The protocol of the port.
protocol:
Expand Down
24 changes: 24 additions & 0 deletions docs/reference/module-types/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ services:
# `servicePort:80 -> containerPort:8080 -> process:8080`
containerPort:

# Specify a preferred local port to attach to when creating a port-forward to the service port. If this port
# is
# busy, a warning will be shown and an alternative port chosen.
localPort:

# The port exposed on the service. Defaults to `containerPort` if not specified.
# This is the port you use when calling a service from another service within the cluster. For example, if
# your service name is my-service and the service port is 8090, you would call it with:
Expand Down Expand Up @@ -1556,6 +1561,25 @@ services:
- containerPort: 8080
```

### `services[].ports[].localPort`

[services](#services) > [ports](#servicesports) > localPort

Specify a preferred local port to attach to when creating a port-forward to the service port. If this port is
busy, a warning will be shown and an alternative port chosen.

| Type | Required |
| -------- | -------- |
| `number` | No |

Example:

```yaml
services:
- ports:
- localPort: 10080
```

### `services[].ports[].servicePort`

[services](#services) > [ports](#servicesports) > servicePort
Expand Down
24 changes: 24 additions & 0 deletions docs/reference/module-types/maven-container.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ services:
# `servicePort:80 -> containerPort:8080 -> process:8080`
containerPort:

# Specify a preferred local port to attach to when creating a port-forward to the service port. If this port
# is
# busy, a warning will be shown and an alternative port chosen.
localPort:

# The port exposed on the service. Defaults to `containerPort` if not specified.
# This is the port you use when calling a service from another service within the cluster. For example, if
# your service name is my-service and the service port is 8090, you would call it with:
Expand Down Expand Up @@ -1564,6 +1569,25 @@ services:
- containerPort: 8080
```

### `services[].ports[].localPort`

[services](#services) > [ports](#servicesports) > localPort

Specify a preferred local port to attach to when creating a port-forward to the service port. If this port is
busy, a warning will be shown and an alternative port chosen.

| Type | Required |
| -------- | -------- |
| `number` | No |

Example:

```yaml
services:
- ports:
- localPort: 10080
```

### `services[].ports[].servicePort`

[services](#services) > [ports](#servicesports) > servicePort
Expand Down

0 comments on commit ba9838b

Please sign in to comment.