Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(container): output build log while building with debug log level #1144

Merged
merged 1 commit into from
Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions garden-service/src/plugins/container/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { ContainerModule } from "./config"
import { ConfigurationError } from "../../exceptions"
import { GetBuildStatusParams } from "../../types/plugin/module/getBuildStatus"
import { BuildModuleParams } from "../../types/plugin/module/build"
import chalk from "chalk"
import { LogLevel } from "../../logger/log-node"
import split2 = require("split2")

export async function getContainerBuildStatus({ module, log }: GetBuildStatusParams<ContainerModule>) {
const identifier = await containerHelpers.imageExistsLocally(module)
Expand Down Expand Up @@ -61,10 +64,17 @@ export async function buildContainerModule({ module, log }: BuildModuleParams<Co
cmdOpts.push("--file", containerHelpers.getDockerfileBuildPath(module))
}

// TODO: log error if it occurs
// TODO: stream output to log if at debug log level
// Stream log to a status line
const outputStream = split2()
const statusLine = log.placeholder(LogLevel.debug)

outputStream.on("error", () => { })
outputStream.on("data", (line: Buffer) => {
statusLine.setState(chalk.gray(" → " + line.toString().slice(0, 80)))
})

const timeout = module.spec.build.timeout
const buildLog = await containerHelpers.dockerCli(module, [...cmdOpts, buildPath], { timeout })
const buildLog = await containerHelpers.dockerCli(module, [...cmdOpts, buildPath], { outputStream, timeout })

return { fresh: true, buildLog, details: { identifier } }
}
Expand Down
5 changes: 3 additions & 2 deletions garden-service/src/plugins/container/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ConfigurationError, RuntimeError } from "../../exceptions"
import { splitFirst, spawn, splitLast } from "../../util/util"
import { ModuleConfig } from "../../config/module"
import { ContainerModule, ContainerRegistryConfig, defaultTag, defaultNamespace, ContainerModuleConfig } from "./config"
import { Writable } from "stream"

