agent-starter-pack 0.0.1b0__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.

Potentially problematic release.


This version of agent-starter-pack might be problematic. Click here for more details.

Files changed (162) hide show
  1. agent_starter_pack-0.0.1b0.dist-info/METADATA +143 -0
  2. agent_starter_pack-0.0.1b0.dist-info/RECORD +162 -0
  3. agent_starter_pack-0.0.1b0.dist-info/WHEEL +4 -0
  4. agent_starter_pack-0.0.1b0.dist-info/entry_points.txt +2 -0
  5. agent_starter_pack-0.0.1b0.dist-info/licenses/LICENSE +201 -0
  6. agents/agentic_rag_vertexai_search/README.md +22 -0
  7. agents/agentic_rag_vertexai_search/app/agent.py +145 -0
  8. agents/agentic_rag_vertexai_search/app/retrievers.py +79 -0
  9. agents/agentic_rag_vertexai_search/app/templates.py +53 -0
  10. agents/agentic_rag_vertexai_search/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  11. agents/agentic_rag_vertexai_search/template/.templateconfig.yaml +14 -0
  12. agents/agentic_rag_vertexai_search/tests/integration/test_agent.py +57 -0
  13. agents/crewai_coding_crew/README.md +34 -0
  14. agents/crewai_coding_crew/app/agent.py +86 -0
  15. agents/crewai_coding_crew/app/crew/config/agents.yaml +39 -0
  16. agents/crewai_coding_crew/app/crew/config/tasks.yaml +37 -0
  17. agents/crewai_coding_crew/app/crew/crew.py +71 -0
  18. agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +1571 -0
  19. agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  20. agents/crewai_coding_crew/template/.templateconfig.yaml +12 -0
  21. agents/crewai_coding_crew/tests/integration/test_agent.py +47 -0
  22. agents/langgraph_base_react/README.md +9 -0
  23. agents/langgraph_base_react/app/agent.py +73 -0
  24. agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  25. agents/langgraph_base_react/template/.templateconfig.yaml +13 -0
  26. agents/langgraph_base_react/tests/integration/test_agent.py +48 -0
  27. agents/multimodal_live_api/README.md +50 -0
  28. agents/multimodal_live_api/app/agent.py +86 -0
  29. agents/multimodal_live_api/app/server.py +193 -0
  30. agents/multimodal_live_api/app/templates.py +51 -0
  31. agents/multimodal_live_api/app/vector_store.py +55 -0
  32. agents/multimodal_live_api/template/.templateconfig.yaml +15 -0
  33. agents/multimodal_live_api/tests/integration/test_server_e2e.py +254 -0
  34. agents/multimodal_live_api/tests/load_test/load_test.py +40 -0
  35. agents/multimodal_live_api/tests/unit/test_server.py +143 -0
  36. src/base_template/.gitignore +197 -0
  37. src/base_template/Makefile +37 -0
  38. src/base_template/README.md +91 -0
  39. src/base_template/app/utils/tracing.py +143 -0
  40. src/base_template/app/utils/typing.py +115 -0
  41. src/base_template/deployment/README.md +123 -0
  42. src/base_template/deployment/cd/deploy-to-prod.yaml +98 -0
  43. src/base_template/deployment/cd/staging.yaml +215 -0
  44. src/base_template/deployment/ci/pr_checks.yaml +51 -0
  45. src/base_template/deployment/terraform/apis.tf +34 -0
  46. src/base_template/deployment/terraform/build_triggers.tf +122 -0
  47. src/base_template/deployment/terraform/dev/apis.tf +42 -0
  48. src/base_template/deployment/terraform/dev/iam.tf +90 -0
  49. src/base_template/deployment/terraform/dev/log_sinks.tf +66 -0
  50. src/base_template/deployment/terraform/dev/providers.tf +29 -0
  51. src/base_template/deployment/terraform/dev/storage.tf +76 -0
  52. src/base_template/deployment/terraform/dev/variables.tf +126 -0
  53. src/base_template/deployment/terraform/dev/vars/env.tfvars +21 -0
  54. src/base_template/deployment/terraform/iam.tf +130 -0
  55. src/base_template/deployment/terraform/locals.tf +50 -0
  56. src/base_template/deployment/terraform/log_sinks.tf +72 -0
  57. src/base_template/deployment/terraform/providers.tf +35 -0
  58. src/base_template/deployment/terraform/service_accounts.tf +42 -0
  59. src/base_template/deployment/terraform/storage.tf +100 -0
  60. src/base_template/deployment/terraform/variables.tf +202 -0
  61. src/base_template/deployment/terraform/vars/env.tfvars +43 -0
  62. src/base_template/pyproject.toml +113 -0
  63. src/base_template/tests/unit/test_utils/test_tracing_exporter.py +140 -0
  64. src/cli/commands/create.py +534 -0
  65. src/cli/commands/setup_cicd.py +730 -0
  66. src/cli/main.py +35 -0
  67. src/cli/utils/__init__.py +35 -0
  68. src/cli/utils/cicd.py +662 -0
  69. src/cli/utils/gcp.py +120 -0
  70. src/cli/utils/logging.py +51 -0
  71. src/cli/utils/template.py +644 -0
  72. src/data_ingestion/README.md +79 -0
  73. src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +175 -0
  74. src/data_ingestion/data_ingestion_pipeline/components/process_data.py +321 -0
  75. src/data_ingestion/data_ingestion_pipeline/pipeline.py +58 -0
  76. src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +184 -0
  77. src/data_ingestion/pyproject.toml +17 -0
  78. src/data_ingestion/uv.lock +999 -0
  79. src/deployment_targets/agent_engine/app/agent_engine_app.py +238 -0
  80. src/deployment_targets/agent_engine/app/utils/gcs.py +42 -0
  81. src/deployment_targets/agent_engine/deployment_metadata.json +4 -0
  82. src/deployment_targets/agent_engine/notebooks/intro_reasoning_engine.ipynb +869 -0
  83. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +120 -0
  84. src/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
  85. src/deployment_targets/agent_engine/tests/load_test/.results/report.html +264 -0
  86. src/deployment_targets/agent_engine/tests/load_test/.results/results_exceptions.csv +1 -0
  87. src/deployment_targets/agent_engine/tests/load_test/.results/results_failures.csv +1 -0
  88. src/deployment_targets/agent_engine/tests/load_test/.results/results_stats.csv +3 -0
  89. src/deployment_targets/agent_engine/tests/load_test/.results/results_stats_history.csv +22 -0
  90. src/deployment_targets/agent_engine/tests/load_test/README.md +42 -0
  91. src/deployment_targets/agent_engine/tests/load_test/load_test.py +100 -0
  92. src/deployment_targets/agent_engine/tests/unit/test_dummy.py +22 -0
  93. src/deployment_targets/cloud_run/Dockerfile +29 -0
  94. src/deployment_targets/cloud_run/app/server.py +128 -0
  95. src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +22 -0
  96. src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +20 -0
  97. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +192 -0
  98. src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
  99. src/deployment_targets/cloud_run/tests/load_test/README.md +79 -0
  100. src/deployment_targets/cloud_run/tests/load_test/load_test.py +85 -0
  101. src/deployment_targets/cloud_run/tests/unit/test_server.py +142 -0
  102. src/deployment_targets/cloud_run/uv.lock +6952 -0
  103. src/frontends/live_api_react/frontend/package-lock.json +19405 -0
  104. src/frontends/live_api_react/frontend/package.json +56 -0
  105. src/frontends/live_api_react/frontend/public/favicon.ico +0 -0
  106. src/frontends/live_api_react/frontend/public/index.html +62 -0
  107. src/frontends/live_api_react/frontend/public/robots.txt +3 -0
  108. src/frontends/live_api_react/frontend/src/App.scss +189 -0
  109. src/frontends/live_api_react/frontend/src/App.test.tsx +25 -0
  110. src/frontends/live_api_react/frontend/src/App.tsx +205 -0
  111. src/frontends/live_api_react/frontend/src/components/audio-pulse/AudioPulse.tsx +64 -0
  112. src/frontends/live_api_react/frontend/src/components/audio-pulse/audio-pulse.scss +68 -0
  113. src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +217 -0
  114. src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +201 -0
  115. src/frontends/live_api_react/frontend/src/components/logger/Logger.tsx +241 -0
  116. src/frontends/live_api_react/frontend/src/components/logger/logger.scss +133 -0
  117. src/frontends/live_api_react/frontend/src/components/logger/mock-logs.ts +151 -0
  118. src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +161 -0
  119. src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +285 -0
  120. src/frontends/live_api_react/frontend/src/contexts/LiveAPIContext.tsx +48 -0
  121. src/frontends/live_api_react/frontend/src/hooks/use-live-api.ts +115 -0
  122. src/frontends/live_api_react/frontend/src/hooks/use-media-stream-mux.ts +23 -0
  123. src/frontends/live_api_react/frontend/src/hooks/use-screen-capture.ts +72 -0
  124. src/frontends/live_api_react/frontend/src/hooks/use-webcam.ts +69 -0
  125. src/frontends/live_api_react/frontend/src/index.css +28 -0
  126. src/frontends/live_api_react/frontend/src/index.tsx +35 -0
  127. src/frontends/live_api_react/frontend/src/multimodal-live-types.ts +242 -0
  128. src/frontends/live_api_react/frontend/src/react-app-env.d.ts +17 -0
  129. src/frontends/live_api_react/frontend/src/reportWebVitals.ts +31 -0
  130. src/frontends/live_api_react/frontend/src/setupTests.ts +21 -0
  131. src/frontends/live_api_react/frontend/src/utils/audio-recorder.ts +111 -0
  132. src/frontends/live_api_react/frontend/src/utils/audio-streamer.ts +270 -0
  133. src/frontends/live_api_react/frontend/src/utils/audioworklet-registry.ts +43 -0
  134. src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +329 -0
  135. src/frontends/live_api_react/frontend/src/utils/store-logger.ts +64 -0
  136. src/frontends/live_api_react/frontend/src/utils/utils.ts +86 -0
  137. src/frontends/live_api_react/frontend/src/utils/worklets/audio-processing.ts +73 -0
  138. src/frontends/live_api_react/frontend/src/utils/worklets/vol-meter.ts +65 -0
  139. src/frontends/live_api_react/frontend/tsconfig.json +25 -0
  140. src/frontends/streamlit/frontend/side_bar.py +213 -0
  141. src/frontends/streamlit/frontend/streamlit_app.py +263 -0
  142. src/frontends/streamlit/frontend/style/app_markdown.py +37 -0
  143. src/frontends/streamlit/frontend/utils/chat_utils.py +67 -0
  144. src/frontends/streamlit/frontend/utils/local_chat_history.py +125 -0
  145. src/frontends/streamlit/frontend/utils/message_editing.py +59 -0
  146. src/frontends/streamlit/frontend/utils/multimodal_utils.py +217 -0
  147. src/frontends/streamlit/frontend/utils/stream_handler.py +282 -0
  148. src/frontends/streamlit/frontend/utils/title_summary.py +77 -0
  149. src/resources/containers/data_processing/Dockerfile +25 -0
  150. src/resources/locks/uv-agentic_rag_vertexai_search-agent_engine.lock +4684 -0
  151. src/resources/locks/uv-agentic_rag_vertexai_search-cloud_run.lock +5799 -0
  152. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +5509 -0
  153. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +6688 -0
  154. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +4595 -0
  155. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +5710 -0
  156. src/resources/locks/uv-multimodal_live_api-cloud_run.lock +5665 -0
  157. src/resources/setup_cicd/cicd_variables.tf +36 -0
  158. src/resources/setup_cicd/github.tf +85 -0
  159. src/resources/setup_cicd/providers.tf +39 -0
  160. src/utils/generate_locks.py +135 -0
  161. src/utils/lock_utils.py +82 -0
  162. src/utils/watch_and_rebuild.py +190 -0
