Skip to content

Commit

Permalink
Introduce a BaseCommand with telemetry code inside instead of each co…
Browse files Browse the repository at this point in the history
…mmand defining it manually
  • Loading branch information
pziemkowski committed Sep 14, 2023
1 parent 4766683 commit eecfd74
Show file tree
Hide file tree
Showing 52 changed files with 730 additions and 891 deletions.
91 changes: 91 additions & 0 deletions packages/internal/cli/src/baseCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Command, Flags, Interfaces } from '@oclif/core';
import { ExitError } from '@oclif/core/lib/errors';
import { Span, SpanStatusCode, trace, Tracer } from '@opentelemetry/api';

import { SB_TELEMETRY_DISABLED } from './config/env';
import { traceExporter } from './config/telemetry';

const formatAttrs = (obj: { [k: string]: string } = {}, prefix = '') => {
return Object.fromEntries(
Object.keys(obj).map((key: string) => {
return [[prefix, key].join('.'), obj[key]];
})
);
};

export type Flags<T extends typeof Command> = Interfaces.InferredFlags<
(typeof BaseCommand)['baseFlags'] & T['flags']
>;
export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;

export abstract class BaseCommand<T extends typeof Command> extends Command {
protected tracer: Tracer | null = null;
protected span: Span | null = null;

static baseFlags = {};

protected flags!: Flags<T>;
protected args!: Args<T>;

public async init(): Promise<void> {
await super.init();
const { args, flags } = await this.parse({
flags: this.ctor.flags,
baseFlags: (super.ctor as typeof BaseCommand).baseFlags,
args: this.ctor.args,
strict: this.ctor.strict,
});
this.flags = flags as Flags<T>;
this.args = args as Args<T>;

if (!SB_TELEMETRY_DISABLED) {
this.printTelemetryInfo();
this.tracer = trace.getTracer('command', this.config.version);

this.span = this.tracer.startSpan(`command.${this.ctor.id}`, {
attributes: {
...formatAttrs(flags, 'flags'),
...formatAttrs(args, 'args'),
},
});
}
}

protected async catch(err: Error & { exitCode?: number }): Promise<any> {
if (!SB_TELEMETRY_DISABLED) {
if (!(err instanceof ExitError) || err.oclif.exit !== 0) {
this.span?.addEvent('Command error');
this.span?.recordException(err);
this.span?.setStatus({ code: SpanStatusCode.ERROR });
}
}
return super.catch(err);
}

protected async finally(_: Error | undefined): Promise<any> {
if (!SB_TELEMETRY_DISABLED) {
this.span?.addEvent('Command finished');
this.span?.end();

// Need to wait en event loop for the internal promise in exporter to be visible
await new Promise((resolve) => setTimeout(() => resolve(true)));
// wait for the exporter to send data
await traceExporter.forceFlush();
}
return super.finally(_);
}

protected printTelemetryInfo(): void {
console.log({SB_TELEMETRY_DISABLED})
if (!SB_TELEMETRY_DISABLED) {
this.log(`\x1b[2m
------ Notice ------
This CLI collects various anonymous events, warnings, and errors to improve the CLI tool and enhance your user experience.
Read more: [Your GitHub or documentation link here]
If you want to opt out of telemetry, you can set the environment variable SB_TELEMETRY_DISABLED to 1 in your shell.
For example:
export SB_TELEMETRY_DISABLED=1
\x1b[0m`);
}
}
}
14 changes: 4 additions & 10 deletions packages/internal/cli/src/commands/aws/get-env.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { Command } from '@oclif/core';
import { trace } from '@opentelemetry/api';

import { initConfig } from '../../config/init';
import { BaseCommand } from '../../baseCommand';

