-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
457 lines (393 loc) · 19.9 KB
/
Copy pathMakefile
File metadata and controls
457 lines (393 loc) · 19.9 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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
.PHONY: local local-db local-down dev dev-api dev-dashboard down net test require-services check line-count check-decisions diff-stats allocate-decision fmt clippy migrate new-migration schema sqlx-prepare check-sqlx mock-target install-hooks \
tofu-init tofu-fmt tofu-validate tofu-plan tofu-apply tofu-destroy \
infra-shutdown infra-resume worktree-clean \
dashboard-static web-build web build install \
logs logs-deploy \
shortener-dev shortener-down shortener-deploy \
deploy db-shell \
e2e e2e-up e2e-down mail-up mail-down \
service-icons check-service-icons
COMPOSE := $(shell command -v podman-compose 2>/dev/null || command -v docker-compose 2>/dev/null || echo "docker compose")
ENGINE := $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null || echo docker)
# Cross-project network bridging Overfolder ↔ Overslash (see docker/docker-compose.dev.yml).
SHARED_NET := overfolder-shared
TOFU := $(shell command -v tofu 2>/dev/null || command -v terraform 2>/dev/null)
TOFU_DIR := infra
ENV ?= dev
TF_VAR_FILE := $(TOFU_DIR)/env/$(ENV).tfvars
# Load .env.local overrides if present (worktree isolation).
# Used by non-compose targets like `test` and `migrate` that read DATABASE_URL.
# Compose targets re-source .env.local inline below to handle the first-run case
# where the file is created by bin/worktree-env.sh just before being read.
-include .env.local
export
# Install prefix (default: ~/.local). Override: PREFIX=/usr/local make install
PREFIX ?= $(HOME)/.local
# Colors
GREEN := \033[0;32m
RED := \033[0;31m
YELLOW := \033[0;33m
NC := \033[0m
# Shell snippet: run worktree-env.sh, source .env.local (if created), then
# build PROJ_FLAG. In worktrees, this becomes `--project-name overslash-wt-XXX`,
# which overrides `name: overslash` in docker-compose.dev.yml (podman-compose
# 1.0.6 does NOT honor COMPOSE_PROJECT_NAME env var when `name:` is set in the
# file, so we must pass the flag explicitly). In the main repo, .env.local is
# not created and PROJ_FLAG is empty, so compose uses `name: overslash`.
WT_ENV = bash bin/worktree-env.sh && set -a && { [ -f .env.local ] && . ./.env.local; }; set +a; \
PROJ_FLAG=$${COMPOSE_PROJECT_NAME:+--project-name $$COMPOSE_PROJECT_NAME}
# Ensure the cross-project podman network exists (idempotent). The Overfolder
# backend attaches to this same network to reach the API by the `overslash`
# alias — see docker/docker-compose.dev.yml.
net:
@$(ENGINE) network inspect $(SHARED_NET) >/dev/null 2>&1 || { \
echo -e "$(GREEN)Creating shared network $(SHARED_NET)...$(NC)"; \
$(ENGINE) network create $(SHARED_NET); }
# Start the full local dev stack (postgres + api with cargo-watch + dashboard).
# Creates the shared network first so Overfolder can reach this API. `dev` is
# kept as an alias of `local`.
local dev: net
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down --remove-orphans 2>/dev/null; \
$(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up --build postgres api dashboard
# Stop the full local dev stack (alias of `down`).
local-down: down
# Start the local backing services the test suite needs (postgres + valkey) —
# used by e2e-up.sh and worktree isolation. Mirrors the `services:` block of
# the CI test job: `cargo test --workspace` needs both, since `oversla-sh`'s
# integration tests panic when VALKEY_URL is unreachable. In a worktree the
# ports (and VALKEY_URL / REDIS_URL) come from .env.local; in the main repo
# they are the compose defaults, 55432 and 6380.
local-db:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up -d postgres valkey
# Start the e2e mail stack (GreenMail IMAP/SMTP + the overfwd gateway) — used
# by e2e-up.sh so `services/email.yaml` has a real mailbox to talk to.
#
# Started explicitly by name rather than via the compose `profiles:` key:
# podman-compose 1.0.6 has no `--profile` flag. `local` and `dev-api` likewise
# name their services, so a bare `make local` never starts these.
mail-up:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up -d greenmail overfwd
mail-down:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml stop greenmail overfwd
# Start only the API (postgres + api)
dev-api:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down --remove-orphans 2>/dev/null; \
$(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml up --build postgres api
# Start only the dashboard dev server (no container)
dev-dashboard:
cd dashboard && npm run dev
# Build the SvelteKit dashboard with adapter-static. Output: dashboard/build/.
# Required before `make web-build` so rust-embed has assets to embed.
dashboard-static:
cd dashboard && npm install && npm run build:static
# Build the self-hosted single-binary release with embedded dashboard and MCP.
# Produces target/release/overslash. Run `overslash web` to start it.
build: dashboard-static
SQLX_OFFLINE=1 cargo build --release -p overslash-cli --features embed-dashboard,sql_policy
# Alias kept for backward compatibility.
web-build: dashboard-static
SQLX_OFFLINE=1 cargo build --release -p overslash-cli --features embed-dashboard,sql_policy
# Install overslash to $(PREFIX)/bin (default: ~/.local/bin).
# Override: PREFIX=/usr/local make install
install: build
install -d $(PREFIX)/bin
install -m 755 target/release/overslash $(PREFIX)/bin/overslash
@echo -e "$(GREEN)Installed:$(NC) $(PREFIX)/bin/overslash"
@echo "Make sure $(PREFIX)/bin is in your PATH, then run: overslash web"
# Build + run the self-hosted binary directly (foreground).
web: build
./target/release/overslash web
# Stop services
down:
@$(WT_ENV); $(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down --remove-orphans
# Bring up the full e2e stack: Postgres → overslash-fakes → API → dashboard
# preview, all on dynamic ports written into a per-worktree .e2e/ state dir.
# Multiple worktrees can run concurrently without colliding.
e2e-up:
@bash scripts/e2e-up.sh
# Tear down the e2e stack started by `make e2e-up`. Postgres is left running;
# use `make worktree-clean` for a full teardown.
e2e-down:
@bash scripts/e2e-down.sh
# Boot the e2e stack, run Playwright, then tear down regardless of result.
# The env file the harness writes (API_URL, DASHBOARD_URL, ...) is sourced
# into the playwright invocation so worker subprocesses see them — setting
# them only inside playwright.config.ts isn't enough.
e2e:
@bash scripts/e2e-up.sh
@STATE_DIR="$${WORKTREE_STATE_DIR:-$$(pwd)}/.e2e"; \
status=0; ( cd dashboard && set -a && . "$$STATE_DIR/dashboard.env" && set +a && npx playwright test ) || status=$$?; \
bash scripts/e2e-down.sh; \
exit $$status
# Bring up the local Metabase stack (docker/metabase/, UI on :3033), mint an
# API key, and seed + register the Pagila sample database. Idempotent.
# Credentials + the Pagila database id land in docker/metabase/.env.metabase.
metabase-up:
@bash docker/metabase/bootstrap.sh
@bash docker/metabase/seed-pagila.sh
# Tear down the Metabase stack (volumes preserved; add -v manually to wipe).
metabase-down:
@cd docker/metabase && (podman-compose -p overslash-metabase -f docker-compose.yml down 2>/dev/null || docker compose -p overslash-metabase -f docker-compose.yml down)
# Run the gated real-Metabase e2e suite (ignored tests) against the local
# stack — boots/seeds it first. Needs Postgres for the API under test too
# (make local-db). The suite compiles with the sql_policy parser.
metabase-e2e: metabase-up
@set -a && . docker/metabase/.env.metabase && set +a && \
cargo test -p overslash-api --features sql_policy --test api metabase -- --ignored --test-threads=1
# Start the oversla.sh shortener dev stack (valkey + shortener on :8081).
# The crate's integration tests do NOT need this — they talk to the Valkey
# `make local-db` starts (VALKEY_URL). This stack runs the service itself.
shortener-dev:
$(COMPOSE) -f docker/docker-compose.shortener.yml up --build
# Stop the shortener dev stack
shortener-down:
$(COMPOSE) -f docker/docker-compose.shortener.yml down --remove-orphans
# Build, push, and deploy the oversla.sh shortener from local.
# Mirrors the cloud-build-shortener pipeline: builds crates/oversla-sh/Dockerfile,
# pushes :$(SHA) + :latest to Artifact Registry, then `gcloud run deploy`s.
# Usage:
# make shortener-deploy ENV=prod # prod with current HEAD sha
# make shortener-deploy ENV=prod SHA=v0.1.0 # override tag
# DEPLOY_AUTO_APPROVE=1 make shortener-deploy ENV=prod # skip prod confirm
shortener-deploy:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found. Use ENV=dev or ENV=prod.$(NC)" && exit 1)
@command -v gcloud >/dev/null || (echo -e "$(RED)gcloud CLI not found.$(NC)" && exit 1)
@command -v docker >/dev/null || (echo -e "$(RED)docker CLI not found.$(NC)" && exit 1)
@test -n "$(GCP_PROJECT)" || (echo -e "$(RED)Could not read project_id from $(TF_VAR_FILE)$(NC)" && exit 1)
$(eval BASE_PREFIX := overslash-$(ENV))
$(eval REPO := $(BASE_PREFIX)-registry)
$(eval SERVICE := $(BASE_PREFIX)-shortener)
$(eval AR_HOST := $(REGION)-docker.pkg.dev)
$(eval IMAGE := $(AR_HOST)/$(GCP_PROJECT)/$(REPO)/oversla-sh)
$(eval SHA ?= $(shell git rev-parse --short HEAD))
@if [ "$(ENV)" = "prod" ] && [ "$(DEPLOY_AUTO_APPROVE)" != "1" ]; then \
echo -e "$(RED)About to deploy $(SERVICE) ($(IMAGE):$(SHA)) to PRODUCTION ($(GCP_PROJECT))$(NC)"; \
echo -n "Type 'prod' to confirm: "; \
read confirm && [ "$$confirm" = "prod" ] || (echo "Aborted." && exit 1); \
fi
@echo -e "$(GREEN)[1/3] docker build -> $(IMAGE):$(SHA)$(NC)"
docker build \
-f crates/oversla-sh/Dockerfile \
-t $(IMAGE):$(SHA) \
-t $(IMAGE):latest \
.
@echo -e "$(GREEN)[2/3] docker push (tags: $(SHA), latest)$(NC)"
docker push $(IMAGE):$(SHA)
docker push $(IMAGE):latest
@echo -e "$(GREEN)[3/3] gcloud run deploy $(SERVICE) --image $(IMAGE):$(SHA)$(NC)"
gcloud run deploy $(SERVICE) \
--image $(IMAGE):$(SHA) \
--region $(REGION) \
--project $(GCP_PROJECT) \
--quiet
@echo -e "$(GREEN)Deployed $(SERVICE) at $(IMAGE):$(SHA)$(NC)"
# Build, push, and deploy a Cloud Run service or Job from local — bypasses
# Cloud Build entirely. Useful when CB is degraded (incidents, weekends) or
# when you want to skip the GitHub-trigger round-trip. Wakes Cloud SQL first
# so it works after the night scheduler has paused the dev instance.
#
# Usage:
# make deploy SVC=api # api → dev
# make deploy SVC=metrics-exporter ENV=dev # exporter Cloud Run Job
# make deploy SVC=shortener ENV=prod # prod (asks for confirmation)
# make deploy SVC=api SHA=v1.2.3 # override image tag
# DEPLOY_AUTO_APPROVE=1 make deploy SVC=api ENV=prod
# CONTAINER_RUNTIME=podman make deploy SVC=api
deploy:
ifndef SVC
@echo -e "$(RED)SVC is required. Usage: make deploy SVC=api|metrics-exporter|shortener [ENV=dev|prod]$(NC)"
@exit 1
endif
@bin/deploy-cloudrun.sh $(SVC) $(ENV)
# Open a psql shell against the Cloud SQL instance via the Auth Proxy.
# No public IP whitelisting needed — the proxy authenticates as your gcloud
# identity. Wakes the instance first if the night scheduler has paused it.
#
# Usage:
# make db-shell # dev
# make db-shell ENV=prod # prod (asks for confirmation)
# READONLY=1 make db-shell ENV=prod # read-only session
db-shell:
@bin/db-shell.sh $(ENV)
# Remove worktree containers and volumes
worktree-clean:
@$(WT_ENV); \
if [ -n "$${COMPOSE_PROJECT_NAME:-}" ]; then \
$(COMPOSE) $$PROJ_FLAG -f docker/docker-compose.dev.yml down -v; \
else \
echo "Not in a worktree — nothing to clean."; \
fi
# Run all tests
# Needs the backing services up (`make local-db`): Postgres for the API crates,
# Valkey for oversla-sh. In a worktree, `make` re-exports .env.local, so
# DATABASE_URL / VALKEY_URL / REDIS_URL already point at this worktree's ports.
# nextest when it's installed — it is what CI runs, it gives each test its own
# process (a handful of env-var tests leak into each other under plain
# `cargo test`), and its default concurrency is tuned per machine. The fallback
# caps threads at 4, the number CLAUDE.md recommends for a plain cargo run.
test: require-services
@if command -v cargo-nextest >/dev/null 2>&1; then \
cargo nextest run --workspace; \
else \
cargo test --workspace -- --test-threads=4; \
fi
# Fail with a usable hint instead of a wall of connection errors when the
# backing services aren't running.
require-services:
@bash bin/worktree-env.sh >/dev/null
@bash scripts/check-test-services.sh
# CI check: line counts + decision numbering + fmt + clippy + test
check: line-count check-decisions require-services
cargo fmt --check
cargo clippy --workspace -- -D warnings
@$(MAKE) --no-print-directory test
# Every .rs under crates/*/src must stay under 1000 lines (mirrors CI).
line-count:
bash scripts/check-line-counts.sh
# Decision numbers are unique, contiguous, and allocated at merge (mirrors CI).
check-decisions:
bash scripts/check-decisions.sh
# How much production code this branch actually changes, the way CI reports it
# on the PR. A report, not a gate — deliberately absent from `check`, and from
# the pre-commit hook. Needs an up-to-date origin/dev (worktrees fetch master
# only by default): git fetch origin dev:refs/remotes/origin/dev
diff-stats:
./scripts/pr-diff-stats.py origin/dev HEAD
# Stamp the real number onto a decision authored as a placeholder. Normally the
# allocate-decision workflow does this on dev; run it by hand when that fails.
allocate-decision:
bash scripts/allocate-decision.sh
# Format
fmt:
cargo fmt
# Lint
clippy:
cargo clippy --workspace -- -D warnings
# Run migrations
migrate:
cd crates/overslash-db && cargo sqlx migrate run
# Create new migration
new-migration:
@read -p "Migration name: " name; \
cd crates/overslash-db && cargo sqlx migrate add -r "$$name"
# Regenerate SCHEMA.sql
schema:
pg_dump --schema-only --no-owner --no-acl --schema=public --exclude-table=_sqlx_migrations "$${DATABASE_URL}" > SCHEMA.sql
# Regenerate the built-in service icons from assets/service-icons/manifest.json
service-icons:
cd dashboard && npm ci --include=dev
node scripts/gen-service-icons.mjs
# Verify the committed icon set matches the manifest
check-service-icons:
@node scripts/gen-service-icons.mjs >/dev/null
@git diff --quiet --exit-code assets/service-icons || \
{ echo "assets/service-icons is stale — run 'make service-icons'"; exit 1; }
# Regenerate sqlx offline caches
sqlx-prepare:
cargo sqlx prepare --workspace -- --tests
# Verify sqlx offline cache is up-to-date
check-sqlx:
cargo sqlx prepare --workspace --check -- --tests
# Start mock target
mock-target:
cargo run -p mock-target
# Install git hooks
install-hooks:
git config core.hooksPath .githooks
@echo "Git hooks installed."
# ---------------------------------------------------------------------------
# Infrastructure (OpenTofu / Terraform)
# Usage: make tofu-plan ENV=dev (default)
# make tofu-plan ENV=prod
# ---------------------------------------------------------------------------
tofu-init:
@echo -e "$(GREEN)Initializing tofu ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) init
tofu-fmt:
@echo -e "$(GREEN)Checking tofu formatting...$(NC)"
cd $(TOFU_DIR) && $(TOFU) fmt -check -recursive
tofu-validate: tofu-init
@echo -e "$(GREEN)Validating tofu configuration...$(NC)"
cd $(TOFU_DIR) && $(TOFU) validate
tofu-plan:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found. Use ENV=dev or ENV=prod.$(NC)" && exit 1)
@echo -e "$(GREEN)Running tofu plan ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) workspace select -or-create $(ENV) && $(TOFU) plan -var-file=env/$(ENV).tfvars -out=$(ENV).tfplan
tofu-apply:
@test -f $(TOFU_DIR)/$(ENV).tfplan || (echo -e "$(RED)No plan file found. Run 'make tofu-plan ENV=$(ENV)' first.$(NC)" && exit 1)
@if [ "$(ENV)" = "prod" ] && [ "$(TF_AUTO_APPROVE)" != "1" ]; then \
echo -e "$(RED)You are about to apply to PRODUCTION (project: overslash)$(NC)"; \
echo -n "Type 'prod' to confirm: "; \
read confirm && [ "$$confirm" = "prod" ] || (echo "Aborted." && exit 1); \
fi
@echo -e "$(GREEN)Applying tofu plan ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) workspace select $(ENV) && $(TOFU) apply $(ENV).tfplan
tofu-destroy:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found. Use ENV=dev or ENV=prod.$(NC)" && exit 1)
@if [ "$(ENV)" = "prod" ]; then \
echo -e "$(RED)You are about to DESTROY production (project: overslash)$(NC)"; \
echo -n "Type 'destroy prod' to confirm: "; \
read confirm && [ "$$confirm" = "destroy prod" ] || (echo "Aborted." && exit 1); \
fi
@echo -e "$(GREEN)Destroying tofu resources ($(ENV))...$(NC)"
cd $(TOFU_DIR) && $(TOFU) workspace select $(ENV) && $(TOFU) destroy -var-file=env/$(ENV).tfvars
# ---------------------------------------------------------------------------
# Infra scheduler — manual shutdown / resume
# Usage: make infra-shutdown ENV=prod
# make infra-resume ENV=prod
# ---------------------------------------------------------------------------
GCP_PROJECT = $(shell grep '^project_id' $(TF_VAR_FILE) 2>/dev/null | sed 's/.*= *"\(.*\)"/\1/')
SQL_INSTANCE = overslash-$(ENV)-db
infra-shutdown:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found.$(NC)" && exit 1)
@echo -e "$(GREEN)Shutting down infra ($(ENV), project: $(GCP_PROJECT))...$(NC)"
gcloud sql instances patch $(SQL_INSTANCE) --activation-policy=NEVER --project=$(GCP_PROJECT) --quiet
@echo -e "$(GREEN)Cloud SQL stopped.$(NC)"
infra-resume:
@test -f $(TF_VAR_FILE) || (echo -e "$(RED)Var file $(TF_VAR_FILE) not found.$(NC)" && exit 1)
@echo -e "$(GREEN)Resuming infra ($(ENV), project: $(GCP_PROJECT))...$(NC)"
gcloud sql instances patch $(SQL_INSTANCE) --activation-policy=ALWAYS --project=$(GCP_PROJECT) --quiet
@echo -e "$(GREEN)Cloud SQL started.$(NC)"
# ---------------------------------------------------------------------------
# Cloud Run logs
# Usage: make logs (tail api)
# make logs ENV=prod (tail prod api)
# make logs SVC=api SINCE=30m (last 30m of history, then tail)
# make logs SVC=api,worker (multiple services, comma-separated)
# ---------------------------------------------------------------------------
REGION ?= europe-west1
SVC ?= api
logs:
@SVC_FILTER=$$(echo "$(SVC)" | sed 's/[^,]\+/resource.labels.service_name="overslash-$(ENV)-&"/g; s/,/ OR /g'); \
FILTER="resource.type=\"cloud_run_revision\" AND ($$SVC_FILTER) AND logName:\"stdout\""; \
STRIP_ANSI='s/\x1B\[[0-9;]*[A-Za-z]//g'; \
FMT='value(timestamp.date("%Y-%m-%d %H:%M:%S"), severity, resource.labels.service_name, jsonPayload.level, jsonPayload.target, jsonPayload.span.name, jsonPayload.message, jsonPayload.fields, textPayload)'; \
if [ -n "$(SINCE)" ]; then \
echo -e "$(GREEN)Reading Cloud Run logs: $(SVC) ($(ENV)) — last $(SINCE), then tailing$(NC)"; \
( set -x; \
gcloud logging read "$$FILTER" \
--project=$(GCP_PROJECT) --freshness=$(SINCE) --limit=10000 \
--format="$$FMT" \
) | tac | sed -E "$$STRIP_ANSI"; \
fi; \
echo -e "$(GREEN)Tailing Cloud Run logs: $(SVC) ($(ENV))$(NC)"; \
set -x; \
gcloud beta logging tail "$$FILTER" \
--project=$(GCP_PROJECT) --buffer-window=3s \
--format="$$FMT" \
| sed -uE "$$STRIP_ANSI"
# View Cloud Build deploy logs (last build per service)
# Usage: make logs-deploy (api only)
# make logs-deploy SVC=api,worker (multiple services)
logs-deploy:
@for svc in $$(echo "$(SVC)" | tr ',' ' '); do \
echo -e "$(GREEN)Latest deploy log: overslash-$(ENV)-$$svc$(NC)"; \
BUILD_ID=$$(gcloud builds list --project=$(GCP_PROJECT) --region=$(REGION) \
--filter="substitutions._SERVICE_NAME=overslash-$(ENV)-$$svc" \
--sort-by=~createTime --limit=1 --format="value(id)"); \
if [ -n "$$BUILD_ID" ]; then \
gcloud builds log "$$BUILD_ID" --project=$(GCP_PROJECT) --region=$(REGION); \
else \
echo -e "$(YELLOW)No builds found for overslash-$(ENV)-$$svc$(NC)"; \
fi; \
done