@@ -0,0 +1,730 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import logging
17
+ import re
18
+ import shutil
19
+ import subprocess
20
+ import sys
21
+ import tempfile
22
+ import time
23
+ from pathlib import Path
24
+
25
+ import click
26
+ from rich.console import Console
27
+
28
+ from src.cli.utils.cicd import (
29
+ E2EDeployment,
30
+ ProjectConfig,
31
+ create_github_connection,
32
+ create_github_repository,
33
+ ensure_apis_enabled,
34
+ print_cicd_summary,
35
+ run_command,
36
+ )
37
+
38
+ console = Console()
39
+
40
+
41
+ def display_intro_message() -> None:
42
+ """Display introduction and warning messages about the setup-cicd command."""
43
+ console.print(
44
+ "\n⚠️ WARNING: The setup-cicd command is experimental and may have unexpected behavior.",
45
+ style="bold yellow",
46
+ )
47
+ console.print("Please report any issues you encounter.\n")
48
+
49
+ console.print("\n📋 About this command:", style="bold blue")
50
+ console.print(
51
+ "This command helps set up a basic CI/CD pipeline for development and testing purposes."
52
+ )
53
+ console.print("It will:")
54
+ console.print("- Create a GitHub repository and connect it to Cloud Build")
55
+ console.print("- Set up development environment infrastructure")
56
+ console.print("- Configure basic CI/CD triggers for PR checks and deployments")
57
+ console.print(
58
+ "- Configure remote Terraform state in GCS (use --local-state to use local state instead)"
59
+ )
60
+
61
+
62
+ def display_production_note() -> None:
63
+ """Display important note about production setup."""
64
+ console.print("\n⚡ Setup Note:", style="bold yellow")
65
+ console.print("For maximum flexibility, we recommend following")
66
+ console.print("the manual setup instructions in deployment/README.md")
67
+ console.print("This will give you more control over:")
68
+ console.print("- Security configurations")
69
+ console.print("- Custom deployment workflows")
70
+ console.print("- Environment-specific settings")
71
+ console.print("- Advanced CI/CD pipeline customization\n")
72
+
73
+
74
+ def setup_git_repository(config: ProjectConfig) -> str:
75
+ """Set up Git repository and remote.
76
+
77
+ Args:
78
+ config: Project configuration containing repository details
79
+
80
+ Returns:
81
+ str: GitHub username of the authenticated user
82
+ """
83
+ console.print("\n🔧 Setting up Git repository...")
84
+
85
+ # Initialize git if not already initialized
86
+ if not (Path.cwd() / ".git").exists():
87
+ run_command(["git", "init"])
88
+ console.print("✅ Git repository initialized")
89
+
90
+ # Get current GitHub username for the remote URL
91
+ result = run_command(["gh", "api", "user", "--jq", ".login"], capture_output=True)
92
+ github_username = result.stdout.strip()
93
+
94
+ # Add remote if it doesn't exist
95
+ try:
96
+ run_command(
97
+ ["git", "remote", "get-url", "origin"], capture_output=True, check=True
98
+ )
99
+ console.print("✅ Git remote already configured")
100
+ except subprocess.CalledProcessError:
101
+ remote_url = (
102
+ f"https://github.com/{github_username}/{config.repository_name}.git"
103
+ )
104
+ run_command(["git", "remote", "add", "origin", remote_url])
105
+ console.print(f"✅ Added git remote: {remote_url}")
106
+
107
+ console.print(
108
+ "\n💡 Tip: Don't forget to commit and push your changes to the repository!"
109
+ )
110
+ return github_username
111
+
112
+
113
+ def prompt_for_git_provider() -> str:
114
+ """Interactively prompt user for git provider selection."""
115
+ providers = ["github"] # Currently only GitHub is supported
116
+ console.print("\n🔄 Git Provider Selection", style="bold blue")
117
+ for i, provider in enumerate(providers, 1):
118
+ console.print(f"{i}. {provider}")
119
+
120
+ while True:
121
+ choice = click.prompt(
122
+ "\nSelect git provider",
123
+ type=click.Choice(["1"]), # Only allow '1' since GitHub is the only option
124
+ default="1",
125
+ )
126
+ return providers[int(choice) - 1]
127
+
128
+
129
+ def validate_working_directory() -> None:
130
+ """Ensure we're in the project root directory."""
131
+ if not Path("pyproject.toml").exists():
132
+ raise click.UsageError(
133
+ "This command must be run from the project root directory containing pyproject.toml. "
134
+ "Make sure you are in the folder created by agent-starter-pack."
135
+ )
136
+
137
+
138
+ def update_build_triggers(tf_dir: Path) -> None:
139
+ """Update build triggers configuration."""
140
+ build_triggers_path = tf_dir / "build_triggers.tf"
141
+ if build_triggers_path.exists():
142
+ with open(build_triggers_path) as f:
143
+ content = f.read()
144
+
145
+ # Add repository dependency to all trigger resources
146
+ modified_content = content.replace(
147
+ "depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]",
148
+ "depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services, google_cloudbuildv2_repository.repo]",
149
+ )
150
+
151
+ # Update repository reference in all triggers
152
+ modified_content = modified_content.replace(
153
+ 'repository = "projects/${var.cicd_runner_project_id}/locations/${var.region}/connections/${var.host_connection_name}/repositories/${var.repository_name}"',
154
+ "repository = google_cloudbuildv2_repository.repo.id",
155
+ )
156
+
157
+ with open(build_triggers_path, "w") as f:
158
+ f.write(modified_content)
159
+
160
+ console.print("✅ Updated build triggers with repository dependency")
161
+
162
+
163
+ def prompt_for_repository_details(
164
+ repository_name: str | None = None, repository_owner: str | None = None
165
+ ) -> tuple[str, str]:
166
+ """Interactive prompt for repository details with option to use existing repo."""
167
+ # Get current GitHub username as default owner
168
+ result = run_command(["gh", "api", "user", "--jq", ".login"], capture_output=True)
169
+ default_owner = result.stdout.strip()
170
+
171
+ if not (repository_name and repository_owner):
172
+ console.print("\n📦 Repository Configuration", style="bold blue")
173
+ console.print("Choose an option:")
174
+ console.print("1. Create new repository")
175
+ console.print("2. Use existing empty repository")
176
+
177
+ choice = click.prompt(
178
+ "Select option", type=click.Choice(["1", "2"]), default="1"
179
+ )
180
+
181
+ if choice == "1":
182
+ # New repository
183
+ if not repository_name:
184
+ repository_name = click.prompt(
185
+ "Enter new repository name", default=f"genai-app-{int(time.time())}"
186
+ )
187
+ if not repository_owner:
188
+ repository_owner = click.prompt(
189
+ "Enter repository owner", default=default_owner
190
+ )
191
+ else:
192
+ # Existing repository
193
+ while True:
194
+ repo_url = click.prompt(
195
+ "Enter existing repository URL (e.g., https://github.com/owner/repo)"
196
+ )
197
+ # Extract owner and name from URL
198
+ match = re.match(r"https://github\.com/([^/]+)/([^/]+)", repo_url)
199
+ if match:
200
+ repository_owner = match.group(1)
201
+ repository_name = match.group(2)
202
+
203
+ # Verify repository exists and is empty
204
+ try:
205
+ result = run_command(
206
+ [
207
+ "gh",
208
+ "repo",
209
+ "view",
210
+ f"{repository_owner}/{repository_name}",
211
+ "--json",
212
+ "isEmpty",
213
+ ],
214
+ capture_output=True,
215
+ )
216
+ if not json.loads(result.stdout).get("isEmpty", False):
217
+ if not click.confirm(
218
+ "Repository is not empty. Are you sure you want to use it?",
219
+ default=False,
220
+ ):
221
+ continue
222
+ break
223
+ except subprocess.CalledProcessError:
224
+ console.print(
225
+ "❌ Repository not found or not accessible",
226
+ style="bold red",
227
+ )
228
+ continue
229
+ else:
230
+ console.print("❌ Invalid repository URL format", style="bold red")
231
+
232
+ if repository_name is None or repository_owner is None:
233
+ raise ValueError("Repository name and owner must be provided")
234
+ return repository_name, repository_owner
235
+
236
+
237
+ def setup_terraform_backend(tf_dir: Path, project_id: str, region: str) -> None:
238
+ """Setup terraform backend configuration with GCS bucket"""
239
+ console.print("\n🔧 Setting up Terraform backend...")
240
+
241
+ bucket_name = f"{project_id}-terraform-state"
242
+
243
+ # Ensure bucket exists
244
+ try:
245
+ result = run_command(
246
+ ["gsutil", "ls", "-b", f"gs://{bucket_name}"],
247
+ check=False,
248
+ capture_output=True,
249
+ )
250
+
251
+ if result.returncode != 0:
252
+ console.print(f"\n📦 Creating Terraform state bucket: {bucket_name}")
253
+ # Create bucket
254
+ run_command(
255
+ ["gsutil", "mb", "-p", project_id, "-l", region, f"gs://{bucket_name}"]
256
+ )
257
+
258
+ # Enable versioning
259
+ run_command(["gsutil", "versioning", "set", "on", f"gs://{bucket_name}"])
260
+ except subprocess.CalledProcessError as e:
261
+ console.print(f"\n❌ Failed to setup state bucket: {e}")
262
+ raise
263
+
264
+ # Create backend.tf in both root and dev directories
265
+ tf_dirs = [
266
+ tf_dir, # Root terraform directory
267
+ tf_dir / "dev", # Dev terraform directory
268
+ ]
269
+
270
+ for dir_path in tf_dirs:
271
+ if dir_path.exists():
272
+ # Use different state prefixes for dev and prod
273
+ is_dev_dir = str(dir_path).endswith("/dev")
274
+ state_prefix = "dev" if is_dev_dir else "prod"
275
+
276
+ backend_file = dir_path / "backend.tf"
277
+ backend_content = f'''terraform {{
278
+ backend "gcs" {{
279
+ bucket = "{bucket_name}"
280
+ prefix = "{state_prefix}"
281
+ }}
282
+ }}
283
+ '''
284
+ with open(backend_file, "w") as f:
285
+ f.write(backend_content)
286
+
287
+ console.print(
288
+ f"✅ Terraform backend configured in {dir_path} to use bucket: {bucket_name} with prefix: {state_prefix}"
289
+ )
290
+
291
+
292
+ def create_or_update_secret(secret_id: str, secret_value: str, project_id: str) -> None:
293
+ """Create or update a secret in Google Cloud Secret Manager.
294
+
295
+ Args:
296
+ secret_id: The ID of the secret to create/update
297
+ secret_value: The value to store in the secret
298
+ project_id: The Google Cloud project ID
299
+
300
+ Raises:
301
+ subprocess.CalledProcessError: If secret creation/update fails
302
+ """
303
+ with tempfile.NamedTemporaryFile(mode="w") as temp_file:
304
+ temp_file.write(secret_value)
305
+ temp_file.flush()
306
+
307
+ # First try to add a new version to existing secret
308
+ try:
309
+ run_command(
310
+ [
311
+ "gcloud",
312
+ "secrets",
313
+ "versions",
314
+ "add",
315
+ secret_id,
316
+ "--data-file",
317
+ temp_file.name,
318
+ f"--project={project_id}",
319
+ ]
320
+ )
321
+ console.print("✅ Updated existing GitHub PAT secret")
322
+ except subprocess.CalledProcessError:
323
+ # If adding version fails (secret doesn't exist), try to create it
324
+ try:
325
+ run_command(
326
+ [
327
+ "gcloud",
328
+ "secrets",
329
+ "create",
330
+ secret_id,
331
+ "--data-file",
332
+ temp_file.name,
333
+ f"--project={project_id}",
334
+ "--replication-policy",
335
+ "automatic",
336
+ ]
337
+ )
338
+ console.print("✅ Created new GitHub PAT secret")
339
+ except subprocess.CalledProcessError as e:
340
+ console.print(
341
+ f"❌ Failed to create/update GitHub PAT secret: {e!s}",
342
+ style="bold red",
343
+ )
344
+ raise
345
+
346
+
347
+ console = Console()
348
+
349
+
350
+ @click.command()
351
+ @click.option("--dev-project", help="Development project ID")
352
+ @click.option("--staging-project", required=True, help="Staging project ID")
353
+ @click.option("--prod-project", required=True, help="Production project ID")
354
+ @click.option("--cicd-project", required=True, help="CICD project ID")
355
+ @click.option("--region", default="us-central1", help="GCP region")
356
+ @click.option("--repository-name", help="Repository name (optional)")
357
+ @click.option(
358
+ "--repository-owner",
359
+ help="Repository owner (optional, defaults to current GitHub user)",
360
+ )
361
+ @click.option("--host-connection-name", help="Host connection name (optional)")
362
+ @click.option("--github-pat", help="GitHub Personal Access Token for programmatic auth")
363
+ @click.option(
364
+ "--github-app-installation-id",
365
+ help="GitHub App Installation ID for programmatic auth",
366
+ )
367
+ @click.option(
368
+ "--git-provider",
369
+ type=click.Choice(["github"]),
370
+ help="Git provider to use (currently only GitHub is supported)",
371
+ )
372
+ @click.option(
373
+ "--local-state",
374
+ is_flag=True,
375
+ default=False,
376
+ help="Use local Terraform state instead of remote GCS backend (defaults to remote)",
377
+ )
378
+ @click.option("--debug", is_flag=True, help="Enable debug logging")
379
+ @click.option(
380
+ "--auto-approve",
381
+ is_flag=True,
382
+ help="Skip confirmation prompts and proceed automatically",
383
+ )
384
+ def setup_cicd(
385
+ dev_project: str | None,
386
+ staging_project: str,
387
+ prod_project: str,
388
+ cicd_project: str,
389
+ region: str,
390
+ repository_name: str | None,
391
+ repository_owner: str | None,
392
+ host_connection_name: str | None,
393
+ github_pat: str | None,
394
+ github_app_installation_id: str | None,
395
+ git_provider: str | None,
396
+ local_state: bool,
397
+ debug: bool,
398
+ auto_approve: bool,
399
+ ) -> None:
400
+ """Set up CI/CD infrastructure using Terraform."""
401
+
402
+ # Check if we're in the root folder by looking for pyproject.toml
403
+ if not Path("pyproject.toml").exists():
404
+ raise click.UsageError(
405
+ "This command must be run from the project root directory containing pyproject.toml. "
406
+ "Make sure you are in the folder created by agent-starter-pack."
407
+ )
408
+
409
+ console.print(
410
+ "\n⚠️ WARNING: The setup-cicd command is experimental and may have unexpected behavior.",
411
+ style="bold yellow",
412
+ )
413
+ console.print("Please report any issues you encounter.\n")
414
+
415
+ console.print("\n📋 About this command:", style="bold blue")
416
+ console.print(
417
+ "This command helps set up a basic CI/CD pipeline for development and testing purposes."
418
+ )
419
+ console.print("It will:")
420
+ console.print("- Create a GitHub repository and connect it to Cloud Build")
421
+ console.print("- Set up development environment infrastructure")
422
+ console.print("- Configure basic CI/CD triggers for PR checks and deployments")
423
+ console.print(
424
+ "- Configure remote Terraform state in GCS (use --local-state to use local state instead)"
425
+ )
426
+
427
+ console.print("\n⚡ Production Setup Note:", style="bold yellow")
428
+ console.print(
429
+ "For production deployments and maximum flexibility, we recommend following"
430
+ )
431
+ console.print("the manual setup instructions in deployment/README.md")
432
+ console.print("This will give you more control over:")
433
+ console.print("- Security configurations")
434
+ console.print("- Custom deployment workflows")
435
+ console.print("- Environment-specific settings")
436
+ console.print("- Advanced CI/CD pipeline customization\n")
437
+
438
+ # Add the confirmation prompt
439
+ if not auto_approve:
440
+ if not click.confirm("\nDo you want to continue with the setup?", default=True):
441
+ console.print("\n🛑 Setup cancelled by user", style="bold yellow")
442
+ return
443
+ console.print(
444
+ "This command helps set up a basic CI/CD pipeline for development and testing purposes."
445
+ )
446
+ console.print("It will:")
447
+ console.print("- Create a GitHub repository and connect it to Cloud Build")
448
+ console.print("- Set up development environment infrastructure")
449
+ console.print("- Configure basic CI/CD triggers for PR checks and deployments")
450
+ console.print(
451
+ "- Configure remote Terraform state in GCS (use --local-state to use local state instead)"
452
+ )
453
+
454
+ console.print("\n⚡ Production Setup Note:", style="bold yellow")
455
+ console.print(
456
+ "For production deployments and maximum flexibility, we recommend following"
457
+ )
458
+ console.print("the manual setup instructions in deployment/README.md")
459
+ console.print("This will give you more control over:")
460
+ console.print("- Security configurations")
461
+ console.print("- Custom deployment workflows")
462
+ console.print("- Environment-specific settings")
463
+ console.print("- Advanced CI/CD pipeline customization\n")
464
+
465
+ if debug:
466
+ logging.basicConfig(level=logging.DEBUG)
467
+ console.print("> Debug mode enabled")
468
+
469
+ # Set git provider through prompt if not provided
470
+ if not git_provider:
471
+ git_provider = prompt_for_git_provider()
472
+
473
+ # Only prompt for repository details if not provided via CLI
474
+ if not (repository_name and repository_owner):
475
+ repository_name, repository_owner = prompt_for_repository_details(
476
+ repository_name, repository_owner
477
+ )
478
+ # Set default host connection name if not provided
479
+ if not host_connection_name:
480
+ host_connection_name = "github-connection"
481
+ # Check and enable required APIs regardless of auth method
482
+ required_apis = ["secretmanager.googleapis.com", "cloudbuild.googleapis.com"]
483
+ ensure_apis_enabled(cicd_project, required_apis)
484
+
485
+ # Create GitHub connection and repository if not using PAT authentication
486
+ oauth_token_secret_id = None
487
+
488
+ # Determine if we're in programmatic or interactive mode based on provided credentials
489
+ detected_mode = (
490
+ "programmatic" if github_pat and github_app_installation_id else "interactive"
491
+ )
492
+
493
+ if git_provider == "github" and detected_mode == "interactive":
494
+ # First create the repository since we're in interactive mode
495
+ create_github_repository(repository_owner, repository_name)
496
+
497
+ # Then create the connection
498
+ oauth_token_secret_id, github_app_installation_id = create_github_connection(
499
+ project_id=cicd_project,
500
+ region=region,
501
+ connection_name=host_connection_name,
502
+ repository_name=repository_name,
503
+ repository_owner=repository_owner,
504
+ )
505
+ elif git_provider == "github" and detected_mode == "programmatic":
506
+ oauth_token_secret_id = "github-pat"
507
+
508
+ if github_pat is None:
509
+ raise ValueError("GitHub PAT is required for programmatic mode")
510
+
511
+ # Create the GitHub PAT secret if provided
512
+ console.print("\n🔐 Creating/updating GitHub PAT secret...")
513
+ create_or_update_secret(
514
+ secret_id=oauth_token_secret_id,
515
+ secret_value=github_pat,
516
+ project_id=cicd_project,
517
+ )
518
+
519
+ else:
520
+ # Unsupported git provider
521
+ console.print("⚠️ Only GitHub is currently supported.", style="bold yellow")
522
+ raise ValueError("Unsupported git provider")
523
+
524
+ console.print("\n📦 Starting CI/CD Infrastructure Setup", style="bold blue")
525
+ console.print("=====================================")
526
+
527
+ config = ProjectConfig(
528
+ dev_project_id=dev_project,
529
+ staging_project_id=staging_project,
530
+ prod_project_id=prod_project,
531
+ cicd_project_id=cicd_project,
532
+ region=region,
533
+ repository_name=repository_name,
534
+ repository_owner=repository_owner,
535
+ host_connection_name=host_connection_name,
536
+ agent="", # Not needed for CICD setup
537
+ deployment_target="", # Not needed for CICD setup
538
+ github_pat=github_pat,
539
+ github_app_installation_id=github_app_installation_id,
540
+ git_provider=git_provider,
541
+ )
542
+
543
+ tf_dir = Path("deployment/terraform")
544
+
545
+ # Copy CICD terraform files
546
+ cicd_utils_path = Path(__file__).parent.parent.parent / "resources" / "setup_cicd"
547
+
548
+ for tf_file in cicd_utils_path.glob("*.tf"):
549
+ shutil.copy2(tf_file, tf_dir)
550
+ console.print("✅ Copied CICD terraform files")
551
+
552
+ # Setup Terraform backend if not using local state
553
+ if not local_state:
554
+ console.print("\n🔧 Setting up remote Terraform backend...")
555
+ setup_terraform_backend(tf_dir, cicd_project, region)
556
+ console.print("✅ Remote Terraform backend configured")
557
+ else:
558
+ console.print("\n📝 Using local Terraform state (remote backend disabled)")
559
+
560
+ # Update terraform variables using existing function
561
+ deployment = E2EDeployment(config)
562
+ deployment.update_terraform_vars(
563
+ Path.cwd(), is_dev=False
564
+ ) # is_dev=False for prod/staging setup
565
+
566
+ # Update env.tfvars with additional variables
567
+ env_vars_path = tf_dir / "vars" / "env.tfvars"
568
+
569
+ # Read existing content
570
+ existing_content = ""
571
+ if env_vars_path.exists():
572
+ with open(env_vars_path) as f:
573
+ existing_content = f.read()
574
+
575
+ # Prepare new variables
576
+ new_vars = {}
577
+ if not config.repository_owner:
578
+ result = run_command(
579
+ ["gh", "api", "user", "--jq", ".login"], capture_output=True
580
+ )
581
+ new_vars["repository_owner"] = result.stdout.strip()
582
+ else:
583
+ new_vars["repository_owner"] = config.repository_owner
584
+
585
+ # Use the app installation ID from the connection if available, otherwise use the provided one
586
+ new_vars["github_app_installation_id"] = github_app_installation_id
587
+ # Use the OAuth token secret ID if available, otherwise use default PAT secret ID
588
+ new_vars["github_pat_secret_id"] = oauth_token_secret_id
589
+ # Set connection_exists based on whether we created a new connection
590
+ new_vars["connection_exists"] = (
591
+ "true" if detected_mode == "interactive" else "false"
592
+ )
593
+
594
+ # Update or append variables
595
+ with open(env_vars_path, "w") as f:
596
+ # Write existing content excluding lines with variables we're updating
597
+ for line in existing_content.splitlines():
598
+ if not any(line.startswith(f"{var} = ") for var in new_vars.keys()):
599
+ f.write(line + "\n")
600
+
601
+ # Write new/updated variables
602
+ for var_name, var_value in new_vars.items():
603
+ if var_value in ("true", "false"): # For boolean values
604
+ f.write(f"{var_name} = {var_value}\n")
605
+ else: # For string values
606
+ f.write(f'{var_name} = "{var_value}"\n')
607
+
608
+ console.print("✅ Updated env.tfvars with additional variables")
609
+
610
+ # Update dev environment vars
611
+ dev_tf_vars_path = tf_dir / "dev" / "vars" / "env.tfvars"
612
+ if (
613
+ dev_tf_vars_path.exists() and dev_project
614
+ ): # Only update if dev_project is provided
615
+ with open(dev_tf_vars_path) as f:
616
+ dev_content = f.read()
617
+
618
+ # Update dev project ID
619
+ dev_content = re.sub(
620
+ r'dev_project_id\s*=\s*"[^"]*"',
621
+ f'dev_project_id = "{dev_project}"',
622
+ dev_content,
623
+ )
624
+
625
+ with open(dev_tf_vars_path, "w") as f:
626
+ f.write(dev_content)
627
+
628
+ console.print("✅ Updated dev env.tfvars with project configuration")
629
+
630
+ # Update build triggers configuration
631
+ update_build_triggers(tf_dir)
632
+
633
+ # First initialize and apply dev terraform
634
+ dev_tf_dir = tf_dir / "dev"
635
+ if dev_tf_dir.exists() and dev_project: # Only deploy if dev_project is provided
636
+ with console.status("[bold blue]Setting up dev environment..."):
637
+ if local_state:
638
+ run_command(["terraform", "init", "-backend=false"], cwd=dev_tf_dir)
639
+ else:
640
+ run_command(["terraform", "init"], cwd=dev_tf_dir)
641
+
642
+ run_command(
643
+ [
644
+ "terraform",
645
+ "apply",
646
+ "-auto-approve",
647
+ "--var-file",
648
+ "vars/env.tfvars",
649
+ ],
650
+ cwd=dev_tf_dir,
651
+ )
652
+
653
+ console.print("✅ Dev environment Terraform configuration applied")
654
+ elif dev_tf_dir.exists():
655
+ console.print("ℹ️ Skipping dev environment setup (no dev project provided)")
656
+
657
+ # Then apply prod terraform to create GitHub repo
658
+ with console.status(
659
+ "[bold blue]Setting up Prod/Staging Terraform configuration..."
660
+ ):
661
+ if local_state:
662
+ run_command(["terraform", "init", "-backend=false"], cwd=tf_dir)
663
+ else:
664
+ run_command(["terraform", "init"], cwd=tf_dir)
665
+
666
+ run_command(
667
+ ["terraform", "apply", "-auto-approve", "--var-file", "vars/env.tfvars"],
668
+ cwd=tf_dir,
669
+ )
670
+
671
+ console.print("✅ Prod/Staging Terraform configuration applied")
672
+
673
+ # Now we can set up git since the repo exists
674
+ if git_provider == "github":
675
+ console.print("\n🔧 Setting up Git repository...")
676
+
677
+ # Initialize git if not already initialized
678
+ if not (Path.cwd() / ".git").exists():
679
+ run_command(["git", "init"])
680
+ console.print("✅ Git repository initialized")
681
+
682
+ # Get current GitHub username for the remote URL
683
+ result = run_command(
684
+ ["gh", "api", "user", "--jq", ".login"], capture_output=True
685
+ )
686
+ github_username = result.stdout.strip()
687
+
688
+ # Add remote if it doesn't exist
689
+ try:
690
+ run_command(
691
+ ["git", "remote", "get-url", "origin"], capture_output=True, check=True
692
+ )
693
+ console.print("✅ Git remote already configured")
694
+ except subprocess.CalledProcessError:
695
+ remote_url = (
696
+ f"https://github.com/{github_username}/{config.repository_name}.git"
697
+ )
698
+ run_command(["git", "remote", "add", "origin", remote_url])
699
+ console.print(f"✅ Added git remote: {remote_url}")
700
+
701
+ console.print(
702
+ "\n💡 Tip: Don't forget to commit and push your changes to the repository!"
703
+ )
704
+
705
+ console.print("\n✅ CICD infrastructure setup complete!")
706
+ if not local_state:
707
+ console.print(
708
+ f"📦 Using remote Terraform state in bucket: {cicd_project}-terraform-state"
709
+ )
710
+ else:
711
+ console.print("📝 Using local Terraform state")
712
+
713
+ try:
714
+ # Print success message with useful links
715
+ result = run_command(
716
+ ["gh", "api", "user", "--jq", ".login"], capture_output=True
717
+ )
718
+ github_username = result.stdout.strip()
719
+
720
+ repo_url = f"https://github.com/{github_username}/{config.repository_name}"
721
+ cloud_build_url = f"https://console.cloud.google.com/cloud-build/builds?project={config.cicd_project_id}"
722
+
723
+ # Print final summary
724
+ print_cicd_summary(config, github_username, repo_url, cloud_build_url)
725
+
726
+ except Exception as e:
727
+ if debug:
728
+ logging.exception("An error occurred:")
729
+ console.print(f"\n❌ Error: {e!s}", style="bold red")
730
+ sys.exit(1)