mirror of
https://github.com/simstudioai/sim.git
synced 2026-11-03 23:31:47 +08:00
feat(dynatrace): add the write and configuration surfaces (#6398)
* feat(dynatrace): add the write and configuration surfaces Takes the block from 22 operations to 47. The original PR shipped the read paths plus a few ingests; this closes the gaps that made those reads dead-end. The one that was a real defect: security was read-only. The audit-vulnerabilities skill promised "a remediation queue" and then gave you no way to act on it, even though muting is the single most common triage action. Adds mute and unmute, singly and in bulk, plus the remediation items behind a third-party finding, plus the Attacks API so an exploited vulnerability can be traced to the request that exploited it. The rest, by how much they unblock: - Custom tags (read/add/delete). Entity tags already drive every selector in the block; being able to write them closes a loop that was half open. - Settings objects (schemas, list, get, create, update, delete). This is how maintenance windows, alerting profiles, and management zones are configured in modern Dynatrace, so "open a maintenance window before the deploy" was simply unreachable before. The value is a schema-defined blob, so the tool is honestly opaque rather than falsely typed; the docs tell you to mirror an existing object. Update and delete carry the updateToken so a concurrent change fails instead of being overwritten. - Synthetic monitors and on-demand batch execution, which pairs with the deploy-marker tool to gate a release on a smoke test. - Problem comment get/update/delete, and SLO create/update/delete, completing CRUD that was previously half-built. Two structural notes. Synthetic monitors are the only endpoints still on Environment API v1, so `buildDynatraceUrl` grew a v1 sibling and the shared base-URL normalizer now strips either version; the query builder also learned to repeat a param per value, which Synthetic's `tag` needs. And creating an SLO returns 201 with an empty body and the new ID in the Location header, so that tool reads the header rather than parsing nothing. Deliberately excluded: the Grail/DQL query API. It is the long-term successor to the deprecated logs/search endpoint, but it authenticates with a platform token rather than an Api-Token, so it is a second auth path and belongs in its own change. * fix(dynatrace): drill the documented JSON shapes, and require the tag selector Two problems, one found in review and one worth more than it was given. The tag operations could run without an entity selector. All three tag tools declare `entitySelector` required, but the shared block field was only marked required for List Entities, so the block let a workflow reach those tools with an invalid configuration and let Dynatrace do the rejecting. My own structural auditor missed it because it only checked that *some* visible subBlock existed for a required param, not that the specific one was required — that check is now precise, and it confirms these three were the only instances across all 47 operations. The larger one: outputs were declaring `type: 'json'` for shapes the API reference documents in full. Thirty-five of them. The top-level entities were mapped properly, but nested payloads — a problem's evidence and impact analysis, a vulnerability's risk assessment and global counts, an attack's attacker, request, entry point and exploited vulnerability, a remediation item's assessment and mute state, the synthetic execution and failure records, the metric ingest error envelope, the DQL translation — were passed through as anonymous blobs. A downstream block could not reference `attacker.sourceIp` without knowing to guess it. All of those now carry their fields. What stays opaque is now only what genuinely is, and each says why in its description: a settings object's schema-defined value, an entity's type-dependent property bag and relationship keys, caller-supplied synthetic metadata, an audit log's JSON patch, the undocumented partial-success body of log ingestion, and the handful of security-detail shapes the reference names without expanding. * fix(dynatrace): make the synthetic enabled filter tri-state Review catch. `enabled` on List Synthetic Monitors is a three-way filter — enabled, disabled, or either — and I had it as a switch. The URL builder deliberately serializes `false` (there is a test pinning that `evaluate=false` survives), so leaving "Enabled Only" unchecked sent `enabled=false` and returned only the disabled monitors: exactly backwards. Made it a dropdown with Any / Enabled only / Disabled only, matching the monitorType field directly above it, which had the same shape and already used an empty-id "Any" option. The params mapper sends nothing for "Any". Checked the other nine switches rather than assuming. None share the bug: for each of them off genuinely means false, and false is Dynatrace's own default, so serializing it is correct. A test now pins that list so the trap cannot be re-introduced by converting one of them, alongside a test covering all three states of the filter.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Dynatrace
|
||||
description: Query Dynatrace problems, metrics, entities, logs, SLOs, and vulnerabilities
|
||||
description: Manage Dynatrace problems, metrics, entities, logs, SLOs, settings, and security
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -15,13 +15,16 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
**What you can reach from Sim**
|
||||
|
||||
- **Problems** — list and inspect Davis problems, read their root cause and affected entities, close them, and add comments.
|
||||
- **Problems** — list and inspect Davis problems, read their root cause and affected entities, close them, and manage their comments.
|
||||
- **Metrics** — query time series with a metric selector, discover which metrics exist, read a metric's descriptor, and push your own data points.
|
||||
- **Entities** — list and inspect monitored hosts, services, applications, and Kubernetes workloads, and enumerate the entity types available for building selectors.
|
||||
- **Tags** — read, add, and remove the custom tags that drive selectors, management zones, and alerting.
|
||||
- **Events** — read deployments, availability changes, and annotations, and ingest your own.
|
||||
- **Logs** — search log records and ingest new ones.
|
||||
- **SLOs** — read service-level objectives with their attainment, error budget, and burn rate.
|
||||
- **Application Security** — list and inspect vulnerabilities with risk assessment and remediation guidance.
|
||||
- **SLOs** — read, create, update, and delete service-level objectives, with their attainment, error budget, and burn rate.
|
||||
- **Application Security** — list and inspect vulnerabilities with risk assessment and remediation guidance, walk their remediation items, mute and unmute them singly or in bulk, and review the runtime attacks that exploited them.
|
||||
- **Settings** — browse schemas and read, create, update, and delete settings objects. This is how maintenance windows, alerting profiles, management zones, and anomaly-detection thresholds are configured.
|
||||
- **Synthetic** — list monitors, trigger an on-demand batch execution, and poll the batch for its result.
|
||||
- **Audit log** — read who changed which configuration, and when.
|
||||
|
||||
**Setup**
|
||||
@@ -35,7 +38,23 @@ The environment URL is the base address of your Dynatrace environment, without t
|
||||
| SaaS | `https://abc12345.live.dynatrace.com` |
|
||||
| Managed / environment ActiveGate | `https://your-activegate:9999/e/abc12345` |
|
||||
|
||||
Create the token under **Access tokens** in Dynatrace, and grant only the scopes for the operations you plan to call. Each action's `apiToken` description names the scope it needs — `problems.read`, `metrics.read`, `entities.read`, `events.read`, `logs.read`, `slo.read`, `securityProblems.read`, `auditLogs.read` for reads, and `problems.write`, `metrics.ingest`, `events.ingest`, `logs.ingest` for writes.
|
||||
Create the token under **Access tokens** in Dynatrace, and grant only the scopes for the operations you plan to call. Each action's `apiToken` description names the scope it needs:
|
||||
|
||||
| Area | Read | Write |
|
||||
| --- | --- | --- |
|
||||
| Problems | `problems.read` | `problems.write` |
|
||||
| Metrics | `metrics.read` | `metrics.ingest` |
|
||||
| Entities and tags | `entities.read` | `entities.write` |
|
||||
| Events | `events.read` | `events.ingest` |
|
||||
| Logs | `logs.read` | `logs.ingest` |
|
||||
| SLOs | `slo.read` | `slo.write` |
|
||||
| Vulnerabilities | `securityProblems.read` | `securityProblems.write` |
|
||||
| Attacks | `attacks.read` | — |
|
||||
| Settings | `settings.read` | `settings.write` |
|
||||
| Synthetic | `syntheticExecutions.read`, `ReadSyntheticData` | `syntheticExecutions.write` |
|
||||
| Audit log | `auditLogs.read` | — |
|
||||
|
||||
Note that **Synthetic monitors are the one part of this integration on Environment API v1** — Sim handles the path difference, but the token scopes differ from the v2 endpoints.
|
||||
|
||||
**Selectors**
|
||||
|
||||
@@ -50,6 +69,10 @@ securityProblemSelector: status("OPEN"),riskLevel("CRITICAL")
|
||||
|
||||
Every selector field in the block has a wand — describe what you want in plain language and Sim writes the selector for you.
|
||||
|
||||
**Settings objects**
|
||||
|
||||
Most Dynatrace configuration is a *settings object*: a JSON `value` whose shape is defined by a *schema* such as `builtin:alerting.maintenance-window`. There is no fixed structure Sim can validate for you, so the reliable way to write one is to read an existing object of the same schema first and mirror its `value`. Get Settings Object also returns an `updateToken` — pass it back on update or delete and the call fails rather than overwriting a change someone else made in the meantime.
|
||||
|
||||
**Pagination**
|
||||
|
||||
List operations return a `nextPageKey` (a `nextSliceKey` for log search). Feed it back into the next call to read the following page. Dynatrace encodes the original filters into the cursor, so Sim sends the cursor alone and ignores the other filters on that call — which is what the API requires.
|
||||
@@ -58,7 +81,7 @@ List operations return a `nextPageKey` (a `nextSliceKey` for log search). Feed i
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Dynatrace into workflows. Investigate and close Davis problems, query metrics and monitored entities, search and ingest logs, push deployment events, track SLO burn rates, review Application Security vulnerabilities, and read the audit log.
|
||||
Integrate Dynatrace into workflows. Investigate and close Davis problems, query metrics and monitored entities, search and ingest logs, push deployment events, manage SLOs and their burn rates, triage and mute Application Security vulnerabilities, review runtime attacks, tag entities, manage settings objects such as maintenance windows and alerting profiles, run synthetic monitors on demand, and read the audit log.
|
||||
|
||||
|
||||
|
||||
@@ -199,6 +222,8 @@ Read metric data points from Dynatrace using a metric selector, with optional en
|
||||
| ↳ `dimensionCountRatio` | number | Queried dimension tuples relative to the query limit |
|
||||
| ↳ `appliedOptionalFilters` | array | Optional filters Dynatrace applied to the query |
|
||||
| ↳ `dql` | json | DQL translation of the query, when available |
|
||||
| ↳ `status` | string | Whether the translation succeeded |
|
||||
| ↳ `query` | string | The equivalent DQL query |
|
||||
| ↳ `warnings` | array | Warnings for this metric |
|
||||
| ↳ `data` | array | Series of the metric, one per dimension tuple |
|
||||
| ↳ `dimensions` | array | Dimension values of the series |
|
||||
@@ -268,8 +293,17 @@ Push custom metric data points into Dynatrace using the metric line protocol, on
|
||||
| --------- | ---- | ----------- |
|
||||
| `linesOk` | number | Number of accepted data points |
|
||||
| `linesInvalid` | number | Number of rejected data points |
|
||||
| `ingestError` | json | Details of the invalid lines, with the line number and reason for each |
|
||||
| `ingestError` | json | Details of the invalid lines |
|
||||
| ↳ `code` | number | Error code |
|
||||
| ↳ `message` | string | Error message |
|
||||
| ↳ `invalidLines` | array | The rejected lines |
|
||||
| ↳ `line` | number | Line number in the payload |
|
||||
| ↳ `error` | string | Why the line was rejected |
|
||||
| `warnings` | json | Warnings raised during ingestion, such as changed metric keys |
|
||||
| ↳ `message` | string | Warning message |
|
||||
| ↳ `changedMetricKeys` | array | Lines whose metric key Dynatrace rewrote |
|
||||
| ↳ `line` | number | Line number in the payload |
|
||||
| ↳ `warning` | string | What was changed |
|
||||
|
||||
### Dynatrace List Entities
|
||||
|
||||
@@ -462,7 +496,7 @@ Push log events into Dynatrace. Accepts a single log event object or an array of
|
||||
| --------- | ---- | ----------- |
|
||||
| `accepted` | boolean | True when Dynatrace accepted every log event \(HTTP 204\) |
|
||||
| `statusCode` | number | HTTP status Dynatrace returned. 204 is full success, 200 is partial success |
|
||||
| `details` | json | Partial-success details, present only when some events were rejected |
|
||||
| `details` | json | Partial-success body, present only when some events were rejected. The reference does not document its shape, so it is passed through as-is |
|
||||
|
||||
### Dynatrace List SLOs
|
||||
|
||||
@@ -590,7 +624,7 @@ Read the Dynatrace audit log — who changed which configuration, when, and whet
|
||||
| ↳ `timestamp` | number | Change timestamp in UTC milliseconds |
|
||||
| ↳ `success` | boolean | Whether the change succeeded |
|
||||
| ↳ `message` | string | Description of the change |
|
||||
| ↳ `patch` | json | JSON patch of the change |
|
||||
| ↳ `patch` | json | JSON patch describing the change. Its shape follows whatever settings object was edited, so it is dynamic |
|
||||
| ↳ `settingsSchemaId` | string | Settings schema ID \(dt.settings.schema_id\) |
|
||||
| ↳ `settingsScopeId` | string | Settings scope ID \(dt.settings.scope_id\) |
|
||||
| ↳ `settingsKey` | string | Settings key \(dt.settings.key\) |
|
||||
@@ -598,4 +632,606 @@ Read the Dynatrace audit log — who changed which configuration, when, and whet
|
||||
| ↳ `settingsObjectSummary` | string | Settings object summary \(dt.settings.object_summary\) |
|
||||
| ↳ `settingsScopeName` | string | Settings scope name \(dt.settings.scope_name\) |
|
||||
|
||||
### Dynatrace Mute Security Problem
|
||||
|
||||
Mute a single Dynatrace vulnerability with a reason, for triaging false positives or accepted risk.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the securityProblems.write scope |
|
||||
| `securityProblemId` | string | Yes | ID of the security problem to mute |
|
||||
| `reason` | string | Yes | One of CONFIGURATION_NOT_AFFECTED, FALSE_POSITIVE, IGNORE, OTHER, VULNERABLE_CODE_NOT_IN_USE |
|
||||
| `comment` | string | No | Explanation recorded alongside the mute |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `securityProblemId` | string | ID of the muted security problem |
|
||||
| `reason` | string | Reason recorded for the mute |
|
||||
| `comment` | string | Comment recorded for the mute |
|
||||
| `alreadyInState` | boolean | True when Dynatrace reported the problem was already muted \(HTTP 204\) |
|
||||
|
||||
### Dynatrace Unmute Security Problem
|
||||
|
||||
Unmute a single Dynatrace vulnerability, returning it to the active triage queue.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the securityProblems.write scope |
|
||||
| `securityProblemId` | string | Yes | ID of the security problem to unmute |
|
||||
| `reason` | string | Yes | Reason for unmuting. AFFECTED is the only value the API accepts |
|
||||
| `comment` | string | No | Explanation recorded alongside the unmute |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `securityProblemId` | string | ID of the unmuted security problem |
|
||||
| `reason` | string | Reason recorded for the unmute |
|
||||
| `comment` | string | Comment recorded for the unmute |
|
||||
| `alreadyInState` | boolean | True when Dynatrace reported the problem was already unmuted \(HTTP 204\) |
|
||||
|
||||
### Dynatrace Mute Security Problems
|
||||
|
||||
Mute several Dynatrace vulnerabilities at once with a shared reason, for bulk triage.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the securityProblems.write scope |
|
||||
| `securityProblemIds` | string | Yes | Security problem IDs to mute, comma-separated or as a JSON array |
|
||||
| `reason` | string | Yes | One of CONFIGURATION_NOT_AFFECTED, FALSE_POSITIVE, IGNORE, OTHER, VULNERABLE_CODE_NOT_IN_USE |
|
||||
| `comment` | string | No | Explanation recorded against every muted problem |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `changedCount` | number | How many problems actually changed state, excluding those already muted |
|
||||
|
||||
### Dynatrace Unmute Security Problems
|
||||
|
||||
Unmute several Dynatrace vulnerabilities at once, returning them to active triage.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the securityProblems.write scope |
|
||||
| `securityProblemIds` | string | Yes | Security problem IDs to unmute, comma-separated or as a JSON array |
|
||||
| `reason` | string | Yes | Reason for unmuting. AFFECTED is the only value the API accepts |
|
||||
| `comment` | string | No | Explanation recorded against every unmuted problem |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `changedCount` | number | How many problems actually changed state, excluding those already unmuted |
|
||||
|
||||
### Dynatrace List Remediation Items
|
||||
|
||||
List the remediation items of a third-party vulnerability — the components to upgrade, their affected entities, and their mute state.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the securityProblems.read scope |
|
||||
| `securityProblemId` | string | Yes | ID of the third-party security problem |
|
||||
| `remediationItemSelector` | string | No | Remediation item selector, e.g. vulnerabilityState\("VULNERABLE"\),muted\("false"\),exposure\("PUBLIC_NETWORK"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `remediationItems` | array | Remediation items of the vulnerability. This endpoint returns no total count |
|
||||
|
||||
### Dynatrace List Attacks
|
||||
|
||||
List runtime attacks Dynatrace Application Protection detected — injection attempts, their source, and whether they were blocked.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the attacks.read scope |
|
||||
| `attackSelector` | string | No | Attack selector, e.g. state\("EXPLOITED"\),attackType\("SQL_INJECTION"\),technology\("JAVA"\) |
|
||||
| `from` | string | No | Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression such as now-7d. Defaults to now-30d |
|
||||
| `to` | string | No | End of the timeframe in the same formats as From. Defaults to now |
|
||||
| `fields` | string | No | Comma-separated optional properties to include: +attackTarget, +request, +entrypoint, +vulnerability, +securityProblem, +attacker, +managementZones, +affectedEntities |
|
||||
| `sort` | string | No | Sort by displayId, displayName, attackType, state, sourceIp, requestPath, or timestamp with a + or - prefix |
|
||||
| `pageSize` | number | No | Attacks per page \(max 500, default 100\) |
|
||||
| `nextPageKey` | string | No | Cursor for the next page. All other filters are ignored when it is set |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `attacks` | array | Matching attacks |
|
||||
|
||||
### Dynatrace Get Attack
|
||||
|
||||
Get a single attack with its entry point, payload, attacker, and the vulnerability it exploited.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the attacks.read scope |
|
||||
| `attackId` | string | Yes | ID of the attack |
|
||||
| `fields` | string | No | Comma-separated optional properties to include: +attackTarget, +request, +entrypoint, +vulnerability, +securityProblem, +attacker, +managementZones |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `attack` | object | The requested attack |
|
||||
|
||||
### Dynatrace List Tags
|
||||
|
||||
List the custom tags applied to the monitored entities an entity selector matches.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the entities.read scope |
|
||||
| `entitySelector` | string | Yes | Entity selector for the entities to read tags from, e.g. type\("HOST"\) |
|
||||
| `from` | string | No | Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h |
|
||||
| `to` | string | No | End of the timeframe in the same formats as From. Defaults to now |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tags` | array | Custom tags on the matched entities |
|
||||
|
||||
### Dynatrace Add Tags
|
||||
|
||||
Add custom tags to every monitored entity an entity selector matches. Tags drive selectors, management zones, and alerting.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the entities.write scope |
|
||||
| `entitySelector` | string | Yes | Entity selector for the entities to tag, e.g. type\("HOST"\),tag\("env:staging"\) |
|
||||
| `tags` | json | Yes | Tags to add, as an array of objects with a key and an optional value \(e.g. \[\{"key":"owner","value":"platform"\},\{"key":"reviewed"\}\]\) |
|
||||
| `from` | string | No | Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h |
|
||||
| `to` | string | No | End of the timeframe in the same formats as From. Defaults to now |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `appliedTags` | array | Tags that were applied |
|
||||
| `matchedEntitiesCount` | number | How many entities the selector matched and were tagged |
|
||||
|
||||
### Dynatrace Delete Tag
|
||||
|
||||
Remove a custom tag from every monitored entity an entity selector matches. Deletes one key-value pair, or every tag with the key.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the entities.write scope |
|
||||
| `entitySelector` | string | Yes | Entity selector for the entities to untag, e.g. type\("HOST"\),tag\("owner:old"\) |
|
||||
| `key` | string | Yes | Key of the tag to delete |
|
||||
| `value` | string | No | Value of the tag to delete. Omit it and set Delete All With Key to remove every value of the key |
|
||||
| `deleteAllWithKey` | boolean | No | Delete every tag carrying the key, regardless of value |
|
||||
| `from` | string | No | Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h |
|
||||
| `to` | string | No | End of the timeframe in the same formats as From. Defaults to now |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `matchedEntitiesCount` | number | How many entities the selector matched and had the tag removed from |
|
||||
|
||||
### Dynatrace List Settings Schemas
|
||||
|
||||
List the settings schemas available in the environment. Use it to find the schema ID for a configuration type such as builtin:alerting.profile.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the settings.read scope |
|
||||
| `fields` | string | No | Comma-separated fields to include: schemaId, displayName, maturity, latestSchemaVersion, multiObject, ordered, ownerBasedAccessControl |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `schemas` | array | Available settings schemas |
|
||||
|
||||
### Dynatrace List Settings Objects
|
||||
|
||||
List settings objects — the configuration behind maintenance windows, alerting profiles, management zones, and anomaly detection.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the settings.read scope |
|
||||
| `schemaIds` | string | No | Comma-separated schema IDs, e.g. builtin:alerting.profile |
|
||||
| `scopes` | string | No | Comma-separated scopes, e.g. environment or HOST-06F288EE2A930951 |
|
||||
| `externalIds` | string | No | Comma-separated external IDs |
|
||||
| `fields` | string | No | Comma-separated fields to include: objectId, value, schemaId, schemaVersion, scope, author, modified, updateToken, created, externalId, summary, searchSummary |
|
||||
| `filter` | string | No | Filter expression over created, modified, createdBy, modifiedBy, or value |
|
||||
| `sort` | string | No | Sort expression, e.g. -modified |
|
||||
| `pageSize` | number | No | Objects per page \(max 500, default 100\) |
|
||||
| `nextPageKey` | string | No | Cursor for the next page. All other filters are ignored when it is set |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | array | Matching settings objects |
|
||||
|
||||
### Dynatrace Get Settings Object
|
||||
|
||||
Get a single settings object with its value and its update token. Read it before updating so the token can guard against a concurrent change.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the settings.read scope |
|
||||
| `objectId` | string | Yes | ID of the settings object |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `object` | object | The requested settings object |
|
||||
|
||||
### Dynatrace Create Settings Object
|
||||
|
||||
Create a settings object — a maintenance window, alerting profile, management zone, or any other schema-backed configuration.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the settings.write scope |
|
||||
| `schemaId` | string | Yes | Schema of the object to create, e.g. builtin:alerting.maintenance-window. List Settings Schemas returns the available IDs |
|
||||
| `scope` | string | Yes | Scope the object applies to, e.g. environment or an entity ID such as HOST-06F288EE2A930951 |
|
||||
| `value` | json | Yes | The configuration itself. Its shape is defined by the schema — read an existing object of the same schema to see the expected fields |
|
||||
| `schemaVersion` | string | No | Schema version to validate against. Defaults to the latest |
|
||||
| `externalId` | string | No | External ID to correlate the object with a system outside Dynatrace |
|
||||
| `validateOnly` | boolean | No | Validate the payload without creating anything |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `results` | array | One result per submitted object |
|
||||
| ↳ `code` | number | Per-object HTTP status |
|
||||
| ↳ `objectId` | string | ID of the created object |
|
||||
| ↳ `writeError` | json | Validation error for this object, when it failed |
|
||||
| ↳ `code` | number | Error code |
|
||||
| ↳ `message` | string | Error message |
|
||||
| ↳ `constraintViolations` | array | Which part of the value failed validation |
|
||||
| ↳ `location` | string | Where the violation was found |
|
||||
| ↳ `message` | string | What is wrong |
|
||||
| ↳ `parameterLocation` | string | HEADER, PATH, PAYLOAD_BODY, or QUERY |
|
||||
| ↳ `path` | string | Path to the offending field |
|
||||
| ↳ `invalidValue` | json | The value that was rejected. Mirrors the submitted schema-defined value, so the shape is dynamic |
|
||||
| `objectId` | string | ID of the created object, lifted from the first result |
|
||||
|
||||
### Dynatrace Update Settings Object
|
||||
|
||||
Update an existing settings object. Pass the update token from Get Settings Object to fail rather than overwrite a concurrent change.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the settings.write scope |
|
||||
| `objectId` | string | Yes | ID of the settings object to update |
|
||||
| `value` | json | Yes | The full replacement configuration. Its shape is defined by the object schema — this replaces the value rather than merging into it |
|
||||
| `schemaVersion` | string | No | Schema version to validate against |
|
||||
| `updateToken` | string | No | Token from Get Settings Object. When set, the update fails if the object changed in the meantime. Omit it to overwrite unconditionally |
|
||||
| `validateOnly` | boolean | No | Validate the payload without saving anything |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `objectId` | string | ID of the updated object |
|
||||
| `code` | number | Status Dynatrace reported for the update |
|
||||
|
||||
### Dynatrace Delete Settings Object
|
||||
|
||||
Delete a settings object. Dynatrace cannot undo this.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the settings.write scope |
|
||||
| `objectId` | string | Yes | ID of the settings object to delete |
|
||||
| `updateToken` | string | No | Token from Get Settings Object. When set, the delete fails if the object changed in the meantime |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `objectId` | string | ID of the deleted settings object |
|
||||
| `deleted` | boolean | Always true — a failed delete raises instead |
|
||||
|
||||
### Dynatrace List Synthetic Monitors
|
||||
|
||||
List synthetic monitors and their IDs. Use it to find the monitor IDs to feed into an on-demand execution.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with one of ReadSyntheticData, DataExport, or ExternalSyntheticIntegration |
|
||||
| `type` | string | No | Filter by monitor type: BROWSER or HTTP |
|
||||
| `enabled` | boolean | No | Filter to enabled \(true\) or disabled \(false\) monitors |
|
||||
| `location` | string | No | Filter to monitors assigned to a synthetic location |
|
||||
| `tag` | string | No | Filter by tag. Comma-separate to require several tags |
|
||||
| `managementZone` | number | No | Filter to monitors in a management zone, by numeric zone ID |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `monitors` | array | Matching synthetic monitors |
|
||||
|
||||
### Dynatrace Execute Synthetic Monitors
|
||||
|
||||
Trigger an on-demand batch execution of synthetic monitors, for gating a deploy on a smoke test. Returns a batch ID to poll.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with syntheticExecutions.write or ExternalSyntheticIntegration |
|
||||
| `monitors` | json | Yes | Monitors to run, as an array of objects with monitorId and optional locations and executionCount \(e.g. \[\{"monitorId":"SYNTHETIC_TEST-123","executionCount":1\}\]\). Execution count caps at 10 |
|
||||
| `processingMode` | string | No | STANDARD, DISABLE_PROBLEM_DETECTION, or EXECUTIONS_DETAILS_ONLY |
|
||||
| `failOnPerformanceIssue` | boolean | No | Treat a performance threshold breach as a failure |
|
||||
| `stopOnProblem` | boolean | No | Stop the batch as soon as one monitor reports a problem |
|
||||
| `takeScreenshotsOnSuccess` | boolean | No | Capture screenshots for successful browser executions too |
|
||||
| `metadata` | json | No | Key-value metadata to attach to the batch, e.g. the release version. Max 64 pairs, 1024 characters each |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `batchId` | string | ID of the batch, to poll with Get Synthetic Batch |
|
||||
| `triggeredCount` | number | How many executions were triggered |
|
||||
| `triggeringProblemsCount` | number | How many executions could not be triggered |
|
||||
| `triggered` | array | Triggered executions, grouped by monitor |
|
||||
| ↳ `monitorId` | string | Monitor that was triggered |
|
||||
| ↳ `executions` | array | One entry per location the monitor ran from |
|
||||
| ↳ `executionId` | string | Execution ID |
|
||||
| ↳ `locationId` | string | Location the execution ran from |
|
||||
| `triggeringProblemsDetails` | array | Why each untriggered execution failed to start |
|
||||
| ↳ `cause` | string | Why the execution could not be triggered |
|
||||
| ↳ `details` | string | Detail behind the cause |
|
||||
| ↳ `entityId` | string | Entity the problem relates to |
|
||||
| ↳ `executionId` | string | Execution ID, when one was assigned |
|
||||
| ↳ `locationId` | string | Location the execution targeted |
|
||||
|
||||
### Dynatrace Get Synthetic Batch
|
||||
|
||||
Get the status and failures of an on-demand synthetic batch execution. Poll it after triggering to gate a deploy on the result.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with syntheticExecutions.read, ReadSyntheticData, or ExternalSyntheticIntegration |
|
||||
| `batchId` | string | Yes | Batch ID returned by Execute Synthetic Monitors |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `batchId` | string | ID of the batch |
|
||||
| `batchStatus` | string | RUNNING, SUCCESS, FAILED, FAILED_TO_EXECUTE, or NOT_TRIGGERED |
|
||||
| `executedCount` | number | Executions completed |
|
||||
| `failedCount` | number | Executions that failed |
|
||||
| `failedToExecuteCount` | number | Executions that never ran |
|
||||
| `triggeredCount` | number | Executions triggered |
|
||||
| `triggeringProblemsCount` | number | Executions that could not be triggered |
|
||||
| `failedExecutions` | array | Executions that ran and failed |
|
||||
| ↳ `errorCode` | string | Error code Dynatrace reported |
|
||||
| ↳ `executionId` | string | Execution ID |
|
||||
| ↳ `executionStage` | string | DATA_RETRIEVED, EXECUTED, NOT_TRIGGERED, TIMED_OUT, TRIGGERED, or WAITING |
|
||||
| ↳ `executionTimestamp` | number | Execution time in UTC ms |
|
||||
| ↳ `failureMessage` | string | Why the execution failed |
|
||||
| ↳ `locationId` | string | Location the execution ran from |
|
||||
| ↳ `monitorId` | string | Monitor that was executed |
|
||||
| `failedToExecute` | array | Executions that never started |
|
||||
| ↳ `errorCode` | string | Error code Dynatrace reported |
|
||||
| ↳ `executionId` | string | Execution ID |
|
||||
| ↳ `executionStage` | string | DATA_RETRIEVED, EXECUTED, NOT_TRIGGERED, TIMED_OUT, TRIGGERED, or WAITING |
|
||||
| ↳ `executionTimestamp` | number | Execution time in UTC ms |
|
||||
| ↳ `failureMessage` | string | Why the execution failed |
|
||||
| ↳ `locationId` | string | Location the execution ran from |
|
||||
| ↳ `monitorId` | string | Monitor that was executed |
|
||||
| `triggeringProblems` | array | Reasons executions could not be triggered |
|
||||
| ↳ `cause` | string | Why the execution could not be triggered |
|
||||
| ↳ `details` | string | Detail behind the cause |
|
||||
| ↳ `entityId` | string | Entity the problem relates to |
|
||||
| ↳ `executionId` | string | Execution ID, when one was assigned |
|
||||
| ↳ `locationId` | string | Location the execution targeted |
|
||||
| `metadata` | json | Key-value metadata supplied when the batch was triggered. Keys are caller-defined, so the shape is dynamic |
|
||||
| `userId` | string | Who triggered the batch |
|
||||
|
||||
### Dynatrace Get Problem Comment
|
||||
|
||||
Get a single comment on a Dynatrace problem.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the problems.read scope |
|
||||
| `problemId` | string | Yes | ID of the problem the comment belongs to |
|
||||
| `commentId` | string | Yes | ID of the comment |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `comment` | object | The requested comment |
|
||||
|
||||
### Dynatrace Update Problem Comment
|
||||
|
||||
Replace the text of an existing comment on a Dynatrace problem.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the problems.write scope |
|
||||
| `problemId` | string | Yes | ID of the problem the comment belongs to |
|
||||
| `commentId` | string | Yes | ID of the comment to update |
|
||||
| `message` | string | Yes | Replacement text of the comment |
|
||||
| `context` | string | No | Context of the comment, shown alongside the author |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `problemId` | string | ID of the problem |
|
||||
| `commentId` | string | ID of the updated comment |
|
||||
| `message` | string | Text the comment now carries |
|
||||
| `context` | string | Context of the comment |
|
||||
|
||||
### Dynatrace Delete Problem Comment
|
||||
|
||||
Delete a comment from a Dynatrace problem.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the problems.write scope |
|
||||
| `problemId` | string | Yes | ID of the problem the comment belongs to |
|
||||
| `commentId` | string | Yes | ID of the comment to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `problemId` | string | ID of the problem |
|
||||
| `commentId` | string | ID of the deleted comment |
|
||||
| `deleted` | boolean | Always true — a failed delete raises instead |
|
||||
|
||||
### Dynatrace Create SLO
|
||||
|
||||
Create a service-level objective from a metric expression, target, and evaluation timeframe.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the slo.write scope |
|
||||
| `name` | string | Yes | Name of the SLO |
|
||||
| `target` | number | Yes | Target success rate as a percentage, e.g. 99.5 |
|
||||
| `warning` | number | Yes | Warning threshold as a percentage. Must sit above the target, e.g. 99.8 |
|
||||
| `timeframe` | string | Yes | Evaluation timeframe in Dynatrace notation, e.g. -1d, -1w, or now-30d |
|
||||
| `evaluationType` | string | Yes | Evaluation type. AGGREGATE is the only value the API accepts |
|
||||
| `description` | string | No | Description of the SLO |
|
||||
| `enabled` | boolean | No | Whether the SLO is evaluated. Dynatrace defaults it to false |
|
||||
| `filter` | string | No | Entity filter scoping the SLO, e.g. type\("SERVICE"\),tag\("env:prod"\) |
|
||||
| `metricExpression` | string | No | Metric expression the SLO evaluates, e.g. \(100\)*\(builtin:service.errors.total.successCount:splitBy\(\)\)/\(builtin:service.requestCount.total:splitBy\(\)\) |
|
||||
| `metricName` | string | No | Display name for the SLO metric |
|
||||
| `burnRateVisualizationEnabled` | boolean | No | Show the error-budget burn rate on the SLO |
|
||||
| `fastBurnThreshold` | number | No | Burn rate above which the SLO is considered fast-burning |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `sloId` | string | ID of the created SLO, read from the Location header |
|
||||
| `name` | string | Name the SLO was created with |
|
||||
|
||||
### Dynatrace Update SLO
|
||||
|
||||
Update an existing service-level objective. Every field is replaced, so send the complete definition.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the slo.write scope |
|
||||
| `sloId` | string | Yes | ID of the SLO to update |
|
||||
| `name` | string | Yes | Name of the SLO |
|
||||
| `target` | number | Yes | Target success rate as a percentage, e.g. 99.5 |
|
||||
| `warning` | number | Yes | Warning threshold as a percentage. Must sit above the target, e.g. 99.8 |
|
||||
| `timeframe` | string | Yes | Evaluation timeframe in Dynatrace notation, e.g. -1d, -1w, or now-30d |
|
||||
| `evaluationType` | string | Yes | Evaluation type. AGGREGATE is the only value the API accepts |
|
||||
| `description` | string | No | Description of the SLO |
|
||||
| `enabled` | boolean | No | Whether the SLO is evaluated. Dynatrace defaults it to false |
|
||||
| `filter` | string | No | Entity filter scoping the SLO, e.g. type\("SERVICE"\),tag\("env:prod"\) |
|
||||
| `metricExpression` | string | No | Metric expression the SLO evaluates, e.g. \(100\)*\(builtin:service.errors.total.successCount:splitBy\(\)\)/\(builtin:service.requestCount.total:splitBy\(\)\) |
|
||||
| `metricName` | string | No | Display name for the SLO metric |
|
||||
| `burnRateVisualizationEnabled` | boolean | No | Show the error-budget burn rate on the SLO |
|
||||
| `fastBurnThreshold` | number | No | Burn rate above which the SLO is considered fast-burning |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `sloId` | string | ID of the updated SLO |
|
||||
| `name` | string | Name the SLO now carries |
|
||||
|
||||
### Dynatrace Delete SLO
|
||||
|
||||
Delete a service-level objective.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `environmentUrl` | string | Yes | Dynatrace environment URL \(e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed\) |
|
||||
| `apiToken` | string | Yes | Dynatrace access token \(dt0c01...\) with the slo.write scope |
|
||||
| `sloId` | string | Yes | ID of the SLO to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `sloId` | string | ID of the deleted SLO |
|
||||
| `deleted` | boolean | Always true — a failed delete raises instead |
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-08-07",
|
||||
"updatedAt": "2026-08-08",
|
||||
"integrations": [
|
||||
{
|
||||
"type": "onepassword",
|
||||
@@ -5431,8 +5431,8 @@
|
||||
"type": "dynatrace",
|
||||
"slug": "dynatrace",
|
||||
"name": "Dynatrace",
|
||||
"description": "Query Dynatrace problems, metrics, entities, logs, SLOs, and vulnerabilities",
|
||||
"longDescription": "Integrate Dynatrace into workflows. Investigate and close Davis problems, query metrics and monitored entities, search and ingest logs, push deployment events, track SLO burn rates, review Application Security vulnerabilities, and read the audit log.",
|
||||
"description": "Manage Dynatrace problems, metrics, entities, logs, SLOs, settings, and security",
|
||||
"longDescription": "Integrate Dynatrace into workflows. Investigate and close Davis problems, query metrics and monitored entities, search and ingest logs, push deployment events, manage SLOs and their burn rates, triage and mute Application Security vulnerabilities, review runtime attacks, tag entities, manage settings objects such as maintenance windows and alerting profiles, run synthetic monitors on demand, and read the audit log.",
|
||||
"bgColor": "#FFFFFF",
|
||||
"iconName": "DynatraceIcon",
|
||||
"docsUrl": "https://docs.sim.ai/integrations/dynatrace",
|
||||
@@ -5521,12 +5521,112 @@
|
||||
"name": "Get Security Problem",
|
||||
"description": "Get a single vulnerability with its description, remediation guidance, affected entities, and risk assessment."
|
||||
},
|
||||
{
|
||||
"name": "Mute Security Problem",
|
||||
"description": "Mute a single Dynatrace vulnerability with a reason, for triaging false positives or accepted risk."
|
||||
},
|
||||
{
|
||||
"name": "Unmute Security Problem",
|
||||
"description": "Unmute a single Dynatrace vulnerability, returning it to the active triage queue."
|
||||
},
|
||||
{
|
||||
"name": "Mute Security Problems (Bulk)",
|
||||
"description": "Mute several Dynatrace vulnerabilities at once with a shared reason, for bulk triage."
|
||||
},
|
||||
{
|
||||
"name": "Unmute Security Problems (Bulk)",
|
||||
"description": "Unmute several Dynatrace vulnerabilities at once, returning them to active triage."
|
||||
},
|
||||
{
|
||||
"name": "List Remediation Items",
|
||||
"description": "List the remediation items of a third-party vulnerability — the components to upgrade, their affected entities, and their mute state."
|
||||
},
|
||||
{
|
||||
"name": "List Attacks",
|
||||
"description": "List runtime attacks Dynatrace Application Protection detected — injection attempts, their source, and whether they were blocked."
|
||||
},
|
||||
{
|
||||
"name": "Get Attack",
|
||||
"description": "Get a single attack with its entry point, payload, attacker, and the vulnerability it exploited."
|
||||
},
|
||||
{
|
||||
"name": "List Tags",
|
||||
"description": "List the custom tags applied to the monitored entities an entity selector matches."
|
||||
},
|
||||
{
|
||||
"name": "Add Tags",
|
||||
"description": "Add custom tags to every monitored entity an entity selector matches. Tags drive selectors, management zones, and alerting."
|
||||
},
|
||||
{
|
||||
"name": "Delete Tag",
|
||||
"description": "Remove a custom tag from every monitored entity an entity selector matches. Deletes one key-value pair, or every tag with the key."
|
||||
},
|
||||
{
|
||||
"name": "List Settings Schemas",
|
||||
"description": "List the settings schemas available in the environment. Use it to find the schema ID for a configuration type such as builtin:alerting.profile."
|
||||
},
|
||||
{
|
||||
"name": "List Settings Objects",
|
||||
"description": "List settings objects — the configuration behind maintenance windows, alerting profiles, management zones, and anomaly detection."
|
||||
},
|
||||
{
|
||||
"name": "Get Settings Object",
|
||||
"description": "Get a single settings object with its value and its update token. Read it before updating so the token can guard against a concurrent change."
|
||||
},
|
||||
{
|
||||
"name": "Create Settings Object",
|
||||
"description": "Create a settings object — a maintenance window, alerting profile, management zone, or any other schema-backed configuration."
|
||||
},
|
||||
{
|
||||
"name": "Update Settings Object",
|
||||
"description": "Update an existing settings object. Pass the update token from Get Settings Object to fail rather than overwrite a concurrent change."
|
||||
},
|
||||
{
|
||||
"name": "Delete Settings Object",
|
||||
"description": "Delete a settings object. Dynatrace cannot undo this."
|
||||
},
|
||||
{
|
||||
"name": "List Synthetic Monitors",
|
||||
"description": "List synthetic monitors and their IDs. Use it to find the monitor IDs to feed into an on-demand execution."
|
||||
},
|
||||
{
|
||||
"name": "Execute Synthetic Monitors",
|
||||
"description": "Trigger an on-demand batch execution of synthetic monitors, for gating a deploy on a smoke test. Returns a batch ID to poll."
|
||||
},
|
||||
{
|
||||
"name": "Get Synthetic Batch",
|
||||
"description": "Get the status and failures of an on-demand synthetic batch execution. Poll it after triggering to gate a deploy on the result."
|
||||
},
|
||||
{
|
||||
"name": "Get Problem Comment",
|
||||
"description": "Get a single comment on a Dynatrace problem."
|
||||
},
|
||||
{
|
||||
"name": "Update Problem Comment",
|
||||
"description": "Replace the text of an existing comment on a Dynatrace problem."
|
||||
},
|
||||
{
|
||||
"name": "Delete Problem Comment",
|
||||
"description": "Delete a comment from a Dynatrace problem."
|
||||
},
|
||||
{
|
||||
"name": "Create SLO",
|
||||
"description": "Create a service-level objective from a metric expression, target, and evaluation timeframe."
|
||||
},
|
||||
{
|
||||
"name": "Update SLO",
|
||||
"description": "Update an existing service-level objective. Every field is replaced, so send the complete definition."
|
||||
},
|
||||
{
|
||||
"name": "Delete SLO",
|
||||
"description": "Delete a service-level objective."
|
||||
},
|
||||
{
|
||||
"name": "Get Audit Logs",
|
||||
"description": "Read the Dynatrace audit log — who changed which configuration, when, and whether it succeeded."
|
||||
}
|
||||
],
|
||||
"operationCount": 22,
|
||||
"operationCount": 47,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { tagProperties } from '@/tools/dynatrace/outputs'
|
||||
import type { DynatraceAddTagsParams, DynatraceAddTagsResponse } from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapTags,
|
||||
parseJsonParam,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const addTagsTool: ToolConfig<DynatraceAddTagsParams, DynatraceAddTagsResponse> = {
|
||||
id: 'dynatrace_add_tags',
|
||||
name: 'Dynatrace Add Tags',
|
||||
description:
|
||||
'Add custom tags to every monitored entity an entity selector matches. Tags drive selectors, management zones, and alerting.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the entities.write scope',
|
||||
},
|
||||
entitySelector: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity selector for the entities to tag, e.g. type("HOST"),tag("env:staging")',
|
||||
},
|
||||
tags: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Tags to add, as an array of objects with a key and an optional value (e.g. [{"key":"owner","value":"platform"},{"key":"reviewed"}])',
|
||||
},
|
||||
from: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the timeframe in the same formats as From. Defaults to now',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, '/tags', {
|
||||
entitySelector: params.entitySelector,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const tags = parseJsonParam(params.tags)
|
||||
if (!Array.isArray(tags) || tags.length === 0) {
|
||||
throw new Error('tags must be a non-empty array of objects with a key')
|
||||
}
|
||||
return { tags }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
appliedTags: mapTags(data.appliedTags),
|
||||
matchedEntitiesCount: (data.matchedEntitiesCount as number) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
appliedTags: {
|
||||
type: 'array',
|
||||
description: 'Tags that were applied',
|
||||
items: { type: 'object', properties: tagProperties },
|
||||
},
|
||||
matchedEntitiesCount: {
|
||||
type: 'number',
|
||||
description: 'How many entities the selector matched and were tagged',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import type {
|
||||
DynatraceCreateSettingsObjectParams,
|
||||
DynatraceCreateSettingsObjectResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapSettingsWriteResult,
|
||||
parseJsonParam,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createSettingsObjectTool: ToolConfig<
|
||||
DynatraceCreateSettingsObjectParams,
|
||||
DynatraceCreateSettingsObjectResponse
|
||||
> = {
|
||||
id: 'dynatrace_create_settings_object',
|
||||
name: 'Dynatrace Create Settings Object',
|
||||
description:
|
||||
'Create a settings object — a maintenance window, alerting profile, management zone, or any other schema-backed configuration.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the settings.write scope',
|
||||
},
|
||||
schemaId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Schema of the object to create, e.g. builtin:alerting.maintenance-window. List Settings Schemas returns the available IDs',
|
||||
},
|
||||
scope: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Scope the object applies to, e.g. environment or an entity ID such as HOST-06F288EE2A930951',
|
||||
},
|
||||
value: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The configuration itself. Its shape is defined by the schema — read an existing object of the same schema to see the expected fields',
|
||||
},
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Schema version to validate against. Defaults to the latest',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External ID to correlate the object with a system outside Dynatrace',
|
||||
},
|
||||
validateOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Validate the payload without creating anything',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, '/settings/objects', {
|
||||
validateOnly: params.validateOnly,
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
// The endpoint takes an array; this tool creates exactly one object.
|
||||
body: (params) => {
|
||||
const value = parseJsonParam(params.value)
|
||||
if (value === undefined) throw new Error('value is required')
|
||||
const entry: Record<string, unknown> = {
|
||||
schemaId: params.schemaId,
|
||||
scope: params.scope,
|
||||
value,
|
||||
}
|
||||
if (params.schemaVersion) entry.schemaVersion = params.schemaVersion
|
||||
if (params.externalId) entry.externalId = params.externalId
|
||||
return JSON.stringify([entry])
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const text = await response.text()
|
||||
const parsed = text.trim() ? JSON.parse(text) : []
|
||||
const entries = Array.isArray(parsed) ? (parsed as Array<Record<string, unknown>>) : []
|
||||
const results = entries.map(mapSettingsWriteResult)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results,
|
||||
objectId: results[0]?.objectId ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'One result per submitted object',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
code: { type: 'number', description: 'Per-object HTTP status', nullable: true },
|
||||
objectId: { type: 'string', description: 'ID of the created object', nullable: true },
|
||||
writeError: {
|
||||
type: 'json',
|
||||
description: 'Validation error for this object, when it failed',
|
||||
nullable: true,
|
||||
properties: {
|
||||
code: { type: 'number', description: 'Error code' },
|
||||
message: { type: 'string', description: 'Error message' },
|
||||
constraintViolations: {
|
||||
type: 'array',
|
||||
description: 'Which part of the value failed validation',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
location: { type: 'string', description: 'Where the violation was found' },
|
||||
message: { type: 'string', description: 'What is wrong' },
|
||||
parameterLocation: {
|
||||
type: 'string',
|
||||
description: 'HEADER, PATH, PAYLOAD_BODY, or QUERY',
|
||||
},
|
||||
path: { type: 'string', description: 'Path to the offending field' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
invalidValue: {
|
||||
type: 'json',
|
||||
description:
|
||||
'The value that was rejected. Mirrors the submitted schema-defined value, so the shape is dynamic',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
objectId: {
|
||||
type: 'string',
|
||||
description: 'ID of the created object, lifted from the first result',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type { DynatraceCreateSloParams, DynatraceCreateSloResponse } from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
buildSloBody,
|
||||
dynatraceHeaders,
|
||||
idFromLocationHeader,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createSloTool: ToolConfig<DynatraceCreateSloParams, DynatraceCreateSloResponse> = {
|
||||
id: 'dynatrace_create_slo',
|
||||
name: 'Dynatrace Create SLO',
|
||||
description:
|
||||
'Create a service-level objective from a metric expression, target, and evaluation timeframe.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the slo.write scope',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the SLO',
|
||||
},
|
||||
target: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Target success rate as a percentage, e.g. 99.5',
|
||||
},
|
||||
warning: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Warning threshold as a percentage. Must sit above the target, e.g. 99.8',
|
||||
},
|
||||
timeframe: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Evaluation timeframe in Dynatrace notation, e.g. -1d, -1w, or now-30d',
|
||||
},
|
||||
evaluationType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
default: 'AGGREGATE',
|
||||
description: 'Evaluation type. AGGREGATE is the only value the API accepts',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the SLO',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the SLO is evaluated. Dynatrace defaults it to false',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity filter scoping the SLO, e.g. type("SERVICE"),tag("env:prod")',
|
||||
},
|
||||
metricExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Metric expression the SLO evaluates, e.g. (100)*(builtin:service.errors.total.successCount:splitBy())/(builtin:service.requestCount.total:splitBy())',
|
||||
},
|
||||
metricName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Display name for the SLO metric',
|
||||
},
|
||||
burnRateVisualizationEnabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Show the error-budget burn rate on the SLO',
|
||||
},
|
||||
fastBurnThreshold: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Burn rate above which the SLO is considered fast-burning',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildDynatraceUrl(params.environmentUrl, '/slo'),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => buildSloBody(params),
|
||||
},
|
||||
|
||||
/** Answers 201 with no body — the new SLO's ID arrives in the Location header. */
|
||||
transformResponse: async (response: Response, params?: DynatraceCreateSloParams) => ({
|
||||
success: true,
|
||||
output: {
|
||||
sloId: idFromLocationHeader(response),
|
||||
name: params?.name ?? '',
|
||||
},
|
||||
}),
|
||||
|
||||
outputs: {
|
||||
sloId: {
|
||||
type: 'string',
|
||||
description: 'ID of the created SLO, read from the Location header',
|
||||
nullable: true,
|
||||
},
|
||||
name: { type: 'string', description: 'Name the SLO was created with' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
DynatraceDeleteProblemCommentParams,
|
||||
DynatraceDeleteProblemCommentResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import { buildDynatraceUrl, dynatraceHeaders, encodeDynatraceId } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteProblemCommentTool: ToolConfig<
|
||||
DynatraceDeleteProblemCommentParams,
|
||||
DynatraceDeleteProblemCommentResponse
|
||||
> = {
|
||||
id: 'dynatrace_delete_problem_comment',
|
||||
name: 'Dynatrace Delete Problem Comment',
|
||||
description: 'Delete a comment from a Dynatrace problem.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the problems.write scope',
|
||||
},
|
||||
problemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the problem the comment belongs to',
|
||||
},
|
||||
commentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the comment to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/problems/${encodeDynatraceId(params.problemId)}/comments/${encodeDynatraceId(
|
||||
params.commentId
|
||||
)}`
|
||||
),
|
||||
method: 'DELETE',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
/** The endpoint answers 204 with no body, so the deleted IDs are echoed back. */
|
||||
transformResponse: async (_response: Response, params?: DynatraceDeleteProblemCommentParams) => ({
|
||||
success: true,
|
||||
output: {
|
||||
problemId: params?.problemId ?? '',
|
||||
commentId: params?.commentId ?? '',
|
||||
deleted: true,
|
||||
},
|
||||
}),
|
||||
|
||||
outputs: {
|
||||
problemId: { type: 'string', description: 'ID of the problem' },
|
||||
commentId: { type: 'string', description: 'ID of the deleted comment' },
|
||||
deleted: { type: 'boolean', description: 'Always true — a failed delete raises instead' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import type {
|
||||
DynatraceDeleteSettingsObjectParams,
|
||||
DynatraceDeleteSettingsObjectResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import { buildDynatraceUrl, dynatraceHeaders, encodeDynatraceId } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteSettingsObjectTool: ToolConfig<
|
||||
DynatraceDeleteSettingsObjectParams,
|
||||
DynatraceDeleteSettingsObjectResponse
|
||||
> = {
|
||||
id: 'dynatrace_delete_settings_object',
|
||||
name: 'Dynatrace Delete Settings Object',
|
||||
description: 'Delete a settings object. Dynatrace cannot undo this.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the settings.write scope',
|
||||
},
|
||||
objectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the settings object to delete',
|
||||
},
|
||||
updateToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Token from Get Settings Object. When set, the delete fails if the object changed in the meantime',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/settings/objects/${encodeDynatraceId(params.objectId)}`,
|
||||
{ updateToken: params.updateToken }
|
||||
),
|
||||
method: 'DELETE',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
/** The endpoint answers 204 with no body, so the deleted ID is echoed back. */
|
||||
transformResponse: async (_response: Response, params?: DynatraceDeleteSettingsObjectParams) => ({
|
||||
success: true,
|
||||
output: {
|
||||
objectId: params?.objectId ?? '',
|
||||
deleted: true,
|
||||
},
|
||||
}),
|
||||
|
||||
outputs: {
|
||||
objectId: { type: 'string', description: 'ID of the deleted settings object' },
|
||||
deleted: { type: 'boolean', description: 'Always true — a failed delete raises instead' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { DynatraceDeleteSloParams, DynatraceDeleteSloResponse } from '@/tools/dynatrace/types'
|
||||
import { buildDynatraceUrl, dynatraceHeaders, encodeDynatraceId } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteSloTool: ToolConfig<DynatraceDeleteSloParams, DynatraceDeleteSloResponse> = {
|
||||
id: 'dynatrace_delete_slo',
|
||||
name: 'Dynatrace Delete SLO',
|
||||
description: 'Delete a service-level objective.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the slo.write scope',
|
||||
},
|
||||
sloId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the SLO to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, `/slo/${encodeDynatraceId(params.sloId)}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
/** The endpoint answers 204 with no body, so the deleted ID is echoed back. */
|
||||
transformResponse: async (_response: Response, params?: DynatraceDeleteSloParams) => ({
|
||||
success: true,
|
||||
output: {
|
||||
sloId: params?.sloId ?? '',
|
||||
deleted: true,
|
||||
},
|
||||
}),
|
||||
|
||||
outputs: {
|
||||
sloId: { type: 'string', description: 'ID of the deleted SLO' },
|
||||
deleted: { type: 'boolean', description: 'Always true — a failed delete raises instead' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { DynatraceDeleteTagParams, DynatraceDeleteTagResponse } from '@/tools/dynatrace/types'
|
||||
import { buildDynatraceUrl, dynatraceHeaders, readJsonBody } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteTagTool: ToolConfig<DynatraceDeleteTagParams, DynatraceDeleteTagResponse> = {
|
||||
id: 'dynatrace_delete_tag',
|
||||
name: 'Dynatrace Delete Tag',
|
||||
description:
|
||||
'Remove a custom tag from every monitored entity an entity selector matches. Deletes one key-value pair, or every tag with the key.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the entities.write scope',
|
||||
},
|
||||
entitySelector: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity selector for the entities to untag, e.g. type("HOST"),tag("owner:old")',
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Key of the tag to delete',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Value of the tag to delete. Omit it and set Delete All With Key to remove every value of the key',
|
||||
},
|
||||
deleteAllWithKey: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Delete every tag carrying the key, regardless of value',
|
||||
},
|
||||
from: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the timeframe in the same formats as From. Defaults to now',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, '/tags', {
|
||||
entitySelector: params.entitySelector,
|
||||
key: params.key,
|
||||
value: params.value,
|
||||
deleteAllWithKey: params.deleteAllWithKey,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
}),
|
||||
method: 'DELETE',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
matchedEntitiesCount: (data.matchedEntitiesCount as number) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
matchedEntitiesCount: {
|
||||
type: 'number',
|
||||
description: 'How many entities the selector matched and had the tag removed from',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -2,25 +2,40 @@
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { DynatraceBlock } from '@/blocks/blocks/dynatrace'
|
||||
import { addTagsTool } from '@/tools/dynatrace/add_tags'
|
||||
import { closeProblemTool } from '@/tools/dynatrace/close_problem'
|
||||
import { createSettingsObjectTool } from '@/tools/dynatrace/create_settings_object'
|
||||
import { createSloTool } from '@/tools/dynatrace/create_slo'
|
||||
import { getAuditLogsTool } from '@/tools/dynatrace/get_audit_logs'
|
||||
import { getEntityTool } from '@/tools/dynatrace/get_entity'
|
||||
import { getMetricTool } from '@/tools/dynatrace/get_metric'
|
||||
import { getProblemTool } from '@/tools/dynatrace/get_problem'
|
||||
import { getSloTool } from '@/tools/dynatrace/get_slo'
|
||||
import { getSyntheticBatchTool } from '@/tools/dynatrace/get_synthetic_batch'
|
||||
import { ingestEventTool } from '@/tools/dynatrace/ingest_event'
|
||||
import { ingestLogsTool } from '@/tools/dynatrace/ingest_logs'
|
||||
import { ingestMetricsTool } from '@/tools/dynatrace/ingest_metrics'
|
||||
import { listAttacksTool } from '@/tools/dynatrace/list_attacks'
|
||||
import { listEntitiesTool } from '@/tools/dynatrace/list_entities'
|
||||
import { listEntityTypesTool } from '@/tools/dynatrace/list_entity_types'
|
||||
import { listEventsTool } from '@/tools/dynatrace/list_events'
|
||||
import { listMetricsTool } from '@/tools/dynatrace/list_metrics'
|
||||
import { listProblemCommentsTool } from '@/tools/dynatrace/list_problem_comments'
|
||||
import { listProblemsTool } from '@/tools/dynatrace/list_problems'
|
||||
import { listRemediationItemsTool } from '@/tools/dynatrace/list_remediation_items'
|
||||
import { listSecurityProblemsTool } from '@/tools/dynatrace/list_security_problems'
|
||||
import { listSettingsObjectsTool } from '@/tools/dynatrace/list_settings_objects'
|
||||
import { listSettingsSchemasTool } from '@/tools/dynatrace/list_settings_schemas'
|
||||
import { listSlosTool } from '@/tools/dynatrace/list_slos'
|
||||
import { listSyntheticMonitorsTool } from '@/tools/dynatrace/list_synthetic_monitors'
|
||||
import { listTagsTool } from '@/tools/dynatrace/list_tags'
|
||||
import { muteSecurityProblemTool } from '@/tools/dynatrace/mute_security_problem'
|
||||
import { muteSecurityProblemsTool } from '@/tools/dynatrace/mute_security_problems'
|
||||
import { queryMetricsTool } from '@/tools/dynatrace/query_metrics'
|
||||
import { searchLogsTool } from '@/tools/dynatrace/search_logs'
|
||||
import { updateSettingsObjectTool } from '@/tools/dynatrace/update_settings_object'
|
||||
import { updateSloTool } from '@/tools/dynatrace/update_slo'
|
||||
import { buildDynatraceUrl, dynatraceHeaders } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId, extractErrorMessageWithId } from '@/tools/error-extractors'
|
||||
|
||||
@@ -97,6 +112,150 @@ describe('path identifiers', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('new-surface request shaping', () => {
|
||||
const base = { environmentUrl: ENV, apiToken: TOKEN }
|
||||
|
||||
it('routes synthetic monitors to Environment API v1, not v2', () => {
|
||||
expect(url(listSyntheticMonitorsTool, base)).toBe(`${ENV}/api/v1/synthetic/monitors`)
|
||||
// ...while executions stay on v2.
|
||||
expect(url(getSyntheticBatchTool, { ...base, batchId: 'B-1' })).toBe(
|
||||
`${ENV}/api/v2/synthetic/executions/batch/B-1`
|
||||
)
|
||||
})
|
||||
|
||||
it('strips a trailing /api/v1 as well as /api/v2 from the environment URL', () => {
|
||||
expect(url(listSyntheticMonitorsTool, { ...base, environmentUrl: `${ENV}/api/v1` })).toBe(
|
||||
`${ENV}/api/v1/synthetic/monitors`
|
||||
)
|
||||
})
|
||||
|
||||
it('treats the synthetic enabled filter as tri-state, not a boolean', () => {
|
||||
const params = (DynatraceBlock.tools.config?.params ?? (() => ({}))) as (
|
||||
p: Record<string, unknown>
|
||||
) => Record<string, unknown>
|
||||
const call = (monitorEnabled: string | undefined) =>
|
||||
params({
|
||||
operation: 'dynatrace_list_synthetic_monitors',
|
||||
environmentUrl: ENV,
|
||||
apiToken: TOKEN,
|
||||
monitorEnabled,
|
||||
})
|
||||
|
||||
// "Any" must send no filter — `enabled=false` would return only the
|
||||
// disabled monitors, which is the opposite of what the user asked for.
|
||||
expect(call('').enabled).toBeUndefined()
|
||||
expect(call(undefined).enabled).toBeUndefined()
|
||||
expect(call('true').enabled).toBe(true)
|
||||
expect(call('false').enabled).toBe(false)
|
||||
|
||||
expect(url(listSyntheticMonitorsTool, { environmentUrl: ENV, apiToken: TOKEN })).toBe(
|
||||
`${ENV}/api/v1/synthetic/monitors`
|
||||
)
|
||||
expect(
|
||||
url(listSyntheticMonitorsTool, { environmentUrl: ENV, apiToken: TOKEN, enabled: true })
|
||||
).toBe(`${ENV}/api/v1/synthetic/monitors?enabled=true`)
|
||||
})
|
||||
|
||||
it('keeps every remaining switch a real boolean, since off means false for each', () => {
|
||||
// monitorEnabled was the only tri-state filter. For the rest, Dynatrace's
|
||||
// own default is false, so serializing the off position is correct — this
|
||||
// pins that they stay switches rather than drifting into the same trap.
|
||||
const switches = DynatraceBlock.subBlocks
|
||||
.filter((sb) => sb.type === 'switch')
|
||||
.map((sb) => sb.id)
|
||||
expect(switches).not.toContain('monitorEnabled')
|
||||
expect(switches.sort()).toEqual(
|
||||
[
|
||||
'burnRateVisualizationEnabled',
|
||||
'deleteAllWithKey',
|
||||
'evaluate',
|
||||
'failOnPerformanceIssue',
|
||||
'showGlobalSlos',
|
||||
'sloEnabled',
|
||||
'stopOnProblem',
|
||||
'takeScreenshotsOnSuccess',
|
||||
'validateOnly',
|
||||
].sort()
|
||||
)
|
||||
})
|
||||
|
||||
it('repeats the synthetic tag param once per value', () => {
|
||||
expect(url(listSyntheticMonitorsTool, { ...base, tag: 'a, b' })).toBe(
|
||||
`${ENV}/api/v1/synthetic/monitors?tag=a&tag=b`
|
||||
)
|
||||
})
|
||||
|
||||
it('accepts bulk security problem IDs as a list, JSON array, or comma string', () => {
|
||||
const read = (ids: unknown) =>
|
||||
(
|
||||
body(muteSecurityProblemsTool, {
|
||||
...base,
|
||||
securityProblemIds: ids,
|
||||
reason: 'FALSE_POSITIVE',
|
||||
}) as Record<string, unknown>
|
||||
).securityProblemIds
|
||||
|
||||
expect(read('S-1, S-2')).toEqual(['S-1', 'S-2'])
|
||||
expect(read('["S-1","S-2"]')).toEqual(['S-1', 'S-2'])
|
||||
expect(read(['S-1', 'S-2'])).toEqual(['S-1', 'S-2'])
|
||||
})
|
||||
|
||||
it('refuses a bulk mute with no IDs rather than sending an empty batch', () => {
|
||||
expect(() =>
|
||||
body(muteSecurityProblemsTool, { ...base, securityProblemIds: '', reason: 'IGNORE' })
|
||||
).toThrow(/at least one ID/)
|
||||
})
|
||||
|
||||
it('wraps a settings object create in the array the endpoint expects', () => {
|
||||
const sent = body(createSettingsObjectTool, {
|
||||
...base,
|
||||
schemaId: 'builtin:alerting.maintenance-window',
|
||||
scope: 'environment',
|
||||
value: '{"enabled":true}',
|
||||
}) as string
|
||||
expect(JSON.parse(sent)).toEqual([
|
||||
{
|
||||
schemaId: 'builtin:alerting.maintenance-window',
|
||||
scope: 'environment',
|
||||
value: { enabled: true },
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('sends the update token so a concurrent settings change is not overwritten', () => {
|
||||
const sent = body(updateSettingsObjectTool, {
|
||||
...base,
|
||||
objectId: 'O-1',
|
||||
value: { enabled: false },
|
||||
updateToken: 'TOKEN-123',
|
||||
}) as Record<string, unknown>
|
||||
expect(sent.updateToken).toBe('TOKEN-123')
|
||||
expect(sent.value).toEqual({ enabled: false })
|
||||
})
|
||||
|
||||
it('builds the same SLO body for create and update', () => {
|
||||
const fields = {
|
||||
...base,
|
||||
name: 'Checkout',
|
||||
target: 99.5,
|
||||
warning: 99.8,
|
||||
timeframe: '-1w',
|
||||
evaluationType: 'AGGREGATE',
|
||||
fastBurnThreshold: 10,
|
||||
}
|
||||
const created = body(createSloTool, fields) as Record<string, unknown>
|
||||
const updated = body(updateSloTool, { ...fields, sloId: 'SLO-1' }) as Record<string, unknown>
|
||||
expect(created).toEqual(updated)
|
||||
expect(created.errorBudgetBurnRate).toEqual({ fastBurnThreshold: 10 })
|
||||
})
|
||||
|
||||
it('requires a non-empty tag array before writing tags', () => {
|
||||
expect(() =>
|
||||
body(addTagsTool, { ...base, entitySelector: 'type("HOST")', tags: '[]' })
|
||||
).toThrow(/non-empty array/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('json request params', () => {
|
||||
const base = { environmentUrl: ENV, apiToken: TOKEN }
|
||||
|
||||
@@ -441,6 +600,102 @@ describe('response mapping', () => {
|
||||
expect(created.output.comments).toEqual([])
|
||||
})
|
||||
|
||||
it('reads the documented top-level key of the newer list responses', async () => {
|
||||
const cases: Array<{
|
||||
name: string
|
||||
tool: { transformResponse?: (r: Response) => Promise<{ output: Record<string, never> }> }
|
||||
payload: Record<string, unknown>
|
||||
read: (out: Record<string, never>) => unknown
|
||||
}> = [
|
||||
{
|
||||
name: 'GET /attacks -> attacks',
|
||||
tool: listAttacksTool,
|
||||
payload: { totalCount: 1, attacks: [{ attackId: 'A-1', state: 'EXPLOITED' }] },
|
||||
read: (o) => o.attacks,
|
||||
},
|
||||
{
|
||||
name: 'GET /tags -> tags',
|
||||
tool: listTagsTool,
|
||||
payload: { totalCount: 1, tags: [{ key: 'env', value: 'prod' }] },
|
||||
read: (o) => o.tags,
|
||||
},
|
||||
{
|
||||
name: 'GET /settings/schemas -> items',
|
||||
tool: listSettingsSchemasTool,
|
||||
payload: { totalCount: 1, items: [{ schemaId: 'builtin:alerting.profile' }] },
|
||||
read: (o) => o.schemas,
|
||||
},
|
||||
{
|
||||
name: 'GET /settings/objects -> items',
|
||||
tool: listSettingsObjectsTool,
|
||||
payload: { totalCount: 1, items: [{ objectId: 'O-1', value: { enabled: true } }] },
|
||||
read: (o) => o.items,
|
||||
},
|
||||
{
|
||||
name: 'GET /securityProblems/{id}/remediationItems -> remediationItems',
|
||||
tool: listRemediationItemsTool,
|
||||
payload: { remediationItems: [{ id: 'R-1', vulnerabilityState: 'VULNERABLE' }] },
|
||||
read: (o) => o.remediationItems,
|
||||
},
|
||||
{
|
||||
name: 'GET /api/v1/synthetic/monitors -> monitors',
|
||||
tool: listSyntheticMonitorsTool,
|
||||
payload: { monitors: [{ entityId: 'SYNTHETIC_TEST-1', name: 'checkout', type: 'HTTP' }] },
|
||||
read: (o) => o.monitors,
|
||||
},
|
||||
]
|
||||
|
||||
for (const { name, tool, payload, read } of cases) {
|
||||
const out = (
|
||||
await tool.transformResponse!(new Response(JSON.stringify(payload), { status: 200 }))
|
||||
).output
|
||||
expect(read(out), `${name} produced an empty list`).toHaveLength(1)
|
||||
}
|
||||
})
|
||||
|
||||
it('reads a created SLO id from the Location header, since the body is empty', async () => {
|
||||
const created = new Response(null, {
|
||||
status: 201,
|
||||
headers: { location: 'https://abc.live.dynatrace.com/api/v2/slo/1234-5678' },
|
||||
})
|
||||
const out = (
|
||||
await createSloTool.transformResponse!(created, {
|
||||
environmentUrl: ENV,
|
||||
apiToken: TOKEN,
|
||||
name: 'Checkout',
|
||||
} as never)
|
||||
).output
|
||||
expect(out.sloId).toBe('1234-5678')
|
||||
expect(out.name).toBe('Checkout')
|
||||
})
|
||||
|
||||
it('counts only the security problems whose mute state actually changed', async () => {
|
||||
const body = JSON.stringify({
|
||||
summary: [
|
||||
{ securityProblemId: 'S-1', muteStateChangeTriggered: true },
|
||||
{ securityProblemId: 'S-2', muteStateChangeTriggered: false, reason: 'ALREADY_MUTED' },
|
||||
],
|
||||
})
|
||||
const out = (
|
||||
await muteSecurityProblemsTool.transformResponse!(new Response(body, { status: 200 }))
|
||||
).output
|
||||
expect(out.summary).toHaveLength(2)
|
||||
expect(out.changedCount).toBe(1)
|
||||
})
|
||||
|
||||
it('treats a 204 mute as "already in that state" rather than a fresh change', async () => {
|
||||
const out = (
|
||||
await muteSecurityProblemTool.transformResponse!(new Response(null, { status: 204 }), {
|
||||
environmentUrl: ENV,
|
||||
apiToken: TOKEN,
|
||||
securityProblemId: 'S-1',
|
||||
reason: 'FALSE_POSITIVE',
|
||||
} as never)
|
||||
).output
|
||||
expect(out.alreadyInState).toBe(true)
|
||||
expect(out.securityProblemId).toBe('S-1')
|
||||
})
|
||||
|
||||
it('surfaces a 200 partial-success log ingestion body', async () => {
|
||||
const response = new Response(JSON.stringify({ error: { message: 'some invalid' } }), {
|
||||
status: 200,
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import type {
|
||||
DynatraceExecuteSyntheticMonitorsParams,
|
||||
DynatraceExecuteSyntheticMonitorsResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
parseJsonParam,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const executeSyntheticMonitorsTool: ToolConfig<
|
||||
DynatraceExecuteSyntheticMonitorsParams,
|
||||
DynatraceExecuteSyntheticMonitorsResponse
|
||||
> = {
|
||||
id: 'dynatrace_execute_synthetic_monitors',
|
||||
name: 'Dynatrace Execute Synthetic Monitors',
|
||||
description:
|
||||
'Trigger an on-demand batch execution of synthetic monitors, for gating a deploy on a smoke test. Returns a batch ID to poll.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace access token (dt0c01...) with syntheticExecutions.write or ExternalSyntheticIntegration',
|
||||
},
|
||||
monitors: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Monitors to run, as an array of objects with monitorId and optional locations and executionCount (e.g. [{"monitorId":"SYNTHETIC_TEST-123","executionCount":1}]). Execution count caps at 10',
|
||||
},
|
||||
processingMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'STANDARD, DISABLE_PROBLEM_DETECTION, or EXECUTIONS_DETAILS_ONLY',
|
||||
},
|
||||
failOnPerformanceIssue: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Treat a performance threshold breach as a failure',
|
||||
},
|
||||
stopOnProblem: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Stop the batch as soon as one monitor reports a problem',
|
||||
},
|
||||
takeScreenshotsOnSuccess: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Capture screenshots for successful browser executions too',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Key-value metadata to attach to the batch, e.g. the release version. Max 64 pairs, 1024 characters each',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildDynatraceUrl(params.environmentUrl, '/synthetic/executions/batch'),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const monitors = parseJsonParam(params.monitors)
|
||||
if (!Array.isArray(monitors) || monitors.length === 0) {
|
||||
throw new Error('monitors must be a non-empty array of objects with a monitorId')
|
||||
}
|
||||
const body: Record<string, unknown> = { monitors }
|
||||
if (params.processingMode) body.processingMode = params.processingMode
|
||||
if (params.failOnPerformanceIssue !== undefined) {
|
||||
body.failOnPerformanceIssue = params.failOnPerformanceIssue
|
||||
}
|
||||
if (params.stopOnProblem !== undefined) body.stopOnProblem = params.stopOnProblem
|
||||
if (params.takeScreenshotsOnSuccess !== undefined) {
|
||||
body.takeScreenshotsOnSuccess = params.takeScreenshotsOnSuccess
|
||||
}
|
||||
const metadata = parseJsonParam(params.metadata)
|
||||
if (metadata) body.metadata = metadata
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
batchId: (data.batchId as string) ?? null,
|
||||
triggeredCount: (data.triggeredCount as number) ?? null,
|
||||
triggeringProblemsCount: (data.triggeringProblemsCount as number) ?? null,
|
||||
triggered: Array.isArray(data.triggered)
|
||||
? (data.triggered as Array<Record<string, unknown>>)
|
||||
: [],
|
||||
triggeringProblemsDetails: Array.isArray(data.triggeringProblemsDetails)
|
||||
? (data.triggeringProblemsDetails as Array<Record<string, unknown>>)
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
batchId: {
|
||||
type: 'string',
|
||||
description: 'ID of the batch, to poll with Get Synthetic Batch',
|
||||
nullable: true,
|
||||
},
|
||||
triggeredCount: {
|
||||
type: 'number',
|
||||
description: 'How many executions were triggered',
|
||||
nullable: true,
|
||||
},
|
||||
triggeringProblemsCount: {
|
||||
type: 'number',
|
||||
description: 'How many executions could not be triggered',
|
||||
nullable: true,
|
||||
},
|
||||
triggered: {
|
||||
type: 'array',
|
||||
description: 'Triggered executions, grouped by monitor',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
monitorId: { type: 'string', description: 'Monitor that was triggered' },
|
||||
executions: {
|
||||
type: 'array',
|
||||
description: 'One entry per location the monitor ran from',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
executionId: { type: 'string', description: 'Execution ID' },
|
||||
locationId: { type: 'string', description: 'Location the execution ran from' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
triggeringProblemsDetails: {
|
||||
type: 'array',
|
||||
description: 'Why each untriggered execution failed to start',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
cause: { type: 'string', description: 'Why the execution could not be triggered' },
|
||||
details: { type: 'string', description: 'Detail behind the cause' },
|
||||
entityId: { type: 'string', description: 'Entity the problem relates to' },
|
||||
executionId: { type: 'string', description: 'Execution ID, when one was assigned' },
|
||||
locationId: { type: 'string', description: 'Location the execution targeted' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { attackProperties } from '@/tools/dynatrace/outputs'
|
||||
import type { DynatraceGetAttackParams, DynatraceGetAttackResponse } from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
mapAttack,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getAttackTool: ToolConfig<DynatraceGetAttackParams, DynatraceGetAttackResponse> = {
|
||||
id: 'dynatrace_get_attack',
|
||||
name: 'Dynatrace Get Attack',
|
||||
description:
|
||||
'Get a single attack with its entry point, payload, attacker, and the vulnerability it exploited.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the attacks.read scope',
|
||||
},
|
||||
attackId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the attack',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated optional properties to include: +attackTarget, +request, +entrypoint, +vulnerability, +securityProblem, +attacker, +managementZones',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, `/attacks/${encodeDynatraceId(params.attackId)}`, {
|
||||
fields: params.fields,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
attack: mapAttack(data),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
attack: {
|
||||
type: 'object',
|
||||
description: 'The requested attack',
|
||||
properties: attackProperties,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -129,7 +129,12 @@ export const getAuditLogsTool: ToolConfig<
|
||||
timestamp: { type: 'number', description: 'Change timestamp in UTC milliseconds' },
|
||||
success: { type: 'boolean', description: 'Whether the change succeeded' },
|
||||
message: { type: 'string', description: 'Description of the change', nullable: true },
|
||||
patch: { type: 'json', description: 'JSON patch of the change', nullable: true },
|
||||
patch: {
|
||||
type: 'json',
|
||||
description:
|
||||
'JSON patch describing the change. Its shape follows whatever settings object was edited, so it is dynamic',
|
||||
nullable: true,
|
||||
},
|
||||
settingsSchemaId: {
|
||||
type: 'string',
|
||||
description: 'Settings schema ID (dt.settings.schema_id)',
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { commentProperties } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceGetProblemCommentParams,
|
||||
DynatraceGetProblemCommentResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
mapComment,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getProblemCommentTool: ToolConfig<
|
||||
DynatraceGetProblemCommentParams,
|
||||
DynatraceGetProblemCommentResponse
|
||||
> = {
|
||||
id: 'dynatrace_get_problem_comment',
|
||||
name: 'Dynatrace Get Problem Comment',
|
||||
description: 'Get a single comment on a Dynatrace problem.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the problems.read scope',
|
||||
},
|
||||
problemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the problem the comment belongs to',
|
||||
},
|
||||
commentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the comment',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/problems/${encodeDynatraceId(params.problemId)}/comments/${encodeDynatraceId(
|
||||
params.commentId
|
||||
)}`
|
||||
),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
comment: mapComment(data) ?? {
|
||||
id: null,
|
||||
authorName: null,
|
||||
content: null,
|
||||
context: null,
|
||||
createdAtTimestamp: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
comment: {
|
||||
type: 'object',
|
||||
description: 'The requested comment',
|
||||
properties: commentProperties,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { settingsObjectProperties } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceGetSettingsObjectParams,
|
||||
DynatraceGetSettingsObjectResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
mapSettingsObject,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getSettingsObjectTool: ToolConfig<
|
||||
DynatraceGetSettingsObjectParams,
|
||||
DynatraceGetSettingsObjectResponse
|
||||
> = {
|
||||
id: 'dynatrace_get_settings_object',
|
||||
name: 'Dynatrace Get Settings Object',
|
||||
description:
|
||||
'Get a single settings object with its value and its update token. Read it before updating so the token can guard against a concurrent change.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the settings.read scope',
|
||||
},
|
||||
objectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the settings object',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/settings/objects/${encodeDynatraceId(params.objectId)}`
|
||||
),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
object: mapSettingsObject(data),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
object: {
|
||||
type: 'object',
|
||||
description: 'The requested settings object',
|
||||
properties: settingsObjectProperties,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import type {
|
||||
DynatraceGetSyntheticBatchParams,
|
||||
DynatraceGetSyntheticBatchResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getSyntheticBatchTool: ToolConfig<
|
||||
DynatraceGetSyntheticBatchParams,
|
||||
DynatraceGetSyntheticBatchResponse
|
||||
> = {
|
||||
id: 'dynatrace_get_synthetic_batch',
|
||||
name: 'Dynatrace Get Synthetic Batch',
|
||||
description:
|
||||
'Get the status and failures of an on-demand synthetic batch execution. Poll it after triggering to gate a deploy on the result.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace access token (dt0c01...) with syntheticExecutions.read, ReadSyntheticData, or ExternalSyntheticIntegration',
|
||||
},
|
||||
batchId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Batch ID returned by Execute Synthetic Monitors',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/synthetic/executions/batch/${encodeDynatraceId(params.batchId)}`
|
||||
),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const list = (key: string) =>
|
||||
Array.isArray(data[key]) ? (data[key] as Array<Record<string, unknown>>) : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
batchId: (data.batchId as string) ?? null,
|
||||
batchStatus: (data.batchStatus as string) ?? null,
|
||||
executedCount: (data.executedCount as number) ?? null,
|
||||
failedCount: (data.failedCount as number) ?? null,
|
||||
failedToExecuteCount: (data.failedToExecuteCount as number) ?? null,
|
||||
triggeredCount: (data.triggeredCount as number) ?? null,
|
||||
triggeringProblemsCount: (data.triggeringProblemsCount as number) ?? null,
|
||||
failedExecutions: list('failedExecutions'),
|
||||
failedToExecute: list('failedToExecute'),
|
||||
triggeringProblems: list('triggeringProblems'),
|
||||
metadata:
|
||||
data.metadata && typeof data.metadata === 'object'
|
||||
? (data.metadata as Record<string, unknown>)
|
||||
: {},
|
||||
userId: (data.userId as string) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
batchId: { type: 'string', description: 'ID of the batch', nullable: true },
|
||||
batchStatus: {
|
||||
type: 'string',
|
||||
description: 'RUNNING, SUCCESS, FAILED, FAILED_TO_EXECUTE, or NOT_TRIGGERED',
|
||||
nullable: true,
|
||||
},
|
||||
executedCount: { type: 'number', description: 'Executions completed', nullable: true },
|
||||
failedCount: { type: 'number', description: 'Executions that failed', nullable: true },
|
||||
failedToExecuteCount: {
|
||||
type: 'number',
|
||||
description: 'Executions that never ran',
|
||||
nullable: true,
|
||||
},
|
||||
triggeredCount: { type: 'number', description: 'Executions triggered', nullable: true },
|
||||
triggeringProblemsCount: {
|
||||
type: 'number',
|
||||
description: 'Executions that could not be triggered',
|
||||
nullable: true,
|
||||
},
|
||||
failedExecutions: {
|
||||
type: 'array',
|
||||
description: 'Executions that ran and failed',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errorCode: { type: 'string', description: 'Error code Dynatrace reported' },
|
||||
executionId: { type: 'string', description: 'Execution ID' },
|
||||
executionStage: {
|
||||
type: 'string',
|
||||
description:
|
||||
'DATA_RETRIEVED, EXECUTED, NOT_TRIGGERED, TIMED_OUT, TRIGGERED, or WAITING',
|
||||
},
|
||||
executionTimestamp: { type: 'number', description: 'Execution time in UTC ms' },
|
||||
failureMessage: { type: 'string', description: 'Why the execution failed' },
|
||||
locationId: { type: 'string', description: 'Location the execution ran from' },
|
||||
monitorId: { type: 'string', description: 'Monitor that was executed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
failedToExecute: {
|
||||
type: 'array',
|
||||
description: 'Executions that never started',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errorCode: { type: 'string', description: 'Error code Dynatrace reported' },
|
||||
executionId: { type: 'string', description: 'Execution ID' },
|
||||
executionStage: {
|
||||
type: 'string',
|
||||
description:
|
||||
'DATA_RETRIEVED, EXECUTED, NOT_TRIGGERED, TIMED_OUT, TRIGGERED, or WAITING',
|
||||
},
|
||||
executionTimestamp: { type: 'number', description: 'Execution time in UTC ms' },
|
||||
failureMessage: { type: 'string', description: 'Why the execution failed' },
|
||||
locationId: { type: 'string', description: 'Location the execution ran from' },
|
||||
monitorId: { type: 'string', description: 'Monitor that was executed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
triggeringProblems: {
|
||||
type: 'array',
|
||||
description: 'Reasons executions could not be triggered',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
cause: { type: 'string', description: 'Why the execution could not be triggered' },
|
||||
details: { type: 'string', description: 'Detail behind the cause' },
|
||||
entityId: { type: 'string', description: 'Entity the problem relates to' },
|
||||
executionId: { type: 'string', description: 'Execution ID, when one was assigned' },
|
||||
locationId: { type: 'string', description: 'Location the execution targeted' },
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Key-value metadata supplied when the batch was triggered. Keys are caller-defined, so the shape is dynamic',
|
||||
},
|
||||
userId: { type: 'string', description: 'Who triggered the batch', nullable: true },
|
||||
},
|
||||
}
|
||||
@@ -1,45 +1,95 @@
|
||||
import { addProblemCommentTool } from '@/tools/dynatrace/add_problem_comment'
|
||||
import { addTagsTool } from '@/tools/dynatrace/add_tags'
|
||||
import { closeProblemTool } from '@/tools/dynatrace/close_problem'
|
||||
import { createSettingsObjectTool } from '@/tools/dynatrace/create_settings_object'
|
||||
import { createSloTool } from '@/tools/dynatrace/create_slo'
|
||||
import { deleteProblemCommentTool } from '@/tools/dynatrace/delete_problem_comment'
|
||||
import { deleteSettingsObjectTool } from '@/tools/dynatrace/delete_settings_object'
|
||||
import { deleteSloTool } from '@/tools/dynatrace/delete_slo'
|
||||
import { deleteTagTool } from '@/tools/dynatrace/delete_tag'
|
||||
import { executeSyntheticMonitorsTool } from '@/tools/dynatrace/execute_synthetic_monitors'
|
||||
import { getAttackTool } from '@/tools/dynatrace/get_attack'
|
||||
import { getAuditLogsTool } from '@/tools/dynatrace/get_audit_logs'
|
||||
import { getEntityTool } from '@/tools/dynatrace/get_entity'
|
||||
import { getEventTool } from '@/tools/dynatrace/get_event'
|
||||
import { getMetricTool } from '@/tools/dynatrace/get_metric'
|
||||
import { getProblemTool } from '@/tools/dynatrace/get_problem'
|
||||
import { getProblemCommentTool } from '@/tools/dynatrace/get_problem_comment'
|
||||
import { getSecurityProblemTool } from '@/tools/dynatrace/get_security_problem'
|
||||
import { getSettingsObjectTool } from '@/tools/dynatrace/get_settings_object'
|
||||
import { getSloTool } from '@/tools/dynatrace/get_slo'
|
||||
import { getSyntheticBatchTool } from '@/tools/dynatrace/get_synthetic_batch'
|
||||
import { ingestEventTool } from '@/tools/dynatrace/ingest_event'
|
||||
import { ingestLogsTool } from '@/tools/dynatrace/ingest_logs'
|
||||
import { ingestMetricsTool } from '@/tools/dynatrace/ingest_metrics'
|
||||
import { listAttacksTool } from '@/tools/dynatrace/list_attacks'
|
||||
import { listEntitiesTool } from '@/tools/dynatrace/list_entities'
|
||||
import { listEntityTypesTool } from '@/tools/dynatrace/list_entity_types'
|
||||
import { listEventsTool } from '@/tools/dynatrace/list_events'
|
||||
import { listMetricsTool } from '@/tools/dynatrace/list_metrics'
|
||||
import { listProblemCommentsTool } from '@/tools/dynatrace/list_problem_comments'
|
||||
import { listProblemsTool } from '@/tools/dynatrace/list_problems'
|
||||
import { listRemediationItemsTool } from '@/tools/dynatrace/list_remediation_items'
|
||||
import { listSecurityProblemsTool } from '@/tools/dynatrace/list_security_problems'
|
||||
import { listSettingsObjectsTool } from '@/tools/dynatrace/list_settings_objects'
|
||||
import { listSettingsSchemasTool } from '@/tools/dynatrace/list_settings_schemas'
|
||||
import { listSlosTool } from '@/tools/dynatrace/list_slos'
|
||||
import { listSyntheticMonitorsTool } from '@/tools/dynatrace/list_synthetic_monitors'
|
||||
import { listTagsTool } from '@/tools/dynatrace/list_tags'
|
||||
import { muteSecurityProblemTool } from '@/tools/dynatrace/mute_security_problem'
|
||||
import { muteSecurityProblemsTool } from '@/tools/dynatrace/mute_security_problems'
|
||||
import { queryMetricsTool } from '@/tools/dynatrace/query_metrics'
|
||||
import { searchLogsTool } from '@/tools/dynatrace/search_logs'
|
||||
import { unmuteSecurityProblemTool } from '@/tools/dynatrace/unmute_security_problem'
|
||||
import { unmuteSecurityProblemsTool } from '@/tools/dynatrace/unmute_security_problems'
|
||||
import { updateProblemCommentTool } from '@/tools/dynatrace/update_problem_comment'
|
||||
import { updateSettingsObjectTool } from '@/tools/dynatrace/update_settings_object'
|
||||
import { updateSloTool } from '@/tools/dynatrace/update_slo'
|
||||
|
||||
export const dynatraceAddProblemCommentTool = addProblemCommentTool
|
||||
export const dynatraceAddTagsTool = addTagsTool
|
||||
export const dynatraceCloseProblemTool = closeProblemTool
|
||||
export const dynatraceCreateSettingsObjectTool = createSettingsObjectTool
|
||||
export const dynatraceCreateSloTool = createSloTool
|
||||
export const dynatraceDeleteProblemCommentTool = deleteProblemCommentTool
|
||||
export const dynatraceDeleteSettingsObjectTool = deleteSettingsObjectTool
|
||||
export const dynatraceDeleteSloTool = deleteSloTool
|
||||
export const dynatraceDeleteTagTool = deleteTagTool
|
||||
export const dynatraceExecuteSyntheticMonitorsTool = executeSyntheticMonitorsTool
|
||||
export const dynatraceGetAttackTool = getAttackTool
|
||||
export const dynatraceGetAuditLogsTool = getAuditLogsTool
|
||||
export const dynatraceGetEntityTool = getEntityTool
|
||||
export const dynatraceGetEventTool = getEventTool
|
||||
export const dynatraceGetMetricTool = getMetricTool
|
||||
export const dynatraceGetProblemTool = getProblemTool
|
||||
export const dynatraceGetProblemCommentTool = getProblemCommentTool
|
||||
export const dynatraceGetSecurityProblemTool = getSecurityProblemTool
|
||||
export const dynatraceGetSettingsObjectTool = getSettingsObjectTool
|
||||
export const dynatraceGetSloTool = getSloTool
|
||||
export const dynatraceGetSyntheticBatchTool = getSyntheticBatchTool
|
||||
export const dynatraceIngestEventTool = ingestEventTool
|
||||
export const dynatraceIngestLogsTool = ingestLogsTool
|
||||
export const dynatraceIngestMetricsTool = ingestMetricsTool
|
||||
export const dynatraceListAttacksTool = listAttacksTool
|
||||
export const dynatraceListEntitiesTool = listEntitiesTool
|
||||
export const dynatraceListEntityTypesTool = listEntityTypesTool
|
||||
export const dynatraceListEventsTool = listEventsTool
|
||||
export const dynatraceListMetricsTool = listMetricsTool
|
||||
export const dynatraceListProblemCommentsTool = listProblemCommentsTool
|
||||
export const dynatraceListProblemsTool = listProblemsTool
|
||||
export const dynatraceListRemediationItemsTool = listRemediationItemsTool
|
||||
export const dynatraceListSecurityProblemsTool = listSecurityProblemsTool
|
||||
export const dynatraceListSettingsObjectsTool = listSettingsObjectsTool
|
||||
export const dynatraceListSettingsSchemasTool = listSettingsSchemasTool
|
||||
export const dynatraceListSlosTool = listSlosTool
|
||||
export const dynatraceListSyntheticMonitorsTool = listSyntheticMonitorsTool
|
||||
export const dynatraceListTagsTool = listTagsTool
|
||||
export const dynatraceMuteSecurityProblemTool = muteSecurityProblemTool
|
||||
export const dynatraceMuteSecurityProblemsTool = muteSecurityProblemsTool
|
||||
export const dynatraceQueryMetricsTool = queryMetricsTool
|
||||
export const dynatraceSearchLogsTool = searchLogsTool
|
||||
export const dynatraceUnmuteSecurityProblemTool = unmuteSecurityProblemTool
|
||||
export const dynatraceUnmuteSecurityProblemsTool = unmuteSecurityProblemsTool
|
||||
export const dynatraceUpdateProblemCommentTool = updateProblemCommentTool
|
||||
export const dynatraceUpdateSettingsObjectTool = updateSettingsObjectTool
|
||||
export const dynatraceUpdateSloTool = updateSloTool
|
||||
|
||||
@@ -83,7 +83,8 @@ export const ingestLogsTool: ToolConfig<DynatraceIngestLogsParams, DynatraceInge
|
||||
},
|
||||
details: {
|
||||
type: 'json',
|
||||
description: 'Partial-success details, present only when some events were rejected',
|
||||
description:
|
||||
'Partial-success body, present only when some events were rejected. The reference does not document its shape, so it is passed through as-is',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -74,13 +74,42 @@ export const ingestMetricsTool: ToolConfig<
|
||||
},
|
||||
ingestError: {
|
||||
type: 'json',
|
||||
description: 'Details of the invalid lines, with the line number and reason for each',
|
||||
description: 'Details of the invalid lines',
|
||||
nullable: true,
|
||||
properties: {
|
||||
code: { type: 'number', description: 'Error code' },
|
||||
message: { type: 'string', description: 'Error message' },
|
||||
invalidLines: {
|
||||
type: 'array',
|
||||
description: 'The rejected lines',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
line: { type: 'number', description: 'Line number in the payload' },
|
||||
error: { type: 'string', description: 'Why the line was rejected' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
warnings: {
|
||||
type: 'json',
|
||||
description: 'Warnings raised during ingestion, such as changed metric keys',
|
||||
nullable: true,
|
||||
properties: {
|
||||
message: { type: 'string', description: 'Warning message' },
|
||||
changedMetricKeys: {
|
||||
type: 'array',
|
||||
description: 'Lines whose metric key Dynatrace rewrote',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
line: { type: 'number', description: 'Line number in the payload' },
|
||||
warning: { type: 'string', description: 'What was changed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
attackProperties,
|
||||
nextPageKeyOutput,
|
||||
pageSizeOutput,
|
||||
totalCountOutput,
|
||||
} from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceListAttacksParams,
|
||||
DynatraceListAttacksResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapAttack,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listAttacksTool: ToolConfig<DynatraceListAttacksParams, DynatraceListAttacksResponse> =
|
||||
{
|
||||
id: 'dynatrace_list_attacks',
|
||||
name: 'Dynatrace List Attacks',
|
||||
description:
|
||||
'List runtime attacks Dynatrace Application Protection detected — injection attempts, their source, and whether they were blocked.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the attacks.read scope',
|
||||
},
|
||||
attackSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Attack selector, e.g. state("EXPLOITED"),attackType("SQL_INJECTION"),technology("JAVA")',
|
||||
},
|
||||
from: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression such as now-7d. Defaults to now-30d',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the timeframe in the same formats as From. Defaults to now',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated optional properties to include: +attackTarget, +request, +entrypoint, +vulnerability, +securityProblem, +attacker, +managementZones, +affectedEntities',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Sort by displayId, displayName, attackType, state, sourceIp, requestPath, or timestamp with a + or - prefix',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Attacks per page (max 500, default 100)',
|
||||
},
|
||||
nextPageKey: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Cursor for the next page. All other filters are ignored when it is set',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
params.nextPageKey
|
||||
? buildDynatraceUrl(params.environmentUrl, '/attacks', {
|
||||
nextPageKey: params.nextPageKey,
|
||||
})
|
||||
: buildDynatraceUrl(params.environmentUrl, '/attacks', {
|
||||
attackSelector: params.attackSelector,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
fields: params.fields,
|
||||
sort: params.sort,
|
||||
pageSize: params.pageSize,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const attacks = Array.isArray(data.attacks)
|
||||
? (data.attacks as Array<Record<string, unknown>>)
|
||||
: []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
attacks: attacks.map(mapAttack),
|
||||
totalCount: (data.totalCount as number) ?? null,
|
||||
pageSize: (data.pageSize as number) ?? null,
|
||||
nextPageKey: (data.nextPageKey as string) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
attacks: {
|
||||
type: 'array',
|
||||
description: 'Matching attacks',
|
||||
items: { type: 'object', properties: attackProperties },
|
||||
},
|
||||
totalCount: totalCountOutput,
|
||||
pageSize: pageSizeOutput,
|
||||
nextPageKey: nextPageKeyOutput,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { remediationItemProperties } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceListRemediationItemsParams,
|
||||
DynatraceListRemediationItemsResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
mapRemediationItem,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listRemediationItemsTool: ToolConfig<
|
||||
DynatraceListRemediationItemsParams,
|
||||
DynatraceListRemediationItemsResponse
|
||||
> = {
|
||||
id: 'dynatrace_list_remediation_items',
|
||||
name: 'Dynatrace List Remediation Items',
|
||||
description:
|
||||
'List the remediation items of a third-party vulnerability — the components to upgrade, their affected entities, and their mute state.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the securityProblems.read scope',
|
||||
},
|
||||
securityProblemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the third-party security problem',
|
||||
},
|
||||
remediationItemSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Remediation item selector, e.g. vulnerabilityState("VULNERABLE"),muted("false"),exposure("PUBLIC_NETWORK")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/securityProblems/${encodeDynatraceId(params.securityProblemId)}/remediationItems`,
|
||||
{ remediationItemSelector: params.remediationItemSelector }
|
||||
),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const remediationItems = Array.isArray(data.remediationItems)
|
||||
? (data.remediationItems as Array<Record<string, unknown>>)
|
||||
: []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
remediationItems: remediationItems.map(mapRemediationItem),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
remediationItems: {
|
||||
type: 'array',
|
||||
description: 'Remediation items of the vulnerability. This endpoint returns no total count',
|
||||
items: { type: 'object', properties: remediationItemProperties },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import {
|
||||
nextPageKeyOutput,
|
||||
pageSizeOutput,
|
||||
settingsObjectProperties,
|
||||
totalCountOutput,
|
||||
} from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceListSettingsObjectsParams,
|
||||
DynatraceListSettingsObjectsResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapSettingsObject,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listSettingsObjectsTool: ToolConfig<
|
||||
DynatraceListSettingsObjectsParams,
|
||||
DynatraceListSettingsObjectsResponse
|
||||
> = {
|
||||
id: 'dynatrace_list_settings_objects',
|
||||
name: 'Dynatrace List Settings Objects',
|
||||
description:
|
||||
'List settings objects — the configuration behind maintenance windows, alerting profiles, management zones, and anomaly detection.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the settings.read scope',
|
||||
},
|
||||
schemaIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated schema IDs, e.g. builtin:alerting.profile',
|
||||
},
|
||||
scopes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated scopes, e.g. environment or HOST-06F288EE2A930951',
|
||||
},
|
||||
externalIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated external IDs',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated fields to include: objectId, value, schemaId, schemaVersion, scope, author, modified, updateToken, created, externalId, summary, searchSummary',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter expression over created, modified, createdBy, modifiedBy, or value',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Sort expression, e.g. -modified',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Objects per page (max 500, default 100)',
|
||||
},
|
||||
nextPageKey: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Cursor for the next page. All other filters are ignored when it is set',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
params.nextPageKey
|
||||
? buildDynatraceUrl(params.environmentUrl, '/settings/objects', {
|
||||
nextPageKey: params.nextPageKey,
|
||||
})
|
||||
: buildDynatraceUrl(params.environmentUrl, '/settings/objects', {
|
||||
schemaIds: params.schemaIds,
|
||||
scopes: params.scopes,
|
||||
externalIds: params.externalIds,
|
||||
fields: params.fields,
|
||||
filter: params.filter,
|
||||
sort: params.sort,
|
||||
pageSize: params.pageSize,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const items = Array.isArray(data.items) ? (data.items as Array<Record<string, unknown>>) : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: items.map(mapSettingsObject),
|
||||
totalCount: (data.totalCount as number) ?? null,
|
||||
pageSize: (data.pageSize as number) ?? null,
|
||||
nextPageKey: (data.nextPageKey as string) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Matching settings objects',
|
||||
items: { type: 'object', properties: settingsObjectProperties },
|
||||
},
|
||||
totalCount: totalCountOutput,
|
||||
pageSize: pageSizeOutput,
|
||||
nextPageKey: nextPageKeyOutput,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { settingsSchemaProperties, totalCountOutput } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceListSettingsSchemasParams,
|
||||
DynatraceListSettingsSchemasResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapSettingsSchema,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listSettingsSchemasTool: ToolConfig<
|
||||
DynatraceListSettingsSchemasParams,
|
||||
DynatraceListSettingsSchemasResponse
|
||||
> = {
|
||||
id: 'dynatrace_list_settings_schemas',
|
||||
name: 'Dynatrace List Settings Schemas',
|
||||
description:
|
||||
'List the settings schemas available in the environment. Use it to find the schema ID for a configuration type such as builtin:alerting.profile.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the settings.read scope',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated fields to include: schemaId, displayName, maturity, latestSchemaVersion, multiObject, ordered, ownerBasedAccessControl',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, '/settings/schemas', { fields: params.fields }),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const items = Array.isArray(data.items) ? (data.items as Array<Record<string, unknown>>) : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
schemas: items.map(mapSettingsSchema),
|
||||
totalCount: (data.totalCount as number) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
schemas: {
|
||||
type: 'array',
|
||||
description: 'Available settings schemas',
|
||||
items: { type: 'object', properties: settingsSchemaProperties },
|
||||
},
|
||||
totalCount: totalCountOutput,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { syntheticMonitorProperties } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceListSyntheticMonitorsParams,
|
||||
DynatraceListSyntheticMonitorsResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceV1Url,
|
||||
dynatraceHeaders,
|
||||
mapSyntheticMonitor,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listSyntheticMonitorsTool: ToolConfig<
|
||||
DynatraceListSyntheticMonitorsParams,
|
||||
DynatraceListSyntheticMonitorsResponse
|
||||
> = {
|
||||
id: 'dynatrace_list_synthetic_monitors',
|
||||
name: 'Dynatrace List Synthetic Monitors',
|
||||
description:
|
||||
'List synthetic monitors and their IDs. Use it to find the monitor IDs to feed into an on-demand execution.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace access token (dt0c01...) with one of ReadSyntheticData, DataExport, or ExternalSyntheticIntegration',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by monitor type: BROWSER or HTTP',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to enabled (true) or disabled (false) monitors',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to monitors assigned to a synthetic location',
|
||||
},
|
||||
tag: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by tag. Comma-separate to require several tags',
|
||||
},
|
||||
managementZone: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter to monitors in a management zone, by numeric zone ID',
|
||||
},
|
||||
},
|
||||
|
||||
/** Synthetic monitors are the one part of this integration still on Environment API v1. */
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceV1Url(params.environmentUrl, '/synthetic/monitors', {
|
||||
type: params.type,
|
||||
enabled: params.enabled,
|
||||
location: params.location,
|
||||
// `tag` is repeatable; the URL builder appends one entry per value.
|
||||
tag: params.tag
|
||||
? params.tag
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
: undefined,
|
||||
managementZone: params.managementZone,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const monitors = Array.isArray(data.monitors)
|
||||
? (data.monitors as Array<Record<string, unknown>>)
|
||||
: []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
monitors: monitors.map(mapSyntheticMonitor),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
monitors: {
|
||||
type: 'array',
|
||||
description: 'Matching synthetic monitors',
|
||||
items: { type: 'object', properties: syntheticMonitorProperties },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { tagProperties, totalCountOutput } from '@/tools/dynatrace/outputs'
|
||||
import type { DynatraceListTagsParams, DynatraceListTagsResponse } from '@/tools/dynatrace/types'
|
||||
import { buildDynatraceUrl, dynatraceHeaders, mapTags, readJsonBody } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listTagsTool: ToolConfig<DynatraceListTagsParams, DynatraceListTagsResponse> = {
|
||||
id: 'dynatrace_list_tags',
|
||||
name: 'Dynatrace List Tags',
|
||||
description: 'List the custom tags applied to the monitored entities an entity selector matches.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the entities.read scope',
|
||||
},
|
||||
entitySelector: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity selector for the entities to read tags from, e.g. type("HOST")',
|
||||
},
|
||||
from: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Start of the timeframe as UTC milliseconds, ISO 8601, or a relative expression. Defaults to now-24h',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the timeframe in the same formats as From. Defaults to now',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, '/tags', {
|
||||
entitySelector: params.entitySelector,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tags: mapTags(data.tags),
|
||||
totalCount: (data.totalCount as number) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Custom tags on the matched entities',
|
||||
items: { type: 'object', properties: tagProperties },
|
||||
},
|
||||
totalCount: totalCountOutput,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type {
|
||||
DynatraceMuteSecurityProblemParams,
|
||||
DynatraceMuteSecurityProblemResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const muteSecurityProblemTool: ToolConfig<
|
||||
DynatraceMuteSecurityProblemParams,
|
||||
DynatraceMuteSecurityProblemResponse
|
||||
> = {
|
||||
id: 'dynatrace_mute_security_problem',
|
||||
name: 'Dynatrace Mute Security Problem',
|
||||
description:
|
||||
'Mute a single Dynatrace vulnerability with a reason, for triaging false positives or accepted risk.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the securityProblems.write scope',
|
||||
},
|
||||
securityProblemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the security problem to mute',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'One of CONFIGURATION_NOT_AFFECTED, FALSE_POSITIVE, IGNORE, OTHER, VULNERABLE_CODE_NOT_IN_USE',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Explanation recorded alongside the mute',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/securityProblems/${encodeDynatraceId(params.securityProblemId)}/mute`
|
||||
),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const body: Record<string, string> = { reason: params.reason }
|
||||
if (params.comment) body.comment = params.comment
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
/** 200 carries the applied reason/comment; 204 means it was already muted. */
|
||||
transformResponse: async (response: Response, params?: DynatraceMuteSecurityProblemParams) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
securityProblemId: params?.securityProblemId ?? '',
|
||||
reason: (data.reason as string) ?? params?.reason ?? null,
|
||||
comment: (data.comment as string) ?? params?.comment ?? null,
|
||||
alreadyInState: response.status === 204,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
securityProblemId: { type: 'string', description: 'ID of the muted security problem' },
|
||||
reason: { type: 'string', description: 'Reason recorded for the mute', nullable: true },
|
||||
comment: { type: 'string', description: 'Comment recorded for the mute', nullable: true },
|
||||
alreadyInState: {
|
||||
type: 'boolean',
|
||||
description: 'True when Dynatrace reported the problem was already muted (HTTP 204)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { muteSummaryOutput } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceMuteSecurityProblemsParams,
|
||||
DynatraceMuteSecurityProblemsResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapMuteSummary,
|
||||
readJsonBody,
|
||||
toStringList,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const muteSecurityProblemsTool: ToolConfig<
|
||||
DynatraceMuteSecurityProblemsParams,
|
||||
DynatraceMuteSecurityProblemsResponse
|
||||
> = {
|
||||
id: 'dynatrace_mute_security_problems',
|
||||
name: 'Dynatrace Mute Security Problems',
|
||||
description:
|
||||
'Mute several Dynatrace vulnerabilities at once with a shared reason, for bulk triage.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the securityProblems.write scope',
|
||||
},
|
||||
securityProblemIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Security problem IDs to mute, comma-separated or as a JSON array',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'One of CONFIGURATION_NOT_AFFECTED, FALSE_POSITIVE, IGNORE, OTHER, VULNERABLE_CODE_NOT_IN_USE',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Explanation recorded against every muted problem',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildDynatraceUrl(params.environmentUrl, '/securityProblems/mute'),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const securityProblemIds = toStringList(params.securityProblemIds)
|
||||
if (securityProblemIds.length === 0) {
|
||||
throw new Error('securityProblemIds must contain at least one ID')
|
||||
}
|
||||
const body: Record<string, unknown> = { securityProblemIds, reason: params.reason }
|
||||
if (params.comment) body.comment = params.comment
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const summary = mapMuteSummary(data.summary)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
summary,
|
||||
changedCount: summary.filter((entry) => entry.muteStateChangeTriggered).length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
summary: muteSummaryOutput,
|
||||
changedCount: {
|
||||
type: 'number',
|
||||
description: 'How many problems actually changed state, excluding those already muted',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,5 +1,214 @@
|
||||
import type { OutputProperty } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Nested shapes below are transcribed from the Dynatrace API reference so that
|
||||
* downstream blocks can reference a field directly rather than digging through
|
||||
* an opaque blob. A bare `type: 'json'` is used ONLY where the payload is
|
||||
* genuinely dynamic — a schema-defined settings value, a type-dependent entity
|
||||
* property bag, user-supplied metadata, or a shape the reference names without
|
||||
* expanding. Each of those carries a description saying so.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A raw `EntityStub` as it appears NESTED inside a passthrough blob — still in
|
||||
* the API's `{ entityId: { id, type }, name }` form, unlike the top-level stubs
|
||||
* which `mapEntityStub` flattens.
|
||||
*/
|
||||
const rawEntityStubProperties: Record<string, OutputProperty> = {
|
||||
entityId: {
|
||||
type: 'json',
|
||||
description: 'Identifier of the entity',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Entity ID' },
|
||||
type: { type: 'string', description: 'Entity type' },
|
||||
},
|
||||
},
|
||||
name: { type: 'string', description: 'Entity display name' },
|
||||
}
|
||||
|
||||
/** A source-code location, as attached to attacks and code-level vulnerabilities. */
|
||||
const codeLocationProperties: Record<string, OutputProperty> = {
|
||||
className: { type: 'string', description: 'Class the code sits in', nullable: true },
|
||||
fileName: { type: 'string', description: 'Source file', nullable: true },
|
||||
functionName: { type: 'string', description: 'Function name', nullable: true },
|
||||
displayName: { type: 'string', description: 'Human-readable location' },
|
||||
lineNumber: { type: 'number', description: 'Line number', nullable: true },
|
||||
columnNumber: { type: 'number', description: 'Column number', nullable: true },
|
||||
returnType: { type: 'string', description: 'Return type', nullable: true },
|
||||
parameterTypes: {
|
||||
type: 'json',
|
||||
description: 'Parameter types, with a truncation marker when the list was cut short',
|
||||
},
|
||||
}
|
||||
|
||||
/** A vulnerable function reference. */
|
||||
const vulnerableFunctionProperties: Record<string, OutputProperty> = {
|
||||
className: { type: 'string', description: 'Class the function sits in' },
|
||||
filePath: { type: 'string', description: 'Path to the source file' },
|
||||
functionName: { type: 'string', description: 'Function name' },
|
||||
}
|
||||
|
||||
/** `EvidenceDetails` — root-cause evidence, present only when requested via Fields. */
|
||||
const evidenceDetailsProperties: Record<string, OutputProperty> = {
|
||||
totalCount: { type: 'number', description: 'Number of evidence entries' },
|
||||
details: {
|
||||
type: 'array',
|
||||
description: 'The evidence entries themselves',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
displayName: { type: 'string', description: 'Name of the evidence' },
|
||||
evidenceType: {
|
||||
type: 'string',
|
||||
description: 'AVAILABILITY_EVIDENCE, EVENT, MAINTENANCE_WINDOW, METRIC, or TRANSACTIONAL',
|
||||
},
|
||||
startTime: { type: 'number', description: 'Evidence start in UTC milliseconds' },
|
||||
rootCauseRelevant: {
|
||||
type: 'boolean',
|
||||
description: 'Whether Davis considered this evidence root-cause relevant',
|
||||
},
|
||||
entity: {
|
||||
type: 'json',
|
||||
description: 'Entity the evidence belongs to',
|
||||
properties: rawEntityStubProperties,
|
||||
},
|
||||
groupingEntity: {
|
||||
type: 'json',
|
||||
description: 'Entity the evidence is grouped under',
|
||||
properties: rawEntityStubProperties,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/** `ImpactAnalysis` — estimated user impact, present only when requested via Fields. */
|
||||
const impactAnalysisProperties: Record<string, OutputProperty> = {
|
||||
impacts: {
|
||||
type: 'array',
|
||||
description: 'One entry per impacted application, service, or mobile app',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
impactType: {
|
||||
type: 'string',
|
||||
description: 'APPLICATION, CUSTOM_APPLICATION, MOBILE, or SERVICE',
|
||||
},
|
||||
impactedEntity: {
|
||||
type: 'json',
|
||||
description: 'The impacted entity',
|
||||
properties: rawEntityStubProperties,
|
||||
},
|
||||
estimatedAffectedUsers: {
|
||||
type: 'number',
|
||||
description: 'Users Davis estimates were affected',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/** `CommentsList` — the paged comment envelope. */
|
||||
const commentsListProperties: Record<string, OutputProperty> = {
|
||||
totalCount: { type: 'number', description: 'Total comments on the problem' },
|
||||
pageSize: { type: 'number', description: 'Comments in this page' },
|
||||
nextPageKey: { type: 'string', description: 'Cursor for the next page', nullable: true },
|
||||
comments: {
|
||||
type: 'array',
|
||||
description: 'The comments themselves',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Comment ID' },
|
||||
authorName: { type: 'string', description: 'Author of the comment' },
|
||||
content: { type: 'string', description: 'Comment text' },
|
||||
context: { type: 'string', description: 'Context of the comment', nullable: true },
|
||||
createdAtTimestamp: { type: 'number', description: 'Created in UTC milliseconds' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/** Davis risk assessment of a vulnerability. */
|
||||
const riskAssessmentProperties: Record<string, OutputProperty> = {
|
||||
riskLevel: { type: 'string', description: 'CRITICAL, HIGH, MEDIUM, LOW, or NONE' },
|
||||
riskScore: { type: 'number', description: 'Davis risk score' },
|
||||
riskVector: { type: 'string', description: 'Risk vector string' },
|
||||
baseRiskLevel: { type: 'string', description: 'CVSS base risk level' },
|
||||
baseRiskScore: { type: 'number', description: 'CVSS base score' },
|
||||
baseRiskVector: { type: 'string', description: 'CVSS base vector' },
|
||||
exposure: { type: 'string', description: 'PUBLIC_NETWORK, NOT_DETECTED, or NOT_AVAILABLE' },
|
||||
dataAssets: { type: 'string', description: 'REACHABLE, NOT_DETECTED, or NOT_AVAILABLE' },
|
||||
publicExploit: { type: 'string', description: 'AVAILABLE or NOT_AVAILABLE' },
|
||||
vulnerableFunctionUsage: {
|
||||
type: 'string',
|
||||
description: 'IN_USE, NOT_IN_USE, or NOT_AVAILABLE',
|
||||
},
|
||||
assessmentAccuracy: { type: 'string', description: 'FULL, REDUCED, or NOT_AVAILABLE' },
|
||||
assessmentAccuracyDetails: {
|
||||
type: 'json',
|
||||
description: 'Why the assessment accuracy is reduced, as a reducedReasons array',
|
||||
},
|
||||
}
|
||||
|
||||
/** Environment-wide counts attached to a vulnerability. */
|
||||
const globalCountsProperties: Record<string, OutputProperty> = {
|
||||
affectedNodes: { type: 'number', description: 'Affected nodes' },
|
||||
affectedProcessGroups: { type: 'number', description: 'Affected process groups' },
|
||||
affectedProcessGroupInstances: {
|
||||
type: 'number',
|
||||
description: 'Affected process group instances',
|
||||
},
|
||||
exposedProcessGroups: { type: 'number', description: 'Publicly exposed process groups' },
|
||||
reachableDataAssets: { type: 'number', description: 'Reachable data assets' },
|
||||
relatedApplications: { type: 'number', description: 'Related applications' },
|
||||
relatedAttacks: { type: 'number', description: 'Related attacks' },
|
||||
relatedHosts: { type: 'number', description: 'Related hosts' },
|
||||
relatedKubernetesClusters: { type: 'number', description: 'Related Kubernetes clusters' },
|
||||
relatedKubernetesWorkloads: { type: 'number', description: 'Related Kubernetes workloads' },
|
||||
relatedServices: { type: 'number', description: 'Related services' },
|
||||
vulnerableComponents: { type: 'number', description: 'Vulnerable components' },
|
||||
}
|
||||
|
||||
/** Code-level vulnerability detail. */
|
||||
const codeLevelVulnerabilityDetailsProperties: Record<string, OutputProperty> = {
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'CMD_INJECTION, IMPROPER_INPUT_VALIDATION, SQL_INJECTION, or SSRF',
|
||||
},
|
||||
vulnerabilityLocation: { type: 'string', description: 'Where the vulnerability sits' },
|
||||
shortVulnerabilityLocation: { type: 'string', description: 'Shortened location' },
|
||||
vulnerableFunction: { type: 'string', description: 'The vulnerable function' },
|
||||
processGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Process groups carrying the vulnerability',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
processGroups: {
|
||||
type: 'array',
|
||||
description: 'Process group names',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
vulnerableFunctionInput: {
|
||||
type: 'json',
|
||||
description: 'What reached the vulnerable function, as a type plus tainted input segments',
|
||||
},
|
||||
}
|
||||
|
||||
/** A vulnerable component of a security problem or remediation item. */
|
||||
const vulnerableComponentProperties: Record<string, OutputProperty> = {
|
||||
id: { type: 'string', description: 'Component ID' },
|
||||
displayName: { type: 'string', description: 'Component display name' },
|
||||
shortName: { type: 'string', description: 'Short component name' },
|
||||
fileName: { type: 'string', description: 'File the component ships as' },
|
||||
numberOfAffectedEntities: { type: 'number', description: 'Entities affected by it' },
|
||||
affectedEntities: {
|
||||
type: 'array',
|
||||
description: 'IDs of the affected entities',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
}
|
||||
|
||||
/** Flattened `EntityStub` shape produced by `mapEntityStub`. */
|
||||
export const entityStubProperties: Record<string, OutputProperty> = {
|
||||
id: { type: 'string', description: 'Entity ID', nullable: true },
|
||||
@@ -103,16 +312,19 @@ export const problemProperties: Record<string, OutputProperty> = {
|
||||
type: 'json',
|
||||
description: 'Evidence behind the problem. Only present when requested via Fields',
|
||||
nullable: true,
|
||||
properties: evidenceDetailsProperties,
|
||||
},
|
||||
impactAnalysis: {
|
||||
type: 'json',
|
||||
description: 'Estimated user impact. Only present when requested via Fields',
|
||||
nullable: true,
|
||||
properties: impactAnalysisProperties,
|
||||
},
|
||||
recentComments: {
|
||||
type: 'json',
|
||||
description: 'Most recent comments. Only present when requested via Fields',
|
||||
nullable: true,
|
||||
properties: commentsListProperties,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -123,7 +335,11 @@ export const entityProperties: Record<string, OutputProperty> = {
|
||||
displayName: { type: 'string', description: 'Entity display name' },
|
||||
firstSeenTms: { type: 'number', description: 'First seen timestamp in UTC milliseconds' },
|
||||
lastSeenTms: { type: 'number', description: 'Last seen timestamp in UTC milliseconds' },
|
||||
properties: { type: 'json', description: 'Type-specific entity properties' },
|
||||
properties: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Entity properties. Keys depend on the entity type (a HOST and a SERVICE carry different ones), so the shape is dynamic',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags of the entity',
|
||||
@@ -134,9 +350,26 @@ export const entityProperties: Record<string, OutputProperty> = {
|
||||
description: 'Management zones of the entity',
|
||||
items: { type: 'object', properties: managementZoneProperties },
|
||||
},
|
||||
icon: { type: 'json', description: 'Icon of the entity', nullable: true },
|
||||
fromRelationships: { type: 'json', description: 'Relationships originating at this entity' },
|
||||
toRelationships: { type: 'json', description: 'Relationships pointing at this entity' },
|
||||
icon: {
|
||||
type: 'json',
|
||||
description: 'Icon of the entity',
|
||||
nullable: true,
|
||||
properties: {
|
||||
customIconPath: { type: 'string', description: 'Path to a custom icon', nullable: true },
|
||||
primaryIconType: { type: 'string', description: 'Primary icon type' },
|
||||
secondaryIconType: { type: 'string', description: 'Secondary icon type', nullable: true },
|
||||
},
|
||||
},
|
||||
fromRelationships: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Relationships originating at this entity, keyed by relationship name. Keys depend on the entity type',
|
||||
},
|
||||
toRelationships: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Relationships pointing at this entity, keyed by relationship name. Keys depend on the entity type',
|
||||
},
|
||||
}
|
||||
|
||||
/** Event shape produced by `mapEvent`. */
|
||||
@@ -201,9 +434,41 @@ export const metricDescriptorProperties: Record<string, OutputProperty> = {
|
||||
description: 'Supported aggregations',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
defaultAggregation: { type: 'json', description: 'Default aggregation', nullable: true },
|
||||
dimensionDefinitions: { type: 'json', description: 'Dimension definitions of the metric' },
|
||||
dimensionCardinalities: { type: 'json', description: 'Estimated dimension cardinalities' },
|
||||
defaultAggregation: {
|
||||
type: 'json',
|
||||
description: 'Default aggregation',
|
||||
nullable: true,
|
||||
properties: {
|
||||
type: { type: 'string', description: 'Aggregation type, e.g. avg or percentile' },
|
||||
parameter: { type: 'number', description: 'Aggregation parameter', nullable: true },
|
||||
},
|
||||
},
|
||||
dimensionDefinitions: {
|
||||
type: 'array',
|
||||
description: 'Dimension definitions of the metric',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: { type: 'string', description: 'Dimension key' },
|
||||
name: { type: 'string', description: 'Dimension name' },
|
||||
displayName: { type: 'string', description: 'Human-readable dimension name' },
|
||||
index: { type: 'number', description: 'Dimension index', nullable: true },
|
||||
type: { type: 'string', description: 'Dimension value type' },
|
||||
},
|
||||
},
|
||||
},
|
||||
dimensionCardinalities: {
|
||||
type: 'array',
|
||||
description: 'Estimated dimension cardinalities',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: { type: 'string', description: 'Dimension key' },
|
||||
estimate: { type: 'number', description: 'Estimated distinct values' },
|
||||
relative: { type: 'number', description: 'Cardinality relative to the metric' },
|
||||
},
|
||||
},
|
||||
},
|
||||
transformations: {
|
||||
type: 'array',
|
||||
description: 'Supported transformations',
|
||||
@@ -218,7 +483,12 @@ export const metricDescriptorProperties: Record<string, OutputProperty> = {
|
||||
maximumValue: { type: 'number', description: 'Largest allowed value', nullable: true },
|
||||
rootCauseRelevant: { type: 'boolean', description: 'Root-cause relevant', nullable: true },
|
||||
impactRelevant: { type: 'boolean', description: 'Impact relevant', nullable: true },
|
||||
metricValueType: { type: 'json', description: 'Value type of the metric', nullable: true },
|
||||
metricValueType: {
|
||||
type: 'json',
|
||||
description: 'Value type of the metric',
|
||||
nullable: true,
|
||||
properties: { type: { type: 'string', description: 'Value type, e.g. score or unknown' } },
|
||||
},
|
||||
latency: { type: 'number', description: 'Expected write latency in minutes', nullable: true },
|
||||
metricSelector: {
|
||||
type: 'string',
|
||||
@@ -249,7 +519,25 @@ export const sloProperties: Record<string, OutputProperty> = {
|
||||
status: { type: 'string', description: 'SLO status: SUCCESS, WARNING, or FAILURE' },
|
||||
error: { type: 'string', description: 'Error that prevented evaluation', nullable: true },
|
||||
errorBudget: { type: 'number', description: 'Remaining error budget', nullable: true },
|
||||
errorBudgetBurnRate: { type: 'json', description: 'Error budget burn rate', nullable: true },
|
||||
errorBudgetBurnRate: {
|
||||
type: 'json',
|
||||
description: 'Error budget burn rate',
|
||||
nullable: true,
|
||||
properties: {
|
||||
burnRateType: { type: 'string', description: 'FAST, SLOW, or NONE' },
|
||||
burnRateValue: { type: 'number', description: 'Current burn rate' },
|
||||
burnRateVisualizationEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the burn rate is shown on the SLO',
|
||||
},
|
||||
estimatedTimeToConsumeErrorBudget: {
|
||||
type: 'number',
|
||||
description: 'Hours until the error budget is exhausted at this rate',
|
||||
},
|
||||
fastBurnThreshold: { type: 'number', description: 'Threshold considered a fast burn' },
|
||||
sloValue: { type: 'number', description: 'SLO value the burn rate was computed from' },
|
||||
},
|
||||
},
|
||||
metricKey: { type: 'string', description: 'Metric key of the SLO', nullable: true },
|
||||
metricName: { type: 'string', description: 'Metric name of the SLO', nullable: true },
|
||||
metricExpression: { type: 'string', description: 'Metric expression', nullable: true },
|
||||
@@ -293,6 +581,7 @@ export const securityProblemProperties: Record<string, OutputProperty> = {
|
||||
type: 'json',
|
||||
description: 'Davis risk assessment. Only present when requested via Fields',
|
||||
nullable: true,
|
||||
properties: riskAssessmentProperties,
|
||||
},
|
||||
managementZones: {
|
||||
type: 'array',
|
||||
@@ -303,11 +592,13 @@ export const securityProblemProperties: Record<string, OutputProperty> = {
|
||||
type: 'json',
|
||||
description: 'Global affected-entity counts. Only present when requested via Fields',
|
||||
nullable: true,
|
||||
properties: globalCountsProperties,
|
||||
},
|
||||
codeLevelVulnerabilityDetails: {
|
||||
type: 'json',
|
||||
description: 'Code-level vulnerability details. Only present when requested via Fields',
|
||||
nullable: true,
|
||||
properties: codeLevelVulnerabilityDetailsProperties,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -340,17 +631,44 @@ export const securityProblemDetailsProperties: Record<string, OutputProperty> =
|
||||
description: 'IDs of entities with reachable data assets',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
vulnerableComponents: { type: 'json', description: 'Vulnerable components' },
|
||||
vulnerableComponents: {
|
||||
type: 'array',
|
||||
description: 'Vulnerable components',
|
||||
items: { type: 'object', properties: vulnerableComponentProperties },
|
||||
},
|
||||
filteredCounts: {
|
||||
type: 'json',
|
||||
description: 'Counts within the management zone filter',
|
||||
description:
|
||||
'Counts within the management zone filter. The API reference names this FilteredCountsDto without expanding it, so the fields are not enumerated here',
|
||||
nullable: true,
|
||||
},
|
||||
events: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Lifecycle events of the security problem. The reference names SecurityProblemEvent without expanding it',
|
||||
},
|
||||
entryPoints: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Entry points into the vulnerability. The reference names EntryPoints without expanding it',
|
||||
nullable: true,
|
||||
},
|
||||
relatedEntities: {
|
||||
type: 'json',
|
||||
description: 'Related entities. The reference names RelatedEntitiesList without expanding it',
|
||||
nullable: true,
|
||||
},
|
||||
relatedAttacks: {
|
||||
type: 'json',
|
||||
description: 'Related attacks. The reference names RelatedAttacksList without expanding it',
|
||||
nullable: true,
|
||||
},
|
||||
relatedContainerImages: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Related container images. The reference names RelatedContainerList without expanding it',
|
||||
nullable: true,
|
||||
},
|
||||
events: { type: 'json', description: 'Lifecycle events of the security problem' },
|
||||
entryPoints: { type: 'json', description: 'Entry points into the vulnerability', nullable: true },
|
||||
relatedEntities: { type: 'json', description: 'Related entities', nullable: true },
|
||||
relatedAttacks: { type: 'json', description: 'Related attacks', nullable: true },
|
||||
relatedContainerImages: { type: 'json', description: 'Related container images', nullable: true },
|
||||
}
|
||||
|
||||
/** Shared pagination outputs of the Environment API v2 list endpoints. */
|
||||
@@ -377,3 +695,350 @@ export const warningsOutput: OutputProperty = {
|
||||
description: 'Warnings returned alongside the result',
|
||||
items: { type: 'string' },
|
||||
}
|
||||
|
||||
/** Per-problem summary a batch mute/unmute returns. */
|
||||
export const muteSummaryOutput: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'One entry per requested security problem',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
securityProblemId: { type: 'string', description: 'Security problem ID' },
|
||||
muteStateChangeTriggered: {
|
||||
type: 'boolean',
|
||||
description: 'False when the problem was already in the requested state',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
description: 'ALREADY_MUTED or ALREADY_UNMUTED when no change was triggered',
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/** Remediation item shape produced by `mapRemediationItem`. */
|
||||
export const remediationItemProperties: Record<string, OutputProperty> = {
|
||||
id: { type: 'string', description: 'Remediation item ID' },
|
||||
name: { type: 'string', description: 'Name of the affected component' },
|
||||
entityIds: {
|
||||
type: 'array',
|
||||
description: 'Entities the remediation item covers',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
firstAffectedTimestamp: {
|
||||
type: 'number',
|
||||
description: 'First affected, in UTC milliseconds',
|
||||
nullable: true,
|
||||
},
|
||||
resolvedTimestamp: {
|
||||
type: 'number',
|
||||
description: 'Resolved, in UTC milliseconds',
|
||||
nullable: true,
|
||||
},
|
||||
vulnerabilityState: { type: 'string', description: 'VULNERABLE or RESOLVED' },
|
||||
assessment: {
|
||||
type: 'json',
|
||||
description: 'Exposure and reachability assessment',
|
||||
nullable: true,
|
||||
properties: {
|
||||
assessmentAccuracy: { type: 'string', description: 'FULL, REDUCED, or NOT_AVAILABLE' },
|
||||
dataAssets: { type: 'string', description: 'REACHABLE, NOT_DETECTED, or NOT_AVAILABLE' },
|
||||
exposure: { type: 'string', description: 'PUBLIC_NETWORK, NOT_DETECTED, or NOT_AVAILABLE' },
|
||||
numberOfDataAssets: { type: 'number', description: 'Reachable data assets' },
|
||||
vulnerableFunctionUsage: {
|
||||
type: 'string',
|
||||
description: 'IN_USE, NOT_IN_USE, or NOT_AVAILABLE',
|
||||
},
|
||||
vulnerableFunctionRestartRequired: {
|
||||
type: 'boolean',
|
||||
description: 'Whether a restart is needed to pick up the fix',
|
||||
},
|
||||
assessmentAccuracyDetails: {
|
||||
type: 'json',
|
||||
description: 'Why accuracy is reduced',
|
||||
properties: {
|
||||
reducedReasons: {
|
||||
type: 'array',
|
||||
description:
|
||||
'LIMITED_AGENT_SUPPORT, LIMITED_BY_CONFIGURATION, or LIMITED_BY_SERVICE_DETECTION_V2',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
vulnerableFunctionsInUse: {
|
||||
type: 'array',
|
||||
description: 'Vulnerable functions in use',
|
||||
items: { type: 'object', properties: vulnerableFunctionProperties },
|
||||
},
|
||||
vulnerableFunctionsNotInUse: {
|
||||
type: 'array',
|
||||
description: 'Vulnerable functions not in use',
|
||||
items: { type: 'object', properties: vulnerableFunctionProperties },
|
||||
},
|
||||
vulnerableFunctionsNotAvailable: {
|
||||
type: 'array',
|
||||
description: 'Vulnerable functions whose usage could not be determined',
|
||||
items: { type: 'object', properties: vulnerableFunctionProperties },
|
||||
},
|
||||
},
|
||||
},
|
||||
muteState: {
|
||||
type: 'json',
|
||||
description: 'Mute state, reason, and author',
|
||||
nullable: true,
|
||||
properties: {
|
||||
muted: { type: 'boolean', description: 'Whether the item is muted' },
|
||||
reason: {
|
||||
type: 'string',
|
||||
description:
|
||||
'AFFECTED, CONFIGURATION_NOT_AFFECTED, FALSE_POSITIVE, IGNORE, INITIAL_STATE, OTHER, or VULNERABLE_CODE_NOT_IN_USE',
|
||||
},
|
||||
comment: { type: 'string', description: 'Comment recorded with the mute', nullable: true },
|
||||
user: { type: 'string', description: 'Who set the mute state' },
|
||||
lastUpdatedTimestamp: { type: 'number', description: 'Last change in UTC milliseconds' },
|
||||
},
|
||||
},
|
||||
remediationProgress: {
|
||||
type: 'json',
|
||||
description: 'Affected and unaffected entities',
|
||||
nullable: true,
|
||||
properties: {
|
||||
affectedEntities: {
|
||||
type: 'array',
|
||||
description: 'Entities still affected',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
unaffectedEntities: {
|
||||
type: 'array',
|
||||
description: 'Entities already remediated',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
trackingLink: {
|
||||
type: 'json',
|
||||
description: 'External tracking link',
|
||||
nullable: true,
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Link to the tracking ticket' },
|
||||
displayName: { type: 'string', description: 'Label for the link' },
|
||||
user: { type: 'string', description: 'Who set the link' },
|
||||
lastUpdatedTimestamp: { type: 'number', description: 'Last change in UTC milliseconds' },
|
||||
},
|
||||
},
|
||||
vulnerableComponents: {
|
||||
type: 'array',
|
||||
description: 'Vulnerable components of the item',
|
||||
items: { type: 'object', properties: vulnerableComponentProperties },
|
||||
},
|
||||
}
|
||||
|
||||
/** Attack shape produced by `mapAttack`. */
|
||||
export const attackProperties: Record<string, OutputProperty> = {
|
||||
attackId: { type: 'string', description: 'Attack ID' },
|
||||
displayId: { type: 'string', description: 'Human-readable attack ID' },
|
||||
displayName: { type: 'string', description: 'Attack display name' },
|
||||
attackType: {
|
||||
type: 'string',
|
||||
description: 'COMMAND_INJECTION, JNDI_INJECTION, SQL_INJECTION, or SSRF',
|
||||
},
|
||||
state: { type: 'string', description: 'ALLOWLISTED, BLOCKED, or EXPLOITED' },
|
||||
technology: { type: 'string', description: 'DOTNET, GO, JAVA, or NODE_JS' },
|
||||
timestamp: { type: 'number', description: 'Occurrence time in UTC milliseconds' },
|
||||
attackTarget: {
|
||||
type: 'json',
|
||||
description: 'Targeted host or database',
|
||||
nullable: true,
|
||||
properties: {
|
||||
entityId: { type: 'string', description: 'ID of the targeted entity' },
|
||||
name: { type: 'string', description: 'Name of the targeted entity' },
|
||||
},
|
||||
},
|
||||
attacker: {
|
||||
type: 'json',
|
||||
description: 'Source IP and geo location',
|
||||
nullable: true,
|
||||
properties: {
|
||||
sourceIp: { type: 'string', description: 'Source IP of the attack' },
|
||||
location: {
|
||||
type: 'json',
|
||||
description: 'Geo location of the source IP',
|
||||
properties: {
|
||||
city: { type: 'string', description: 'City', nullable: true },
|
||||
country: { type: 'string', description: 'Country', nullable: true },
|
||||
countryCode: { type: 'string', description: 'ISO country code', nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
affectedEntities: {
|
||||
type: 'json',
|
||||
description: 'Affected process groups',
|
||||
nullable: true,
|
||||
properties: {
|
||||
processGroup: {
|
||||
type: 'json',
|
||||
description: 'Affected process group',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Process group ID' },
|
||||
name: { type: 'string', description: 'Process group name' },
|
||||
},
|
||||
},
|
||||
processGroupInstance: {
|
||||
type: 'json',
|
||||
description: 'Affected process group instance',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Process group instance ID' },
|
||||
name: { type: 'string', description: 'Process group instance name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entrypoint: {
|
||||
type: 'json',
|
||||
description: 'Entry point and payload',
|
||||
nullable: true,
|
||||
properties: {
|
||||
codeLocation: {
|
||||
type: 'json',
|
||||
description: 'Where in the code the attack entered',
|
||||
properties: codeLocationProperties,
|
||||
},
|
||||
entrypointFunction: {
|
||||
type: 'json',
|
||||
description: 'The entry-point function',
|
||||
properties: codeLocationProperties,
|
||||
},
|
||||
payload: {
|
||||
type: 'array',
|
||||
description: 'Payload values passed in',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Payload parameter name' },
|
||||
type: { type: 'string', description: 'Payload parameter type' },
|
||||
value: { type: 'string', description: 'Payload parameter value' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
type: 'json',
|
||||
description: 'The offending request',
|
||||
nullable: true,
|
||||
properties: {
|
||||
host: { type: 'string', description: 'Host the request hit' },
|
||||
path: { type: 'string', description: 'Request path' },
|
||||
url: { type: 'string', description: 'Full request URL' },
|
||||
protocolDetails: {
|
||||
type: 'json',
|
||||
description: 'Protocol-specific detail, including HTTP method, headers, and parameters',
|
||||
},
|
||||
},
|
||||
},
|
||||
securityProblem: {
|
||||
type: 'json',
|
||||
description: 'Related security problem',
|
||||
nullable: true,
|
||||
properties: {
|
||||
securityProblemId: { type: 'string', description: 'ID of the exploited security problem' },
|
||||
assessment: {
|
||||
type: 'json',
|
||||
description: 'Exposure assessment at the time of the attack',
|
||||
properties: {
|
||||
dataAssets: { type: 'string', description: 'Data asset reachability' },
|
||||
exposure: { type: 'string', description: 'Network exposure' },
|
||||
numberOfReachableDataAssets: {
|
||||
type: 'number',
|
||||
description: 'Reachable data assets',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
vulnerability: {
|
||||
type: 'json',
|
||||
description: 'Exploited vulnerability',
|
||||
nullable: true,
|
||||
properties: {
|
||||
vulnerabilityId: { type: 'string', description: 'ID of the vulnerability' },
|
||||
displayName: { type: 'string', description: 'Vulnerability display name' },
|
||||
codeLocation: {
|
||||
type: 'json',
|
||||
description: 'Where the vulnerability sits in the code',
|
||||
properties: codeLocationProperties,
|
||||
},
|
||||
vulnerableFunction: {
|
||||
type: 'json',
|
||||
description: 'The vulnerable function',
|
||||
properties: codeLocationProperties,
|
||||
},
|
||||
vulnerableFunctionInput: {
|
||||
type: 'json',
|
||||
description: 'The tainted input that reached the vulnerable function',
|
||||
},
|
||||
},
|
||||
},
|
||||
managementZones: {
|
||||
type: 'array',
|
||||
description: 'Management zones of the attack',
|
||||
items: { type: 'object', properties: managementZoneProperties },
|
||||
},
|
||||
}
|
||||
|
||||
/** Settings schema descriptor shape. */
|
||||
export const settingsSchemaProperties: Record<string, OutputProperty> = {
|
||||
schemaId: { type: 'string', description: 'Schema ID (e.g., builtin:alerting.profile)' },
|
||||
displayName: { type: 'string', description: 'Human-readable schema name', nullable: true },
|
||||
latestSchemaVersion: { type: 'string', description: 'Latest schema version', nullable: true },
|
||||
maturity: {
|
||||
type: 'string',
|
||||
description: 'GENERAL_AVAILABILITY, EARLY_ADOPTER, or PREVIEW',
|
||||
nullable: true,
|
||||
},
|
||||
multiObject: {
|
||||
type: 'boolean',
|
||||
description: 'Whether a scope may hold several objects of this schema',
|
||||
nullable: true,
|
||||
},
|
||||
ordered: { type: 'boolean', description: 'Whether objects are ordered', nullable: true },
|
||||
ownerBasedAccessControl: {
|
||||
type: 'boolean',
|
||||
description: 'Whether owner-based access control applies',
|
||||
nullable: true,
|
||||
},
|
||||
}
|
||||
|
||||
/** Settings object shape. */
|
||||
export const settingsObjectProperties: Record<string, OutputProperty> = {
|
||||
objectId: { type: 'string', description: 'Settings object ID' },
|
||||
schemaId: { type: 'string', description: 'Schema the object belongs to' },
|
||||
schemaVersion: { type: 'string', description: 'Schema version', nullable: true },
|
||||
scope: { type: 'string', description: 'Scope the object applies to' },
|
||||
value: {
|
||||
type: 'json',
|
||||
description:
|
||||
'The configuration itself. Its shape is defined by the object schema, so it is genuinely dynamic — read an existing object of the same schema to learn the fields',
|
||||
},
|
||||
author: { type: 'string', description: 'Who created the object', nullable: true },
|
||||
created: { type: 'number', description: 'Creation time in UTC milliseconds', nullable: true },
|
||||
modified: { type: 'number', description: 'Last change in UTC milliseconds', nullable: true },
|
||||
updateToken: {
|
||||
type: 'string',
|
||||
description: 'Optimistic-concurrency token to pass back on update or delete',
|
||||
nullable: true,
|
||||
},
|
||||
externalId: { type: 'string', description: 'External ID, if set', nullable: true },
|
||||
summary: { type: 'string', description: 'Short summary of the object', nullable: true },
|
||||
searchSummary: { type: 'string', description: 'Searchable summary', nullable: true },
|
||||
}
|
||||
|
||||
/** Synthetic monitor short representation. */
|
||||
export const syntheticMonitorProperties: Record<string, OutputProperty> = {
|
||||
entityId: { type: 'string', description: 'Monitor entity ID (e.g., SYNTHETIC_TEST-...)' },
|
||||
name: { type: 'string', description: 'Monitor name' },
|
||||
type: { type: 'string', description: 'BROWSER or HTTP' },
|
||||
enabled: { type: 'boolean', description: 'Whether the monitor is enabled' },
|
||||
}
|
||||
|
||||
@@ -136,6 +136,10 @@ export const queryMetricsTool: ToolConfig<
|
||||
type: 'json',
|
||||
description: 'DQL translation of the query, when available',
|
||||
nullable: true,
|
||||
properties: {
|
||||
status: { type: 'string', description: 'Whether the translation succeeded' },
|
||||
query: { type: 'string', description: 'The equivalent DQL query' },
|
||||
},
|
||||
},
|
||||
warnings: {
|
||||
type: 'array',
|
||||
|
||||
@@ -602,27 +602,470 @@ export interface DynatraceGetAuditLogsResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/** Outcome of one entry in a batch mute/unmute call. */
|
||||
export interface DynatraceMuteSummaryEntry {
|
||||
securityProblemId: string | null
|
||||
muteStateChangeTriggered: boolean | null
|
||||
reason: string | null
|
||||
}
|
||||
|
||||
/** A remediation item of a third-party vulnerability. */
|
||||
export interface DynatraceRemediationItem {
|
||||
id: string | null
|
||||
name: string | null
|
||||
entityIds: string[]
|
||||
firstAffectedTimestamp: number | null
|
||||
resolvedTimestamp: number | null
|
||||
vulnerabilityState: string | null
|
||||
assessment: Record<string, unknown> | null
|
||||
muteState: Record<string, unknown> | null
|
||||
remediationProgress: Record<string, unknown> | null
|
||||
trackingLink: Record<string, unknown> | null
|
||||
vulnerableComponents: Array<Record<string, unknown>>
|
||||
}
|
||||
|
||||
/** An Application Security attack. */
|
||||
export interface DynatraceAttack {
|
||||
attackId: string | null
|
||||
displayId: string | null
|
||||
displayName: string | null
|
||||
attackType: string | null
|
||||
state: string | null
|
||||
technology: string | null
|
||||
timestamp: number | null
|
||||
attackTarget: Record<string, unknown> | null
|
||||
attacker: Record<string, unknown> | null
|
||||
affectedEntities: Record<string, unknown> | null
|
||||
entrypoint: Record<string, unknown> | null
|
||||
request: Record<string, unknown> | null
|
||||
securityProblem: Record<string, unknown> | null
|
||||
vulnerability: Record<string, unknown> | null
|
||||
managementZones: DynatraceManagementZone[]
|
||||
}
|
||||
|
||||
/** A settings schema descriptor. */
|
||||
export interface DynatraceSettingsSchema {
|
||||
schemaId: string | null
|
||||
displayName: string | null
|
||||
latestSchemaVersion: string | null
|
||||
maturity: string | null
|
||||
multiObject: boolean | null
|
||||
ordered: boolean | null
|
||||
ownerBasedAccessControl: boolean | null
|
||||
}
|
||||
|
||||
/** A settings object. */
|
||||
export interface DynatraceSettingsObject {
|
||||
objectId: string | null
|
||||
schemaId: string | null
|
||||
schemaVersion: string | null
|
||||
scope: string | null
|
||||
value: Record<string, unknown> | null
|
||||
author: string | null
|
||||
created: number | null
|
||||
modified: number | null
|
||||
updateToken: string | null
|
||||
externalId: string | null
|
||||
summary: string | null
|
||||
searchSummary: string | null
|
||||
}
|
||||
|
||||
/** Result of a settings object write. */
|
||||
export interface DynatraceSettingsWriteResult {
|
||||
code: number | null
|
||||
objectId: string | null
|
||||
writeError: Record<string, unknown> | null
|
||||
invalidValue: unknown
|
||||
}
|
||||
|
||||
/** Short representation of a synthetic monitor. */
|
||||
export interface DynatraceSyntheticMonitor {
|
||||
entityId: string | null
|
||||
name: string | null
|
||||
type: string | null
|
||||
enabled: boolean | null
|
||||
}
|
||||
|
||||
export interface DynatraceMuteSecurityProblemParams extends DynatraceBaseParams {
|
||||
securityProblemId: string
|
||||
reason: string
|
||||
comment?: string
|
||||
}
|
||||
|
||||
export interface DynatraceMuteSecurityProblemResponse extends ToolResponse {
|
||||
output: {
|
||||
securityProblemId: string
|
||||
reason: string | null
|
||||
comment: string | null
|
||||
alreadyInState: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceMuteSecurityProblemsParams extends DynatraceBaseParams {
|
||||
securityProblemIds: string | string[]
|
||||
reason: string
|
||||
comment?: string
|
||||
}
|
||||
|
||||
export interface DynatraceMuteSecurityProblemsResponse extends ToolResponse {
|
||||
output: {
|
||||
summary: DynatraceMuteSummaryEntry[]
|
||||
changedCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceListRemediationItemsParams extends DynatraceBaseParams {
|
||||
securityProblemId: string
|
||||
remediationItemSelector?: string
|
||||
}
|
||||
|
||||
export interface DynatraceListRemediationItemsResponse extends ToolResponse {
|
||||
output: {
|
||||
remediationItems: DynatraceRemediationItem[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceListAttacksParams extends DynatraceBaseParams {
|
||||
attackSelector?: string
|
||||
from?: string
|
||||
to?: string
|
||||
fields?: string
|
||||
sort?: string
|
||||
pageSize?: number
|
||||
nextPageKey?: string
|
||||
}
|
||||
|
||||
export interface DynatraceListAttacksResponse extends ToolResponse {
|
||||
output: {
|
||||
attacks: DynatraceAttack[]
|
||||
totalCount: number | null
|
||||
pageSize: number | null
|
||||
nextPageKey: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceGetAttackParams extends DynatraceBaseParams {
|
||||
attackId: string
|
||||
fields?: string
|
||||
}
|
||||
|
||||
export interface DynatraceGetAttackResponse extends ToolResponse {
|
||||
output: {
|
||||
attack: DynatraceAttack
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceListTagsParams extends DynatraceBaseParams {
|
||||
entitySelector: string
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
|
||||
export interface DynatraceListTagsResponse extends ToolResponse {
|
||||
output: {
|
||||
tags: DynatraceTag[]
|
||||
totalCount: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceAddTagsParams extends DynatraceBaseParams {
|
||||
entitySelector: string
|
||||
tags: Array<{ key: string; value?: string }> | string
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
|
||||
export interface DynatraceAddTagsResponse extends ToolResponse {
|
||||
output: {
|
||||
appliedTags: DynatraceTag[]
|
||||
matchedEntitiesCount: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteTagParams extends DynatraceBaseParams {
|
||||
entitySelector: string
|
||||
key: string
|
||||
value?: string
|
||||
deleteAllWithKey?: boolean
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteTagResponse extends ToolResponse {
|
||||
output: {
|
||||
matchedEntitiesCount: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceListSettingsSchemasParams extends DynatraceBaseParams {
|
||||
fields?: string
|
||||
}
|
||||
|
||||
export interface DynatraceListSettingsSchemasResponse extends ToolResponse {
|
||||
output: {
|
||||
schemas: DynatraceSettingsSchema[]
|
||||
totalCount: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceListSettingsObjectsParams extends DynatraceBaseParams {
|
||||
schemaIds?: string
|
||||
scopes?: string
|
||||
externalIds?: string
|
||||
fields?: string
|
||||
filter?: string
|
||||
sort?: string
|
||||
pageSize?: number
|
||||
nextPageKey?: string
|
||||
}
|
||||
|
||||
export interface DynatraceListSettingsObjectsResponse extends ToolResponse {
|
||||
output: {
|
||||
items: DynatraceSettingsObject[]
|
||||
totalCount: number | null
|
||||
pageSize: number | null
|
||||
nextPageKey: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceGetSettingsObjectParams extends DynatraceBaseParams {
|
||||
objectId: string
|
||||
}
|
||||
|
||||
export interface DynatraceGetSettingsObjectResponse extends ToolResponse {
|
||||
output: {
|
||||
object: DynatraceSettingsObject
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceCreateSettingsObjectParams extends DynatraceBaseParams {
|
||||
schemaId: string
|
||||
scope: string
|
||||
value: Record<string, unknown> | string
|
||||
schemaVersion?: string
|
||||
externalId?: string
|
||||
validateOnly?: boolean
|
||||
}
|
||||
|
||||
export interface DynatraceCreateSettingsObjectResponse extends ToolResponse {
|
||||
output: {
|
||||
results: DynatraceSettingsWriteResult[]
|
||||
objectId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceUpdateSettingsObjectParams extends DynatraceBaseParams {
|
||||
objectId: string
|
||||
value: Record<string, unknown> | string
|
||||
schemaVersion?: string
|
||||
updateToken?: string
|
||||
validateOnly?: boolean
|
||||
}
|
||||
|
||||
export interface DynatraceUpdateSettingsObjectResponse extends ToolResponse {
|
||||
output: {
|
||||
objectId: string | null
|
||||
code: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteSettingsObjectParams extends DynatraceBaseParams {
|
||||
objectId: string
|
||||
updateToken?: string
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteSettingsObjectResponse extends ToolResponse {
|
||||
output: {
|
||||
objectId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceListSyntheticMonitorsParams extends DynatraceBaseParams {
|
||||
type?: string
|
||||
enabled?: boolean
|
||||
location?: string
|
||||
tag?: string
|
||||
managementZone?: number
|
||||
}
|
||||
|
||||
export interface DynatraceListSyntheticMonitorsResponse extends ToolResponse {
|
||||
output: {
|
||||
monitors: DynatraceSyntheticMonitor[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceExecuteSyntheticMonitorsParams extends DynatraceBaseParams {
|
||||
monitors: Array<Record<string, unknown>> | string
|
||||
processingMode?: string
|
||||
failOnPerformanceIssue?: boolean
|
||||
stopOnProblem?: boolean
|
||||
takeScreenshotsOnSuccess?: boolean
|
||||
metadata?: Record<string, string> | string
|
||||
}
|
||||
|
||||
export interface DynatraceExecuteSyntheticMonitorsResponse extends ToolResponse {
|
||||
output: {
|
||||
batchId: string | null
|
||||
triggeredCount: number | null
|
||||
triggeringProblemsCount: number | null
|
||||
triggered: Array<Record<string, unknown>>
|
||||
triggeringProblemsDetails: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceGetSyntheticBatchParams extends DynatraceBaseParams {
|
||||
batchId: string
|
||||
}
|
||||
|
||||
export interface DynatraceGetSyntheticBatchResponse extends ToolResponse {
|
||||
output: {
|
||||
batchId: string | null
|
||||
batchStatus: string | null
|
||||
executedCount: number | null
|
||||
failedCount: number | null
|
||||
failedToExecuteCount: number | null
|
||||
triggeredCount: number | null
|
||||
triggeringProblemsCount: number | null
|
||||
failedExecutions: Array<Record<string, unknown>>
|
||||
failedToExecute: Array<Record<string, unknown>>
|
||||
triggeringProblems: Array<Record<string, unknown>>
|
||||
metadata: Record<string, unknown>
|
||||
userId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceGetProblemCommentParams extends DynatraceBaseParams {
|
||||
problemId: string
|
||||
commentId: string
|
||||
}
|
||||
|
||||
export interface DynatraceGetProblemCommentResponse extends ToolResponse {
|
||||
output: {
|
||||
comment: DynatraceComment
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceUpdateProblemCommentParams extends DynatraceBaseParams {
|
||||
problemId: string
|
||||
commentId: string
|
||||
message: string
|
||||
context?: string
|
||||
}
|
||||
|
||||
export interface DynatraceUpdateProblemCommentResponse extends ToolResponse {
|
||||
output: {
|
||||
problemId: string
|
||||
commentId: string
|
||||
message: string
|
||||
context: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteProblemCommentParams extends DynatraceBaseParams {
|
||||
problemId: string
|
||||
commentId: string
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteProblemCommentResponse extends ToolResponse {
|
||||
output: {
|
||||
problemId: string
|
||||
commentId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/** Fields shared by the SLO create and update payloads. */
|
||||
export interface DynatraceSloWriteFields {
|
||||
name: string
|
||||
target: number
|
||||
warning: number
|
||||
timeframe: string
|
||||
evaluationType: string
|
||||
description?: string
|
||||
enabled?: boolean
|
||||
filter?: string
|
||||
metricExpression?: string
|
||||
metricName?: string
|
||||
burnRateVisualizationEnabled?: boolean
|
||||
fastBurnThreshold?: number
|
||||
}
|
||||
|
||||
export interface DynatraceCreateSloParams extends DynatraceBaseParams, DynatraceSloWriteFields {}
|
||||
|
||||
export interface DynatraceCreateSloResponse extends ToolResponse {
|
||||
output: {
|
||||
sloId: string | null
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceUpdateSloParams extends DynatraceBaseParams, DynatraceSloWriteFields {
|
||||
sloId: string
|
||||
}
|
||||
|
||||
export interface DynatraceUpdateSloResponse extends ToolResponse {
|
||||
output: {
|
||||
sloId: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteSloParams extends DynatraceBaseParams {
|
||||
sloId: string
|
||||
}
|
||||
|
||||
export interface DynatraceDeleteSloResponse extends ToolResponse {
|
||||
output: {
|
||||
sloId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/** Union of every Dynatrace tool response, used as the block's response type. */
|
||||
export type DynatraceResponse =
|
||||
| DynatraceListProblemsResponse
|
||||
| DynatraceGetProblemResponse
|
||||
| DynatraceCloseProblemResponse
|
||||
| DynatraceListProblemCommentsResponse
|
||||
| DynatraceAddProblemCommentResponse
|
||||
| DynatraceQueryMetricsResponse
|
||||
| DynatraceListMetricsResponse
|
||||
| DynatraceGetMetricResponse
|
||||
| DynatraceIngestMetricsResponse
|
||||
| DynatraceListEntitiesResponse
|
||||
| DynatraceAddTagsResponse
|
||||
| DynatraceCloseProblemResponse
|
||||
| DynatraceCreateSettingsObjectResponse
|
||||
| DynatraceCreateSloResponse
|
||||
| DynatraceDeleteProblemCommentResponse
|
||||
| DynatraceDeleteSettingsObjectResponse
|
||||
| DynatraceDeleteSloResponse
|
||||
| DynatraceDeleteTagResponse
|
||||
| DynatraceExecuteSyntheticMonitorsResponse
|
||||
| DynatraceGetAttackResponse
|
||||
| DynatraceGetAuditLogsResponse
|
||||
| DynatraceGetEntityResponse
|
||||
| DynatraceGetEventResponse
|
||||
| DynatraceGetMetricResponse
|
||||
| DynatraceGetProblemCommentResponse
|
||||
| DynatraceGetProblemResponse
|
||||
| DynatraceGetSecurityProblemResponse
|
||||
| DynatraceGetSettingsObjectResponse
|
||||
| DynatraceGetSloResponse
|
||||
| DynatraceGetSyntheticBatchResponse
|
||||
| DynatraceIngestEventResponse
|
||||
| DynatraceIngestLogsResponse
|
||||
| DynatraceIngestMetricsResponse
|
||||
| DynatraceListAttacksResponse
|
||||
| DynatraceListEntitiesResponse
|
||||
| DynatraceListEntityTypesResponse
|
||||
| DynatraceListEventsResponse
|
||||
| DynatraceGetEventResponse
|
||||
| DynatraceIngestEventResponse
|
||||
| DynatraceSearchLogsResponse
|
||||
| DynatraceIngestLogsResponse
|
||||
| DynatraceListSlosResponse
|
||||
| DynatraceGetSloResponse
|
||||
| DynatraceListMetricsResponse
|
||||
| DynatraceListProblemCommentsResponse
|
||||
| DynatraceListProblemsResponse
|
||||
| DynatraceListRemediationItemsResponse
|
||||
| DynatraceListSecurityProblemsResponse
|
||||
| DynatraceGetSecurityProblemResponse
|
||||
| DynatraceGetAuditLogsResponse
|
||||
| DynatraceListSettingsObjectsResponse
|
||||
| DynatraceListSettingsSchemasResponse
|
||||
| DynatraceListSlosResponse
|
||||
| DynatraceListSyntheticMonitorsResponse
|
||||
| DynatraceListTagsResponse
|
||||
| DynatraceMuteSecurityProblemResponse
|
||||
| DynatraceMuteSecurityProblemsResponse
|
||||
| DynatraceQueryMetricsResponse
|
||||
| DynatraceSearchLogsResponse
|
||||
| DynatraceUpdateProblemCommentResponse
|
||||
| DynatraceUpdateSettingsObjectResponse
|
||||
| DynatraceUpdateSloResponse
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
DynatraceMuteSecurityProblemParams,
|
||||
DynatraceMuteSecurityProblemResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const unmuteSecurityProblemTool: ToolConfig<
|
||||
DynatraceMuteSecurityProblemParams,
|
||||
DynatraceMuteSecurityProblemResponse
|
||||
> = {
|
||||
id: 'dynatrace_unmute_security_problem',
|
||||
name: 'Dynatrace Unmute Security Problem',
|
||||
description: 'Unmute a single Dynatrace vulnerability, returning it to the active triage queue.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the securityProblems.write scope',
|
||||
},
|
||||
securityProblemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the security problem to unmute',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
default: 'AFFECTED',
|
||||
description: 'Reason for unmuting. AFFECTED is the only value the API accepts',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Explanation recorded alongside the unmute',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/securityProblems/${encodeDynatraceId(params.securityProblemId)}/unmute`
|
||||
),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const body: Record<string, string> = { reason: params.reason || 'AFFECTED' }
|
||||
if (params.comment) body.comment = params.comment
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
/** 200 on success; 204 means it was already unmuted. */
|
||||
transformResponse: async (response: Response, params?: DynatraceMuteSecurityProblemParams) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
securityProblemId: params?.securityProblemId ?? '',
|
||||
reason: (data.reason as string) ?? params?.reason ?? 'AFFECTED',
|
||||
comment: (data.comment as string) ?? params?.comment ?? null,
|
||||
alreadyInState: response.status === 204,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
securityProblemId: { type: 'string', description: 'ID of the unmuted security problem' },
|
||||
reason: { type: 'string', description: 'Reason recorded for the unmute', nullable: true },
|
||||
comment: { type: 'string', description: 'Comment recorded for the unmute', nullable: true },
|
||||
alreadyInState: {
|
||||
type: 'boolean',
|
||||
description: 'True when Dynatrace reported the problem was already unmuted (HTTP 204)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { muteSummaryOutput } from '@/tools/dynatrace/outputs'
|
||||
import type {
|
||||
DynatraceMuteSecurityProblemsParams,
|
||||
DynatraceMuteSecurityProblemsResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
mapMuteSummary,
|
||||
readJsonBody,
|
||||
toStringList,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const unmuteSecurityProblemsTool: ToolConfig<
|
||||
DynatraceMuteSecurityProblemsParams,
|
||||
DynatraceMuteSecurityProblemsResponse
|
||||
> = {
|
||||
id: 'dynatrace_unmute_security_problems',
|
||||
name: 'Dynatrace Unmute Security Problems',
|
||||
description: 'Unmute several Dynatrace vulnerabilities at once, returning them to active triage.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the securityProblems.write scope',
|
||||
},
|
||||
securityProblemIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Security problem IDs to unmute, comma-separated or as a JSON array',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
default: 'AFFECTED',
|
||||
description: 'Reason for unmuting. AFFECTED is the only value the API accepts',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Explanation recorded against every unmuted problem',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildDynatraceUrl(params.environmentUrl, '/securityProblems/unmute'),
|
||||
method: 'POST',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const securityProblemIds = toStringList(params.securityProblemIds)
|
||||
if (securityProblemIds.length === 0) {
|
||||
throw new Error('securityProblemIds must contain at least one ID')
|
||||
}
|
||||
const body: Record<string, unknown> = {
|
||||
securityProblemIds,
|
||||
reason: params.reason || 'AFFECTED',
|
||||
}
|
||||
if (params.comment) body.comment = params.comment
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await readJsonBody(response)
|
||||
const summary = mapMuteSummary(data.summary)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
summary,
|
||||
changedCount: summary.filter((entry) => entry.muteStateChangeTriggered).length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
summary: muteSummaryOutput,
|
||||
changedCount: {
|
||||
type: 'number',
|
||||
description: 'How many problems actually changed state, excluding those already unmuted',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import type {
|
||||
DynatraceUpdateProblemCommentParams,
|
||||
DynatraceUpdateProblemCommentResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import { buildDynatraceUrl, dynatraceHeaders, encodeDynatraceId } from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateProblemCommentTool: ToolConfig<
|
||||
DynatraceUpdateProblemCommentParams,
|
||||
DynatraceUpdateProblemCommentResponse
|
||||
> = {
|
||||
id: 'dynatrace_update_problem_comment',
|
||||
name: 'Dynatrace Update Problem Comment',
|
||||
description: 'Replace the text of an existing comment on a Dynatrace problem.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the problems.write scope',
|
||||
},
|
||||
problemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the problem the comment belongs to',
|
||||
},
|
||||
commentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the comment to update',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Replacement text of the comment',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Context of the comment, shown alongside the author',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/problems/${encodeDynatraceId(params.problemId)}/comments/${encodeDynatraceId(
|
||||
params.commentId
|
||||
)}`
|
||||
),
|
||||
method: 'PUT',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const body: Record<string, string> = { message: params.message }
|
||||
if (params.context) body.context = params.context
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
/** The endpoint answers 204 with no body, so the update is echoed back. */
|
||||
transformResponse: async (_response: Response, params?: DynatraceUpdateProblemCommentParams) => ({
|
||||
success: true,
|
||||
output: {
|
||||
problemId: params?.problemId ?? '',
|
||||
commentId: params?.commentId ?? '',
|
||||
message: params?.message ?? '',
|
||||
context: params?.context ?? null,
|
||||
},
|
||||
}),
|
||||
|
||||
outputs: {
|
||||
problemId: { type: 'string', description: 'ID of the problem' },
|
||||
commentId: { type: 'string', description: 'ID of the updated comment' },
|
||||
message: { type: 'string', description: 'Text the comment now carries' },
|
||||
context: { type: 'string', description: 'Context of the comment', nullable: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import type {
|
||||
DynatraceUpdateSettingsObjectParams,
|
||||
DynatraceUpdateSettingsObjectResponse,
|
||||
} from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
parseJsonParam,
|
||||
readJsonBody,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateSettingsObjectTool: ToolConfig<
|
||||
DynatraceUpdateSettingsObjectParams,
|
||||
DynatraceUpdateSettingsObjectResponse
|
||||
> = {
|
||||
id: 'dynatrace_update_settings_object',
|
||||
name: 'Dynatrace Update Settings Object',
|
||||
description:
|
||||
'Update an existing settings object. Pass the update token from Get Settings Object to fail rather than overwrite a concurrent change.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the settings.write scope',
|
||||
},
|
||||
objectId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the settings object to update',
|
||||
},
|
||||
value: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The full replacement configuration. Its shape is defined by the object schema — this replaces the value rather than merging into it',
|
||||
},
|
||||
schemaVersion: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Schema version to validate against',
|
||||
},
|
||||
updateToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Token from Get Settings Object. When set, the update fails if the object changed in the meantime. Omit it to overwrite unconditionally',
|
||||
},
|
||||
validateOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Validate the payload without saving anything',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(
|
||||
params.environmentUrl,
|
||||
`/settings/objects/${encodeDynatraceId(params.objectId)}`,
|
||||
{ validateOnly: params.validateOnly }
|
||||
),
|
||||
method: 'PUT',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => {
|
||||
const value = parseJsonParam(params.value)
|
||||
if (value === undefined) throw new Error('value is required')
|
||||
const body: Record<string, unknown> = { value }
|
||||
if (params.schemaVersion) body.schemaVersion = params.schemaVersion
|
||||
if (params.updateToken) body.updateToken = params.updateToken
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: DynatraceUpdateSettingsObjectParams) => {
|
||||
const data = await readJsonBody(response)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
objectId: (data.objectId as string) ?? params?.objectId ?? null,
|
||||
code: (data.code as number) ?? response.status,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
objectId: { type: 'string', description: 'ID of the updated object', nullable: true },
|
||||
code: { type: 'number', description: 'Status Dynatrace reported for the update' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { DynatraceUpdateSloParams, DynatraceUpdateSloResponse } from '@/tools/dynatrace/types'
|
||||
import {
|
||||
buildDynatraceUrl,
|
||||
buildSloBody,
|
||||
dynatraceHeaders,
|
||||
encodeDynatraceId,
|
||||
} from '@/tools/dynatrace/utils'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateSloTool: ToolConfig<DynatraceUpdateSloParams, DynatraceUpdateSloResponse> = {
|
||||
id: 'dynatrace_update_slo',
|
||||
name: 'Dynatrace Update SLO',
|
||||
description:
|
||||
'Update an existing service-level objective. Every field is replaced, so send the complete definition.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.DYNATRACE_ERRORS,
|
||||
|
||||
params: {
|
||||
environmentUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Dynatrace environment URL (e.g., https://abc12345.live.dynatrace.com, or https://your-activegate:9999/e/abc12345 for Managed)',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dynatrace access token (dt0c01...) with the slo.write scope',
|
||||
},
|
||||
sloId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the SLO to update',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the SLO',
|
||||
},
|
||||
target: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Target success rate as a percentage, e.g. 99.5',
|
||||
},
|
||||
warning: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Warning threshold as a percentage. Must sit above the target, e.g. 99.8',
|
||||
},
|
||||
timeframe: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Evaluation timeframe in Dynatrace notation, e.g. -1d, -1w, or now-30d',
|
||||
},
|
||||
evaluationType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
default: 'AGGREGATE',
|
||||
description: 'Evaluation type. AGGREGATE is the only value the API accepts',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the SLO',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the SLO is evaluated. Dynatrace defaults it to false',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Entity filter scoping the SLO, e.g. type("SERVICE"),tag("env:prod")',
|
||||
},
|
||||
metricExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Metric expression the SLO evaluates, e.g. (100)*(builtin:service.errors.total.successCount:splitBy())/(builtin:service.requestCount.total:splitBy())',
|
||||
},
|
||||
metricName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Display name for the SLO metric',
|
||||
},
|
||||
burnRateVisualizationEnabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Show the error-budget burn rate on the SLO',
|
||||
},
|
||||
fastBurnThreshold: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Burn rate above which the SLO is considered fast-burning',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
buildDynatraceUrl(params.environmentUrl, `/slo/${encodeDynatraceId(params.sloId)}`),
|
||||
method: 'PUT',
|
||||
headers: (params) => dynatraceHeaders(params.apiToken),
|
||||
body: (params) => buildSloBody(params),
|
||||
},
|
||||
|
||||
/** Answers 200 with no documented body, so the updated identity is echoed back. */
|
||||
transformResponse: async (_response: Response, params?: DynatraceUpdateSloParams) => ({
|
||||
success: true,
|
||||
output: {
|
||||
sloId: params?.sloId ?? '',
|
||||
name: params?.name ?? '',
|
||||
},
|
||||
}),
|
||||
|
||||
outputs: {
|
||||
sloId: { type: 'string', description: 'ID of the updated SLO' },
|
||||
name: { type: 'string', description: 'Name the SLO now carries' },
|
||||
},
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { truncate } from '@sim/utils/string'
|
||||
import type {
|
||||
DynatraceAttack,
|
||||
DynatraceAuditLog,
|
||||
DynatraceComment,
|
||||
DynatraceEntity,
|
||||
@@ -10,15 +11,54 @@ import type {
|
||||
DynatraceManagementZone,
|
||||
DynatraceMetricDescriptor,
|
||||
DynatraceMetricResult,
|
||||
DynatraceMuteSummaryEntry,
|
||||
DynatraceProblem,
|
||||
DynatraceProblemFilter,
|
||||
DynatraceRemediationItem,
|
||||
DynatraceSecurityProblem,
|
||||
DynatraceSecurityProblemDetails,
|
||||
DynatraceSettingsObject,
|
||||
DynatraceSettingsSchema,
|
||||
DynatraceSettingsWriteResult,
|
||||
DynatraceSlo,
|
||||
DynatraceSloWriteFields,
|
||||
DynatraceSyntheticMonitor,
|
||||
DynatraceTag,
|
||||
} from '@/tools/dynatrace/types'
|
||||
|
||||
type QueryValue = string | number | boolean | undefined | null
|
||||
type QueryScalar = string | number | boolean | undefined | null
|
||||
type QueryValue = QueryScalar | QueryScalar[]
|
||||
|
||||
/** Strips a trailing slash and a trailing `/api/v1` or `/api/v2` from the environment URL. */
|
||||
function normalizeEnvironmentUrl(environmentUrl: string): string {
|
||||
return environmentUrl
|
||||
.trim()
|
||||
.replace(/\/+$/, '')
|
||||
.replace(/\/api\/v[12]$/, '')
|
||||
}
|
||||
|
||||
function buildUrl(
|
||||
environmentUrl: string,
|
||||
apiPrefix: string,
|
||||
path: string,
|
||||
query?: Record<string, QueryValue>
|
||||
): string {
|
||||
const search = new URLSearchParams()
|
||||
|
||||
for (const [key, value] of Object.entries(query ?? {})) {
|
||||
// Repeatable params (e.g. Synthetic's `tag`) append once per entry.
|
||||
const values = Array.isArray(value) ? value : [value]
|
||||
for (const entry of values) {
|
||||
if (entry === undefined || entry === null || entry === '') continue
|
||||
search.append(key, String(entry))
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = search.toString()
|
||||
return `${normalizeEnvironmentUrl(environmentUrl)}${apiPrefix}${path}${
|
||||
queryString ? `?${queryString}` : ''
|
||||
}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an absolute Environment API v2 URL. Tolerates a trailing slash and a
|
||||
@@ -30,19 +70,19 @@ export function buildDynatraceUrl(
|
||||
path: string,
|
||||
query?: Record<string, QueryValue>
|
||||
): string {
|
||||
const base = environmentUrl
|
||||
.trim()
|
||||
.replace(/\/+$/, '')
|
||||
.replace(/\/api\/v2$/, '')
|
||||
const search = new URLSearchParams()
|
||||
return buildUrl(environmentUrl, '/api/v2', path, query)
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(query ?? {})) {
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
search.set(key, String(value))
|
||||
}
|
||||
|
||||
const queryString = search.toString()
|
||||
return `${base}/api/v2${path}${queryString ? `?${queryString}` : ''}`
|
||||
/**
|
||||
* Builds an absolute Environment API **v1** URL. Only the Synthetic monitors
|
||||
* endpoints still live on v1 — everything else in this integration is v2.
|
||||
*/
|
||||
export function buildDynatraceV1Url(
|
||||
environmentUrl: string,
|
||||
path: string,
|
||||
query?: Record<string, QueryValue>
|
||||
): string {
|
||||
return buildUrl(environmentUrl, '/api/v1', path, query)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -445,6 +485,164 @@ export function mapLogRecord(value: Record<string, unknown>): DynatraceLogRecord
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a list param that may arrive as a real array, a JSON array string,
|
||||
* or a comma-separated string.
|
||||
*/
|
||||
export function toStringList(value: unknown): string[] {
|
||||
if (Array.isArray(value)) return value.map((entry) => String(entry).trim()).filter(Boolean)
|
||||
if (typeof value !== 'string') return []
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed) return []
|
||||
if (trimmed.startsWith('[')) {
|
||||
const parsed = parseJsonParam(trimmed)
|
||||
if (Array.isArray(parsed)) return parsed.map((entry) => String(entry).trim()).filter(Boolean)
|
||||
}
|
||||
return trimmed
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
/** Maps the per-problem summary a batch mute/unmute returns. */
|
||||
export function mapMuteSummary(value: unknown): DynatraceMuteSummaryEntry[] {
|
||||
return toRecordArray(value).map((entry) => ({
|
||||
securityProblemId: (entry.securityProblemId as string) ?? null,
|
||||
muteStateChangeTriggered: (entry.muteStateChangeTriggered as boolean) ?? null,
|
||||
reason: (entry.reason as string) ?? null,
|
||||
}))
|
||||
}
|
||||
|
||||
/** Maps a remediation item of a third-party vulnerability. */
|
||||
export function mapRemediationItem(value: Record<string, unknown>): DynatraceRemediationItem {
|
||||
return {
|
||||
id: (value.id as string) ?? null,
|
||||
name: (value.name as string) ?? null,
|
||||
entityIds: toStringArray(value.entityIds),
|
||||
firstAffectedTimestamp: (value.firstAffectedTimestamp as number) ?? null,
|
||||
resolvedTimestamp: (value.resolvedTimestamp as number) ?? null,
|
||||
vulnerabilityState: (value.vulnerabilityState as string) ?? null,
|
||||
assessment: toRecordOrNull(value.assessment),
|
||||
muteState: toRecordOrNull(value.muteState),
|
||||
remediationProgress: toRecordOrNull(value.remediationProgress),
|
||||
trackingLink: toRecordOrNull(value.trackingLink),
|
||||
vulnerableComponents: toRecordArray(value.vulnerableComponents),
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps an Application Security attack. */
|
||||
export function mapAttack(value: Record<string, unknown>): DynatraceAttack {
|
||||
return {
|
||||
attackId: (value.attackId as string) ?? null,
|
||||
displayId: (value.displayId as string) ?? null,
|
||||
displayName: (value.displayName as string) ?? null,
|
||||
attackType: (value.attackType as string) ?? null,
|
||||
state: (value.state as string) ?? null,
|
||||
technology: (value.technology as string) ?? null,
|
||||
timestamp: (value.timestamp as number) ?? null,
|
||||
attackTarget: toRecordOrNull(value.attackTarget),
|
||||
attacker: toRecordOrNull(value.attacker),
|
||||
affectedEntities: toRecordOrNull(value.affectedEntities),
|
||||
entrypoint: toRecordOrNull(value.entrypoint),
|
||||
request: toRecordOrNull(value.request),
|
||||
securityProblem: toRecordOrNull(value.securityProblem),
|
||||
vulnerability: toRecordOrNull(value.vulnerability),
|
||||
managementZones: mapManagementZones(value.managementZones),
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps a settings schema descriptor. */
|
||||
export function mapSettingsSchema(value: Record<string, unknown>): DynatraceSettingsSchema {
|
||||
return {
|
||||
schemaId: (value.schemaId as string) ?? null,
|
||||
displayName: (value.displayName as string) ?? null,
|
||||
latestSchemaVersion: (value.latestSchemaVersion as string) ?? null,
|
||||
maturity: (value.maturity as string) ?? null,
|
||||
multiObject: (value.multiObject as boolean) ?? null,
|
||||
ordered: (value.ordered as boolean) ?? null,
|
||||
ownerBasedAccessControl: (value.ownerBasedAccessControl as boolean) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps a settings object. */
|
||||
export function mapSettingsObject(value: Record<string, unknown>): DynatraceSettingsObject {
|
||||
return {
|
||||
objectId: (value.objectId as string) ?? null,
|
||||
schemaId: (value.schemaId as string) ?? null,
|
||||
schemaVersion: (value.schemaVersion as string) ?? null,
|
||||
scope: (value.scope as string) ?? null,
|
||||
value: toRecordOrNull(value.value),
|
||||
author: (value.author as string) ?? null,
|
||||
created: (value.created as number) ?? null,
|
||||
modified: (value.modified as number) ?? null,
|
||||
updateToken: (value.updateToken as string) ?? null,
|
||||
externalId: (value.externalId as string) ?? null,
|
||||
summary: (value.summary as string) ?? null,
|
||||
searchSummary: (value.searchSummary as string) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps the per-object result a settings write returns. */
|
||||
export function mapSettingsWriteResult(
|
||||
value: Record<string, unknown>
|
||||
): DynatraceSettingsWriteResult {
|
||||
return {
|
||||
code: (value.code as number) ?? null,
|
||||
objectId: (value.objectId as string) ?? null,
|
||||
writeError: toRecordOrNull(value.error),
|
||||
invalidValue: value.invalidValue,
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps a short synthetic monitor representation. */
|
||||
export function mapSyntheticMonitor(value: Record<string, unknown>): DynatraceSyntheticMonitor {
|
||||
return {
|
||||
entityId: (value.entityId as string) ?? null,
|
||||
name: (value.name as string) ?? null,
|
||||
type: (value.type as string) ?? null,
|
||||
enabled: (value.enabled as boolean) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the SLO payload shared by create and update. Both endpoints take the
|
||||
* same `SloConfigItemDto`, so keeping one builder stops them drifting apart.
|
||||
*/
|
||||
export function buildSloBody(params: DynatraceSloWriteFields): Record<string, unknown> {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
target: params.target,
|
||||
warning: params.warning,
|
||||
timeframe: params.timeframe,
|
||||
evaluationType: params.evaluationType || 'AGGREGATE',
|
||||
}
|
||||
if (params.description) body.description = params.description
|
||||
if (params.enabled !== undefined) body.enabled = params.enabled
|
||||
if (params.filter) body.filter = params.filter
|
||||
if (params.metricExpression) body.metricExpression = params.metricExpression
|
||||
if (params.metricName) body.metricName = params.metricName
|
||||
|
||||
const burnRate: Record<string, unknown> = {}
|
||||
if (params.burnRateVisualizationEnabled !== undefined) {
|
||||
burnRate.burnRateVisualizationEnabled = params.burnRateVisualizationEnabled
|
||||
}
|
||||
if (params.fastBurnThreshold !== undefined) burnRate.fastBurnThreshold = params.fastBurnThreshold
|
||||
if (Object.keys(burnRate).length > 0) body.errorBudgetBurnRate = burnRate
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the trailing identifier from a `Location` response header. Dynatrace
|
||||
* returns a created SLO's ID there rather than in a body.
|
||||
*/
|
||||
export function idFromLocationHeader(response: Response): string | null {
|
||||
const location = response.headers.get('location')
|
||||
if (!location) return null
|
||||
const id = location.split('/').filter(Boolean).pop()
|
||||
return id ?? null
|
||||
}
|
||||
|
||||
/** Normalizes the `warnings` array Dynatrace list endpoints return. */
|
||||
export function mapWarnings(value: unknown): string[] {
|
||||
if (Array.isArray(value)) return value.map((warning) => String(warning))
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+67
-17
@@ -917,27 +917,52 @@ import {
|
||||
} from '@/tools/dynamodb'
|
||||
import {
|
||||
dynatraceAddProblemCommentTool,
|
||||
dynatraceAddTagsTool,
|
||||
dynatraceCloseProblemTool,
|
||||
dynatraceCreateSettingsObjectTool,
|
||||
dynatraceCreateSloTool,
|
||||
dynatraceDeleteProblemCommentTool,
|
||||
dynatraceDeleteSettingsObjectTool,
|
||||
dynatraceDeleteSloTool,
|
||||
dynatraceDeleteTagTool,
|
||||
dynatraceExecuteSyntheticMonitorsTool,
|
||||
dynatraceGetAttackTool,
|
||||
dynatraceGetAuditLogsTool,
|
||||
dynatraceGetEntityTool,
|
||||
dynatraceGetEventTool,
|
||||
dynatraceGetMetricTool,
|
||||
dynatraceGetProblemCommentTool,
|
||||
dynatraceGetProblemTool,
|
||||
dynatraceGetSecurityProblemTool,
|
||||
dynatraceGetSettingsObjectTool,
|
||||
dynatraceGetSloTool,
|
||||
dynatraceGetSyntheticBatchTool,
|
||||
dynatraceIngestEventTool,
|
||||
dynatraceIngestLogsTool,
|
||||
dynatraceIngestMetricsTool,
|
||||
dynatraceListAttacksTool,
|
||||
dynatraceListEntitiesTool,
|
||||
dynatraceListEntityTypesTool,
|
||||
dynatraceListEventsTool,
|
||||
dynatraceListMetricsTool,
|
||||
dynatraceListProblemCommentsTool,
|
||||
dynatraceListProblemsTool,
|
||||
dynatraceListRemediationItemsTool,
|
||||
dynatraceListSecurityProblemsTool,
|
||||
dynatraceListSettingsObjectsTool,
|
||||
dynatraceListSettingsSchemasTool,
|
||||
dynatraceListSlosTool,
|
||||
dynatraceListSyntheticMonitorsTool,
|
||||
dynatraceListTagsTool,
|
||||
dynatraceMuteSecurityProblemsTool,
|
||||
dynatraceMuteSecurityProblemTool,
|
||||
dynatraceQueryMetricsTool,
|
||||
dynatraceSearchLogsTool,
|
||||
dynatraceUnmuteSecurityProblemsTool,
|
||||
dynatraceUnmuteSecurityProblemTool,
|
||||
dynatraceUpdateProblemCommentTool,
|
||||
dynatraceUpdateSettingsObjectTool,
|
||||
dynatraceUpdateSloTool,
|
||||
} from '@/tools/dynatrace'
|
||||
import {
|
||||
elasticsearchBulkTool,
|
||||
@@ -6246,28 +6271,53 @@ export const tools: Record<string, ToolConfig> = {
|
||||
dynamodb_update: dynamodbUpdateTool,
|
||||
dynamodb_delete: dynamodbDeleteTool,
|
||||
dynamodb_introspect: dynamodbIntrospectTool,
|
||||
dynatrace_list_problems: dynatraceListProblemsTool,
|
||||
dynatrace_get_problem: dynatraceGetProblemTool,
|
||||
dynatrace_close_problem: dynatraceCloseProblemTool,
|
||||
dynatrace_list_problem_comments: dynatraceListProblemCommentsTool,
|
||||
dynatrace_add_problem_comment: dynatraceAddProblemCommentTool,
|
||||
dynatrace_query_metrics: dynatraceQueryMetricsTool,
|
||||
dynatrace_list_metrics: dynatraceListMetricsTool,
|
||||
dynatrace_get_metric: dynatraceGetMetricTool,
|
||||
dynatrace_ingest_metrics: dynatraceIngestMetricsTool,
|
||||
dynatrace_list_entities: dynatraceListEntitiesTool,
|
||||
dynatrace_add_tags: dynatraceAddTagsTool,
|
||||
dynatrace_close_problem: dynatraceCloseProblemTool,
|
||||
dynatrace_create_settings_object: dynatraceCreateSettingsObjectTool,
|
||||
dynatrace_create_slo: dynatraceCreateSloTool,
|
||||
dynatrace_delete_problem_comment: dynatraceDeleteProblemCommentTool,
|
||||
dynatrace_delete_settings_object: dynatraceDeleteSettingsObjectTool,
|
||||
dynatrace_delete_slo: dynatraceDeleteSloTool,
|
||||
dynatrace_delete_tag: dynatraceDeleteTagTool,
|
||||
dynatrace_execute_synthetic_monitors: dynatraceExecuteSyntheticMonitorsTool,
|
||||
dynatrace_get_attack: dynatraceGetAttackTool,
|
||||
dynatrace_get_audit_logs: dynatraceGetAuditLogsTool,
|
||||
dynatrace_get_entity: dynatraceGetEntityTool,
|
||||
dynatrace_get_event: dynatraceGetEventTool,
|
||||
dynatrace_get_metric: dynatraceGetMetricTool,
|
||||
dynatrace_get_problem: dynatraceGetProblemTool,
|
||||
dynatrace_get_problem_comment: dynatraceGetProblemCommentTool,
|
||||
dynatrace_get_security_problem: dynatraceGetSecurityProblemTool,
|
||||
dynatrace_get_settings_object: dynatraceGetSettingsObjectTool,
|
||||
dynatrace_get_slo: dynatraceGetSloTool,
|
||||
dynatrace_get_synthetic_batch: dynatraceGetSyntheticBatchTool,
|
||||
dynatrace_ingest_event: dynatraceIngestEventTool,
|
||||
dynatrace_ingest_logs: dynatraceIngestLogsTool,
|
||||
dynatrace_ingest_metrics: dynatraceIngestMetricsTool,
|
||||
dynatrace_list_attacks: dynatraceListAttacksTool,
|
||||
dynatrace_list_entities: dynatraceListEntitiesTool,
|
||||
dynatrace_list_entity_types: dynatraceListEntityTypesTool,
|
||||
dynatrace_list_events: dynatraceListEventsTool,
|
||||
dynatrace_get_event: dynatraceGetEventTool,
|
||||
dynatrace_ingest_event: dynatraceIngestEventTool,
|
||||
dynatrace_search_logs: dynatraceSearchLogsTool,
|
||||
dynatrace_ingest_logs: dynatraceIngestLogsTool,
|
||||
dynatrace_list_slos: dynatraceListSlosTool,
|
||||
dynatrace_get_slo: dynatraceGetSloTool,
|
||||
dynatrace_list_metrics: dynatraceListMetricsTool,
|
||||
dynatrace_list_problem_comments: dynatraceListProblemCommentsTool,
|
||||
dynatrace_list_problems: dynatraceListProblemsTool,
|
||||
dynatrace_list_remediation_items: dynatraceListRemediationItemsTool,
|
||||
dynatrace_list_security_problems: dynatraceListSecurityProblemsTool,
|
||||
dynatrace_get_security_problem: dynatraceGetSecurityProblemTool,
|
||||
dynatrace_get_audit_logs: dynatraceGetAuditLogsTool,
|
||||
dynatrace_list_settings_objects: dynatraceListSettingsObjectsTool,
|
||||
dynatrace_list_settings_schemas: dynatraceListSettingsSchemasTool,
|
||||
dynatrace_list_slos: dynatraceListSlosTool,
|
||||
dynatrace_list_synthetic_monitors: dynatraceListSyntheticMonitorsTool,
|
||||
dynatrace_list_tags: dynatraceListTagsTool,
|
||||
dynatrace_mute_security_problem: dynatraceMuteSecurityProblemTool,
|
||||
dynatrace_mute_security_problems: dynatraceMuteSecurityProblemsTool,
|
||||
dynatrace_query_metrics: dynatraceQueryMetricsTool,
|
||||
dynatrace_search_logs: dynatraceSearchLogsTool,
|
||||
dynatrace_unmute_security_problem: dynatraceUnmuteSecurityProblemTool,
|
||||
dynatrace_unmute_security_problems: dynatraceUnmuteSecurityProblemsTool,
|
||||
dynatrace_update_problem_comment: dynatraceUpdateProblemCommentTool,
|
||||
dynatrace_update_settings_object: dynatraceUpdateSettingsObjectTool,
|
||||
dynatrace_update_slo: dynatraceUpdateSloTool,
|
||||
dropbox_upload: dropboxUploadTool,
|
||||
dropbox_download: dropboxDownloadTool,
|
||||
dropbox_list_folder: dropboxListFolderTool,
|
||||
|
||||
Reference in New Issue
Block a user