agent-starter-pack 0.9.2__py3-none-any.whl → 0.10.1__py3-none-any.whl
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.
- {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.1.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.1.dist-info}/RECORD +54 -52
- agents/adk_base/.template/templateconfig.yaml +1 -1
- agents/adk_gemini_fullstack/.template/templateconfig.yaml +2 -1
- agents/agentic_rag/.template/templateconfig.yaml +1 -1
- agents/agentic_rag/README.md +1 -1
- agents/live_api/tests/integration/test_server_e2e.py +7 -1
- agents/live_api/tests/unit/test_server.py +2 -1
- llm.txt +3 -2
- src/base_template/Makefile +4 -3
- src/base_template/README.md +7 -2
- src/base_template/deployment/README.md +6 -121
- src/base_template/deployment/terraform/github.tf +284 -0
- src/base_template/deployment/terraform/providers.tf +5 -0
- src/base_template/deployment/terraform/variables.tf +40 -1
- src/base_template/deployment/terraform/vars/env.tfvars +7 -1
- src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +43 -0
- src/base_template/deployment/terraform/{build_triggers.tf → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} } +33 -18
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +114 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +65 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +170 -0
- src/base_template/{deployment/cd/deploy-to-prod.yaml → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml } +7 -7
- src/base_template/{deployment/cd/staging.yaml → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml } +7 -7
- src/cli/commands/create.py +149 -4
- src/cli/commands/list.py +1 -1
- src/cli/commands/setup_cicd.py +293 -299
- src/cli/utils/cicd.py +19 -7
- src/cli/utils/remote_template.py +4 -4
- src/cli/utils/template.py +67 -19
- src/deployment_targets/cloud_run/app/server.py +19 -0
- src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -3
- src/deployment_targets/cloud_run/deployment/terraform/service.tf +4 -3
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +35 -0
- src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
- src/frontends/live_api_react/frontend/package-lock.json +19 -16
- src/frontends/streamlit/frontend/side_bar.py +1 -1
- src/frontends/streamlit/frontend/utils/chat_utils.py +1 -1
- src/frontends/streamlit/frontend/utils/local_chat_history.py +2 -2
- src/resources/docs/adk-cheatsheet.md +1 -1
- src/resources/locks/uv-adk_base-agent_engine.lock +164 -131
- src/resources/locks/uv-adk_base-cloud_run.lock +177 -144
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +164 -131
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +177 -144
- src/resources/locks/uv-agentic_rag-agent_engine.lock +223 -190
- src/resources/locks/uv-agentic_rag-cloud_run.lock +251 -218
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +315 -485
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +358 -531
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +281 -249
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +323 -290
- src/resources/locks/uv-live_api-cloud_run.lock +350 -327
- src/resources/setup_cicd/cicd_variables.tf +0 -41
- src/resources/setup_cicd/github.tf +0 -87
- src/resources/setup_cicd/providers.tf +0 -39
- {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.1.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.1.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.9.2.dist-info → agent_starter_pack-0.10.1.dist-info}/licenses/LICENSE +0 -0
- /src/base_template/{deployment/ci → {% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}}/pr_checks.yaml +0 -0
src/cli/commands/setup_cicd.py
CHANGED
@@ -14,7 +14,6 @@
|
|
14
14
|
|
15
15
|
import logging
|
16
16
|
import re
|
17
|
-
import shutil
|
18
17
|
import subprocess
|
19
18
|
import sys
|
20
19
|
import tempfile
|
@@ -26,14 +25,10 @@ import click
|
|
26
25
|
from rich.console import Console
|
27
26
|
|
28
27
|
from src.cli.utils.cicd import (
|
29
|
-
E2EDeployment,
|
30
28
|
ProjectConfig,
|
31
29
|
create_github_connection,
|
32
|
-
create_github_repository,
|
33
|
-
ensure_apis_enabled,
|
34
30
|
handle_github_authentication,
|
35
31
|
is_github_authenticated,
|
36
|
-
print_cicd_summary,
|
37
32
|
run_command,
|
38
33
|
)
|
39
34
|
|
@@ -86,6 +81,66 @@ def check_gh_cli_installed() -> bool:
|
|
86
81
|
return False
|
87
82
|
|
88
83
|
|
84
|
+
def check_github_scopes(cicd_runner: str) -> None:
|
85
|
+
"""Check if GitHub CLI has required scopes for the CI/CD runner.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
cicd_runner: Either 'github_actions' or 'google_cloud_build'
|
89
|
+
|
90
|
+
Raises:
|
91
|
+
click.ClickException: If required scopes are missing
|
92
|
+
"""
|
93
|
+
try:
|
94
|
+
# Get scopes from gh auth status
|
95
|
+
result = run_command(["gh", "auth", "status"], capture_output=True, check=True)
|
96
|
+
|
97
|
+
# Parse scopes from the output
|
98
|
+
scopes = []
|
99
|
+
for line in result.stdout.split("\n"):
|
100
|
+
if "Token scopes:" in line:
|
101
|
+
# Extract scopes from line like "- Token scopes: 'gist', 'read:org', 'repo', 'workflow'"
|
102
|
+
scopes_part = line.split("Token scopes:")[1].strip()
|
103
|
+
# Remove quotes and split by comma
|
104
|
+
scopes = [
|
105
|
+
s.strip().strip("'\"") for s in scopes_part.split(",") if s.strip()
|
106
|
+
]
|
107
|
+
break
|
108
|
+
|
109
|
+
# Define required scopes based on CI/CD runner
|
110
|
+
if cicd_runner == "github_actions":
|
111
|
+
required_scopes = ["repo", "workflow"]
|
112
|
+
missing_scopes = [scope for scope in required_scopes if scope not in scopes]
|
113
|
+
|
114
|
+
if missing_scopes:
|
115
|
+
console.print(
|
116
|
+
f"❌ Missing required GitHub scopes: {', '.join(missing_scopes)}",
|
117
|
+
style="bold red",
|
118
|
+
)
|
119
|
+
console.print("To fix this: gh auth login --scopes repo,workflow")
|
120
|
+
raise click.ClickException(
|
121
|
+
"GitHub CLI authentication lacks required scopes"
|
122
|
+
)
|
123
|
+
|
124
|
+
elif cicd_runner == "google_cloud_build":
|
125
|
+
required_scopes = ["repo"]
|
126
|
+
missing_scopes = [scope for scope in required_scopes if scope not in scopes]
|
127
|
+
|
128
|
+
if missing_scopes:
|
129
|
+
console.print(
|
130
|
+
f"❌ Missing required GitHub scopes: {', '.join(missing_scopes)}",
|
131
|
+
style="bold red",
|
132
|
+
)
|
133
|
+
console.print("To fix this: gh auth login --scopes repo")
|
134
|
+
raise click.ClickException(
|
135
|
+
"GitHub CLI authentication lacks required scopes"
|
136
|
+
)
|
137
|
+
|
138
|
+
console.print("✅ GitHub CLI scopes verified")
|
139
|
+
|
140
|
+
except subprocess.CalledProcessError:
|
141
|
+
console.print("⚠️ Could not verify GitHub CLI scopes", style="yellow")
|
142
|
+
|
143
|
+
|
89
144
|
def prompt_gh_cli_installation() -> None:
|
90
145
|
"""Display instructions for installing GitHub CLI and exit."""
|
91
146
|
console.print("\n❌ GitHub CLI not found", style="bold red")
|
@@ -102,7 +157,7 @@ def setup_git_repository(config: ProjectConfig) -> str:
|
|
102
157
|
config: Project configuration containing repository details
|
103
158
|
|
104
159
|
Returns:
|
105
|
-
str:
|
160
|
+
str: Repository owner from the config
|
106
161
|
"""
|
107
162
|
console.print("\n🔧 Setting up Git repository...")
|
108
163
|
|
@@ -111,27 +166,31 @@ def setup_git_repository(config: ProjectConfig) -> str:
|
|
111
166
|
run_command(["git", "init", "-b", "main"])
|
112
167
|
console.print("✅ Git repository initialized")
|
113
168
|
|
114
|
-
# Get current GitHub username for the remote URL
|
115
|
-
result = run_command(["gh", "api", "user", "--jq", ".login"], capture_output=True)
|
116
|
-
github_username = result.stdout.strip()
|
117
|
-
|
118
169
|
# Add remote if it doesn't exist
|
170
|
+
remote_url = (
|
171
|
+
f"https://github.com/{config.repository_owner}/{config.repository_name}.git"
|
172
|
+
)
|
119
173
|
try:
|
120
174
|
run_command(
|
121
175
|
["git", "remote", "get-url", "origin"], capture_output=True, check=True
|
122
176
|
)
|
123
177
|
console.print("✅ Git remote already configured")
|
124
178
|
except subprocess.CalledProcessError:
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
179
|
+
try:
|
180
|
+
run_command(
|
181
|
+
["git", "remote", "add", "origin", remote_url],
|
182
|
+
capture_output=True,
|
183
|
+
check=True,
|
184
|
+
)
|
185
|
+
console.print(f"✅ Added git remote: {remote_url}")
|
186
|
+
except subprocess.CalledProcessError as e:
|
187
|
+
console.print(f"❌ Failed to add git remote: {e}", style="bold red")
|
188
|
+
raise click.ClickException(f"Failed to add git remote: {e}") from e
|
130
189
|
|
131
190
|
console.print(
|
132
191
|
"\n💡 Tip: Don't forget to commit and push your changes to the repository!"
|
133
192
|
)
|
134
|
-
return
|
193
|
+
return config.repository_owner
|
135
194
|
|
136
195
|
|
137
196
|
def prompt_for_git_provider() -> str:
|
@@ -163,7 +222,7 @@ def update_build_triggers(tf_dir: Path) -> None:
|
|
163
222
|
"""Update build triggers configuration."""
|
164
223
|
build_triggers_path = tf_dir / "build_triggers.tf"
|
165
224
|
if build_triggers_path.exists():
|
166
|
-
with open(build_triggers_path) as f:
|
225
|
+
with open(build_triggers_path, encoding="utf-8") as f:
|
167
226
|
content = f.read()
|
168
227
|
|
169
228
|
# Add repository dependency to all trigger resources
|
@@ -178,7 +237,7 @@ def update_build_triggers(tf_dir: Path) -> None:
|
|
178
237
|
"repository = google_cloudbuildv2_repository.repo.id",
|
179
238
|
)
|
180
239
|
|
181
|
-
with open(build_triggers_path, "w") as f:
|
240
|
+
with open(build_triggers_path, "w", encoding="utf-8") as f:
|
182
241
|
f.write(modified_content)
|
183
242
|
|
184
243
|
console.print("✅ Updated build triggers with repository dependency")
|
@@ -191,7 +250,7 @@ def prompt_for_repository_details(
|
|
191
250
|
# Get current GitHub username as default owner
|
192
251
|
result = run_command(["gh", "api", "user", "--jq", ".login"], capture_output=True)
|
193
252
|
default_owner = result.stdout.strip()
|
194
|
-
|
253
|
+
create_repository = False
|
195
254
|
|
196
255
|
if not (repository_name and repository_owner):
|
197
256
|
console.print("\n📦 Repository Configuration", style="bold blue")
|
@@ -204,9 +263,10 @@ def prompt_for_repository_details(
|
|
204
263
|
)
|
205
264
|
if choice == "1":
|
206
265
|
# New repository
|
266
|
+
create_repository = True
|
207
267
|
if not repository_name:
|
208
268
|
# Get project name from pyproject.toml
|
209
|
-
with open("pyproject.toml") as f:
|
269
|
+
with open("pyproject.toml", encoding="utf-8") as f:
|
210
270
|
for line in f:
|
211
271
|
if line.strip().startswith("name ="):
|
212
272
|
default_name = line.split("=")[1].strip().strip("\"'")
|
@@ -222,7 +282,7 @@ def prompt_for_repository_details(
|
|
222
282
|
)
|
223
283
|
else:
|
224
284
|
# Existing repository
|
225
|
-
|
285
|
+
create_repository = False
|
226
286
|
while True:
|
227
287
|
repo_url = click.prompt(
|
228
288
|
"Enter existing repository URL (e.g., https://github.com/owner/repo)"
|
@@ -242,7 +302,7 @@ def prompt_for_repository_details(
|
|
242
302
|
|
243
303
|
if repository_name is None or repository_owner is None:
|
244
304
|
raise ValueError("Repository name and owner must be provided")
|
245
|
-
return repository_name, repository_owner,
|
305
|
+
return repository_name, repository_owner, create_repository
|
246
306
|
|
247
307
|
|
248
308
|
def setup_terraform_backend(
|
@@ -294,7 +354,7 @@ def setup_terraform_backend(
|
|
294
354
|
}}
|
295
355
|
}}
|
296
356
|
'''
|
297
|
-
with open(backend_file, "w") as f:
|
357
|
+
with open(backend_file, "w", encoding="utf-8") as f:
|
298
358
|
f.write(backend_content)
|
299
359
|
|
300
360
|
console.print(
|
@@ -313,7 +373,7 @@ def create_or_update_secret(secret_id: str, secret_value: str, project_id: str)
|
|
313
373
|
Raises:
|
314
374
|
subprocess.CalledProcessError: If secret creation/update fails
|
315
375
|
"""
|
316
|
-
with tempfile.NamedTemporaryFile(mode="w") as temp_file:
|
376
|
+
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as temp_file:
|
317
377
|
temp_file.write(secret_value)
|
318
378
|
temp_file.flush()
|
319
379
|
|
@@ -379,11 +439,6 @@ console = Console()
|
|
379
439
|
"--github-app-installation-id",
|
380
440
|
help="GitHub App Installation ID for programmatic auth",
|
381
441
|
)
|
382
|
-
@click.option(
|
383
|
-
"--git-provider",
|
384
|
-
type=click.Choice(["github"]),
|
385
|
-
help="Git provider to use (currently only GitHub is supported)",
|
386
|
-
)
|
387
442
|
@click.option(
|
388
443
|
"--local-state",
|
389
444
|
is_flag=True,
|
@@ -397,10 +452,10 @@ console = Console()
|
|
397
452
|
help="Skip confirmation prompts and proceed automatically",
|
398
453
|
)
|
399
454
|
@click.option(
|
400
|
-
"--repository
|
455
|
+
"--create-repository",
|
401
456
|
is_flag=True,
|
402
457
|
default=False,
|
403
|
-
help="Flag indicating
|
458
|
+
help="Flag indicating whether to create a new repository",
|
404
459
|
)
|
405
460
|
@backoff.on_exception(
|
406
461
|
backoff.expo,
|
@@ -419,11 +474,10 @@ def setup_cicd(
|
|
419
474
|
host_connection_name: str | None,
|
420
475
|
github_pat: str | None,
|
421
476
|
github_app_installation_id: str | None,
|
422
|
-
git_provider: str | None,
|
423
477
|
local_state: bool,
|
424
478
|
debug: bool,
|
425
479
|
auto_approve: bool,
|
426
|
-
|
480
|
+
create_repository: bool,
|
427
481
|
) -> None:
|
428
482
|
"""Set up CI/CD infrastructure using Terraform."""
|
429
483
|
|
@@ -509,99 +563,123 @@ def setup_cicd(
|
|
509
563
|
logging.basicConfig(level=logging.DEBUG)
|
510
564
|
console.print("> Debug mode enabled")
|
511
565
|
|
512
|
-
#
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
566
|
+
# Auto-detect CI/CD runner based on Terraform files
|
567
|
+
tf_dir = Path("deployment/terraform")
|
568
|
+
is_github_actions = (tf_dir / "wif.tf").exists() and (tf_dir / "github.tf").exists()
|
569
|
+
cicd_runner = "github_actions" if is_github_actions else "google_cloud_build"
|
570
|
+
if debug:
|
571
|
+
logging.debug(f"Detected CI/CD runner: {cicd_runner}")
|
572
|
+
|
573
|
+
# Ensure GitHub CLI is available and authenticated
|
574
|
+
if not check_gh_cli_installed():
|
575
|
+
prompt_gh_cli_installation()
|
576
|
+
if not is_github_authenticated():
|
577
|
+
console.print("\n⚠️ Not authenticated with GitHub CLI", style="yellow")
|
578
|
+
handle_github_authentication()
|
579
|
+
else:
|
580
|
+
console.print("✅ GitHub CLI authentication verified")
|
581
|
+
|
582
|
+
# Check if GitHub CLI has required scopes for the CI/CD runner
|
583
|
+
console.print("\n🔍 Checking GitHub CLI scopes...")
|
584
|
+
check_github_scopes(cicd_runner)
|
527
585
|
|
528
|
-
#
|
586
|
+
# Gather repository details if not provided
|
529
587
|
if not (repository_name and repository_owner):
|
530
|
-
|
531
|
-
|
532
|
-
|
588
|
+
if auto_approve:
|
589
|
+
# Auto-generate repository details when auto-approve is used
|
590
|
+
if not repository_owner:
|
591
|
+
repository_owner = run_command(
|
592
|
+
["gh", "api", "user", "--jq", ".login"], capture_output=True
|
593
|
+
).stdout.strip()
|
594
|
+
if not repository_name:
|
595
|
+
# Get project name from pyproject.toml or generate one
|
596
|
+
try:
|
597
|
+
with open("pyproject.toml", encoding="utf-8") as f:
|
598
|
+
for line in f:
|
599
|
+
if line.strip().startswith("name ="):
|
600
|
+
repository_name = (
|
601
|
+
line.split("=")[1].strip().strip("\"'")
|
602
|
+
)
|
603
|
+
break
|
604
|
+
else:
|
605
|
+
repository_name = f"genai-app-{int(time.time())}"
|
606
|
+
except FileNotFoundError:
|
607
|
+
repository_name = f"genai-app-{int(time.time())}"
|
608
|
+
console.print(
|
609
|
+
f"✅ Auto-generated repository: {repository_owner}/{repository_name}"
|
610
|
+
)
|
611
|
+
# Keep the CLI argument value for create_repository
|
612
|
+
else:
|
613
|
+
repository_name, repository_owner, create_repository = (
|
614
|
+
prompt_for_repository_details(repository_name, repository_owner)
|
615
|
+
)
|
616
|
+
|
617
|
+
assert repository_name is not None, "Repository name must be provided"
|
618
|
+
assert repository_owner is not None, "Repository owner must be provided"
|
619
|
+
|
533
620
|
# Set default host connection name if not provided
|
534
621
|
if not host_connection_name:
|
535
622
|
host_connection_name = f"git-{repository_name}"
|
536
|
-
# Check and enable required APIs regardless of auth method
|
537
|
-
required_apis = ["secretmanager.googleapis.com", "cloudbuild.googleapis.com"]
|
538
|
-
ensure_apis_enabled(cicd_project, required_apis)
|
539
623
|
|
540
|
-
#
|
624
|
+
# For Cloud Build, determine mode and handle connection creation
|
541
625
|
oauth_token_secret_id = None
|
626
|
+
# Track original repository state for Terraform (before we create it)
|
627
|
+
terraform_create_repository = create_repository
|
628
|
+
|
629
|
+
if cicd_runner == "google_cloud_build":
|
630
|
+
# Determine if we're in programmatic or interactive mode based on provided credentials
|
631
|
+
detected_mode = (
|
632
|
+
"programmatic"
|
633
|
+
if github_pat and github_app_installation_id
|
634
|
+
else "interactive"
|
635
|
+
)
|
542
636
|
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
if git_provider == "github" and detected_mode == "interactive":
|
549
|
-
# First create the repository since we're in interactive mode
|
550
|
-
if not repository_exists:
|
551
|
-
create_github_repository(repository_owner, repository_name)
|
637
|
+
if detected_mode == "interactive":
|
638
|
+
console.print(
|
639
|
+
"\n🔗 Interactive mode: Creating GitHub connection using gcloud CLI..."
|
640
|
+
)
|
552
641
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
642
|
+
# Create connection using gcloud CLI (interactive approach)
|
643
|
+
try:
|
644
|
+
oauth_token_secret_id, github_app_installation_id = (
|
645
|
+
create_github_connection(
|
646
|
+
project_id=cicd_project,
|
647
|
+
region=region,
|
648
|
+
connection_name=host_connection_name,
|
649
|
+
)
|
650
|
+
)
|
651
|
+
create_cb_connection = (
|
652
|
+
True # Connection created by gcloud, Terraform will reference it
|
653
|
+
)
|
654
|
+
console.print("✅ GitHub connection created successfully")
|
655
|
+
except Exception as e:
|
656
|
+
console.print(
|
657
|
+
f"❌ Failed to create GitHub connection: {e}", style="red"
|
658
|
+
)
|
659
|
+
raise
|
571
660
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
661
|
+
elif detected_mode == "programmatic":
|
662
|
+
console.print(
|
663
|
+
"\n🔐 Programmatic mode: Creating GitHub PAT secret using gcloud CLI..."
|
664
|
+
)
|
576
665
|
|
577
|
-
|
578
|
-
console.print("=====================================")
|
666
|
+
oauth_token_secret_id = "github-pat" # Use fixed secret ID like main branch
|
579
667
|
|
580
|
-
|
581
|
-
|
582
|
-
staging_project_id=staging_project,
|
583
|
-
prod_project_id=prod_project,
|
584
|
-
cicd_project_id=cicd_project,
|
585
|
-
region=region,
|
586
|
-
repository_name=repository_name,
|
587
|
-
repository_owner=repository_owner,
|
588
|
-
host_connection_name=host_connection_name,
|
589
|
-
agent="", # Not needed for CICD setup
|
590
|
-
deployment_target="", # Not needed for CICD setup
|
591
|
-
github_pat=github_pat,
|
592
|
-
github_app_installation_id=github_app_installation_id,
|
593
|
-
git_provider=git_provider,
|
594
|
-
repository_exists=repository_exists,
|
595
|
-
)
|
668
|
+
if github_pat is None:
|
669
|
+
raise ValueError("GitHub PAT is required for programmatic mode")
|
596
670
|
|
597
|
-
|
671
|
+
# Create GitHub PAT secret using gcloud CLI instead of Terraform
|
672
|
+
console.print("📝 Creating GitHub PAT secret using gcloud CLI...")
|
673
|
+
create_or_update_secret(oauth_token_secret_id, github_pat, cicd_project)
|
674
|
+
create_cb_connection = False # Terraform will not create connection, will reference existing secret
|
675
|
+
console.print("✅ GitHub PAT secret created using gcloud CLI")
|
598
676
|
|
599
|
-
#
|
600
|
-
|
677
|
+
# For GitHub Actions, no connection management needed
|
678
|
+
if cicd_runner == "github_actions":
|
679
|
+
create_cb_connection = False
|
601
680
|
|
602
|
-
|
603
|
-
|
604
|
-
console.print("✅ Copied CICD terraform files")
|
681
|
+
console.print("\n📦 Starting CI/CD Infrastructure Setup", style="bold blue")
|
682
|
+
console.print("=====================================")
|
605
683
|
|
606
684
|
# Setup Terraform backend if not using local state
|
607
685
|
if not local_state:
|
@@ -616,133 +694,65 @@ def setup_cicd(
|
|
616
694
|
else:
|
617
695
|
console.print("\n📝 Using local Terraform state (remote backend disabled)")
|
618
696
|
|
619
|
-
#
|
620
|
-
deployment = E2EDeployment(config)
|
621
|
-
deployment.update_terraform_vars(
|
622
|
-
Path.cwd(), is_dev=False
|
623
|
-
) # is_dev=False for prod/staging setup
|
624
|
-
|
625
|
-
# Update env.tfvars with additional variables
|
697
|
+
# Prepare Terraform variables
|
626
698
|
env_vars_path = tf_dir / "vars" / "env.tfvars"
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
new_vars = {}
|
636
|
-
if not config.repository_owner:
|
637
|
-
result = run_command(
|
699
|
+
terraform_vars = {
|
700
|
+
"staging_project_id": staging_project,
|
701
|
+
"prod_project_id": prod_project,
|
702
|
+
"cicd_runner_project_id": cicd_project,
|
703
|
+
"region": region,
|
704
|
+
"repository_name": repository_name,
|
705
|
+
"repository_owner": repository_owner
|
706
|
+
or run_command(
|
638
707
|
["gh", "api", "user", "--jq", ".login"], capture_output=True
|
708
|
+
).stdout.strip(),
|
709
|
+
}
|
710
|
+
|
711
|
+
# Add CI/CD runner specific variables
|
712
|
+
if cicd_runner == "google_cloud_build":
|
713
|
+
terraform_vars.update(
|
714
|
+
{
|
715
|
+
"host_connection_name": host_connection_name,
|
716
|
+
"create_cb_connection": str(create_cb_connection).lower(),
|
717
|
+
"create_repository": str(
|
718
|
+
terraform_create_repository
|
719
|
+
).lower(), # Use original state
|
720
|
+
"github_app_installation_id": github_app_installation_id,
|
721
|
+
"github_pat_secret_id": oauth_token_secret_id,
|
722
|
+
}
|
639
723
|
)
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
new_vars["connection_exists"] = (
|
650
|
-
"true" if detected_mode == "interactive" else "false"
|
651
|
-
)
|
652
|
-
new_vars["repository_exists"] = "true" if config.repository_exists else "false"
|
653
|
-
|
654
|
-
# Update or append variables
|
655
|
-
with open(env_vars_path, "w") as f:
|
656
|
-
# Write existing content excluding lines with variables we're updating
|
657
|
-
for line in existing_content.splitlines():
|
658
|
-
if not any(line.startswith(f"{var} = ") for var in new_vars.keys()):
|
659
|
-
f.write(line + "\n")
|
660
|
-
|
661
|
-
# Write new/updated variables
|
662
|
-
for var_name, var_value in new_vars.items():
|
663
|
-
if var_value in ("true", "false"): # For boolean values
|
724
|
+
else: # github_actions
|
725
|
+
terraform_vars["create_repository"] = str(
|
726
|
+
terraform_create_repository
|
727
|
+
).lower() # Use original state
|
728
|
+
|
729
|
+
# Write Terraform variables
|
730
|
+
with open(env_vars_path, "w", encoding="utf-8") as f:
|
731
|
+
for var_name, var_value in terraform_vars.items():
|
732
|
+
if var_value in ("true", "false"): # Boolean values
|
664
733
|
f.write(f"{var_name} = {var_value}\n")
|
665
|
-
|
734
|
+
elif var_value is not None: # String values
|
666
735
|
f.write(f'{var_name} = "{var_value}"\n')
|
667
736
|
|
668
|
-
console.print("✅ Updated env.tfvars with
|
669
|
-
|
670
|
-
# Update dev environment vars
|
671
|
-
|
672
|
-
|
673
|
-
dev_tf_vars_path.exists()
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
)
|
684
|
-
|
685
|
-
with open(dev_tf_vars_path, "w") as f:
|
686
|
-
f.write(dev_content)
|
687
|
-
|
688
|
-
console.print("✅ Updated dev env.tfvars with project configuration")
|
689
|
-
|
690
|
-
# Update build triggers configuration
|
691
|
-
update_build_triggers(tf_dir)
|
692
|
-
|
693
|
-
# First initialize and apply dev terraform
|
694
|
-
dev_tf_dir = tf_dir / "dev"
|
695
|
-
if dev_tf_dir.exists() and dev_project: # Only deploy if dev_project is provided
|
696
|
-
with console.status("[bold blue]Setting up dev environment..."):
|
737
|
+
console.print("✅ Updated env.tfvars with variables")
|
738
|
+
|
739
|
+
# Update dev environment vars if dev project provided
|
740
|
+
if dev_project:
|
741
|
+
dev_tf_vars_path = tf_dir / "dev" / "vars" / "env.tfvars"
|
742
|
+
if dev_tf_vars_path.exists():
|
743
|
+
with open(dev_tf_vars_path, "w", encoding="utf-8") as f:
|
744
|
+
f.write(f'dev_project_id = "{dev_project}"\n')
|
745
|
+
console.print("✅ Updated dev env.tfvars")
|
746
|
+
|
747
|
+
# Apply dev Terraform if dev project is provided
|
748
|
+
if dev_project:
|
749
|
+
dev_tf_dir = tf_dir / "dev"
|
750
|
+
if dev_tf_dir.exists():
|
751
|
+
console.print("\n🏗️ Applying dev Terraform configuration...")
|
697
752
|
if local_state:
|
698
753
|
run_command(["terraform", "init", "-backend=false"], cwd=dev_tf_dir)
|
699
754
|
else:
|
700
755
|
run_command(["terraform", "init"], cwd=dev_tf_dir)
|
701
|
-
|
702
|
-
try:
|
703
|
-
run_command(
|
704
|
-
[
|
705
|
-
"terraform",
|
706
|
-
"apply",
|
707
|
-
"-auto-approve",
|
708
|
-
"--var-file",
|
709
|
-
"vars/env.tfvars",
|
710
|
-
],
|
711
|
-
cwd=dev_tf_dir,
|
712
|
-
)
|
713
|
-
except subprocess.CalledProcessError as e:
|
714
|
-
if "Error acquiring the state lock" in str(e):
|
715
|
-
console.print(
|
716
|
-
"[yellow]State lock error detected, retrying without lock...[/yellow]"
|
717
|
-
)
|
718
|
-
run_command(
|
719
|
-
[
|
720
|
-
"terraform",
|
721
|
-
"apply",
|
722
|
-
"-auto-approve",
|
723
|
-
"--var-file",
|
724
|
-
"vars/env.tfvars",
|
725
|
-
"-lock=false",
|
726
|
-
],
|
727
|
-
cwd=dev_tf_dir,
|
728
|
-
)
|
729
|
-
else:
|
730
|
-
raise
|
731
|
-
|
732
|
-
console.print("✅ Dev environment Terraform configuration applied")
|
733
|
-
elif dev_tf_dir.exists():
|
734
|
-
console.print("ℹ️ Skipping dev environment setup (no dev project provided)")
|
735
|
-
|
736
|
-
# Then apply prod terraform to create GitHub repo
|
737
|
-
with console.status(
|
738
|
-
"[bold blue]Setting up Prod/Staging Terraform configuration..."
|
739
|
-
):
|
740
|
-
if local_state:
|
741
|
-
run_command(["terraform", "init", "-backend=false"], cwd=tf_dir)
|
742
|
-
else:
|
743
|
-
run_command(["terraform", "init"], cwd=tf_dir)
|
744
|
-
|
745
|
-
try:
|
746
756
|
run_command(
|
747
757
|
[
|
748
758
|
"terraform",
|
@@ -751,87 +761,71 @@ def setup_cicd(
|
|
751
761
|
"--var-file",
|
752
762
|
"vars/env.tfvars",
|
753
763
|
],
|
754
|
-
cwd=
|
764
|
+
cwd=dev_tf_dir,
|
755
765
|
)
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
"[yellow]State lock error detected, retrying without lock...[/yellow]"
|
760
|
-
)
|
761
|
-
run_command(
|
762
|
-
[
|
763
|
-
"terraform",
|
764
|
-
"apply",
|
765
|
-
"-auto-approve",
|
766
|
-
"--var-file",
|
767
|
-
"vars/env.tfvars",
|
768
|
-
"-lock=false",
|
769
|
-
],
|
770
|
-
cwd=tf_dir,
|
771
|
-
)
|
772
|
-
else:
|
773
|
-
raise
|
766
|
+
console.print("✅ Dev environment deployed")
|
767
|
+
else:
|
768
|
+
console.print("ℹ️ No dev Terraform directory found")
|
774
769
|
|
775
|
-
|
770
|
+
# Apply prod Terraform
|
771
|
+
console.print("\n🚀 Applying prod Terraform configuration...")
|
772
|
+
if local_state:
|
773
|
+
run_command(["terraform", "init", "-backend=false"], cwd=tf_dir)
|
774
|
+
else:
|
775
|
+
run_command(["terraform", "init"], cwd=tf_dir)
|
776
776
|
|
777
|
-
#
|
778
|
-
|
779
|
-
|
777
|
+
# Prepare environment variables for Terraform
|
778
|
+
terraform_env_vars = {}
|
779
|
+
if (
|
780
|
+
cicd_runner == "google_cloud_build"
|
781
|
+
and detected_mode == "programmatic"
|
782
|
+
and github_pat
|
783
|
+
):
|
784
|
+
terraform_env_vars["GITHUB_TOKEN"] = (
|
785
|
+
github_pat # For GitHub provider authentication
|
786
|
+
)
|
780
787
|
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
788
|
+
run_command(
|
789
|
+
["terraform", "apply", "-auto-approve", "--var-file", "vars/env.tfvars"],
|
790
|
+
cwd=tf_dir,
|
791
|
+
env_vars=terraform_env_vars if terraform_env_vars else None,
|
792
|
+
)
|
793
|
+
console.print("✅ Prod/Staging infrastructure deployed")
|
785
794
|
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
795
|
+
config = ProjectConfig(
|
796
|
+
staging_project_id=staging_project,
|
797
|
+
prod_project_id=prod_project,
|
798
|
+
cicd_project_id=cicd_project,
|
799
|
+
agent="", # Not used in git setup
|
800
|
+
deployment_target="", # Not used in git setup
|
801
|
+
region=region,
|
802
|
+
repository_name=repository_name,
|
803
|
+
repository_owner=repository_owner,
|
804
|
+
)
|
791
805
|
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
["git", "remote", "get-url", "origin"], capture_output=True, check=True
|
796
|
-
)
|
797
|
-
console.print("✅ Git remote already configured")
|
798
|
-
except subprocess.CalledProcessError:
|
799
|
-
remote_url = (
|
800
|
-
f"https://github.com/{github_username}/{config.repository_name}.git"
|
801
|
-
)
|
802
|
-
run_command(["git", "remote", "add", "origin", remote_url])
|
803
|
-
console.print(f"✅ Added git remote: {remote_url}")
|
806
|
+
setup_git_repository(config)
|
807
|
+
|
808
|
+
console.print("\n✅ CI/CD infrastructure setup complete!")
|
804
809
|
|
810
|
+
# Print useful information
|
811
|
+
repo_url = f"https://github.com/{repository_owner}/{repository_name}"
|
812
|
+
|
813
|
+
console.print("\n📋 Summary:")
|
814
|
+
console.print(f"• Repository: {repo_url}")
|
815
|
+
console.print(f"• CI/CD Runner: {cicd_runner.replace('_', ' ').title()}")
|
816
|
+
|
817
|
+
if cicd_runner == "google_cloud_build":
|
805
818
|
console.print(
|
806
|
-
"
|
819
|
+
f"• Cloud Build: https://console.cloud.google.com/cloud-build/builds?project={cicd_project}"
|
807
820
|
)
|
821
|
+
else:
|
822
|
+
console.print(f"• GitHub Actions: {repo_url}/actions")
|
808
823
|
|
809
|
-
console.print("\n✅ CICD infrastructure setup complete!")
|
810
824
|
if not local_state:
|
811
|
-
console.print(
|
812
|
-
f"📦 Using remote Terraform state in bucket: {cicd_project}-terraform-state"
|
813
|
-
)
|
825
|
+
console.print(f"• Terraform State: gs://{cicd_project}-terraform-state")
|
814
826
|
else:
|
815
|
-
console.print("
|
827
|
+
console.print("• Terraform State: Local")
|
816
828
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
["gh", "api", "user", "--jq", ".login"], capture_output=True
|
821
|
-
)
|
822
|
-
github_username = result.stdout.strip()
|
823
|
-
|
824
|
-
repo_url = f"https://github.com/{github_username}/{config.repository_name}"
|
825
|
-
cloud_build_url = f"https://console.cloud.google.com/cloud-build/builds?project={config.cicd_project_id}"
|
826
|
-
# Sleep to allow resources to propagate
|
827
|
-
console.print("\n⏳ Waiting for resources to propagate...")
|
828
|
-
time.sleep(10)
|
829
|
-
|
830
|
-
# Print final summary
|
831
|
-
print_cicd_summary(config, github_username, repo_url, cloud_build_url)
|
832
|
-
|
833
|
-
except Exception as e:
|
834
|
-
if debug:
|
835
|
-
logging.exception("An error occurred:")
|
836
|
-
console.print(f"\n❌ Error: {e!s}", style="bold red")
|
837
|
-
sys.exit(1)
|
829
|
+
console.print("\n💡 Next steps:")
|
830
|
+
console.print("1. Commit and push your code to the repository")
|
831
|
+
console.print("2. Your CI/CD pipeline will automatically trigger on pushes")
|