-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMakefile
More file actions
274 lines (246 loc) · 13.4 KB
/
Copy pathMakefile
File metadata and controls
274 lines (246 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# CloudOps Multi-Agent Platform -- Makefile
#
# Common commands for development, testing, and deployment.
# Run `make help` to see all available targets.
.PHONY: help setup configure quickstart reconfigure-shared nuke-shared-config test test-unit test-integration test-topology test-scripts lint plan deploy deploy-auto destroy destroy-all package build-agents clean
SHELL := /bin/bash
# NOTE: we deliberately do NOT `-include .env` + `export` here. Make's
# default behaviour is that Makefile-level assignments beat inherited
# environment variables, which meant `AWS_REGION=eu-west-1 make deploy-auto`
# silently got overridden by whatever .env held. All downstream scripts
# (deploy.sh, run-local.sh, invoke_agent.py) source .env themselves and
# correctly honour CLI overrides.
VENV := .venv/bin
PYTHON := $(VENV)/python
PYTEST := $(PYTHON) -m pytest
PIP := $(VENV)/pip
HASH_DIR := .lambda-hashes
# Default target
help: ## Show this help message
@echo "CloudOps Multi-Agent Platform"
@echo ""
@echo "Usage: make <target>"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
# ---------------------------------------------------------------------------
# Setup / Configure
# ---------------------------------------------------------------------------
setup: ## Interactive identity setup (.env) + install Python deps
@./scripts/deploy.sh --setup
configure: ## First-run shared project config (writes to SSM via shared-config module)
@./scripts/deploy.sh --configure
quickstart: ## First-time deploy: setup + configure + deploy in one command
@echo "══════════════════════════════════════════════════════════"
@echo " CloudOps Multi-Agent Platform — Quick Start"
@echo "══════════════════════════════════════════════════════════"
@echo ""
@echo "Step 1/3: Setting up local environment..."
@$(MAKE) setup
@echo ""
@echo "Step 2/3: Configuring shared deployment settings..."
@$(MAKE) configure
@echo ""
@echo "Step 3/3: Deploying platform (this takes ~10 minutes)..."
@$(MAKE) deploy-auto
@echo ""
@echo "══════════════════════════════════════════════════════════"
@echo " Done! Your platform is live at the CloudFront URL above."
@echo " Run 'make run-local' for local development."
@echo "══════════════════════════════════════════════════════════"
reconfigure-shared: ## Change shared config with diff + APPLY CHANGES confirmation
@./scripts/deploy.sh --reconfigure-shared
nuke-shared-config: ## Delete every SSM parameter under /$$PROJECT_PREFIX/$$ENVIRONMENT/config (typed confirmation required)
@./scripts/deploy.sh --nuke-shared-config
# ---------------------------------------------------------------------------
# Testing
# ---------------------------------------------------------------------------
test: ## Run all unit + topology tests (no AWS, ~20s)
$(PYTEST) tests/unit/ tests/topology/ -v
test-unit: ## Run unit + topology tests (no AWS, ~20s)
$(PYTEST) tests/unit/ tests/topology/ -v
test-integration: ## Run integration tests against live stack (requires deployed stack + Cognito creds)
$(PYTEST) tests/integration/ -v --tb=short
test-topology: ## Run ONLY the topology tests (slice + terraform validate, no AWS)
$(PYTEST) tests/topology/ -v --tb=short
test-scripts: ## Run shell-level unit tests for scripts/lib/* (auth.sh, cmd_setup, ...)
@for t in tests/scripts/test_*.sh; do echo "==> $$t"; bash "$$t" || exit 1; done
# ---------------------------------------------------------------------------
# Deployment
# ---------------------------------------------------------------------------
plan: ## Terraform plan only (no apply)
./scripts/deploy.sh --all --plan
deploy: ## Interactive deployment (prompts for what to deploy)
./scripts/deploy.sh
deploy-auto: package ## Non-interactive full deploy (CI-friendly, no prompts)
./scripts/deploy.sh --all --auto
destroy: ## Destroy infrastructure (with confirmation)
./scripts/deploy.sh --destroy
destroy-all: ## Full teardown: infra + ECR + memory + state backend (with confirmation)
./scripts/deploy.sh --destroy-all
build-agents: ## Build container images for all resolved agents
./scripts/deploy.sh --build-agents-only
run-local: ## Run frontend locally with Cognito auth (fetches config from Terraform)
./scripts/run-local.sh
run-local-bypass: ## Run frontend locally without auth (dev bypass)
./scripts/run-local.sh --bypass-auth
# ---------------------------------------------------------------------------
# Health events backfill (optional, needs Business+ AWS Support)
# ---------------------------------------------------------------------------
# DAYS window in days (default 30, max 90 — AWS Health retention).
# ORG set to 1 or true to use DescribeEventsForOrganization
# (requires Health org view enabled + run from mgmt/delegated admin).
# ROLE_ARN optional IAM role to assume before calling Health/Organizations.
# DRY_RUN set to 1 or true to list events without writing to DynamoDB.
#
# Examples:
# make backfill-health DAYS=30
# make backfill-health DAYS=90 ORG=1
# make backfill-health DAYS=14 ORG=1 ROLE_ARN=arn:aws:iam::111122223333:role/HealthBackfill
# make backfill-health DAYS=30 DRY_RUN=1
backfill-health: ## Backfill AWS Health events (needs Business+ Support)
@.venv/bin/python scripts/backfill_health.py \
--days $(or $(DAYS),30) \
$(if $(filter 1 true True TRUE,$(ORG)),--org,) \
$(if $(ROLE_ARN),--role-arn $(ROLE_ARN),) \
$(if $(filter 1 true True TRUE,$(DRY_RUN)),--dry-run,)
# ---------------------------------------------------------------------------
# Lambda Packaging -- hash-based rebuild (skip unchanged tools)
# ---------------------------------------------------------------------------
LAMBDA_DIRS := $(wildcard src/lambda/mcp/*/)
LAMBDA_TOOLS := $(patsubst src/lambda/mcp/%/,%,$(LAMBDA_DIRS))
$(HASH_DIR):
@mkdir -p $(HASH_DIR)
package: $(HASH_DIR) ## Package Lambda tools (only changed ones, parallel)
@echo "Packaging Lambda tools..."
@# The shared/ subdir is a helper package (cross_account etc.) copied into
@# every tool zip — it has no standalone handler. Compute its hash once
@# and fold it into each tool's hash so edits there force a rebuild of
@# every tool (same idea as hierarchy.json triggering agent rebuilds).
@shared_hash=$$(find src/lambda/mcp/shared -type f \( -name '*.py' \) -exec shasum {} + 2>/dev/null | sort | shasum | cut -d' ' -f1); \
_pids=""; _failed=0; \
for dir in src/lambda/mcp/*/; do \
tool=$$(basename "$$dir"); \
if [ "$$tool" = "shared" ]; then continue; fi; \
tool_hash=$$(find "$$dir" -type f \( -name '*.py' -o -name '*.txt' -o -name '*.json' \) -exec shasum {} + 2>/dev/null | sort | shasum | cut -d' ' -f1); \
current_hash=$$(printf '%s%s' "$$tool_hash" "$$shared_hash" | shasum | cut -d' ' -f1); \
stored_hash=$$(cat $(HASH_DIR)/$$tool.sha 2>/dev/null || echo ""); \
if [ "$$current_hash" = "$$stored_hash" ] && [ -f "src/lambda/mcp/$$tool.zip" ]; then \
echo " $$tool: unchanged, skipping"; \
else \
( \
echo " Packaging $$tool..."; \
rm -rf "$$dir/package"; \
mkdir -p "$$dir/package/"; \
if grep -qvE '^[[:space:]]*(#|$$)' "$$dir/requirements.txt" 2>/dev/null; then \
pip install -r "$$dir/requirements.txt" -t "$$dir/package/" --quiet 2>/dev/null; \
fi; \
cp $$dir/*.py "$$dir/package/" 2>/dev/null; \
for sub in "$$dir"*/; do \
subname=$$(basename "$$sub"); \
[ -d "$$sub" ] && [ "$$subname" != "package" ] && [ "$$subname" != "__pycache__" ] && cp -R "$${sub%/}" "$$dir/package/" 2>/dev/null; \
done; \
cp -R src/lambda/mcp/shared "$$dir/package/"; \
(cd "$$dir/package" && zip -r "../../$$tool.zip" . -x '*.pyc' '*/__pycache__/*' --quiet 2>/dev/null); \
rm -rf "$$dir/package"; \
echo "$$current_hash" > $(HASH_DIR)/$$tool.sha; \
echo " $$tool: done"; \
) & \
_pids="$$_pids $$!"; \
fi; \
done; \
for _pid in $$_pids; do \
wait $$_pid || _failed=1; \
done; \
if [ "$$_failed" = "1" ]; then echo "ERROR: Lambda packaging failed"; exit 1; fi
@echo "Lambda packaging complete."
@# Package frontend (browser-facing REST) Lambdas. Each subdir under
@# src/lambda/frontend/ is packaged independently. core-api has its own
@# assets (report_templates/); network-resilience imports the shared
@# network_resilience/ package copied in from src/lambda/mcp/network-resilience/.
@for dir in src/lambda/frontend/*/; do \
name=$$(basename "$$dir"); \
zip_path="src/lambda/frontend/$$name.zip"; \
hash_key="frontend-$$name"; \
current_hash=$$(find "$$dir" -type f \( -name '*.py' -o -name '*.txt' -o -name '*.json' -o -name '*.yaml' -o -name '*.md' \) -exec shasum {} + 2>/dev/null | sort | shasum | cut -d' ' -f1); \
if [ "$$name" = "network-resilience" ]; then \
extra_hash=$$(find src/lambda/mcp/network-resilience/network_resilience -type f \( -name '*.py' -o -name '*.json' \) -exec shasum {} + 2>/dev/null | sort | shasum | cut -d' ' -f1); \
current_hash=$$(printf '%s%s' "$$current_hash" "$$extra_hash" | shasum | cut -d' ' -f1); \
fi; \
stored_hash=$$(cat $(HASH_DIR)/$$hash_key.sha 2>/dev/null || echo ""); \
if [ "$$current_hash" = "$$stored_hash" ] && [ -f "$$zip_path" ]; then \
echo " frontend/$$name: unchanged, skipping"; \
else \
echo " Packaging frontend/$$name..."; \
rm -rf "$$dir/package" "$$zip_path"; \
mkdir -p "$$dir/package"; \
if [ -f "$$dir/requirements.txt" ] && grep -qvE '^[[:space:]]*(#|$$)' "$$dir/requirements.txt" 2>/dev/null; then \
pip install -r "$$dir/requirements.txt" -t "$$dir/package/" --quiet 2>/dev/null; \
fi; \
cp "$$dir"*.py "$$dir/package/" 2>/dev/null; \
for sub in "$$dir"*/; do \
subname=$$(basename "$$sub"); \
[ -d "$$sub" ] && [ "$$subname" != "package" ] && [ "$$subname" != "__pycache__" ] && cp -R "$${sub%/}" "$$dir/package/" 2>/dev/null; \
done; \
if [ "$$name" = "network-resilience" ]; then \
cp -R src/lambda/mcp/network-resilience/network_resilience "$$dir/package/"; \
fi; \
(cd "$$dir/package" && zip -r "../../../../../$$zip_path" . -x '*.pyc' '*/__pycache__/*' --quiet 2>/dev/null); \
rm -rf "$$dir/package"; \
echo "$$current_hash" > $(HASH_DIR)/$$hash_key.sha; \
echo " frontend/$$name: done"; \
fi; \
done
@# Package collector Lambdas (EventBridge → SQS → DynamoDB background jobs).
@# Terraform's `collector_zip_path` hashes each zip via `filebase64sha256`,
@# so an unchanged zip means Lambda keeps running old code — hence this must
@# land in `make package`, not be built by hand.
@#
@# `src/lambda/mcp/shared/` (cross-account helpers) is copied into each
@# collector zip exactly like it is for MCP tools above — one source of
@# truth for assume-role logic. Its hash folds into each collector's hash
@# so edits there force all collectors to rebuild.
@shared_hash=$$(find src/lambda/mcp/shared -type f \( -name '*.py' \) -exec shasum {} + 2>/dev/null | sort | shasum | cut -d' ' -f1); \
for dir in src/lambda/collectors/*/; do \
name=$$(basename "$$dir"); \
zip_path="$$dir$$name-collector.zip"; \
hash_key="collector-$$name"; \
src_hash=$$(find "$$dir" -type f \( -name '*.py' -o -name '*.txt' -o -name '*.json' \) ! -name "$$name-collector.zip" -exec shasum {} + 2>/dev/null | sort | shasum | cut -d' ' -f1); \
current_hash=$$(printf '%s%s' "$$src_hash" "$$shared_hash" | shasum | cut -d' ' -f1); \
stored_hash=$$(cat $(HASH_DIR)/$$hash_key.sha 2>/dev/null || echo ""); \
if [ "$$current_hash" = "$$stored_hash" ] && [ -f "$$zip_path" ]; then \
echo " collector/$$name: unchanged, skipping"; \
else \
echo " Packaging collector/$$name..."; \
rm -rf "$$dir/package" "$$zip_path"; \
mkdir -p "$$dir/package/"; \
if [ -f "$$dir/requirements.txt" ] && grep -qvE '^[[:space:]]*(#|$$)' "$$dir/requirements.txt" 2>/dev/null; then \
pip install -r "$$dir/requirements.txt" -t "$$dir/package/" --quiet 2>/dev/null; \
fi; \
cp "$$dir"*.py "$$dir/package/" 2>/dev/null; \
cp -R src/lambda/mcp/shared "$$dir/package/"; \
(cd "$$dir/package" && zip -r "../$$name-collector.zip" . -x '*.pyc' '*/__pycache__/*' --quiet 2>/dev/null); \
rm -rf "$$dir/package"; \
echo "$$current_hash" > $(HASH_DIR)/$$hash_key.sha; \
echo " collector/$$name: done"; \
fi; \
done
# ---------------------------------------------------------------------------
# Cleanup
# ---------------------------------------------------------------------------
clean: ## Remove build artifacts, caches, and Lambda packages
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
find . -type d -name .pytest_cache -exec rm -rf {} + 2>/dev/null || true
find . -name '*.pyc' -delete 2>/dev/null || true
rm -rf *.egg-info dist build
rm -f src/lambda/mcp/*.zip
rm -f src/lambda/frontend/*.zip
rm -f src/lambda/collectors/*/*-collector.zip
rm -rf src/lambda/mcp/*/package
rm -rf src/lambda/collectors/*/package
rm -rf src/lambda/frontend/*/package
rm -rf $(HASH_DIR)
rm -f src/agents/.hierarchy-*.json
rm -rf src/frontend/out src/frontend/.next src/frontend/node_modules
rm -f terraform/terraform.tfvars terraform/tfplan
@echo "Cleaned."