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

chore(cli): send telemetry on errors #829

Merged
merged 10 commits into from
Jul 23, 2021
20 changes: 20 additions & 0 deletions packages/cdktf-cli/bin/cmds/helper/synth-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ Command output on stderr:
{dim ${indentString(e.stderr, 4)}}
`
: ""
}
${
e.stdout
? `Command output on stdout:

{dim ${indentString(e.stdout, 4)}}`
: ""
}`;
await this.synthErrorTelemetry(command);
console.error(errorOutput);
process.exit(1);
}
Expand Down Expand Up @@ -129,4 +137,16 @@ Command output on stderr:

await ReportRequest(reportParams);
}

public static async synthErrorTelemetry(command: string) {
const reportParams: ReportParams = {
command: "synth",
product: "cdktf",
version: versionNumber(),
dateTime: new Date(),
payload: { command, error: true },
};

await ReportRequest(reportParams);
}
}
1 change: 1 addition & 0 deletions packages/cdktf-cli/bin/cmds/helper/terraform-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as fs from "fs";
import * as readlineSync from "readline-sync";
import * as open from "open";
import * as chalk from "chalk";

const chalkColour = new chalk.Instance();
const homedir = require("os").homedir();
const terraformCredentialsFilePath = `${homedir}/.terraform.d/credentials.tfrc.json`;
Expand Down
19 changes: 13 additions & 6 deletions packages/cdktf-cli/bin/cmds/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { displayVersionMessage } from "./version-check";
import { FUTURE_FLAGS } from "cdktf/lib/features";
import { downloadFile, HttpError } from "../../lib/util";
import { logFileName, logger } from "../../lib/logging";
import { Errors } from "../../lib/errors";

const chalkColour = new chalk.Instance();

