@opscompass/skills 0.1.0
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 +78 -0
- package/manifest.json +35 -0
- package/package.json +23 -0
- package/skills/opscompass-oracle-licensing/.opscompass-runtime/tools/cli-entrypoint.js +15 -0
- package/skills/opscompass-oracle-licensing/SKILL.md +211 -0
- package/skills/opscompass-oracle-licensing/api/entitlement-compliance/entitlement-compliance.json +189 -0
- package/skills/opscompass-oracle-licensing/api/entitlement-deployment/entitlement-deployment +26 -0
- package/skills/opscompass-oracle-licensing/api/entitlement-usage/entitlement-usage.json +114 -0
- package/skills/opscompass-oracle-licensing/assets/mock-entitlements/deployment-details.json +61 -0
- package/skills/opscompass-oracle-licensing/assets/mock-entitlements/license-usage-map.json +70 -0
- package/skills/opscompass-oracle-licensing/assets/mock-entitlements/overall-compliance.json +36 -0
- package/skills/opscompass-oracle-licensing/package.json +3 -0
- package/skills/opscompass-oracle-licensing/references/evidence-model.md +90 -0
- package/skills/opscompass-oracle-licensing/references/report-structure.md +54 -0
- package/skills/opscompass-oracle-licensing/scripts/fake-entitlements-api.tool.js +31 -0
- package/skills/opscompass-oracle-licensing/scripts/fake_entitlements_api.js +72 -0
- package/skills/opscompass-oracle-licensing/scripts/tools.js +2 -0
- package/skills/opscompass-resources/.opscompass-runtime/tools/cli-entrypoint.js +15 -0
- package/skills/opscompass-resources/.opscompass-runtime/tools/index.js +2 -0
- package/skills/opscompass-resources/.opscompass-runtime/tools/opsc-cli-context.js +24 -0
- package/skills/opscompass-resources/.opscompass-runtime/tools/opsc-cli-defaults.js +125 -0
- package/skills/opscompass-resources/.opscompass-runtime/tools/opsc-cli.js +90 -0
- package/skills/opscompass-resources/SKILL.md +335 -0
- package/skills/opscompass-resources/package.json +3 -0
- package/skills/opscompass-resources/references/resources-commands.md +178 -0
- package/skills/opscompass-resources/scripts/build-resources-command.tool.js +53 -0
- package/skills/opscompass-resources/scripts/build_resources_command.js +240 -0
- package/skills/opscompass-resources/scripts/list-and-fetch-latest-configs.tool.js +116 -0
- package/skills/opscompass-resources/scripts/list_and_fetch_latest_configs.js +714 -0
- package/skills/opscompass-resources/scripts/map-oracle-workloads-to-platform-cpus.tool.js +24 -0
- package/skills/opscompass-resources/scripts/map_oracle_workloads_to_platform_cpus.js +273 -0
- package/skills/opscompass-resources/scripts/opscompass-defaults.tool.js +80 -0
- package/skills/opscompass-resources/scripts/opscompass-resources.tool.js +51 -0
- package/skills/opscompass-resources/scripts/summarize-latest-configuration.tool.js +24 -0
- package/skills/opscompass-resources/scripts/summarize-resources-list.tool.js +24 -0
- package/skills/opscompass-resources/scripts/summarize_latest_configuration.js +254 -0
- package/skills/opscompass-resources/scripts/summarize_resources_list.js +126 -0
- package/skills/opscompass-resources/scripts/tools.js +16 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# OpsCompass Resources `list` + Follow-up Details Reference
|
|
2
|
+
|
|
3
|
+
Use this file to map workload and infrastructure questions to `opscompass resources list` flags and then inspect selected resources by iterating their ids with follow-up actions like `get-latest-configuration` and `get-compliance`.
|
|
4
|
+
|
|
5
|
+
This skill is primarily for:
|
|
6
|
+
- Oracle license risk analysis
|
|
7
|
+
- Oracle and SQL Server sprawl analysis
|
|
8
|
+
- tracing workloads to AWS, Azure, VMware, or Nutanix infrastructure
|
|
9
|
+
- identifying misconfiguration from latest configuration data
|
|
10
|
+
|
|
11
|
+
For Oracle licensing and sprawl work, date-bounded inventory is a standard workflow. Use `resources list` with:
|
|
12
|
+
- `--resource-creation-date-beginning` for created-on-or-after questions
|
|
13
|
+
- `--resource-creation-date-end` for created-before questions
|
|
14
|
+
- both flags together for date-range questions
|
|
15
|
+
|
|
16
|
+
## Company scope and CLI defaults
|
|
17
|
+
|
|
18
|
+
- `--company`: Company id or slug. Include it explicitly in final `opscompass resources ...` commands so transcripts show tenant scope.
|
|
19
|
+
- Defaults belong to `<selected-home>/defaults.json`: production uses `~/.opscompass`, dev uses `~/.opscompassdev`, and demo uses `~/.opscompassdemo`. Never fall back to another environment.
|
|
20
|
+
- In embedded use, prefer approved resource tools and `opscompass_resources_defaults` with the authoritative host-supplied CLI entrypoint, command identity, and home. Do not assume global `PATH` or a production session.
|
|
21
|
+
- Standalone `build_resources_command.js` and `list_and_fetch_latest_configs.js` accept `--cli opscompass|opscompassdev|opscompassdemo`; omission defaults to production unless a recognized bare `OPSCOMPASS_CLI_ENTRYPOINT` selects another identity. `OPSCOMPASS_CLI_ENTRYPOINT` may instead be a custom executable path: that retains production identity unless paired with explicit `--cli`. Explicit `--cli` wins over conflicting recognized ambient commands; custom paths can pair with an explicit identity. Pure summarizers process supplied JSON and do not accept `--cli`.
|
|
22
|
+
- For standalone defaults inspection, use the selected CLI's `defaults list-defaults`, for example `opscompassdev defaults list-defaults` for dev.
|
|
23
|
+
- Use the selected CLI's interactive `data-gathering setup` for guided defaults setup, or `defaults set-defaults --company <company> --account <account> --monitoring-configuration-id <id>` only when the user explicitly wants to change local defaults.
|
|
24
|
+
- An explicit `--company` wins over defaults. Helpers can use the selected home's default `company` when it is omitted. Pass `--ignore-defaults --company <company>` to ignore defaults and force explicit scope. A company name does not select the CLI environment.
|
|
25
|
+
- Builder output and tool calls with `execute: false` are previews, not executed queries or evidence. Preserve the selected context when executing a preview.
|
|
26
|
+
- Report actual failures without exposing credentials. Authentication failure may warrant the selected CLI's login guidance; missing scope, authorization denial, invalid arguments, and network/service errors require their own diagnosis, not a generic expired-login response.
|
|
27
|
+
|
|
28
|
+
## Provider interpretation note
|
|
29
|
+
|
|
30
|
+
- In Opscompass inventory, `cloud_provider: "mlss"` usually indicates a database or on-prem infrastructure context such as VMware or Nutanix.
|
|
31
|
+
- Do not treat `mlss` by itself as the final infrastructure platform answer when the user is asking what hosts a workload.
|
|
32
|
+
- In this skill, `Oracle` means Oracle database workloads by default, not Oracle Cloud / OCI. Only interpret `Oracle` as Oracle Cloud when the user explicitly asks about Oracle Cloud, OCI, or cloud-provider/platform inventory.
|
|
33
|
+
|
|
34
|
+
## Follow-up details action
|
|
35
|
+
|
|
36
|
+
- `get-latest-configuration`: Full latest configuration for one resource.
|
|
37
|
+
- Required for this skill: resolved `--company` and numeric `--id` (from `list` output).
|
|
38
|
+
- In this skill, do not guess ids; get them from a preceding `list` command and iterate as needed.
|
|
39
|
+
- Use the OpsCompass numeric resource `--id` for follow-up actions. Do not substitute `--resource-id` or `--resource-ids`; those are cloud platform resource identifiers and not the same value.
|
|
40
|
+
- Do not use `resources show` for this skill workflow.
|
|
41
|
+
- For Oracle DBs, inspect `document.version` and `document.edition` in the `get-latest-configuration` JSON.
|
|
42
|
+
- For Oracle license-risk questions, do not stop at Oracle config alone if hosting and compute context can be established. Correlate the workload to the infrastructure it runs on.
|
|
43
|
+
- `get-compliance`: Compliance findings array for one resource.
|
|
44
|
+
- Required for this skill: resolved `--company`, numeric `--id` (from `list` output), and `--status`.
|
|
45
|
+
- Builder syntax note: when using `scripts/build_resources_command.js` for compliance, pass `--param status=...` to the builder. The final printed `opscompass resources get-compliance ...` command will use `--status ...`.
|
|
46
|
+
- Allowed `get-compliance --status` values: `open`, `remediated`, `policy-exception`, `mitigation`.
|
|
47
|
+
- For compliance summaries, focus on `checkName`, `checkSeverity`, `errorMessage`, `recommendedAction`, and framework/control names.
|
|
48
|
+
- For Oracle DBs, interpret `get-compliance` results primarily as Oracle license compliance findings unless the user explicitly frames the request differently.
|
|
49
|
+
- For Oracle DB compliance workflows, also inspect `document.version`, `document.edition`, `document.productUsageConcerns`, and concerning `document.parameters` from `get-latest-configuration` so the answer has license context and feature-usage evidence.
|
|
50
|
+
- For Oracle DB compliance workflows, the important output is usually the license-relevant findings themselves, not the raw count of `compliance_problems`. Treat the findings as evidence of feature, option, pack, or parameter usage that may exceed entitlement.
|
|
51
|
+
- For Oracle latest-configuration, non-empty `document.productUsageConcerns` is per-database product/feature usage concern evidence.
|
|
52
|
+
- For Oracle latest-configuration, `document.parameters.*.isConcerning=true` is license-relevant parameter evidence. Example: `CONTROL_MANAGEMENT_PACK_ACCESS=DIAGNOSTIC+TUNING` indicates Diagnostic Pack and Tuning Pack exposure.
|
|
53
|
+
- Do not say per-database Oracle feature usage is unavailable until both `get-compliance --status open` and `get-latest-configuration` have been checked for the database.
|
|
54
|
+
- If a prior `resources list` result already shows non-zero `compliance_problems` for Oracle or SQL Server resources and the user asks for the specific problems, iterate those resource ids with `get-compliance --status open`. Do not stop at the count-level list output.
|
|
55
|
+
- For Oracle on VMware or Nutanix, the hosting path matters: database workload -> VM -> host -> broader platform context. If that chain cannot be established, the Oracle license-risk conclusion is partial.
|
|
56
|
+
- Opscompass does not currently provide compliance checks for VMware vCenter or Nutanix resource types in this workflow. For `vcenter/*` and `Nutanix/*`, focus on `get-latest-configuration`.
|
|
57
|
+
- For VMware investigations, inspect both VM and host `get-latest-configuration` results when movement or placement is relevant, because a VM moving hosts can appear in both resources' latest configuration. The latest-configuration payloads include VM and host ids that can be used to establish those relationships.
|
|
58
|
+
- In `vcenter/host` configs, inspect `document.virtualMachines[]` for hosted VM ids/names and use `document.id`, `document.name`, `document.cluster`, and `document.datacenter` for host context.
|
|
59
|
+
- For VMware vCPU mapping, inspect VM latest-configuration for direct CPU count fields such as `numCpu`, `numCPU`, `cpuCount`, `numCpus`, `vcpuCount`, or nested hardware/config equivalents like `hardware.numCPU` and `hardware.numCoresPerSocket`.
|
|
60
|
+
- For Nutanix investigations, inspect `Nutanix/Vm`, `Nutanix/Host`, and `Nutanix/Cluster` latest configuration results together. Use `vm.document.host.extId`, `vm.document.cluster.extId`, `host.document.extId`, `host.document.cluster.uuid`, and `cluster.document.nodes.nodeList[].nodeUuid` to establish the relationships.
|
|
61
|
+
- For Nutanix host-to-VM mapping, list `Nutanix/Vm` and `Nutanix/Host`, fetch latest configuration for the relevant ids, and map `Nutanix/Vm.document.host.extId` to `Nutanix/Host.document.extId`. Do not treat list output alone as a blocker; latest configuration is the relationship source.
|
|
62
|
+
- For Nutanix vCPU mapping, inspect `document.numSockets`, `document.numCoresPerSocket`, and `document.numThreadsPerCore`, then compute provisioned vCPU as `numSockets * numCoresPerSocket * numThreadsPerCore`.
|
|
63
|
+
- For Oracle and SQL Server workloads on Nutanix, the VM-to-host-to-cluster relationship is part of the workload answer and should be summarized explicitly.
|
|
64
|
+
- If workload and infrastructure names line up, use that as strong supporting evidence for the correlation. Do not require a naming match, and do not treat the absence of one as evidence that the relationship is missing.
|
|
65
|
+
|
|
66
|
+
## Core list filters
|
|
67
|
+
|
|
68
|
+
- `--accounts [account_id]`
|
|
69
|
+
- `--ids [12345]` (OpsCompass resource ids)
|
|
70
|
+
- `--resource-ids [cloud-resource-id]` (cloud platform resource ids such as AWS ARNs or Azure resource ids; not the same as the numeric OpsCompass `--id` used in follow-up actions)
|
|
71
|
+
- `--cloud-providers [aws | azure | gcp | microsoft365]`
|
|
72
|
+
- `--resource-types [resource-type-name]` (restricted in this skill to approved resource types)
|
|
73
|
+
- `--region-ids [region-id]`
|
|
74
|
+
- `--tags [tag-value]`
|
|
75
|
+
- `--resource-creation-date-beginning ["YYYY-MM-DD HH:MM:SS.mmm"]`
|
|
76
|
+
- `--resource-creation-date-end ["YYYY-MM-DD HH:MM:SS.mmm"]`
|
|
77
|
+
- `--open-compliance-issues [true | false]`
|
|
78
|
+
- `--unacknowledged-changes [true | false]`
|
|
79
|
+
- `--skip [number]`
|
|
80
|
+
|
|
81
|
+
## Common request mappings
|
|
82
|
+
|
|
83
|
+
- "What Oracle licensing risk do I have?" -> treat as Oracle workload plus infrastructure analysis; use Oracle compliance/config plus hosting context
|
|
84
|
+
- "Which Oracle databases were created on or after X?" -> `--resource-creation-date-beginning`, usually with `--resource-types databases/oracle`
|
|
85
|
+
- "Which Oracle databases were created between X and Y?" -> `--resource-creation-date-beginning` plus `--resource-creation-date-end`, usually with `--resource-types databases/oracle`
|
|
86
|
+
- "Which recently created Oracle or SQL Server workloads may be sprawl candidates?" -> start with creation-date filters, then inspect latest config and hosting context
|
|
87
|
+
- "Do I have any Oracle?" -> `--resource-types databases/oracle`
|
|
88
|
+
- "Show me my Oracle" -> `--resource-types databases/oracle`
|
|
89
|
+
- "Do I have Oracle Cloud resources?" -> only then treat it as a provider/platform question
|
|
90
|
+
- "Map Oracle databases to provisioned vCPU cores on Nutanix" -> collect Oracle plus `Nutanix/Vm` latest configurations, then use `scripts/map_oracle_workloads_to_platform_cpus.js`
|
|
91
|
+
- "Map Oracle databases to provisioned vCPU cores on VMware" -> collect Oracle plus `vcenter/virtualMachine` latest configurations, then use `scripts/map_oracle_workloads_to_platform_cpus.js`
|
|
92
|
+
- "Where is this Oracle database hosted and what infrastructure backs it?" -> correlate the Oracle workload to VMware or Nutanix resources instead of stopping at the database record
|
|
93
|
+
- "Which Oracle or SQL Server workloads may be sprawl candidates?" -> list workloads, inspect latest config, and summarize hosting/platform footprint and notable risk signals
|
|
94
|
+
- "What misconfigurations or concerning settings do these Oracle or SQL Server workloads have?" -> inspect latest configuration and compliance where applicable, then summarize the risk-oriented differences
|
|
95
|
+
- "resources created after X" -> `--resource-creation-date-beginning`
|
|
96
|
+
- "resources created before X" -> `--resource-creation-date-end`
|
|
97
|
+
- "resources in account A" -> `--accounts`
|
|
98
|
+
- "AWS resources" -> `--cloud-providers aws`
|
|
99
|
+
- "resources in us-east-1" -> `--region-ids us-east-1`
|
|
100
|
+
- "resources with tag X" -> `--tags`
|
|
101
|
+
- "EC2 instances" -> `--resource-types AWS::EC2::Instance`
|
|
102
|
+
- "Nutanix clusters" -> `--resource-types Nutanix/Cluster`
|
|
103
|
+
- "Nutanix hosts" -> `--resource-types Nutanix/Host`
|
|
104
|
+
- "Nutanix virtual machines" -> `--resource-types Nutanix/Vm`
|
|
105
|
+
- "Map Nutanix hosts to VMs" -> collect `Nutanix/Vm` and `Nutanix/Host` latest configurations, then map `vm.document.host.extId` to `host.document.extId`
|
|
106
|
+
- "Which VMs are running on each Nutanix host?" -> collect `Nutanix/Vm` and `Nutanix/Host` latest configurations, then group VMs by matched host
|
|
107
|
+
- "Nutanix subnets" -> `--resource-types Nutanix/Subnet`
|
|
108
|
+
- "Nutanix volume groups" -> `--resource-types Nutanix/VolumeGroup`
|
|
109
|
+
- "Oracle on Nutanix" -> start with `Nutanix/Vm`, then relate to `Nutanix/Host` and `Nutanix/Cluster`
|
|
110
|
+
- "SQL Server on Nutanix" -> start with `Nutanix/Vm`, then relate to `Nutanix/Host` and `Nutanix/Cluster`
|
|
111
|
+
- "Nutanix compliance" -> explain that Nutanix resources do not currently have compliance checks here; inspect latest configuration instead
|
|
112
|
+
- "VMware hosts" -> `--resource-types vcenter/host`
|
|
113
|
+
- "VMware virtual machines" -> `--resource-types vcenter/virtualMachine`
|
|
114
|
+
- "VMware VM moved hosts" -> inspect both `vcenter/virtualMachine` and `vcenter/host` latest configuration results and use the VM/host ids in those payloads to map the relationship
|
|
115
|
+
- "VMware compliance" -> explain that vCenter resources do not currently have compliance checks here; inspect latest configuration instead
|
|
116
|
+
- "Azure SQL servers" -> `--resource-types Microsoft.Sql/servers`
|
|
117
|
+
- "RDS database instances" -> `--resource-types AWS::RDS::DBInstance`
|
|
118
|
+
- "SQL Server databases" -> `--resource-types databases/mssqlserver`
|
|
119
|
+
- "Oracle databases" -> `--resource-types databases/oracle`
|
|
120
|
+
- "Oracle database compliance" -> usually Oracle license compliance via `get-compliance`
|
|
121
|
+
- "Are these Oracle databases on Nutanix?" -> do not stop at `cloud_provider: "mlss"`; continue workload-to-infrastructure correlation through Nutanix resources
|
|
122
|
+
- "resources with open compliance issues" -> `--open-compliance-issues true`
|
|
123
|
+
- "resources with unacknowledged changes" -> `--unacknowledged-changes true`
|
|
124
|
+
|
|
125
|
+
## Allowed `--resource-types` in this skill
|
|
126
|
+
|
|
127
|
+
- `AWS::EC2::Instance`
|
|
128
|
+
- `Microsoft.Sql/servers`
|
|
129
|
+
- `AWS::RDS::DBInstance`
|
|
130
|
+
- `databases/mssqlserver`
|
|
131
|
+
- `databases/oracle`
|
|
132
|
+
- `Nutanix/AuthorizationPolicy`
|
|
133
|
+
- `Nutanix/Cluster`
|
|
134
|
+
- `Nutanix/Host`
|
|
135
|
+
- `Nutanix/Image`
|
|
136
|
+
- `Nutanix/Role`
|
|
137
|
+
- `Nutanix/Subnet`
|
|
138
|
+
- `Nutanix/User`
|
|
139
|
+
- `Nutanix/VirtualSwitch`
|
|
140
|
+
- `Nutanix/Vm`
|
|
141
|
+
- `Nutanix/VolumeGroup`
|
|
142
|
+
- `vcenter/host`
|
|
143
|
+
- `vcenter/virtualMachine`
|
|
144
|
+
|
|
145
|
+
## Query strategy
|
|
146
|
+
|
|
147
|
+
- Start with resolved company scope plus one or two filters. If defaults are used, still include `--company <resolved-company>` in the command you run or report.
|
|
148
|
+
- If empty, remove the most restrictive filter (often date range, tag, or type).
|
|
149
|
+
- Add `--skip` only when paging through a known large result set.
|
|
150
|
+
- For large result sets, page deliberately and summarize the aggregate result instead of inspecting one oversized raw response.
|
|
151
|
+
- Choose follow-up actions with this priority:
|
|
152
|
+
- Oracle license/compliance question -> `get-compliance` plus `get-latest-configuration`, then correlate to infrastructure when possible
|
|
153
|
+
- Oracle or SQL Server hosting/sprawl/misconfiguration question -> `get-latest-configuration` first, then correlate to infrastructure
|
|
154
|
+
- VMware or Nutanix question -> `get-latest-configuration`, not `get-compliance`
|
|
155
|
+
- generic inventory question -> `list`, then only fetch follow-up data if needed
|
|
156
|
+
- For Oracle licensing or sprawl questions scoped to time, start with a date-bounded `resources list` rather than a broad list.
|
|
157
|
+
- When the user wants full details, pick a resource `id` from the list output and run `get-latest-configuration`.
|
|
158
|
+
- When the user wants compliance findings, pick a resource `id` from the list output and run `get-compliance --status ...`.
|
|
159
|
+
- If the conversation was previously focused on VMware or Nutanix and the user pivots to Oracle or SQL Server, re-evaluate intent from scratch and default to `databases/oracle` or `databases/mssqlserver` rather than continuing the prior infrastructure framing.
|
|
160
|
+
- For multiple resources, iterate through the list ids and run `get-latest-configuration` per resource.
|
|
161
|
+
- For multiple compliance checks, iterate through list ids and run `get-compliance --status ...` per resource.
|
|
162
|
+
- If the list output already identified which resources have `compliance_problems > 0`, target those ids first for `get-compliance --status open`.
|
|
163
|
+
- For Oracle compliance checks, pair `get-compliance` with `get-latest-configuration` so version, edition, product usage concerns, and concerning parameters can be included in the summary.
|
|
164
|
+
- For Oracle compliance answers, prioritize the actual findings and their license impact over the count of affected resources unless the user specifically asked for counts.
|
|
165
|
+
- Do not infer that the CLI lacks resource-level compliance detail unless a real `get-compliance` command failed and the error output supports that conclusion.
|
|
166
|
+
- For Oracle license-risk analysis, try to establish where the database is hosted and what underlying compute context applies. If that evidence is missing, say the conclusion is incomplete or partial.
|
|
167
|
+
- For Oracle workload-to-vCPU mapping on Nutanix or VMware, prefer collecting mixed Oracle and VM latest-configuration payloads and running `scripts/map_oracle_workloads_to_platform_cpus.js` instead of extracting CPU fields manually in the model.
|
|
168
|
+
- Do not plan `get-compliance` follow-ups for `vcenter/*` or `Nutanix/*` resource types in normal workflows.
|
|
169
|
+
- For VMware host/VM investigations, prefer collecting latest configuration from both resource types, use VM/host ids from those payloads to establish the relationship, and summarize them together.
|
|
170
|
+
- For host-side VMware summaries, include hosted VM ids/names from `document.virtualMachines[]` when relevant.
|
|
171
|
+
- For Nutanix VM/host/cluster investigations, prefer collecting latest configuration from all three resource types and summarize VM-to-host and VM-to-cluster relationships together.
|
|
172
|
+
- For Nutanix host-to-VM mapping, proceed directly to latest-configuration relationship analysis; do not ask the user to confirm an extra step when the mapping request is explicit.
|
|
173
|
+
- For Nutanix host-to-VM mapping output, distinguish complete inventories from retrieved subsets, include unmapped VMs, and call out duplicate VM names or duplicate resource ids when present.
|
|
174
|
+
- For Oracle and SQL Server on Nutanix, explicitly report which VM, host, and cluster are involved when those relationships can be established.
|
|
175
|
+
- Prefer direct configuration relationships first, then use naming conventions as supporting evidence when they are strong.
|
|
176
|
+
- If the workload inventory shows `cloud_provider: "mlss"`, use that as evidence of database/on-prem context and continue tracing to VMware or Nutanix rather than stopping there.
|
|
177
|
+
- For many configuration results, save/collect JSON and use `scripts/summarize_latest_configuration.js` to compact the data for reasoning across resource types.
|
|
178
|
+
- For repeated list->follow-up workflows, use `scripts/list_and_fetch_latest_configs.js` to run the list, iterate ids, collect configs and/or compliance, and print a compact summary.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { runCli } from "../.opscompass-runtime/tools/opsc-cli.js";
|
|
2
|
+
import { buildCommand, commandText, parseParamMap, validateArgs, } from "./build_resources_command.js";
|
|
3
|
+
import { resolveCompany } from "../.opscompass-runtime/tools/index.js";
|
|
4
|
+
export const buildResourcesCommandTool = {
|
|
5
|
+
name: "opscompass_resources_build_command",
|
|
6
|
+
label: "build resources command",
|
|
7
|
+
description: "Build a validated Opscompass resources command without shell interpolation.",
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
action: {
|
|
12
|
+
type: "string",
|
|
13
|
+
enum: ["list", "get-latest-configuration", "get-compliance"],
|
|
14
|
+
},
|
|
15
|
+
company: { type: "string" },
|
|
16
|
+
id: { type: "string" },
|
|
17
|
+
params: { type: "array", items: { type: "string" } },
|
|
18
|
+
execute: { type: "boolean" },
|
|
19
|
+
ignoreDefaults: { type: "boolean" },
|
|
20
|
+
},
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
23
|
+
async execute(input, context) {
|
|
24
|
+
const value = (input ?? {});
|
|
25
|
+
const company = resolveCompany(value.company, {
|
|
26
|
+
ignoreDefaults: value.ignoreDefaults ?? false,
|
|
27
|
+
context,
|
|
28
|
+
});
|
|
29
|
+
const args = {
|
|
30
|
+
action: value.action ?? "list",
|
|
31
|
+
company,
|
|
32
|
+
resourceNumericId: value.id,
|
|
33
|
+
param: value.params ?? [],
|
|
34
|
+
execute: value.execute ?? false,
|
|
35
|
+
ignoreDefaults: value.ignoreDefaults ?? false,
|
|
36
|
+
};
|
|
37
|
+
const paramMap = parseParamMap(args.param);
|
|
38
|
+
validateArgs(args, paramMap);
|
|
39
|
+
const command = buildCommand(args, paramMap, context);
|
|
40
|
+
const text = commandText(command);
|
|
41
|
+
const output = args.execute
|
|
42
|
+
? await runCli(context, command.slice(1))
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: output === undefined ? text : `${text}\n\n${output}`,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { isMainModule } from "../.opscompass-runtime/tools/cli-entrypoint.js";
|
|
3
|
+
import { resolveCliContext } from "../.opscompass-runtime/tools/opsc-cli-context.js";
|
|
4
|
+
import { resolveCompany } from "../.opscompass-runtime/tools/opsc-cli-defaults.js";
|
|
5
|
+
export { resolveCompany };
|
|
6
|
+
export const ALLOWED_LIST_PARAMS = new Set([
|
|
7
|
+
"accounts",
|
|
8
|
+
"ids",
|
|
9
|
+
"resource-ids",
|
|
10
|
+
"cloud-providers",
|
|
11
|
+
"resource-types",
|
|
12
|
+
"region-ids",
|
|
13
|
+
"tags",
|
|
14
|
+
"resource-creation-date-beginning",
|
|
15
|
+
"resource-creation-date-end",
|
|
16
|
+
"open-compliance-issues",
|
|
17
|
+
"unacknowledged-changes",
|
|
18
|
+
"skip",
|
|
19
|
+
]);
|
|
20
|
+
export const ALLOWED_LIST_ENUMS = {
|
|
21
|
+
"cloud-providers": new Set(["aws", "azure", "gcp", "microsoft365"]),
|
|
22
|
+
"open-compliance-issues": new Set(["true", "false"]),
|
|
23
|
+
"unacknowledged-changes": new Set(["true", "false"]),
|
|
24
|
+
};
|
|
25
|
+
export const ALLOWED_RESOURCE_TYPES = new Set([
|
|
26
|
+
"AWS::EC2::Instance",
|
|
27
|
+
"Microsoft.Sql/servers",
|
|
28
|
+
"AWS::RDS::DBInstance",
|
|
29
|
+
"databases/mssqlserver",
|
|
30
|
+
"databases/oracle",
|
|
31
|
+
"Nutanix/AuthorizationPolicy",
|
|
32
|
+
"Nutanix/Cluster",
|
|
33
|
+
"Nutanix/Host",
|
|
34
|
+
"Nutanix/Image",
|
|
35
|
+
"Nutanix/Role",
|
|
36
|
+
"Nutanix/Subnet",
|
|
37
|
+
"Nutanix/User",
|
|
38
|
+
"Nutanix/VirtualSwitch",
|
|
39
|
+
"Nutanix/Vm",
|
|
40
|
+
"Nutanix/VolumeGroup",
|
|
41
|
+
"vcenter/host",
|
|
42
|
+
"vcenter/virtualMachine",
|
|
43
|
+
]);
|
|
44
|
+
const PARAM_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*=.+$/;
|
|
45
|
+
const DATE_FILTER_KEYS = new Set([
|
|
46
|
+
"resource-creation-date-beginning",
|
|
47
|
+
"resource-creation-date-end",
|
|
48
|
+
]);
|
|
49
|
+
const DATE_FILTER_PATTERN = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}$/;
|
|
50
|
+
const ALLOWED_ACTIONS = new Set([
|
|
51
|
+
"list",
|
|
52
|
+
"get-latest-configuration",
|
|
53
|
+
"get-compliance",
|
|
54
|
+
]);
|
|
55
|
+
const ALLOWED_COMPLIANCE_STATUSES = new Set([
|
|
56
|
+
"open",
|
|
57
|
+
"remediated",
|
|
58
|
+
"policy-exception",
|
|
59
|
+
"mitigation",
|
|
60
|
+
]);
|
|
61
|
+
const sorted = (values) => [...values].sort();
|
|
62
|
+
const shellQuote = (value) => /^[A-Za-z0-9_./:-]+$/.test(value)
|
|
63
|
+
? value
|
|
64
|
+
: `'${value.replaceAll("'", "'\\''")}'`;
|
|
65
|
+
export const commandText = (command) => command.map(shellQuote).join(" ");
|
|
66
|
+
function runCommand(command, context) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn(context.cliEntryPoint, command.slice(1), {
|
|
69
|
+
shell: false,
|
|
70
|
+
stdio: "inherit",
|
|
71
|
+
});
|
|
72
|
+
child.once("error", reject);
|
|
73
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function parseKeyValues(rawItems) {
|
|
77
|
+
return rawItems.map((item) => {
|
|
78
|
+
if (!PARAM_PATTERN.test(item))
|
|
79
|
+
throw new Error(`invalid --param '${item}'. Use key=value (example: cloud-providers=aws).`);
|
|
80
|
+
const index = item.indexOf("=");
|
|
81
|
+
return [item.slice(0, index), item.slice(index + 1)];
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function parseParamMap(rawItems) {
|
|
85
|
+
const result = new Map();
|
|
86
|
+
for (const [key, value] of parseKeyValues(rawItems)) {
|
|
87
|
+
if (!result.has(key))
|
|
88
|
+
result.set(key, []);
|
|
89
|
+
result.get(key).push(value);
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
const mapGet = (map, key) => map instanceof Map ? map.get(key) : map[key];
|
|
94
|
+
const mapHas = (map, key) => map instanceof Map ? map.has(key) : Object.hasOwn(map, key);
|
|
95
|
+
const mapEntries = (map) => map instanceof Map ? [...map.entries()] : Object.entries(map);
|
|
96
|
+
const mapSize = (map) => map instanceof Map ? map.size : Object.keys(map).length;
|
|
97
|
+
export function validateArgs(args, paramMap) {
|
|
98
|
+
if (!ALLOWED_ACTIONS.has(args.action))
|
|
99
|
+
throw new Error(`unsupported action '${args.action}' (allowed: ${sorted(ALLOWED_ACTIONS).join(", ")}).`);
|
|
100
|
+
if (!args.company)
|
|
101
|
+
throw new Error(`resources ${args.action} requires --company.`);
|
|
102
|
+
if (args.action === "get-latest-configuration") {
|
|
103
|
+
if (!args.resourceNumericId)
|
|
104
|
+
throw new Error("resources get-latest-configuration requires --id.");
|
|
105
|
+
if (mapSize(paramMap))
|
|
106
|
+
throw new Error("resources get-latest-configuration does not support --param filters.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (args.action === "get-compliance") {
|
|
110
|
+
if (!args.resourceNumericId)
|
|
111
|
+
throw new Error("resources get-compliance requires --id.");
|
|
112
|
+
const statuses = mapGet(paramMap, "status");
|
|
113
|
+
if (!statuses?.length)
|
|
114
|
+
throw new Error("resources get-compliance requires --param status=...");
|
|
115
|
+
if (statuses.length !== 1)
|
|
116
|
+
throw new Error("resources get-compliance accepts exactly one --param status=...");
|
|
117
|
+
if (mapSize(paramMap) !== 1)
|
|
118
|
+
throw new Error("resources get-compliance only supports --param status=...");
|
|
119
|
+
const status = statuses[0].trim();
|
|
120
|
+
if (!ALLOWED_COMPLIANCE_STATUSES.has(status))
|
|
121
|
+
throw new Error(`invalid value '${status}' for --param status=... (allowed: ${sorted(ALLOWED_COMPLIANCE_STATUSES).join(", ")}).`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (args.resourceNumericId)
|
|
125
|
+
throw new Error("resources list does not support top-level --id.");
|
|
126
|
+
for (const key of mapEntries(paramMap).map(([key]) => key))
|
|
127
|
+
if (!ALLOWED_LIST_PARAMS.has(key))
|
|
128
|
+
throw new Error(`unsupported list filter '${key}'. Allowed filters: ${sorted(ALLOWED_LIST_PARAMS).join(", ")}.`);
|
|
129
|
+
for (const [key, allowed] of Object.entries(ALLOWED_LIST_ENUMS)) {
|
|
130
|
+
if (!mapHas(paramMap, key))
|
|
131
|
+
continue;
|
|
132
|
+
for (const raw of mapGet(paramMap, key))
|
|
133
|
+
for (const value of raw.split(",")) {
|
|
134
|
+
const candidate = value.trim();
|
|
135
|
+
if (!allowed.has(candidate))
|
|
136
|
+
throw new Error(`invalid value '${candidate}' for --param ${key}=... (allowed: ${sorted(allowed).join(", ")}).`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const key of DATE_FILTER_KEYS)
|
|
140
|
+
for (const raw of mapGet(paramMap, key) ?? []) {
|
|
141
|
+
if (!DATE_FILTER_PATTERN.test(raw))
|
|
142
|
+
throw new Error(`invalid date format for --param ${key}=... (expected: YYYY-MM-DD HH:MM:SS.mmm).`);
|
|
143
|
+
const date = new Date(raw.replace(" ", "T") + "Z");
|
|
144
|
+
if (Number.isNaN(date.getTime()) ||
|
|
145
|
+
date.toISOString().slice(0, 23).replace("T", " ") !== raw)
|
|
146
|
+
throw new Error(`invalid date value for --param ${key}=... (invalid date).`);
|
|
147
|
+
}
|
|
148
|
+
for (const raw of mapGet(paramMap, "resource-types") ?? [])
|
|
149
|
+
for (const value of raw.split(",")) {
|
|
150
|
+
const candidate = value.trim();
|
|
151
|
+
if (!ALLOWED_RESOURCE_TYPES.has(candidate))
|
|
152
|
+
throw new Error(`invalid value '${candidate}' for --param resource-types=... (allowed: ${sorted(ALLOWED_RESOURCE_TYPES).join(", ")}).`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export function buildCommand(args, paramMap, context = resolveCliContext()) {
|
|
156
|
+
const command = [
|
|
157
|
+
context.cliCommand,
|
|
158
|
+
"resources",
|
|
159
|
+
args.action,
|
|
160
|
+
"--company",
|
|
161
|
+
args.company,
|
|
162
|
+
];
|
|
163
|
+
if (["get-latest-configuration", "get-compliance"].includes(args.action)) {
|
|
164
|
+
command.push("--id", args.resourceNumericId);
|
|
165
|
+
if (args.action === "get-compliance")
|
|
166
|
+
for (const [key, values] of mapEntries(paramMap))
|
|
167
|
+
for (const value of values)
|
|
168
|
+
command.push(`--${key}`, value);
|
|
169
|
+
return command;
|
|
170
|
+
}
|
|
171
|
+
for (const [key, values] of mapEntries(paramMap))
|
|
172
|
+
for (const value of values)
|
|
173
|
+
command.push(`--${key}`, value);
|
|
174
|
+
return command;
|
|
175
|
+
}
|
|
176
|
+
function parseArgs(argv) {
|
|
177
|
+
const args = {
|
|
178
|
+
action: "list",
|
|
179
|
+
param: [],
|
|
180
|
+
execute: false,
|
|
181
|
+
ignoreDefaults: false,
|
|
182
|
+
};
|
|
183
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
184
|
+
const flag = argv[i];
|
|
185
|
+
if (flag === "--execute")
|
|
186
|
+
args.execute = true;
|
|
187
|
+
else if (flag === "--ignore-defaults")
|
|
188
|
+
args.ignoreDefaults = true;
|
|
189
|
+
else if (["--action", "--company", "--id", "--cli"].includes(flag)) {
|
|
190
|
+
if (++i >= argv.length)
|
|
191
|
+
throw new Error(`argument ${flag}: expected one argument`);
|
|
192
|
+
const value = argv[i];
|
|
193
|
+
if (flag === "--action")
|
|
194
|
+
args.action = value;
|
|
195
|
+
else if (flag === "--company")
|
|
196
|
+
args.company = value;
|
|
197
|
+
else if (flag === "--cli")
|
|
198
|
+
args.cli = value;
|
|
199
|
+
else
|
|
200
|
+
args.resourceNumericId = value;
|
|
201
|
+
}
|
|
202
|
+
else if (flag === "--param") {
|
|
203
|
+
if (++i >= argv.length)
|
|
204
|
+
throw new Error("argument --param: expected one argument");
|
|
205
|
+
args.param.push(argv[i]);
|
|
206
|
+
}
|
|
207
|
+
else if (flag === "--help" || flag === "-h") {
|
|
208
|
+
console.log("Build an opscompass resources list/get-latest-configuration/get-compliance command.");
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
throw new Error(`unrecognized arguments: ${flag}`);
|
|
213
|
+
}
|
|
214
|
+
return args;
|
|
215
|
+
}
|
|
216
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
217
|
+
let args;
|
|
218
|
+
try {
|
|
219
|
+
args = parseArgs(argv);
|
|
220
|
+
const context = resolveCliContext(args.cli);
|
|
221
|
+
args.company = resolveCompany(args.company, {
|
|
222
|
+
ignoreDefaults: args.ignoreDefaults,
|
|
223
|
+
context,
|
|
224
|
+
});
|
|
225
|
+
const paramMap = parseParamMap(args.param);
|
|
226
|
+
validateArgs(args, paramMap);
|
|
227
|
+
const command = buildCommand(args, paramMap, context);
|
|
228
|
+
console.log(commandText(command));
|
|
229
|
+
if (!args.execute)
|
|
230
|
+
return 0;
|
|
231
|
+
return await runCommand(command, context);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
|
+
return 2;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (isMainModule(import.meta.url)) {
|
|
239
|
+
process.exitCode = await main();
|
|
240
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { main as runWorkflowMain } from "./list_and_fetch_latest_configs.js";
|
|
2
|
+
function appendPositiveInteger(args, flag, value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return;
|
|
5
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
6
|
+
throw new Error(`${flag} must be a positive integer.`);
|
|
7
|
+
}
|
|
8
|
+
args.push(flag, String(value));
|
|
9
|
+
}
|
|
10
|
+
function appendBooleanFlag(args, flag, value) {
|
|
11
|
+
if (value === undefined)
|
|
12
|
+
return;
|
|
13
|
+
if (typeof value !== "boolean")
|
|
14
|
+
throw new Error(`${flag} must be a boolean.`);
|
|
15
|
+
if (value)
|
|
16
|
+
args.push(flag);
|
|
17
|
+
}
|
|
18
|
+
export function buildWorkflowArgs(value) {
|
|
19
|
+
if (typeof value.company !== "string" || !value.company.trim()) {
|
|
20
|
+
throw new Error("--company is required.");
|
|
21
|
+
}
|
|
22
|
+
const params = value.params === undefined ? [] : value.params;
|
|
23
|
+
if (!Array.isArray(params) ||
|
|
24
|
+
params.some((param) => typeof param !== "string")) {
|
|
25
|
+
throw new Error("params must be an array of key=value resource filters.");
|
|
26
|
+
}
|
|
27
|
+
const args = ["--company", value.company.trim()];
|
|
28
|
+
// These --param arguments belong to the workflow helper. Its existing
|
|
29
|
+
// resource command builder validates each key=value and emits the actual
|
|
30
|
+
// --<filter> value flags for `resources list`.
|
|
31
|
+
for (const param of params)
|
|
32
|
+
args.push("--param", param);
|
|
33
|
+
appendPositiveInteger(args, "--max-resources", value.maxResources);
|
|
34
|
+
appendPositiveInteger(args, "--page-size-hint", value.pageSizeHint);
|
|
35
|
+
appendPositiveInteger(args, "--max-pages", value.maxPages);
|
|
36
|
+
if (value.complianceStatus !== undefined) {
|
|
37
|
+
if (typeof value.complianceStatus !== "string" ||
|
|
38
|
+
!value.complianceStatus.trim()) {
|
|
39
|
+
throw new Error("--compliance-status must be a non-empty string.");
|
|
40
|
+
}
|
|
41
|
+
args.push("--compliance-status", value.complianceStatus.trim());
|
|
42
|
+
}
|
|
43
|
+
appendBooleanFlag(args, "--no-configs", value.noConfigs);
|
|
44
|
+
appendBooleanFlag(args, "--stop-on-error", value.stopOnError);
|
|
45
|
+
appendBooleanFlag(args, "--verbose", value.verbose);
|
|
46
|
+
appendBooleanFlag(args, "--ignore-defaults", value.ignoreDefaults);
|
|
47
|
+
// WHY: a bounded default prevents an omitted caller limit from triggering
|
|
48
|
+
// an unbounded configuration sweep on large tenants.
|
|
49
|
+
if (value.maxResources === undefined) {
|
|
50
|
+
args.push("--max-resources", "100");
|
|
51
|
+
}
|
|
52
|
+
return args;
|
|
53
|
+
}
|
|
54
|
+
export function formatCapturedError(values) {
|
|
55
|
+
return `[error] ${formatConsoleArgs(values)}`;
|
|
56
|
+
}
|
|
57
|
+
function formatConsoleArgs(values) {
|
|
58
|
+
return values.map((value) => String(value)).join(" ");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run the existing list -> configuration/compliance workflow with tool
|
|
62
|
+
* context, returning the workflow's captured text output.
|
|
63
|
+
*/
|
|
64
|
+
export async function runListAndFetchLatestConfigs(value, context) {
|
|
65
|
+
const args = buildWorkflowArgs(value);
|
|
66
|
+
if (context.signal.aborted)
|
|
67
|
+
throw new Error("Opscompass command aborted.");
|
|
68
|
+
const lines = [];
|
|
69
|
+
const capture = {
|
|
70
|
+
log(...values) {
|
|
71
|
+
lines.push(formatConsoleArgs(values));
|
|
72
|
+
},
|
|
73
|
+
error(...values) {
|
|
74
|
+
lines.push(formatCapturedError(values));
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const exitCode = await runWorkflowMain(args, capture, {
|
|
78
|
+
context,
|
|
79
|
+
workspaceRoot: context.workspaceRoot,
|
|
80
|
+
signal: context.signal,
|
|
81
|
+
});
|
|
82
|
+
const output = lines.join("\n");
|
|
83
|
+
if (exitCode !== 0) {
|
|
84
|
+
throw new Error(output.trim() || `Resources workflow failed with exit code ${exitCode}.`);
|
|
85
|
+
}
|
|
86
|
+
return output.trim() || "Resources workflow completed without output.";
|
|
87
|
+
}
|
|
88
|
+
export const listAndFetchLatestConfigsTool = {
|
|
89
|
+
name: "opscompass_resources_list_and_fetch_latest_configs",
|
|
90
|
+
label: "list and fetch latest configurations",
|
|
91
|
+
description: "Run the bounded resources inventory workflow and return its JSON output.",
|
|
92
|
+
parameters: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
company: { type: "string" },
|
|
96
|
+
params: { type: "array", items: { type: "string" } },
|
|
97
|
+
maxResources: { type: "integer", minimum: 1 },
|
|
98
|
+
pageSizeHint: { type: "integer", minimum: 1 },
|
|
99
|
+
maxPages: { type: "integer", minimum: 1 },
|
|
100
|
+
complianceStatus: { type: "string" },
|
|
101
|
+
noConfigs: { type: "boolean" },
|
|
102
|
+
stopOnError: { type: "boolean" },
|
|
103
|
+
verbose: { type: "boolean" },
|
|
104
|
+
ignoreDefaults: { type: "boolean" },
|
|
105
|
+
},
|
|
106
|
+
required: ["company"],
|
|
107
|
+
additionalProperties: false,
|
|
108
|
+
},
|
|
109
|
+
async execute(input, context) {
|
|
110
|
+
const value = input && typeof input === "object" && !Array.isArray(input)
|
|
111
|
+
? input
|
|
112
|
+
: {};
|
|
113
|
+
const output = await runListAndFetchLatestConfigs(value, context);
|
|
114
|
+
return { content: [{ type: "text", text: output }] };
|
|
115
|
+
},
|
|
116
|
+
};
|