Skip to content

Commit

Permalink
refactor: remove node-pty dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
edvald committed Oct 22, 2018
1 parent 3a8a786 commit 5082196
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 626 deletions.
385 changes: 69 additions & 316 deletions garden-service/package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion garden-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"log-symbols": "^2.2.0",
"moment": "^2.22.2",
"node-emoji": "^1.8.1",
"node-pty-prebuilt": "^0.7.6",
"normalize-url": "^3.2.0",
"p-queue": "^3.0.0",
"path-is-inside": "^1.0.2",
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ExecCommand extends Command<Args> {
})

const service = await garden.getService(serviceName)
const result = await garden.actions.execInService({ service, command })
const result = await garden.actions.execInService({ service, command, interactive: true })

return { result }
}
Expand Down
1 change: 0 additions & 1 deletion garden-service/src/commands/run/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export class RunModuleCommand extends Command<Args, Opts> {
module,
command,
runtimeContext,
silent: false,
interactive: opts.interactive,
})

Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/commands/run/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class RunServiceCommand extends Command<Args, Opts> {

printRuntimeContext(garden, runtimeContext)

const result = await garden.actions.runService({ service, runtimeContext, silent: false, interactive: true })
const result = await garden.actions.runService({ service, runtimeContext, interactive: true })

return { result }
}
Expand Down
2 changes: 1 addition & 1 deletion garden-service/src/plugins/google/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export async function prepareEnvironment({ status, logEntry }: PrepareEnvironmen
section: "google-cloud-functions",
msg: `Initializing SDK...`,
})
await gcloud().tty(["init"], { silent: false })
await gcloud().call(["init"], { timeout: 600, tty: true })
}

return {}
Expand Down
73 changes: 5 additions & 68 deletions garden-service/src/plugins/google/gcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import * as spawn from "cross-spawn"
import { extend } from "lodash"
import { spawnPty } from "../../util/util"
import { spawn, SpawnOpts } from "../../util/util"

