argus-cloud-optimizer 0.5.0__tar.gz → 0.6.0__tar.gz
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.
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/.env.example +6 -2
- {argus_cloud_optimizer-0.5.0/argus_cloud_optimizer.egg-info → argus_cloud_optimizer-0.6.0}/PKG-INFO +35 -21
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/README.md +34 -20
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/cost_management.py +1 -1
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/monitor.py +0 -2
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0/argus_cloud_optimizer.egg-info}/PKG-INFO +35 -21
- argus_cloud_optimizer-0.6.0/core/__version__.py +1 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/agent/loop.py +38 -1
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/config.py +16 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/models/finding.py +8 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/engine.py +19 -13
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/loader.py +27 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/generator.py +5 -1
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/multi_cloud.py +4 -2
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/blog/posts/react-vs-rule-based.md +13 -7
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/concepts/adapters.md +15 -12
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/concepts/ai-providers.md +3 -3
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/concepts/ai-reasoning.md +11 -10
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/concepts/how-it-works.md +28 -28
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/contributing/development.md +25 -9
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/contributing/index.md +2 -2
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/contributing/new-adapter.md +37 -19
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/contributing/new-ai-provider.md +12 -1
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/deployment/aws.md +20 -11
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/deployment/azure.md +17 -12
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/deployment/gcp.md +15 -5
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/deployment/multi-account.md +9 -11
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/getting-started/configuration.md +15 -6
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/getting-started/first-scan.md +7 -6
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/getting-started/index.md +2 -2
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/getting-started/quickstart.md +18 -9
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/index.md +24 -27
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/reference/env-vars.md +17 -9
- argus_cloud_optimizer-0.6.0/docs/reference/iam-permissions.md +173 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/reference/report-schema.md +15 -5
- argus_cloud_optimizer-0.6.0/docs/reference/resource-types.md +139 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/reference/security.md +19 -11
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/reference/troubleshooting.md +24 -11
- argus_cloud_optimizer-0.6.0/docs/remediation/cli.md +215 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/remediation/index.md +1 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/remediation/jira.md +32 -14
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/remediation/policies.md +29 -17
- argus_cloud_optimizer-0.6.0/docs/roadmap.md +118 -0
- argus_cloud_optimizer-0.6.0/entrypoints/_remediation.py +159 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/entrypoints/cli.py +7 -3
- argus_cloud_optimizer-0.5.0/core/__version__.py +0 -1
- argus_cloud_optimizer-0.5.0/docs/reference/iam-permissions.md +0 -145
- argus_cloud_optimizer-0.5.0/docs/reference/resource-types.md +0 -103
- argus_cloud_optimizer-0.5.0/docs/remediation/cli.md +0 -174
- argus_cloud_optimizer-0.5.0/docs/roadmap.md +0 -87
- argus_cloud_optimizer-0.5.0/entrypoints/_remediation.py +0 -73
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/LICENSE +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/MANIFEST.in +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/accounts.yaml.example +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/adapter.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/auth.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/cloudtrail.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/cloudwatch.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/config.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/cost_explorer.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/resource_explorer.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/aws/retry.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/activity_log.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/adapter.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/resource_graph.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/retry.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/base.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/adapter.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/asset_inventory.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/billing.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/cloud_logging.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/cloud_monitoring.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/gcp/retry.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/ai/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/ai/anthropic.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/ai/azure_openai.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/ai/base.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/ai/bedrock.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/ai/vertexai.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/SOURCES.txt +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/dependency_links.txt +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/entry_points.txt +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/requires.txt +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/top_level.txt +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/agent/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/agent/chat.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/agent/prompts.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/log.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/models/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/py.typed +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/aws.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/azure.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/factory.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/gcp.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/models.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/registry/registry.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/audit.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/models.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/rightsizing.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/remediation/validator.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/comparison.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/delivery.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/export.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/reports/html.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/secrets.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/token_tracker.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/core/validation.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/deploy/aws/multi-account/hub/template.yaml +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/deploy/aws/multi-account/spoke-role.yaml +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/deploy/aws/single-account/template.yaml +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/deploy/azure/function-app.bicep +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/deploy/gcp/deploy.sh +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/blog/index.md +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/concepts/index.md +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/deployment/index.md +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/docs/reference/index.md +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/entrypoints/__init__.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/entrypoints/aws_lambda.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/entrypoints/azure_function.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/entrypoints/cli_chat.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/entrypoints/gcp_cloudrun.py +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/pyproject.toml +0 -0
- {argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/setup.cfg +0 -0
|
@@ -70,11 +70,13 @@ DRY_RUN=false
|
|
|
70
70
|
|
|
71
71
|
# -----------------------------------------------------------------------------
|
|
72
72
|
# Remediation — Policy Engine + Jira Integration
|
|
73
|
-
# Policies are YAML files in
|
|
73
|
+
# Policies are YAML files in ARGUS_POLICY_DIR. Argus never executes changes;
|
|
74
74
|
# it creates Jira tickets for human review. See config/policies/ for examples
|
|
75
75
|
# and CONTRIBUTING.md for how to write policies.
|
|
76
76
|
# -----------------------------------------------------------------------------
|
|
77
|
-
|
|
77
|
+
# Opt-in: create Jira tickets automatically after every scan (default: false).
|
|
78
|
+
# `argus policies apply --confirm` works without this; it is already explicit.
|
|
79
|
+
REMEDIATION_ENABLED=false
|
|
78
80
|
|
|
79
81
|
# Jira credentials — required for `argus policies apply --confirm`
|
|
80
82
|
# Create an API token at: https://id.atlassian.com/manage-profile/security/api-tokens
|
|
@@ -84,6 +86,8 @@ JIRA_API_TOKEN= # Atlassian API token (not your pass
|
|
|
84
86
|
|
|
85
87
|
# Directory containing your *.yaml policy files (one policy per file).
|
|
86
88
|
# Copy from config/policies.example/ to get started, then customise.
|
|
89
|
+
# Used by both `argus policies ...` and scheduled scans. The older name
|
|
90
|
+
# ARGUS_POLICIES_DIR is still accepted; if both are set, this one wins.
|
|
87
91
|
ARGUS_POLICY_DIR=./config/policies
|
|
88
92
|
|
|
89
93
|
# Path to integrations config (project key, labels, priority map).
|
{argus_cloud_optimizer-0.5.0/argus_cloud_optimizer.egg-info → argus_cloud_optimizer-0.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argus-cloud-optimizer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Argus — AI Cloud Detective for AWS, GCP, and Azure
|
|
5
5
|
Author-email: Vamshi Siddarth Gaddam <vamshisiddarth02@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -101,7 +101,17 @@ Top findings
|
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
104
|
-
## 🆕 What's new in v0.
|
|
104
|
+
## 🆕 What's new in v0.6.0
|
|
105
|
+
|
|
106
|
+
> **Breaking:** scheduled scans now create Jira tickets only when `REMEDIATION_ENABLED=true` (default `false`). If you ran v0.5.0 with Jira configured, set it to keep tickets flowing. `argus policies apply --confirm` is unaffected.
|
|
107
|
+
|
|
108
|
+
- **Dry runs never touch Jira** — `DRY_RUN=true` / `--dry-run` previously still created real tickets when Jira was configured.
|
|
109
|
+
- **One summary log line per remediation run**, with `error_type` on failures.
|
|
110
|
+
- **`ARGUS_POLICY_DIR`** is read by both the CLI and scheduled scans (`ARGUS_POLICIES_DIR` still accepted).
|
|
111
|
+
|
|
112
|
+
[Full changelog →](CHANGELOG.md) · [Project status →](docs/roadmap.md)
|
|
113
|
+
|
|
114
|
+
### v0.5.0
|
|
105
115
|
|
|
106
116
|
> **Remediation is now first-class.** Argus goes beyond reporting — it generates Jira tickets, suggests exact resize targets, and tracks acceptance rates.
|
|
107
117
|
|
|
@@ -159,25 +169,29 @@ argus policies apply --report scan_report.json --confirm
|
|
|
159
169
|
argus policies stats # acceptance rate per policy from audit log
|
|
160
170
|
```
|
|
161
171
|
|
|
172
|
+
To create tickets automatically after every scheduled scan (Lambda, Cloud Run, Azure Function), set `REMEDIATION_ENABLED=true`. It is off by default; `apply --confirm` above works without it.
|
|
173
|
+
|
|
162
174
|
### Bundled policies (13)
|
|
163
175
|
|
|
164
|
-
| Policy | Cloud | Action | Cost
|
|
165
|
-
|
|
166
|
-
| `aws-
|
|
167
|
-
| `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo |
|
|
168
|
-
| `aws-ebs-delete-unattached-30d` | AWS |
|
|
169
|
-
| `aws-
|
|
170
|
-
| `aws-
|
|
171
|
-
| `aws-
|
|
172
|
-
| `aws-
|
|
173
|
-
| `gcp-
|
|
174
|
-
| `gcp-sql-stop-idle-14d` | GCP | stop |
|
|
175
|
-
| `gcp-
|
|
176
|
-
| `azure-
|
|
177
|
-
| `azure-sql-resize-underutilised` | Azure | resize | $50/mo |
|
|
178
|
-
| `azure-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
| Policy | Cloud | Action | Cost floor | Idle days |
|
|
177
|
+
|--------|-------|--------|-----------|-----------|
|
|
178
|
+
| `aws-redshift-snapshot-delete-14d` | AWS | snapshot_delete | $100/mo | 14 |
|
|
179
|
+
| `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo | — |
|
|
180
|
+
| `aws-ebs-delete-unattached-30d` | AWS | snapshot_delete | — | 30 |
|
|
181
|
+
| `aws-elasticache-delete-idle-30d` | AWS | delete | — | 30 |
|
|
182
|
+
| `aws-elb-delete-idle` | AWS | delete | $10/mo | — |
|
|
183
|
+
| `aws-ec2-stop-idle-14d` | AWS | stop | — | 14 |
|
|
184
|
+
| `aws-lambda-delete-unused-30d` | AWS | delete | — | 30 |
|
|
185
|
+
| `gcp-gke-reduce-nodes-underutilised` | GCP | reduce_nodes | $200/mo | — |
|
|
186
|
+
| `gcp-sql-stop-idle-14d` | GCP | stop | — | 14 |
|
|
187
|
+
| `gcp-compute-stop-idle-7d` | GCP | stop | — | 7 |
|
|
188
|
+
| `azure-aks-reduce-nodes-underutilised` | Azure | reduce_nodes | $150/mo | — |
|
|
189
|
+
| `azure-sql-resize-underutilised` | Azure | resize | $50/mo | — |
|
|
190
|
+
| `azure-vm-stop-idle-14d` | Azure | stop | — | 14 |
|
|
191
|
+
|
|
192
|
+
Each policy also has AI-priority and metric conditions; see [Policies](https://vamshisiddarth.github.io/argus/remediation/policies/#bundled-policies) for the full table.
|
|
193
|
+
|
|
194
|
+
All policies skip resources tagged `environment: prod` (or `production`) and resources tagged `argus-exempt: "true"` — either tag on its own is enough. Add your own in `config/policies/` — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
181
195
|
|
|
182
196
|
### Safety guarantees
|
|
183
197
|
|
|
@@ -239,7 +253,7 @@ docker run --rm \
|
|
|
239
253
|
|
|
240
254
|
```bash
|
|
241
255
|
pip install argus-cloud-optimizer
|
|
242
|
-
argus --version # argus 0.
|
|
256
|
+
argus --version # argus 0.6.0
|
|
243
257
|
argus --help
|
|
244
258
|
```
|
|
245
259
|
|
|
@@ -304,7 +318,7 @@ argus chat # auto-detects cloud
|
|
|
304
318
|
```
|
|
305
319
|
|
|
306
320
|
```
|
|
307
|
-
Argus v0.
|
|
321
|
+
Argus v0.6.0 — AI Cloud Detective
|
|
308
322
|
Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
|
|
309
323
|
Tip: end a line with \ to continue on the next line.
|
|
310
324
|
Type your question, or /help for commands.
|
|
@@ -37,7 +37,17 @@ Top findings
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 What's new in v0.
|
|
40
|
+
## 🆕 What's new in v0.6.0
|
|
41
|
+
|
|
42
|
+
> **Breaking:** scheduled scans now create Jira tickets only when `REMEDIATION_ENABLED=true` (default `false`). If you ran v0.5.0 with Jira configured, set it to keep tickets flowing. `argus policies apply --confirm` is unaffected.
|
|
43
|
+
|
|
44
|
+
- **Dry runs never touch Jira** — `DRY_RUN=true` / `--dry-run` previously still created real tickets when Jira was configured.
|
|
45
|
+
- **One summary log line per remediation run**, with `error_type` on failures.
|
|
46
|
+
- **`ARGUS_POLICY_DIR`** is read by both the CLI and scheduled scans (`ARGUS_POLICIES_DIR` still accepted).
|
|
47
|
+
|
|
48
|
+
[Full changelog →](CHANGELOG.md) · [Project status →](docs/roadmap.md)
|
|
49
|
+
|
|
50
|
+
### v0.5.0
|
|
41
51
|
|
|
42
52
|
> **Remediation is now first-class.** Argus goes beyond reporting — it generates Jira tickets, suggests exact resize targets, and tracks acceptance rates.
|
|
43
53
|
|
|
@@ -95,25 +105,29 @@ argus policies apply --report scan_report.json --confirm
|
|
|
95
105
|
argus policies stats # acceptance rate per policy from audit log
|
|
96
106
|
```
|
|
97
107
|
|
|
108
|
+
To create tickets automatically after every scheduled scan (Lambda, Cloud Run, Azure Function), set `REMEDIATION_ENABLED=true`. It is off by default; `apply --confirm` above works without it.
|
|
109
|
+
|
|
98
110
|
### Bundled policies (13)
|
|
99
111
|
|
|
100
|
-
| Policy | Cloud | Action | Cost
|
|
101
|
-
|
|
102
|
-
| `aws-
|
|
103
|
-
| `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo |
|
|
104
|
-
| `aws-ebs-delete-unattached-30d` | AWS |
|
|
105
|
-
| `aws-
|
|
106
|
-
| `aws-
|
|
107
|
-
| `aws-
|
|
108
|
-
| `aws-
|
|
109
|
-
| `gcp-
|
|
110
|
-
| `gcp-sql-stop-idle-14d` | GCP | stop |
|
|
111
|
-
| `gcp-
|
|
112
|
-
| `azure-
|
|
113
|
-
| `azure-sql-resize-underutilised` | Azure | resize | $50/mo |
|
|
114
|
-
| `azure-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
| Policy | Cloud | Action | Cost floor | Idle days |
|
|
113
|
+
|--------|-------|--------|-----------|-----------|
|
|
114
|
+
| `aws-redshift-snapshot-delete-14d` | AWS | snapshot_delete | $100/mo | 14 |
|
|
115
|
+
| `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo | — |
|
|
116
|
+
| `aws-ebs-delete-unattached-30d` | AWS | snapshot_delete | — | 30 |
|
|
117
|
+
| `aws-elasticache-delete-idle-30d` | AWS | delete | — | 30 |
|
|
118
|
+
| `aws-elb-delete-idle` | AWS | delete | $10/mo | — |
|
|
119
|
+
| `aws-ec2-stop-idle-14d` | AWS | stop | — | 14 |
|
|
120
|
+
| `aws-lambda-delete-unused-30d` | AWS | delete | — | 30 |
|
|
121
|
+
| `gcp-gke-reduce-nodes-underutilised` | GCP | reduce_nodes | $200/mo | — |
|
|
122
|
+
| `gcp-sql-stop-idle-14d` | GCP | stop | — | 14 |
|
|
123
|
+
| `gcp-compute-stop-idle-7d` | GCP | stop | — | 7 |
|
|
124
|
+
| `azure-aks-reduce-nodes-underutilised` | Azure | reduce_nodes | $150/mo | — |
|
|
125
|
+
| `azure-sql-resize-underutilised` | Azure | resize | $50/mo | — |
|
|
126
|
+
| `azure-vm-stop-idle-14d` | Azure | stop | — | 14 |
|
|
127
|
+
|
|
128
|
+
Each policy also has AI-priority and metric conditions; see [Policies](https://vamshisiddarth.github.io/argus/remediation/policies/#bundled-policies) for the full table.
|
|
129
|
+
|
|
130
|
+
All policies skip resources tagged `environment: prod` (or `production`) and resources tagged `argus-exempt: "true"` — either tag on its own is enough. Add your own in `config/policies/` — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
117
131
|
|
|
118
132
|
### Safety guarantees
|
|
119
133
|
|
|
@@ -175,7 +189,7 @@ docker run --rm \
|
|
|
175
189
|
|
|
176
190
|
```bash
|
|
177
191
|
pip install argus-cloud-optimizer
|
|
178
|
-
argus --version # argus 0.
|
|
192
|
+
argus --version # argus 0.6.0
|
|
179
193
|
argus --help
|
|
180
194
|
```
|
|
181
195
|
|
|
@@ -240,7 +254,7 @@ argus chat # auto-detects cloud
|
|
|
240
254
|
```
|
|
241
255
|
|
|
242
256
|
```
|
|
243
|
-
Argus v0.
|
|
257
|
+
Argus v0.6.0 — AI Cloud Detective
|
|
244
258
|
Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
|
|
245
259
|
Tip: end a line with \ to continue on the next line.
|
|
246
260
|
Type your question, or /help for commands.
|
{argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0}/adapters/azure/cost_management.py
RENAMED
|
@@ -102,7 +102,7 @@ def _query_batch(
|
|
|
102
102
|
aggregation={"totalCost": {"name": "PreTaxCost", "function": "Sum"}}, # type: ignore[dict-item]
|
|
103
103
|
grouping=[QueryGrouping(type="Dimension", name="ResourceId")],
|
|
104
104
|
filter=QueryFilter(
|
|
105
|
-
dimensions=QueryComparisonExpression(
|
|
105
|
+
dimensions=QueryComparisonExpression( # type: ignore[call-overload]
|
|
106
106
|
name="ResourceId",
|
|
107
107
|
operator="In",
|
|
108
108
|
values=resource_ids,
|
|
@@ -372,8 +372,6 @@ def _discover_metrics(
|
|
|
372
372
|
credential: Any = None,
|
|
373
373
|
) -> list[tuple[str, str]]:
|
|
374
374
|
"""Auto-discover available metrics for unknown Azure resource types."""
|
|
375
|
-
from azure.monitor.query import MetricsQueryClient
|
|
376
|
-
|
|
377
375
|
cred = credential or DefaultAzureCredential()
|
|
378
376
|
client = MetricsQueryClient(cred, connection_timeout=10, read_timeout=60)
|
|
379
377
|
discovered: list[tuple[str, str]] = []
|
{argus_cloud_optimizer-0.5.0 → argus_cloud_optimizer-0.6.0/argus_cloud_optimizer.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argus-cloud-optimizer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Argus — AI Cloud Detective for AWS, GCP, and Azure
|
|
5
5
|
Author-email: Vamshi Siddarth Gaddam <vamshisiddarth02@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -101,7 +101,17 @@ Top findings
|
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
104
|
-
## 🆕 What's new in v0.
|
|
104
|
+
## 🆕 What's new in v0.6.0
|
|
105
|
+
|
|
106
|
+
> **Breaking:** scheduled scans now create Jira tickets only when `REMEDIATION_ENABLED=true` (default `false`). If you ran v0.5.0 with Jira configured, set it to keep tickets flowing. `argus policies apply --confirm` is unaffected.
|
|
107
|
+
|
|
108
|
+
- **Dry runs never touch Jira** — `DRY_RUN=true` / `--dry-run` previously still created real tickets when Jira was configured.
|
|
109
|
+
- **One summary log line per remediation run**, with `error_type` on failures.
|
|
110
|
+
- **`ARGUS_POLICY_DIR`** is read by both the CLI and scheduled scans (`ARGUS_POLICIES_DIR` still accepted).
|
|
111
|
+
|
|
112
|
+
[Full changelog →](CHANGELOG.md) · [Project status →](docs/roadmap.md)
|
|
113
|
+
|
|
114
|
+
### v0.5.0
|
|
105
115
|
|
|
106
116
|
> **Remediation is now first-class.** Argus goes beyond reporting — it generates Jira tickets, suggests exact resize targets, and tracks acceptance rates.
|
|
107
117
|
|
|
@@ -159,25 +169,29 @@ argus policies apply --report scan_report.json --confirm
|
|
|
159
169
|
argus policies stats # acceptance rate per policy from audit log
|
|
160
170
|
```
|
|
161
171
|
|
|
172
|
+
To create tickets automatically after every scheduled scan (Lambda, Cloud Run, Azure Function), set `REMEDIATION_ENABLED=true`. It is off by default; `apply --confirm` above works without it.
|
|
173
|
+
|
|
162
174
|
### Bundled policies (13)
|
|
163
175
|
|
|
164
|
-
| Policy | Cloud | Action | Cost
|
|
165
|
-
|
|
166
|
-
| `aws-
|
|
167
|
-
| `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo |
|
|
168
|
-
| `aws-ebs-delete-unattached-30d` | AWS |
|
|
169
|
-
| `aws-
|
|
170
|
-
| `aws-
|
|
171
|
-
| `aws-
|
|
172
|
-
| `aws-
|
|
173
|
-
| `gcp-
|
|
174
|
-
| `gcp-sql-stop-idle-14d` | GCP | stop |
|
|
175
|
-
| `gcp-
|
|
176
|
-
| `azure-
|
|
177
|
-
| `azure-sql-resize-underutilised` | Azure | resize | $50/mo |
|
|
178
|
-
| `azure-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
| Policy | Cloud | Action | Cost floor | Idle days |
|
|
177
|
+
|--------|-------|--------|-----------|-----------|
|
|
178
|
+
| `aws-redshift-snapshot-delete-14d` | AWS | snapshot_delete | $100/mo | 14 |
|
|
179
|
+
| `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo | — |
|
|
180
|
+
| `aws-ebs-delete-unattached-30d` | AWS | snapshot_delete | — | 30 |
|
|
181
|
+
| `aws-elasticache-delete-idle-30d` | AWS | delete | — | 30 |
|
|
182
|
+
| `aws-elb-delete-idle` | AWS | delete | $10/mo | — |
|
|
183
|
+
| `aws-ec2-stop-idle-14d` | AWS | stop | — | 14 |
|
|
184
|
+
| `aws-lambda-delete-unused-30d` | AWS | delete | — | 30 |
|
|
185
|
+
| `gcp-gke-reduce-nodes-underutilised` | GCP | reduce_nodes | $200/mo | — |
|
|
186
|
+
| `gcp-sql-stop-idle-14d` | GCP | stop | — | 14 |
|
|
187
|
+
| `gcp-compute-stop-idle-7d` | GCP | stop | — | 7 |
|
|
188
|
+
| `azure-aks-reduce-nodes-underutilised` | Azure | reduce_nodes | $150/mo | — |
|
|
189
|
+
| `azure-sql-resize-underutilised` | Azure | resize | $50/mo | — |
|
|
190
|
+
| `azure-vm-stop-idle-14d` | Azure | stop | — | 14 |
|
|
191
|
+
|
|
192
|
+
Each policy also has AI-priority and metric conditions; see [Policies](https://vamshisiddarth.github.io/argus/remediation/policies/#bundled-policies) for the full table.
|
|
193
|
+
|
|
194
|
+
All policies skip resources tagged `environment: prod` (or `production`) and resources tagged `argus-exempt: "true"` — either tag on its own is enough. Add your own in `config/policies/` — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
181
195
|
|
|
182
196
|
### Safety guarantees
|
|
183
197
|
|
|
@@ -239,7 +253,7 @@ docker run --rm \
|
|
|
239
253
|
|
|
240
254
|
```bash
|
|
241
255
|
pip install argus-cloud-optimizer
|
|
242
|
-
argus --version # argus 0.
|
|
256
|
+
argus --version # argus 0.6.0
|
|
243
257
|
argus --help
|
|
244
258
|
```
|
|
245
259
|
|
|
@@ -304,7 +318,7 @@ argus chat # auto-detects cloud
|
|
|
304
318
|
```
|
|
305
319
|
|
|
306
320
|
```
|
|
307
|
-
Argus v0.
|
|
321
|
+
Argus v0.6.0 — AI Cloud Detective
|
|
308
322
|
Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
|
|
309
323
|
Tip: end a line with \ to continue on the next line.
|
|
310
324
|
Type your question, or /help for commands.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -164,7 +164,8 @@ class AgentLoop:
|
|
|
164
164
|
findings_count=len(tc.arguments.get("findings", [])),
|
|
165
165
|
**self.tracker.summary(),
|
|
166
166
|
)
|
|
167
|
-
|
|
167
|
+
findings, summary = _parse_findings(tc.arguments, cloud=cloud)
|
|
168
|
+
return _attach_accounts(findings, accounts), summary
|
|
168
169
|
|
|
169
170
|
# Persist the assistant turn before executing tools
|
|
170
171
|
messages.append(
|
|
@@ -471,6 +472,42 @@ def _compress_resource(r: dict) -> dict:
|
|
|
471
472
|
return out
|
|
472
473
|
|
|
473
474
|
|
|
475
|
+
def _attach_accounts(
|
|
476
|
+
findings: list[ResourceFinding], accounts: list[dict[str, Any]]
|
|
477
|
+
) -> list[ResourceFinding]:
|
|
478
|
+
"""
|
|
479
|
+
Stamp each finding with the account/project/subscription it came from.
|
|
480
|
+
|
|
481
|
+
One account per run (AWS, GCP, single-subscription Azure): every finding
|
|
482
|
+
belongs to it. Several accounts in one run (multi-subscription Azure):
|
|
483
|
+
match on the ``/subscriptions/<id>/`` segment of the resource ID.
|
|
484
|
+
Findings that can't be attributed keep ``account_id=None``.
|
|
485
|
+
"""
|
|
486
|
+
if not accounts:
|
|
487
|
+
return findings
|
|
488
|
+
names = {str(a.get("id")): a.get("name") for a in accounts if a.get("id")}
|
|
489
|
+
only = next(iter(names)) if len(names) == 1 else None
|
|
490
|
+
for f in findings:
|
|
491
|
+
acct = only or _subscription_from_resource_id(f.resource_id, names)
|
|
492
|
+
if acct:
|
|
493
|
+
f.account_id = acct
|
|
494
|
+
f.account_name = names.get(acct) or acct
|
|
495
|
+
return findings
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _subscription_from_resource_id(
|
|
499
|
+
resource_id: str, known: dict[str, Any]
|
|
500
|
+
) -> str | None:
|
|
501
|
+
parts = resource_id.split("/")
|
|
502
|
+
for i, part in enumerate(parts[:-1]):
|
|
503
|
+
if part.lower() == "subscriptions":
|
|
504
|
+
sub = parts[i + 1].lower()
|
|
505
|
+
for acct in known:
|
|
506
|
+
if acct.lower() == sub:
|
|
507
|
+
return acct
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
|
|
474
511
|
def _parse_findings(
|
|
475
512
|
args: dict[str, Any],
|
|
476
513
|
cloud: str,
|
|
@@ -159,6 +159,19 @@ class ReportSettings(BaseSettings):
|
|
|
159
159
|
)
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Remediation
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class RemediationSettings(BaseSettings):
|
|
168
|
+
model_config = SettingsConfigDict(env_prefix="", env_file=".env", extra="ignore")
|
|
169
|
+
|
|
170
|
+
# Opt-in: the post-scan runner creates Jira tickets only when this is true.
|
|
171
|
+
# `argus policies apply --confirm` is already explicit and is not gated.
|
|
172
|
+
enabled: bool = Field(False, alias="REMEDIATION_ENABLED")
|
|
173
|
+
|
|
174
|
+
|
|
162
175
|
# ---------------------------------------------------------------------------
|
|
163
176
|
# Scan tuning
|
|
164
177
|
# ---------------------------------------------------------------------------
|
|
@@ -212,6 +225,9 @@ class Settings(BaseSettings):
|
|
|
212
225
|
report: ReportSettings = Field(default_factory=ReportSettings) # type: ignore[arg-type]
|
|
213
226
|
scan: ScanSettings = Field(default_factory=ScanSettings) # type: ignore[arg-type]
|
|
214
227
|
log: LogSettings = Field(default_factory=LogSettings) # type: ignore[arg-type]
|
|
228
|
+
remediation: RemediationSettings = Field(
|
|
229
|
+
default_factory=RemediationSettings # type: ignore[arg-type]
|
|
230
|
+
)
|
|
215
231
|
|
|
216
232
|
|
|
217
233
|
@lru_cache(maxsize=1)
|
|
@@ -31,6 +31,10 @@ class ResourceFinding:
|
|
|
31
31
|
name: str | None = None
|
|
32
32
|
last_activity: datetime | None = None
|
|
33
33
|
status: FindingStatus = "new"
|
|
34
|
+
# Set by the agent loop from the scanned account/project/subscription.
|
|
35
|
+
# Used by remediation `accounts` scope filters and runbook templates.
|
|
36
|
+
account_id: str | None = None
|
|
37
|
+
account_name: str | None = None
|
|
34
38
|
|
|
35
39
|
def to_dict(self) -> dict[str, Any]:
|
|
36
40
|
return {
|
|
@@ -50,6 +54,8 @@ class ResourceFinding:
|
|
|
50
54
|
),
|
|
51
55
|
"scan_time": self.scan_time.isoformat(),
|
|
52
56
|
"status": self.status,
|
|
57
|
+
"account_id": self.account_id,
|
|
58
|
+
"account_name": self.account_name,
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
@classmethod
|
|
@@ -73,4 +79,6 @@ class ResourceFinding:
|
|
|
73
79
|
last_activity=last_activity,
|
|
74
80
|
scan_time=scan_time,
|
|
75
81
|
status=data.get("status", "new"),
|
|
82
|
+
account_id=data.get("account_id"),
|
|
83
|
+
account_name=data.get("account_name"),
|
|
76
84
|
)
|
|
@@ -91,8 +91,7 @@ def _scope_includes(finding: ResourceFinding, scope: ScopeFilter) -> bool:
|
|
|
91
91
|
if scope.cloud_platforms and finding.cloud not in scope.cloud_platforms:
|
|
92
92
|
return False
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
if scope.accounts and account not in scope.accounts:
|
|
94
|
+
if scope.accounts and finding.account_id not in scope.accounts:
|
|
96
95
|
return False
|
|
97
96
|
|
|
98
97
|
if scope.regions and finding.region not in scope.regions:
|
|
@@ -108,30 +107,37 @@ def _scope_excludes(finding: ResourceFinding, scope: ScopeFilter) -> bool:
|
|
|
108
107
|
if scope.cloud_platforms and finding.cloud in scope.cloud_platforms:
|
|
109
108
|
return True
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
if scope.accounts and account in scope.accounts:
|
|
110
|
+
if scope.accounts and finding.account_id in scope.accounts:
|
|
113
111
|
return True
|
|
114
112
|
|
|
115
113
|
if scope.regions and finding.region in scope.regions:
|
|
116
114
|
return True
|
|
117
115
|
|
|
118
|
-
if scope.tags and
|
|
116
|
+
if scope.tags and _any_tag_entry_matches(finding.tags, scope.tags):
|
|
119
117
|
return True
|
|
120
118
|
|
|
121
119
|
return False
|
|
122
120
|
|
|
123
121
|
|
|
122
|
+
def _tag_entry_matches(resource_tags: dict, tag_entry: dict[str, list[str]]) -> bool:
|
|
123
|
+
"""True if the resource satisfies every key in one tag entry."""
|
|
124
|
+
return all(resource_tags.get(key) in allowed for key, allowed in tag_entry.items())
|
|
125
|
+
|
|
126
|
+
|
|
124
127
|
def _tags_match(
|
|
125
128
|
resource_tags: dict,
|
|
126
129
|
scope_tags: tuple[dict[str, list[str]], ...],
|
|
127
130
|
) -> bool:
|
|
128
|
-
"""
|
|
129
|
-
for
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
"""Include semantics: the resource must satisfy ALL tag entries."""
|
|
132
|
+
return all(_tag_entry_matches(resource_tags, entry) for entry in scope_tags)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _any_tag_entry_matches(
|
|
136
|
+
resource_tags: dict,
|
|
137
|
+
scope_tags: tuple[dict[str, list[str]], ...],
|
|
138
|
+
) -> bool:
|
|
139
|
+
"""Exclude semantics: matching ANY tag entry excludes the resource."""
|
|
140
|
+
return any(_tag_entry_matches(resource_tags, entry) for entry in scope_tags)
|
|
135
141
|
|
|
136
142
|
|
|
137
143
|
def _tier1_matches(finding: ResourceFinding, cond: Condition) -> bool:
|
|
@@ -212,7 +218,7 @@ def _build_proposal(finding: ResourceFinding, policy: Policy) -> ChangeProposal:
|
|
|
212
218
|
action=policy.action,
|
|
213
219
|
resource_id=finding.resource_id,
|
|
214
220
|
region=finding.region,
|
|
215
|
-
account_id=
|
|
221
|
+
account_id=finding.account_id or "",
|
|
216
222
|
)
|
|
217
223
|
or f"# No CLI template available for {finding.resource_type} / {policy.action}"
|
|
218
224
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import yaml
|
|
@@ -30,6 +31,32 @@ _VALID_ACTIONS = frozenset(
|
|
|
30
31
|
|
|
31
32
|
_SUPPORTED_VERSIONS = frozenset({"1"})
|
|
32
33
|
|
|
34
|
+
DEFAULT_POLICY_DIR = "./config/policies"
|
|
35
|
+
POLICY_DIR_ENV = "ARGUS_POLICY_DIR"
|
|
36
|
+
# Older name used by the CLI and .env.example before v0.6.0. Still honored.
|
|
37
|
+
POLICY_DIR_ENV_ALIAS = "ARGUS_POLICIES_DIR"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_policy_dir() -> str:
|
|
41
|
+
"""
|
|
42
|
+
Return the policy directory from the environment.
|
|
43
|
+
|
|
44
|
+
Precedence: ARGUS_POLICY_DIR, then ARGUS_POLICIES_DIR, then
|
|
45
|
+
./config/policies. Used by both the CLI and the post-scan runner so
|
|
46
|
+
`argus policies plan` and a scheduled scan always read the same folder.
|
|
47
|
+
"""
|
|
48
|
+
primary = os.environ.get(POLICY_DIR_ENV, "").strip()
|
|
49
|
+
alias = os.environ.get(POLICY_DIR_ENV_ALIAS, "").strip()
|
|
50
|
+
if primary and alias and primary != alias:
|
|
51
|
+
logger.warning(
|
|
52
|
+
"policy_dir_env_conflict using=%s=%s ignored=%s=%s",
|
|
53
|
+
POLICY_DIR_ENV,
|
|
54
|
+
primary,
|
|
55
|
+
POLICY_DIR_ENV_ALIAS,
|
|
56
|
+
alias,
|
|
57
|
+
)
|
|
58
|
+
return primary or alias or DEFAULT_POLICY_DIR
|
|
59
|
+
|
|
33
60
|
|
|
34
61
|
def load_policies(policies_dir: str | Path) -> list[Policy]:
|
|
35
62
|
"""
|
|
@@ -17,6 +17,10 @@ def _registry() -> ResourceRegistry:
|
|
|
17
17
|
return get_registry()
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
# Report JSON schema version (see docs/reference/report-schema.md).
|
|
21
|
+
# 1.0.1: findings gained optional account_id / account_name.
|
|
22
|
+
SCHEMA_VERSION = "1.0.1"
|
|
23
|
+
|
|
20
24
|
# Maximum findings shown as individual rows in the Slack digest
|
|
21
25
|
SLACK_DIGEST_LIMIT = 5
|
|
22
26
|
|
|
@@ -48,7 +52,7 @@ def build_report(
|
|
|
48
52
|
total_waste = sum(f.estimated_monthly_cost for f in sorted_findings)
|
|
49
53
|
|
|
50
54
|
return {
|
|
51
|
-
"schema_version":
|
|
55
|
+
"schema_version": SCHEMA_VERSION,
|
|
52
56
|
"scan_id": str(uuid.uuid4()),
|
|
53
57
|
"generated_at": datetime.now(tz=timezone.utc).isoformat(),
|
|
54
58
|
"cloud": cloud,
|
|
@@ -11,6 +11,8 @@ import uuid
|
|
|
11
11
|
from datetime import datetime, timezone
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
+
from core.reports.generator import SCHEMA_VERSION
|
|
15
|
+
|
|
14
16
|
RESOURCE_TAXONOMY: dict[str, str] = {
|
|
15
17
|
# Compute
|
|
16
18
|
"AWS::EC2::Instance": "Compute Instance",
|
|
@@ -110,7 +112,7 @@ def merge_reports(reports: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
110
112
|
total_waste = sum(f["estimated_monthly_cost"] for f in all_findings)
|
|
111
113
|
|
|
112
114
|
return {
|
|
113
|
-
"schema_version":
|
|
115
|
+
"schema_version": SCHEMA_VERSION,
|
|
114
116
|
"scan_id": str(uuid.uuid4()),
|
|
115
117
|
"generated_at": datetime.now(tz=timezone.utc).isoformat(),
|
|
116
118
|
"cloud": "multi",
|
|
@@ -128,7 +130,7 @@ def merge_reports(reports: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
128
130
|
|
|
129
131
|
def _empty_merged_report() -> dict[str, Any]:
|
|
130
132
|
return {
|
|
131
|
-
"schema_version":
|
|
133
|
+
"schema_version": SCHEMA_VERSION,
|
|
132
134
|
"scan_id": str(uuid.uuid4()),
|
|
133
135
|
"generated_at": datetime.now(tz=timezone.utc).isoformat(),
|
|
134
136
|
"cloud": "multi",
|