From a7b98215b4ba36f8364bec5f4c680969bee5f220 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Wed, 18 May 2022 19:38:50 +0200 Subject: [PATCH] chore(cli): add sentry error reporting --- .github/workflows/release.yml | 49 ++++++++ .github/workflows/release_next.yml | 48 +++++++ packages/@cdktf/hcl2cdk/test/hcl2cdk.test.ts | 2 +- packages/cdktf-cli/bin/cdktf.ts | 9 +- packages/cdktf-cli/bin/cmds/handlers.ts | 14 ++- packages/cdktf-cli/bin/cmds/helper/init.ts | 6 + packages/cdktf-cli/bin/cmds/init.ts | 4 + packages/cdktf-cli/build.ts | 3 + packages/cdktf-cli/lib/checkpoint.ts | 4 +- packages/cdktf-cli/lib/debug.ts | 36 ++++-- packages/cdktf-cli/lib/error-reporting.ts | 117 ++++++++++++++++++ packages/cdktf-cli/lib/errors.ts | 2 + packages/cdktf-cli/lib/init.ts | 3 + packages/cdktf-cli/lib/logging.ts | 62 ++++++++-- packages/cdktf-cli/package.json | 1 + .../cdktf-cli/templates/csharp/cdktf.json | 1 + packages/cdktf-cli/templates/go/cdktf.json | 1 + packages/cdktf-cli/templates/java/cdktf.json | 1 + .../cdktf-cli/templates/python-pip/cdktf.json | 1 + .../cdktf-cli/templates/python/cdktf.json | 1 + .../cdktf-cli/templates/typescript/cdktf.json | 1 + .../cdktf-cli/test/lib/cdktf-project.test.ts | 1 + test/test-helper.ts | 2 +- website/docs/cdktf/cli-reference/commands.mdx | 32 +++-- .../create-and-deploy/configuration-file.mdx | 13 ++ website/docs/cdktf/telemetry.mdx | 10 ++ yarn.lock | 68 +++++++++- 27 files changed, 442 insertions(+), 50 deletions(-) create mode 100644 packages/cdktf-cli/lib/error-reporting.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe02450820..bde70b4764 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,10 +25,27 @@ jobs: - name: installing dependencies run: | yarn install + - name: Install the Sentry CLI + run: curl -sL https://sentry.io/get-cli/ | bash + - name: version + id: get_version + run: | + version=$(node -p "require('./package.json').version") + echo "::set-output name=version::${version}" + - name: Create a release + run: sentry-cli releases new ${{ steps.get_version.outputs.version }} + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} - name: compile run: | tools/align-version.sh yarn build + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + - name: Add sourcemaps + run: sentry-cli releases files ${{ steps.get_version.outputs.version }} upload-sourcemaps ./packages/cdktf-cli/bundle + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} - name: create bundle run: yarn package - name: test @@ -207,6 +224,38 @@ jobs: GIT_USER_NAME: "CDK for Terraform Team" GIT_USER_EMAIL: "github-team-tf-cdk@hashicorp.com" + release_sentry: + name: Finalize the sentry release + needs: + - prepare-release + - integration-tests + - release_homebrew + - release_golang + - release_nuget + - release_maven + - release_pypi + - release_npm + runs-on: ubuntu-latest + container: + image: docker.mirror.hashicorp.services/hashicorp/jsii-terraform + steps: + - name: Download dist + uses: actions/download-artifact@v2 + with: + name: dist + path: dist + - name: Install the Sentry CLI + run: curl -sL https://sentry.io/get-cli/ | bash + - name: version + id: get_version + run: | + version=$(node -p "require('./package.json').version") + echo "::set-output name=version::${version}" + - name: Create a release + run: sentry-cli releases finalize ${{ steps.get_version.outputs.version }} + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} + release_homebrew: name: Release to Homebrew # The branch or tag ref that triggered the workflow run. diff --git a/.github/workflows/release_next.yml b/.github/workflows/release_next.yml index e908278eae..3b3e3be380 100644 --- a/.github/workflows/release_next.yml +++ b/.github/workflows/release_next.yml @@ -32,7 +32,24 @@ jobs: - run: | yarn prepare-next-release tools/align-version.sh + - name: Install the Sentry CLI + run: curl -sL https://sentry.io/get-cli/ | bash + - name: version + id: get_version + run: | + version=$(node -p "require('./package.json').version") + echo "::set-output name=version::${version}" + - name: Create a release + run: sentry-cli releases new ${{ steps.get_version.outputs.version }} + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} - run: yarn build + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + - name: Add sourcemaps + run: sentry-cli releases files ${{ steps.get_version.outputs.version }} upload-sourcemaps ./packages/cdktf-cli/bundle + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} - run: yarn package - run: yarn test:ci - name: Upload artifact @@ -188,3 +205,34 @@ jobs: GITHUB_TOKEN: ${{ secrets.TERRAFORM_CDK_GO_REPO_GITHUB_TOKEN }} GIT_USER_NAME: "CDK for Terraform Team" GIT_USER_EMAIL: "github-team-tf-cdk@hashicorp.com" + + release_sentry: + name: Finalize the sentry release + needs: + - prepare-release + - integration-tests + - release_golang + - release_nuget + - release_maven + - release_pypi + - release_npm + runs-on: ubuntu-latest + container: + image: docker.mirror.hashicorp.services/hashicorp/jsii-terraform + steps: + - name: Download dist + uses: actions/download-artifact@v2 + with: + name: dist + path: dist + - name: Install the Sentry CLI + run: curl -sL https://sentry.io/get-cli/ | bash + - name: version + id: get_version + run: | + version=$(node -p "require('./package.json').version") + echo "::set-output name=version::${version}" + - name: Create a release + run: sentry-cli releases finalize ${{ steps.get_version.outputs.version }} + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} diff --git a/packages/@cdktf/hcl2cdk/test/hcl2cdk.test.ts b/packages/@cdktf/hcl2cdk/test/hcl2cdk.test.ts index 84443bd442..4a5ff2d383 100644 --- a/packages/@cdktf/hcl2cdk/test/hcl2cdk.test.ts +++ b/packages/@cdktf/hcl2cdk/test/hcl2cdk.test.ts @@ -116,7 +116,7 @@ describe("convert", () => { // Initialize a new project projectDir = fs.mkdtempSync("cdktf-convert-test"); execSync( - `cd ${projectDir} && ${cdktfBin} init --local --dist=${cdktfDist} --project-name="hello" --project-description="world" --template=typescript` + `cd ${projectDir} && ${cdktfBin} init --local --dist=${cdktfDist} --project-name="hello" --project-description="world" --template=typescript --enable-crash-reporting=false` ); const cdktfJson = JSON.parse( fs.readFileSync(path.join(projectDir, "cdktf.json"), "utf8") diff --git a/packages/cdktf-cli/bin/cdktf.ts b/packages/cdktf-cli/bin/cdktf.ts index 944a1e291f..341d7c75c2 100644 --- a/packages/cdktf-cli/bin/cdktf.ts +++ b/packages/cdktf-cli/bin/cdktf.ts @@ -1,7 +1,6 @@ process.env.NODE_ENV = process.env.NODE_ENV || "production"; import * as yargs from "yargs"; -import * as semver from "semver"; import * as path from "path"; import * as os from "os"; import * as fs from "fs-extra"; @@ -9,6 +8,7 @@ import { readCDKTFManifest } from "../lib/util"; import { IsErrorType } from "../lib/errors"; import { collectDebugInformation } from "../lib/debug"; import { CDKTF_DISABLE_PLUGIN_CACHE_ENV } from "../lib/environment"; +import * as Sentry from "@sentry/node"; const ensurePluginCache = (): string => { const pluginCachePath = @@ -24,11 +24,6 @@ if (!CDKTF_DISABLE_PLUGIN_CACHE_ENV) { process.env.TF_PLUGIN_CACHE_DIR = ensurePluginCache(); } -if (semver.lt(process.version, "10.12.0")) { - console.error("Need at least Node v10.12 to run"); - process.exit(1); -} - const customCompletion = function ( _current: string, argv: any, @@ -141,11 +136,13 @@ yargs console.error(error.stack); console.error("Collecting Debug Information..."); const debugOutput = await collectDebugInformation(); + console.error("Debug Information:"); Object.entries(debugOutput).forEach(([key, value]) => { console.log(`${key}: ${value === null ? "null" : value}`); }); } + await Sentry.close(4000); process.exit(1); }).argv; diff --git a/packages/cdktf-cli/bin/cmds/handlers.ts b/packages/cdktf-cli/bin/cmds/handlers.ts index 5e1540e815..acbe4a41e4 100644 --- a/packages/cdktf-cli/bin/cmds/handlers.ts +++ b/packages/cdktf-cli/bin/cmds/handlers.ts @@ -41,6 +41,8 @@ import { verifySimilarLibraryVersion, } from "./helper/check-environment"; import { collectDebugInformation } from "../../lib/debug"; +import { logger } from "../../lib/logging"; +import { initializErrorReporting } from "../../lib/error-reporting"; const chalkColour = new chalk.Instance(); const config = cfg.readConfigSync(); @@ -59,6 +61,7 @@ async function getProviderRequirements(provider: string[]) { } export async function convert({ language, provider }: any) { + await initializErrorReporting(); await displayVersionMessage(); const providerRequirements = await getProviderRequirements(provider); @@ -89,6 +92,7 @@ export async function convert({ language, provider }: any) { } export async function deploy(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); await checkEnvironment(); @@ -124,6 +128,7 @@ export async function deploy(argv: any) { } export async function destroy(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); await checkEnvironment(); @@ -146,6 +151,7 @@ export async function destroy(argv: any) { } export async function diff(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); await checkEnvironment(); @@ -163,8 +169,7 @@ export async function diff(argv: any) { } export async function get(argv: any) { - throwIfNotProjectDirectory(); - await displayVersionMessage(); + await initializErrorReporting(true); await checkEnvironment(); await verifySimilarLibraryVersion(); const args = argv as { @@ -174,6 +179,7 @@ export async function get(argv: any) { const providers = config.terraformProviders ?? []; const modules = config.terraformModules ?? []; const { output, language } = args; + logger.info(`Getting outputs for ${language}`); const constraints: cfg.TerraformDependencyConstraint[] = [ ...providers, @@ -212,6 +218,7 @@ export async function init(argv: any) { } export async function list(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); await checkEnvironment(); @@ -260,6 +267,7 @@ export async function login(argv: any) { } export async function synth(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); await checkEnvironment(); @@ -283,6 +291,7 @@ export async function synth(argv: any) { } export async function watch(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); const command = argv.app; @@ -308,6 +317,7 @@ export async function watch(argv: any) { } export async function output(argv: any) { + await initializErrorReporting(true); throwIfNotProjectDirectory(); await displayVersionMessage(); await checkEnvironment(); diff --git a/packages/cdktf-cli/bin/cmds/helper/init.ts b/packages/cdktf-cli/bin/cmds/helper/init.ts index e616439558..0df4bee4a6 100644 --- a/packages/cdktf-cli/bin/cmds/helper/init.ts +++ b/packages/cdktf-cli/bin/cmds/helper/init.ts @@ -29,6 +29,7 @@ import { } from "@cdktf/provider-generator"; import { templates, templatesDir } from "./init-templates"; import { init, Project } from "../../../lib"; +import { askForCrashReportingConsent } from "../../../lib/error-reporting"; const chalkColour = new chalk.Instance(); @@ -57,6 +58,7 @@ type Options = { dist?: string; destination: string; fromTerraformProject?: string; + enableCrashReporting?: boolean; }; export async function runInit(argv: Options) { const telemetryData: Record = {}; @@ -122,6 +124,9 @@ This means that your Terraform state file will be stored locally on disk in a fi } } + const sendCrashReports = + argv.enableCrashReporting ?? (await askForCrashReportingConsent()); + await init({ cdktfVersion: argv.cdktfVersion, destination, @@ -129,6 +134,7 @@ This means that your Terraform state file will be stored locally on disk in a fi projectId, projectInfo, templatePath: templateInfo.Path, + sendCrashReports: sendCrashReports, }); if (argv.fromTerraformProject) { diff --git a/packages/cdktf-cli/bin/cmds/init.ts b/packages/cdktf-cli/bin/cmds/init.ts index 3dfd41d2fd..3de154e322 100644 --- a/packages/cdktf-cli/bin/cmds/init.ts +++ b/packages/cdktf-cli/bin/cmds/init.ts @@ -45,6 +45,10 @@ class Command extends BaseCommand { type: "string", desc: "Use a terraform project as the basis, CDK constructs will be generated based on the .tf files in the path", }) + .option("enable-crash-reporting", { + type: "boolean", + desc: "Enable crash reporting for the CLI, see https://www.terraform.io/cdktf/telemetry#crash-reporting for more details", + }) .strict(); public async handleCommand(argv: any) { diff --git a/packages/cdktf-cli/build.ts b/packages/cdktf-cli/build.ts index 7ba9f17a45..c1492ec495 100644 --- a/packages/cdktf-cli/build.ts +++ b/packages/cdktf-cli/build.ts @@ -68,6 +68,9 @@ const nativeNodeModulesPlugin = { "constructs", ], plugins: [nativeNodeModulesPlugin], + define: { + "process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN), + }, tsconfig: "tsconfig.json", }); diff --git a/packages/cdktf-cli/lib/checkpoint.ts b/packages/cdktf-cli/lib/checkpoint.ts index 359c0f43f4..e4d7d1b3e5 100644 --- a/packages/cdktf-cli/lib/checkpoint.ts +++ b/packages/cdktf-cli/lib/checkpoint.ts @@ -121,11 +121,11 @@ function getId( } } -function getProjectId(projectPath = process.cwd()): string { +export function getProjectId(projectPath = process.cwd()): string { return getId(path.resolve(projectPath, "cdktf.json"), "projectId"); } -function getUserId(): string { +export function getUserId(): string { return getId( path.resolve(homeDir(), "config.json"), "userId", diff --git a/packages/cdktf-cli/lib/debug.ts b/packages/cdktf-cli/lib/debug.ts index 40a071cf07..3feedd250b 100644 --- a/packages/cdktf-cli/lib/debug.ts +++ b/packages/cdktf-cli/lib/debug.ts @@ -300,12 +300,18 @@ export async function collectDebugInformation() { const language = getLanguage(); debugOutput["language"] = language ?? null; debugOutput["cdktf-cli"] = DISPLAY_VERSION; - debugOutput["node"] = (await getNodeVersion()) ?? null; + + const node = getNodeVersion(); + + debugOutput["node"] = (await node) ?? null; if (language) { - debugOutput["cdktf"] = (await getPackageVersion(language, "cdktf")) ?? null; - debugOutput["constructs"] = - (await getPackageVersion(language, "constructs")) ?? null; - debugOutput["jsii"] = (await getPackageVersion(language, "jsii")) ?? null; + const cdktf = getPackageVersion(language, "cdktf"); + const constructs = getPackageVersion(language, "constructs"); + const jsii = getPackageVersion(language, "jsii"); + + debugOutput["cdktf"] = (await cdktf) ?? null; + debugOutput["constructs"] = (await constructs) ?? null; + debugOutput["jsii"] = (await jsii) ?? null; } debugOutput["terraform"] = await terraformVersion; debugOutput["arch"] = os.arch(); @@ -313,13 +319,22 @@ export async function collectDebugInformation() { switch (language) { case "python": - debugOutput["python"] = (await getPythonVersion()) ?? null; - debugOutput["pip"] = (await getPipVersion()) ?? null; - debugOutput["pipenv"] = (await getPipenvVersion()) ?? null; + { + const python = getPythonVersion(); + const pip = getPipVersion(); + const pipenv = getPipenvVersion(); + debugOutput["python"] = (await python) ?? null; + debugOutput["pip"] = (await pip) ?? null; + debugOutput["pipenv"] = (await pipenv) ?? null; + } break; case "java": - debugOutput["java"] = (await getJavaVersion()) ?? null; - debugOutput["maven"] = (await getMavenVersion()) ?? null; + { + const java = getJavaVersion(); + const maven = getMavenVersion(); + debugOutput["java"] = (await java) ?? null; + debugOutput["maven"] = (await maven) ?? null; + } break; case "csharp": debugOutput["dotnet"] = (await getDotnetVersion()) ?? null; @@ -328,5 +343,6 @@ export async function collectDebugInformation() { debugOutput["go"] = (await getGoVersion()) ?? null; break; } + return debugOutput; } diff --git a/packages/cdktf-cli/lib/error-reporting.ts b/packages/cdktf-cli/lib/error-reporting.ts new file mode 100644 index 0000000000..03eceac12a --- /dev/null +++ b/packages/cdktf-cli/lib/error-reporting.ts @@ -0,0 +1,117 @@ +import * as Sentry from "@sentry/node"; +import { getProjectId, getUserId } from "./checkpoint"; +import { collectDebugInformation } from "./debug"; +import { logger } from "./logging"; +import * as path from "path"; +import * as fs from "fs-extra"; +import ciDetect from "@npmcli/ci-detect"; +import inquirer from "inquirer"; + +export function shouldReportCrash( + projectPath = process.cwd() +): boolean | undefined { + try { + const cdktfJson = JSON.parse( + fs.readFileSync(path.resolve(projectPath, "cdktf.json"), "utf8") + ); + + return cdktfJson.sendCrashReports; + } catch (e) { + logger.debug( + `Error determining if crash reporting should be enabled, defaulting to false: ${e}` + ); + return false; + } +} + +export function persistReportCrashReportDecision( + decision: boolean, + projectPath = process.cwd() +) { + const cdktfJson = JSON.parse( + fs.readFileSync(path.resolve(projectPath, "cdktf.json"), "utf8") + ); + cdktfJson.sendCrashReports = decision; + fs.writeFileSync( + path.resolve(projectPath, "cdktf.json"), + JSON.stringify(cdktfJson, null, 2) + ); +} + +export async function askForCrashReportingConsent() { + const answer: { reportCrash: boolean } = await inquirer.prompt({ + name: "reportCrash", + message: + "Do you want to send crash reports to the CDK team? See https://www.terraform.io/cdktf/create-and-deploy/configuration-file for more information", + type: "confirm", + default: true, + }); + + return answer.reportCrash; +} + +export async function initializErrorReporting(askForConsent = false) { + let shouldReport = shouldReportCrash(); + const ci: string | false = ciDetect(); + + // We have no info yet, so we need to ask the user + if (shouldReport === undefined && askForConsent) { + // But only if it's a user + if (ci) { + return; + } + + shouldReport = await askForCrashReportingConsent(); + persistReportCrashReportDecision(shouldReport); + } + + if (!shouldReport) { + logger.debug("Error reporting disabled"); + return; + } + if (!process.env.SENTRY_DSN) { + logger.info("Error reporting disabled: SENTRY_DSN not set"); + return; + } + + logger.debug("Initializing error reporting"); + + Sentry.init({ + autoSessionTracking: true, + dsn: process.env.SENTRY_DSN, + }); + + Sentry.configureScope(function (scope) { + scope.setUser({ + id: getUserId(), + }); + scope.setTag("projectId", getProjectId()); + }); + + logger.debug("Collecting environment information for error reporting"); + collectDebugInformation().then((debugOutput) => { + Sentry.setContext("environment", debugOutput); + }); +} + +export function captureException({ + message, + type, + command, + context, +}: { + message: string; + type: string; + command: string; + context?: Record; +}) { + if (process.env.SENTRY_DSN && shouldReportCrash()) { + Sentry.captureException(new Error(message), { + tags: { + context: JSON.stringify(context), + type, + command, + }, + }); + } +} diff --git a/packages/cdktf-cli/lib/errors.ts b/packages/cdktf-cli/lib/errors.ts index 5d4f1402ba..eeb4432b84 100644 --- a/packages/cdktf-cli/lib/errors.ts +++ b/packages/cdktf-cli/lib/errors.ts @@ -1,5 +1,6 @@ import { ReportParams, ReportRequest } from "./checkpoint"; import { DISPLAY_VERSION } from "./version"; +import * as Sentry from "@sentry/node"; // Errors that will emit telemetry events async function report(command: string, payload: Record) { @@ -45,5 +46,6 @@ export const Errors = { // Set the scope for all errors setScope(scope: string) { errorScope = scope; + Sentry.configureScope((s) => s.setTransactionName(scope)); }, }; diff --git a/packages/cdktf-cli/lib/init.ts b/packages/cdktf-cli/lib/init.ts index 6bbee317e4..8c21b70bda 100644 --- a/packages/cdktf-cli/lib/init.ts +++ b/packages/cdktf-cli/lib/init.ts @@ -27,6 +27,7 @@ export type InitArgs = { projectId: string; projectInfo: Project; templatePath: string; + sendCrashReports: boolean; }; export async function init({ @@ -36,6 +37,7 @@ export async function init({ projectId, projectInfo, templatePath, + sendCrashReports, }: InitArgs) { const deps: any = await determineDeps(cdktfVersion, dist); @@ -48,6 +50,7 @@ export async function init({ ...projectInfo, futureFlags, projectId, + sendCrashReports, }); } diff --git a/packages/cdktf-cli/lib/logging.ts b/packages/cdktf-cli/lib/logging.ts index 03715dee45..a45ecb4330 100644 --- a/packages/cdktf-cli/lib/logging.ts +++ b/packages/cdktf-cli/lib/logging.ts @@ -1,10 +1,60 @@ import { configure, getLogger } from "log4js"; import * as fs from "fs-extra"; import * as path from "path"; +import * as Sentry from "@sentry/node"; -const logger = getLogger(); +const cliLogger = getLogger(); +const logger = { + trace(message: any, ...args: any[]) { + cliLogger.trace(message, ...args); + Sentry.addBreadcrumb({ + message, + level: Sentry.Severity.Debug, + }); + }, -logger.level = process.env.CDKTF_LOG_LEVEL || "INFO"; + debug(message: any, ...args: any[]) { + cliLogger.debug(message, ...args); + Sentry.addBreadcrumb({ + message, + level: Sentry.Severity.Debug, + }); + }, + + info(message: any, ...args: any[]) { + cliLogger.info(message, ...args); + Sentry.addBreadcrumb({ + message, + level: Sentry.Severity.Info, + }); + }, + + warn(message: any, ...args: any[]) { + cliLogger.warn(message, ...args); + Sentry.addBreadcrumb({ + message, + level: Sentry.Severity.Warning, + }); + }, + + error(message: any, ...args: any[]) { + cliLogger.error(message, ...args); + Sentry.addBreadcrumb({ + message, + level: Sentry.Severity.Error, + }); + }, + + fatal(message: any, ...args: any[]) { + cliLogger.fatal(message, ...args); + Sentry.addBreadcrumb({ + message, + level: Sentry.Severity.Critical, + }); + }, +}; + +cliLogger.level = process.env.CDKTF_LOG_LEVEL || "INFO"; const logFileName = "cdktf.log"; if ( @@ -30,10 +80,4 @@ const processLoggerError = (chunk: Buffer | string | Uint8Array) => { logger.error(chunk.toString()); }; -export { - logger, - getLogger, - processLoggerDebug, - processLoggerError, - logFileName, -}; +export { logger, processLoggerDebug, processLoggerError, logFileName }; diff --git a/packages/cdktf-cli/package.json b/packages/cdktf-cli/package.json index c66b817300..059e991711 100644 --- a/packages/cdktf-cli/package.json +++ b/packages/cdktf-cli/package.json @@ -33,6 +33,7 @@ "dependencies": { "@cdktf/hcl2cdk": "0.0.0", "@cdktf/hcl2json": "0.0.0", + "@sentry/node": "^6.19.7", "@types/yargs": "^17.0.10", "cdktf": "0.0.0", "constructs": "^10.0.25", diff --git a/packages/cdktf-cli/templates/csharp/cdktf.json b/packages/cdktf-cli/templates/csharp/cdktf.json index efde067910..843cc996d6 100644 --- a/packages/cdktf-cli/templates/csharp/cdktf.json +++ b/packages/cdktf-cli/templates/csharp/cdktf.json @@ -2,6 +2,7 @@ "language": "csharp", "app": "dotnet run -p MyTerraformStack.csproj", "projectId": "{{projectId}}", + "sendCrashReports": "{{sendCrashReports}}", "terraformProviders": [], "terraformModules": [], "context": { diff --git a/packages/cdktf-cli/templates/go/cdktf.json b/packages/cdktf-cli/templates/go/cdktf.json index 9aacc9b2fb..35983cb343 100644 --- a/packages/cdktf-cli/templates/go/cdktf.json +++ b/packages/cdktf-cli/templates/go/cdktf.json @@ -3,6 +3,7 @@ "app": "go run main.go", "codeMakerOutput": "generated", "projectId": "{{projectId}}", + "sendCrashReports": "{{sendCrashReports}}", "terraformProviders": [], "terraformModules": [], "context": { diff --git a/packages/cdktf-cli/templates/java/cdktf.json b/packages/cdktf-cli/templates/java/cdktf.json index 4671a019ca..2aa35cf2bc 100644 --- a/packages/cdktf-cli/templates/java/cdktf.json +++ b/packages/cdktf-cli/templates/java/cdktf.json @@ -2,6 +2,7 @@ "language": "java", "app": "mvn -e -q compile exec:java", "projectId": "{{projectId}}", + "sendCrashReports": "{{sendCrashReports}}", "terraformProviders": [], "terraformModules": [], "context": { diff --git a/packages/cdktf-cli/templates/python-pip/cdktf.json b/packages/cdktf-cli/templates/python-pip/cdktf.json index 2679a106c5..5f4045b8cc 100644 --- a/packages/cdktf-cli/templates/python-pip/cdktf.json +++ b/packages/cdktf-cli/templates/python-pip/cdktf.json @@ -2,6 +2,7 @@ "language": "python", "app": "python3 ./main.py", "projectId": "{{projectId}}", + "sendCrashReports": "{{sendCrashReports}}", "terraformProviders": [], "terraformModules": [], "codeMakerOutput": "imports", diff --git a/packages/cdktf-cli/templates/python/cdktf.json b/packages/cdktf-cli/templates/python/cdktf.json index 44ad17afd5..64270ebd9e 100644 --- a/packages/cdktf-cli/templates/python/cdktf.json +++ b/packages/cdktf-cli/templates/python/cdktf.json @@ -2,6 +2,7 @@ "language": "python", "app": "pipenv run python main.py", "projectId": "{{projectId}}", + "sendCrashReports": "{{sendCrashReports}}", "terraformProviders": [], "terraformModules": [], "codeMakerOutput": "imports", diff --git a/packages/cdktf-cli/templates/typescript/cdktf.json b/packages/cdktf-cli/templates/typescript/cdktf.json index f24c7faf7d..52383c3366 100644 --- a/packages/cdktf-cli/templates/typescript/cdktf.json +++ b/packages/cdktf-cli/templates/typescript/cdktf.json @@ -2,6 +2,7 @@ "language": "typescript", "app": "npx ts-node main.ts", "projectId": "{{projectId}}", + "sendCrashReports": "{{sendCrashReports}}", "terraformProviders": [], "terraformModules": [], "context": { diff --git a/packages/cdktf-cli/test/lib/cdktf-project.test.ts b/packages/cdktf-cli/test/lib/cdktf-project.test.ts index e70d7c2421..b2714086e3 100644 --- a/packages/cdktf-cli/test/lib/cdktf-project.test.ts +++ b/packages/cdktf-cli/test/lib/cdktf-project.test.ts @@ -27,6 +27,7 @@ describe("CdktfProject", () => { Description: "cdktf-api-test", Name: "cdktf-api-test", }, + sendCrashReports: false, dist: path.join(__dirname, "../../../../dist"), }); diff --git a/test/test-helper.ts b/test/test-helper.ts index 00551cc537..cc0fe9e237 100644 --- a/test/test-helper.ts +++ b/test/test-helper.ts @@ -165,7 +165,7 @@ export class TestDriver { init = async (template: string, additionalOptions = "") => { await this.exec( - `cdktf init --template ${template} --project-name="typescript-test" --project-description="typescript test app" --local ${additionalOptions}` + `cdktf init --template ${template} --project-name="typescript-test" --project-description="typescript test app" --local --enable-crash-reporting=false ${additionalOptions}` ); }; diff --git a/website/docs/cdktf/cli-reference/commands.mdx b/website/docs/cdktf/cli-reference/commands.mdx index b61f2f11b6..57aeb1329b 100644 --- a/website/docs/cdktf/cli-reference/commands.mdx +++ b/website/docs/cdktf/cli-reference/commands.mdx @@ -339,24 +339,20 @@ cdktf init [OPTIONS] Create a new cdktf project from a template. Options: - --version Show version number [boolean] - [boolean] [default: true] - --disable-plugin-cache-env Dont set TF_PLUGIN_CACHE_DIR automatically. This is useful when the plugin cache is - configured differently. Supported using the env CDKTF_DISABLE_PLUGIN_CACHE_ENV. - [boolean] [default: false] - --log-level Which log level should be written. Only supported via setting the env CDKTF_LOG_LEVEL - [string] - --template The template to be used to create a new project. Either URL to zip file or one of the - built-in templates: ["csharp", "go", "java", "python", "python-pip", "typescript"] - [string] - --project-name The name of the project. [string] - --project-description The description of the project. [string] - --dist Install dependencies from a "dist" directory (for development) [string] - --local Use local state storage for generated Terraform. [boolean] [default: false] - --cdktf-version The cdktf version to use while creating a new project. [string] [default: "0.0.0"] - --from-terraform-project Use a terraform project as the basis, CDK constructs will be generated based on the - .tf files in the path [string] - -h, --help Show help [boolean] + --version Show version number [boolean] + --disable-plugin-cache-env Dont set TF_PLUGIN_CACHE_DIR automatically. This is useful when the plugin cache is configured differently. Supported using the env + CDKTF_DISABLE_PLUGIN_CACHE_ENV. [boolean] [default: false] + --log-level Which log level should be written. Only supported via setting the env CDKTF_LOG_LEVEL [string] + --template The template to be used to create a new project. Either URL to zip file or one of the built-in templates: ["csharp", "go", "java", "python", + "python-pip", "typescript"] [string] + --project-name The name of the project. [string] + --project-description The description of the project. [string] + --dist Install dependencies from a "dist" directory (for development) [string] + --local Use local state storage for generated Terraform. [boolean] [default: false] + --cdktf-version The cdktf version to use while creating a new project. [string] [default: "0.0.0"] + --from-terraform-project Use a terraform project as the basis, CDK constructs will be generated based on the .tf files in the path [string] + --enable-crash-reporting Enable crash reporting for the CLI, see https://www.terraform.io/cdktf/telemetry#crash-reporting for more details [boolean] + -h, --help Show help [boolean] ``` **Examples** diff --git a/website/docs/cdktf/create-and-deploy/configuration-file.mdx b/website/docs/cdktf/create-and-deploy/configuration-file.mdx index 0cc6b87663..62119262a3 100644 --- a/website/docs/cdktf/create-and-deploy/configuration-file.mdx +++ b/website/docs/cdktf/create-and-deploy/configuration-file.mdx @@ -33,6 +33,7 @@ export interface Config { readonly output: string; // Default: 'cdktf.out'. Where the synthesized JSON should go. Also will be the working directory for Terraform operations readonly codeMakerOutput: string; // Default: '.gen'. Path where generated provider bindings will be rendered to. readonly projectId: string; // Default: generated UUID. Unique identifier for the project used to differentiate projects + readonly sendCrashReports: boolean; // Default: false. Whether to send crash reports to the CDK team readonly terraformProviders?: RequirementDefinition[]; // Terraform Providers to build readonly terraformModules?: RequirementDefinition[]; // Terraform Modules to build } @@ -206,3 +207,15 @@ This generates the `aws` provider bindings in the folder `./imports`. This is us "codeMakerOutput": "imports" } ``` + +### Enable Crash Reporting for the CLI + +You can enable or disable crash reporting by setting the `sendCrashReports` property to `true` or `false`. Sending crash reports helps our team improve the CLI faster, you can learn more about what we track on our [telemetry page](/cdktf/telemetry#crash-reporting). + +```json +{ + "language": "typescript", + "app": "npm run --silent compile && node main.js", + "sendCrashReports": "true" +} +``` diff --git a/website/docs/cdktf/telemetry.mdx b/website/docs/cdktf/telemetry.mdx index d90649637b..077dae37e1 100644 --- a/website/docs/cdktf/telemetry.mdx +++ b/website/docs/cdktf/telemetry.mdx @@ -14,3 +14,13 @@ Starting with CDK for Terraform 0.6, this information includes a random UUID tha CDK for Terraform fingerprints the type of CI used and includes that in telemetry instead of the UUID when CDK for Terraform runs in a Continuous Integration tool, such as GitHub Actions, Jenkins, or GitLab. The only information submitted is the type of CI system, and no unique information about accounts, paths, workspaces, environment variables, or other potentially private information is shared. This data helps the team plan where to focus future efforts, because running infrastructure as code tools in CI is a key workflow we seek to improve. The information that is sent to Checkpoint is anonymous and cannot be used to identify the user or host. The use of Checkpoint is completely optional and it can be disabled at any time by setting the `CHECKPOINT_DISABLE` environment variable to a non-empty value. + +## Crash Reporting + +To improve the stability of the [CDK for Terraform CLI](/cdktf/cli-reference/cli-configuration) we also [Sentry](https://sentry.io/) for error reporting. + +The error reports send include a stack trace and log events that happened before the error. This information only gets send if there is an error. If no error happens a session is tracked, so that we can calculate the percentage of crash free sessions as a metric. + +The error reporting is scoped to the CLI and no environment variables are tracked, limiting the risk of sending secret information by accident. + +If you don't want your project to track errors, change the [`sendCrashReports`](/cdktf/create-and-deploy/configuration-file#enable-crash-reporting-for-the-cli) property in your `cdktf.json` file to false. diff --git a/yarn.lock b/yarn.lock index 90a5c7e00a..3a802a3795 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1631,6 +1631,62 @@ dependencies: "@octokit/openapi-types" "^11.2.0" +"@sentry/core@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.19.7.tgz#156aaa56dd7fad8c89c145be6ad7a4f7209f9785" + integrity sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw== + dependencies: + "@sentry/hub" "6.19.7" + "@sentry/minimal" "6.19.7" + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + tslib "^1.9.3" + +"@sentry/hub@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.19.7.tgz#58ad7776bbd31e9596a8ec46365b45cd8b9cfd11" + integrity sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA== + dependencies: + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + tslib "^1.9.3" + +"@sentry/minimal@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.19.7.tgz#b3ee46d6abef9ef3dd4837ebcb6bdfd01b9aa7b4" + integrity sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ== + dependencies: + "@sentry/hub" "6.19.7" + "@sentry/types" "6.19.7" + tslib "^1.9.3" + +"@sentry/node@^6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.19.7.tgz#32963b36b48daebbd559e6f13b1deb2415448592" + integrity sha512-gtmRC4dAXKODMpHXKfrkfvyBL3cI8y64vEi3fDD046uqYcrWdgoQsffuBbxMAizc6Ez1ia+f0Flue6p15Qaltg== + dependencies: + "@sentry/core" "6.19.7" + "@sentry/hub" "6.19.7" + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + cookie "^0.4.1" + https-proxy-agent "^5.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/types@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.7.tgz#c6b337912e588083fc2896eb012526cf7cfec7c7" + integrity sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg== + +"@sentry/utils@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79" + integrity sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA== + dependencies: + "@sentry/types" "6.19.7" + tslib "^1.9.3" + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -3381,6 +3437,11 @@ convert-to-spaces@^1.0.1: resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz#7e3e48bbe6d997b1417ddca2868204b4d3d85715" integrity sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU= +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -7091,6 +7152,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= + make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -9725,7 +9791,7 @@ tsconfig-paths@^3.12.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==