From 9111a48009df32255cdd7b31a82cd23f2570c572 Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Mon, 9 Aug 2021 15:11:55 +0200 Subject: [PATCH] feat(k8s): manual port forward config for helm and kubernetes modules This allows users to explicitly configure the ports to forward for kubernetes and helm modules, including which local port to forward to. Example: ```yaml kind: Module type: kubernetes ... portForwards: - name: http resource: Service/my-service targetPort: 80 localPort: 8080 ``` --- core/src/plugins/kubernetes/config.ts | 33 +++ core/src/plugins/kubernetes/helm/config.ts | 18 +- .../src/plugins/kubernetes/helm/deployment.ts | 2 +- core/src/plugins/kubernetes/helm/status.ts | 2 +- .../kubernetes/kubernetes-module/config.ts | 18 +- .../kubernetes/kubernetes-module/handlers.ts | 2 +- core/src/plugins/kubernetes/port-forward.ts | 17 +- .../src/plugins/kubernetes/port-forward.ts | 219 ++++++++++------ docs/reference/module-types/helm.md | 67 +++++ docs/reference/module-types/kubernetes.md | 233 +++++++++++------- 10 files changed, 433 insertions(+), 178 deletions(-) diff --git a/core/src/plugins/kubernetes/config.ts b/core/src/plugins/kubernetes/config.ts index 2b5e42a589..1fb943870a 100644 --- a/core/src/plugins/kubernetes/config.ts +++ b/core/src/plugins/kubernetes/config.ts @@ -780,6 +780,39 @@ export const hotReloadArgsSchema = () => .description("If specified, overrides the arguments for the main container when running in hot-reload mode.") .example(["nodemon", "my-server.js"]) +export interface PortForwardSpec { + name?: string + resource: string + targetPort: number + localPort?: number +} + +const portForwardSpecSchema = () => + joi.object().keys({ + name: joiIdentifier().description("An identifier to describe the port forward."), + resource: joi + .string() + .required() + .description( + "The full resource kind and name to forward to, e.g. Service/my-service or Deployment/my-deployment. Note that Garden will not validate this ahead of attempting to start the port forward, so you need to make sure this is correctly set. The types of resources supported will match that of the `kubectl port-forward` CLI command." + ), + targetPort: joi.number().integer().required().description("The port number on the remote resource to forward to."), + localPort: joi + .number() + .integer() + .description( + "The _preferred_ local port to forward from. If none is set, a random port is chosen. If the specified port is not available, a warning is shown and a random port chosen instead." + ), + }) + +export const portForwardsSchema = () => + joi + .array() + .items(portForwardSpecSchema()) + .description( + "Manually specify port forwards that Garden should set up when deploying in dev or watch mode. If specified, these override the auto-detection of forwardable ports, so you'll need to specify the full list of port forwards to create." + ) + const runPodSpecWhitelistDescription = runPodSpecIncludeFields.map((f) => `* \`${f}\``).join("\n") export const kubernetesTaskSchema = () => diff --git a/core/src/plugins/kubernetes/helm/config.ts b/core/src/plugins/kubernetes/helm/config.ts index 9e328cafd3..7aededeca9 100644 --- a/core/src/plugins/kubernetes/helm/config.ts +++ b/core/src/plugins/kubernetes/helm/config.ts @@ -34,6 +34,8 @@ import { containerModuleSchema, hotReloadArgsSchema, serviceResourceDescription, + portForwardsSchema, + PortForwardSpec, } from "../config" import { posix } from "path" import { runPodSpecIncludeFields } from "../run" @@ -57,6 +59,7 @@ export interface HelmServiceSpec { dependencies: string[] devMode?: KubernetesDevModeSpec namespace?: string + portForwards?: PortForwardSpec[] releaseName?: string repo?: string serviceResource?: ServiceResourceSpec @@ -169,7 +172,15 @@ export const helmModuleSpecSchema = () => "List of names of services that should be deployed before this chart." ), devMode: kubernetesDevModeSchema(), + include: joiModuleIncludeDirective(dedent` + If neither \`include\` nor \`exclude\` is set, and the module has local chart sources, Garden + automatically sets \`include\` to: \`["*", "charts/**/*", "templates/**/*"]\`. + + If neither \`include\` nor \`exclude\` is set and the module specifies a remote chart, Garden + automatically sets \`ìnclude\` to \`[]\`. + `), namespace: namespaceNameSchema(), + portForwards: portForwardsSchema(), releaseName: joiIdentifier().description( "Optionally override the release name used when installing (defaults to the module name)." ), @@ -190,13 +201,6 @@ export const helmModuleSpecSchema = () => deline`Set this to true if the chart should only be built, but not deployed as a service. Use this, for example, if the chart should only be used as a base for other modules.` ), - include: joiModuleIncludeDirective(dedent` - If neither \`include\` nor \`exclude\` is set, and the module has local chart sources, Garden - automatically sets \`include\` to: \`["*", "charts/**/*", "templates/**/*"]\`. - - If neither \`include\` nor \`exclude\` is set and the module specifies a remote chart, Garden - automatically sets \`ìnclude\` to \`[]\`. - `), tasks: joiSparseArray(helmTaskSchema()).description("The task definitions for this module."), tests: joiSparseArray(helmTestSchema()).description("The test suite definitions for this module."), timeout: joi diff --git a/core/src/plugins/kubernetes/helm/deployment.ts b/core/src/plugins/kubernetes/helm/deployment.ts index 51e42e2e1f..596a03e428 100644 --- a/core/src/plugins/kubernetes/helm/deployment.ts +++ b/core/src/plugins/kubernetes/helm/deployment.ts @@ -127,7 +127,7 @@ export async function deployHelmService({ timeoutSec: module.spec.timeout, }) - const forwardablePorts = getForwardablePorts(manifests) + const forwardablePorts = getForwardablePorts(manifests, service) // Make sure port forwards work after redeployment killPortForwards(service, forwardablePorts || [], log) diff --git a/core/src/plugins/kubernetes/helm/status.ts b/core/src/plugins/kubernetes/helm/status.ts index aedda49e87..1617507353 100644 --- a/core/src/plugins/kubernetes/helm/status.ts +++ b/core/src/plugins/kubernetes/helm/status.ts @@ -67,7 +67,7 @@ export async function getServiceStatus({ if (state !== "missing") { const deployedResources = await getDeployedResources({ ctx: k8sCtx, module, releaseName, log }) - forwardablePorts = getForwardablePorts(deployedResources) + forwardablePorts = getForwardablePorts(deployedResources, service) if (state === "ready" && devMode && service.spec.devMode) { // Need to start the dev-mode sync here, since the deployment handler won't be called. diff --git a/core/src/plugins/kubernetes/kubernetes-module/config.ts b/core/src/plugins/kubernetes/kubernetes-module/config.ts index 99edbfd109..ef13a93c92 100644 --- a/core/src/plugins/kubernetes/kubernetes-module/config.ts +++ b/core/src/plugins/kubernetes/kubernetes-module/config.ts @@ -25,6 +25,8 @@ import { containerModuleSchema, hotReloadArgsSchema, serviceResourceDescription, + portForwardsSchema, + PortForwardSpec, } from "../config" import { ContainerModule } from "../../container/config" import { kubernetesDevModeSchema, KubernetesDevModeSpec } from "../dev-mode" @@ -41,8 +43,9 @@ export interface KubernetesServiceSpec { dependencies: string[] devMode?: KubernetesDevModeSpec files: string[] - namespace?: string manifests: KubernetesResource[] + namespace?: string + portForwards?: PortForwardSpec[] serviceResource?: ServiceResourceSpec tasks: KubernetesTaskSpec[] tests: KubernetesTestSpec[] @@ -71,11 +74,7 @@ export const kubernetesModuleSpecSchema = () => joi.object().keys({ build: baseBuildSpecSchema(), dependencies: dependenciesSchema(), - manifests: joiSparseArray(kubernetesResourceSchema()).description( - deline` - List of Kubernetes resource manifests to deploy. Use this instead of the \`files\` field if you need to - resolve template strings in any of the manifests.` - ), + devMode: kubernetesDevModeSchema(), files: joiSparseArray(joi.posixPath().subPathOnly()).description( "POSIX-style paths to YAML files to load manifests from. Each can contain multiple manifests, and can include any Garden template strings, which will be resolved before applying the manifests." ), @@ -83,8 +82,13 @@ export const kubernetesModuleSpecSchema = () => If neither \`include\` nor \`exclude\` is set, Garden automatically sets \`include\` to equal the \`files\` directive so that only the Kubernetes manifests get included. `), + manifests: joiSparseArray(kubernetesResourceSchema()).description( + deline` + List of Kubernetes resource manifests to deploy. Use this instead of the \`files\` field if you need to + resolve template strings in any of the manifests.` + ), namespace: namespaceNameSchema(), - devMode: kubernetesDevModeSchema(), + portForwards: portForwardsSchema(), serviceResource: serviceResourceSchema() .description( dedent` diff --git a/core/src/plugins/kubernetes/kubernetes-module/handlers.ts b/core/src/plugins/kubernetes/kubernetes-module/handlers.ts index d7f61f44b6..9edf03911d 100644 --- a/core/src/plugins/kubernetes/kubernetes-module/handlers.ts +++ b/core/src/plugins/kubernetes/kubernetes-module/handlers.ts @@ -93,7 +93,7 @@ export async function getKubernetesServiceStatus({ let { state, remoteResources } = await compareDeployedResources(k8sCtx, api, namespace, prepareResult.manifests, log) - const forwardablePorts = getForwardablePorts(remoteResources) + const forwardablePorts = getForwardablePorts(remoteResources, service) if (state === "ready" && devMode && service.spec.devMode) { // Need to start the dev-mode sync here, since the deployment handler won't be called. diff --git a/core/src/plugins/kubernetes/port-forward.ts b/core/src/plugins/kubernetes/port-forward.ts index 85a78931de..de1f7ec549 100644 --- a/core/src/plugins/kubernetes/port-forward.ts +++ b/core/src/plugins/kubernetes/port-forward.ts @@ -24,6 +24,8 @@ import { isBuiltIn, matchSelector } from "./util" import { LogEntry } from "../../logger/log-entry" import { RuntimeError } from "../../exceptions" import execa = require("execa") +import { KubernetesService } from "./kubernetes-module/config" +import { HelmService } from "./helm/config" // TODO: implement stopPortForward handler @@ -209,7 +211,20 @@ function getTargetResource(service: GardenService, targetName?: string) { /** * Returns a list of forwardable ports based on the specified resources. */ -export function getForwardablePorts(resources: KubernetesResource[]) { +export function getForwardablePorts( + resources: KubernetesResource[], + parentService: KubernetesService | HelmService | undefined +): ForwardablePort[] { + if (parentService?.spec.portForwards) { + return parentService?.spec.portForwards.map((p) => ({ + name: p.name, + protocol: "TCP", + targetName: p.resource, + targetPort: p.targetPort, + preferredLocalPort: p.localPort, + })) + } + const ports: ForwardablePort[] = [] // Start by getting ports defined by Service resources diff --git a/core/test/unit/src/plugins/kubernetes/port-forward.ts b/core/test/unit/src/plugins/kubernetes/port-forward.ts index d099808844..417fd87c5b 100644 --- a/core/test/unit/src/plugins/kubernetes/port-forward.ts +++ b/core/test/unit/src/plugins/kubernetes/port-forward.ts @@ -7,22 +7,26 @@ */ import { expect } from "chai" +import { KubernetesService } from "../../../../../src/plugins/kubernetes/kubernetes-module/config" import { getForwardablePorts } from "../../../../../src/plugins/kubernetes/port-forward" describe("getForwardablePorts", () => { it("returns all ports for Service resources", () => { - const ports = getForwardablePorts([ - { - apiVersion: "v1", - kind: "Service", - metadata: { - name: "foo", - }, - spec: { - ports: [{ port: 12345 }], + const ports = getForwardablePorts( + [ + { + apiVersion: "v1", + kind: "Service", + metadata: { + name: "foo", + }, + spec: { + ports: [{ port: 12345 }], + }, }, - }, - ]) + ], + undefined + ) expect(ports).to.eql([ { @@ -34,27 +38,82 @@ describe("getForwardablePorts", () => { ]) }) - it("returns all ports for Deployment resources", () => { - const ports = getForwardablePorts([ - { - apiVersion: "apps/v1", - kind: "Deployment", - metadata: { - name: "foo", - }, - spec: { - template: { - spec: { - containers: [ - { - ports: [{ containerPort: 12345 }], - }, - ], - }, + it("returns explicitly configured port forwards if set", () => { + const service: KubernetesService = { + name: "foo", + config: {}, // Not needed here + disabled: false, + module: {}, // Not needed here + sourceModule: {}, // Not needed here + spec: { + dependencies: [], + files: [], + manifests: [], + portForwards: [ + { + name: "test", + resource: "Service/test", + targetPort: 999, + localPort: 9999, + }, + ], + tasks: [], + tests: [], + }, + version: {}, // Not needed here + } + + const ports = getForwardablePorts( + [ + { + apiVersion: "v1", + kind: "Service", + metadata: { + name: "foo", + }, + spec: { + ports: [{ port: 12345 }], }, }, + ], + service + ) + + expect(ports).to.eql([ + { + name: "test", + protocol: "TCP", + targetName: "Service/test", + targetPort: 999, + preferredLocalPort: 9999, }, ]) + }) + + it("returns all ports for Deployment resources", () => { + const ports = getForwardablePorts( + [ + { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { + name: "foo", + }, + spec: { + template: { + spec: { + containers: [ + { + ports: [{ containerPort: 12345 }], + }, + ], + }, + }, + }, + }, + ], + undefined + ) expect(ports).to.eql([ { @@ -67,26 +126,29 @@ describe("getForwardablePorts", () => { }) it("returns all ports for DaemonSet resources", () => { - const ports = getForwardablePorts([ - { - apiVersion: "apps/v1", - kind: "DaemonSet", - metadata: { - name: "foo", - }, - spec: { - template: { - spec: { - containers: [ - { - ports: [{ containerPort: 12345 }], - }, - ], + const ports = getForwardablePorts( + [ + { + apiVersion: "apps/v1", + kind: "DaemonSet", + metadata: { + name: "foo", + }, + spec: { + template: { + spec: { + containers: [ + { + ports: [{ containerPort: 12345 }], + }, + ], + }, }, }, }, - }, - ]) + ], + undefined + ) expect(ports).to.eql([ { @@ -99,44 +161,47 @@ describe("getForwardablePorts", () => { }) it("omits a Deployment port that is already pointed to by a Service resource", () => { - const ports = getForwardablePorts([ - { - apiVersion: "v1", - kind: "Service", - metadata: { - name: "foo", - }, - spec: { - selector: { - app: "foo", + const ports = getForwardablePorts( + [ + { + apiVersion: "v1", + kind: "Service", + metadata: { + name: "foo", }, - ports: [{ port: 12345, targetPort: 12346 }], - }, - }, - { - apiVersion: "apps/v1", - kind: "Deployment", - metadata: { - name: "foo", - }, - spec: { - template: { - metadata: { - labels: { - app: "foo", - }, + spec: { + selector: { + app: "foo", }, - spec: { - containers: [ - { - ports: [{ containerPort: 12346 }], + ports: [{ port: 12345, targetPort: 12346 }], + }, + }, + { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { + name: "foo", + }, + spec: { + template: { + metadata: { + labels: { + app: "foo", }, - ], + }, + spec: { + containers: [ + { + ports: [{ containerPort: 12346 }], + }, + ], + }, }, }, }, - }, - ]) + ], + undefined + ) expect(ports).to.eql([ { diff --git a/docs/reference/module-types/helm.md b/docs/reference/module-types/helm.md index cae598627b..f5fe952c17 100644 --- a/docs/reference/module-types/helm.md +++ b/docs/reference/module-types/helm.md @@ -215,6 +215,25 @@ devMode: # numbers and dashes, must start with a letter, and cannot end with a dash) and must not be longer than 63 characters. namespace: +# Manually specify port forwards that Garden should set up when deploying in dev or watch mode. If specified, these +# override the auto-detection of forwardable ports, so you'll need to specify the full list of port forwards to +# create. +portForwards: + - # An identifier to describe the port forward. + name: + + # The full resource kind and name to forward to, e.g. Service/my-service or Deployment/my-deployment. Note that + # Garden will not validate this ahead of attempting to start the port forward, so you need to make sure this is + # correctly set. The types of resources supported will match that of the `kubectl port-forward` CLI command. + resource: + + # The port number on the remote resource to forward to. + targetPort: + + # The _preferred_ local port to forward from. If none is set, a random port is chosen. If the specified port is + # not available, a warning is shown and a random port chosen instead. + localPort: + # Optionally override the release name used when installing (defaults to the module name). releaseName: @@ -997,6 +1016,54 @@ A valid Kubernetes namespace name. Must be a valid RFC1035/RFC1123 (DNS) label ( | -------- | -------- | | `string` | No | +### `portForwards[]` + +Manually specify port forwards that Garden should set up when deploying in dev or watch mode. If specified, these override the auto-detection of forwardable ports, so you'll need to specify the full list of port forwards to create. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `portForwards[].name` + +[portForwards](#portforwards) > name + +An identifier to describe the port forward. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `portForwards[].resource` + +[portForwards](#portforwards) > resource + +The full resource kind and name to forward to, e.g. Service/my-service or Deployment/my-deployment. Note that Garden will not validate this ahead of attempting to start the port forward, so you need to make sure this is correctly set. The types of resources supported will match that of the `kubectl port-forward` CLI command. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `portForwards[].targetPort` + +[portForwards](#portforwards) > targetPort + +The port number on the remote resource to forward to. + +| Type | Required | +| -------- | -------- | +| `number` | Yes | + +### `portForwards[].localPort` + +[portForwards](#portforwards) > localPort + +The _preferred_ local port to forward from. If none is set, a random port is chosen. If the specified port is not available, a warning is shown and a random port chosen instead. + +| Type | Required | +| -------- | -------- | +| `number` | No | + ### `releaseName` Optionally override the release name used when installing (defaults to the module name). diff --git a/docs/reference/module-types/kubernetes.md b/docs/reference/module-types/kubernetes.md index f60ade3c83..0ab364a2cf 100644 --- a/docs/reference/module-types/kubernetes.md +++ b/docs/reference/module-types/kubernetes.md @@ -140,27 +140,6 @@ variables: # executed before this service is deployed. dependencies: [] -# List of Kubernetes resource manifests to deploy. Use this instead of the `files` field if you need to resolve -# template strings in any of the manifests. -manifests: - - # The API version of the resource. - apiVersion: - - # The kind of the resource. - kind: - - metadata: - # The name of the resource. - name: - -# POSIX-style paths to YAML files to load manifests from. Each can contain multiple manifests, and can include any -# Garden template strings, which will be resolved before applying the manifests. -files: [] - -# A valid Kubernetes namespace name. Must be a valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, -# numbers and dashes, must start with a letter, and cannot end with a dash) and must not be longer than 63 characters. -namespace: - # Specifies which files or directories to sync to which paths inside the running containers of the service when it's # in dev mode, and overrides for the container command and/or arguments. # @@ -218,6 +197,46 @@ devMode: # workload is used. containerName: +# POSIX-style paths to YAML files to load manifests from. Each can contain multiple manifests, and can include any +# Garden template strings, which will be resolved before applying the manifests. +files: [] + +# List of Kubernetes resource manifests to deploy. Use this instead of the `files` field if you need to resolve +# template strings in any of the manifests. +manifests: + - # The API version of the resource. + apiVersion: + + # The kind of the resource. + kind: + + metadata: + # The name of the resource. + name: + +# A valid Kubernetes namespace name. Must be a valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, +# numbers and dashes, must start with a letter, and cannot end with a dash) and must not be longer than 63 characters. +namespace: + +# Manually specify port forwards that Garden should set up when deploying in dev or watch mode. If specified, these +# override the auto-detection of forwardable ports, so you'll need to specify the full list of port forwards to +# create. +portForwards: + - # An identifier to describe the port forward. + name: + + # The full resource kind and name to forward to, e.g. Service/my-service or Deployment/my-deployment. Note that + # Garden will not validate this ahead of attempting to start the port forward, so you need to make sure this is + # correctly set. The types of resources supported will match that of the `kubectl port-forward` CLI command. + resource: + + # The port number on the remote resource to forward to. + targetPort: + + # The _preferred_ local port to forward from. If none is set, a random port is chosen. If the specified port is + # not available, a warning is shown and a random port chosen instead. + localPort: + # The Deployment, DaemonSet or StatefulSet or Pod that Garden should regard as the _Garden service_ in this module # (not to be confused with Kubernetes Service resources). # @@ -709,68 +728,6 @@ The names of any services that this service depends on at runtime, and the names | --------------- | ------- | -------- | | `array[string]` | `[]` | No | -### `manifests[]` - -List of Kubernetes resource manifests to deploy. Use this instead of the `files` field if you need to resolve template strings in any of the manifests. - -| Type | Default | Required | -| --------------- | ------- | -------- | -| `array[object]` | `[]` | No | - -### `manifests[].apiVersion` - -[manifests](#manifests) > apiVersion - -The API version of the resource. - -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `manifests[].kind` - -[manifests](#manifests) > kind - -The kind of the resource. - -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `manifests[].metadata` - -[manifests](#manifests) > metadata - -| Type | Required | -| -------- | -------- | -| `object` | Yes | - -### `manifests[].metadata.name` - -[manifests](#manifests) > [metadata](#manifestsmetadata) > name - -The name of the resource. - -| Type | Required | -| -------- | -------- | -| `string` | Yes | - -### `files[]` - -POSIX-style paths to YAML files to load manifests from. Each can contain multiple manifests, and can include any Garden template strings, which will be resolved before applying the manifests. - -| Type | Default | Required | -| ------------------ | ------- | -------- | -| `array[posixPath]` | `[]` | No | - -### `namespace` - -A valid Kubernetes namespace name. Must be a valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, numbers and dashes, must start with a letter, and cannot end with a dash) and must not be longer than 63 characters. - -| Type | Required | -| -------- | -------- | -| `string` | No | - ### `devMode` Specifies which files or directories to sync to which paths inside the running containers of the service when it's in dev mode, and overrides for the container command and/or arguments. @@ -934,6 +891,116 @@ Optionally specify the name of a specific container to sync to. If not specified | -------- | -------- | | `string` | No | +### `files[]` + +POSIX-style paths to YAML files to load manifests from. Each can contain multiple manifests, and can include any Garden template strings, which will be resolved before applying the manifests. + +| Type | Default | Required | +| ------------------ | ------- | -------- | +| `array[posixPath]` | `[]` | No | + +### `manifests[]` + +List of Kubernetes resource manifests to deploy. Use this instead of the `files` field if you need to resolve template strings in any of the manifests. + +| Type | Default | Required | +| --------------- | ------- | -------- | +| `array[object]` | `[]` | No | + +### `manifests[].apiVersion` + +[manifests](#manifests) > apiVersion + +The API version of the resource. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `manifests[].kind` + +[manifests](#manifests) > kind + +The kind of the resource. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `manifests[].metadata` + +[manifests](#manifests) > metadata + +| Type | Required | +| -------- | -------- | +| `object` | Yes | + +### `manifests[].metadata.name` + +[manifests](#manifests) > [metadata](#manifestsmetadata) > name + +The name of the resource. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `namespace` + +A valid Kubernetes namespace name. Must be a valid RFC1035/RFC1123 (DNS) label (may contain lowercase letters, numbers and dashes, must start with a letter, and cannot end with a dash) and must not be longer than 63 characters. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `portForwards[]` + +Manually specify port forwards that Garden should set up when deploying in dev or watch mode. If specified, these override the auto-detection of forwardable ports, so you'll need to specify the full list of port forwards to create. + +| Type | Required | +| --------------- | -------- | +| `array[object]` | No | + +### `portForwards[].name` + +[portForwards](#portforwards) > name + +An identifier to describe the port forward. + +| Type | Required | +| -------- | -------- | +| `string` | No | + +### `portForwards[].resource` + +[portForwards](#portforwards) > resource + +The full resource kind and name to forward to, e.g. Service/my-service or Deployment/my-deployment. Note that Garden will not validate this ahead of attempting to start the port forward, so you need to make sure this is correctly set. The types of resources supported will match that of the `kubectl port-forward` CLI command. + +| Type | Required | +| -------- | -------- | +| `string` | Yes | + +### `portForwards[].targetPort` + +[portForwards](#portforwards) > targetPort + +The port number on the remote resource to forward to. + +| Type | Required | +| -------- | -------- | +| `number` | Yes | + +### `portForwards[].localPort` + +[portForwards](#portforwards) > localPort + +The _preferred_ local port to forward from. If none is set, a random port is chosen. If the specified port is not available, a warning is shown and a random port chosen instead. + +| Type | Required | +| -------- | -------- | +| `number` | No | + ### `serviceResource` The Deployment, DaemonSet or StatefulSet or Pod that Garden should regard as the _Garden service_ in this module (not to be confused with Kubernetes Service resources).