Skip to content

Commit

Permalink
feat(exec): add --target flag in exec command
Browse files Browse the repository at this point in the history
introduces a flag --target in exec command. This is useful when a
Deploy action consists of multiple underlying components and user
wants to target a specific one. At the moment, this flag is only
supported for kubernetes-exec and container-exec.

When a pod has multiple containers, user can use target flag to exec
into a specific container. Additionally, if no container name is
provided, it respects the annotation kubectl.kubernetes.io/default-container
and defaults to that container.

The existing behavior is still the default: it picks the first container
in Pod.
  • Loading branch information
shumailxyz committed Jul 18, 2023
1 parent 35ef0bd commit ac70427
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 5 deletions.
9 changes: 9 additions & 0 deletions core/src/commands/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { executeAction } from "../graph/actions"
import { NotFoundError } from "../exceptions"
import { DeployStatus } from "../plugin/handlers/Deploy/get-status"
import { createActionLog } from "../logger/log-entry"
import { K8_POD_DEFAULT_CONTAINER_ANNOTATION_KEY } from "../plugins/kubernetes/run"

const execArgs = {
deploy: new StringParameter({
Expand All @@ -40,6 +41,12 @@ const execOpts = {
cliDefault: true,
cliOnly: true,
}),
target: new StringParameter({
help: `Specify name of the target if a Deploy action consists of multiple components. _NOTE: This option is only relevant in certain scenarios and will be ignored otherwise._ For Kubernetes deploy actions, this is useful if a Deployment includes multiple containers, such as sidecar containers. By default, the container with \`${K8_POD_DEFAULT_CONTAINER_ANNOTATION_KEY}\` annotation or the first container is picked.`,
cliOnly: true,
defaultValue: undefined,
required: false,
}),
}

type Args = typeof execArgs
Expand Down Expand Up @@ -74,6 +81,7 @@ export class ExecCommand extends Command<Args, Opts> {
async action({ garden, log, args, opts }: CommandParams<Args, Opts>): Promise<CommandResult<ExecInDeployResult>> {
const deployName = args.deploy
const command = this.getCommand(args)
const target = opts["target"] as string | undefined

const graph = await garden.getConfigGraph({ log, emit: false })
const action = graph.getDeploy(deployName)
Expand Down Expand Up @@ -138,6 +146,7 @@ export class ExecCommand extends Command<Args, Opts> {
graph,
action: executed,
command,
target,
interactive: opts.interactive,
})

Expand Down
1 change: 1 addition & 0 deletions core/src/plugin/handlers/Deploy/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Executed } from "../../../actions/types"
interface ExecInDeployParams<T extends DeployAction> extends PluginDeployActionParamsBase<T> {
command: string[]
interactive: boolean
target?: string
}

export interface ExecInDeployResult {
Expand Down
3 changes: 2 additions & 1 deletion core/src/plugins/kubernetes/container/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { DeployActionHandler } from "../../../plugin/action-types"
import { k8sGetContainerDeployStatus } from "./status"

export const execInContainer: DeployActionHandler<"exec", ContainerDeployAction> = async (params) => {
const { ctx, log, action, command, interactive } = params
const { ctx, log, action, command, interactive, target: containerName } = params
const k8sCtx = <KubernetesPluginContext>ctx
const provider = k8sCtx.provider
const status = await k8sGetContainerDeployStatus({
Expand All @@ -43,6 +43,7 @@ export const execInContainer: DeployActionHandler<"exec", ContainerDeployAction>
log,
namespace,
workload: status.detail?.detail.workload,
containerName,
command,
interactive,
})
Expand Down
2 changes: 1 addition & 1 deletion core/src/plugins/kubernetes/kubernetes-type/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function getManifests({

// remove *List objects
const manifests = rawManifests.flatMap((manifest) => {
if (manifest.kind.endsWith("List")) {
if (manifest?.kind?.endsWith("List")) {
if (!manifest.items || manifest.items.length === 0) {
// empty list
return []
Expand Down
4 changes: 2 additions & 2 deletions core/src/plugins/kubernetes/kubernetes-type/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { getKubernetesDeployStatus } from "./handlers"
import chalk from "chalk"

export const execInKubernetesDeploy: DeployActionHandler<"exec", KubernetesDeployAction> = async (params) => {
const { ctx, log, action, command, interactive } = params
const { ctx, log, action, command, interactive, target: containerName } = params
const k8sCtx = <KubernetesPluginContext>ctx
const provider = k8sCtx.provider

Expand Down Expand Up @@ -64,5 +64,5 @@ export const execInKubernetesDeploy: DeployActionHandler<"exec", KubernetesDeplo
})
}

return execInWorkload({ ctx, provider, log, namespace, workload: target, command, interactive })
return execInWorkload({ ctx, provider, log, namespace, workload: target, containerName, command, interactive })
}
27 changes: 26 additions & 1 deletion core/src/plugins/kubernetes/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ import { LogLevel } from "../../logger/logger"
import { getResourceEvents } from "./status/events"
import stringify from "json-stringify-safe"

// ref: https://kubernetes.io/docs/reference/labels-annotations-taints/#kubectl-kubernetes-io-default-container
export const K8_POD_DEFAULT_CONTAINER_ANNOTATION_KEY = "kubectl.kubernetes.io/default-container"

/**
* When a `podSpec` is passed to `runAndCopy`, only these fields will be used for the runner's pod spec
* (and, in some cases, overridden/populated in `runAndCopy`).
Expand Down Expand Up @@ -1047,7 +1050,29 @@ export class PodRunner extends PodRunnerParams {
}

const startedAt = new Date()
const containerName = container || this.pod.spec.containers[0].name
let containerName = container
if (!containerName) {
// if no container name is specified, check if the Pod has annotation kubectl.kubernetes.io/default-container
const defaultAnnotationContainer = this.pod.metadata.annotations
? this.pod.metadata.annotations[K8_POD_DEFAULT_CONTAINER_ANNOTATION_KEY]
: undefined

if (defaultAnnotationContainer) {
containerName = defaultAnnotationContainer
if (this.pod.spec.containers.length > 1) {
log.info(
// in case there are more than 1 containers and exec picks container with annotation
`Defaulted container ${containerName} due to the annotation ${K8_POD_DEFAULT_CONTAINER_ANNOTATION_KEY}.`
)
}
} else {
containerName = this.pod.spec.containers[0].name
if (this.pod.spec.containers.length > 1) {
const allContainerNames = this.pod.spec.containers.map((c) => c.name)
log.info(`Defaulted container ${containerName} out of: ${allContainerNames.join(", ")}.`)
}
}
}

log.debug(`Execing command in ${this.namespace}/Pod/${this.podName}/${containerName}: ${command.join(" ")}`)
const startTime = new Date(Date.now())
Expand Down
3 changes: 3 additions & 0 deletions core/src/plugins/kubernetes/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ export async function execInWorkload({
namespace,
workload,
command,
containerName,
streamLogs = false,
interactive,
}: {
Expand All @@ -261,6 +262,7 @@ export async function execInWorkload({
namespace: string
workload: KubernetesWorkload | KubernetesPod
command: string[]
containerName?: string
streamLogs?: boolean
interactive: boolean
}) {
Expand All @@ -285,6 +287,7 @@ export async function execInWorkload({
timeoutSec: 999999,
tty: interactive,
buffer: true,
containerName,
}

if (streamLogs) {
Expand Down
1 change: 1 addition & 0 deletions core/test/unit/src/commands/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe("ExecCommand", () => {
args,
opts: withDefaultGlobalOpts({
interactive: false,
target: "",
}),
})

Expand Down
1 change: 1 addition & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,7 @@ Examples:
| Argument | Alias | Type | Description |
| -------- | ----- | ---- | ----------- |
| `--interactive` | | boolean | Set to false to skip interactive mode and just output the command result
| `--target` | | string | Specify name of the target if a Deploy action consists of multiple components. _NOTE: This option is only relevant in certain scenarios and will be ignored otherwise._ For Kubernetes deploy actions, this is useful if a Deployment includes multiple containers, such as sidecar containers. By default, the container with &#x60;kubectl.kubernetes.io/default-container&#x60; annotation or the first container is picked.

#### Outputs

Expand Down

0 comments on commit ac70427

Please sign in to comment.