const tracer = trace.getTracer('aws');
export default class GetEnv extends Command {
export default class GetEnv extends BaseCommand<typeof GetEnv> {
static description = 'Get currently selected ENV stage';

static examples = [`$ <%= config.bin %> <%= command.id %>`];

async run(): Promise<void> {
return tracer.startActiveSpan('get-env', async (span) => {
const { envStage } = await initConfig(this, {});
this.log(envStage);
span.end();
});
const { envStage } = await initConfig(this, {});
this.log(envStage);
}
}
16 changes: 5 additions & 11 deletions packages/internal/cli/src/commands/aws/login.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { Command } from '@oclif/core';
import { trace } from '@opentelemetry/api';

import { initConfig } from '../../config/init';
import { runCommand } from '../../lib/runCommand';
import { assertAwsVaultInstalled } from '../../lib/awsVault';
import { BaseCommand } from '../../baseCommand';

const tracer = trace.getTracer('aws');
export default class AwsLogin extends Command {
export default class AwsLogin extends BaseCommand<typeof AwsLogin> {
static description = 'Get currently selected ENV stage';

static examples = [`$ <%= config.bin %> <%= command.id %>`];

async run(): Promise<void> {
return tracer.startActiveSpan('login', async (span) => {
await initConfig(this, { requireAws: true });
await assertAwsVaultInstalled();
await initConfig(this, { requireAws: true });
await assertAwsVaultInstalled();

await runCommand('aws-vault', ['login']);
span.end();
});
await runCommand('aws-vault', ['login']);
}
}
30 changes: 13 additions & 17 deletions packages/internal/cli/src/commands/aws/set-env.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Args, Command } from '@oclif/core';
import { trace } from '@opentelemetry/api';
import {Args} from '@oclif/core';

import { setEnvStage } from '../../config/storage';
import { initConfig } from '../../config/init';
import {setEnvStage} from '../../config/storage';
import {initConfig} from '../../config/init';
import {BaseCommand} from '../../baseCommand';

const tracer = trace.getTracer('aws');
export default class SetEnv extends Command {
export default class SetEnv extends BaseCommand<typeof SetEnv> {
static description = 'Select ENV stage';

static examples = [
Expand All @@ -23,18 +22,15 @@ export default class SetEnv extends Command {
};

async run(): Promise<void> {
return tracer.startActiveSpan('set-env', async (span) => {
const { args, flags } = await this.parse(SetEnv);
const { envStage: currentEnvStage } = await initConfig(this, {});
const { args, flags } = await this.parse(SetEnv);
const { envStage: currentEnvStage } = await initConfig(this, {});

if (currentEnvStage === args.envStage) {
this.log(`Your environment stage is already set to ${args.envStage}.`);
return;
}
if (currentEnvStage === args.envStage) {
this.log(`Your environment stage is already set to ${args.envStage}.`);
return;
}

await setEnvStage(args.envStage);
this.log(`Switched environment stage to ${args.envStage}.`);
span.end();
});
await setEnvStage(args.envStage);
this.log(`Switched environment stage to ${args.envStage}.`);
}
}
56 changes: 24 additions & 32 deletions packages/internal/cli/src/commands/backend/black.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,36 @@
import { Command } from '@oclif/core';
import { trace } from '@opentelemetry/api';

import { initConfig } from '../../config/init';
import { runCommand } from '../../lib/runCommand';
import { assertDockerIsRunning, dockerHubLogin } from '../../lib/docker';
import { BaseCommand } from '../../baseCommand';

const tracer = trace.getTracer('backend');

export default class BackendBlack extends Command {
export default class BackendBlack extends BaseCommand<typeof BackendBlack> {
static description = 'Run black inside backend docker container';

static examples = [`$ <%= config.bin %> <%= command.id %>`];

async run(): Promise<void> {
return tracer.startActiveSpan('black', async (span) => {
const { rootPath } = await initConfig(this, {
requireLocalEnvStage: true,
});
await assertDockerIsRunning();
await dockerHubLogin();

await runCommand(
'docker',
[
'compose',
'run',
'--rm',
'-T',
'--no-deps',
'backend',
'black',
'--config=pyproject.toml',
'.',
],
{
cwd: rootPath,
}
);

span.end();
const { rootPath } = await initConfig(this, {
requireLocalEnvStage: true,
});
await assertDockerIsRunning();
await dockerHubLogin();

await runCommand(
'docker',
[
'compose',
'run',
'--rm',
'-T',
'--no-deps',
'backend',
'black',
'--config=pyproject.toml',
'.',
],
{
cwd: rootPath,
}
);
}
}
19 changes: 7 additions & 12 deletions packages/internal/cli/src/commands/backend/build-docs.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { Command } from '@oclif/core';
import { trace } from '@opentelemetry/api';

import { initConfig } from '../../config/init';
import { runCommand } from '../../lib/runCommand';
import { dockerHubLogin } from '../../lib/docker';
import { BaseCommand } from '../../baseCommand';

const tracer = trace.getTracer('backend');

export default class BackendBuild extends Command {
export default class BackendBuildDocs extends BaseCommand<
typeof BackendBuildDocs
> {
static description = 'Build backend docs and put results into docs package';

static examples = [`$ <%= config.bin %> <%= command.id %>`];

async run(): Promise<void> {
return tracer.startActiveSpan('build-docs', async (span) => {
await initConfig(this, {});
await dockerHubLogin();
await initConfig(this, {});
await dockerHubLogin();

await runCommand('pnpm', ['nx', 'run', 'backend:build-docs']);
span.end();
});
await runCommand('pnpm', ['nx', 'run', 'backend:build-docs']);
}
}
23 changes: 9 additions & 14 deletions packages/internal/cli/src/commands/backend/build.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import { Command } from '@oclif/core';
import { color } from '@oclif/color';
import { trace } from '@opentelemetry/api';

import { initConfig } from '../../config/init';
import { runCommand } from '../../lib/runCommand';
import { dockerHubLogin } from '../../lib/docker';
import { BaseCommand } from '../../baseCommand';

const tracer = trace.getTracer('backend');
export default class BackendBuild extends Command {
export default class BackendBuild extends BaseCommand<typeof BackendBuild> {
static description = 'Build backend docker image and upload it to AWS ECR';

static examples = [`$ <%= config.bin %> <%= command.id %>`];

async run(): Promise<void> {
return tracer.startActiveSpan('build', async (span) => {
const { envStage, version, awsRegion, awsAccountId } = await initConfig(
this,
{ requireAws: true }
);
await dockerHubLogin();
const { envStage, version, awsRegion, awsAccountId } = await initConfig(
this,
{ requireAws: true }
);
await dockerHubLogin();

this.log(`Building backend:
this.log(`Building backend:
envStage: ${color.green(envStage)}
version: ${color.green(version)}
AWS account: ${color.green(awsAccountId)}
AWS region: ${color.green(awsRegion)}
`);

await runCommand('pnpm', ['nx', 'run', 'backend:build']);
span.end();
});
await runCommand('pnpm', ['nx', 'run', 'backend:build']);
}
}
28 changes: 13 additions & 15 deletions packages/internal/cli/src/commands/backend/deploy/api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Command, Flags } from '@oclif/core';
import { Flags } from '@oclif/core';
import { color } from '@oclif/color';
import { trace } from '@opentelemetry/api';

import { initConfig } from '../../../config/init';
import { runCommand } from '../../../lib/runCommand';
import { BaseCommand } from '../../../baseCommand';

const tracer = trace.getTracer('backend');
export default class BackendDeployApi extends Command {
export default class BackendDeployApi extends BaseCommand<
typeof BackendDeployApi
> {
static description =
'Deploys backend API to AWS using previously built artifact';

Expand All @@ -22,23 +23,20 @@ export default class BackendDeployApi extends Command {
};

async run(): Promise<void> {
return tracer.startActiveSpan('deploy-api', async (span) => {
const { flags } = await this.parse(BackendDeployApi);
const { envStage, version, awsRegion, awsAccountId } = await initConfig(
this,
{ requireAws: true }
);
const { flags } = await this.parse(BackendDeployApi);
const { envStage, version, awsRegion, awsAccountId } = await initConfig(
this,
{ requireAws: true }
);

this.log(`Deploying backend:
this.log(`Deploying backend:
envStage: ${color.green(envStage)}
version: ${color.green(version)}
AWS account: ${color.green(awsAccountId)}
AWS region: ${color.green(awsRegion)}
`);

const verb = flags.diff ? 'diff' : 'deploy';
await runCommand('pnpm', ['nx', 'run', `backend:${verb}:api`]);
span.end();
});
const verb = flags.diff ? 'diff' : 'deploy';
await runCommand('pnpm', ['nx', 'run', `backend:${verb}:api`]);
}
}
Loading

0 comments on commit eecfd74

Please sign in to comment.