import { kody } from 'kody:runtime'
import {
API_KEY_SETUP_URL,
APPLICATION_KEY_SETUP_URL,
DATADOG_API_HOSTS,
resolveApiHost,
resolveApiKeySecretName,
resolveApplicationKeySecretName,
secretSetupUrl,
} from './core.ts'
import {
createDashboard,
deleteDashboard,
listDashboards,
updateDashboard,
} from './dashboards.ts'
import { createIncident, deleteIncident, updateIncident } from './incidents.ts'
import {
createMonitor,
deleteMonitor,
listMonitors,
muteMonitor,
updateMonitor,
} from './monitors.ts'
import { validateKeys } from './validate.ts'
import type { DatadogSmokeTestInput } from './types.ts'
function secretEntries(result: unknown): Array<{ name?: string; scope?: string }> {
if (result && typeof result === 'object' && Array.isArray((result as { secrets?: unknown }).secrets)) {
return (result as { secrets: Array<{ name?: string; scope?: string }> }).secrets
}
return Array.isArray(result) ? result : []
}
async function listUserSecretNames(): Promise<Set<string>> {
try {
const listed = await kody.secret_list({ scope: 'user' })
const names = new Set<string>()
for (const entry of secretEntries(listed)) {
if (entry?.name && (entry.scope === 'user' || !entry.scope)) names.add(entry.name)
}
return names
} catch {
return new Set()
}
}
async function mutationSelfCheck() {
const monitor = {
monitorId: 1,
type: 'query alert',
query: 'avg(last_5m):avg:system.cpu.user{*} > 95',
name: 'smoke-preview',
message: 'dry-run only',
}
const create = await createMonitor({ ...monitor, dryRun: true })
const update = await updateMonitor({ ...monitor, dryRun: true })
const mute = await muteMonitor({ monitorId: 1, dryRun: true })
const del = await deleteMonitor({ monitorId: 1, dryRun: true })
const incident = await createIncident({ title: 'smoke-preview', customerImpacted: false, dryRun: true })
const incidentUpdate = await updateIncident({
incidentId: 'incident-smoke',
title: 'smoke-preview',
dryRun: true,
})
const incidentDelete = await deleteIncident({ incidentId: 'incident-smoke', dryRun: true })
const dashboard = await createDashboard({
title: 'smoke-preview',
layoutType: 'ordered',
widgets: [],
dryRun: true,
})
const dashboardUpdate = await updateDashboard({
dashboardId: 'abc-def',
title: 'smoke-preview',
layoutType: 'ordered',
widgets: [],
dryRun: true,
})
const dashboardDelete = await deleteDashboard({ dashboardId: 'abc-def', dryRun: true })
const checks = [
create,
update,
mute,
del,
incident,
incidentUpdate,
incidentDelete,
dashboard,
dashboardUpdate,
dashboardDelete,
]
for (const check of checks) {
if (!('dryRun' in check) || check.dryRun !== true) {
throw new Error('Datadog dryRun mutation self-check failed.')
}
}
return {
monitors: true,
incidents: true,
dashboards: true,
}
}
/**
* Local dry-run checks plus an optional live key validation and sample reads.
*
* Never mutates monitors, incidents, or dashboards.
*
* @example
* import smokeTest from 'kody:@kody/datadog/smoke-test'
* const result = await smokeTest()
*/
export async function smokeTest(input: DatadogSmokeTestInput = {}) {
const dryRunMutations = await mutationSelfCheck()
const apiKeySecret = resolveApiKeySecretName(input)
const applicationKeySecret = resolveApplicationKeySecretName(input)
const names = await listUserSecretNames()
const hasApiKey = names.has(apiKeySecret)
const hasApplicationKey = names.has(applicationKeySecret)
const host = resolveApiHost(input)
if (!hasApiKey || !hasApplicationKey) {
return {
ok: true,
live: false,
selfCheck: { dryRunMutations },
secrets: { apiKeySecret, applicationKeySecret, hasApiKey, hasApplicationKey },
setup: {
auth: 'api-key-and-application-key',
hosts: [...DATADOG_API_HOSTS],
site: input.site ?? 'us1',
host,
nextSteps: [
'Create an API key and application key in Datadog Organization Settings.',
hasApiKey ? null : secretSetupUrl(apiKeySecret),
hasApplicationKey ? null : secretSetupUrl(applicationKeySecret),
'Approve the Datadog API hosts for those secrets.',
].filter(Boolean),
},
}
}
const validation = await validateKeys(input)
const monitors =
input.includeMonitors === false
? null
: await listMonitors({ ...input, pageSize: 5 })
const dashboards =
input.includeDashboards === false
? null
: await listDashboards({ ...input, count: 5 })
return {
ok: true,
live: true,
selfCheck: { dryRunMutations },
secrets: { apiKeySecret, applicationKeySecret, hasApiKey, hasApplicationKey },
validation,
monitorCount: monitors?.count ?? null,
monitorsSample: monitors?.monitors.slice(0, 5) ?? [],
dashboardCount: dashboards?.count ?? null,
dashboardsSample: dashboards?.dashboards.slice(0, 5) ?? [],
setup: {
auth: 'api-key-and-application-key',
hosts: [...DATADOG_API_HOSTS],
host,
apiKeyUrl: API_KEY_SETUP_URL,
applicationKeyUrl: APPLICATION_KEY_SETUP_URL,
},
}
}
export default smokeTest