export const DEFAULT_BUILD_TIMEOUT = 600
export const minDockerVersion = "17.07.0"
Expand Down Expand Up @@ -251,14 +252,14 @@ const helpers = {

async dockerCli(
module: ContainerModule, args: string[],
{ timeout = DEFAULT_BUILD_TIMEOUT }: { timeout?: number } = {},
{ outputStream, timeout = DEFAULT_BUILD_TIMEOUT }: { outputStream?: Writable, timeout?: number } = {},
) {
await helpers.checkDockerVersion()

const cwd = module.buildPath

try {
const res = await spawn("docker", args, { cwd, timeout })
const res = await spawn("docker", args, { cwd, outputStream, timeout })
return res.output || ""
} catch (err) {
throw new RuntimeError(
Expand Down
35 changes: 30 additions & 5 deletions garden-service/src/plugins/kubernetes/container/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import pRetry from "p-retry"
import split2 = require("split2")
import { ContainerModule } from "../../container/config"
import { containerHelpers } from "../../container/helpers"
import { buildContainerModule, getContainerBuildStatus, getDockerBuildFlags } from "../../container/build"
Expand All @@ -27,6 +28,9 @@ import { getRegistryHostname } from "../init"
import { getManifestFromRegistry } from "./util"
import { normalizeLocalRsyncPath } from "../../../util/fs"
import { getPortForward } from "../port-forward"
import chalk from "chalk"
import { Writable } from "stream"
import { LogLevel } from "../../../logger/log-node"

const dockerDaemonDeploymentName = "garden-docker-daemon"
const dockerDaemonContainerName = "docker-daemon"
Expand Down Expand Up @@ -160,6 +164,15 @@ const remoteBuild: BuildHandler = async (params) => {

let buildLog = ""

// Stream debug log to a status line
const outputStream = split2()
const statusLine = log.placeholder(LogLevel.debug)

outputStream.on("error", () => { })
outputStream.on("data", (line: Buffer) => {
statusLine.setState(chalk.gray(" → " + line.toString().slice(0, 80)))
})

if (provider.config.buildMode === "cluster-docker") {
// Prepare the build command
const dockerfilePath = posix.join(contextPath, dockerfile)
Expand All @@ -175,7 +188,8 @@ const remoteBuild: BuildHandler = async (params) => {
// Execute the build
const podName = await getBuilderPodName(provider, log)
const buildTimeout = module.spec.build.timeout
const buildRes = await execInBuilder({ provider, log, args, timeout: buildTimeout, podName })

const buildRes = await execInBuilder({ provider, log, args, timeout: buildTimeout, podName, outputStream })
buildLog = buildRes.stdout + buildRes.stderr

// Push the image to the registry
Expand All @@ -184,7 +198,7 @@ const remoteBuild: BuildHandler = async (params) => {
const dockerCmd = ["docker", "push", deploymentImageId]
const pushArgs = ["/bin/sh", "-c", dockerCmd.join(" ")]

const pushRes = await execInBuilder({ provider, log, args: pushArgs, timeout: 300, podName })
const pushRes = await execInBuilder({ provider, log, args: pushArgs, timeout: 300, podName, outputStream })
buildLog += pushRes.stdout + pushRes.stderr

} else {
Expand All @@ -201,7 +215,7 @@ const remoteBuild: BuildHandler = async (params) => {
]

// Execute the build
const buildRes = await runKaniko(provider, log, module, args)
const buildRes = await runKaniko({ provider, log, module, args, outputStream })
buildLog = buildRes.log
}

Expand All @@ -221,6 +235,7 @@ export interface BuilderExecParams {
args: string[],
timeout: number,
podName: string,
outputStream?: Writable
}

const buildHandlers: { [mode in ContainerBuildMode]: BuildHandler } = {
Expand All @@ -230,7 +245,7 @@ const buildHandlers: { [mode in ContainerBuildMode]: BuildHandler } = {
}

// TODO: we should make a simple service around this instead of execing into containers
export async function execInBuilder({ provider, log, args, timeout, podName }: BuilderExecParams) {
export async function execInBuilder({ provider, log, args, timeout, podName, outputStream }: BuilderExecParams) {
const execCmd = ["exec", "-i", podName, "-c", dockerDaemonContainerName, "--", ...args]

log.verbose(`Running: kubectl ${execCmd.join(" ")}`)
Expand All @@ -241,6 +256,7 @@ export async function execInBuilder({ provider, log, args, timeout, podName }: B
log,
namespace: systemNamespace,
timeout,
outputStream,
})
}

Expand All @@ -261,7 +277,15 @@ export async function getBuilderPodName(provider: KubernetesProvider, log: LogEn
return builderPods[0].metadata.name
}

async function runKaniko(provider: KubernetesProvider, log: LogEntry, module: ContainerModule, args: string[]) {
interface RunKanikoParams {
provider: KubernetesProvider
log: LogEntry
module: ContainerModule
args: string[]
outputStream: Writable
}

async function runKaniko({ provider, log, module, args, outputStream }: RunKanikoParams) {
const podName = `kaniko-${module.name}-${Math.round(new Date().getTime())}`
const registryHostname = getRegistryHostname()

Expand Down Expand Up @@ -325,5 +349,6 @@ async function runKaniko(provider: KubernetesProvider, log: LogEntry, module: Co
},
podName,
timeout: module.spec.build.timeout,
outputStream,
})
}
4 changes: 4 additions & 0 deletions garden-service/src/plugins/kubernetes/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LogEntry } from "../../logger/log-entry"
import { V1PodSpec } from "@kubernetes/client-node"
import { PluginError } from "../../exceptions"
import { KubernetesProvider } from "./config"
import { Writable } from "stream"

interface RunPodParams {
provider: KubernetesProvider,
Expand All @@ -24,6 +25,7 @@ interface RunPodParams {
namespace: string,
annotations?: { [key: string]: string }
spec: V1PodSpec,
outputStream?: Writable,
podName?: string,
timeout?: number,
}
Expand All @@ -39,6 +41,7 @@ export async function runPod(
namespace,
annotations,
spec,
outputStream,
podName,
timeout,
}: RunPodParams,
Expand Down Expand Up @@ -98,6 +101,7 @@ export async function runPod(
namespace,
ignoreError,
args: kubecmd,
outputStream,
timeout,
tty: interactive,
})
Expand Down
16 changes: 13 additions & 3 deletions garden-service/src/util/ext-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { createHash } from "crypto"
import * as uuid from "uuid"
import * as crossSpawn from "cross-spawn"
import { spawn } from "./util"
import { Writable } from "stream"
const AsyncLock = require("async-lock")

const toolsPath = join(GARDEN_GLOBAL_PATH, "tools")
Expand Down Expand Up @@ -218,6 +219,7 @@ export interface ExecParams {
timeout?: number
input?: Buffer | string
ignoreError?: boolean
outputStream?: Writable
}

export interface SpawnParams extends ExecParams {
Expand Down Expand Up @@ -255,7 +257,7 @@ export class BinaryCmd extends Library {
return path
}

async exec({ args, cwd, env, log, timeout, input, ignoreError }: ExecParams) {
async exec({ args, cwd, env, log, timeout, input, ignoreError, outputStream }: ExecParams) {
const path = await this.getPath(log)

if (!args) {
Expand All @@ -264,13 +266,20 @@ export class BinaryCmd extends Library {

log.verbose(`Execing ${path} ${args.join(" ")}`)

return execa(path, args, {
const proc = execa(path, args, {
cwd: cwd || dirname(path),
timeout: this.getTimeout(timeout) * 1000,
env,
input,
reject: !ignoreError,
})

if (outputStream) {
proc.stdout && proc.stdout.pipe(outputStream)
proc.stderr && proc.stderr.pipe(outputStream)
}

return proc
}

async stdout(params: ExecParams) {
Expand All @@ -288,13 +297,14 @@ export class BinaryCmd extends Library {
return crossSpawn(path, args || [], { cwd: cwd || dirname(path), env })
}

async spawnAndWait({ args, cwd, env, log, ignoreError, timeout, tty }: SpawnParams) {
async spawnAndWait({ args, cwd, env, log, ignoreError, outputStream, timeout, tty }: SpawnParams) {
const path = await this.getPath(log)
return spawn(path, args || [], {
cwd: cwd || dirname(path),
timeout: this.getTimeout(timeout),
ignoreError,
env,
outputStream,
tty,
})
}
Expand Down
9 changes: 8 additions & 1 deletion garden-service/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import chalk from "chalk"
import { safeDump } from "js-yaml"
import { createHash } from "crypto"
import { tailString } from "./string"
import { Writable } from "stream"

// shim to allow async generator functions
if (typeof (Symbol as any).asyncIterator === "undefined") {
Expand Down Expand Up @@ -76,6 +77,7 @@ export interface SpawnOpts {
data?: Buffer
ignoreError?: boolean
env?: { [key: string]: string | undefined }
outputStream?: Writable
tty?: boolean
wait?: boolean
}
Expand All @@ -90,7 +92,7 @@ export interface SpawnOutput {

// TODO Dump output to a log file if it exceeds the MAX_BUFFER_SIZE
export function spawn(cmd: string, args: string[], opts: SpawnOpts = {}) {
const { timeout = 0, cwd, data, ignoreError = false, env, tty, wait = true } = opts
const { timeout = 0, cwd, data, ignoreError = false, env, outputStream, tty, wait = true } = opts

const stdio = tty ? "inherit" : "pipe"
const proc = _spawn(cmd, args, { cwd, env, stdio })
Expand Down Expand Up @@ -126,6 +128,11 @@ export function spawn(cmd: string, args: string[], opts: SpawnOpts = {}) {
result.stderr! = tailString(result.stderr! + s, MAX_BUFFER_SIZE, true)
})

if (outputStream) {
proc.stdout!.pipe(outputStream)
proc.stderr!.pipe(outputStream)
}

if (data) {
// This may happen if the spawned process errors while we're still writing data.
proc.stdin!.on("error", () => { })
Expand Down
6 changes: 3 additions & 3 deletions garden-service/test/unit/src/plugins/container/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ describe("plugins.container", () => {
})

const cmdArgs = ["build", "-t", "some/image", module.buildPath]
td.verify(dockerCli(module, cmdArgs, { timeout: DEFAULT_BUILD_TIMEOUT }))
td.verify(dockerCli(module, cmdArgs), { ignoreExtraArgs: true })
})

it("should set build target image parameter if configured", async () => {
Expand All @@ -564,7 +564,7 @@ describe("plugins.container", () => {
})

const cmdArgs = ["build", "-t", "some/image", "--target", "foo", module.buildPath]
td.verify(dockerCli(module, cmdArgs, { timeout: DEFAULT_BUILD_TIMEOUT }))
td.verify(dockerCli(module, cmdArgs), { ignoreExtraArgs: true })
})

it("should build image using the user specified Dockerfile path", async () => {
Expand Down Expand Up @@ -596,7 +596,7 @@ describe("plugins.container", () => {
join(module.buildPath, relDockerfilePath),
module.buildPath,
]
td.verify(dockerCli(module, cmdArgs, { timeout: DEFAULT_BUILD_TIMEOUT }))
td.verify(dockerCli(module, cmdArgs), { ignoreExtraArgs: true })
})
})

Expand Down