legate 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- metadata +823 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
# File: lib/legate/cli/deployment_commands.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'thor'
|
|
5
|
+
require_relative 'base_command'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'yaml'
|
|
9
|
+
require 'logger' # Needed for sample entrypoint
|
|
10
|
+
require 'securerandom' # Needed for suggested project ID
|
|
11
|
+
require 'shellwords'
|
|
12
|
+
|
|
13
|
+
module Legate
|
|
14
|
+
module CLI
|
|
15
|
+
# CLI commands for generating deployment assets
|
|
16
|
+
class DeploymentCommands < BaseCommand
|
|
17
|
+
# Default Ruby image if not specified
|
|
18
|
+
DEFAULT_RUBY_IMAGE = 'ruby:3.2-slim'
|
|
19
|
+
# Default output directory name
|
|
20
|
+
DEFAULT_DEPLOYMENT_DIR_NAME = 'deployment'
|
|
21
|
+
# Default sample entrypoint path
|
|
22
|
+
DEFAULT_SAMPLE_ENTRYPOINT_PATH = 'bin/legate_web_entrypoint.rb'
|
|
23
|
+
|
|
24
|
+
# --- Generic Options ---
|
|
25
|
+
desc 'generate', 'Generate deployment assets (Dockerfile, .dockerignore, cloud-specific configs)'
|
|
26
|
+
method_option :cloud, type: :string, aliases: '-c', default: 'none', required: true,
|
|
27
|
+
enum: %w[gcp aws azure none], desc: 'Target cloud provider (gcp, aws, azure, none)'
|
|
28
|
+
method_option :entry_point, type: :string, aliases: '-e', required: false,
|
|
29
|
+
desc: 'Entry point script for the main application/web process (e.g., bin/web). Required unless --generate-sample-entrypoint is used.'
|
|
30
|
+
method_option :agent_entry_points, type: :array, aliases: '-a',
|
|
31
|
+
desc: 'Entry points for user agents (comma separated)'
|
|
32
|
+
method_option :name, type: :string, aliases: '-n', default: DEFAULT_DEPLOYMENT_DIR_NAME,
|
|
33
|
+
desc: 'Base name for the output directory and potentially generated resources'
|
|
34
|
+
method_option :base_image, type: :string, default: DEFAULT_RUBY_IMAGE, desc: 'Base Ruby Docker image to use'
|
|
35
|
+
method_option :generate_sample_entrypoint, type: :boolean, default: false,
|
|
36
|
+
desc: "Generate a sample web entrypoint script (#{DEFAULT_SAMPLE_ENTRYPOINT_PATH}) with a /healthz check."
|
|
37
|
+
|
|
38
|
+
# --- GCP Specific Options (Only relevant if --cloud gcp) ---
|
|
39
|
+
class_option :gcp_project_id, type: :string, group: 'GCP', desc: 'GCP Project ID (required for GCP deployment)'
|
|
40
|
+
class_option :gcp_region, type: :string, default: 'us-central1', group: 'GCP', desc: 'GCP Region'
|
|
41
|
+
class_option :gcp_service_name, type: :string, default: 'legate-agent-service', group: 'GCP',
|
|
42
|
+
desc: 'GCP Cloud Run service name for the main process'
|
|
43
|
+
class_option :gcp_memory, type: :string, default: '512Mi', group: 'GCP',
|
|
44
|
+
desc: 'GCP Cloud Run memory allocation (e.g., 512Mi, 1Gi)'
|
|
45
|
+
class_option :gcp_cpu, type: :string, default: '1', group: 'GCP', desc: 'GCP Cloud Run CPU allocation'
|
|
46
|
+
# We might add options for agent service names, memory, cpu later.
|
|
47
|
+
|
|
48
|
+
def generate(_directory = '.')
|
|
49
|
+
# Determine the effective entry point
|
|
50
|
+
effective_entry_point = if options[:generate_sample_entrypoint]
|
|
51
|
+
options[:entry_point] || DEFAULT_SAMPLE_ENTRYPOINT_PATH
|
|
52
|
+
else
|
|
53
|
+
options[:entry_point]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Validate entry_point is provided if sample isn't generated
|
|
57
|
+
unless effective_entry_point
|
|
58
|
+
say 'Error: --entry-point is required unless --generate-sample-entrypoint is used.', :red
|
|
59
|
+
exit 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
deployment_dir = File.expand_path(options[:name])
|
|
63
|
+
deployment_dir_basename = File.basename(deployment_dir)
|
|
64
|
+
gcp_config_name = nil # Store generated config name for final message
|
|
65
|
+
FileUtils.mkdir_p(deployment_dir)
|
|
66
|
+
|
|
67
|
+
say "Generating deployment assets in #{deployment_dir}...", :green
|
|
68
|
+
|
|
69
|
+
# 0. Generate sample entrypoint if requested (BEFORE generating Dockerfiles)
|
|
70
|
+
generate_sample_entrypoint_script(effective_entry_point) if options[:generate_sample_entrypoint]
|
|
71
|
+
|
|
72
|
+
# 1. Generate Generic Assets (Dockerfile(s), .dockerignore, config.ru)
|
|
73
|
+
generate_dockerfiles(deployment_dir, effective_entry_point, deployment_dir_basename)
|
|
74
|
+
generate_dockerignore(deployment_dir)
|
|
75
|
+
generate_config_ru(deployment_dir, effective_entry_point)
|
|
76
|
+
|
|
77
|
+
# 2. Generate Cloud-Specific Assets
|
|
78
|
+
case options[:cloud]
|
|
79
|
+
when 'gcp'
|
|
80
|
+
gcp_config_name = generate_gcp_assets(deployment_dir)
|
|
81
|
+
when 'aws'
|
|
82
|
+
generate_aws_assets(deployment_dir)
|
|
83
|
+
when 'azure'
|
|
84
|
+
generate_azure_assets(deployment_dir)
|
|
85
|
+
when 'none'
|
|
86
|
+
say 'Generated generic Docker assets only.', :yellow
|
|
87
|
+
else
|
|
88
|
+
# Should not happen due to Thor's enum check, but good practice
|
|
89
|
+
say "Unsupported cloud provider: #{options[:cloud]}", :red
|
|
90
|
+
exit 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
say 'Deployment asset generation complete!', :green
|
|
94
|
+
say "NOTE: Sample entrypoint generated at '#{effective_entry_point}'.", :yellow if options[:generate_sample_entrypoint]
|
|
95
|
+
if gcp_config_name
|
|
96
|
+
say "NOTE: A gcloud configuration named '#{gcp_config_name}' was created/updated.", :yellow
|
|
97
|
+
say ' Activate it using:', :yellow
|
|
98
|
+
say " gcloud config configurations activate #{gcp_config_name}", :cyan
|
|
99
|
+
say ' Before running the deployment script.', :yellow
|
|
100
|
+
end
|
|
101
|
+
return unless options[:cloud] == 'gcp'
|
|
102
|
+
|
|
103
|
+
say "Review the generated files in #{deployment_dir} and the deployment guide:"
|
|
104
|
+
say " #{File.join(deployment_dir, 'README-GCP-DEPLOYMENT.md')}", :cyan
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def generate_dockerfiles(directory, main_entry_point, deployment_dir_basename)
|
|
110
|
+
# Main Dockerfile
|
|
111
|
+
main_dockerfile_path = File.join(directory, 'Dockerfile')
|
|
112
|
+
generate_dockerfile_content(main_dockerfile_path, main_entry_point, options[:base_image],
|
|
113
|
+
deployment_dir_basename)
|
|
114
|
+
say "Created main Dockerfile at #{main_dockerfile_path}", :cyan
|
|
115
|
+
|
|
116
|
+
# Agent Dockerfiles (if specified)
|
|
117
|
+
options[:agent_entry_points]&.each_with_index do |agent_entry, index|
|
|
118
|
+
agent_name = File.basename(agent_entry, '.rb').gsub(/[^0-9a-z_.-]/i, '_')
|
|
119
|
+
agent_dockerfile_path = File.join(directory, "Dockerfile.agent.#{agent_name}.#{index}")
|
|
120
|
+
generate_dockerfile_content(agent_dockerfile_path, agent_entry, options[:base_image], '')
|
|
121
|
+
say "Created agent Dockerfile for '#{agent_entry}' at #{agent_dockerfile_path}", :cyan
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def generate_dockerfile_content(path, entry_point, base_image, deployment_dir_basename)
|
|
126
|
+
# Basic validation for entry point format (crude check)
|
|
127
|
+
say "Warning: Entry point '#{entry_point}' does not look like a path. Ensure it's correct.", :yellow unless entry_point&.include?('/') || entry_point&.start_with?('bin/')
|
|
128
|
+
|
|
129
|
+
# Determine the path to config.ru relative to the build context (project root)
|
|
130
|
+
# config.ru is generated inside the deployment directory
|
|
131
|
+
config_ru_build_context_path = File.join(deployment_dir_basename, 'config.ru')
|
|
132
|
+
|
|
133
|
+
# Apply changes based on user provided diff
|
|
134
|
+
content = <<~DOCKERFILE
|
|
135
|
+
# syntax=docker/dockerfile:1
|
|
136
|
+
|
|
137
|
+
# Dockerfile generated by Legate CLI
|
|
138
|
+
ARG RUBY_VERSION=#{base_image.split(':').last || '3.2-slim'} # Extract tag if possible
|
|
139
|
+
FROM #{base_image}
|
|
140
|
+
|
|
141
|
+
WORKDIR /app
|
|
142
|
+
|
|
143
|
+
# Install essential dependencies
|
|
144
|
+
# You may need to add more depending on your Gemfile (e.g., libpq-dev for pg gem)
|
|
145
|
+
RUN apt-get update -qq && \
|
|
146
|
+
apt-get install -y --no-install-recommends \
|
|
147
|
+
build-essential \
|
|
148
|
+
git \
|
|
149
|
+
libcurl4 \
|
|
150
|
+
&& apt-get clean && \
|
|
151
|
+
rm -rf /var/lib/apt/lists/*
|
|
152
|
+
|
|
153
|
+
# Install Bundler
|
|
154
|
+
RUN gem install bundler --no-document
|
|
155
|
+
|
|
156
|
+
# Copy dependency definition files
|
|
157
|
+
COPY Gemfile Gemfile.lock ./
|
|
158
|
+
|
|
159
|
+
# Install Legate gem if present locally, then bundle install
|
|
160
|
+
COPY legate-*.gem ./
|
|
161
|
+
# Use wildcard and ignore errors if no gem file exists
|
|
162
|
+
RUN gem install legate-*.gem || echo "No local legate gem found, assuming it is in Gemfile."
|
|
163
|
+
RUN bundle config set without 'development test'
|
|
164
|
+
RUN bundle install --jobs $(nproc) --retry 3
|
|
165
|
+
|
|
166
|
+
# Copy the rest of the application code
|
|
167
|
+
# Ensure .dockerignore is properly configured
|
|
168
|
+
COPY . .
|
|
169
|
+
# Copy the generated config.ru from the deployment dir in the build context
|
|
170
|
+
COPY #{config_ru_build_context_path} ./
|
|
171
|
+
|
|
172
|
+
# --- Runtime Environment Variables ---
|
|
173
|
+
# Set sensible defaults, overrideable at runtime (e.g., via Cloud Run)
|
|
174
|
+
ENV RACK_ENV="production"
|
|
175
|
+
|
|
176
|
+
# Port (Required by Cloud Run)
|
|
177
|
+
ENV PORT="8080"
|
|
178
|
+
|
|
179
|
+
# Log Level
|
|
180
|
+
ENV LEGATE_LOG_LEVEL="INFO"
|
|
181
|
+
|
|
182
|
+
# Required by Legate for Gemini access, override with secret injection
|
|
183
|
+
ENV GOOGLE_API_KEY=""
|
|
184
|
+
|
|
185
|
+
# Expose the port the application listens on
|
|
186
|
+
EXPOSE ${PORT}
|
|
187
|
+
|
|
188
|
+
# --- Entry Point ---
|
|
189
|
+
# Runs the specified application or agent script using rackup
|
|
190
|
+
# Assumes a config.ru file exists in the root directory
|
|
191
|
+
# The config.ru should load the entrypoint script (e.g., bin/legate_web_entrypoint.rb)
|
|
192
|
+
# and run the defined Rack application (e.g., LegateWebApp).
|
|
193
|
+
CMD ["bundle", "exec", "rackup", "-p", "${PORT}", "-o", "0.0.0.0"]
|
|
194
|
+
DOCKERFILE
|
|
195
|
+
|
|
196
|
+
File.write(path, content)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def generate_dockerignore(directory)
|
|
200
|
+
dockerignore_path = File.join(directory, '.dockerignore')
|
|
201
|
+
# Avoid overwriting if it exists, maybe merge or warn later?
|
|
202
|
+
if File.exist?(dockerignore_path)
|
|
203
|
+
say "Skipping .dockerignore generation, file already exists: #{dockerignore_path}", :yellow
|
|
204
|
+
return
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
content = <<~IGNORE
|
|
208
|
+
# Dockerignore generated by Legate CLI
|
|
209
|
+
# Add files/directories here that are not needed in the final image
|
|
210
|
+
|
|
211
|
+
# Git files
|
|
212
|
+
.git
|
|
213
|
+
.gitignore
|
|
214
|
+
|
|
215
|
+
# Docker artifacts
|
|
216
|
+
.dockerignore
|
|
217
|
+
Dockerfile*
|
|
218
|
+
|
|
219
|
+
# Legate / Ruby specific
|
|
220
|
+
*.gem
|
|
221
|
+
.bundle/
|
|
222
|
+
vendor/bundle/
|
|
223
|
+
coverage/
|
|
224
|
+
spec/
|
|
225
|
+
tmp/
|
|
226
|
+
logs/
|
|
227
|
+
*.log
|
|
228
|
+
|
|
229
|
+
# Local config / secrets
|
|
230
|
+
.env*
|
|
231
|
+
|
|
232
|
+
# IDE / Editor specific
|
|
233
|
+
.vscode/
|
|
234
|
+
.idea/
|
|
235
|
+
.ruby-mine/
|
|
236
|
+
.project
|
|
237
|
+
*~ # Backup files
|
|
238
|
+
|
|
239
|
+
# OS specific
|
|
240
|
+
.DS_Store
|
|
241
|
+
Thumbs.db
|
|
242
|
+
|
|
243
|
+
# Deployment directory itself (if it's inside the project)
|
|
244
|
+
#{File.basename(directory)}/
|
|
245
|
+
|
|
246
|
+
IGNORE
|
|
247
|
+
|
|
248
|
+
File.write(dockerignore_path, content)
|
|
249
|
+
say "Created .dockerignore at #{dockerignore_path}", :cyan
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# --- Generate config.ru (Generic) ---
|
|
253
|
+
def generate_config_ru(directory, entry_point_script)
|
|
254
|
+
config_ru_path = File.join(directory, 'config.ru')
|
|
255
|
+
|
|
256
|
+
if File.exist?(config_ru_path)
|
|
257
|
+
say "Skipping config.ru generation, file already exists: #{config_ru_path}", :yellow
|
|
258
|
+
return
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Determine the relative path from config.ru (in deployment dir) to the entry_point
|
|
262
|
+
# This assumes entry_point_script is relative to the project root.
|
|
263
|
+
# We need the path *inside* the container (relative to /app)
|
|
264
|
+
relative_entry_point = entry_point_script # Use the path as provided (e.g., 'bin/legate_web_entrypoint.rb')
|
|
265
|
+
|
|
266
|
+
# Basic validation
|
|
267
|
+
unless relative_entry_point&.include?('/')
|
|
268
|
+
say "Warning: Entry point '#{relative_entry_point}' for config.ru doesn't look like a relative path. Ensure it's correct.",
|
|
269
|
+
:yellow
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
content = <<~RACKUP
|
|
273
|
+
# File: config.ru (Generated by Legate CLI)
|
|
274
|
+
# This file is used by 'rackup' to start the web application.
|
|
275
|
+
|
|
276
|
+
# Load the environment and application defined in the entrypoint script.
|
|
277
|
+
# Ensure the path is correct relative to the application root inside the container.
|
|
278
|
+
require_relative '#{relative_entry_point}'
|
|
279
|
+
|
|
280
|
+
# Tell rackup which Rack application class to run.
|
|
281
|
+
# This should match the class name defined in your entrypoint script (e.g., LegateWebApp).
|
|
282
|
+
run LegateWebApp
|
|
283
|
+
|
|
284
|
+
RACKUP
|
|
285
|
+
|
|
286
|
+
File.write(config_ru_path, content)
|
|
287
|
+
say "Created config.ru at #{config_ru_path}", :cyan
|
|
288
|
+
say "Ensure the entrypoint path in config.ru ('#{relative_entry_point}') is correct for your project structure.",
|
|
289
|
+
:yellow
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# --- Sample Entrypoint Generation (Optional, generic) ---
|
|
293
|
+
def generate_sample_entrypoint_script(sample_path)
|
|
294
|
+
sample_path = File.expand_path(sample_path) # Ensure absolute path
|
|
295
|
+
sample_dir = File.dirname(sample_path)
|
|
296
|
+
|
|
297
|
+
unless Dir.exist?(sample_dir)
|
|
298
|
+
say "Creating directory: #{sample_dir}", :green
|
|
299
|
+
FileUtils.mkdir_p(sample_dir)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
if File.exist?(sample_path)
|
|
303
|
+
say "Sample entrypoint already exists, skipping: #{sample_path}", :yellow
|
|
304
|
+
return
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
say "Generating sample entrypoint script at #{sample_path}", :cyan
|
|
308
|
+
|
|
309
|
+
content = <<-'RUBYCONTENT'
|
|
310
|
+
#!/usr/bin/env ruby
|
|
311
|
+
# frozen_string_literal: true
|
|
312
|
+
|
|
313
|
+
# --- Generated Sample Legate Web Entrypoint ---
|
|
314
|
+
# This script provides a basic starting point for running Legate with a web server
|
|
315
|
+
# and includes a /healthz endpoint suitable for Cloud Run health checks.
|
|
316
|
+
|
|
317
|
+
require 'sinatra/base'
|
|
318
|
+
require 'sinatra/json'
|
|
319
|
+
require 'legate'
|
|
320
|
+
require 'legate/agent'
|
|
321
|
+
require 'legate/session_service/base'
|
|
322
|
+
require 'legate/tools/echo'
|
|
323
|
+
|
|
324
|
+
# --- Configuration ---
|
|
325
|
+
# Legate components will often rely on environment variables for configuration
|
|
326
|
+
# (e.g., GOOGLE_API_KEY, PORT).
|
|
327
|
+
# Ensure these are set correctly in your deployment environment (e.g., Cloud Run).
|
|
328
|
+
|
|
329
|
+
# Configure Legate settings if needed
|
|
330
|
+
# Example: Set the default model
|
|
331
|
+
# config.default_model_name = 'gemini-1.5-pro'
|
|
332
|
+
|
|
333
|
+
# Example: Configure webhooks if you plan to use them
|
|
334
|
+
# config.webhooks.listener_enabled = true
|
|
335
|
+
# config.webhooks.listen_address = '0.0.0.0' # Important for Cloud Run
|
|
336
|
+
# config.webhooks.listen_port = ENV.fetch('PORT', 8080).to_i
|
|
337
|
+
# config.webhooks.base_path = '/webhooks'
|
|
338
|
+
# config.webhooks.global_secret = ENV['WEBHOOK_SECRET'] # Load from env
|
|
339
|
+
|
|
340
|
+
Legate.configure do |config|
|
|
341
|
+
# Configure Legate settings if needed
|
|
342
|
+
# Example: Set the default model
|
|
343
|
+
# config.default_model_name = 'gemini-1.5-pro'
|
|
344
|
+
|
|
345
|
+
# Example: Configure webhooks if you plan to use them
|
|
346
|
+
# config.webhooks.listener_enabled = true
|
|
347
|
+
# config.webhooks.listen_address = '0.0.0.0' # Important for Cloud Run
|
|
348
|
+
# config.webhooks.listen_port = ENV.fetch('PORT', 8080).to_i
|
|
349
|
+
# config.webhooks.base_path = '/webhooks'
|
|
350
|
+
# config.webhooks.global_secret = ENV['WEBHOOK_SECRET'] # Load from env
|
|
351
|
+
|
|
352
|
+
# Session service uses in-memory storage
|
|
353
|
+
config.session_service = Legate::SessionService::InMemory.new
|
|
354
|
+
|
|
355
|
+
# --- IMPORTANT ---
|
|
356
|
+
# The Legate framework initializes its own logger.
|
|
357
|
+
# You generally don't need to set it here unless you have specific needs.
|
|
358
|
+
# If you DO need to customize logging, refer to the Legate documentation.
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
Legate.logger.info("Sample Legate Web Entrypoint environment configured.")
|
|
362
|
+
|
|
363
|
+
# --- Legate Agent/Application Logic Integration ---
|
|
364
|
+
# You might load agent definitions or start background tasks here.
|
|
365
|
+
# Example:
|
|
366
|
+
# Dir[File.expand_path('../../../app/agents/**/*.rb', __FILE__)].each { |file| require file }
|
|
367
|
+
# puts "INFO: Loaded agent definitions."
|
|
368
|
+
|
|
369
|
+
# --- Define Rack Application(s) ---
|
|
370
|
+
# Define your main application logic within a Rack-compatible class (like Sinatra).
|
|
371
|
+
# The actual server (Puma, Unicorn, etc.) will be started via rackup/config.ru
|
|
372
|
+
# based on the Dockerfile\'s CMD.
|
|
373
|
+
|
|
374
|
+
class LegateWebApp < Sinatra::Base
|
|
375
|
+
configure do
|
|
376
|
+
# Use the central Legate logger
|
|
377
|
+
set :logger, Legate.logger
|
|
378
|
+
# You might want to disable Sinatra\'s default logging if it\'s noisy
|
|
379
|
+
# disable :logging
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# --- Health Check Endpoint ---
|
|
383
|
+
# Cloud Run uses this to check if the container is ready to serve requests.
|
|
384
|
+
get '/healthz' do
|
|
385
|
+
# Check essential dependencies (e.g., database connection, Legate services)
|
|
386
|
+
# Return 503 if not ready.
|
|
387
|
+
begin
|
|
388
|
+
# Example: Check Legate session service (adjust based on your config)
|
|
389
|
+
# raise "Session service not available" unless Legate.config.session_service&.check_connection
|
|
390
|
+
status 200
|
|
391
|
+
headers 'Content-Type' => 'text/plain'
|
|
392
|
+
body 'OK'
|
|
393
|
+
rescue => e
|
|
394
|
+
logger.error("Health check failed: #{e.message}")
|
|
395
|
+
status 503
|
|
396
|
+
headers 'Content-Type' => 'text/plain'
|
|
397
|
+
body "Service Unavailable: #{e.message}"
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# --- Echo Agent Endpoint ---
|
|
402
|
+
post '/echo' do
|
|
403
|
+
content_type :json
|
|
404
|
+
|
|
405
|
+
begin
|
|
406
|
+
# 1. Get input from request body (expecting JSON: { "message": "..." })
|
|
407
|
+
request.body.rewind
|
|
408
|
+
request_payload = JSON.parse(request.body.read)
|
|
409
|
+
user_message = request_payload['message']
|
|
410
|
+
|
|
411
|
+
unless user_message
|
|
412
|
+
halt 400, json({ status: :error, error_message: "Missing 'message' key in JSON request body." })
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# 2. Get configured session service
|
|
416
|
+
session_service = Legate.config.session_service
|
|
417
|
+
unless session_service
|
|
418
|
+
logger.error("/echo: Legate session service is not configured!")
|
|
419
|
+
halt 500, json({ status: :error, error_message: "Internal Server Error: Session service not configured." })
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# 3. Instantiate an ephemeral Echo agent
|
|
423
|
+
# Create an ephemeral definition for the echo agent.
|
|
424
|
+
echo_agent_definition = Legate::AgentDefinition.new
|
|
425
|
+
echo_agent_definition.define do |def_proxy|
|
|
426
|
+
def_proxy.name :ephemeral_echo_sample # Ensure a unique name if needed, or just :ephemeral_echo
|
|
427
|
+
def_proxy.description 'Temporary Echo Agent for sample endpoint'
|
|
428
|
+
def_proxy.instruction 'You are an echo agent. You will use the echo tool to repeat the input.'
|
|
429
|
+
def_proxy.use_tool :echo # Assumes EchoTool is globally registered
|
|
430
|
+
# Model, fallback_mode, etc., will use defaults from AgentDefinition
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Legate::GlobalToolManager.register(Legate::Tools::Echo) unless Legate::GlobalToolManager.find_class(:echo)
|
|
434
|
+
# Ensure EchoTool is globally available if not already (though Legate typically handles this for built-ins)
|
|
435
|
+
|
|
436
|
+
echo_agent = Legate::Agent.new(definition: echo_agent_definition)
|
|
437
|
+
# The agent will use the session_service from Legate.config for its internal @session_service,
|
|
438
|
+
# but run_task below will use the specific session_service instance.
|
|
439
|
+
|
|
440
|
+
# 4. Create a temporary session for this request
|
|
441
|
+
temp_session = session_service.create_session(app_name: :echo_service, user_id: "web_#{SecureRandom.hex(4)}")
|
|
442
|
+
session_id = temp_session.id
|
|
443
|
+
|
|
444
|
+
logger.info("/echo: Running echo task in session #{session_id} for message: \"#{user_message}\"")
|
|
445
|
+
|
|
446
|
+
# 5. Run the task
|
|
447
|
+
# The Echo tool doesn't require planning, agent.run_task handles it.
|
|
448
|
+
final_event_or_error = echo_agent.run_task(
|
|
449
|
+
session_id: session_id,
|
|
450
|
+
user_input: user_message, # The agent/planner uses this
|
|
451
|
+
session_service: session_service
|
|
452
|
+
# We don't *need* to explicitly tell it to use the echo tool;
|
|
453
|
+
# the agent should figure it out or the Echo tool might be a fallback.
|
|
454
|
+
# If direct tool execution was needed: agent.execute_tool(:echo, {message: user_message}, session_id, session_service)
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# 6. Process the result
|
|
458
|
+
if final_event_or_error.is_a?(Legate::Event)
|
|
459
|
+
result_content = final_event_or_error.content
|
|
460
|
+
# Successfully echoed
|
|
461
|
+
json({ status: :success, echoed_message: result_content[:result] })
|
|
462
|
+
elsif final_event_or_error.is_a?(Hash) && final_event_or_error[:status] == :error
|
|
463
|
+
# Handle errors reported by run_task
|
|
464
|
+
logger.error("/echo: Agent execution failed: #{final_event_or_error[:error_message]}")
|
|
465
|
+
status 500
|
|
466
|
+
json(final_event_or_error) # Return the error hash
|
|
467
|
+
else
|
|
468
|
+
# Unexpected result
|
|
469
|
+
logger.error("/echo: Unexpected result from agent execution: #{final_event_or_error.inspect}")
|
|
470
|
+
halt 500, json({ status: :error, error_message: "Internal Server Error: Unexpected agent result." })
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
rescue JSON::ParserError => e
|
|
474
|
+
logger.error("/echo: Invalid JSON input: #{e.message}")
|
|
475
|
+
halt 400, json({ status: :error, error_message: "Invalid JSON format: #{e.message}" })
|
|
476
|
+
rescue => e
|
|
477
|
+
logger.error("/echo: Unhandled error: #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}")
|
|
478
|
+
halt 500, json({ status: :error, error_message: "Internal Server Error: #{e.message}" })
|
|
479
|
+
ensure
|
|
480
|
+
# Clean up temporary session if created
|
|
481
|
+
session_service.delete_session(session_id: session_id) if session_service && session_id
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# --- Add Your Application Routes Here ---
|
|
486
|
+
# Example:
|
|
487
|
+
# get \'/\' do
|
|
488
|
+
# \'Hello from Legate Web App!\'
|
|
489
|
+
# end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# --- NOTE ---
|
|
493
|
+
# This script NO LONGER starts the web server directly.
|
|
494
|
+
# The Docker container\'s CMD should use \'rackup\' (referencing config.ru)
|
|
495
|
+
# to start a web server (like Puma) which will load this environment
|
|
496
|
+
# and run the LegateWebApp.
|
|
497
|
+
|
|
498
|
+
# Example config.ru content:
|
|
499
|
+
# require_relative \'./bin/legate_web_entrypoint\' # Load this script\'s environment
|
|
500
|
+
# run LegateWebApp # Tell rackup to run your Sinatra app
|
|
501
|
+
|
|
502
|
+
RUBYCONTENT
|
|
503
|
+
|
|
504
|
+
File.write(sample_path, content)
|
|
505
|
+
# Make the script executable
|
|
506
|
+
FileUtils.chmod(0o755, sample_path)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# --- GCP Asset Generation (Only called if --cloud gcp) ---
|
|
510
|
+
def generate_gcp_assets(directory)
|
|
511
|
+
say 'Generating GCP specific assets...', :magenta
|
|
512
|
+
gcp_config_name = nil # Initialize
|
|
513
|
+
|
|
514
|
+
# Validate required GCP options
|
|
515
|
+
project_id = options[:gcp_project_id]
|
|
516
|
+
|
|
517
|
+
unless project_id
|
|
518
|
+
# Project ID is missing, generate a suggestion and exit
|
|
519
|
+
random_hex = SecureRandom.hex(3) # Generate 6 hex characters
|
|
520
|
+
# Ensure name is valid for project ID (lowercase, digits, hyphens, 6-30 chars)
|
|
521
|
+
sanitized_base_name = options[:name].downcase.gsub(/[^a-z0-9-]/, '-').gsub(/^-+|-+$/, '')
|
|
522
|
+
suggested_project_id = "legate-deploy-#{sanitized_base_name}-#{random_hex}".slice(0, 30).gsub(/-+$/, '') # Ensure max 30 chars, no trailing hyphen
|
|
523
|
+
# Ensure it starts with a letter (though our pattern should ensure this)
|
|
524
|
+
suggested_project_id = "a#{suggested_project_id}" unless suggested_project_id[/^[a-z]/]
|
|
525
|
+
suggested_project_id = suggested_project_id.slice(0, 30) # Re-slice if prepended 'a' pushed length
|
|
526
|
+
say 'Error: --gcp-project-id is required for GCP deployment.', :red
|
|
527
|
+
say 'You must provide an existing GCP project ID where you have appropriate permissions.', :yellow
|
|
528
|
+
say 'If you need to create a new project first, you could use a command like this ', :yellow
|
|
529
|
+
say 'After ensuring billing is configured for your account):', :yellow
|
|
530
|
+
say " gcloud projects create #{suggested_project_id}", :cyan
|
|
531
|
+
say 'Then, re-run this command adding the flag:', :yellow
|
|
532
|
+
say " --gcp-project-id #{suggested_project_id}", :cyan
|
|
533
|
+
exit 1 # Stop execution, user needs to provide a valid project ID
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# --- Project ID is present, proceed ---
|
|
537
|
+
region = options[:gcp_region] # Use the class_option value
|
|
538
|
+
|
|
539
|
+
# 1. Attempt to create gcloud configuration
|
|
540
|
+
# gcp_config_name = create_gcloud_config(options[:name], project_id, region)
|
|
541
|
+
|
|
542
|
+
# 2. Generate GCP specific config files (optional for now, script preferred)
|
|
543
|
+
# generate_gcp_cloud_run_config(directory)
|
|
544
|
+
|
|
545
|
+
# 3. Generate GCP deploy script
|
|
546
|
+
generate_gcp_deploy_script(directory)
|
|
547
|
+
|
|
548
|
+
# 4. Generate Cloud Build Config
|
|
549
|
+
generate_gcp_cloudbuild_yaml(directory)
|
|
550
|
+
|
|
551
|
+
# 5. Generate/Copy GCP docs
|
|
552
|
+
generate_gcp_deployment_docs(directory)
|
|
553
|
+
|
|
554
|
+
gcp_config_name # Return the generated name for the final message
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# Helper to execute shell commands and check status
|
|
558
|
+
def run_gcloud_command(command, error_message)
|
|
559
|
+
say "Executing: gcloud #{command}"
|
|
560
|
+
output = `gcloud #{command} 2>&1` # Capture stderr too
|
|
561
|
+
unless $?.success?
|
|
562
|
+
say "Error: #{error_message}", :red
|
|
563
|
+
say "gcloud output:\n#{output}", :red
|
|
564
|
+
# Decide if we should exit or just warn
|
|
565
|
+
# For config commands, maybe warn and continue?
|
|
566
|
+
# For critical commands in deploy script, exit is better.
|
|
567
|
+
# Let's warn for config issues but allow script generation.
|
|
568
|
+
say 'Warning: Failed to automatically configure gcloud. Please ensure configuration is correct manually.',
|
|
569
|
+
:yellow
|
|
570
|
+
return false # Indicate failure
|
|
571
|
+
end
|
|
572
|
+
true # Indicate success
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def create_gcloud_config(base_name, project_id, region)
|
|
576
|
+
# Sanitize base_name for config name
|
|
577
|
+
config_name = "legate-deploy-#{base_name.gsub(/[^0-9a-zA-Z_-]/, '-')}"
|
|
578
|
+
say "Attempting to create/update gcloud configuration: #{config_name}"
|
|
579
|
+
|
|
580
|
+
# Check if gcloud command exists first
|
|
581
|
+
unless system('command -v gcloud > /dev/null 2>&1')
|
|
582
|
+
say "Error: 'gcloud' command not found in PATH. Cannot create gcloud configuration.", :red
|
|
583
|
+
say 'Please install the Google Cloud SDK.', :yellow
|
|
584
|
+
return nil # Cannot proceed
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# 1. Create or check configuration
|
|
588
|
+
# Use describe to check existence non-destructively
|
|
589
|
+
`gcloud config configurations describe #{config_name} > /dev/null 2>&1`
|
|
590
|
+
if $?.success?
|
|
591
|
+
say "Configuration '#{config_name}' already exists. Settings will be updated.", :yellow
|
|
592
|
+
else
|
|
593
|
+
# Try to create (use --no-activate)
|
|
594
|
+
unless run_gcloud_command("config configurations create #{config_name} --no-activate",
|
|
595
|
+
"Failed to create gcloud configuration '#{config_name}'.")
|
|
596
|
+
return nil # Failed, can't set properties
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
say "Created gcloud configuration: #{config_name}"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# 2. Set properties
|
|
603
|
+
run_gcloud_command("config set project #{project_id} --configuration=#{config_name}",
|
|
604
|
+
'Failed to set project in gcloud config.')
|
|
605
|
+
run_gcloud_command("config set compute/region #{region} --configuration=#{config_name}",
|
|
606
|
+
'Failed to set region in gcloud config.')
|
|
607
|
+
# Add other relevant defaults? e.g., run/region?
|
|
608
|
+
# run_gcloud_command("config set run/region #{region} --configuration=#{config_name}", "Failed to set run/region in gcloud config.")
|
|
609
|
+
|
|
610
|
+
config_name # Return the name used
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# --- GCP Specific Helper Methods ---
|
|
614
|
+
def generate_gcp_cloud_run_config(_directory)
|
|
615
|
+
# NOTE: Generating a static YAML is less flexible than the deploy script.
|
|
616
|
+
# The script can dynamically fetch Redis IP etc. Keeping this commented out
|
|
617
|
+
# as generating the script is generally preferred.
|
|
618
|
+
say 'Skipping generation of static cloud-run-service.yaml, deploy script is preferred.', :yellow
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def generate_gcp_deploy_script(directory)
|
|
622
|
+
deploy_script_path = File.join(directory, 'deploy-gcp.sh')
|
|
623
|
+
|
|
624
|
+
# Extract GCP options with defaults from class_options
|
|
625
|
+
# Escape inputs for Bash safety
|
|
626
|
+
project_id = Shellwords.escape(options[:gcp_project_id])
|
|
627
|
+
region = Shellwords.escape(options[:gcp_region])
|
|
628
|
+
main_service_name = Shellwords.escape(options[:gcp_service_name])
|
|
629
|
+
main_memory = Shellwords.escape(options[:gcp_memory])
|
|
630
|
+
main_cpu = Shellwords.escape(options[:gcp_cpu])
|
|
631
|
+
base_name = options[:name] # Used for image naming
|
|
632
|
+
deployment_dir_basename = File.basename(directory) # Get basename
|
|
633
|
+
|
|
634
|
+
# Image names
|
|
635
|
+
main_image_name = Shellwords.escape("#{base_name}-web")
|
|
636
|
+
main_image_tag = Shellwords.escape('latest')
|
|
637
|
+
|
|
638
|
+
# Artifact Registry setup
|
|
639
|
+
ar_location = region # Already escaped
|
|
640
|
+
ar_repo_name = Shellwords.escape('legate-images')
|
|
641
|
+
|
|
642
|
+
# Cloud Build config file path (relative to project root)
|
|
643
|
+
cloudbuild_config_file = Shellwords.escape(File.join(deployment_dir_basename, 'cloudbuild.yaml'))
|
|
644
|
+
# Dockerfile path relative to deployment dir
|
|
645
|
+
main_dockerfile_path_relative = Shellwords.escape('Dockerfile')
|
|
646
|
+
|
|
647
|
+
# --- Script Content ---
|
|
648
|
+
# This script is more comprehensive than before, includes setup
|
|
649
|
+
script_content = <<~BASH
|
|
650
|
+
#!/bin/bash
|
|
651
|
+
# Generated by Legate CLI for GCP Cloud Run deployment
|
|
652
|
+
set -euo pipefail # Enable strict mode
|
|
653
|
+
|
|
654
|
+
# --- Configuration (Edit these if needed) ---
|
|
655
|
+
PROJECT_ID=#{project_id}
|
|
656
|
+
REGION=#{region}
|
|
657
|
+
MAIN_SERVICE_NAME=#{main_service_name}
|
|
658
|
+
MAIN_DOCKERFILE=#{main_dockerfile_path_relative} # Relative path within deployment dir
|
|
659
|
+
MAIN_IMAGE_NAME=#{main_image_name}
|
|
660
|
+
MAIN_IMAGE_TAG=#{main_image_tag}
|
|
661
|
+
MAIN_MEMORY=#{main_memory}
|
|
662
|
+
MAIN_CPU=#{main_cpu}
|
|
663
|
+
|
|
664
|
+
# Secrets Configuration
|
|
665
|
+
SECRET_NAME="google-api-key" # Name of the secret in Secret Manager
|
|
666
|
+
SECRET_ENV_VAR="GOOGLE_API_KEY" # Env var name in Cloud Run
|
|
667
|
+
|
|
668
|
+
# Artifact Registry Repository
|
|
669
|
+
AR_REPO_NAME=#{ar_repo_name} # Artifact Registry repo name
|
|
670
|
+
AR_LOCATION=#{ar_location} # Often same as REGION, but can differ
|
|
671
|
+
|
|
672
|
+
# VPC Access Connector (optional, for private networking)
|
|
673
|
+
CONNECTOR_NAME="legate-vpc-connector" # Name for the VPC Access Connector
|
|
674
|
+
# Important: Ensure this range does not overlap with other subnets!
|
|
675
|
+
CONNECTOR_IP_RANGE="10.8.0.0/28"
|
|
676
|
+
VPC_NETWORK_NAME="default" # Use 'default' or your specific VPC network
|
|
677
|
+
|
|
678
|
+
# Service Account for Cloud Run (Recommended: Create a dedicated one)
|
|
679
|
+
# Leave empty to use the default Compute Engine service account (less secure)
|
|
680
|
+
RUN_SERVICE_ACCOUNT=""
|
|
681
|
+
# Example: RUN_SERVICE_ACCOUNT="legate-runner@${PROJECT_ID}.iam.gserviceaccount.com"
|
|
682
|
+
|
|
683
|
+
# --- Helper Functions ---
|
|
684
|
+
info() {
|
|
685
|
+
echo "[INFO] $1"
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
error() {
|
|
689
|
+
echo "[ERROR] $1 >&2
|
|
690
|
+
exit 1
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
check_command() {
|
|
694
|
+
command -v "$1" >/dev/null 2>&1 || error "$1' command not found. Please install it."
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
# --- Prerequisites Check ---
|
|
698
|
+
info "Checking prerequisites..."
|
|
699
|
+
check_command gcloud
|
|
700
|
+
#check_command docker # Often not needed if using Cloud Build
|
|
701
|
+
|
|
702
|
+
# --- Set GCP Project ---
|
|
703
|
+
info "Setting GCP project to ${PROJECT_ID}"
|
|
704
|
+
gcloud config set project "${PROJECT_ID}"
|
|
705
|
+
|
|
706
|
+
# --- Enable Required APIs ---
|
|
707
|
+
info "Enabling necessary GCP APIs..."
|
|
708
|
+
gcloud services enable \
|
|
709
|
+
run.googleapis.com \
|
|
710
|
+
artifactregistry.googleapis.com \
|
|
711
|
+
secretmanager.googleapis.com \
|
|
712
|
+
cloudbuild.googleapis.com \
|
|
713
|
+
vpcaccess.googleapis.com \
|
|
714
|
+
compute.googleapis.com || error "Failed to enable APIs"
|
|
715
|
+
|
|
716
|
+
# --- Configure Docker for Artifact Registry ---
|
|
717
|
+
#info "Configuring Docker authentication for ${AR_LOCATION}..."
|
|
718
|
+
#gcloud auth configure-docker "${AR_LOCATION}-docker.pkg.dev" || error "Docker auth configuration failed"
|
|
719
|
+
|
|
720
|
+
# --- Create Artifact Registry Repository (if it doesn't exist) ---
|
|
721
|
+
info "Ensuring Artifact Registry repository '${AR_REPO_NAME}' exists in ${AR_LOCATION}..."
|
|
722
|
+
if ! gcloud artifacts repositories describe "${AR_REPO_NAME}" --location="${AR_LOCATION}" --project="${PROJECT_ID}" &>/dev/null; then
|
|
723
|
+
info "Creating Artifact Registry repository '${AR_REPO_NAME}'..."
|
|
724
|
+
gcloud artifacts repositories create "${AR_REPO_NAME}" \
|
|
725
|
+
--repository-format=docker \
|
|
726
|
+
--location="${AR_LOCATION}" \
|
|
727
|
+
--description="Legate Application Images" \
|
|
728
|
+
--project="${PROJECT_ID}" || error "Failed to create Artifact Registry repository"
|
|
729
|
+
else
|
|
730
|
+
info "Artifact Registry repository '${AR_REPO_NAME}' already exists."
|
|
731
|
+
fi
|
|
732
|
+
MAIN_IMAGE_URI="${AR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${MAIN_IMAGE_NAME}:${MAIN_IMAGE_TAG}"
|
|
733
|
+
|
|
734
|
+
# --- Create VPC Access Connector (if it doesn't exist) ---
|
|
735
|
+
info "Ensuring Serverless VPC Access connector '${CONNECTOR_NAME}' exists in ${REGION}..."
|
|
736
|
+
if ! gcloud compute networks vpc-access connectors describe "${CONNECTOR_NAME}" --region="${REGION}" --project="${PROJECT_ID}" &>/dev/null; then
|
|
737
|
+
info "Creating Serverless VPC Access connector '${CONNECTOR_NAME}'..."
|
|
738
|
+
gcloud compute networks vpc-access connectors create "${CONNECTOR_NAME}" \
|
|
739
|
+
--region="${REGION}" \
|
|
740
|
+
--range="${CONNECTOR_IP_RANGE}" \
|
|
741
|
+
--network="${VPC_NETWORK_NAME}" \
|
|
742
|
+
--project="${PROJECT_ID}" || error "Failed to create VPC Access connector"
|
|
743
|
+
else
|
|
744
|
+
info "Serverless VPC Access connector '${CONNECTOR_NAME}' already exists."
|
|
745
|
+
fi
|
|
746
|
+
VPC_CONNECTOR_FULL_NAME="projects/${PROJECT_ID}/locations/${REGION}/connectors/${CONNECTOR_NAME}"
|
|
747
|
+
|
|
748
|
+
# --- Create Secret for API Key (if it doesn't exist) ---
|
|
749
|
+
info "Ensuring Secret Manager secret '${SECRET_NAME}' exists..."
|
|
750
|
+
if ! gcloud secrets describe "${SECRET_NAME}" --project="${PROJECT_ID}" &>/dev/null; then
|
|
751
|
+
info "Secret '${SECRET_NAME}' not found. Please create it manually or enter the value now."
|
|
752
|
+
read -sp "Enter value for ${SECRET_NAME}: " SECRET_VALUE
|
|
753
|
+
echo # Newline after password prompt
|
|
754
|
+
if [[ -z "$SECRET_VALUE" ]]; then
|
|
755
|
+
error "Secret value cannot be empty."
|
|
756
|
+
fi
|
|
757
|
+
echo -n "$SECRET_VALUE" | gcloud secrets create "${SECRET_NAME}" --data-file=- \
|
|
758
|
+
--replication-policy="automatic" \
|
|
759
|
+
--project="${PROJECT_ID}" || error "Failed to create secret '${SECRET_NAME}'"
|
|
760
|
+
# Grant default compute service account access (adjust if using dedicated SA)
|
|
761
|
+
info "Granting default compute SA access to secret..."
|
|
762
|
+
PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format='value(projectNumber)')
|
|
763
|
+
DEFAULT_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
|
|
764
|
+
gcloud secrets add-iam-policy-binding ${SECRET_NAME} \
|
|
765
|
+
--member="serviceAccount:${DEFAULT_SA}" \
|
|
766
|
+
--role="roles/secretmanager.secretAccessor" \
|
|
767
|
+
--project="${PROJECT_ID}" || echo "Warning: Failed to grant default SA access to secret. Ensure the running service account has access."
|
|
768
|
+
else
|
|
769
|
+
info "Secret '${SECRET_NAME}' already exists."
|
|
770
|
+
fi
|
|
771
|
+
# Use name:version format for Cloud Run secret injection
|
|
772
|
+
SECRET_RESOURCE_FOR_RUN="${SECRET_NAME}:latest"
|
|
773
|
+
|
|
774
|
+
# --- Grant Service Account Access to Secret ---
|
|
775
|
+
info "Ensuring Cloud Run service account can access secret '${SECRET_NAME}'..."
|
|
776
|
+
TARGET_SERVICE_ACCOUNT="${RUN_SERVICE_ACCOUNT}"
|
|
777
|
+
if [[ -z "${TARGET_SERVICE_ACCOUNT}" ]]; then
|
|
778
|
+
info "Using default Compute Engine service account."
|
|
779
|
+
PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format='value(projectNumber)') || error "Failed to get project number."
|
|
780
|
+
TARGET_SERVICE_ACCOUNT="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
|
|
781
|
+
else
|
|
782
|
+
info "Using specified service account: ${TARGET_SERVICE_ACCOUNT}"
|
|
783
|
+
fi
|
|
784
|
+
|
|
785
|
+
# Attempt to grant the role. Might fail if runner lacks permissions.
|
|
786
|
+
gcloud secrets add-iam-policy-binding "${SECRET_NAME}" \
|
|
787
|
+
--member="serviceAccount:${TARGET_SERVICE_ACCOUNT}" \
|
|
788
|
+
--role="roles/secretmanager.secretAccessor" \
|
|
789
|
+
--project="${PROJECT_ID}" \
|
|
790
|
+
--condition=None \
|
|
791
|
+
>/dev/null || echo "[WARNING] Failed to automatically grant Secret Accessor role to ${TARGET_SERVICE_ACCOUNT}. Please ensure it has permission manually."
|
|
792
|
+
|
|
793
|
+
# --- Build and Push Main Docker Image ---
|
|
794
|
+
info "Building main application image: ${MAIN_IMAGE_URI}..."
|
|
795
|
+
# Using Cloud Build with an explicit config file
|
|
796
|
+
CONFIG_FILE=#{cloudbuild_config_file}
|
|
797
|
+
gcloud builds submit --config "${CONFIG_FILE}" --project="${PROJECT_ID}" --substitutions=_IMAGE_URI="${MAIN_IMAGE_URI}" .
|
|
798
|
+
if [[ $? -ne 0 ]]; then
|
|
799
|
+
error "Failed to build main image using ${CONFIG_FILE}"
|
|
800
|
+
fi
|
|
801
|
+
# Alternatively, build locally (Requires Docker):
|
|
802
|
+
# info "Configuring Docker authentication for ${AR_LOCATION}..."
|
|
803
|
+
# gcloud auth configure-docker "${AR_LOCATION}-docker.pkg.dev" || error "Docker auth configuration failed"
|
|
804
|
+
# docker build -t "${MAIN_IMAGE_URI}" -f "#{File.join(deployment_dir_basename, main_dockerfile_path_relative)}" . || error "Failed to build main image locally"
|
|
805
|
+
# docker push "${MAIN_IMAGE_URI}" || error "Failed to push main image"
|
|
806
|
+
|
|
807
|
+
# --- Build and Push Agent Docker Images (if configured) ---
|
|
808
|
+
# <<< Add logic here to loop through agent entry points, build and push their images >>>
|
|
809
|
+
# Example for one agent:
|
|
810
|
+
# AGENT_SERVICE_NAME="legate-agent-processor"
|
|
811
|
+
# AGENT_DOCKERFILE="Dockerfile.agent.processor"
|
|
812
|
+
# AGENT_IMAGE_NAME="legate-agent-processor"
|
|
813
|
+
# AGENT_IMAGE_URI="${AR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${AGENT_IMAGE_NAME}:${MAIN_IMAGE_TAG}"
|
|
814
|
+
# AGENT_CLOUDBUILD_CONFIG="path/to/agent/cloudbuild.yaml" # Example path
|
|
815
|
+
# info "Building agent image: ${AGENT_IMAGE_URI}..."
|
|
816
|
+
# gcloud builds submit --config "${AGENT_CLOUDBUILD_CONFIG}" --project="${PROJECT_ID}" --substitutions=_IMAGE_URI="${AGENT_IMAGE_URI}" . || error "Failed to build agent image"
|
|
817
|
+
|
|
818
|
+
# --- Deploy Main Service to Cloud Run ---
|
|
819
|
+
info "Deploying main service '${MAIN_SERVICE_NAME}' to Cloud Run in ${REGION}..."
|
|
820
|
+
|
|
821
|
+
# Base command arguments
|
|
822
|
+
CMD_ARGS=(
|
|
823
|
+
gcloud run deploy "${MAIN_SERVICE_NAME}"
|
|
824
|
+
--project="${PROJECT_ID}"
|
|
825
|
+
--region="${REGION}"
|
|
826
|
+
--image="${MAIN_IMAGE_URI}"
|
|
827
|
+
--platform="managed"
|
|
828
|
+
--memory="${MAIN_MEMORY}"
|
|
829
|
+
--cpu="${MAIN_CPU}"
|
|
830
|
+
--port="8080"
|
|
831
|
+
--set-env-vars="RACK_ENV=production"
|
|
832
|
+
# Use name:version format for secrets
|
|
833
|
+
--set-secrets="${SECRET_ENV_VAR}=${SECRET_RESOURCE_FOR_RUN}"
|
|
834
|
+
--vpc-connector="${VPC_CONNECTOR_FULL_NAME}"
|
|
835
|
+
--vpc-egress="all-traffic"
|
|
836
|
+
--allow-unauthenticated # Remove if service should not be public
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
# Conditionally add service account
|
|
840
|
+
if [[ -n "${RUN_SERVICE_ACCOUNT}" ]]; then
|
|
841
|
+
info "Using service account: ${RUN_SERVICE_ACCOUNT}"
|
|
842
|
+
# Ensure this SA has roles/secretmanager.secretAccessor for the secret!
|
|
843
|
+
CMD_ARGS+=(--service-account="${RUN_SERVICE_ACCOUNT}")
|
|
844
|
+
else
|
|
845
|
+
info "Using default Compute Engine service account. Ensure it has Secret Accessor role."
|
|
846
|
+
fi
|
|
847
|
+
|
|
848
|
+
# Debug: Print the command arguments
|
|
849
|
+
echo "DEBUG: Executing: ${CMD_ARGS[@]}"
|
|
850
|
+
|
|
851
|
+
# Execute the command
|
|
852
|
+
"${CMD_ARGS[@]}"
|
|
853
|
+
if [[ $? -ne 0 ]]; then
|
|
854
|
+
error "Failed to deploy main service '${MAIN_SERVICE_NAME}'"
|
|
855
|
+
fi
|
|
856
|
+
|
|
857
|
+
# --- Deploy Agent Services to Cloud Run (if configured) ---
|
|
858
|
+
# <<< Add logic here to loop through agents and deploy them >>>
|
|
859
|
+
# Example for one agent:
|
|
860
|
+
# info "Deploying agent service '${AGENT_SERVICE_NAME}'..."
|
|
861
|
+
# AGENT_DEPLOY_ARGS=( ...) # Construct agent deployment args similarly
|
|
862
|
+
# gcloud run deploy "${AGENT_DEPLOY_ARGS[@]}" || error "Failed to deploy agent service '${AGENT_SERVICE_NAME}'"
|
|
863
|
+
|
|
864
|
+
# --- Deployment Complete ---
|
|
865
|
+
info "Deployment successful!"
|
|
866
|
+
MAIN_SERVICE_URL=$(gcloud run services describe "${MAIN_SERVICE_NAME}" --region="${REGION}" --project="${PROJECT_ID}" --platform="managed" --format="value(status.url)")
|
|
867
|
+
if [[ -n "${MAIN_SERVICE_URL}" ]]; then
|
|
868
|
+
info "Main service '${MAIN_SERVICE_NAME}' URL: ${MAIN_SERVICE_URL}"
|
|
869
|
+
else
|
|
870
|
+
info "Main service '${MAIN_SERVICE_NAME}' deployed, but URL not available yet."
|
|
871
|
+
fi
|
|
872
|
+
|
|
873
|
+
BASH
|
|
874
|
+
|
|
875
|
+
File.write(deploy_script_path, script_content)
|
|
876
|
+
FileUtils.chmod(0o755, deploy_script_path)
|
|
877
|
+
say "Created GCP deployment script at #{deploy_script_path}", :cyan
|
|
878
|
+
say 'Please review and customize the script, especially the Configuration section, before running.', :yellow
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
def generate_gcp_deployment_docs(directory)
|
|
882
|
+
# Instead of hardcoding, copy the canonical doc we maintain
|
|
883
|
+
# Use __dir__ to get the directory of the current file (deployment_commands.rb)
|
|
884
|
+
source_doc_path = File.expand_path('../../../../docs/go-to-gcp-production-gemini.md', __dir__)
|
|
885
|
+
target_doc_path = File.join(directory, 'README-GCP-DEPLOYMENT.md')
|
|
886
|
+
|
|
887
|
+
if File.exist?(source_doc_path)
|
|
888
|
+
FileUtils.cp(source_doc_path, target_doc_path)
|
|
889
|
+
say "Copied GCP deployment guide to #{target_doc_path}", :cyan
|
|
890
|
+
else
|
|
891
|
+
say "Warning: Source deployment document not found at #{source_doc_path}", :yellow
|
|
892
|
+
# Optionally generate a placeholder
|
|
893
|
+
File.write(target_doc_path, "# GCP Deployment Guide\n\nSee online documentation for deployment steps.\n")
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# New method to generate cloudbuild.yaml
|
|
898
|
+
def generate_gcp_cloudbuild_yaml(directory)
|
|
899
|
+
cloudbuild_path = File.join(directory, 'cloudbuild.yaml')
|
|
900
|
+
deployment_dir_basename = File.basename(directory)
|
|
901
|
+
main_dockerfile_path_relative = File.join(deployment_dir_basename, 'Dockerfile')
|
|
902
|
+
|
|
903
|
+
content = <<~YAML
|
|
904
|
+
steps:
|
|
905
|
+
# Build the container image
|
|
906
|
+
- name: 'gcr.io/cloud-builders/docker'
|
|
907
|
+
args: ['build', '-t', '${_IMAGE_URI}', '-f', '#{main_dockerfile_path_relative}', '.']
|
|
908
|
+
|
|
909
|
+
# Push the container image to Artifact Registry
|
|
910
|
+
images: ['${_IMAGE_URI}']
|
|
911
|
+
|
|
912
|
+
# Define substitutions that can be passed in via --substitutions flag
|
|
913
|
+
substitutions:
|
|
914
|
+
_IMAGE_URI: 'gcr.io/cloud-build/image' # Default value, will be overridden
|
|
915
|
+
YAML
|
|
916
|
+
|
|
917
|
+
File.write(cloudbuild_path, content)
|
|
918
|
+
say "Created GCP Cloud Build config at #{cloudbuild_path}", :cyan
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# --- AWS Asset Generation (Placeholder) ---
|
|
922
|
+
def generate_aws_assets(_directory)
|
|
923
|
+
say 'AWS deployment asset generation is not yet implemented.', :yellow
|
|
924
|
+
# Placeholder for future: generate CloudFormation/CDK/Terraform, deploy scripts etc.
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
# --- Azure Asset Generation (Placeholder) ---
|
|
928
|
+
def generate_azure_assets(_directory)
|
|
929
|
+
say 'Azure deployment asset generation is not yet implemented.', :yellow
|
|
930
|
+
# Placeholder for future: generate ARM templates/Bicep, deploy scripts etc.
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
end
|