harness-mcp-v2 3.2.19 → 3.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -75
- package/build/client/harness-client.d.ts.map +1 -1
- package/build/client/harness-client.js +32 -6
- package/build/client/harness-client.js.map +1 -1
- package/build/config.d.ts +10 -0
- package/build/config.d.ts.map +1 -1
- package/build/config.js +15 -0
- package/build/config.js.map +1 -1
- package/build/data/schemas/v0/pipeline.d.ts.map +1 -1
- package/build/data/schemas/v0/pipeline.js +1388 -580
- package/build/data/schemas/v0/pipeline.js.map +1 -1
- package/build/data/schemas/v0/template.d.ts.map +1 -1
- package/build/data/schemas/v0/template.js +1142 -166
- package/build/data/schemas/v0/template.js.map +1 -1
- package/build/data/schemas/v1/pipeline.d.ts.map +1 -1
- package/build/data/schemas/v1/pipeline.js +302 -90
- package/build/data/schemas/v1/pipeline.js.map +1 -1
- package/build/data/schemas/v1/template.d.ts.map +1 -1
- package/build/data/schemas/v1/template.js +708 -488
- package/build/data/schemas/v1/template.js.map +1 -1
- package/build/prompts/deploy-ai-agent.d.ts +3 -0
- package/build/prompts/deploy-ai-agent.d.ts.map +1 -0
- package/build/prompts/deploy-ai-agent.js +300 -0
- package/build/prompts/deploy-ai-agent.js.map +1 -0
- package/build/prompts/feature-flag-rollout.d.ts.map +1 -1
- package/build/prompts/feature-flag-rollout.js +19 -8
- package/build/prompts/feature-flag-rollout.js.map +1 -1
- package/build/prompts/index.d.ts.map +1 -1
- package/build/prompts/index.js +2 -0
- package/build/prompts/index.js.map +1 -1
- package/build/registry/extractors.d.ts +87 -10
- package/build/registry/extractors.d.ts.map +1 -1
- package/build/registry/extractors.js +426 -26
- package/build/registry/extractors.js.map +1 -1
- package/build/registry/index.d.ts.map +1 -1
- package/build/registry/index.js +34 -15
- package/build/registry/index.js.map +1 -1
- package/build/registry/scope-utils.d.ts +45 -0
- package/build/registry/scope-utils.d.ts.map +1 -1
- package/build/registry/scope-utils.js +78 -0
- package/build/registry/scope-utils.js.map +1 -1
- package/build/registry/toolsets/chaos-descriptions.d.ts +72 -20
- package/build/registry/toolsets/chaos-descriptions.d.ts.map +1 -1
- package/build/registry/toolsets/chaos-descriptions.js +435 -140
- package/build/registry/toolsets/chaos-descriptions.js.map +1 -1
- package/build/registry/toolsets/chaos.d.ts.map +1 -1
- package/build/registry/toolsets/chaos.js +967 -193
- package/build/registry/toolsets/chaos.js.map +1 -1
- package/build/registry/toolsets/feature-flags.d.ts.map +1 -1
- package/build/registry/toolsets/feature-flags.js +888 -85
- package/build/registry/toolsets/feature-flags.js.map +1 -1
- package/build/registry/toolsets/governance.d.ts.map +1 -1
- package/build/registry/toolsets/governance.js +4 -0
- package/build/registry/toolsets/governance.js.map +1 -1
- package/build/registry/toolsets/iacm.d.ts.map +1 -1
- package/build/registry/toolsets/iacm.js +787 -11
- package/build/registry/toolsets/iacm.js.map +1 -1
- package/build/registry/toolsets/infrastructure.d.ts.map +1 -1
- package/build/registry/toolsets/infrastructure.js +30 -5
- package/build/registry/toolsets/infrastructure.js.map +1 -1
- package/build/registry/toolsets/pipelines.d.ts.map +1 -1
- package/build/registry/toolsets/pipelines.js +208 -5
- package/build/registry/toolsets/pipelines.js.map +1 -1
- package/build/registry/toolsets/release-management.d.ts +8 -0
- package/build/registry/toolsets/release-management.d.ts.map +1 -0
- package/build/registry/toolsets/release-management.js +783 -0
- package/build/registry/toolsets/release-management.js.map +1 -0
- package/build/registry/toolsets/sto.d.ts.map +1 -1
- package/build/registry/toolsets/sto.js +93 -17
- package/build/registry/toolsets/sto.js.map +1 -1
- package/build/registry/types.d.ts +52 -4
- package/build/registry/types.d.ts.map +1 -1
- package/build/resources/harness-schema.d.ts.map +1 -1
- package/build/resources/harness-schema.js +2 -1
- package/build/resources/harness-schema.js.map +1 -1
- package/build/tools/entity-schema/live.d.ts +6 -5
- package/build/tools/entity-schema/live.d.ts.map +1 -1
- package/build/tools/entity-schema/live.js +114 -10
- package/build/tools/entity-schema/live.js.map +1 -1
- package/build/tools/entity-schema/types.d.ts +7 -1
- package/build/tools/entity-schema/types.d.ts.map +1 -1
- package/build/tools/harness-create.d.ts.map +1 -1
- package/build/tools/harness-create.js +5 -1
- package/build/tools/harness-create.js.map +1 -1
- package/build/tools/harness-delete.d.ts.map +1 -1
- package/build/tools/harness-delete.js +5 -1
- package/build/tools/harness-delete.js.map +1 -1
- package/build/tools/harness-execute.d.ts.map +1 -1
- package/build/tools/harness-execute.js +15 -9
- package/build/tools/harness-execute.js.map +1 -1
- package/build/tools/harness-get.d.ts.map +1 -1
- package/build/tools/harness-get.js +5 -1
- package/build/tools/harness-get.js.map +1 -1
- package/build/tools/harness-list.d.ts.map +1 -1
- package/build/tools/harness-list.js +18 -2
- package/build/tools/harness-list.js.map +1 -1
- package/build/tools/harness-schema.d.ts +2 -1
- package/build/tools/harness-schema.d.ts.map +1 -1
- package/build/tools/harness-schema.js +10 -8
- package/build/tools/harness-schema.js.map +1 -1
- package/build/tools/harness-update.d.ts.map +1 -1
- package/build/tools/harness-update.js +5 -1
- package/build/tools/harness-update.js.map +1 -1
- package/build/tools/index.js +1 -1
- package/build/tools/index.js.map +1 -1
- package/build/utils/url-parser.d.ts +2 -0
- package/build/utils/url-parser.d.ts.map +1 -1
- package/build/utils/url-parser.js +55 -0
- package/build/utils/url-parser.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
import { passthrough, ngExtract, chaosPageExtract, chaosHeatmapExtract, chaosScannedRiskGetExtract, chaosExperimentListExtract, chaosInputSetListExtract, chaosAppMapPageExtract, chaosProbeListExtract, chaosInfraListExtract, chaosK8sInfraListExtract, chaosLoadTestListExtract, chaosLoadTestExtract, chaosHubListExtract, chaosDRTestListExtract, sdPageExtract, chaosRunTimeInputsExtract, chaosActionExtract, } from "../extractors.js";
|
|
3
|
+
import { passthrough, ngExtract, chaosPageExtract, chaosHeatmapExtract, chaosScannedRiskGetExtract, chaosExperimentListExtract, chaosInputSetListExtract, chaosAppMapPageExtract, chaosProbeListExtract, chaosInfraListExtract, chaosK8sInfraListExtract, chaosLoadTestListExtract, chaosLoadTestExtract, chaosServiceListExtract, chaosHubListExtract, chaosDRTestListExtract, sdPageExtract, chaosRunTimeInputsExtract, chaosActionExtract, } from "../extractors.js";
|
|
4
4
|
import { descToolsetChaos,
|
|
5
5
|
// Resource descriptions
|
|
6
|
-
descChaosExperiment, descChaosExperimentRun, descChaosProbe, descChaosExperimentTemplate, descChaosExperimentVariable, descChaosInfrastructure, descChaosLoadtest, descChaosK8sInfrastructure, descChaosEnabledInfrastructure, descChaosHub, descChaosFault, descChaosFaultExperimentRun, descChaosFaultTemplate, descChaosProbeTemplate, descChaosActionTemplate, descChaosHubFault, descChaosEnvironment, descChaosApplicationMap, descDiscoveredNetworkMap, descChaosGuardCondition, descChaosGuardRule, descChaosRecommendation, descChaosRisk, descChaosAction, descChaosProbeInRun, descChaosDRTest, descChaosComponentVariable,
|
|
6
|
+
descChaosExperiment, descChaosExperimentRun, descChaosProbe, descChaosExperimentTemplate, descChaosExperimentVariable, descChaosInfrastructure, descChaosLoadtest, descChaosService, descChaosK8sInfrastructure, descChaosEnabledInfrastructure, descChaosHub, descChaosFault, descChaosFaultExperimentRun, descChaosFaultTemplate, descChaosProbeTemplate, descChaosActionTemplate, descChaosHubFault, descChaosEnvironment, descChaosApplicationMap, descDiscoveredNetworkMap, descChaosGuardCondition, descChaosGuardRule, descChaosRecommendation, descChaosRisk, descChaosAction, descChaosProbeInRun, descChaosDRTest, descChaosComponentVariable,
|
|
7
7
|
// Operation descriptions
|
|
8
|
-
descListExperiments, descGetExperiment, descGetExperimentRun, descListProbes, descGetProbe, descCreateProbe, descListExperimentTemplates, descGetExperimentTemplate, descDeleteExperimentTemplate, descListExperimentVariables, descGetComponentVariable, descCreateExperiment, descListLinuxInfra, descListLoadtests, descGetLoadtest, descCreateLoadtest, descDeleteLoadtest, descListK8sInfra, descGetK8sInfra, descListChaosEnabledInfra, descListHubs, descGetHub, descCreateHub, descUpdateHub, descDeleteHub, descListFaults, descGetFault, descListFaultTemplates, descGetFaultTemplate, descDeleteFaultTemplate, descListProbeTemplates, descGetProbeTemplate, descDeleteProbeTemplate, descListActionTemplates, descGetActionTemplate, descDeleteActionTemplate, descListHubFaults, descListChaosEnvironments, descListApplicationMaps, descGetApplicationMap, descAppMapSearch, descAppMapEnvironmentId, descAppMapInfraId, descAppMapAll, descAppMapMinimal, descListDiscoveredNetworkMaps, descSDNetworkMapSearch, descListGuardConditions, descGetGuardCondition, descDeleteGuardCondition, descListGuardRules, descGetGuardRule, descDeleteGuardRule, descListRecommendations, descGetRecommendation, descListRisks, descGetRisk, descListDRTests, descDeleteProbe, descGetProbeManifest, descListProbesInRun, descGetFaultVariables, descGetFaultYaml, descListFaultExperimentRuns, descDeleteFault, descListActions, descGetAction, descGetActionManifest, descDeleteAction, descCreateAction, descBodyActionCreate, descActionName, descActionIdentityCreate, descActionEntityTypeCreate, descActionInfraTypeCreate, descActionPropertiesBody, descActionDurationShorthand, descActionDescriptionCreate, descActionTagsCreate, descActionVariablesBody, descActionRunPropertiesBody, descActionInputsBody,
|
|
8
|
+
descListExperiments, descGetExperiment, descGetExperimentRun, descListProbes, descGetProbe, descCreateProbe, descListExperimentTemplates, descGetExperimentTemplate, descDeleteExperimentTemplate, descListExperimentVariables, descGetComponentVariable, descCreateExperiment, descListLinuxInfra, descListLoadtests, descGetLoadtest, descCreateLoadtest, descUpdateLoadtest, descDeleteLoadtest, descListChaosServices, descGetChaosService, descDeleteChaosService, descCreateChaosService, descUpdateChaosService, descChaosServiceEnvironmentIds, descChaosServiceInfrastructureIds, descChaosServiceTags, descChaosServiceIncludeAllScope, descChaosServiceProbeIds, descChaosServiceOnboardingIdFilter, descListChaosServiceExperimentRuns, descListChaosServiceLoadTests, descChaosServiceSearch, descChaosServiceIdentity, descBodyChaosServiceCreate, descBodyChaosServiceUpdate, descChaosServiceName, descChaosServiceDescription, descChaosServiceTagsBody, descChaosServiceExternalServiceId, descChaosServiceAgentId, descChaosServiceEnvironmentId, descChaosServiceInfrastructureId, descChaosServiceInfrastructureType, descChaosServiceOnboardingId, descChaosServiceProbes, descChaosServiceProbesUpdate, descListK8sInfra, descGetK8sInfra, descCreateK8sInfra, descListChaosEnabledInfra, descBodyK8sInfraCreate, descK8sInfraIdentityCreate, descK8sInfraNameCreate, descK8sInfraEnvironmentIdCreate, descK8sInfraInfraIdCreate, descK8sInfraConnectorIdCreate, descK8sInfraNamespaceCreate, descK8sInfraServiceAccountCreate, descK8sInfraScopeCreate, descK8sInfraTypeCreate, descK8sInfraAiEnabledCreate, descListHubs, descGetHub, descCreateHub, descUpdateHub, descDeleteHub, descListFaults, descGetFault, descListFaultTemplates, descGetFaultTemplate, descDeleteFaultTemplate, descListProbeTemplates, descGetProbeTemplate, descDeleteProbeTemplate, descListActionTemplates, descGetActionTemplate, descDeleteActionTemplate, descListHubFaults, descListChaosEnvironments, descListApplicationMaps, descGetApplicationMap, descAppMapSearch, descAppMapEnvironmentId, descAppMapInfraId, descAppMapAll, descAppMapMinimal, descListDiscoveredNetworkMaps, descSDNetworkMapSearch, descListGuardConditions, descGetGuardCondition, descDeleteGuardCondition, descListGuardRules, descGetGuardRule, descDeleteGuardRule, descListRecommendations, descGetRecommendation, descListRisks, descGetRisk, descListDRTests, descDeleteProbe, descGetProbeManifest, descListProbesInRun, descGetFaultVariables, descGetFaultYaml, descListFaultExperimentRuns, descDeleteFault, descListActions, descGetAction, descGetActionManifest, descDeleteAction, descCreateAction, descBodyActionCreate, descActionName, descActionIdentityCreate, descActionEntityTypeCreate, descActionInfraTypeCreate, descActionPropertiesBody, descActionDurationShorthand, descActionDescriptionCreate, descActionTagsCreate, descActionVariablesBody, descActionRunPropertiesBody, descActionInputsBody,
|
|
9
9
|
// Action descriptions
|
|
10
10
|
descRunExperiment, descStopExperiment, descDeleteExperiment, descEnableProbe, descVerifyProbe, descCreateFromTemplate, descListRevisions, descGetVariables, descGetYaml, descCompareRevisions, descRunLoadtest, descStopLoadtest, descCheckK8sHealth, descEnableGuardRule, descGetProbeTemplateVariables, descListActionTemplateRevisions, descGetActionTemplateVariables, descCompareActionTemplateRevisions,
|
|
11
11
|
// Body schema descriptions
|
|
12
12
|
descBodyExperimentRun, descBodyNoBody, descBodyExperimentCreate, descBodyCreateFromTemplate, descBodyLoadtestDefinition, descBodyProbeEnable, descBodyProbeVerify, descBodyProbesInRun, descBodyProbeCreate,
|
|
13
13
|
// Field descriptions
|
|
14
|
-
descInputsetIdentity, descRuntimeInputs, descHubIdentity, descInfraType, descExperimentName, descExperimentIdentity, descInfraRef, descExperimentId, descInfraStatus, descLoadtestName, descLoadtestType, descLoadtestIdentity, descLoadtestDescription, descLoadtestTags, descLoadtestEnvId, descLoadtestInfraId, descLoadtestTargetType, descLoadtestTargetUrl, descLoadtestScript, descLoadtestUsers, descLoadtestDurationSec, descLoadtestRampUpSec, descLoadtestWorkerCount, descLoadtestScriptSource, descLoadtestScriptImage, descLoadtestScriptEntrypoint, descLoadtestLoadArgs, descLoadtestHostUrl, descLoadtestRpsLimit, descLoadtestIterations, descLoadtestEnvVars, descHubIdentityExact, descHubName, descHubNameUpdate, descHubDescription, descHubDescriptionUpdate, descHubTags, descHubTagsReplace, descConnectorRef, descRepoName, descRepoBranch, descHubSearch, descIncludeAllScope, descTemplateSearch, descSortField, descSortAsc, descTags, descInfrastructure, descTemplateIdentity, descRevision, descRevision1, descRevision2, descRevisionToCompare, descFaultType, descFaultCategory, descFaultPermissions, descFaultIsEnterprise, descImportType, descExperimentDescription, descExperimentTags, descEntityTypeProbe, descEntityTypeAction, descEntityTypeFault, descPermissionsRequiredEnum, descOnlyTemplatisedFaults, descEnvironmentId, descK8sInfraStatus, descIncludeLegacyInfra, descSearchK8sInfra, descChaosEnabledInfraType, descInfraScope, descInfraAiEnabled, descSearchTermEnv, descSortEnv, descEnvironmentType, descGuardSearch, descGuardInfraType, descGuardTags, descGuardEnabled, descExperimentRunIdStop, descNotifyId, descForce, descIsEnabledFlag, descIsBulkUpdate, descVerifyFlag, descExperimentRunIds, descNotifyIds, descFaultIdentityParam, descIsEnterpriseFilter, descFaultSearch, descFaultListType, descFaultListInfraType, descFaultListInfrastructure, descFaultListTags, descFaultListCategory, descFaultListSortField, descFaultListSortAscending, descIsEnterpriseYaml, descIsEnterpriseVars, descIsEnterpriseRuns, descActionIdentityParam, descSearchActionsParam, descHubIdentityActions, descExperimentVariablesParam, descTasksParam, descEnvironmentIdCreate, descInfraIdCreate, descAccountIdBody, descOrgIdBody, descProjectIdBody, descSearchExperiments, descExperimentInfraId, descExperimentEnvironmentId, descExperimentIds, descExperimentStartDate, descExperimentEndDate, descExperimentTargetNetworkMapIds, descExperimentMyExperiments, descExperimentExcludeAutomation, descSearchProbes, descProbeIds, descProbeSortField, descProbeIdField, descProbeNameField, descProbePropertiesField, descRunPropertiesField, descDRTestSort, descCreateDRTest, descBodyDRTestCreate, descDRTestName, descDRTestIdentifier, descDRTestDescription, descDRTestObjective, descDRTestTags,
|
|
14
|
+
descInputsetIdentity, descRuntimeInputs, descHubIdentity, descInfraType, descExperimentName, descExperimentIdentity, descInfraRef, descExperimentId, descInfraStatus, descLoadtestName, descLoadtestType, descLoadtestIdentity, descLoadtestDescription, descLoadtestTags, descLoadtestEnvId, descLoadtestInfraId, descLoadtestTargetType, descLoadtestTargetUrl, descLoadtestScript, descLoadtestUsers, descLoadtestDurationSec, descLoadtestRampUpSec, descLoadtestWorkerCount, descLoadtestScriptSource, descLoadtestScriptImage, descLoadtestScriptEntrypoint, descLoadtestLoadArgs, descLoadtestImagePullSecret, descLoadtestHostUrl, descLoadtestRpsLimit, descLoadtestIterations, descLoadtestEnvVars, descLoadtestProperties, descLoadtestThresholds, descLoadtestCleanupPolicy, descLoadtestResources, descHubIdentityExact, descHubName, descHubNameUpdate, descHubDescription, descHubDescriptionUpdate, descHubTags, descHubTagsReplace, descConnectorRef, descRepoName, descRepoBranch, descHubSearch, descIncludeAllScope, descTemplateSearch, descSortField, descSortAsc, descTags, descInfrastructure, descTemplateIdentity, descRevision, descRevision1, descRevision2, descRevisionToCompare, descFaultType, descFaultCategory, descFaultPermissions, descFaultIsEnterprise, descImportType, descExperimentDescription, descExperimentTags, descEntityTypeProbe, descEntityTypeAction, descEntityTypeFault, descPermissionsRequiredEnum, descOnlyTemplatisedFaults, descEnvironmentId, descK8sInfraStatus, descIncludeLegacyInfra, descSearchK8sInfra, descChaosEnabledInfraType, descInfraScope, descInfraAiEnabled, descSearchTermEnv, descSortEnv, descEnvironmentType, descGuardSearch, descGuardInfraType, descGuardTags, descGuardEnabled, descExperimentRunIdStop, descNotifyId, descForce, descIsEnabledFlag, descIsBulkUpdate, descVerifyFlag, descExperimentRunIds, descNotifyIds, descFaultIdentityParam, descIsEnterpriseFilter, descFaultSearch, descFaultListType, descFaultListInfraType, descFaultListInfrastructure, descFaultListTags, descFaultListCategory, descFaultListSortField, descFaultListSortAscending, descIsEnterpriseYaml, descIsEnterpriseVars, descIsEnterpriseRuns, descActionIdentityParam, descSearchActionsParam, descHubIdentityActions, descExperimentVariablesParam, descTasksParam, descEnvironmentIdCreate, descInfraIdCreate, descAccountIdBody, descOrgIdBody, descProjectIdBody, descSearchExperiments, descExperimentInfraId, descExperimentEnvironmentId, descExperimentIds, descExperimentStartDate, descExperimentEndDate, descExperimentTargetNetworkMapIds, descExperimentMyExperiments, descExperimentExcludeAutomation, descSearchProbes, descProbeIds, descProbeSortField, descProbeIdField, descProbeNameField, descProbePropertiesField, descRunPropertiesField, descDRTestSort, descCreateDRTest, descBodyDRTestCreate, descDRTestName, descDRTestIdentifier, descDRTestDescription, descDRTestObjective, descDRTestTags,
|
|
15
15
|
// Input set descriptions
|
|
16
16
|
descChaosInputSet, descListInputSets, descGetInputSet, descCreateInputSet, descUpdateInputSet, descDeleteInputSet, descInputSetIdentityField, descInputSetName, descInputSetDescription, descInputSetSpec, descIsIdentity,
|
|
17
17
|
// Component variable descriptions
|
|
@@ -19,7 +19,7 @@ descComponentType, descComponentIdentifier, descComponentHubReference,
|
|
|
19
19
|
// Experiment create field descriptions
|
|
20
20
|
descExperimentManifest, descExperimentInfraType, descExperimentInfraIdCreate, descExperimentCronSyntax, descExperimentIdUUID,
|
|
21
21
|
// Service Discovery
|
|
22
|
-
descSDAgentIdentity, descSDEnvironmentId, descSDFetchAll, descSDAgentDiagnostic, descDiscoveredNamespace, descListDiscoveredNamespaces, descSDNamespaceNameFilter, descDiscoveredService, descListDiscoveredServices, descSDNamespaceFilter, descSDSearchFilter,
|
|
22
|
+
descSDAgentIdentity, descSDEnvironmentId, descSDAgentListEnvironmentId, descSDFetchAll, descSDAgentDiagnostic, descDiscoveredAgent, descListDiscoveredAgents, descDiscoveredAgentSearch, descDiscoveredNamespace, descListDiscoveredNamespaces, descSDNamespaceNameFilter, descDiscoveredService, descListDiscoveredServices, descSDNamespaceFilter, descSDSearchFilter,
|
|
23
23
|
// Scanned Risks / Risk Rules / Risk Scans (chaos-manager v3)
|
|
24
24
|
descScannedRisk, descChaosRiskRule, descChaosRiskScan, descListScannedRisks, descGetScannedRisk, descListScannedRiskOccurrences, descSummarizeScannedRisksByService, descListRiskRules, descGetRiskRule, descListRiskScans, descGetRiskScan, descCreateRiskScan, descUpdateRiskScan, descDeleteRiskScan, descRetryRiskScan, descAbortRiskScan, descGetRiskScanReport, descGetRiskScanReportDownload, descGetRiskScanHeatmap, descBodyRiskScanCreate, descBodyRiskScanUpdate, descRiskScanIdentity, descRiskScanName, descRiskScanDescription, descRiskScanTags, descRiskScanType, descRiskScanSource, descScannedRiskSeverity, descScannedRiskRuleId, descScannedRiskValidationType, descScannedRiskServiceIdentity, descScannedRiskScanType, descRiskEnvironmentIdentity, descRiskAgentIdentity, descRiskPipelineIdentity, descRiskScanStatus, descRiskSearch, descRiskSortField, descRiskSortAscending, descRiskTags, descRiskIncludeAllScope, descRiskStartTime, descRiskEndTime, descRiskRuleDataSource, descRiskRuleIsSystem, descHeatmapSearch, } from "./chaos-descriptions.js";
|
|
25
25
|
/**
|
|
@@ -44,6 +44,39 @@ const chaosComponentVarExtract = (raw) => {
|
|
|
44
44
|
const r = raw;
|
|
45
45
|
return r.items?.[0] ?? raw;
|
|
46
46
|
};
|
|
47
|
+
/** Compact projection for chaos_loadtest list items — keeps cheap discriminator
|
|
48
|
+
* scalars (toolType/targetType/scriptSource/infraType/cleanupPolicy) that the
|
|
49
|
+
* generic compactItems() whitelist in utils/compact.ts would otherwise drop
|
|
50
|
+
* (it only matches the literal key "type", not "toolType"/"targetType"), while
|
|
51
|
+
* still excluding heavy fields (toolConfig, yaml, variables, envVars, recentRuns). */
|
|
52
|
+
function compactLoadTest(item) {
|
|
53
|
+
const slim = {};
|
|
54
|
+
for (const key of [
|
|
55
|
+
"loadtestId", "uniqueId", "identity", "name", "description", "tags",
|
|
56
|
+
"environmentIdentifier", "infraIdentifier", "infraType", "targetType",
|
|
57
|
+
"toolType", "scriptSource", "cleanupPolicy", "latestRevisionIdentifier",
|
|
58
|
+
"createdAt", "updatedAt", "openInHarness",
|
|
59
|
+
]) {
|
|
60
|
+
if (item[key] !== undefined)
|
|
61
|
+
slim[key] = item[key];
|
|
62
|
+
}
|
|
63
|
+
return slim;
|
|
64
|
+
}
|
|
65
|
+
/** Compact projection for discovered_agent list items — keeps serviceCount/
|
|
66
|
+
* networkMapCount (STEP 1 of chaos_service create shows these) which the
|
|
67
|
+
* generic compactItems() whitelist in utils/compact.ts would otherwise drop. */
|
|
68
|
+
function compactDiscoveredAgent(item) {
|
|
69
|
+
const slim = {};
|
|
70
|
+
for (const key of [
|
|
71
|
+
"identity", "name", "description", "tags", "environmentIdentifier",
|
|
72
|
+
"serviceCount", "networkMapCount", "installationType",
|
|
73
|
+
"createdAt", "updatedAt", "openInHarness",
|
|
74
|
+
]) {
|
|
75
|
+
if (item[key] !== undefined)
|
|
76
|
+
slim[key] = item[key];
|
|
77
|
+
}
|
|
78
|
+
return slim;
|
|
79
|
+
}
|
|
47
80
|
/**
|
|
48
81
|
* Parse input.body when LLMs double-serialize it as a JSON string instead of an object.
|
|
49
82
|
* Fails loudly on malformed JSON so callers' defaults can never silently produce a
|
|
@@ -62,56 +95,19 @@ function coerceBody(input) {
|
|
|
62
95
|
}
|
|
63
96
|
return raw;
|
|
64
97
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
inputs.push({ name: "targetUsers", value: users, type: "Integer", required: true });
|
|
77
|
-
inputs.push({ name: "durationSeconds", value: duration, type: "Integer" });
|
|
78
|
-
inputs.push({ name: "rampUpTimeSec", value: rampUp, type: "Integer" });
|
|
79
|
-
// workerCount is Kubernetes-only (0 = standalone, N > 0 = distributed).
|
|
80
|
-
if (opts.targetType === "kubernetes") {
|
|
81
|
-
const workerCount = b.worker_count != null ? b.worker_count : 0;
|
|
82
|
-
inputs.push({ name: "workerCount", value: workerCount, type: "Integer" });
|
|
83
|
-
}
|
|
84
|
-
// Target URL: present on every load test (backend treats it as the host under test).
|
|
85
|
-
const targetUrl = (b.target_url ?? b.targetUrl);
|
|
86
|
-
if (targetUrl != null) {
|
|
87
|
-
inputs.push({ name: "targetUrl", value: targetUrl, type: "String" });
|
|
88
|
-
}
|
|
89
|
-
// Image-mode tunables (Custom Image): scriptImage/scriptEntrypoint are
|
|
90
|
-
// mandatory in image mode (marked required), loadArgs is optional.
|
|
91
|
-
if (opts.scriptSource === "image") {
|
|
92
|
-
const scriptImage = (b.script_image ?? b.scriptImage);
|
|
93
|
-
if (scriptImage != null) {
|
|
94
|
-
inputs.push({ name: "scriptImage", value: scriptImage, type: "String", required: true });
|
|
95
|
-
}
|
|
96
|
-
const entrypoint = (b.script_entrypoint ?? b.scriptEntrypoint);
|
|
97
|
-
if (entrypoint != null) {
|
|
98
|
-
inputs.push({ name: "scriptEntrypoint", value: entrypoint, type: "String", required: true });
|
|
99
|
-
}
|
|
100
|
-
const loadArgs = (b.load_args ?? b.loadArgs);
|
|
101
|
-
if (loadArgs != null) {
|
|
102
|
-
inputs.push({ name: "loadArgs", value: loadArgs, type: "String" });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return inputs;
|
|
106
|
-
}
|
|
107
|
-
// ── K6-specific helpers ──────────────────────────────────────────────
|
|
108
|
-
// K6 is a Kubernetes-only load test runner. Script mode wraps the K6 JS source
|
|
109
|
-
// in a `toolConfig` object (rather than top-level `scriptContent` like Locust).
|
|
110
|
-
// Env vars and secret references live exclusively in `toolConfig.envVars`.
|
|
111
|
-
// Reserved K6 env-var names (case-insensitive). Mirrors
|
|
112
|
-
// loadTestManager/internal/domain/ReservedEnvVarNames; the frontend list is at
|
|
98
|
+
// ── Load test helpers ────────────────────────────────────────────────
|
|
99
|
+
// Since the loadTestManager variables migration, all tunables and custom env
|
|
100
|
+
// vars live under toolConfig.<tool>.tunables / toolConfig.<tool>.variables. The
|
|
101
|
+
// MCP keeps its LLM surface as ergonomic snake_case scalars and translates
|
|
102
|
+
// them into the nested toolConfig shape here, so agents never construct the
|
|
103
|
+
// nested map, base64, or YAML themselves.
|
|
104
|
+
// ── Shared env-var helper (K6 + JMeter) ──────────────────────────────
|
|
105
|
+
// Env vars and secret references live in `toolConfig.<tool>.envVars`.
|
|
106
|
+
// Reserved env-var names (case-insensitive). Mirrors
|
|
107
|
+
// loadTestManager/internal/domain/ReservedEnvVarNames (shared base set, all
|
|
108
|
+
// tools) + K6ReservedEnvVarNames (K6-only extras). The frontend list is at
|
|
113
109
|
// hce-saas/web/src/services/loadTest/loadTestVariables.ts:337-372.
|
|
114
|
-
const
|
|
110
|
+
const BASE_RESERVED_ENV_VAR_NAMES = new Set([
|
|
115
111
|
"RUN_ID", "LOAD_TEST_ID", "TARGET_USERS", "SPAWN_RATE", "SCRIPT_CONTENT_BASE64",
|
|
116
112
|
"TARGET_URL", "ACCOUNT_ID", "ORG_ID", "PROJECT_ID", "ENV_ID", "DURATION_SECONDS",
|
|
117
113
|
"CONTROL_PLANE_URL", "CONTROL_PLANE_TOKEN", "HARNESS_CUSTOM_VAR_NAMES",
|
|
@@ -119,18 +115,21 @@ const RESERVED_K6_ENV_VAR_NAMES = new Set([
|
|
|
119
115
|
"PYTHONPATH", "PATH", "HOME", "USER", "SHELL", "LANG", "TERM", "HOSTNAME",
|
|
120
116
|
"PWD", "LD_LIBRARY_PATH", "LD_PRELOAD", "TMPDIR", "TMP", "TEMP",
|
|
121
117
|
]);
|
|
122
|
-
const
|
|
118
|
+
const K6_EXTRA_RESERVED_ENV_VAR_NAMES = new Set([
|
|
119
|
+
"HOST_URL", "K6_VUS", "K6_DURATION", "K6_ITERATIONS", "K6_STAGES", "K6_RPS",
|
|
120
|
+
]);
|
|
121
|
+
const ENV_VAR_KEY_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
123
122
|
// Build the HSM secret reference: account → "account.<id>", org → "org.<id>", project → "<id>".
|
|
124
123
|
// Matches the Harness UI convention (scope prefix encodes which secret manager the value lives in).
|
|
125
124
|
function buildSecretReference(secretId, scope) {
|
|
126
125
|
const prefix = scope === "account" ? "account." : scope === "org" ? "org." : "";
|
|
127
126
|
return `secrets.getValue("${prefix}${secretId}")`;
|
|
128
127
|
}
|
|
129
|
-
// Validate + project the structured env_vars input into the wire shape K6
|
|
128
|
+
// Validate + project the structured env_vars input into the wire shape K6/JMeter expect.
|
|
130
129
|
// Each entry sets exactly one of:
|
|
131
130
|
// - { key, value } → literal env var
|
|
132
131
|
// - { key, secret_id, secret_scope?: "..." } → MCP builds the secrets.getValue(...) string
|
|
133
|
-
function
|
|
132
|
+
function buildEnvVars(raw, tool) {
|
|
134
133
|
if (raw == null)
|
|
135
134
|
return [];
|
|
136
135
|
if (!Array.isArray(raw)) {
|
|
@@ -143,10 +142,11 @@ function buildK6EnvVars(raw) {
|
|
|
143
142
|
if (!key || typeof key !== "string") {
|
|
144
143
|
throw new Error(`env_vars[${idx}].key is required.`);
|
|
145
144
|
}
|
|
146
|
-
if (!
|
|
145
|
+
if (!ENV_VAR_KEY_REGEX.test(key)) {
|
|
147
146
|
throw new Error(`env_vars[${idx}].key '${key}' is invalid: must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);
|
|
148
147
|
}
|
|
149
|
-
|
|
148
|
+
const upper = key.toUpperCase();
|
|
149
|
+
if (BASE_RESERVED_ENV_VAR_NAMES.has(upper) || (tool === "K6" && K6_EXTRA_RESERVED_ENV_VAR_NAMES.has(upper))) {
|
|
150
150
|
throw new Error(`env_vars[${idx}].key '${key}' is a reserved name and cannot be used as a custom env var.`);
|
|
151
151
|
}
|
|
152
152
|
if (seen.has(key)) {
|
|
@@ -181,108 +181,319 @@ function parseHostOrigin(rawUrl) {
|
|
|
181
181
|
return rawUrl;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
// Build the K6 `toolConfig`
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
184
|
+
// Build the K6 `toolConfig.k6` block for script or image mode (UI mode deferred).
|
|
185
|
+
// Matches K6Spec + ScriptSpec in loadTestManager: mode +
|
|
186
|
+
// script{content|image|entrypoint|loadArgs|imagePullSecret} +
|
|
187
|
+
// tunables{targetUrl, targetUsers, durationSeconds, rampUpTimeSec, workerCount, hostUrl,
|
|
188
|
+
// iterations, rpsLimit} + envVars.
|
|
188
189
|
function buildK6ToolConfig(b, args) {
|
|
189
190
|
const hostUrl = b.host_url ??
|
|
190
191
|
b.hostUrl ??
|
|
191
192
|
parseHostOrigin(args.targetUrl);
|
|
192
193
|
const rpsLimit = b.rps_limit != null ? b.rps_limit : undefined;
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// iterations) are appended after so we get a stable key order matching the studio.
|
|
199
|
-
let toolConfig;
|
|
194
|
+
const iterations = b.iterations != null ? b.iterations : undefined;
|
|
195
|
+
const envVars = buildEnvVars(b.env_vars, "K6");
|
|
196
|
+
// Script/image artifact lives under `script`, not `scriptContent`/`customImage`.
|
|
197
|
+
let mode;
|
|
198
|
+
const script = {};
|
|
200
199
|
if (args.scriptSource === "image") {
|
|
201
|
-
// Custom Image: image + optional entrypoint live in toolConfig.customImage.
|
|
202
|
-
// Note: load_args is NOT part of customImage for K6 — it rides only in inputs[]
|
|
203
|
-
// (matches K6LoadTestService.ts:487-495 where customImage only has image/entrypoint).
|
|
204
200
|
const scriptImage = (b.script_image ?? b.scriptImage);
|
|
205
201
|
if (scriptImage == null) {
|
|
206
|
-
// Defensive — the early image-mode gate in bodyBuilder already throws this.
|
|
207
202
|
throw new Error("K6 image mode requires 'script_image'.");
|
|
208
203
|
}
|
|
209
|
-
|
|
204
|
+
mode = "image";
|
|
205
|
+
script.image = scriptImage;
|
|
210
206
|
const entrypoint = (b.script_entrypoint ?? b.scriptEntrypoint);
|
|
211
207
|
if (entrypoint != null)
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
script.entrypoint = entrypoint;
|
|
209
|
+
const loadArgs = (b.load_args ?? b.loadArgs);
|
|
210
|
+
if (loadArgs != null && loadArgs !== "") {
|
|
211
|
+
validateLoadArgs(loadArgs);
|
|
212
|
+
script.loadArgs = loadArgs;
|
|
213
|
+
}
|
|
214
|
+
const imagePullSecret = (b.image_pull_secret ?? b.imagePullSecret);
|
|
215
|
+
if (imagePullSecret != null && imagePullSecret !== "") {
|
|
216
|
+
script.imagePullSecret = imagePullSecret;
|
|
217
|
+
}
|
|
214
218
|
}
|
|
215
219
|
else {
|
|
216
|
-
// Script mode: base64 JS source. Mandatory client-side rule (matches Harness UI:
|
|
217
|
-
// validateScriptContent at K6LoadTestService.ts:767-774). Substring check (not
|
|
218
|
-
// full AST) matches the UI exactly.
|
|
219
220
|
if (args.script == null) {
|
|
220
221
|
throw new Error("K6 script mode requires 'script' (the raw JavaScript K6 source).");
|
|
221
222
|
}
|
|
223
|
+
// Mandatory client-side rule (matches Harness UI validateScriptContent).
|
|
222
224
|
if (!args.script.includes("export default")) {
|
|
223
225
|
throw new Error("K6 script must export a default function (export default function ...).");
|
|
224
226
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
scriptContent: Buffer.from(args.script, "utf8").toString("base64"),
|
|
228
|
-
};
|
|
229
|
-
// iterations is K6 script-mode-only (matches K6LoadTestService.ts:483-484).
|
|
230
|
-
const iterations = b.iterations != null ? b.iterations : undefined;
|
|
231
|
-
if (iterations != null && iterations > 0)
|
|
232
|
-
toolConfig.iterations = iterations;
|
|
227
|
+
mode = "script";
|
|
228
|
+
script.content = Buffer.from(args.script, "utf8").toString("base64");
|
|
233
229
|
}
|
|
230
|
+
// Tunables live under `tunables`, not flat on the toolConfig object.
|
|
231
|
+
const tunables = {};
|
|
232
|
+
if (args.targetUrl != null)
|
|
233
|
+
tunables.targetUrl = args.targetUrl;
|
|
234
|
+
if (b.users != null)
|
|
235
|
+
tunables.targetUsers = b.users;
|
|
236
|
+
if (b.duration_sec != null)
|
|
237
|
+
tunables.durationSeconds = b.duration_sec;
|
|
238
|
+
if (b.ramp_up_sec != null)
|
|
239
|
+
tunables.rampUpTimeSec = b.ramp_up_sec;
|
|
240
|
+
if (b.worker_count != null)
|
|
241
|
+
tunables.workerCount = b.worker_count;
|
|
234
242
|
if (hostUrl)
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
243
|
+
tunables.hostUrl = hostUrl;
|
|
244
|
+
if (iterations != null && iterations > 0)
|
|
245
|
+
tunables.iterations = iterations;
|
|
246
|
+
// rpsLimit's sole authoring surface is tunables (loadTestManager k6.go);
|
|
247
|
+
// options.rpsLimit is a legacy run-dispatch fallback only, never written here.
|
|
248
|
+
if (rpsLimit != null && rpsLimit > 0)
|
|
249
|
+
tunables.rpsLimit = rpsLimit;
|
|
250
|
+
const toolConfig = { mode, script };
|
|
251
|
+
if (Object.keys(tunables).length > 0)
|
|
252
|
+
toolConfig.tunables = tunables;
|
|
238
253
|
if (envVars.length > 0)
|
|
239
254
|
toolConfig.envVars = envVars;
|
|
240
255
|
return toolConfig;
|
|
241
256
|
}
|
|
257
|
+
// Reject known legacy/mistaken field names on chaos_loadtest create/update instead
|
|
258
|
+
// of silently ignoring them (Fail Loudly — see coerceBody above). 'scriptContent' /
|
|
259
|
+
// 'customImage' are old wire-shape names superseded by 'script' / 'script_image';
|
|
260
|
+
// 'inputs' belongs to chaos_probe, not chaos_loadtest.
|
|
261
|
+
const LEGACY_LOADTEST_KEYS = ["scriptContent", "customImage", "inputs"];
|
|
262
|
+
function rejectLegacyLoadtestFields(b) {
|
|
263
|
+
for (const key of LEGACY_LOADTEST_KEYS) {
|
|
264
|
+
if (b[key] !== undefined) {
|
|
265
|
+
throw new Error(`'${key}' is not a supported chaos_loadtest field. Use 'script'/'script_image' instead of 'scriptContent'/'customImage'; 'inputs' is a chaos_probe field, not chaos_loadtest.`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const JMETER_THRESHOLD_METRICS = new Set(["response_time_ms", "error_rate_pct", "throughput_rps", "latency_ms"]);
|
|
270
|
+
const JMETER_THRESHOLD_OPERATORS = new Set(["<", "<=", ">", ">=", "==", "!="]);
|
|
271
|
+
const JMETER_STAT_REQUIRED_METRICS = new Set(["response_time_ms", "latency_ms"]);
|
|
272
|
+
// Validate + project the structured properties input into toolConfig.jmeter.properties[].
|
|
273
|
+
function buildJMeterProperties(raw) {
|
|
274
|
+
if (raw == null)
|
|
275
|
+
return [];
|
|
276
|
+
if (!Array.isArray(raw)) {
|
|
277
|
+
throw new Error("properties must be an array of {key, value, send_to_engines?} entries.");
|
|
278
|
+
}
|
|
279
|
+
return raw.map((entry, idx) => {
|
|
280
|
+
const p = entry;
|
|
281
|
+
const key = p.key;
|
|
282
|
+
if (!key) {
|
|
283
|
+
throw new Error(`properties[${idx}].key is required.`);
|
|
284
|
+
}
|
|
285
|
+
const out = { key, value: String(p.value ?? "") };
|
|
286
|
+
if (typeof p.send_to_engines === "boolean")
|
|
287
|
+
out.sendToEngines = p.send_to_engines;
|
|
288
|
+
return out;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Validate + project the structured thresholds input into toolConfig.jmeter.thresholds[].
|
|
292
|
+
// Mirrors JMeterThreshold.validate() in loadTestManager/internal/domain/jmeter.go:245-256.
|
|
293
|
+
function buildJMeterThresholds(raw) {
|
|
294
|
+
if (raw == null)
|
|
295
|
+
return [];
|
|
296
|
+
if (!Array.isArray(raw)) {
|
|
297
|
+
throw new Error("thresholds must be an array of {metric, stat?, operator, value, abort_on_fail?} entries.");
|
|
298
|
+
}
|
|
299
|
+
return raw.map((entry, idx) => {
|
|
300
|
+
const t = entry;
|
|
301
|
+
const metric = t.metric;
|
|
302
|
+
if (!metric || !JMETER_THRESHOLD_METRICS.has(metric)) {
|
|
303
|
+
throw new Error(`thresholds[${idx}].metric must be one of ${[...JMETER_THRESHOLD_METRICS].join(", ")}.`);
|
|
304
|
+
}
|
|
305
|
+
const operator = t.operator;
|
|
306
|
+
if (!operator || !JMETER_THRESHOLD_OPERATORS.has(operator)) {
|
|
307
|
+
throw new Error(`thresholds[${idx}].operator must be one of ${[...JMETER_THRESHOLD_OPERATORS].join(", ")}.`);
|
|
308
|
+
}
|
|
309
|
+
const stat = t.stat;
|
|
310
|
+
if (JMETER_STAT_REQUIRED_METRICS.has(metric) && !stat) {
|
|
311
|
+
throw new Error(`thresholds[${idx}]: metric '${metric}' requires 'stat' (e.g. p95, p99, avg, median, max).`);
|
|
312
|
+
}
|
|
313
|
+
if (t.value == null) {
|
|
314
|
+
throw new Error(`thresholds[${idx}].value is required.`);
|
|
315
|
+
}
|
|
316
|
+
const out = { metric, operator, value: t.value };
|
|
317
|
+
if (stat)
|
|
318
|
+
out.stat = stat;
|
|
319
|
+
if (typeof t.abort_on_fail === "boolean")
|
|
320
|
+
out.abortOnFail = t.abort_on_fail;
|
|
321
|
+
return out;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// Build the JMeter `toolConfig.jmeter` block for script or image mode.
|
|
325
|
+
// Matches JMeterSpec in loadTestManager/internal/domain/jmeter.go: mode + script{content|image|entrypoint}
|
|
326
|
+
// + tunables{workerCount} + properties[] + envVars[] + thresholds[].
|
|
327
|
+
function buildJMeterToolConfig(b, args) {
|
|
328
|
+
let mode;
|
|
329
|
+
const script = {};
|
|
330
|
+
if (args.scriptSource === "image") {
|
|
331
|
+
const scriptImage = (b.script_image ?? b.scriptImage);
|
|
332
|
+
if (scriptImage == null) {
|
|
333
|
+
throw new Error("JMeter image mode requires 'script_image'.");
|
|
334
|
+
}
|
|
335
|
+
mode = "image";
|
|
336
|
+
script.image = scriptImage;
|
|
337
|
+
const entrypoint = (b.script_entrypoint ?? b.scriptEntrypoint);
|
|
338
|
+
if (entrypoint != null)
|
|
339
|
+
script.entrypoint = entrypoint;
|
|
340
|
+
const loadArgs = (b.load_args ?? b.loadArgs);
|
|
341
|
+
if (loadArgs != null && loadArgs !== "") {
|
|
342
|
+
validateLoadArgs(loadArgs);
|
|
343
|
+
script.loadArgs = loadArgs;
|
|
344
|
+
}
|
|
345
|
+
const imagePullSecret = (b.image_pull_secret ?? b.imagePullSecret);
|
|
346
|
+
if (imagePullSecret != null && imagePullSecret !== "") {
|
|
347
|
+
script.imagePullSecret = imagePullSecret;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
if (args.script == null) {
|
|
352
|
+
throw new Error("JMeter script mode requires 'script' (the raw .jmx/.xml plan text).");
|
|
353
|
+
}
|
|
354
|
+
mode = "script";
|
|
355
|
+
script.content = Buffer.from(args.script, "utf8").toString("base64");
|
|
356
|
+
}
|
|
357
|
+
const toolConfig = { mode, script };
|
|
358
|
+
if (b.worker_count != null)
|
|
359
|
+
toolConfig.tunables = { workerCount: b.worker_count };
|
|
360
|
+
const properties = buildJMeterProperties(b.properties);
|
|
361
|
+
if (properties.length > 0)
|
|
362
|
+
toolConfig.properties = properties;
|
|
363
|
+
const envVars = buildEnvVars(b.env_vars, "JMeter");
|
|
364
|
+
if (envVars.length > 0)
|
|
365
|
+
toolConfig.envVars = envVars;
|
|
366
|
+
const thresholds = buildJMeterThresholds(b.thresholds);
|
|
367
|
+
if (thresholds.length > 0)
|
|
368
|
+
toolConfig.thresholds = thresholds;
|
|
369
|
+
return toolConfig;
|
|
370
|
+
}
|
|
371
|
+
// Validate the Locust/K6/JMeter load_args string. Mirrors ValidateLoadArgs in
|
|
372
|
+
// loadTestManager/internal/api/dto.go: semicolon-separated k=v pairs; keys
|
|
373
|
+
// must be non-empty, contain no whitespace, and not start with '-'.
|
|
374
|
+
function validateLoadArgs(loadArgs) {
|
|
375
|
+
for (const raw of loadArgs.split(";")) {
|
|
376
|
+
const pair = raw.trim();
|
|
377
|
+
if (!pair)
|
|
378
|
+
continue;
|
|
379
|
+
const eq = pair.indexOf("=");
|
|
380
|
+
const key = (eq >= 0 ? pair.slice(0, eq) : pair).trim();
|
|
381
|
+
if (!key)
|
|
382
|
+
throw new Error(`load_args: empty key in pair ${JSON.stringify(pair)}`);
|
|
383
|
+
if (/[ \t]/.test(key)) {
|
|
384
|
+
throw new Error(`load_args: key ${JSON.stringify(key)} must not contain spaces`);
|
|
385
|
+
}
|
|
386
|
+
if (key.startsWith("-")) {
|
|
387
|
+
throw new Error(`load_args: key ${JSON.stringify(key)} must not start with '-'`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Build the Locust `toolConfig.locust` block for script or image mode. Matches
|
|
392
|
+
// LocustSpec + ScriptSpec in loadTestManager: mode +
|
|
393
|
+
// script{content|image|entrypoint|loadArgs|imagePullSecret} +
|
|
394
|
+
// tunables{targetUrl, targetUsers, spawnRate, rampUpTimeSec, durationSeconds, workerCount}.
|
|
395
|
+
// NOTE: Locust intentionally has no target_type guard (unlike K6/JMeter) —
|
|
396
|
+
// loadTestManager supports Locust script AND image mode on both Linux VM and
|
|
397
|
+
// Kubernetes infra (loadtest_handlers.go has no Locust kubernetes-only check).
|
|
398
|
+
// Do not add one here.
|
|
399
|
+
function buildLocustToolConfig(b, args) {
|
|
400
|
+
let mode;
|
|
401
|
+
const script = {};
|
|
402
|
+
if (args.scriptSource === "image") {
|
|
403
|
+
const scriptImage = (b.script_image ?? b.scriptImage);
|
|
404
|
+
if (scriptImage == null) {
|
|
405
|
+
throw new Error("Locust image mode requires 'script_image'.");
|
|
406
|
+
}
|
|
407
|
+
mode = "image";
|
|
408
|
+
script.image = scriptImage;
|
|
409
|
+
const entrypoint = (b.script_entrypoint ?? b.scriptEntrypoint);
|
|
410
|
+
if (entrypoint != null)
|
|
411
|
+
script.entrypoint = entrypoint;
|
|
412
|
+
const loadArgs = (b.load_args ?? b.loadArgs);
|
|
413
|
+
if (loadArgs != null && loadArgs !== "") {
|
|
414
|
+
validateLoadArgs(loadArgs);
|
|
415
|
+
script.loadArgs = loadArgs;
|
|
416
|
+
}
|
|
417
|
+
const imagePullSecret = (b.image_pull_secret ?? b.imagePullSecret);
|
|
418
|
+
if (imagePullSecret != null && imagePullSecret !== "") {
|
|
419
|
+
script.imagePullSecret = imagePullSecret;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
if (args.script == null) {
|
|
424
|
+
throw new Error("Locust script mode requires 'script' (the raw Python locustfile).");
|
|
425
|
+
}
|
|
426
|
+
mode = "script";
|
|
427
|
+
script.content = Buffer.from(args.script, "utf8").toString("base64");
|
|
428
|
+
}
|
|
429
|
+
const tunables = {};
|
|
430
|
+
if (args.targetUrl != null)
|
|
431
|
+
tunables.targetUrl = args.targetUrl;
|
|
432
|
+
if (b.users != null)
|
|
433
|
+
tunables.targetUsers = b.users;
|
|
434
|
+
if (b.spawn_rate != null)
|
|
435
|
+
tunables.spawnRate = b.spawn_rate;
|
|
436
|
+
if (b.duration_sec != null)
|
|
437
|
+
tunables.durationSeconds = b.duration_sec;
|
|
438
|
+
if (b.ramp_up_sec != null)
|
|
439
|
+
tunables.rampUpTimeSec = b.ramp_up_sec;
|
|
440
|
+
if (b.worker_count != null)
|
|
441
|
+
tunables.workerCount = b.worker_count;
|
|
442
|
+
const toolConfig = { mode, script };
|
|
443
|
+
if (Object.keys(tunables).length > 0)
|
|
444
|
+
toolConfig.tunables = tunables;
|
|
445
|
+
return toolConfig;
|
|
446
|
+
}
|
|
242
447
|
/**
|
|
243
448
|
* Build the canonical LoadTest YAML manifest. Mirrors the studio's
|
|
244
449
|
* formDataToManifest shape (kind: LoadTest, apiVersion: v1alpha1, spec.{...}).
|
|
245
450
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
451
|
+
* The wire toolBlock carries base64 script.content; for readability the YAML
|
|
452
|
+
* view carries plain-text script.content instead. Caller base64-encodes the
|
|
453
|
+
* returned string into the `yaml` request field.
|
|
249
454
|
*/
|
|
250
455
|
function buildLoadtestYamlManifest(args) {
|
|
251
456
|
const infraType = args.targetType === "kubernetes" ? "kubernetes" : "linux";
|
|
457
|
+
const metadata = { name: args.name };
|
|
458
|
+
if (args.description)
|
|
459
|
+
metadata.description = args.description;
|
|
460
|
+
if (args.tags && args.tags.length)
|
|
461
|
+
metadata.tags = args.tags;
|
|
462
|
+
if (args.serviceReferences && args.serviceReferences.length) {
|
|
463
|
+
metadata.serviceReferences = args.serviceReferences;
|
|
464
|
+
}
|
|
252
465
|
const manifest = {
|
|
253
466
|
kind: "LoadTest",
|
|
254
467
|
apiVersion: "v1alpha1",
|
|
255
|
-
|
|
468
|
+
metadata,
|
|
256
469
|
};
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
470
|
+
// In the YAML view we want plain-text script content for readability; the
|
|
471
|
+
// wire toolConfig carries it base64. Decode a copy for the manifest.
|
|
472
|
+
const yamlToolBlock = { ...args.toolBlock };
|
|
473
|
+
const s = yamlToolBlock.script;
|
|
474
|
+
if (s?.content && typeof s.content === "string") {
|
|
475
|
+
try {
|
|
476
|
+
yamlToolBlock.script = {
|
|
477
|
+
...s,
|
|
478
|
+
content: Buffer.from(s.content, "base64").toString("utf8"),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// If the content isn't valid base64 for any reason, keep as-is.
|
|
483
|
+
}
|
|
484
|
+
}
|
|
261
485
|
const spec = {
|
|
262
486
|
identity: args.identity,
|
|
263
487
|
toolType: args.toolType,
|
|
264
488
|
infraType,
|
|
265
489
|
targetType: args.targetType,
|
|
266
|
-
|
|
490
|
+
cleanupPolicy: args.cleanupPolicy ?? "delete",
|
|
267
491
|
};
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (args.toolType === "Locust" && args.scriptSource === "inline" && args.script) {
|
|
271
|
-
spec.scriptContent = args.script;
|
|
272
|
-
}
|
|
492
|
+
if (args.resources != null)
|
|
493
|
+
spec.resources = args.resources;
|
|
273
494
|
spec.infraId = args.infraIdentifier;
|
|
274
495
|
spec.envId = args.environmentIdentifier;
|
|
275
|
-
spec.
|
|
276
|
-
// K6: emit spec.toolConfig with PLAIN TEXT scriptContent (decode the base64 from
|
|
277
|
-
// the wire-shape toolConfig so the YAML view is human-readable). Other
|
|
278
|
-
// toolConfig keys (mode, hostUrl, options, envVars, iterations) pass through.
|
|
279
|
-
if (args.toolType === "K6" && args.k6ToolConfig) {
|
|
280
|
-
const tc = { ...args.k6ToolConfig };
|
|
281
|
-
if (typeof tc.scriptContent === "string" && args.script) {
|
|
282
|
-
tc.scriptContent = args.script; // plain text for YAML readability
|
|
283
|
-
}
|
|
284
|
-
spec.toolConfig = tc;
|
|
285
|
-
}
|
|
496
|
+
spec.toolConfig = { [args.toolType.toLowerCase()]: yamlToolBlock };
|
|
286
497
|
manifest.spec = spec;
|
|
287
498
|
return YAML.stringify(manifest);
|
|
288
499
|
}
|
|
@@ -1516,7 +1727,34 @@ export const chaosToolset = {
|
|
|
1516
1727
|
scope: "project",
|
|
1517
1728
|
scopeParams: CHAOS_SCOPE,
|
|
1518
1729
|
identifierFields: ["loadtest_id"],
|
|
1730
|
+
compactItem: compactLoadTest,
|
|
1519
1731
|
deepLinkTemplate: "/ng/account/{accountId}/module/chaos/orgs/{orgIdentifier}/projects/{projectIdentifier}/load-tests/{loadtestId}",
|
|
1732
|
+
listFilterFields: [
|
|
1733
|
+
{ name: "environment_id", description: descEnvironmentId },
|
|
1734
|
+
{ name: "tool_type", description: descLoadtestType, enum: ["Locust", "K6", "JMeter"] },
|
|
1735
|
+
{ name: "tags", description: descLoadtestTags },
|
|
1736
|
+
{ name: "search", description: "Free-text search. NOTE: backend currently matches only the load test's display name (substring, case-insensitive) -- it does not match identity/slug and is not fuzzy or prefix-tolerant." },
|
|
1737
|
+
{ name: "sort_field", description: "Field to sort by (e.g. createdAt, updatedAt, name)." },
|
|
1738
|
+
{ name: "sort_ascending", description: "Sort ascending when true, descending when false.", type: "boolean" },
|
|
1739
|
+
],
|
|
1740
|
+
diagnosticHint: "Load tests are looked up by their identity slug (e.g. 'mcplocustscript001'), not the uniqueId UUID. If you only have a uniqueId, call harness_list resource_type=chaos_loadtest (optionally with search_term) and use the matching item's 'identity'/'loadtestId' field instead.",
|
|
1741
|
+
relatedResources: [
|
|
1742
|
+
{
|
|
1743
|
+
resourceType: "chaos_infrastructure",
|
|
1744
|
+
relationship: "prerequisite",
|
|
1745
|
+
description: "Linux VM load-runner infrastructure. When the load test targets Linux VM (target_type='machine-chaos-linux', the default), its 'environmentID' supplies environment_id and its 'infraID' supplies infra_id. Only loadEnabled + ACTIVE infras are usable. In MCP, Linux VM supports tool_type Locust only -- K6 (a genuine backend restriction) and JMeter (an MCP/UI-parity restriction; the backend itself allows Linux JMeter) with a Linux target_type are rejected by MCP.",
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
resourceType: "chaos_enabled_infrastructure",
|
|
1749
|
+
relationship: "prerequisite",
|
|
1750
|
+
description: "Kubernetes load-runner infrastructure. When the load test targets Kubernetes, filter this list by infra_type='KubernetesV2'; its 'environmentID' supplies environment_id and its 'infraID' supplies infra_id. Kubernetes infra supports tool_type Locust, K6, or JMeter. IMPORTANT: target_type MUST be explicitly set to 'kubernetes' -- it is not auto-derived from which infra list you picked from; omitting it defaults to Linux naming and dispatches wrong.",
|
|
1751
|
+
},
|
|
1752
|
+
{
|
|
1753
|
+
resourceType: "chaos_service",
|
|
1754
|
+
relationship: "prerequisite",
|
|
1755
|
+
description: "Resilience Testing Services attached to a load test. List with infrastructure_ids='<environment_id>/<infra_id>' to see services already onboarded on the chosen load-runner infra. A user may pick one or more identities from the list, OR onboard a brand-new service via chaos_service's create flow (see chaos_service.relatedResources for the discovered_agent/discovered_service chain) regardless of whether the list is empty -- creating a new service is a user-intent decision, not gated on list length. The chosen identity/identities are passed on the load test as service_references (required when the account has CHAOS_RISK_SERVICES_ENABLED on).",
|
|
1756
|
+
},
|
|
1757
|
+
],
|
|
1520
1758
|
operations: {
|
|
1521
1759
|
list: {
|
|
1522
1760
|
method: "GET",
|
|
@@ -1529,6 +1767,10 @@ export const chaosToolset = {
|
|
|
1529
1767
|
search: "search",
|
|
1530
1768
|
search_term: "search",
|
|
1531
1769
|
environment_id: "environmentIdentifier",
|
|
1770
|
+
tool_type: "toolType",
|
|
1771
|
+
tags: "tags",
|
|
1772
|
+
sort_field: "sortField",
|
|
1773
|
+
sort_ascending: "sortAscending",
|
|
1532
1774
|
},
|
|
1533
1775
|
responseExtractor: chaosLoadTestListExtract,
|
|
1534
1776
|
description: descListLoadtests,
|
|
@@ -1548,45 +1790,33 @@ export const chaosToolset = {
|
|
|
1548
1790
|
skipScopeBodyInjection: true,
|
|
1549
1791
|
bodyBuilder: (input) => {
|
|
1550
1792
|
const b = coerceBody(input);
|
|
1793
|
+
rejectLegacyLoadtestFields(b);
|
|
1551
1794
|
const name = b.name ?? "";
|
|
1552
1795
|
if (!name) {
|
|
1553
1796
|
throw new Error("name is required.");
|
|
1554
1797
|
}
|
|
1555
1798
|
// identity is the slug-constrained key; auto-derive from name when omitted.
|
|
1556
|
-
// The display name is permissive (any non-empty string) — only the identity
|
|
1557
|
-
// must be a slug. We strip non-alphanumerics from name as a sensible default;
|
|
1558
|
-
// a fully-blank slug falls back to a UUID so we never write an empty id.
|
|
1559
1799
|
const slug = (s) => s.replace(/[^a-zA-Z0-9]/g, "");
|
|
1560
1800
|
const identity = b.identity || slug(name) || randomUUID();
|
|
1561
1801
|
if (!/^[a-zA-Z0-9_]+$/.test(identity)) {
|
|
1562
1802
|
throw new Error(`Invalid identity '${identity}': only letters, numbers and underscores are allowed (derived from name when omitted).`);
|
|
1563
1803
|
}
|
|
1804
|
+
// Tool type: Locust (default), K6, or JMeter. Gatling/Custom are deprecated
|
|
1805
|
+
// and the backend now rejects them with a 400.
|
|
1806
|
+
const toolType = (b.tool_type ?? "Locust");
|
|
1807
|
+
if (toolType !== "Locust" && toolType !== "K6" && toolType !== "JMeter") {
|
|
1808
|
+
throw new Error(`tool_type '${toolType}' must be 'Locust', 'K6', or 'JMeter'.`);
|
|
1809
|
+
}
|
|
1564
1810
|
const targetType = b.target_type ?? "machine-chaos-linux";
|
|
1811
|
+
if ((toolType === "K6" || toolType === "JMeter") && targetType !== "kubernetes") {
|
|
1812
|
+
throw new Error(`${toolType} load tests require target_type='kubernetes'.`);
|
|
1813
|
+
}
|
|
1565
1814
|
const environmentIdentifier = (b.environment_id ?? b.environmentIdentifier);
|
|
1566
1815
|
const infraIdentifier = (b.infra_id ?? b.infraIdentifier);
|
|
1567
1816
|
const targetUrl = (b.target_url ?? b.targetUrl);
|
|
1568
1817
|
const script = b.script;
|
|
1569
1818
|
const scriptImage = (b.script_image ?? b.scriptImage);
|
|
1570
|
-
// Explicit script_source wins, else infer "image" when an image is supplied.
|
|
1571
1819
|
const scriptSource = b.script_source ?? (scriptImage != null ? "image" : "inline");
|
|
1572
|
-
// Mode-specific required fields (fail loudly before we touch the wire).
|
|
1573
|
-
// Tool-agnostic message: Locust = Python locust source, K6 = JavaScript with 'export default'.
|
|
1574
|
-
// The K6-specific 'export default' rule is enforced later in buildK6ToolConfig.
|
|
1575
|
-
if (scriptSource === "inline" && script == null) {
|
|
1576
|
-
throw new Error("script is required when script_source='inline'.");
|
|
1577
|
-
}
|
|
1578
|
-
if (scriptSource === "image" && scriptImage == null) {
|
|
1579
|
-
throw new Error("script_image is required when script_source='image'.");
|
|
1580
|
-
}
|
|
1581
|
-
// Tool type: Locust (default) or K6. K6 is Kubernetes-only and supports
|
|
1582
|
-
// script and image modes (UI mode is deferred).
|
|
1583
|
-
const toolType = (b.tool_type ?? "Locust");
|
|
1584
|
-
if (toolType !== "Locust" && toolType !== "K6") {
|
|
1585
|
-
throw new Error(`tool_type '${toolType}' must be 'Locust' or 'K6'.`);
|
|
1586
|
-
}
|
|
1587
|
-
if (toolType === "K6" && targetType !== "kubernetes") {
|
|
1588
|
-
throw new Error("K6 load tests require target_type='kubernetes' (LinuxVM is not supported).");
|
|
1589
|
-
}
|
|
1590
1820
|
// Normalise tags (accept array or comma-separated string; default []).
|
|
1591
1821
|
const rawTags = b.tags;
|
|
1592
1822
|
const tags = Array.isArray(rawTags)
|
|
@@ -1594,33 +1824,28 @@ export const chaosToolset = {
|
|
|
1594
1824
|
: typeof rawTags === "string"
|
|
1595
1825
|
? rawTags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
1596
1826
|
: [];
|
|
1597
|
-
// Build
|
|
1598
|
-
//
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
// Built up-front so the same value drives both the JSON body and the YAML view.
|
|
1602
|
-
let k6ToolConfig;
|
|
1827
|
+
// Build toolConfig.<tool>. JMeter builds from scalars by default;
|
|
1828
|
+
// 'tool_config' remains an escape hatch for advanced/back-compat callers
|
|
1829
|
+
// (e.g. .zip bundles) who want to hand-construct the object.
|
|
1830
|
+
let toolBlock;
|
|
1603
1831
|
if (toolType === "K6") {
|
|
1604
|
-
|
|
1832
|
+
toolBlock = buildK6ToolConfig(b, { scriptSource, script, targetUrl });
|
|
1833
|
+
}
|
|
1834
|
+
else if (toolType === "Locust") {
|
|
1835
|
+
toolBlock = buildLocustToolConfig(b, { scriptSource, script, targetUrl });
|
|
1836
|
+
}
|
|
1837
|
+
else {
|
|
1838
|
+
const supplied = b.tool_config;
|
|
1839
|
+
toolBlock = supplied != null
|
|
1840
|
+
? (supplied.jmeter ?? supplied)
|
|
1841
|
+
: buildJMeterToolConfig(b, { scriptSource, script });
|
|
1842
|
+
}
|
|
1843
|
+
const toolKey = toolType.toLowerCase();
|
|
1844
|
+
const toolConfig = { [toolKey]: toolBlock };
|
|
1845
|
+
// Variables live under toolConfig.<tool>.variables.
|
|
1846
|
+
if (Array.isArray(b.variables)) {
|
|
1847
|
+
toolBlock.variables = b.variables;
|
|
1605
1848
|
}
|
|
1606
|
-
// Build the canonical LoadTest YAML manifest, then base64-encode for the wire.
|
|
1607
|
-
// The manifest carries plain-text scriptContent (Locust inline) OR plain-text
|
|
1608
|
-
// toolConfig.scriptContent (K6), so the YAML view is human-readable; the JSON
|
|
1609
|
-
// body's base64 lives only on the wire.
|
|
1610
|
-
const yamlManifest = buildLoadtestYamlManifest({
|
|
1611
|
-
name,
|
|
1612
|
-
description: b.description,
|
|
1613
|
-
tags,
|
|
1614
|
-
identity,
|
|
1615
|
-
toolType,
|
|
1616
|
-
targetType,
|
|
1617
|
-
scriptSource,
|
|
1618
|
-
script,
|
|
1619
|
-
k6ToolConfig,
|
|
1620
|
-
environmentIdentifier: environmentIdentifier,
|
|
1621
|
-
infraIdentifier: infraIdentifier,
|
|
1622
|
-
inputs,
|
|
1623
|
-
});
|
|
1624
1849
|
const body = {
|
|
1625
1850
|
identity,
|
|
1626
1851
|
name,
|
|
@@ -1628,35 +1853,42 @@ export const chaosToolset = {
|
|
|
1628
1853
|
tags,
|
|
1629
1854
|
environmentIdentifier,
|
|
1630
1855
|
infraIdentifier,
|
|
1631
|
-
scriptSource,
|
|
1632
1856
|
targetType,
|
|
1633
1857
|
toolType,
|
|
1634
|
-
|
|
1635
|
-
yaml: Buffer.from(yamlManifest, "utf8").toString("base64"),
|
|
1858
|
+
toolConfig,
|
|
1636
1859
|
};
|
|
1637
|
-
//
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
if (
|
|
1645
|
-
body.
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1860
|
+
// Optional pass-through fields.
|
|
1861
|
+
if (b.service_references != null)
|
|
1862
|
+
body.serviceReferences = b.service_references;
|
|
1863
|
+
if (b.cleanup_policy != null)
|
|
1864
|
+
body.cleanupPolicy = b.cleanup_policy;
|
|
1865
|
+
if (b.max_duration_sec != null)
|
|
1866
|
+
body.maxDurationSec = b.max_duration_sec;
|
|
1867
|
+
if (b.resources != null)
|
|
1868
|
+
body.resources = b.resources;
|
|
1869
|
+
// Optional canonical YAML manifest (backend accepts it as an alternative
|
|
1870
|
+
// source; MCP still sends toolConfig authoritatively).
|
|
1871
|
+
const yamlManifest = buildLoadtestYamlManifest({
|
|
1872
|
+
name,
|
|
1873
|
+
description: b.description,
|
|
1874
|
+
tags,
|
|
1875
|
+
serviceReferences: body.serviceReferences,
|
|
1876
|
+
identity,
|
|
1877
|
+
toolType,
|
|
1878
|
+
targetType,
|
|
1879
|
+
toolBlock,
|
|
1880
|
+
environmentIdentifier: environmentIdentifier,
|
|
1881
|
+
infraIdentifier: infraIdentifier,
|
|
1882
|
+
cleanupPolicy: body.cleanupPolicy,
|
|
1883
|
+
resources: body.resources,
|
|
1884
|
+
});
|
|
1885
|
+
body.yaml = Buffer.from(yamlManifest, "utf8").toString("base64");
|
|
1886
|
+
// Snake-case aliases for the registry's required-field validator (which
|
|
1887
|
+
// checks the built body against the snake_case bodySchema field names).
|
|
1654
1888
|
if (environmentIdentifier != null)
|
|
1655
1889
|
body.environment_id = environmentIdentifier;
|
|
1656
1890
|
if (infraIdentifier != null)
|
|
1657
1891
|
body.infra_id = infraIdentifier;
|
|
1658
|
-
if (targetUrl != null)
|
|
1659
|
-
body.target_url = targetUrl;
|
|
1660
1892
|
return body;
|
|
1661
1893
|
},
|
|
1662
1894
|
responseExtractor: chaosLoadTestExtract,
|
|
@@ -1667,18 +1899,158 @@ export const chaosToolset = {
|
|
|
1667
1899
|
{ name: "name", type: "string", required: true, description: descLoadtestName },
|
|
1668
1900
|
{ name: "environment_id", type: "string", required: true, description: descLoadtestEnvId },
|
|
1669
1901
|
{ name: "infra_id", type: "string", required: true, description: descLoadtestInfraId },
|
|
1670
|
-
{ name: "
|
|
1671
|
-
{ name: "
|
|
1902
|
+
{ name: "identity", type: "string", required: false, description: descLoadtestIdentity },
|
|
1903
|
+
{ name: "description", type: "string", required: false, description: descLoadtestDescription },
|
|
1904
|
+
{ name: "tags", type: "array", required: false, description: descLoadtestTags },
|
|
1905
|
+
{ name: "target_type", type: "string", required: false, description: descLoadtestTargetType },
|
|
1906
|
+
{ name: "tool_type", type: "string", required: false, description: descLoadtestType },
|
|
1907
|
+
{ name: "target_url", type: "string", required: false, description: descLoadtestTargetUrl },
|
|
1672
1908
|
{ name: "script_source", type: "string", required: false, description: descLoadtestScriptSource },
|
|
1909
|
+
{ name: "script", type: "string", required: false, description: descLoadtestScript },
|
|
1673
1910
|
{ name: "script_image", type: "string", required: false, description: descLoadtestScriptImage },
|
|
1674
1911
|
{ name: "script_entrypoint", type: "string", required: false, description: descLoadtestScriptEntrypoint },
|
|
1675
1912
|
{ name: "load_args", type: "string", required: false, description: descLoadtestLoadArgs },
|
|
1676
|
-
{ name: "
|
|
1913
|
+
{ name: "image_pull_secret", type: "string", required: false, description: descLoadtestImagePullSecret },
|
|
1914
|
+
{ name: "users", type: "number", required: false, description: descLoadtestUsers },
|
|
1915
|
+
{ name: "spawn_rate", type: "number", required: false, description: "Locust spawn rate (users/sec)." },
|
|
1916
|
+
{ name: "duration_sec", type: "number", required: false, description: descLoadtestDurationSec },
|
|
1917
|
+
{ name: "ramp_up_sec", type: "number", required: false, description: descLoadtestRampUpSec },
|
|
1918
|
+
{ name: "worker_count", type: "number", required: false, description: descLoadtestWorkerCount },
|
|
1919
|
+
{ name: "host_url", type: "string", required: false, description: descLoadtestHostUrl },
|
|
1920
|
+
{ name: "rps_limit", type: "number", required: false, description: descLoadtestRpsLimit },
|
|
1921
|
+
{ name: "iterations", type: "number", required: false, description: descLoadtestIterations },
|
|
1922
|
+
{ name: "env_vars", type: "array", required: false, description: descLoadtestEnvVars },
|
|
1923
|
+
{ name: "properties", type: "array", required: false, description: descLoadtestProperties },
|
|
1924
|
+
{ name: "thresholds", type: "array", required: false, description: descLoadtestThresholds },
|
|
1925
|
+
{ name: "variables", type: "array", required: false, description: "Custom template.Variable entries stored under toolConfig.<tool>.variables." },
|
|
1926
|
+
{ name: "tool_config", type: "object", required: false, description: "JMeter-only pass-through toolConfig.jmeter object -- escape hatch for advanced/back-compat use (e.g. .zip test-plan bundles). Ignored for K6/Locust on create (use the scalar fields for those); not required for JMeter either -- prefer script/script_image/properties/env_vars/thresholds/worker_count unless you need this override." },
|
|
1927
|
+
{ name: "service_references", type: "array", required: false, description: "chaosService identity strings; required when CHAOS_RISK_SERVICES_ENABLED is on." },
|
|
1928
|
+
{ name: "cleanup_policy", type: "string", required: false, description: descLoadtestCleanupPolicy },
|
|
1929
|
+
{ name: "max_duration_sec", type: "number", required: false, description: "Per-load-test hard cap on any run." },
|
|
1930
|
+
{ name: "resources", type: "object", required: false, description: descLoadtestResources },
|
|
1931
|
+
],
|
|
1932
|
+
},
|
|
1933
|
+
},
|
|
1934
|
+
update: {
|
|
1935
|
+
method: "PUT",
|
|
1936
|
+
path: `${CHAOS_LOADTEST}/v1/load-tests/{loadtestId}`,
|
|
1937
|
+
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
1938
|
+
pathParams: { loadtest_id: "loadtestId" },
|
|
1939
|
+
skipScopeBodyInjection: true,
|
|
1940
|
+
bodyBuilder: (input) => {
|
|
1941
|
+
const b = coerceBody(input);
|
|
1942
|
+
rejectLegacyLoadtestFields(b);
|
|
1943
|
+
const body = {};
|
|
1944
|
+
if (b.name != null)
|
|
1945
|
+
body.name = b.name;
|
|
1946
|
+
if (b.description != null)
|
|
1947
|
+
body.description = b.description;
|
|
1948
|
+
if (b.tags != null)
|
|
1949
|
+
body.tags = b.tags;
|
|
1950
|
+
if (b.service_references != null)
|
|
1951
|
+
body.serviceReferences = b.service_references;
|
|
1952
|
+
if (b.environment_id != null)
|
|
1953
|
+
body.environmentIdentifier = b.environment_id;
|
|
1954
|
+
if (b.infra_id != null)
|
|
1955
|
+
body.infraIdentifier = b.infra_id;
|
|
1956
|
+
if (b.target_type != null)
|
|
1957
|
+
body.targetType = b.target_type;
|
|
1958
|
+
if (b.max_duration_sec != null)
|
|
1959
|
+
body.maxDurationSec = b.max_duration_sec;
|
|
1960
|
+
if (b.cleanup_policy != null)
|
|
1961
|
+
body.cleanupPolicy = b.cleanup_policy;
|
|
1962
|
+
if (b.resources != null)
|
|
1963
|
+
body.resources = b.resources;
|
|
1964
|
+
// tool_type is never sent to the API (immutable after creation) --
|
|
1965
|
+
// used only internally to pick the right scalar builder / wrap key.
|
|
1966
|
+
const toolType = b.tool_type;
|
|
1967
|
+
const toolConfigEscapeHatch = b.tool_config;
|
|
1968
|
+
// Same scalar surface as create -- if any of these are present,
|
|
1969
|
+
// toolConfig.<tool> must be rebuilt wholesale (server does a full
|
|
1970
|
+
// overwrite; see UpdateLoadTestRequest.ToolConfig in loadTestManager).
|
|
1971
|
+
const SCALAR_TOOL_FIELDS = [
|
|
1972
|
+
"target_url", "script_source", "script", "script_image", "script_entrypoint",
|
|
1973
|
+
"load_args", "image_pull_secret", "users", "spawn_rate", "duration_sec",
|
|
1974
|
+
"ramp_up_sec", "worker_count", "host_url", "rps_limit", "iterations",
|
|
1975
|
+
"env_vars", "properties", "thresholds",
|
|
1976
|
+
];
|
|
1977
|
+
const hasScalarToolField = SCALAR_TOOL_FIELDS.some((f) => b[f] != null);
|
|
1978
|
+
if (hasScalarToolField) {
|
|
1979
|
+
if (toolType == null) {
|
|
1980
|
+
throw new Error("tool_type is required to edit script/tunables/properties/thresholds/env_vars on update -- call harness_get first if you don't know the existing tool_type.");
|
|
1981
|
+
}
|
|
1982
|
+
if (toolType !== "Locust" && toolType !== "K6" && toolType !== "JMeter") {
|
|
1983
|
+
throw new Error(`tool_type '${toolType}' must be 'Locust', 'K6', or 'JMeter'.`);
|
|
1984
|
+
}
|
|
1985
|
+
const targetUrl = (b.target_url ?? b.targetUrl);
|
|
1986
|
+
const script = b.script;
|
|
1987
|
+
const scriptImage = (b.script_image ?? b.scriptImage);
|
|
1988
|
+
const scriptSource = b.script_source ?? (scriptImage != null ? "image" : "inline");
|
|
1989
|
+
let toolBlock;
|
|
1990
|
+
if (toolType === "K6") {
|
|
1991
|
+
toolBlock = buildK6ToolConfig(b, { scriptSource, script, targetUrl });
|
|
1992
|
+
}
|
|
1993
|
+
else if (toolType === "Locust") {
|
|
1994
|
+
toolBlock = buildLocustToolConfig(b, { scriptSource, script, targetUrl });
|
|
1995
|
+
}
|
|
1996
|
+
else {
|
|
1997
|
+
toolBlock = buildJMeterToolConfig(b, { scriptSource, script });
|
|
1998
|
+
}
|
|
1999
|
+
// Mirrors create: variables live under toolConfig.<tool>.variables,
|
|
2000
|
+
// which ReconcileToolConfigVariables prefers over the top-level
|
|
2001
|
+
// field -- do not also set body.variables to avoid two sources.
|
|
2002
|
+
if (Array.isArray(b.variables)) {
|
|
2003
|
+
toolBlock.variables = b.variables;
|
|
2004
|
+
}
|
|
2005
|
+
body.toolConfig = { [toolType.toLowerCase()]: toolBlock };
|
|
2006
|
+
}
|
|
2007
|
+
else if (toolConfigEscapeHatch != null) {
|
|
2008
|
+
if (toolType != null) {
|
|
2009
|
+
const toolKey = toolType.toLowerCase();
|
|
2010
|
+
const inner = toolConfigEscapeHatch[toolKey] ??
|
|
2011
|
+
toolConfigEscapeHatch;
|
|
2012
|
+
body.toolConfig = { [toolKey]: inner };
|
|
2013
|
+
}
|
|
2014
|
+
else {
|
|
2015
|
+
// Back-compat: caller already sends the fully-wired { <tool>: {...} } shape.
|
|
2016
|
+
body.toolConfig = toolConfigEscapeHatch;
|
|
2017
|
+
}
|
|
2018
|
+
if (b.variables != null)
|
|
2019
|
+
body.variables = b.variables;
|
|
2020
|
+
}
|
|
2021
|
+
else if (b.variables != null) {
|
|
2022
|
+
// No toolConfig rebuild in this call -- variables-only update,
|
|
2023
|
+
// top-level field (server falls back to it when
|
|
2024
|
+
// toolConfig.<tool>.variables is empty).
|
|
2025
|
+
body.variables = b.variables;
|
|
2026
|
+
}
|
|
2027
|
+
return body;
|
|
2028
|
+
},
|
|
2029
|
+
responseExtractor: chaosLoadTestExtract,
|
|
2030
|
+
description: descUpdateLoadtest,
|
|
2031
|
+
bodySchema: {
|
|
2032
|
+
description: descUpdateLoadtest,
|
|
2033
|
+
fields: [
|
|
2034
|
+
{ name: "name", type: "string", required: false, description: descLoadtestName },
|
|
1677
2035
|
{ name: "description", type: "string", required: false, description: descLoadtestDescription },
|
|
1678
2036
|
{ name: "tags", type: "array", required: false, description: descLoadtestTags },
|
|
2037
|
+
{ name: "service_references", type: "array", required: false, description: "chaosService identity strings; a non-null slice must be non-empty." },
|
|
2038
|
+
{ name: "environment_id", type: "string", required: false, description: descLoadtestEnvId },
|
|
2039
|
+
{ name: "infra_id", type: "string", required: false, description: descLoadtestInfraId },
|
|
1679
2040
|
{ name: "target_type", type: "string", required: false, description: descLoadtestTargetType },
|
|
2041
|
+
{ name: "max_duration_sec", type: "number", required: false, description: "Per-load-test hard cap on any run." },
|
|
2042
|
+
{ name: "cleanup_policy", type: "string", required: false, description: descLoadtestCleanupPolicy },
|
|
2043
|
+
{ name: "resources", type: "object", required: false, description: descLoadtestResources },
|
|
1680
2044
|
{ name: "tool_type", type: "string", required: false, description: descLoadtestType },
|
|
2045
|
+
{ name: "target_url", type: "string", required: false, description: descLoadtestTargetUrl },
|
|
2046
|
+
{ name: "script_source", type: "string", required: false, description: descLoadtestScriptSource },
|
|
2047
|
+
{ name: "script", type: "string", required: false, description: descLoadtestScript },
|
|
2048
|
+
{ name: "script_image", type: "string", required: false, description: descLoadtestScriptImage },
|
|
2049
|
+
{ name: "script_entrypoint", type: "string", required: false, description: descLoadtestScriptEntrypoint },
|
|
2050
|
+
{ name: "load_args", type: "string", required: false, description: descLoadtestLoadArgs },
|
|
2051
|
+
{ name: "image_pull_secret", type: "string", required: false, description: descLoadtestImagePullSecret },
|
|
1681
2052
|
{ name: "users", type: "number", required: false, description: descLoadtestUsers },
|
|
2053
|
+
{ name: "spawn_rate", type: "number", required: false, description: "Locust spawn rate (users/sec)." },
|
|
1682
2054
|
{ name: "duration_sec", type: "number", required: false, description: descLoadtestDurationSec },
|
|
1683
2055
|
{ name: "ramp_up_sec", type: "number", required: false, description: descLoadtestRampUpSec },
|
|
1684
2056
|
{ name: "worker_count", type: "number", required: false, description: descLoadtestWorkerCount },
|
|
@@ -1686,6 +2058,10 @@ export const chaosToolset = {
|
|
|
1686
2058
|
{ name: "rps_limit", type: "number", required: false, description: descLoadtestRpsLimit },
|
|
1687
2059
|
{ name: "iterations", type: "number", required: false, description: descLoadtestIterations },
|
|
1688
2060
|
{ name: "env_vars", type: "array", required: false, description: descLoadtestEnvVars },
|
|
2061
|
+
{ name: "properties", type: "array", required: false, description: descLoadtestProperties },
|
|
2062
|
+
{ name: "thresholds", type: "array", required: false, description: descLoadtestThresholds },
|
|
2063
|
+
{ name: "tool_config", type: "object", required: false, description: "Advanced escape hatch to hand-construct toolConfig.<tool> directly (e.g. JMeter .zip bundles) instead of the scalar fields above. Pass 'tool_type' alongside it so MCP wraps/unwraps it consistently with create; without 'tool_type' it is sent to the API exactly as given (must already be the full '{ <tool>: {...} }' wire shape). Full replacement of toolConfig.<tool> either way -- null/omit to keep existing." },
|
|
2064
|
+
{ name: "variables", type: "array", required: false, description: "Full replacement variables list; null to keep existing. When also editing script/tunables via the scalar fields above, this is nested into toolConfig.<tool>.variables instead of sent top-level (mirrors create)." },
|
|
1689
2065
|
],
|
|
1690
2066
|
},
|
|
1691
2067
|
},
|
|
@@ -1704,13 +2080,32 @@ export const chaosToolset = {
|
|
|
1704
2080
|
path: `${CHAOS_LOADTEST}/v1/load-tests/{loadtestId}/runs`,
|
|
1705
2081
|
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
1706
2082
|
pathParams: { loadtest_id: "loadtestId" },
|
|
1707
|
-
//
|
|
1708
|
-
//
|
|
1709
|
-
|
|
1710
|
-
|
|
2083
|
+
// CreateLoadTestRunRequest.Identity is binding:"required" — send a UUID
|
|
2084
|
+
// when the caller doesn't supply one so ShouldBindJSON succeeds.
|
|
2085
|
+
bodyBuilder: (input) => {
|
|
2086
|
+
const b = coerceBody(input);
|
|
2087
|
+
const body = {
|
|
2088
|
+
identity: b.run_identity ?? randomUUID(),
|
|
2089
|
+
};
|
|
2090
|
+
if (b.run_name != null)
|
|
2091
|
+
body.name = b.run_name;
|
|
2092
|
+
if (b.values != null)
|
|
2093
|
+
body.values = b.values;
|
|
2094
|
+
if (b.runtime_values != null)
|
|
2095
|
+
body.runtimeValues = b.runtime_values;
|
|
2096
|
+
return body;
|
|
2097
|
+
},
|
|
1711
2098
|
responseExtractor: passthrough,
|
|
1712
2099
|
actionDescription: descRunLoadtest,
|
|
1713
|
-
bodySchema: {
|
|
2100
|
+
bodySchema: {
|
|
2101
|
+
description: "Run parameters (all optional).",
|
|
2102
|
+
fields: [
|
|
2103
|
+
{ name: "run_identity", type: "string", required: false, description: "Client-supplied identity for the run; a UUID is generated when omitted." },
|
|
2104
|
+
{ name: "run_name", type: "string", required: false, description: "Optional display name for the run." },
|
|
2105
|
+
{ name: "values", type: "array", required: false, description: "Runtime override values for load-test Variables (template.InputMinimum entries)." },
|
|
2106
|
+
{ name: "runtime_values", type: "object", required: false, description: "Path-keyed runtime overrides for toolConfig '<+input>' leaves." },
|
|
2107
|
+
],
|
|
2108
|
+
},
|
|
1714
2109
|
},
|
|
1715
2110
|
stop: {
|
|
1716
2111
|
method: "POST",
|
|
@@ -1724,6 +2119,259 @@ export const chaosToolset = {
|
|
|
1724
2119
|
},
|
|
1725
2120
|
},
|
|
1726
2121
|
},
|
|
2122
|
+
// ── Chaos Service (Service Management) ────────────────────────────
|
|
2123
|
+
// v3 REST endpoints under the chaos manager:
|
|
2124
|
+
// GET /v3/chaos-services
|
|
2125
|
+
// GET /v3/chaos-services/{identity}
|
|
2126
|
+
// POST /v3/chaos-services
|
|
2127
|
+
// PUT /v3/chaos-services/{identity}
|
|
2128
|
+
// DELETE /v3/chaos-services/{identity}
|
|
2129
|
+
// List response envelope is
|
|
2130
|
+
// { data: [...], correlationID, pagination: { totalItems, ... } }
|
|
2131
|
+
// (distinct from the load-test v1 envelope of { items, pagination }).
|
|
2132
|
+
// Create and update return the full ChaosServiceResponse. Update is a
|
|
2133
|
+
// full-replace on mutable fields plus desired-state reconcile of probes.
|
|
2134
|
+
// Delete soft-deletes the service and purges its probe mappings, returning
|
|
2135
|
+
// { success, correlationID }.
|
|
2136
|
+
{
|
|
2137
|
+
resourceType: "chaos_service",
|
|
2138
|
+
displayName: "Chaos Service",
|
|
2139
|
+
description: descChaosService,
|
|
2140
|
+
toolset: "chaos",
|
|
2141
|
+
scope: "project",
|
|
2142
|
+
scopeParams: CHAOS_SCOPE,
|
|
2143
|
+
identifierFields: ["identity"],
|
|
2144
|
+
deepLinkTemplate: "/ng/account/{accountId}/module/chaos/orgs/{orgIdentifier}/projects/{projectIdentifier}/risks/services/{identity}",
|
|
2145
|
+
listFilterFields: [
|
|
2146
|
+
{ name: "environment_ids", description: descChaosServiceEnvironmentIds },
|
|
2147
|
+
{ name: "infrastructure_ids", description: descChaosServiceInfrastructureIds },
|
|
2148
|
+
{ name: "tags", description: descChaosServiceTags },
|
|
2149
|
+
{ name: "include_all_scope", description: descChaosServiceIncludeAllScope, type: "boolean" },
|
|
2150
|
+
{ name: "search", description: descChaosServiceSearch },
|
|
2151
|
+
{ name: "probe_ids", description: descChaosServiceProbeIds },
|
|
2152
|
+
{ name: "onboarding_id", description: descChaosServiceOnboardingIdFilter },
|
|
2153
|
+
],
|
|
2154
|
+
relatedResources: [
|
|
2155
|
+
{ resourceType: "discovered_agent", relationship: "prerequisite", description: "STEP 1 of create: pick the Service Discovery agent. Its 'identity' becomes agent_id, its environmentIdentifier becomes environment_id, and (for SD K8s) its identity is also the bare infrastructure_id." },
|
|
2156
|
+
{ resourceType: "discovered_namespace", relationship: "filters", description: "Optional STEP 2a: list namespaces for the chosen agent to narrow the discovered_service list before picking a service." },
|
|
2157
|
+
{ resourceType: "discovered_service", relationship: "prerequisite", description: "STEP 2b of create: pick the service to onboard. Its 'id' becomes external_service_id (the server derives serviceType/namespace from it)." },
|
|
2158
|
+
{ resourceType: "chaos_probe", relationship: "associates", description: "STEP 4 of create: optional health-check probes to attach. Source probe identities and their inputs[] schema here; fill each input value before adding to the create body's probes array." },
|
|
2159
|
+
],
|
|
2160
|
+
operations: {
|
|
2161
|
+
list: {
|
|
2162
|
+
method: "GET",
|
|
2163
|
+
path: `${CHAOS}/v3/chaos-services`,
|
|
2164
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
2165
|
+
queryParams: {
|
|
2166
|
+
page: "page",
|
|
2167
|
+
limit: "limit",
|
|
2168
|
+
size: "limit",
|
|
2169
|
+
search: "search",
|
|
2170
|
+
search_term: "search",
|
|
2171
|
+
environment_ids: "environmentIds",
|
|
2172
|
+
infrastructure_ids: "infrastructureIds",
|
|
2173
|
+
tags: "tags",
|
|
2174
|
+
include_all_scope: "includeAllScope",
|
|
2175
|
+
sort_field: "sortField",
|
|
2176
|
+
sort_ascending: "sortAscending",
|
|
2177
|
+
probe_ids: "probeIds",
|
|
2178
|
+
onboarding_id: "onboardingId",
|
|
2179
|
+
},
|
|
2180
|
+
responseExtractor: chaosServiceListExtract,
|
|
2181
|
+
description: descListChaosServices,
|
|
2182
|
+
},
|
|
2183
|
+
get: {
|
|
2184
|
+
method: "GET",
|
|
2185
|
+
path: `${CHAOS}/v3/chaos-services/{identity}`,
|
|
2186
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
2187
|
+
pathParams: { identity: "identity" },
|
|
2188
|
+
responseExtractor: passthrough,
|
|
2189
|
+
description: descGetChaosService,
|
|
2190
|
+
},
|
|
2191
|
+
delete: {
|
|
2192
|
+
method: "DELETE",
|
|
2193
|
+
path: `${CHAOS}/v3/chaos-services/{identity}`,
|
|
2194
|
+
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
2195
|
+
pathParams: { identity: "identity" },
|
|
2196
|
+
responseExtractor: passthrough,
|
|
2197
|
+
description: descDeleteChaosService,
|
|
2198
|
+
},
|
|
2199
|
+
create: {
|
|
2200
|
+
method: "POST",
|
|
2201
|
+
path: `${CHAOS}/v3/chaos-services`,
|
|
2202
|
+
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
2203
|
+
bodyBuilder: (input) => {
|
|
2204
|
+
const b = coerceBody(input);
|
|
2205
|
+
const externalServiceId = b.external_service_id ?? b.externalServiceId;
|
|
2206
|
+
const agentId = b.agent_id ?? b.agentId;
|
|
2207
|
+
const environmentId = b.environment_id ?? b.environmentId;
|
|
2208
|
+
const infrastructureId = b.infrastructure_id ?? b.infrastructureId;
|
|
2209
|
+
const infrastructureType = b.infrastructure_type ?? b.infrastructureType;
|
|
2210
|
+
const onboardingId = b.onboarding_id ?? b.onboardingId;
|
|
2211
|
+
const tags = b.tags;
|
|
2212
|
+
const probes = b.probes?.map((p) => ({
|
|
2213
|
+
probeId: p.probeId ?? p.probe_id,
|
|
2214
|
+
...(p.inputs ? { inputs: p.inputs } : {}),
|
|
2215
|
+
}));
|
|
2216
|
+
return {
|
|
2217
|
+
identity: b.identity,
|
|
2218
|
+
name: b.name,
|
|
2219
|
+
...(b.description ? { description: b.description } : {}),
|
|
2220
|
+
...(tags
|
|
2221
|
+
? {
|
|
2222
|
+
tags: Array.isArray(tags)
|
|
2223
|
+
? tags
|
|
2224
|
+
: String(tags)
|
|
2225
|
+
.split(",")
|
|
2226
|
+
.map((t) => t.trim())
|
|
2227
|
+
.filter(Boolean),
|
|
2228
|
+
}
|
|
2229
|
+
: {}),
|
|
2230
|
+
externalServiceId,
|
|
2231
|
+
agentId,
|
|
2232
|
+
environmentId,
|
|
2233
|
+
infrastructureId,
|
|
2234
|
+
// Dual-write snake_case keys so registry required-field validation
|
|
2235
|
+
// (which checks bodySchema.fields names against this transformed
|
|
2236
|
+
// payload) sees the fields it expects. See getBodySchemaValidationPayload
|
|
2237
|
+
// in registry/index.ts.
|
|
2238
|
+
external_service_id: externalServiceId,
|
|
2239
|
+
agent_id: agentId,
|
|
2240
|
+
environment_id: environmentId,
|
|
2241
|
+
infrastructure_id: infrastructureId,
|
|
2242
|
+
...(infrastructureType ? { infrastructureType } : {}),
|
|
2243
|
+
...(onboardingId ? { onboardingId } : {}),
|
|
2244
|
+
...(probes && probes.length > 0 ? { probes } : {}),
|
|
2245
|
+
};
|
|
2246
|
+
},
|
|
2247
|
+
responseExtractor: passthrough,
|
|
2248
|
+
description: descCreateChaosService,
|
|
2249
|
+
bodySchema: {
|
|
2250
|
+
description: descBodyChaosServiceCreate,
|
|
2251
|
+
fields: [
|
|
2252
|
+
{ name: "identity", type: "string", required: true, description: descChaosServiceIdentity },
|
|
2253
|
+
{ name: "name", type: "string", required: true, description: descChaosServiceName },
|
|
2254
|
+
{ name: "external_service_id", type: "string", required: true, description: descChaosServiceExternalServiceId },
|
|
2255
|
+
{ name: "agent_id", type: "string", required: true, description: descChaosServiceAgentId },
|
|
2256
|
+
{ name: "environment_id", type: "string", required: true, description: descChaosServiceEnvironmentId },
|
|
2257
|
+
{ name: "infrastructure_id", type: "string", required: true, description: descChaosServiceInfrastructureId },
|
|
2258
|
+
{ name: "infrastructure_type", type: "string", required: false, description: descChaosServiceInfrastructureType },
|
|
2259
|
+
{ name: "description", type: "string", required: false, description: descChaosServiceDescription },
|
|
2260
|
+
{ name: "tags", type: "array", required: false, description: descChaosServiceTagsBody },
|
|
2261
|
+
{ name: "onboarding_id", type: "string", required: false, description: descChaosServiceOnboardingId },
|
|
2262
|
+
{ name: "probes", type: "array", required: false, description: descChaosServiceProbes },
|
|
2263
|
+
],
|
|
2264
|
+
},
|
|
2265
|
+
},
|
|
2266
|
+
update: {
|
|
2267
|
+
method: "PUT",
|
|
2268
|
+
path: `${CHAOS}/v3/chaos-services/{identity}`,
|
|
2269
|
+
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
2270
|
+
pathParams: { identity: "identity" },
|
|
2271
|
+
bodyBuilder: (input) => {
|
|
2272
|
+
const b = coerceBody(input);
|
|
2273
|
+
const externalServiceId = b.external_service_id ?? b.externalServiceId;
|
|
2274
|
+
const agentId = b.agent_id ?? b.agentId;
|
|
2275
|
+
const environmentId = b.environment_id ?? b.environmentId;
|
|
2276
|
+
const infrastructureId = b.infrastructure_id ?? b.infrastructureId;
|
|
2277
|
+
const tags = b.tags;
|
|
2278
|
+
const probes = b.probes?.map((p) => ({
|
|
2279
|
+
probeId: p.probeId ?? p.probe_id,
|
|
2280
|
+
...(p.inputs ? { inputs: p.inputs } : {}),
|
|
2281
|
+
}));
|
|
2282
|
+
return {
|
|
2283
|
+
name: b.name,
|
|
2284
|
+
...(b.description !== undefined ? { description: b.description } : {}),
|
|
2285
|
+
...(tags
|
|
2286
|
+
? {
|
|
2287
|
+
tags: Array.isArray(tags)
|
|
2288
|
+
? tags
|
|
2289
|
+
: String(tags)
|
|
2290
|
+
.split(",")
|
|
2291
|
+
.map((t) => t.trim())
|
|
2292
|
+
.filter(Boolean),
|
|
2293
|
+
}
|
|
2294
|
+
: {}),
|
|
2295
|
+
externalServiceId,
|
|
2296
|
+
agentId,
|
|
2297
|
+
environmentId,
|
|
2298
|
+
infrastructureId,
|
|
2299
|
+
// Dual-write snake_case keys — see comment in create bodyBuilder above.
|
|
2300
|
+
external_service_id: externalServiceId,
|
|
2301
|
+
agent_id: agentId,
|
|
2302
|
+
environment_id: environmentId,
|
|
2303
|
+
infrastructure_id: infrastructureId,
|
|
2304
|
+
// Full desired-state replace (hce-saas reconcileProbeMappings):
|
|
2305
|
+
// pass an explicit [] straight through (detach all) rather than
|
|
2306
|
+
// dropping it, but leave the key absent when the caller omitted it
|
|
2307
|
+
// so the required-field validation below rejects the request
|
|
2308
|
+
// instead of silently clearing every probe.
|
|
2309
|
+
...(probes !== undefined ? { probes } : {}),
|
|
2310
|
+
};
|
|
2311
|
+
},
|
|
2312
|
+
responseExtractor: passthrough,
|
|
2313
|
+
description: descUpdateChaosService,
|
|
2314
|
+
bodySchema: {
|
|
2315
|
+
description: descBodyChaosServiceUpdate,
|
|
2316
|
+
fields: [
|
|
2317
|
+
{ name: "name", type: "string", required: true, description: descChaosServiceName },
|
|
2318
|
+
{ name: "external_service_id", type: "string", required: true, description: descChaosServiceExternalServiceId },
|
|
2319
|
+
{ name: "agent_id", type: "string", required: true, description: descChaosServiceAgentId },
|
|
2320
|
+
{ name: "environment_id", type: "string", required: true, description: descChaosServiceEnvironmentId },
|
|
2321
|
+
{ name: "infrastructure_id", type: "string", required: true, description: descChaosServiceInfrastructureId },
|
|
2322
|
+
{ name: "description", type: "string", required: false, description: descChaosServiceDescription },
|
|
2323
|
+
{ name: "tags", type: "array", required: false, description: descChaosServiceTagsBody },
|
|
2324
|
+
{ name: "probes", type: "array", required: true, description: descChaosServiceProbesUpdate },
|
|
2325
|
+
],
|
|
2326
|
+
},
|
|
2327
|
+
},
|
|
2328
|
+
},
|
|
2329
|
+
executeActions: {
|
|
2330
|
+
list_experiment_runs: {
|
|
2331
|
+
method: "GET",
|
|
2332
|
+
path: `${CHAOS}/v3/chaos-services/{identity}/experiment-runs`,
|
|
2333
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
2334
|
+
pathParams: { identity: "identity" },
|
|
2335
|
+
queryParams: {
|
|
2336
|
+
page: "page",
|
|
2337
|
+
limit: "limit",
|
|
2338
|
+
size: "limit",
|
|
2339
|
+
search: "search",
|
|
2340
|
+
sort_field: "sortField",
|
|
2341
|
+
sort_ascending: "sortAscending",
|
|
2342
|
+
include_all_scope: "includeAllScope",
|
|
2343
|
+
infra_ids: "infraIds",
|
|
2344
|
+
statuses: "statuses",
|
|
2345
|
+
step_types: "stepTypes",
|
|
2346
|
+
},
|
|
2347
|
+
responseExtractor: passthrough,
|
|
2348
|
+
actionDescription: descListChaosServiceExperimentRuns,
|
|
2349
|
+
bodySchema: { description: descBodyNoBody, fields: [] },
|
|
2350
|
+
},
|
|
2351
|
+
list_load_tests: {
|
|
2352
|
+
method: "GET",
|
|
2353
|
+
path: `${CHAOS}/v3/chaos-services/{identity}/load-tests`,
|
|
2354
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
2355
|
+
pathParams: { identity: "identity" },
|
|
2356
|
+
queryParams: {
|
|
2357
|
+
page: "page",
|
|
2358
|
+
limit: "limit",
|
|
2359
|
+
size: "limit",
|
|
2360
|
+
search: "search",
|
|
2361
|
+
sort_field: "sortField",
|
|
2362
|
+
sort_ascending: "sortAscending",
|
|
2363
|
+
include_all_scope: "includeAllScope",
|
|
2364
|
+
tool_type: "toolType",
|
|
2365
|
+
environment_ids: "environmentIds",
|
|
2366
|
+
infra_ids: "infraIds",
|
|
2367
|
+
tags: "tags",
|
|
2368
|
+
},
|
|
2369
|
+
responseExtractor: passthrough,
|
|
2370
|
+
actionDescription: descListChaosServiceLoadTests,
|
|
2371
|
+
bodySchema: { description: descBodyNoBody, fields: [] },
|
|
2372
|
+
},
|
|
2373
|
+
},
|
|
2374
|
+
},
|
|
1727
2375
|
// ── Chaos Kubernetes Infrastructure ──────────────────────────────
|
|
1728
2376
|
{
|
|
1729
2377
|
resourceType: "chaos_k8s_infrastructure",
|
|
@@ -1733,7 +2381,13 @@ export const chaosToolset = {
|
|
|
1733
2381
|
scope: "project",
|
|
1734
2382
|
scopeParams: CHAOS_SCOPE,
|
|
1735
2383
|
identifierFields: ["infra_id"],
|
|
2384
|
+
deepLinkTemplate: "/ng/account/{accountId}/module/chaos/orgs/{orgIdentifier}/projects/{projectIdentifier}/settings/chaos/infrastructures/{infraId}",
|
|
1736
2385
|
diagnosticHint: "An infrastructure can only create chaos experiments when status is ACTIVE and isChaosEnabled is true. Filter out any infrastructure that does not meet both conditions.",
|
|
2386
|
+
relatedResources: [
|
|
2387
|
+
{ resourceType: "chaos_environment", relationship: "scoped_by", description: "STEP 1 of create: pick the Harness environment. Its identifier becomes environment_id." },
|
|
2388
|
+
{ resourceType: "infrastructure", relationship: "prerequisite", description: "STEP 2 of create: pick the backing CD Kubernetes infrastructure definition in that environment. Its identifier becomes infra_id (defaults to chaos identity when omitted)." },
|
|
2389
|
+
{ resourceType: "chaos_enabled_infrastructure", relationship: "child", description: "After install completes, the registered infra appears here once status=ACTIVE and isChaosEnabled=true." },
|
|
2390
|
+
],
|
|
1737
2391
|
listFilterFields: [
|
|
1738
2392
|
{ name: "environment_id", description: descEnvironmentId },
|
|
1739
2393
|
{ name: "status", description: descK8sInfraStatus, enum: ["ACTIVE", "INACTIVE", "PENDING", "All"] },
|
|
@@ -1776,6 +2430,72 @@ export const chaosToolset = {
|
|
|
1776
2430
|
responseExtractor: passthrough,
|
|
1777
2431
|
description: descGetK8sInfra,
|
|
1778
2432
|
},
|
|
2433
|
+
create: {
|
|
2434
|
+
method: "POST",
|
|
2435
|
+
path: `${CHAOS}/rest/v2/infrastructure`,
|
|
2436
|
+
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
2437
|
+
bodyBuilder: (input) => {
|
|
2438
|
+
const b = coerceBody(input);
|
|
2439
|
+
const identity = b.identity;
|
|
2440
|
+
const name = b.name;
|
|
2441
|
+
const environmentId = (b.environment_id ?? b.environmentId);
|
|
2442
|
+
if (!identity)
|
|
2443
|
+
throw new Error("identity is required.");
|
|
2444
|
+
if (!name)
|
|
2445
|
+
throw new Error("name is required.");
|
|
2446
|
+
if (!environmentId)
|
|
2447
|
+
throw new Error("environment_id is required.");
|
|
2448
|
+
const infraId = (b.infra_id ?? b.infraId ?? identity);
|
|
2449
|
+
const infraNamespace = (b.infra_namespace ?? b.infraNamespace ?? "hce");
|
|
2450
|
+
const serviceAccount = (b.service_account ?? b.serviceAccount ?? "litmus");
|
|
2451
|
+
const infraScope = (b.infra_scope ?? b.infraScope ?? "CLUSTER").toUpperCase();
|
|
2452
|
+
const infraType = (b.infra_type ?? b.infraType ?? "KUBERNETES").toUpperCase();
|
|
2453
|
+
const k8sConnectorId = (b.k8s_connector_id ?? b.k8sConnectorID ?? b.k8sConnectorId);
|
|
2454
|
+
const discoveryAgentId = (b.discovery_agent_id ?? b.discoveryAgentID ?? b.discoveryAgentId);
|
|
2455
|
+
const aiEnabled = Boolean(b.ai_enabled ?? b.aiEnabled ?? false);
|
|
2456
|
+
const rawTags = b.tags;
|
|
2457
|
+
const tags = Array.isArray(rawTags)
|
|
2458
|
+
? rawTags
|
|
2459
|
+
: typeof rawTags === "string"
|
|
2460
|
+
? rawTags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
2461
|
+
: [];
|
|
2462
|
+
return {
|
|
2463
|
+
identity,
|
|
2464
|
+
name,
|
|
2465
|
+
environmentID: environmentId,
|
|
2466
|
+
infraID: infraId,
|
|
2467
|
+
infraNamespace,
|
|
2468
|
+
serviceAccount,
|
|
2469
|
+
infraScope,
|
|
2470
|
+
infraType,
|
|
2471
|
+
...(b.description ? { description: b.description } : {}),
|
|
2472
|
+
...(tags.length > 0 ? { tags } : {}),
|
|
2473
|
+
...(k8sConnectorId ? { k8sConnectorID: k8sConnectorId } : {}),
|
|
2474
|
+
...(discoveryAgentId ? { discoveryAgentID: discoveryAgentId } : {}),
|
|
2475
|
+
aiEnabled,
|
|
2476
|
+
insecureSkipVerify: Boolean(b.insecure_skip_verify ?? b.insecureSkipVerify ?? false),
|
|
2477
|
+
};
|
|
2478
|
+
},
|
|
2479
|
+
responseExtractor: passthrough,
|
|
2480
|
+
description: descCreateK8sInfra,
|
|
2481
|
+
bodySchema: {
|
|
2482
|
+
description: descBodyK8sInfraCreate,
|
|
2483
|
+
fields: [
|
|
2484
|
+
{ name: "identity", type: "string", required: true, description: descK8sInfraIdentityCreate },
|
|
2485
|
+
{ name: "name", type: "string", required: true, description: descK8sInfraNameCreate },
|
|
2486
|
+
{ name: "environment_id", type: "string", required: true, description: descK8sInfraEnvironmentIdCreate },
|
|
2487
|
+
{ name: "infra_id", type: "string", required: false, description: descK8sInfraInfraIdCreate },
|
|
2488
|
+
{ name: "k8s_connector_id", type: "string", required: false, description: descK8sInfraConnectorIdCreate },
|
|
2489
|
+
{ name: "infra_namespace", type: "string", required: false, description: descK8sInfraNamespaceCreate },
|
|
2490
|
+
{ name: "service_account", type: "string", required: false, description: descK8sInfraServiceAccountCreate },
|
|
2491
|
+
{ name: "infra_scope", type: "string", required: false, description: descK8sInfraScopeCreate },
|
|
2492
|
+
{ name: "infra_type", type: "string", required: false, description: descK8sInfraTypeCreate },
|
|
2493
|
+
{ name: "description", type: "string", required: false, description: "Optional description for the chaos server." },
|
|
2494
|
+
{ name: "tags", type: "array", required: false, description: "Optional tags (array of strings or comma-separated string)." },
|
|
2495
|
+
{ name: "ai_enabled", type: "boolean", required: false, description: descK8sInfraAiEnabledCreate },
|
|
2496
|
+
],
|
|
2497
|
+
},
|
|
2498
|
+
},
|
|
1779
2499
|
},
|
|
1780
2500
|
executeActions: {
|
|
1781
2501
|
check_health: {
|
|
@@ -3032,6 +3752,60 @@ export const chaosToolset = {
|
|
|
3032
3752
|
},
|
|
3033
3753
|
},
|
|
3034
3754
|
},
|
|
3755
|
+
// ── Service Discovery: Agents ──────────────────────────────────────
|
|
3756
|
+
// GET /gateway/servicediscovery/api/v1/agents (service-discovery repo).
|
|
3757
|
+
// Envelope matches the other SD list endpoints:
|
|
3758
|
+
// { items, page: { totalItems, ... }, correlationID }
|
|
3759
|
+
// so we reuse sdPageExtract. The Go handler treats environmentIdentifier
|
|
3760
|
+
// as an optional narrowing filter even though swagger marks it required —
|
|
3761
|
+
// omit to enumerate agents across every environment in the current scope.
|
|
3762
|
+
{
|
|
3763
|
+
resourceType: "discovered_agent",
|
|
3764
|
+
displayName: "Discovered Agent",
|
|
3765
|
+
description: descDiscoveredAgent,
|
|
3766
|
+
toolset: "chaos",
|
|
3767
|
+
scope: "project",
|
|
3768
|
+
scopeParams: CHAOS_SCOPE,
|
|
3769
|
+
identifierFields: ["identity"],
|
|
3770
|
+
compactItem: compactDiscoveredAgent,
|
|
3771
|
+
searchAliases: [
|
|
3772
|
+
"service discovery agent", "sd agent", "discovery agent",
|
|
3773
|
+
"chaos discovery agent",
|
|
3774
|
+
],
|
|
3775
|
+
listFilterFields: [
|
|
3776
|
+
{ name: "environment_id", description: descSDAgentListEnvironmentId },
|
|
3777
|
+
{ name: "search", description: descDiscoveredAgentSearch },
|
|
3778
|
+
{ name: "all", type: "boolean", description: descSDFetchAll },
|
|
3779
|
+
],
|
|
3780
|
+
relatedResources: [
|
|
3781
|
+
{
|
|
3782
|
+
resourceType: "discovered_namespace",
|
|
3783
|
+
relationship: "produces",
|
|
3784
|
+
description: "The 'identity' field returned here is the value discovered_namespace / discovered_service / discovered_network_map take as agent_identity.",
|
|
3785
|
+
},
|
|
3786
|
+
{
|
|
3787
|
+
resourceType: "chaos_service",
|
|
3788
|
+
relationship: "referenced_by",
|
|
3789
|
+
description: "Use an agent's 'identity' as the agent_id field when creating or updating a chaos_service.",
|
|
3790
|
+
},
|
|
3791
|
+
],
|
|
3792
|
+
operations: {
|
|
3793
|
+
list: {
|
|
3794
|
+
method: "GET",
|
|
3795
|
+
path: `${SD}/agents`,
|
|
3796
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
3797
|
+
queryParams: {
|
|
3798
|
+
environment_id: "environmentIdentifier",
|
|
3799
|
+
search: "search",
|
|
3800
|
+
page: "page",
|
|
3801
|
+
size: "limit", // SD uses `limit`, not `size`
|
|
3802
|
+
all: "all",
|
|
3803
|
+
},
|
|
3804
|
+
responseExtractor: sdPageExtract,
|
|
3805
|
+
description: descListDiscoveredAgents,
|
|
3806
|
+
},
|
|
3807
|
+
},
|
|
3808
|
+
},
|
|
3035
3809
|
// ── Service Discovery: Namespaces ──────────────────────────────────
|
|
3036
3810
|
{
|
|
3037
3811
|
resourceType: "discovered_namespace",
|