export interface GCloudParams {
data?: Buffer,
Expand Down Expand Up @@ -37,67 +35,10 @@ export class GCloud {
this.project = project
}

async call(
args: string[],
{ data, ignoreError = false, silent = true, timeout = DEFAULT_TIMEOUT, cwd }: GCloudParams = {},
): Promise<GCloudOutput> {

const out: GCloudOutput = {
code: 0,
output: "",
stdout: "",
stderr: "",
}

const proc = spawn("gcloud", this.prepareArgs(args), { cwd })

proc.stdout.on("data", (s) => {
if (!silent) {
process.stdout.write(s)
}
out.output += s
out.stdout! += s
})

proc.stderr.on("data", (s) => {
if (!silent) {
process.stderr.write(s)
}
out.output += s
out.stderr! += s
})

if (data) {
proc.stdin.end(data)
}

return new Promise<GCloudOutput>((resolve, reject) => {
let _timeout

const _reject = (msg: string) => {
const err = new Error(msg)
extend(err, <any>out)
reject(err)
}

if (timeout > 0) {
_timeout = setTimeout(() => {
proc.kill("SIGKILL")
_reject(`gcloud timed out after ${timeout} seconds.`)
}, timeout * 1000)
}

proc.on("close", (code) => {
_timeout && clearTimeout(_timeout)
out.code = code

if (code === 0 || ignoreError) {
resolve(out)
} else {
_reject("Process exited with code " + code)
}
})
})
async call(args: string[], opts: SpawnOpts = {}): Promise<GCloudOutput> {
const { data, ignoreError = false, timeout = DEFAULT_TIMEOUT } = opts
const preparedArgs = this.prepareArgs(args)
return spawn("gcloud", preparedArgs, { ignoreError, data, timeout })
}

async json(args: string[], opts: GCloudParams = {}): Promise<any> {
Expand All @@ -110,10 +51,6 @@ export class GCloud {
return JSON.parse(result.output)
}

async tty(args: string[], { silent = true, cwd }: { silent?: boolean, cwd?: string } = {}): Promise<GCloudOutput> {
return spawnPty("gcloud", this.prepareArgs(args), { silent, cwd, tty: true })
}

private prepareArgs(args: string[]) {
const ops: string[] = []

Expand Down
21 changes: 8 additions & 13 deletions garden-service/src/plugins/kubernetes/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export async function getServiceOutputs({ service }: GetServiceOutputsParams<Con
}

export async function execInService(params: ExecInServiceParams<ContainerModule>) {
const { ctx, service, command } = params
const { ctx, service, command, interactive } = params
const api = new KubeApi(ctx.provider)
const status = await getContainerServiceStatus(params)
const namespace = await getAppNamespace(ctx, ctx.provider)
Expand Down Expand Up @@ -123,12 +123,11 @@ export async function execInService(params: ExecInServiceParams<ContainerModule>
}

// exec in the pod via kubectl
const kubecmd = ["exec", "-it", pod.metadata.name, "--", ...command]
const res = await kubectl(api.context, namespace).tty(kubecmd, {
const kubecmd = ["exec", "-i", pod.metadata.name, "--", ...command]
const res = await kubectl(api.context, namespace).call(kubecmd, {
ignoreError: true,
silent: false,
timeout: 999999,
tty: true,
tty: interactive,
})

return { code: res.code, output: res.output }
Expand Down Expand Up @@ -170,7 +169,7 @@ export async function hotReload(
}

export async function runModule(
{ ctx, module, command, interactive, runtimeContext, silent, timeout }: RunModuleParams<ContainerModule>,
{ ctx, module, command, interactive, runtimeContext, timeout }: RunModuleParams<ContainerModule>,
): Promise<RunResult> {
const context = ctx.provider.config.context
const namespace = await getAppNamespace(ctx, ctx.provider)
Expand All @@ -185,7 +184,6 @@ export async function runModule(
`--image=${image}`,
"--restart=Never",
"--command",
"--tty",
"--rm",
"-i",
"--quiet",
Expand All @@ -203,9 +201,8 @@ export async function runModule(

const startedAt = new Date()

const res = await kubectl(context, namespace).tty(kubecmd, {
const res = await kubectl(context, namespace).call(kubecmd, {
ignoreError: true,
silent: !interactive || silent, // shouldn't be silent in interactive mode
timeout,
tty: interactive,
})
Expand All @@ -222,7 +219,7 @@ export async function runModule(
}

export async function runService(
{ ctx, service, interactive, runtimeContext, silent, timeout, logEntry, buildDependencies }:
{ ctx, service, interactive, runtimeContext, timeout, logEntry, buildDependencies }:
RunServiceParams<ContainerModule>,
) {
return runModule({
Expand All @@ -231,15 +228,14 @@ export async function runService(
command: service.spec.command || [],
interactive,
runtimeContext,
silent,
timeout,
logEntry,
buildDependencies,
})
}

export async function testModule(
{ ctx, interactive, module, runtimeContext, silent, testConfig, logEntry, buildDependencies }:
{ ctx, interactive, module, runtimeContext, testConfig, logEntry, buildDependencies }:
TestModuleParams<ContainerModule>,
): Promise<TestResult> {
const testName = testConfig.name
Expand All @@ -253,7 +249,6 @@ export async function testModule(
command,
interactive,
runtimeContext,
silent,
timeout,
logEntry,
buildDependencies,
Expand Down
118 changes: 13 additions & 105 deletions garden-service/src/plugins/kubernetes/kubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import chalk from "chalk"
import { ChildProcess } from "child_process"
import * as spawn from "cross-spawn"
import { extend } from "lodash"
import { encodeYamlMulti, spawnPty } from "../../util/util"
import { RuntimeError } from "../../exceptions"
import { getLogger } from "../../logger/logger"
import { platform } from "os"
import hasAnsi = require("has-ansi")

export interface KubectlParams {
data?: Buffer,
ignoreError?: boolean,
silent?: boolean,
timeout?: number,
tty?: boolean,
}

export interface KubectlOutput {
code: number,
output: string,
stdout?: string,
stderr?: string,
}
import * as _spawn from "cross-spawn"
import { encodeYamlMulti, spawn, SpawnOpts } from "../../util/util"

export interface ApplyOptions {
dryRun?: boolean,
Expand All @@ -52,79 +31,13 @@ export class Kubectl {
this.configPath = configPath
}

async call(
args: string[],
{ data, ignoreError = false, silent = true, timeout = KUBECTL_DEFAULT_TIMEOUT }: KubectlParams = {},
): Promise<KubectlOutput> {
// TODO: use the spawn helper from index.ts
const logger = getLogger()
const out: KubectlOutput = {
code: 0,
output: "",
stdout: "",
stderr: "",
}

const preparedArgs = this.prepareArgs(args)
const proc = spawn(this.getExececutable(), preparedArgs)

proc.stdout.on("data", (s) => {
if (!silent) {
const str = s.toString()
logger.info(hasAnsi(str) ? str : chalk.white(str))
}
out.output += s
out.stdout! += s
})

proc.stderr.on("data", (s) => {
if (!silent) {
const str = s.toString()
logger.info(hasAnsi(str) ? str : chalk.white(str))
}
out.output += s
out.stderr! += s
})

if (data) {
proc.stdin.end(data)
}

return new Promise<KubectlOutput>((resolve, reject) => {
let _timeout

const _reject = (msg: string) => {
const dataStr = data ? data.toString() : null
const details = extend({ args, preparedArgs, msg, data: dataStr }, <any>out)

const err = new RuntimeError(
`Failed running 'kubectl ${preparedArgs.join(" ")}': ${out.output}`,
details,
)
reject(err)
}

if (timeout > 0) {
_timeout = setTimeout(() => {
proc.kill("SIGKILL")
_reject(`kubectl timed out after ${timeout} seconds.`)
}, timeout * 1000)
}

proc.on("close", (code) => {
_timeout && clearTimeout(_timeout)
out.code = code

if (code === 0 || ignoreError) {
resolve(out)
} else {
_reject("Process exited with code " + code)
}
})
})
async call(args: string[], opts: SpawnOpts = {}) {
const { data, ignoreError = false, timeout = KUBECTL_DEFAULT_TIMEOUT } = opts
const preparedArgs = this.prepareArgs(args, opts)
return spawn("kubectl", preparedArgs, { ignoreError, data, timeout })
}

async json(args: string[], opts: KubectlParams = {}): Promise<KubectlOutput> {
async json(args: string[], opts: SpawnOpts = {}): Promise<any> {
if (!args.includes("--output=json")) {
args.push("--output=json")
}
Expand All @@ -134,20 +47,11 @@ export class Kubectl {
return JSON.parse(result.output)
}

async tty(args: string[], opts: KubectlParams = {}): Promise<KubectlOutput> {
return spawnPty(this.getExececutable(), this.prepareArgs(args), opts)
}

spawn(args: string[]): ChildProcess {
return spawn(this.getExececutable(), this.prepareArgs(args))
}

private getExececutable() {
// workaround for https://github.com/Microsoft/node-pty/issues/109
return platform() === "win32" ? "kubectl.exe" : "kubectl"
return _spawn("kubectl", this.prepareArgs(args, {}))
}

private prepareArgs(args: string[]) {
private prepareArgs(args: string[], { tty }: SpawnOpts) {
const ops: string[] = []

if (this.namespace) {
Expand All @@ -162,6 +66,10 @@ export class Kubectl {
ops.push(`--kubeconfig=${this.configPath}`)
}

if (tty) {
ops.push("--tty")
}

return ops.concat(args)
}
}
Expand Down
Loading

0 comments on commit 5082196

Please sign in to comment.