-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
389 lines (329 loc) · 21.3 KB
/
Makefile
File metadata and controls
389 lines (329 loc) · 21.3 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
TF_DIR := cloud
SECRETS_DIR := secrets
TF_PLAN_FILE := tfplan
TF_CONFIG_FILE := $(TF_DIR)/config.auto.tfvars
TF_CONFIG_TEMPLATE := $(TF_DIR)/config.auto.tfvars.example
TF_SECRETS_FILE := $(SECRETS_DIR)/secrets.sops.yaml
TF_SECRETS_TEMPLATE := $(SECRETS_DIR)/secrets.sops.yaml.example
TF_SECRETS_JSON := $(SECRETS_DIR)/.secrets.auto.tfvars.json
# Load local environment overrides (ignored by git).
-include .env
export
GCLOUD_LOGIN_FLAGS ?=
PROJECT_ID ?= intrepid-stage-489905-m7
SOPS_GCP_KMS ?=
SOPS_AGE_RECIPIENT ?=
SOPS_KMS_LOCATION ?= global
SOPS_KMS_KEYRING ?= sops
SOPS_KMS_KEY ?= infra-secrets
GCLOUD_CONFIG_DIR ?= .gcloud
ADMIN_GCLOUD_CONFIG_DIR ?= .gcloud-admin
GCP_REQUIRED_SERVICES := compute.googleapis.com container.googleapis.com cloudkms.googleapis.com artifactregistry.googleapis.com
KMS_GRANT_MEMBER ?=
KMS_GRANT_MEMBERS_CSV ?= robin.holzinger@berkeley.edu
GKE_CLUSTER_NAME ?= social-media-agent
GKE_CLUSTER_REGION ?= us-central1-a
GKE_NAMESPACE ?= default
GKE_DUMMY_AR_REGION ?= us-central1
GKE_DUMMY_AR_REPOSITORY ?= gke-workflows
GKE_DUMMY_IMAGE ?= $(GKE_DUMMY_AR_REGION)-docker.pkg.dev/$(PROJECT_ID)/$(GKE_DUMMY_AR_REPOSITORY)/gke-dummy-workflow:latest
GKE_DUMMY_DOCKERFILE ?= cloud/docker/gke-dummy.Dockerfile
GKE_DUMMY_DOCKER_PLATFORM ?= linux/amd64
GKE_DUMMY_MANIFEST_DIR ?= cloud/k8s/dummy-workflow
SENTIMENT_AR_REGION ?= us-central1
SENTIMENT_AR_REPOSITORY ?= gke-workflows
SENTIMENT_IMAGE ?= $(SENTIMENT_AR_REGION)-docker.pkg.dev/$(PROJECT_ID)/$(SENTIMENT_AR_REPOSITORY)/sentiment-agent:latest
SENTIMENT_DOCKERFILE ?= cloud/docker/sentiment-agent.Dockerfile
SENTIMENT_DOCKER_PLATFORM ?= linux/amd64
SENTIMENT_MANIFEST_DIR ?= cloud/k8s/sentiment-agent
SENTIMENT_TOPIC ?= latest hot topics in AI
OPENAI_API_KEY ?=
TAVILY_API_KEY ?=
GCLOUD_CONFIG_ABS := $(abspath $(GCLOUD_CONFIG_DIR))
ADMIN_GCLOUD_CONFIG_ABS := $(abspath $(ADMIN_GCLOUD_CONFIG_DIR))
ADC_FILE := $(GCLOUD_CONFIG_ABS)/application_default_credentials.json
TF := CLOUDSDK_CONFIG=$(GCLOUD_CONFIG_ABS) GOOGLE_APPLICATION_CREDENTIALS=$(ADC_FILE) pixi run terraform -chdir=$(TF_DIR)
SOPS := CLOUDSDK_CONFIG=$(GCLOUD_CONFIG_ABS) GOOGLE_APPLICATION_CREDENTIALS=$(ADC_FILE) pixi run sops
GCLOUD := CLOUDSDK_CONFIG=$(GCLOUD_CONFIG_ABS) pixi run gcloud
ADMIN_GCLOUD := CLOUDSDK_CONFIG=$(ADMIN_GCLOUD_CONFIG_ABS) pixi run gcloud
KUBECTL := CLOUDSDK_CONFIG=$(GCLOUD_CONFIG_ABS) pixi run kubectl
.PHONY: clean \
tf-init tf-fmt tf-validate tf-plan tf-apply tf-destroy tf-output \
tf-config-template tf-config-check \
tf-secrets-template tf-secrets-encrypt tf-secrets-encrypt-kms tf-secrets-edit tf-secrets-decrypt tf-secrets-materialize tf-secrets-clean \
gcp-auth gcp-project gcp-adc-quota gcp-enable-services gcp-kms-bootstrap gcp-init gcp-docker-auth gcp-artifact-registry-repo \
gcp-admin-auth gcp-admin-project gcp-admin-kms-create-keyring gcp-admin-kms-create-key gcp-admin-kms-grant-user gcp-admin-kms-setup \
gke-auth gke-namespace gke-dummy-build gke-dummy-push gke-dummy-run-once gke-dummy-schedule gke-dummy-delete gke-dummy-logs \
sentiment-build sentiment-push sentiment-run-once sentiment-schedule sentiment-delete sentiment-logs \
redis-deploy redis-delete sentiment-rbac \
logout
# ------------------------------------------------------------------------------------ #
# Terraform #
# ------------------------------------------------------------------------------------ #
tf-init:
$(TF) init
tf-fmt:
$(TF) fmt -recursive
tf-validate: tf-init
$(TF) validate
tf-plan: tf-validate tf-config-check gcp-init tf-secrets-materialize
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
$(TF) plan -var "project_id=$$PROJECT_ID_RESOLVED" -var-file=$(abspath $(TF_CONFIG_FILE)) -var-file=$(abspath $(TF_SECRETS_JSON)) -out=$(TF_PLAN_FILE)
tf-apply: tf-plan
echo "Applying Terraform plan from $(TF_PLAN_FILE)..."
echo "Starting a new GKE cluster can take several minutes. Please be patient and do not interrupt the process."
$(TF) apply $(TF_PLAN_FILE)
tf-destroy: tf-validate tf-config-check gcp-init tf-secrets-materialize
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
$(TF) destroy -var "project_id=$$PROJECT_ID_RESOLVED" -var-file=$(abspath $(TF_CONFIG_FILE)) -var-file=$(abspath $(TF_SECRETS_JSON))
tf-output:
$(TF) output
tf-config-check:
@test -f "$(TF_CONFIG_FILE)" || (echo "Missing $(TF_CONFIG_FILE). Run make tf-config-template first."; exit 1)
tf-secrets-encrypt:
@test -f "$(TF_SECRETS_FILE)" || (echo "Missing $(TF_SECRETS_FILE). Run make tf-secrets-template first."; exit 1)
@if [ -n "$(SOPS_GCP_KMS)" ]; then \
$(SOPS) --encrypt --gcp-kms "$(SOPS_GCP_KMS)" --in-place "$(TF_SECRETS_FILE)"; \
elif [ -n "$(SOPS_AGE_RECIPIENT)" ]; then \
$(SOPS) --encrypt --age "$(SOPS_AGE_RECIPIENT)" --in-place "$(TF_SECRETS_FILE)"; \
elif [ -f "$(SECRETS_DIR)/.sops.yaml" ]; then \
$(SOPS) --encrypt --in-place "$(TF_SECRETS_FILE)"; \
else \
echo "Set SOPS_GCP_KMS=projects/.../cryptoKeys/... or SOPS_AGE_RECIPIENT=age1..., or create $(SECRETS_DIR)/.sops.yaml"; \
exit 1; \
fi
tf-secrets-encrypt-kms: gcp-auth gcp-project
@test -f "$(TF_SECRETS_FILE)" || (echo "Missing $(TF_SECRETS_FILE). Run make tf-secrets-template first."; exit 1)
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
KMS_PATH="projects/$$PROJECT_ID_RESOLVED/locations/$(SOPS_KMS_LOCATION)/keyRings/$(SOPS_KMS_KEYRING)/cryptoKeys/$(SOPS_KMS_KEY)"; \
echo "Encrypting $(TF_SECRETS_FILE) with $$KMS_PATH"; \
$(SOPS) --encrypt --gcp-kms "$$KMS_PATH" --in-place "$(TF_SECRETS_FILE)" || ( \
echo ""; \
echo "KMS encryption failed. Run: make gcp-kms-bootstrap"; \
echo "Then retry: make tf-secrets-encrypt-kms"; \
exit 1; \
)
tf-secrets-edit:
@test -f "$(TF_SECRETS_FILE)" || (echo "Missing $(TF_SECRETS_FILE)."; exit 1)
$(SOPS) "$(TF_SECRETS_FILE)"
tf-secrets-decrypt:
@test -f "$(TF_SECRETS_FILE)" || (echo "Missing $(TF_SECRETS_FILE)."; exit 1)
@if grep -q '^[[:space:]]*sops:[[:space:]]*$$' "$(TF_SECRETS_FILE)"; then \
$(SOPS) --decrypt --output-type json "$(TF_SECRETS_FILE)"; \
else \
echo "{}"; \
fi
tf-secrets-materialize:
@test -f "$(TF_SECRETS_FILE)" || (echo "Missing $(TF_SECRETS_FILE)."; exit 1)
@if grep -q '^[[:space:]]*sops:[[:space:]]*$$' "$(TF_SECRETS_FILE)"; then \
$(SOPS) --decrypt --output-type json "$(TF_SECRETS_FILE)" > "$(TF_SECRETS_JSON)"; \
else \
echo "{}" > "$(TF_SECRETS_JSON)"; \
fi
tf-secrets-clean:
rm -f "$(TF_SECRETS_JSON)"
# ------------------------------------------------------------------------------------ #
# GCP (user context: .gcloud) #
# ------------------------------------------------------------------------------------ #
gcp-auth:
@$(GCLOUD) auth print-access-token >/dev/null 2>&1 || (echo "Running gcloud auth login..."; $(GCLOUD) auth login $(GCLOUD_LOGIN_FLAGS))
@$(GCLOUD) auth application-default print-access-token >/dev/null 2>&1 || (echo "Running gcloud auth application-default login..."; $(GCLOUD) auth application-default login $(GCLOUD_LOGIN_FLAGS))
gcp-project:
@CONFIG_PROJECT_ID=""; \
if [ -f "$(TF_CONFIG_FILE)" ]; then \
CONFIG_PROJECT_ID="$$(sed -n 's/^[[:space:]]*project_id[[:space:]]*=[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' "$(TF_CONFIG_FILE)" | head -n 1)"; \
fi; \
DEFAULT_PROJECT_ID="$(PROJECT_ID)"; \
CURRENT_PROJECT_ID="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
if [ "$$CURRENT_PROJECT_ID" = "(unset)" ]; then CURRENT_PROJECT_ID=""; fi; \
PROJECT_ID_RESOLVED="$$DEFAULT_PROJECT_ID"; \
if [ -z "$$PROJECT_ID_RESOLVED" ]; then PROJECT_ID_RESOLVED="$$CONFIG_PROJECT_ID"; fi; \
if [ -z "$$PROJECT_ID_RESOLVED" ]; then PROJECT_ID_RESOLVED="$$CURRENT_PROJECT_ID"; fi; \
if [ -z "$$PROJECT_ID_RESOLVED" ]; then \
echo "Set PROJECT_ID=... (for example: make PROJECT_ID=my-project tf-plan)."; \
exit 1; \
fi; \
$(GCLOUD) config set project "$$PROJECT_ID_RESOLVED"
gcp-adc-quota: gcp-project
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
$(GCLOUD) auth application-default set-quota-project "$$PROJECT_ID_RESOLVED"
gcp-enable-services: gcp-adc-quota
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
$(GCLOUD) services enable $(GCP_REQUIRED_SERVICES) --project "$$PROJECT_ID_RESOLVED"
gcp-kms-bootstrap: gcp-enable-services
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
ACCOUNT="$$( $(GCLOUD) config get-value account 2>/dev/null | tr -d '\n' )"; \
test -n "$$ACCOUNT" && [ "$$ACCOUNT" != "(unset)" ] || (echo "No active gcloud account configured."; exit 1); \
if echo "$$ACCOUNT" | grep -q 'gserviceaccount.com$$'; then MEMBER="serviceAccount:$$ACCOUNT"; else MEMBER="user:$$ACCOUNT"; fi; \
($(GCLOUD) kms keyrings describe "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" >/dev/null 2>&1 || $(GCLOUD) kms keyrings create "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)") && \
($(GCLOUD) kms keys describe "$(SOPS_KMS_KEY)" --keyring "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" >/dev/null 2>&1 || $(GCLOUD) kms keys create "$(SOPS_KMS_KEY)" --keyring "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" --purpose "encryption") && \
$(GCLOUD) kms keys add-iam-policy-binding "$(SOPS_KMS_KEY)" --keyring "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" --member "$$MEMBER" --role "roles/cloudkms.cryptoKeyEncrypterDecrypter" && \
echo "KMS key ready and IAM binding applied for $$MEMBER."
gcp-init: gcp-auth gcp-enable-services
@echo "GCP auth and project setup ready."
gcp-docker-auth: gcp-init
@REGISTRY_HOST="$$(echo "$(GKE_DUMMY_IMAGE)" | cut -d/ -f1)"; \
echo "Configuring Docker auth for $$REGISTRY_HOST"; \
$(GCLOUD) auth configure-docker "$$REGISTRY_HOST" --quiet
gcp-artifact-registry-repo: gcp-init
@PROJECT_ID_RESOLVED="$$( $(GCLOUD) config get-value project 2>/dev/null | tr -d '\n' )"; \
test -n "$$PROJECT_ID_RESOLVED" && [ "$$PROJECT_ID_RESOLVED" != "(unset)" ] || (echo "No gcloud project is configured."; exit 1); \
($(GCLOUD) artifacts repositories describe "$(GKE_DUMMY_AR_REPOSITORY)" --location "$(GKE_DUMMY_AR_REGION)" --project "$$PROJECT_ID_RESOLVED" >/dev/null 2>&1 || \
$(GCLOUD) artifacts repositories create "$(GKE_DUMMY_AR_REPOSITORY)" --repository-format=docker --location "$(GKE_DUMMY_AR_REGION)" --description "GKE workflow images" --project "$$PROJECT_ID_RESOLVED")
# ------------------------------------------------------------------------------------ #
# GKE Dummy Workflow #
# ------------------------------------------------------------------------------------ #
gke-auth: gcp-init
@PLUGIN_PATH="$$(command -v gke-gcloud-auth-plugin || true)"; \
if [ -n "$$PLUGIN_PATH" ]; then \
$(GCLOUD) container clusters get-credentials "$(GKE_CLUSTER_NAME)" --region "$(GKE_CLUSTER_REGION)" --project "$(PROJECT_ID)"; \
else \
echo "gke-gcloud-auth-plugin not found; configuring kubectl with short-lived access token."; \
ENDPOINT="$$( $(GCLOUD) container clusters describe "$(GKE_CLUSTER_NAME)" --region "$(GKE_CLUSTER_REGION)" --project "$(PROJECT_ID)" --format='value(endpoint)' )"; \
CA_CERT="$$( $(GCLOUD) container clusters describe "$(GKE_CLUSTER_NAME)" --region "$(GKE_CLUSTER_REGION)" --project "$(PROJECT_ID)" --format='value(masterAuth.clusterCaCertificate)' )"; \
test -n "$$ENDPOINT" || (echo "Failed to resolve cluster endpoint."; exit 1); \
test -n "$$CA_CERT" || (echo "Failed to resolve cluster CA certificate."; exit 1); \
CTX="gke_$(PROJECT_ID)_$(GKE_CLUSTER_REGION)_$(GKE_CLUSTER_NAME)"; \
USER_NAME="token-user-$(GKE_CLUSTER_NAME)"; \
TOKEN="$$( $(GCLOUD) auth print-access-token )"; \
CA_CERT_FILE="$$(mktemp)"; \
printf '%s' "$$CA_CERT" | base64 --decode > "$$CA_CERT_FILE"; \
$(KUBECTL) config set-cluster "$$CTX" --server="https://$$ENDPOINT" --certificate-authority="$$CA_CERT_FILE" --embed-certs=true >/dev/null; \
rm -f "$$CA_CERT_FILE"; \
$(KUBECTL) config set-credentials "$$USER_NAME" --token="$$TOKEN" >/dev/null; \
$(KUBECTL) config set-context "$$CTX" --cluster="$$CTX" --user="$$USER_NAME" >/dev/null; \
$(KUBECTL) config use-context "$$CTX" >/dev/null; \
fi
gke-namespace: gke-auth
@$(KUBECTL) get namespace "$(GKE_NAMESPACE)" >/dev/null 2>&1 || $(KUBECTL) create namespace "$(GKE_NAMESPACE)"
gke-dummy-build:
docker build --platform "$(GKE_DUMMY_DOCKER_PLATFORM)" -f "$(GKE_DUMMY_DOCKERFILE)" -t "$(GKE_DUMMY_IMAGE)" .
gke-dummy-push: gcp-docker-auth gcp-artifact-registry-repo
@REGISTRY_HOST="$$(echo "$(GKE_DUMMY_IMAGE)" | cut -d/ -f1)"; \
$(GCLOUD) auth print-access-token | docker login -u oauth2accesstoken --password-stdin "https://$$REGISTRY_HOST"
@CLOUDSDK_CONFIG=$(GCLOUD_CONFIG_ABS) docker push "$(GKE_DUMMY_IMAGE)"
gke-dummy-run-once: gke-namespace
@sed -e 's|__IMAGE__|$(GKE_DUMMY_IMAGE)|g' -e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' "$(GKE_DUMMY_MANIFEST_DIR)/job.yaml" | $(KUBECTL) apply -f -
gke-dummy-schedule: gke-namespace
@sed -e 's|__IMAGE__|$(GKE_DUMMY_IMAGE)|g' -e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' "$(GKE_DUMMY_MANIFEST_DIR)/cronjob.yaml" | $(KUBECTL) apply -f -
gke-dummy-delete: gke-auth
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete cronjob gke-dummy-workflow --ignore-not-found
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete job gke-dummy-workflow-once --ignore-not-found
gke-dummy-logs: gke-auth
@$(KUBECTL) -n "$(GKE_NAMESPACE)" logs -l app=gke-dummy-workflow --all-containers=true --tail=200 --prefix=true || ( \
echo "Logs are not available yet (pod likely still creating). Showing pod status/events:"; \
$(KUBECTL) -n "$(GKE_NAMESPACE)" get pods -l app=gke-dummy-workflow -o wide; \
POD="$$( $(KUBECTL) -n "$(GKE_NAMESPACE)" get pods -l app=gke-dummy-workflow -o jsonpath='{.items[0].metadata.name}' 2>/dev/null )"; \
if [ -n "$$POD" ]; then \
echo ""; \
$(KUBECTL) -n "$(GKE_NAMESPACE)" describe pod "$$POD" | sed -n '/Events:/,$$p'; \
fi; \
true \
)
# ------------------------------------------------------------------------------------ #
# Sentiment Analysis Agent #
# ------------------------------------------------------------------------------------ #
sentiment-build:
docker build --platform "$(SENTIMENT_DOCKER_PLATFORM)" -f "$(SENTIMENT_DOCKERFILE)" -t "$(SENTIMENT_IMAGE)" .
sentiment-push: gcp-docker-auth gcp-artifact-registry-repo
@REGISTRY_HOST="$$(echo "$(SENTIMENT_IMAGE)" | cut -d/ -f1)"; \
$(GCLOUD) auth print-access-token | docker login -u oauth2accesstoken --password-stdin "https://$$REGISTRY_HOST"
@CLOUDSDK_CONFIG=$(GCLOUD_CONFIG_ABS) docker push "$(SENTIMENT_IMAGE)"
redis-deploy: gke-namespace
@sed -e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' cloud/k8s/redis/deployment.yaml | $(KUBECTL) apply -f -
@sed -e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' cloud/k8s/redis/service.yaml | $(KUBECTL) apply -f -
@echo "Redis deployed. Waiting for pod to be ready..."
@$(KUBECTL) -n "$(GKE_NAMESPACE)" rollout status deployment/redis --timeout=60s
redis-delete: gke-auth
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete deployment redis --ignore-not-found
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete service redis-service --ignore-not-found
sentiment-rbac: gke-namespace
@sed -e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' "$(SENTIMENT_MANIFEST_DIR)/rbac.yaml" | $(KUBECTL) apply -f -
sentiment-run-once: gke-namespace sentiment-rbac
@test -n "$(OPENAI_API_KEY)" || (echo "Set OPENAI_API_KEY=... (e.g. make OPENAI_API_KEY=... sentiment-run-once)"; exit 1)
@test -n "$(TAVILY_API_KEY)" || (echo "Set TAVILY_API_KEY=... (e.g. make TAVILY_API_KEY=... sentiment-run-once)"; exit 1)
@sed -e 's|__IMAGE__|$(SENTIMENT_IMAGE)|g' \
-e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' \
-e 's|__OPENAI_API_KEY__|$(OPENAI_API_KEY)|g' \
-e 's|__TAVILY_API_KEY__|$(TAVILY_API_KEY)|g' \
-e 's|__SENTIMENT_TOPIC__|$(SENTIMENT_TOPIC)|g' \
"$(SENTIMENT_MANIFEST_DIR)/job.yaml" | $(KUBECTL) apply -f -
sentiment-schedule: gke-namespace sentiment-rbac
@test -n "$(OPENAI_API_KEY)" || (echo "Set OPENAI_API_KEY=... (e.g. make OPENAI_API_KEY=... sentiment-schedule)"; exit 1)
@test -n "$(TAVILY_API_KEY)" || (echo "Set TAVILY_API_KEY=... (e.g. make TAVILY_API_KEY=... sentiment-schedule)"; exit 1)
@sed -e 's|__IMAGE__|$(SENTIMENT_IMAGE)|g' \
-e 's|__NAMESPACE__|$(GKE_NAMESPACE)|g' \
-e 's|__OPENAI_API_KEY__|$(OPENAI_API_KEY)|g' \
-e 's|__TAVILY_API_KEY__|$(TAVILY_API_KEY)|g' \
-e 's|__SENTIMENT_TOPIC__|$(SENTIMENT_TOPIC)|g' \
"$(SENTIMENT_MANIFEST_DIR)/cronjob.yaml" | $(KUBECTL) apply -f -
sentiment-delete: gke-auth
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete cronjob sentiment-agent --ignore-not-found
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete job sentiment-agent-once --ignore-not-found
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete jobs -l role=worker --ignore-not-found
@$(KUBECTL) -n "$(GKE_NAMESPACE)" delete jobs -l role=summarizer --ignore-not-found
sentiment-logs: gke-auth
@$(KUBECTL) -n "$(GKE_NAMESPACE)" logs -l app=sentiment-agent --all-containers=true --tail=200 --prefix=true || ( \
echo "Logs are not available yet (pod likely still creating). Showing pod status/events:"; \
$(KUBECTL) -n "$(GKE_NAMESPACE)" get pods -l app=sentiment-agent -o wide; \
POD="$$( $(KUBECTL) -n "$(GKE_NAMESPACE)" get pods -l app=sentiment-agent -o jsonpath='{.items[0].metadata.name}' 2>/dev/null )"; \
if [ -n "$$POD" ]; then \
echo ""; \
$(KUBECTL) -n "$(GKE_NAMESPACE)" describe pod "$$POD" | sed -n '/Events:/,$$p'; \
fi; \
true \
)
# ------------------------------------------------------------------------------------ #
# Session #
# ------------------------------------------------------------------------------------ #
logout:
@$(GCLOUD) auth revoke --all --quiet || true
@$(GCLOUD) auth application-default revoke --quiet || true
@echo "Logged out from gcloud user and ADC credentials in $(GCLOUD_CONFIG_DIR)."
# ------------------------------------------------------------------------------------ #
# GCP KMS Admin Helpers (admin context: .gcloud-admin) #
# ------------------------------------------------------------------------------------ #
gcp-admin-auth:
@$(ADMIN_GCLOUD) auth print-access-token >/dev/null 2>&1 || (echo "Running admin gcloud auth login..."; $(ADMIN_GCLOUD) auth login $(GCLOUD_LOGIN_FLAGS))
gcp-admin-project: gcp-admin-auth
@$(ADMIN_GCLOUD) config set project "$(PROJECT_ID)"
gcp-admin-kms-create-keyring: gcp-admin-project
@($(ADMIN_GCLOUD) kms keyrings describe "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" >/dev/null 2>&1 || $(ADMIN_GCLOUD) kms keyrings create "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)")
gcp-admin-kms-create-key: gcp-admin-kms-create-keyring
@($(ADMIN_GCLOUD) kms keys describe "$(SOPS_KMS_KEY)" --keyring "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" >/dev/null 2>&1 || $(ADMIN_GCLOUD) kms keys create "$(SOPS_KMS_KEY)" --keyring "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" --purpose "encryption")
gcp-admin-kms-grant-user: gcp-admin-kms-create-key
@MEMBER_LIST="$(KMS_GRANT_MEMBER)"; \
if [ -z "$$MEMBER_LIST" ]; then MEMBER_LIST="$(KMS_GRANT_MEMBERS_CSV)"; fi; \
test -n "$$MEMBER_LIST" || (echo "Set KMS_GRANT_MEMBER or KMS_GRANT_MEMBERS_CSV."; exit 1); \
for RAW_MEMBER in $$(printf '%s' "$$MEMBER_LIST" | tr ',' ' '); do \
MEMBER="$$(echo "$$RAW_MEMBER" | tr -d '[:space:]')"; \
test -n "$$MEMBER" || continue; \
if echo "$$MEMBER" | grep -qE '^(user:|serviceAccount:|group:|domain:)'; then \
MEMBER_ARG="$$MEMBER"; \
elif echo "$$MEMBER" | grep -q 'gserviceaccount.com$$'; then \
MEMBER_ARG="serviceAccount:$$MEMBER"; \
else \
MEMBER_ARG="user:$$MEMBER"; \
fi; \
echo "Granting roles/cloudkms.cryptoKeyEncrypterDecrypter to $$MEMBER_ARG"; \
$(ADMIN_GCLOUD) kms keys add-iam-policy-binding "$(SOPS_KMS_KEY)" --keyring "$(SOPS_KMS_KEYRING)" --location "$(SOPS_KMS_LOCATION)" --member "$$MEMBER_ARG" --role "roles/cloudkms.cryptoKeyEncrypterDecrypter"; \
done
gcp-admin-kms-setup: gcp-admin-kms-grant-user
@MEMBER_LIST="$(KMS_GRANT_MEMBER)"; \
if [ -z "$$MEMBER_LIST" ]; then MEMBER_LIST="$(KMS_GRANT_MEMBERS_CSV)"; fi; \
echo "Admin KMS setup complete for: $$MEMBER_LIST"
# ------------------------------------------------------------------------------------ #
# Cleanup #
# ------------------------------------------------------------------------------------ #
clean: tf-secrets-clean
rm -rf "$(TF_DIR)/.terraform" \
"$(TF_DIR)/.terraform.lock.hcl" \
"$(TF_DIR)/terraform.tfstate" \
"$(TF_DIR)/terraform.tfstate.backup" \
"$(TF_DIR)/$(TF_PLAN_FILE)"