kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.4__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 kubiya-control-plane-api might be problematic. Click here for more details.
- control_plane_api/README.md +266 -0
- control_plane_api/__init__.py +0 -0
- control_plane_api/__version__.py +1 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +98 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
- control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
- control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
- control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
- control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
- control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
- control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
- control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
- control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
- control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
- control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +379 -0
- control_plane_api/app/activities/team_activities.py +410 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +354 -0
- control_plane_api/app/config/model_pricing.py +318 -0
- control_plane_api/app/config.py +95 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/job_executor.py +312 -0
- control_plane_api/app/lib/kubiya_client.py +235 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/planning_tools/__init__.py +22 -0
- control_plane_api/app/lib/planning_tools/agents.py +155 -0
- control_plane_api/app/lib/planning_tools/base.py +189 -0
- control_plane_api/app/lib/planning_tools/environments.py +214 -0
- control_plane_api/app/lib/planning_tools/resources.py +240 -0
- control_plane_api/app/lib/planning_tools/teams.py +198 -0
- control_plane_api/app/lib/policy_enforcer_client.py +939 -0
- control_plane_api/app/lib/redis_client.py +436 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/temporal_client.py +138 -0
- control_plane_api/app/lib/validation/__init__.py +20 -0
- control_plane_api/app/lib/validation/runtime_validation.py +287 -0
- control_plane_api/app/main.py +128 -0
- control_plane_api/app/middleware/__init__.py +8 -0
- control_plane_api/app/middleware/auth.py +513 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +27 -0
- control_plane_api/app/models/agent.py +79 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +81 -0
- control_plane_api/app/models/environment.py +63 -0
- control_plane_api/app/models/execution.py +93 -0
- control_plane_api/app/models/job.py +179 -0
- control_plane_api/app/models/llm_model.py +75 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +47 -0
- control_plane_api/app/models/session.py +38 -0
- control_plane_api/app/models/team.py +66 -0
- control_plane_api/app/models/workflow.py +55 -0
- control_plane_api/app/policies/README.md +121 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +364 -0
- control_plane_api/app/routers/agents_v2.py +1260 -0
- control_plane_api/app/routers/analytics.py +1014 -0
- control_plane_api/app/routers/context_manager.py +562 -0
- control_plane_api/app/routers/environment_context.py +270 -0
- control_plane_api/app/routers/environments.py +715 -0
- control_plane_api/app/routers/execution_environment.py +517 -0
- control_plane_api/app/routers/executions.py +1911 -0
- control_plane_api/app/routers/health.py +92 -0
- control_plane_api/app/routers/health_v2.py +326 -0
- control_plane_api/app/routers/integrations.py +274 -0
- control_plane_api/app/routers/jobs.py +1344 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +361 -0
- control_plane_api/app/routers/policies.py +639 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +902 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +155 -0
- control_plane_api/app/routers/skills.py +1001 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/task_planning.py +1256 -0
- control_plane_api/app/routers/task_queues.py +654 -0
- control_plane_api/app/routers/team_context.py +270 -0
- control_plane_api/app/routers/teams.py +1400 -0
- control_plane_api/app/routers/worker_queues.py +1545 -0
- control_plane_api/app/routers/workers.py +935 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/job_schemas.py +295 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_service.py +619 -0
- control_plane_api/app/services/litellm_service.py +190 -0
- control_plane_api/app/services/policy_service.py +525 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/skills/__init__.py +44 -0
- control_plane_api/app/skills/base.py +229 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/data_visualization.py +154 -0
- control_plane_api/app/skills/docker.py +104 -0
- control_plane_api/app/skills/file_generation.py +94 -0
- control_plane_api/app/skills/file_system.py +110 -0
- control_plane_api/app/skills/python.py +92 -0
- control_plane_api/app/skills/registry.py +65 -0
- control_plane_api/app/skills/shell.py +102 -0
- control_plane_api/app/skills/workflow_executor.py +469 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +507 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/team_execution.py +399 -0
- control_plane_api/scripts/seed_models.py +239 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1241 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/runtime_activities.py +388 -0
- control_plane_api/worker/activities/skill_activities.py +267 -0
- control_plane_api/worker/activities/team_activities.py +1217 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +275 -0
- control_plane_api/worker/control_plane_client.py +529 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +31 -0
- control_plane_api/worker/runtimes/base.py +789 -0
- control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
- control_plane_api/worker/runtimes/default_runtime.py +617 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_executor.py +422 -0
- control_plane_api/worker/services/agent_executor_v2.py +383 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/data_visualization.py +827 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +194 -0
- control_plane_api/worker/services/skill_factory.py +175 -0
- control_plane_api/worker/services/team_executor.py +574 -0
- control_plane_api/worker/services/team_executor_v2.py +465 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +305 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +373 -0
- control_plane_api/worker/worker.py +753 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +589 -0
- control_plane_api/worker/workflows/team_execution.py +429 -0
- kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
- kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
- kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
- kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
- kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
- kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
- kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
- {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
- {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Execution Workflow with Skill Support.
|
|
3
|
+
|
|
4
|
+
This workflow demonstrates how to execute an agent with skills resolved
|
|
5
|
+
from the Control Plane API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from temporalio import workflow
|
|
11
|
+
import structlog
|
|
12
|
+
|
|
13
|
+
# Import activities
|
|
14
|
+
from control_plane_api.app.activities.agent_activities import (
|
|
15
|
+
execute_agent_llm,
|
|
16
|
+
update_execution_status,
|
|
17
|
+
)
|
|
18
|
+
from control_plane_api.app.activities.skill_activities import (
|
|
19
|
+
resolve_agent_skills,
|
|
20
|
+
instantiate_agent_skills,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
logger = structlog.get_logger()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class AgentExecutionInput:
|
|
28
|
+
"""Input for agent execution workflow"""
|
|
29
|
+
execution_id: str
|
|
30
|
+
organization_id: str
|
|
31
|
+
agent_id: str
|
|
32
|
+
prompt: str
|
|
33
|
+
system_prompt: str | None = None
|
|
34
|
+
control_plane_url: str = "https://agent-control-plane.vercel.app"
|
|
35
|
+
api_key: str = None # Kubiya API key for authentication
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class AgentExecutionResult:
|
|
40
|
+
"""Result of agent execution workflow"""
|
|
41
|
+
execution_id: str
|
|
42
|
+
response: str
|
|
43
|
+
usage: dict
|
|
44
|
+
status: str
|
|
45
|
+
skills_used: list[str] | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@workflow.defn
|
|
49
|
+
class AgentExecutionWithToolSetsWorkflow:
|
|
50
|
+
"""
|
|
51
|
+
Orchestrates agent execution with skill resolution.
|
|
52
|
+
|
|
53
|
+
Flow:
|
|
54
|
+
1. Fetch agent configuration from Control Plane
|
|
55
|
+
2. Resolve skills from Control Plane (with inheritance)
|
|
56
|
+
3. Instantiate agno tool instances
|
|
57
|
+
4. Create agent with tools
|
|
58
|
+
5. Execute agent
|
|
59
|
+
6. Return results
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
@workflow.run
|
|
63
|
+
async def run(self, input: AgentExecutionInput) -> AgentExecutionResult:
|
|
64
|
+
"""
|
|
65
|
+
Execute agent workflow with skills.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
input: AgentExecutionInput with agent_id, prompt, etc.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
AgentExecutionResult with response and metadata
|
|
72
|
+
"""
|
|
73
|
+
workflow.logger.info(
|
|
74
|
+
"agent_execution_workflow_started",
|
|
75
|
+
execution_id=input.execution_id,
|
|
76
|
+
agent_id=input.agent_id,
|
|
77
|
+
organization_id=input.organization_id
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# 1. Update execution status to 'running'
|
|
81
|
+
await workflow.execute_activity(
|
|
82
|
+
update_execution_status,
|
|
83
|
+
args=[input.execution_id, "running"],
|
|
84
|
+
start_to_close_timeout=timedelta(seconds=10)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# 2. Resolve skills from Control Plane
|
|
89
|
+
# The Control Plane handles inheritance: Environment → Team → Agent
|
|
90
|
+
skill_definitions = await workflow.execute_activity(
|
|
91
|
+
resolve_agent_skills,
|
|
92
|
+
args=[
|
|
93
|
+
input.agent_id,
|
|
94
|
+
input.control_plane_url,
|
|
95
|
+
input.api_key
|
|
96
|
+
],
|
|
97
|
+
start_to_close_timeout=timedelta(seconds=30)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
workflow.logger.info(
|
|
101
|
+
"skills_resolved",
|
|
102
|
+
execution_id=input.execution_id,
|
|
103
|
+
skill_count=len(skill_definitions),
|
|
104
|
+
skill_types=[t.get("type") for t in skill_definitions]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# 3. Instantiate agno tool instances
|
|
108
|
+
agent_tools = await workflow.execute_activity(
|
|
109
|
+
instantiate_agent_skills,
|
|
110
|
+
args=[skill_definitions],
|
|
111
|
+
start_to_close_timeout=timedelta(seconds=10)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
workflow.logger.info(
|
|
115
|
+
"tools_instantiated",
|
|
116
|
+
execution_id=input.execution_id,
|
|
117
|
+
tool_count=len(agent_tools)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# 4. Execute agent with LLM
|
|
121
|
+
# Pass tools to the execution activity
|
|
122
|
+
llm_result = await workflow.execute_activity(
|
|
123
|
+
execute_agent_llm,
|
|
124
|
+
args=[
|
|
125
|
+
input.agent_id,
|
|
126
|
+
input.prompt,
|
|
127
|
+
input.system_prompt,
|
|
128
|
+
agent_tools, # Pass instantiated tools
|
|
129
|
+
input.control_plane_url,
|
|
130
|
+
input.api_key
|
|
131
|
+
],
|
|
132
|
+
start_to_close_timeout=timedelta(minutes=10),
|
|
133
|
+
retry_policy=workflow.RetryPolicy(
|
|
134
|
+
maximum_attempts=3,
|
|
135
|
+
initial_interval=timedelta(seconds=1),
|
|
136
|
+
backoff_coefficient=2.0
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 5. Update execution status to 'completed'
|
|
141
|
+
await workflow.execute_activity(
|
|
142
|
+
update_execution_status,
|
|
143
|
+
args=[input.execution_id, "completed", llm_result],
|
|
144
|
+
start_to_close_timeout=timedelta(seconds=30)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
workflow.logger.info(
|
|
148
|
+
"agent_execution_workflow_completed",
|
|
149
|
+
execution_id=input.execution_id,
|
|
150
|
+
agent_id=input.agent_id
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return AgentExecutionResult(
|
|
154
|
+
execution_id=input.execution_id,
|
|
155
|
+
response=llm_result.get("response", ""),
|
|
156
|
+
usage=llm_result.get("usage", {}),
|
|
157
|
+
status="completed",
|
|
158
|
+
skills_used=[t.get("name") for t in skill_definitions]
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
workflow.logger.error(
|
|
163
|
+
"agent_execution_workflow_failed",
|
|
164
|
+
execution_id=input.execution_id,
|
|
165
|
+
error=str(e)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Update execution status to 'failed'
|
|
169
|
+
await workflow.execute_activity(
|
|
170
|
+
update_execution_status,
|
|
171
|
+
args=[input.execution_id, "failed", {"error": str(e)}],
|
|
172
|
+
start_to_close_timeout=timedelta(seconds=30)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
raise
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# Example usage in Control Plane API:
|
|
179
|
+
"""
|
|
180
|
+
from temporalio.client import Client
|
|
181
|
+
from control_plane_api.app.workflows.agent_execution_with_skills import (
|
|
182
|
+
AgentExecutionWithToolSetsWorkflow,
|
|
183
|
+
AgentExecutionInput
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# In your agent execution endpoint:
|
|
187
|
+
@router.post("/api/v1/agents/{agent_id}/execute")
|
|
188
|
+
async def execute_agent(
|
|
189
|
+
agent_id: str,
|
|
190
|
+
request: AgentExecutionRequest,
|
|
191
|
+
organization: dict = Depends(get_current_organization),
|
|
192
|
+
):
|
|
193
|
+
# Get Temporal client
|
|
194
|
+
temporal_client = await get_temporal_client()
|
|
195
|
+
|
|
196
|
+
# Create execution record
|
|
197
|
+
execution_id = str(uuid.uuid4())
|
|
198
|
+
|
|
199
|
+
# Submit workflow to Temporal
|
|
200
|
+
workflow_handle = await temporal_client.start_workflow(
|
|
201
|
+
AgentExecutionWithToolSetsWorkflow.run,
|
|
202
|
+
AgentExecutionInput(
|
|
203
|
+
execution_id=execution_id,
|
|
204
|
+
organization_id=organization["id"],
|
|
205
|
+
agent_id=agent_id,
|
|
206
|
+
prompt=request.prompt,
|
|
207
|
+
system_prompt=request.system_prompt,
|
|
208
|
+
control_plane_url=settings.CONTROL_PLANE_URL,
|
|
209
|
+
api_key=organization["api_key"] # Or get from auth
|
|
210
|
+
),
|
|
211
|
+
id=f"agent-exec-{execution_id}",
|
|
212
|
+
task_queue=request.worker_queue_id, # Route to specific worker queue
|
|
213
|
+
execution_timeout=timedelta(hours=1)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return AgentExecutionResponse(
|
|
217
|
+
execution_id=execution_id,
|
|
218
|
+
workflow_id=workflow_handle.id,
|
|
219
|
+
status="pending",
|
|
220
|
+
message="Agent execution submitted successfully"
|
|
221
|
+
)
|
|
222
|
+
"""
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Temporal Cloud Namespace Provisioning Workflow
|
|
3
|
+
|
|
4
|
+
This workflow handles the provisioning of Temporal Cloud namespaces using tcld CLI.
|
|
5
|
+
Since Temporal doesn't provide SDK/API for namespace creation, we use the CLI tool.
|
|
6
|
+
|
|
7
|
+
Flow:
|
|
8
|
+
1. Check if namespace already exists
|
|
9
|
+
2. Create namespace if needed
|
|
10
|
+
3. Poll until namespace is ready
|
|
11
|
+
4. Generate API key
|
|
12
|
+
5. Store credentials
|
|
13
|
+
6. Update task queue status to 'ready'
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import timedelta
|
|
18
|
+
from temporalio import workflow
|
|
19
|
+
from temporalio.common import RetryPolicy
|
|
20
|
+
import structlog
|
|
21
|
+
|
|
22
|
+
# Import activities
|
|
23
|
+
with workflow.unsafe.imports_passed_through():
|
|
24
|
+
from control_plane_api.app.activities.temporal_cloud_activities import (
|
|
25
|
+
check_namespace_exists,
|
|
26
|
+
create_namespace,
|
|
27
|
+
poll_namespace_status,
|
|
28
|
+
generate_namespace_api_key,
|
|
29
|
+
store_namespace_credentials,
|
|
30
|
+
update_task_queue_status,
|
|
31
|
+
CheckNamespaceInput,
|
|
32
|
+
CreateNamespaceInput,
|
|
33
|
+
PollNamespaceStatusInput,
|
|
34
|
+
GenerateApiKeyInput,
|
|
35
|
+
StoreNamespaceCredentialsInput,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = structlog.get_logger()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ProvisionNamespaceInput:
|
|
43
|
+
"""Input for namespace provisioning workflow"""
|
|
44
|
+
organization_id: str
|
|
45
|
+
organization_name: str
|
|
46
|
+
task_queue_id: str
|
|
47
|
+
account_id: str
|
|
48
|
+
region: str = "aws-us-east-1"
|
|
49
|
+
retention_days: int = 30
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ProvisionNamespaceOutput:
|
|
54
|
+
"""Output from namespace provisioning workflow"""
|
|
55
|
+
success: bool
|
|
56
|
+
namespace_name: str
|
|
57
|
+
namespace_id: str | None = None
|
|
58
|
+
status: str = "pending"
|
|
59
|
+
error_message: str | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@workflow.defn
|
|
63
|
+
class ProvisionTemporalNamespaceWorkflow:
|
|
64
|
+
"""
|
|
65
|
+
Workflow to provision a Temporal Cloud namespace for an organization.
|
|
66
|
+
|
|
67
|
+
This workflow is triggered when the first task queue is created for an org.
|
|
68
|
+
It handles the entire provisioning process including retries and error handling.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
@workflow.run
|
|
72
|
+
async def run(self, input: ProvisionNamespaceInput) -> ProvisionNamespaceOutput:
|
|
73
|
+
"""
|
|
74
|
+
Main workflow execution.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
input: Provisioning input with org details
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
ProvisionNamespaceOutput with result
|
|
81
|
+
"""
|
|
82
|
+
workflow.logger.info(
|
|
83
|
+
f"Starting namespace provisioning workflow",
|
|
84
|
+
extra={
|
|
85
|
+
"organization_id": input.organization_id,
|
|
86
|
+
"task_queue_id": input.task_queue_id,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Generate namespace name: kubiya-{org_slug}-{short_id}
|
|
91
|
+
# Format: kubiya-acme-corp-a1b2c3
|
|
92
|
+
org_slug = input.organization_name.lower().replace(" ", "-")[:20]
|
|
93
|
+
org_short_id = input.organization_id[:6]
|
|
94
|
+
namespace_name = f"kubiya-{org_slug}-{org_short_id}"
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Step 1: Check if namespace already exists
|
|
98
|
+
workflow.logger.info("Step 1: Checking if namespace exists")
|
|
99
|
+
|
|
100
|
+
check_result = await workflow.execute_activity(
|
|
101
|
+
check_namespace_exists,
|
|
102
|
+
CheckNamespaceInput(
|
|
103
|
+
organization_id=input.organization_id,
|
|
104
|
+
namespace_name=namespace_name,
|
|
105
|
+
),
|
|
106
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
107
|
+
retry_policy=RetryPolicy(
|
|
108
|
+
maximum_attempts=3,
|
|
109
|
+
initial_interval=timedelta(seconds=1),
|
|
110
|
+
maximum_interval=timedelta(seconds=10),
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if check_result.get("exists"):
|
|
115
|
+
workflow.logger.info(
|
|
116
|
+
f"Namespace already exists",
|
|
117
|
+
extra={"namespace_name": namespace_name}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# If it exists and is ready, update task queue and we're done
|
|
121
|
+
if check_result.get("status") == "ready":
|
|
122
|
+
namespace_id = check_result.get("details", {}).get("id")
|
|
123
|
+
|
|
124
|
+
await workflow.execute_activity(
|
|
125
|
+
update_task_queue_status,
|
|
126
|
+
args=[input.task_queue_id, "ready", None, namespace_id],
|
|
127
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return ProvisionNamespaceOutput(
|
|
131
|
+
success=True,
|
|
132
|
+
namespace_name=namespace_name,
|
|
133
|
+
namespace_id=namespace_id,
|
|
134
|
+
status="ready",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Step 2: Create namespace
|
|
138
|
+
workflow.logger.info("Step 2: Creating namespace")
|
|
139
|
+
|
|
140
|
+
create_result = await workflow.execute_activity(
|
|
141
|
+
create_namespace,
|
|
142
|
+
CreateNamespaceInput(
|
|
143
|
+
organization_id=input.organization_id,
|
|
144
|
+
namespace_name=namespace_name,
|
|
145
|
+
account_id=input.account_id,
|
|
146
|
+
region=input.region,
|
|
147
|
+
retention_days=input.retention_days,
|
|
148
|
+
),
|
|
149
|
+
start_to_close_timeout=timedelta(seconds=60),
|
|
150
|
+
retry_policy=RetryPolicy(
|
|
151
|
+
maximum_attempts=3,
|
|
152
|
+
initial_interval=timedelta(seconds=2),
|
|
153
|
+
maximum_interval=timedelta(seconds=10),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if not create_result.get("success"):
|
|
158
|
+
error_msg = create_result.get("error", "Failed to create namespace")
|
|
159
|
+
workflow.logger.error(
|
|
160
|
+
f"Namespace creation failed",
|
|
161
|
+
extra={"error": error_msg}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Update task queue with error
|
|
165
|
+
await workflow.execute_activity(
|
|
166
|
+
update_task_queue_status,
|
|
167
|
+
args=[input.task_queue_id, "error", error_msg, None],
|
|
168
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return ProvisionNamespaceOutput(
|
|
172
|
+
success=False,
|
|
173
|
+
namespace_name=namespace_name,
|
|
174
|
+
status="error",
|
|
175
|
+
error_message=error_msg,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
namespace_id = create_result.get("namespace_id")
|
|
179
|
+
|
|
180
|
+
# Step 3: Poll namespace status until ready
|
|
181
|
+
workflow.logger.info("Step 3: Polling namespace status")
|
|
182
|
+
|
|
183
|
+
poll_result = await workflow.execute_activity(
|
|
184
|
+
poll_namespace_status,
|
|
185
|
+
PollNamespaceStatusInput(
|
|
186
|
+
namespace_name=namespace_name,
|
|
187
|
+
max_attempts=60, # 5 minutes max
|
|
188
|
+
poll_interval_seconds=5,
|
|
189
|
+
),
|
|
190
|
+
start_to_close_timeout=timedelta(minutes=6),
|
|
191
|
+
retry_policy=RetryPolicy(
|
|
192
|
+
maximum_attempts=2,
|
|
193
|
+
initial_interval=timedelta(seconds=5),
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if not poll_result.get("ready"):
|
|
198
|
+
error_msg = poll_result.get("error", "Namespace not ready")
|
|
199
|
+
workflow.logger.error(
|
|
200
|
+
f"Namespace provisioning timed out",
|
|
201
|
+
extra={"attempts": poll_result.get("attempts")}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Update task queue with error
|
|
205
|
+
await workflow.execute_activity(
|
|
206
|
+
update_task_queue_status,
|
|
207
|
+
args=[input.task_queue_id, "error", error_msg, namespace_id],
|
|
208
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return ProvisionNamespaceOutput(
|
|
212
|
+
success=False,
|
|
213
|
+
namespace_name=namespace_name,
|
|
214
|
+
namespace_id=namespace_id,
|
|
215
|
+
status="error",
|
|
216
|
+
error_message=error_msg,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Step 4: Generate API key
|
|
220
|
+
workflow.logger.info("Step 4: Generating API key")
|
|
221
|
+
|
|
222
|
+
api_key_result = await workflow.execute_activity(
|
|
223
|
+
generate_namespace_api_key,
|
|
224
|
+
GenerateApiKeyInput(
|
|
225
|
+
namespace_name=namespace_name,
|
|
226
|
+
key_description=f"Control Plane API Key for {input.organization_name}",
|
|
227
|
+
),
|
|
228
|
+
start_to_close_timeout=timedelta(seconds=30),
|
|
229
|
+
retry_policy=RetryPolicy(
|
|
230
|
+
maximum_attempts=3,
|
|
231
|
+
initial_interval=timedelta(seconds=2),
|
|
232
|
+
maximum_interval=timedelta(seconds=10),
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if not api_key_result.get("success"):
|
|
237
|
+
error_msg = api_key_result.get("error", "Failed to generate API key")
|
|
238
|
+
workflow.logger.error(
|
|
239
|
+
f"API key generation failed",
|
|
240
|
+
extra={"error": error_msg}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Update task queue with error
|
|
244
|
+
await workflow.execute_activity(
|
|
245
|
+
update_task_queue_status,
|
|
246
|
+
args=[input.task_queue_id, "error", error_msg, namespace_id],
|
|
247
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return ProvisionNamespaceOutput(
|
|
251
|
+
success=False,
|
|
252
|
+
namespace_name=namespace_name,
|
|
253
|
+
namespace_id=namespace_id,
|
|
254
|
+
status="error",
|
|
255
|
+
error_message=error_msg,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
api_key = api_key_result.get("api_key")
|
|
259
|
+
|
|
260
|
+
# Step 5: Store credentials
|
|
261
|
+
workflow.logger.info("Step 5: Storing credentials")
|
|
262
|
+
|
|
263
|
+
store_result = await workflow.execute_activity(
|
|
264
|
+
store_namespace_credentials,
|
|
265
|
+
StoreNamespaceCredentialsInput(
|
|
266
|
+
organization_id=input.organization_id,
|
|
267
|
+
namespace_name=namespace_name,
|
|
268
|
+
api_key=api_key,
|
|
269
|
+
status="ready",
|
|
270
|
+
),
|
|
271
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
272
|
+
retry_policy=RetryPolicy(
|
|
273
|
+
maximum_attempts=3,
|
|
274
|
+
initial_interval=timedelta(seconds=1),
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
stored_namespace_id = store_result.get("namespace_id")
|
|
279
|
+
|
|
280
|
+
# Step 6: Update task queue status to ready
|
|
281
|
+
workflow.logger.info("Step 6: Updating task queue status")
|
|
282
|
+
|
|
283
|
+
await workflow.execute_activity(
|
|
284
|
+
update_task_queue_status,
|
|
285
|
+
args=[input.task_queue_id, "ready", None, stored_namespace_id],
|
|
286
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
workflow.logger.info(
|
|
290
|
+
f"Namespace provisioning complete",
|
|
291
|
+
extra={
|
|
292
|
+
"namespace_name": namespace_name,
|
|
293
|
+
"namespace_id": stored_namespace_id,
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return ProvisionNamespaceOutput(
|
|
298
|
+
success=True,
|
|
299
|
+
namespace_name=namespace_name,
|
|
300
|
+
namespace_id=stored_namespace_id,
|
|
301
|
+
status="ready",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
except Exception as e:
|
|
305
|
+
error_msg = f"Workflow failed: {str(e)}"
|
|
306
|
+
workflow.logger.error(
|
|
307
|
+
f"Namespace provisioning workflow failed",
|
|
308
|
+
extra={"error": str(e)}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Update task queue with error
|
|
312
|
+
try:
|
|
313
|
+
await workflow.execute_activity(
|
|
314
|
+
update_task_queue_status,
|
|
315
|
+
args=[input.task_queue_id, "error", error_msg, None],
|
|
316
|
+
start_to_close_timeout=timedelta(seconds=15),
|
|
317
|
+
)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass # Best effort
|
|
320
|
+
|
|
321
|
+
return ProvisionNamespaceOutput(
|
|
322
|
+
success=False,
|
|
323
|
+
namespace_name=namespace_name,
|
|
324
|
+
status="error",
|
|
325
|
+
error_message=error_msg,
|
|
326
|
+
)
|