diff --git a/packages/cli/src/commands/telemetry/add.ts b/packages/cli/src/commands/telemetry/add.ts index cb1157616a..0e00acd248 100644 --- a/packages/cli/src/commands/telemetry/add.ts +++ b/packages/cli/src/commands/telemetry/add.ts @@ -2,7 +2,7 @@ import {Command, flags as Flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import {TelemetryDrain} from '../../lib/types/telemetry' import heredoc from 'tsheredoc' - +import {validateAndFormatSignal} from '../../lib/telemetry/util' export default class Add extends Command { static description = 'Add and configure a new telemetry drain. Defaults to collecting all telemetry unless otherwise specified.' @@ -24,18 +24,6 @@ export default class Add extends Command { $ heroku telemetry:add --signal logs,traces --endpoint https://my-endpoint.com --transport http 'x-drain-example-team: API_KEY x-drain-example-dataset: METRICS_DATASET' `) - private validateAndFormatSignal = function (signalInput: string | undefined): string[] { - const signalOptions = ['traces', 'metrics', 'logs'] - if (!signalInput || signalInput === 'all') return signalOptions - const signalArray = signalInput.split(',') - signalArray.forEach(signal => { - if (!signalOptions.includes(signal)) { - ux.error(`Invalid signal option: ${signalArray}. Run heroku telemetry:add --help to see signal options.`, {exit: 1}) - } - }) - return signalArray - } - private getTypeAndName = function (app: string | undefined, space: string | undefined) { if (app) { return {type: 'app', name: app} @@ -54,7 +42,7 @@ export default class Add extends Command { type: typeAndName.type, id: typeAndName.name, }, - signals: this.validateAndFormatSignal(signal), + signals: validateAndFormatSignal(signal), exporter: { endpoint, type: `otlp${transport}`, diff --git a/packages/cli/src/commands/telemetry/index.ts b/packages/cli/src/commands/telemetry/index.ts index 69e806d680..8aa57f04d3 100644 --- a/packages/cli/src/commands/telemetry/index.ts +++ b/packages/cli/src/commands/telemetry/index.ts @@ -37,7 +37,7 @@ export default class Index extends Command { telemetryDrains, { ID: {get: telemetryDrain => telemetryDrain.id}, - Signals: {get: telemetryDrain => telemetryDrain.capabilities}, + Signals: {get: telemetryDrain => telemetryDrain.signals}, Endpoint: {get: telemetryDrain => telemetryDrain.exporter.endpoint}, [ownerType]: {get: telemetryDrain => telemetryDrain.owner.name}, }, diff --git a/packages/cli/src/commands/telemetry/info.ts b/packages/cli/src/commands/telemetry/info.ts index b92374cc98..3babe14791 100644 --- a/packages/cli/src/commands/telemetry/info.ts +++ b/packages/cli/src/commands/telemetry/info.ts @@ -26,7 +26,7 @@ export default class Info extends Command { const drainType = telemetryDrain.owner.type.charAt(0).toUpperCase() + telemetryDrain.owner.type.slice(1) ux.styledObject({ [drainType]: telemetryDrain.owner.name, - Signals: telemetryDrain.capabilities.join(', '), + Signals: telemetryDrain.signals.join(', '), Endpoint: telemetryDrain.exporter.endpoint, Kind: telemetryDrain.exporter.type, Headers: telemetryDrain.exporter.headers, diff --git a/packages/cli/src/commands/telemetry/update.ts b/packages/cli/src/commands/telemetry/update.ts new file mode 100644 index 0000000000..5d4619b8e5 --- /dev/null +++ b/packages/cli/src/commands/telemetry/update.ts @@ -0,0 +1,63 @@ +import {flags as Flags, Command} from '@heroku-cli/command' +import {Args, ux} from '@oclif/core' +import {TelemetryDrain, TelemetryDrainWithOptionalKeys, TelemetryExporterWithOptionalKeys} from '../../lib/types/telemetry' +import heredoc from 'tsheredoc' +import {validateAndFormatSignal} from '../../lib/telemetry/util' + +export default class Update extends Command { + static topic = 'telemetry' + static description = 'updates a telemetry drain' + static args = { + telemetry_drain_id: Args.string({required: true, description: 'ID of the drain to update'}), + headers: Args.string({description: 'custom headers to configure the drain in json format'}), + } + + static flags = { + signal: Flags.string({default: 'all', description: 'comma-delimited list of signals to collect (traces, metrics, logs). Use "all" to collect all signals.'}), + endpoint: Flags.string({description: 'drain url'}), + transport: Flags.string({options: ['http', 'gprc'], description: 'transport protocol for the drain'}), + } + + public async run(): Promise { + const {args, flags} = await this.parse(Update) + const {telemetry_drain_id, headers} = args + const {signal, endpoint, transport} = flags + if (!(headers || signal || endpoint || transport)) { + ux.error(heredoc(` + Requires either --signal, --endpoint, --transport or HEADERS to be provided. + See more help with --help + `)) + } + + const drainConfig: TelemetryDrainWithOptionalKeys = {} + if (signal) { + drainConfig.signals = validateAndFormatSignal(signal) + } + + if (headers || endpoint || transport) { + const exporter: TelemetryExporterWithOptionalKeys = {} + if (headers) { + exporter.headers = JSON.parse(headers) + } + + if (endpoint) { + exporter.endpoint = endpoint + } + + if (transport) { + exporter.type = `otlp${transport}` + } + + drainConfig.exporter = exporter + } + + const {body: telemetryDrain} = await this.heroku.patch(`/telemetry-drains/${telemetry_drain_id}`, { + headers: { + Accept: 'application/vnd.heroku+json; version=3.sdk', + }, + body: drainConfig, + }) + ux.action.start(`Updating telemetry drain ${telemetry_drain_id}, which was configured for ${telemetryDrain.owner.type} ${telemetryDrain.owner.name}`) + ux.action.stop() + } +} diff --git a/packages/cli/src/lib/telemetry/util.ts b/packages/cli/src/lib/telemetry/util.ts new file mode 100644 index 0000000000..8cdf721251 --- /dev/null +++ b/packages/cli/src/lib/telemetry/util.ts @@ -0,0 +1,13 @@ +import {ux} from '@oclif/core' + +export function validateAndFormatSignal(signalInput: string | undefined): string[] { + const signalOptions = ['traces', 'metrics', 'logs'] + if (!signalInput || signalInput === 'all') return signalOptions + const signalArray = signalInput.split(',') + signalArray.forEach(signal => { + if (!signalOptions.includes(signal)) { + ux.error(`Invalid signal option: ${signalArray}. Run heroku telemetry:add --help to see signal options.`, {exit: 1}) + } + }) + return signalArray +} diff --git a/packages/cli/src/lib/types/telemetry.d.ts b/packages/cli/src/lib/types/telemetry.d.ts index 4d77fa64f4..6c10124c07 100644 --- a/packages/cli/src/lib/types/telemetry.d.ts +++ b/packages/cli/src/lib/types/telemetry.d.ts @@ -2,7 +2,7 @@ export type TelemetryDrains = TelemetryDrain[] export type TelemetryDrain = { id: string; - capabilities: string[]; + signals: string[]; owner: TelemetryDrainOwner; exporter: TelemetryExporter } @@ -14,7 +14,10 @@ type TelemetryDrainOwner = { } type TelemetryExporter = { - type: 'otlphttp' | 'otlpgrpc'; + type: string; endpoint: string; headers: unknown; } + +type TelemetryDrainWithOptionalKeys = Partial +type TelemetryExporterWithOptionalKeys = Partial diff --git a/packages/cli/test/fixtures/telemetry/fixtures.ts b/packages/cli/test/fixtures/telemetry/fixtures.ts index 28fc955c0a..3d6f5ab2bd 100644 --- a/packages/cli/test/fixtures/telemetry/fixtures.ts +++ b/packages/cli/test/fixtures/telemetry/fixtures.ts @@ -3,7 +3,7 @@ import {TelemetryDrain} from '../../../src/lib/types/telemetry' export const spaceTelemetryDrain1: TelemetryDrain = { id: '44444321-5717-4562-b3fc-2c963f66afa6', owner: {id: '12345678-5717-4562-b3fc-2c963f66afa6', type: 'space', name: 'myspace'}, - capabilities: ['traces', 'metrics', 'logs'], + signals: ['traces', 'metrics', 'logs'], exporter: { type: 'otlphttp', endpoint: 'https://api.honeycomb.io/', @@ -17,7 +17,7 @@ export const spaceTelemetryDrain1: TelemetryDrain = { export const appTelemetryDrain1: TelemetryDrain = { id: '3fa85f64-5717-4562-b3fc-2c963f66afa6', owner: {id: '87654321-5717-4562-b3fc-2c963f66afa6', type: 'app', name: 'myapp'}, - capabilities: ['traces', 'metrics'], + signals: ['traces', 'metrics'], exporter: { type: 'otlphttp', endpoint: 'https://api.honeycomb.io/', @@ -31,7 +31,7 @@ export const appTelemetryDrain1: TelemetryDrain = { export const appTelemetryDrain2: TelemetryDrain = { id: '55555f64-5717-4562-b3fc-2c963f66afa6', owner: {id: '87654321-5717-4562-b3fc-2c963f66afa6', type: 'app', name: 'myapp'}, - capabilities: ['logs'], + signals: ['logs'], exporter: { type: 'otlphttp', endpoint: 'https://api.papertrail.com/', diff --git a/packages/cli/test/unit/commands/telemetry/info.unit.test.ts b/packages/cli/test/unit/commands/telemetry/info.unit.test.ts index d158314a67..dfdda82cbc 100644 --- a/packages/cli/test/unit/commands/telemetry/info.unit.test.ts +++ b/packages/cli/test/unit/commands/telemetry/info.unit.test.ts @@ -16,7 +16,7 @@ describe('telemetry:info', function () { spaceTelemetryDrain = { id: '44444321-5717-4562-b3fc-2c963f66afa6', owner: {id: spaceId, type: 'space', name: 'myspace'}, - capabilities: ['traces', 'metrics', 'logs'], + signals: ['traces', 'metrics', 'logs'], exporter: { type: 'otlphttp', endpoint: 'https://api.honeycomb.io/', @@ -29,7 +29,7 @@ describe('telemetry:info', function () { appTelemetryDrain = { id: '3fa85f64-5717-4562-b3fc-2c963f66afa6', owner: {id: appId, type: 'app', name: 'myapp'}, - capabilities: ['traces', 'metrics'], + signals: ['traces', 'metrics'], exporter: { type: 'otlphttp', endpoint: 'https://api.honeycomb.io/', @@ -56,7 +56,7 @@ describe('telemetry:info', function () { expectOutput(stdout.output, heredoc(` === ${spaceTelemetryDrain.id} Space: ${spaceTelemetryDrain.owner.name} - Signals: ${spaceTelemetryDrain.capabilities.join(', ')} + Signals: ${spaceTelemetryDrain.signals.join(', ')} Endpoint: ${spaceTelemetryDrain.exporter.endpoint} Kind: ${spaceTelemetryDrain.exporter.type} Headers: x-honeycomb-team: 'your-api-key', x-honeycomb-dataset: 'your-dataset' @@ -74,7 +74,7 @@ describe('telemetry:info', function () { expectOutput(stdout.output, heredoc(` === ${appTelemetryDrain.id} App: ${appTelemetryDrain.owner.name} - Signals: ${appTelemetryDrain.capabilities.join(', ')} + Signals: ${appTelemetryDrain.signals.join(', ')} Endpoint: ${appTelemetryDrain.exporter.endpoint} Kind: ${appTelemetryDrain.exporter.type} Headers: x-honeycomb-team: 'your-api-key', x-honeycomb-dataset: 'your-dataset'