Expand Down Expand Up @@ -172,8 +173,10 @@ async function determineDeps(version: string, dist?: string): Promise<Deps> {

for (const file of Object.values(ret)) {
if (!(await fs.pathExists(file))) {
throw new Error(
`unable to find ${file} under the "dist" directory (${dist})`
throw Errors.Internal(
"init",
`unable to find ${file} under the "dist" directory (${dist})`,
{ version }
);
}
}
Expand All @@ -190,8 +193,10 @@ async function determineDeps(version: string, dist?: string): Promise<Deps> {
}

if (version === "0.0.0") {
throw new Error(
chalkColour`{redBright cannot use version 0.0.0, use --cdktf-version, --dist or CDKTF_DIST to install from a "dist" directory}`
throw Errors.Usage(
"init",
chalkColour`{redBright cannot use version 0.0.0, use --cdktf-version, --dist or CDKTF_DIST to install from a "dist" directory}`,
{}
);
}

Expand Down Expand Up @@ -365,8 +370,10 @@ async function fetchRemoteTemplate(templateUrl: string): Promise<Template> {
const templatePath = await findCdkTfJsonDirectory(zipExtractDir);

if (!templatePath) {
throw new Error(
chalkColour`Could not find a {whiteBright cdktf.json} in the extracted directory`
throw Errors.Usage(
"init",
chalkColour`Could not find a {whiteBright cdktf.json} in the extracted directory`,
{}
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/cdktf-cli/bin/cmds/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as terraformCloudClient from "./helper/terraform-cloud-client";
import * as chalk from "chalk";
import { terraformCheck } from "./terraform-check";
import { displayVersionMessage } from "./version-check";

const chalkColour = new chalk.Instance();

class Command implements yargs.CommandModule {
Expand Down
33 changes: 33 additions & 0 deletions packages/cdktf-cli/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ReportParams, ReportRequest } from "./checkpoint";
import { versionNumber } from "../bin/cmds/version-check";

// Errors that will emit telemetry events
async function report(command: string, payload: Record<string, any>) {
const reportParams: ReportParams = {
command,
product: "cdktf",
version: versionNumber(),
dateTime: new Date(),
payload,
};

await ReportRequest(reportParams);
}

function reportPrefixedError(type: string) {
return (command: string, message: string, context?: Record<string, any>) => {
report(command, { ...context, message, type });
const err: any = new Error(`${type} Error: ${message}`);
Object.entries(context || {}).forEach(([key, value]) => {
err[key] = value;
});
return err;
};
}

export const Errors = {
// Error within our control
Internal: reportPrefixedError("Internal"),
// Error in the usage
Usage: reportPrefixedError("Usage"),
};
13 changes: 9 additions & 4 deletions packages/cdktf-cli/lib/get/constructs-maker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ModuleGenerator } from "./generator/module-generator";
import { ModuleSchema } from "./generator/module-schema";
import { versionNumber } from "../../bin/cmds/version-check";
import { ReportParams, ReportRequest } from "../checkpoint";
import { Errors } from "../errors";

const VERSION = versionNumber();

Expand Down Expand Up @@ -397,8 +398,10 @@ export const determineGoModuleName = async (dir: string): Promise<string> => {
const childdir = path.relative(currentDir, dir).replace(/\\/g, "/"); // replace '\' with '/' for windows paths
return childdir.length > 0 ? `${match[1]}/${childdir}` : match[1];
}
throw new Error(
`Could not determine the root Go module name. Found ${file} but failed to regex match the module name directive`
throw Errors.Internal(
"get",
`Could not determine the root Go module name. Found ${file} but failed to regex match the module name directive`,
{ gomod }
);
}
// go up one directory. As dirname('/') will return '/' we cancel the loop
Expand All @@ -407,7 +410,9 @@ export const determineGoModuleName = async (dir: string): Promise<string> => {
currentDir = path.dirname(currentDir);
} while (currentDir !== previousDir);

throw new Error(
`Could not determine the root Go module name. No go.mod found in ${dir} and any parent directories`
throw Errors.Usage(
"get",
`Could not determine the root Go module name. No go.mod found in ${dir} and any parent directories`,
{}
);
};
9 changes: 6 additions & 3 deletions packages/cdktf-cli/lib/get/generator/module-generator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CodeMaker, toCamelCase } from "codemaker";
import { ConstructsMakerModuleTarget } from "../constructs-maker";
import { Errors } from "../../errors";

export class ModuleGenerator {
constructor(
Expand All @@ -17,7 +18,9 @@ export class ModuleGenerator {
const spec = target.spec;

if (!spec) {
throw new Error(`missing spec for ${target.name}`);
throw Errors.Internal("get", `missing spec for ${target.name}`, {
targetName: target.name,
});
}

this.code.openFile(target.fileName);
Expand Down Expand Up @@ -126,7 +129,7 @@ function parseType(type: string) {
return complexType;
}

throw new Error(`unknown type ${type}`);
throw Errors.Internal("get", `unknown type ${type}`, { type });
}

function parseComplexType(type: string): string | undefined {
Expand All @@ -150,5 +153,5 @@ function parseComplexType(type: string): string | undefined {
return `{ [key: string]: ${parseType(innerType)} }`;
}

throw new Error(`unexpected kind ${kind}`);
throw Errors.Internal("get", `unexpected kind ${kind}`, { kind, type });
}
7 changes: 4 additions & 3 deletions packages/cdktf-cli/lib/get/generator/provider-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ResourceModel } from "./models";
import { ResourceParser } from "./resource-parser";
import { ResourceEmitter, StructEmitter } from "./emitter";
import { ConstructsMakerTarget } from "../constructs-maker";
import { Errors } from "../../errors";

interface ProviderData {
name: string;
Expand All @@ -25,7 +26,7 @@ const isMatching = (
const [hostname, scope, provider] = elements;

if (!hostname || !scope || !provider) {
throw new Error(`can't handle ${terraformSchemaName}`);
throw Errors.Internal("get", `can't handle ${terraformSchemaName}`);
}

return target.name === provider;
Expand Down Expand Up @@ -73,7 +74,7 @@ export class TerraformProviderGenerator {
private emitProvider(fqpn: string, provider: Provider) {
const name = fqpn.split("/").pop();
if (!name) {
throw new Error(`can't handle ${fqpn}`);
throw Errors.Internal("get", `can't handle ${fqpn}`, { fqpn });
}

const files: string[] = [];
Expand Down Expand Up @@ -114,7 +115,7 @@ export class TerraformProviderGenerator {
isMatching(p, fqpn)
);
if (!constraint) {
throw new Error(`can't handle ${fqpn}`);
throw Errors.Internal("get", `can't handle ${fqpn}`);
}
providerResource.providerVersionConstraint = constraint.version;
providerResource.terraformProviderSource = constraint.source;
Expand Down
15 changes: 10 additions & 5 deletions packages/cdktf-cli/lib/get/generator/provider-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { exec, withTempDir } from "../../util";
import { ModuleSchema, Input } from "./module-schema";
import { ConstructsMakerTarget } from "../constructs-maker";
import { convertFiles } from "@cdktf/hcl2json";
import { Errors } from "../../errors";

const terraformBinaryName = process.env.TERRAFORM_BINARY_NAME || "terraform";

Expand Down Expand Up @@ -154,8 +155,10 @@ const harvestModuleSchema = async (
const result: Record<string, any> = {};

if (!fs.existsSync(fileName)) {
throw new Error(
`Modules were not generated properly - couldn't find ${fileName}`
throw Errors.Internal(
"get",
`Modules were not generated properly - couldn't find ${fileName}`,
{ fileName }
);
}

Expand All @@ -167,14 +170,16 @@ const harvestModuleSchema = async (
const m = moduleIndex.Modules.find((other) => mod === other.Key);

if (!m) {
throw new Error(`Couldn't find ${m}`);
throw Errors.Internal("get", `Couldn't find ${m}`, { mod });
}

const parsed = await convertFiles(path.join(workingDirectory, m.Dir));

if (!parsed) {
throw new Error(
`Modules were not generated properly - couldn't parse ${m.Dir}`
throw Errors.Internal(
"get",
`Modules were not generated properly - couldn't parse ${m.Dir}`,
{ mod }
);
}

Expand Down
10 changes: 7 additions & 3 deletions packages/cdktf-cli/lib/get/generator/resource-parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toCamelCase, toPascalCase, toSnakeCase } from "codemaker";
import { Errors } from "../../errors";
import {
Attribute,
AttributeType,
Expand Down Expand Up @@ -131,13 +132,16 @@ class Parser {
isMap: true,
});
default:
throw new Error(`invalid primitive type ${attributeType}`);
throw Errors.Internal(
"get",
`invalid primitive type ${attributeType}`
);
}
}

if (Array.isArray(attributeType)) {
if (attributeType.length !== 2) {
throw new Error(`unexpected array`);
throw Errors.Internal("get", `unexpected array`);
}

const [kind, type] = attributeType;
Expand Down Expand Up @@ -183,7 +187,7 @@ class Parser {
}
}

throw new Error(`unknown type ${attributeType}`);
throw Errors.Internal("get", `unknown type ${attributeType}`);
}

public renderAttributesForBlock(parentType: Scope, block: Block) {
Expand Down
9 changes: 8 additions & 1 deletion packages/cdktf-cli/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@ export async function shell(
options: SpawnOptions = {}
) {
const stderr = new Array<string | Uint8Array>();
const stdout = new Array<string>();
try {
return await exec(
program,
args,
options,
(chunk: Buffer) => console.log(chunk.toString()),
(chunk: Buffer) => {
stdout.push(chunk.toString());
console.log(chunk.toString());
},
(chunk: string | Uint8Array) => stderr.push(chunk)
);
} catch (e) {
if (stderr.length > 0) {
e.stderr = stderr.map((chunk) => chunk.toString()).join("");
}
if (stdout.length > 0) {
e.stdout = stdout.join("");
}
throw e;
}
}
Expand Down
9 changes: 7 additions & 2 deletions test/test-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,13 @@ export class TestDriver {
});
});
} catch (e) {
console.log(e.stdout.toString());
console.error(e.stderr.toString());
if (e.stdout) {
console.log(e.stdout.toString());
}
if (e.stderr) {
console.error(e.stderr.toString());
}

throw e;
}
}
Expand Down
12 changes: 8 additions & 4 deletions test/typescript/synth-app-error/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ describe("full integration test synth", () => {
fail("Expected synth to fail but no error was thrown");
} catch (e) {
expect(e.code).toBe(1);
expect(e.stderr.toString())
.toContain(`cdktf encountered an error while synthesizing
const errorString = e.stderr.toString();
expect(errorString).toContain(
`cdktf encountered an error while synthesizing`
);

Synth command: npm run --silent compile && node thisFileDoesNotExist.js
Error: non-zero exit code 1`);
expect(errorString).toContain(
`Synth command: npm run --silent compile && node thisFileDoesNotExist.js`
);
expect(errorString).toContain(`Error: non-zero exit code 1`);
}
});
});