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,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shell Skill
|
|
3
|
+
|
|
4
|
+
Provides shell command execution capabilities with configurable restrictions.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
|
+
from .base import SkillDefinition, SkillType, SkillCategory, SkillVariant
|
|
8
|
+
from .registry import register_skill
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ShellSkill(SkillDefinition):
|
|
12
|
+
"""Shell command execution skill"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def type(self) -> SkillType:
|
|
16
|
+
return SkillType.SHELL
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def name(self) -> str:
|
|
20
|
+
return "Shell"
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def description(self) -> str:
|
|
24
|
+
return "Execute shell commands on the local system with configurable restrictions"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def icon(self) -> str:
|
|
28
|
+
return "Terminal"
|
|
29
|
+
|
|
30
|
+
def get_variants(self) -> List[SkillVariant]:
|
|
31
|
+
return [
|
|
32
|
+
SkillVariant(
|
|
33
|
+
id="shell_safe_commands",
|
|
34
|
+
name="Shell - Safe Commands",
|
|
35
|
+
description="Execute read-only shell commands on the local system (ls, cat, grep, ps)",
|
|
36
|
+
category=SkillCategory.COMMON,
|
|
37
|
+
badge="Safe",
|
|
38
|
+
icon="Terminal",
|
|
39
|
+
configuration={
|
|
40
|
+
"allowed_commands": ["ls", "cat", "grep", "find", "ps", "top", "pwd", "echo", "head", "tail"],
|
|
41
|
+
"timeout": 30,
|
|
42
|
+
},
|
|
43
|
+
is_default=True,
|
|
44
|
+
),
|
|
45
|
+
SkillVariant(
|
|
46
|
+
id="shell_full_access",
|
|
47
|
+
name="Shell - Full Access",
|
|
48
|
+
description="Unrestricted shell access to execute any command on local system",
|
|
49
|
+
category=SkillCategory.ADVANCED,
|
|
50
|
+
badge="Advanced",
|
|
51
|
+
icon="Terminal",
|
|
52
|
+
configuration={
|
|
53
|
+
"timeout": 300,
|
|
54
|
+
},
|
|
55
|
+
is_default=False,
|
|
56
|
+
),
|
|
57
|
+
SkillVariant(
|
|
58
|
+
id="shell_read_only",
|
|
59
|
+
name="Shell - Read Only",
|
|
60
|
+
description="Maximum security: only non-destructive read commands allowed",
|
|
61
|
+
category=SkillCategory.SECURITY,
|
|
62
|
+
badge="Secure",
|
|
63
|
+
icon="ShieldCheck",
|
|
64
|
+
configuration={
|
|
65
|
+
"allowed_commands": ["ls", "cat", "head", "tail", "grep", "find", "pwd"],
|
|
66
|
+
"blocked_commands": ["rm", "mv", "cp", "chmod", "chown", "kill"],
|
|
67
|
+
"timeout": 15,
|
|
68
|
+
},
|
|
69
|
+
is_default=False,
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def validate_configuration(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
|
74
|
+
"""Validate shell configuration"""
|
|
75
|
+
validated = {
|
|
76
|
+
"timeout": min(config.get("timeout", 30), 600), # Max 10 minutes
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Add allowed_commands if specified
|
|
80
|
+
if "allowed_commands" in config:
|
|
81
|
+
validated["allowed_commands"] = list(config["allowed_commands"])
|
|
82
|
+
|
|
83
|
+
# Add blocked_commands if specified
|
|
84
|
+
if "blocked_commands" in config:
|
|
85
|
+
validated["blocked_commands"] = list(config["blocked_commands"])
|
|
86
|
+
|
|
87
|
+
# Add working_directory if specified
|
|
88
|
+
if "working_directory" in config:
|
|
89
|
+
validated["working_directory"] = str(config["working_directory"])
|
|
90
|
+
|
|
91
|
+
return validated
|
|
92
|
+
|
|
93
|
+
def get_default_configuration(self) -> Dict[str, Any]:
|
|
94
|
+
"""Default: safe commands only"""
|
|
95
|
+
return {
|
|
96
|
+
"allowed_commands": ["ls", "cat", "grep", "find", "ps", "top", "pwd", "echo", "head", "tail"],
|
|
97
|
+
"timeout": 30,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Auto-register this skill
|
|
102
|
+
register_skill(ShellSkill())
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""Workflow Executor Skill - Execute workflows defined via JSON or Python DSL."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import traceback
|
|
5
|
+
from io import StringIO
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, validator
|
|
9
|
+
|
|
10
|
+
from .base import SkillDefinition, SkillType, SkillCategory, SkillVariant, SkillRequirements
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkflowStepConfig(BaseModel):
|
|
14
|
+
"""Configuration for a workflow step."""
|
|
15
|
+
name: str = Field(..., description="Step name")
|
|
16
|
+
description: Optional[str] = Field(None, description="Step description")
|
|
17
|
+
executor: Dict[str, Any] = Field(..., description="Executor configuration (type, config)")
|
|
18
|
+
depends_on: Optional[List[str]] = Field(None, description="Step dependencies")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WorkflowTriggerConfig(BaseModel):
|
|
22
|
+
"""Configuration for a workflow trigger."""
|
|
23
|
+
type: str = Field(..., description="Trigger type (manual, cron, webhook)")
|
|
24
|
+
config: Dict[str, Any] = Field(default_factory=dict, description="Trigger configuration")
|
|
25
|
+
runner: Optional[str] = Field(None, description="Runner/environment name")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WorkflowDefinitionConfig(BaseModel):
|
|
29
|
+
"""Workflow definition in JSON format."""
|
|
30
|
+
name: str = Field(..., description="Workflow name")
|
|
31
|
+
description: Optional[str] = Field(None, description="Workflow description")
|
|
32
|
+
steps: List[Dict[str, Any]] = Field(..., min_items=1, description="Workflow steps")
|
|
33
|
+
triggers: Optional[List[Dict[str, Any]]] = Field(None, description="Workflow triggers")
|
|
34
|
+
runner: Optional[str] = Field(None, description="Default runner/environment")
|
|
35
|
+
parameters: Optional[Dict[str, Any]] = Field(
|
|
36
|
+
None,
|
|
37
|
+
description="Parameter schema - defines available parameters (name, type, description). Agent fills values dynamically at runtime."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@validator('steps')
|
|
41
|
+
def validate_steps(cls, steps):
|
|
42
|
+
"""Validate that steps have required fields."""
|
|
43
|
+
if not steps:
|
|
44
|
+
raise ValueError("Workflow must have at least one step")
|
|
45
|
+
|
|
46
|
+
step_names = set()
|
|
47
|
+
for step in steps:
|
|
48
|
+
if 'name' not in step:
|
|
49
|
+
raise ValueError("Each step must have a 'name' field")
|
|
50
|
+
if step['name'] in step_names:
|
|
51
|
+
raise ValueError(f"Duplicate step name: {step['name']}")
|
|
52
|
+
step_names.add(step['name'])
|
|
53
|
+
|
|
54
|
+
if 'executor' not in step:
|
|
55
|
+
raise ValueError(f"Step '{step['name']}' must have an 'executor' field")
|
|
56
|
+
|
|
57
|
+
executor = step['executor']
|
|
58
|
+
if not isinstance(executor, dict) or 'type' not in executor:
|
|
59
|
+
raise ValueError(f"Step '{step['name']}' executor must be a dict with 'type' field")
|
|
60
|
+
|
|
61
|
+
return steps
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class WorkflowExecutorConfiguration(BaseModel):
|
|
65
|
+
"""Configuration for the Workflow Executor skill."""
|
|
66
|
+
|
|
67
|
+
workflows: List[Dict[str, Any]] = Field(
|
|
68
|
+
default_factory=list,
|
|
69
|
+
description="Collection of workflow definitions. Each workflow becomes a separate tool."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
validation_enabled: bool = Field(
|
|
73
|
+
True,
|
|
74
|
+
description="Enable workflow validation before saving"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
default_runner: Optional[str] = Field(
|
|
78
|
+
None,
|
|
79
|
+
description="Default runner/environment for workflow execution"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
timeout: int = Field(
|
|
83
|
+
3600,
|
|
84
|
+
ge=30,
|
|
85
|
+
le=7200,
|
|
86
|
+
description="Maximum workflow execution timeout in seconds (30s - 2h)"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
default_parameters: Optional[Dict[str, Any]] = Field(
|
|
90
|
+
None,
|
|
91
|
+
description="Default parameter values (optional). Used only if agent doesn't provide values at runtime."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Legacy fields for backwards compatibility
|
|
95
|
+
workflow_type: Optional[str] = Field(
|
|
96
|
+
None,
|
|
97
|
+
description="LEGACY: Type of workflow definition: 'json' or 'python_dsl'"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
workflow_definition: Optional[str] = Field(
|
|
101
|
+
None,
|
|
102
|
+
description="LEGACY: JSON workflow definition as a string"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
python_dsl_code: Optional[str] = Field(
|
|
106
|
+
None,
|
|
107
|
+
description="LEGACY: Python DSL code for workflow definition"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@validator('workflows')
|
|
111
|
+
def validate_workflows(cls, v, values):
|
|
112
|
+
"""Validate workflows collection."""
|
|
113
|
+
if not v:
|
|
114
|
+
# Check if using legacy fields
|
|
115
|
+
if values.get('workflow_definition') or values.get('python_dsl_code'):
|
|
116
|
+
return v # Allow empty if using legacy mode
|
|
117
|
+
raise ValueError("At least one workflow must be defined in 'workflows' array")
|
|
118
|
+
|
|
119
|
+
validation_enabled = values.get('validation_enabled', True)
|
|
120
|
+
|
|
121
|
+
workflow_names = set()
|
|
122
|
+
for idx, workflow in enumerate(v):
|
|
123
|
+
# Each workflow must have required fields
|
|
124
|
+
if 'name' not in workflow:
|
|
125
|
+
raise ValueError(f"Workflow at index {idx} missing 'name' field")
|
|
126
|
+
|
|
127
|
+
if 'type' not in workflow:
|
|
128
|
+
raise ValueError(f"Workflow '{workflow['name']}' missing 'type' field (json or python_dsl)")
|
|
129
|
+
|
|
130
|
+
# Check for duplicate names
|
|
131
|
+
name = workflow['name']
|
|
132
|
+
if name in workflow_names:
|
|
133
|
+
raise ValueError(f"Duplicate workflow name: {name}")
|
|
134
|
+
workflow_names.add(name)
|
|
135
|
+
|
|
136
|
+
# Validate based on type
|
|
137
|
+
wf_type = workflow['type']
|
|
138
|
+
if wf_type not in ['json', 'python_dsl']:
|
|
139
|
+
raise ValueError(f"Workflow '{name}' has invalid type: {wf_type}")
|
|
140
|
+
|
|
141
|
+
if wf_type == 'json':
|
|
142
|
+
if 'definition' not in workflow:
|
|
143
|
+
raise ValueError(f"JSON workflow '{name}' missing 'definition' field")
|
|
144
|
+
|
|
145
|
+
if validation_enabled:
|
|
146
|
+
try:
|
|
147
|
+
# Validate JSON structure
|
|
148
|
+
if isinstance(workflow['definition'], str):
|
|
149
|
+
workflow_data = json.loads(workflow['definition'])
|
|
150
|
+
else:
|
|
151
|
+
workflow_data = workflow['definition']
|
|
152
|
+
|
|
153
|
+
# Validate using WorkflowDefinitionConfig
|
|
154
|
+
WorkflowDefinitionConfig(**workflow_data)
|
|
155
|
+
except json.JSONDecodeError as e:
|
|
156
|
+
raise ValueError(f"Workflow '{name}' has invalid JSON: {str(e)}")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
raise ValueError(f"Workflow '{name}' validation failed: {str(e)}")
|
|
159
|
+
|
|
160
|
+
elif wf_type == 'python_dsl':
|
|
161
|
+
if 'code' not in workflow:
|
|
162
|
+
raise ValueError(f"Python DSL workflow '{name}' missing 'code' field")
|
|
163
|
+
|
|
164
|
+
if validation_enabled:
|
|
165
|
+
try:
|
|
166
|
+
compile(workflow['code'], f'<workflow:{name}>', 'exec')
|
|
167
|
+
except SyntaxError as e:
|
|
168
|
+
raise ValueError(f"Workflow '{name}' has invalid Python syntax: {str(e)}")
|
|
169
|
+
|
|
170
|
+
return v
|
|
171
|
+
|
|
172
|
+
@validator('workflow_type')
|
|
173
|
+
def validate_workflow_type(cls, v):
|
|
174
|
+
"""Validate workflow type (legacy)."""
|
|
175
|
+
if v and v not in ['json', 'python_dsl']:
|
|
176
|
+
raise ValueError("workflow_type must be 'json' or 'python_dsl'")
|
|
177
|
+
return v
|
|
178
|
+
|
|
179
|
+
@validator('workflow_definition')
|
|
180
|
+
def validate_workflow_definition(cls, v, values):
|
|
181
|
+
"""Validate JSON workflow definition (legacy)."""
|
|
182
|
+
if v and values.get('validation_enabled', True):
|
|
183
|
+
try:
|
|
184
|
+
workflow_data = json.loads(v)
|
|
185
|
+
# Validate using WorkflowDefinitionConfig
|
|
186
|
+
WorkflowDefinitionConfig(**workflow_data)
|
|
187
|
+
except json.JSONDecodeError as e:
|
|
188
|
+
raise ValueError(f"Invalid JSON in workflow_definition: {str(e)}")
|
|
189
|
+
except Exception as e:
|
|
190
|
+
raise ValueError(f"Invalid workflow definition: {str(e)}")
|
|
191
|
+
return v
|
|
192
|
+
|
|
193
|
+
@validator('python_dsl_code')
|
|
194
|
+
def validate_python_dsl_code(cls, v, values):
|
|
195
|
+
"""Validate Python DSL code (legacy)."""
|
|
196
|
+
if v and values.get('validation_enabled', True):
|
|
197
|
+
try:
|
|
198
|
+
compile(v, '<workflow>', 'exec')
|
|
199
|
+
except SyntaxError as e:
|
|
200
|
+
raise ValueError(f"Invalid Python syntax in DSL code: {str(e)}")
|
|
201
|
+
return v
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class WorkflowExecutorSkill(SkillDefinition):
|
|
205
|
+
"""Workflow Executor Skill - Execute workflows defined via JSON or Python DSL.
|
|
206
|
+
|
|
207
|
+
This skill allows defining and executing workflows using either:
|
|
208
|
+
1. JSON workflow definitions with steps and executors
|
|
209
|
+
2. Python DSL using the kubiya-sdk
|
|
210
|
+
|
|
211
|
+
Workflows are validated before saving and can be executed via the control plane.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def type(self) -> SkillType:
|
|
216
|
+
"""Return the skill type."""
|
|
217
|
+
return SkillType.WORKFLOW_EXECUTOR
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def name(self) -> str:
|
|
221
|
+
"""Return the skill name."""
|
|
222
|
+
return "Workflow Executor"
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def description(self) -> str:
|
|
226
|
+
"""Return the skill description."""
|
|
227
|
+
return (
|
|
228
|
+
"Execute workflows defined via JSON or Python DSL. "
|
|
229
|
+
"Supports complex multi-step workflows with dependencies, "
|
|
230
|
+
"conditional execution, and various executor types."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def icon(self) -> str:
|
|
235
|
+
"""Return the skill icon."""
|
|
236
|
+
return "Workflow"
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def icon_type(self) -> str:
|
|
240
|
+
"""Return the icon type."""
|
|
241
|
+
return "lucide"
|
|
242
|
+
|
|
243
|
+
def get_variants(self) -> List[SkillVariant]:
|
|
244
|
+
"""Return single workflow executor variant with empty configuration for user to fill."""
|
|
245
|
+
return [
|
|
246
|
+
SkillVariant(
|
|
247
|
+
id="workflow_executor",
|
|
248
|
+
name="Workflow Executor",
|
|
249
|
+
description="Execute workflows with dynamic parameters. Agent fills parameter values at runtime based on user requests.",
|
|
250
|
+
category=SkillCategory.ADVANCED,
|
|
251
|
+
configuration={
|
|
252
|
+
"workflows": [
|
|
253
|
+
{
|
|
254
|
+
"name": "deploy-app",
|
|
255
|
+
"type": "json",
|
|
256
|
+
"definition": {
|
|
257
|
+
"name": "deploy-app",
|
|
258
|
+
"description": "Deploy application to specified environment",
|
|
259
|
+
"runner": "kubiya-prod",
|
|
260
|
+
"parameters": {
|
|
261
|
+
"app_name": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"description": "Application name to deploy"
|
|
264
|
+
},
|
|
265
|
+
"environment": {
|
|
266
|
+
"type": "string",
|
|
267
|
+
"description": "Target environment (dev, staging, prod)"
|
|
268
|
+
},
|
|
269
|
+
"version": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"description": "Version to deploy"
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"steps": [
|
|
275
|
+
{
|
|
276
|
+
"name": "deploy",
|
|
277
|
+
"description": "Deploy application",
|
|
278
|
+
"executor": {
|
|
279
|
+
"type": "shell",
|
|
280
|
+
"config": {
|
|
281
|
+
"command": "kubectl apply -f deploy.yaml --namespace={{environment}} && kubectl set image deployment/{{app_name}} app={{app_name}}:{{version}}"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
"validation_enabled": True,
|
|
290
|
+
"default_runner": "kubiya-prod",
|
|
291
|
+
"timeout": 3600,
|
|
292
|
+
"default_parameters": {
|
|
293
|
+
"environment": "staging"
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
icon="Workflow",
|
|
297
|
+
is_default=True
|
|
298
|
+
)
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
def validate_configuration(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
|
302
|
+
"""Validate the workflow executor configuration.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
config: Configuration dictionary to validate
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Validated and normalized configuration
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
ValueError: If configuration is invalid
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
# Use Pydantic model for validation
|
|
315
|
+
validated = WorkflowExecutorConfiguration(**config)
|
|
316
|
+
|
|
317
|
+
# Additional validation for workflow execution
|
|
318
|
+
if validated.workflow_type == 'json' and validated.workflow_definition:
|
|
319
|
+
workflow_data = json.loads(validated.workflow_definition)
|
|
320
|
+
# Ensure step dependencies are valid
|
|
321
|
+
step_names = {step['name'] for step in workflow_data.get('steps', [])}
|
|
322
|
+
for step in workflow_data.get('steps', []):
|
|
323
|
+
depends_on = step.get('depends_on', [])
|
|
324
|
+
if depends_on:
|
|
325
|
+
for dep in depends_on:
|
|
326
|
+
if dep not in step_names:
|
|
327
|
+
raise ValueError(
|
|
328
|
+
f"Step '{step['name']}' depends on non-existent step: {dep}"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
elif validated.workflow_type == 'python_dsl' and validated.python_dsl_code:
|
|
332
|
+
# Try to execute the DSL code to validate it can create a workflow
|
|
333
|
+
if validated.validation_enabled:
|
|
334
|
+
self._validate_python_dsl(validated.python_dsl_code)
|
|
335
|
+
|
|
336
|
+
return validated.dict()
|
|
337
|
+
|
|
338
|
+
except Exception as e:
|
|
339
|
+
raise ValueError(f"Workflow executor configuration validation failed: {str(e)}")
|
|
340
|
+
|
|
341
|
+
def _validate_python_dsl(self, dsl_code: str) -> None:
|
|
342
|
+
"""Validate Python DSL code by attempting to compile and check imports.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
dsl_code: Python DSL code to validate
|
|
346
|
+
|
|
347
|
+
Raises:
|
|
348
|
+
ValueError: If DSL code is invalid
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
# First check if kubiya_sdk is available
|
|
352
|
+
try:
|
|
353
|
+
import kubiya_sdk
|
|
354
|
+
except ImportError:
|
|
355
|
+
raise ValueError(
|
|
356
|
+
"kubiya-sdk is not installed. Please install it to use Python DSL workflows."
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Compile the code to check for syntax errors
|
|
360
|
+
try:
|
|
361
|
+
compile(dsl_code, '<workflow>', 'exec')
|
|
362
|
+
except SyntaxError as e:
|
|
363
|
+
raise ValueError(f"Python syntax error in DSL code: {str(e)}")
|
|
364
|
+
|
|
365
|
+
# Create a restricted execution environment
|
|
366
|
+
namespace = {
|
|
367
|
+
'__builtins__': __builtins__,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
# Import kubiya_sdk modules for validation
|
|
371
|
+
try:
|
|
372
|
+
from kubiya_sdk import StatefulWorkflow, Tool
|
|
373
|
+
from kubiya_sdk.workflows import step, tool_step
|
|
374
|
+
namespace.update({
|
|
375
|
+
'StatefulWorkflow': StatefulWorkflow,
|
|
376
|
+
'Tool': Tool,
|
|
377
|
+
'step': step,
|
|
378
|
+
'tool_step': tool_step,
|
|
379
|
+
})
|
|
380
|
+
except ImportError as e:
|
|
381
|
+
raise ValueError(f"Failed to import kubiya_sdk modules: {str(e)}")
|
|
382
|
+
|
|
383
|
+
# Capture stdout/stderr during validation
|
|
384
|
+
old_stdout = sys.stdout
|
|
385
|
+
old_stderr = sys.stderr
|
|
386
|
+
stdout_capture = StringIO()
|
|
387
|
+
stderr_capture = StringIO()
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
sys.stdout = stdout_capture
|
|
391
|
+
sys.stderr = stderr_capture
|
|
392
|
+
|
|
393
|
+
# Execute the DSL code
|
|
394
|
+
exec(dsl_code, namespace)
|
|
395
|
+
|
|
396
|
+
# Check if a workflow was created
|
|
397
|
+
workflow_found = False
|
|
398
|
+
for name, obj in namespace.items():
|
|
399
|
+
if name.startswith('_'):
|
|
400
|
+
continue
|
|
401
|
+
# Check for StatefulWorkflow instance
|
|
402
|
+
if hasattr(obj, '__class__'):
|
|
403
|
+
class_name = obj.__class__.__name__
|
|
404
|
+
if 'StatefulWorkflow' in class_name or 'Workflow' in class_name:
|
|
405
|
+
workflow_found = True
|
|
406
|
+
break
|
|
407
|
+
|
|
408
|
+
if not workflow_found:
|
|
409
|
+
raise ValueError(
|
|
410
|
+
"Python DSL code must create a StatefulWorkflow instance. "
|
|
411
|
+
"Example: workflow = StatefulWorkflow(name='my-workflow', description='...')"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
finally:
|
|
415
|
+
sys.stdout = old_stdout
|
|
416
|
+
sys.stderr = old_stderr
|
|
417
|
+
|
|
418
|
+
# Check for any errors in stderr
|
|
419
|
+
stderr_output = stderr_capture.getvalue()
|
|
420
|
+
if stderr_output and 'error' in stderr_output.lower():
|
|
421
|
+
raise ValueError(f"Python DSL execution produced errors: {stderr_output}")
|
|
422
|
+
|
|
423
|
+
except ValueError:
|
|
424
|
+
raise
|
|
425
|
+
except Exception as e:
|
|
426
|
+
raise ValueError(f"Python DSL validation failed: {str(e)}\n{traceback.format_exc()}")
|
|
427
|
+
|
|
428
|
+
def get_default_configuration(self) -> Dict[str, Any]:
|
|
429
|
+
"""Return the default configuration."""
|
|
430
|
+
return {
|
|
431
|
+
"workflow_type": "json",
|
|
432
|
+
"validation_enabled": True,
|
|
433
|
+
"timeout": 3600,
|
|
434
|
+
"workflow_definition": json.dumps({
|
|
435
|
+
"name": "new-workflow",
|
|
436
|
+
"description": "New workflow",
|
|
437
|
+
"steps": [
|
|
438
|
+
{
|
|
439
|
+
"name": "step-1",
|
|
440
|
+
"description": "First step",
|
|
441
|
+
"executor": {
|
|
442
|
+
"type": "shell",
|
|
443
|
+
"config": {
|
|
444
|
+
"command": "echo 'Hello World'"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
}, indent=2)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
def get_framework_class_name(self) -> str:
|
|
453
|
+
"""Return the framework class name for this skill."""
|
|
454
|
+
return "WorkflowExecutorTool"
|
|
455
|
+
|
|
456
|
+
def get_requirements(self) -> SkillRequirements:
|
|
457
|
+
"""Return the skill requirements."""
|
|
458
|
+
return SkillRequirements(
|
|
459
|
+
supported_os=["linux", "darwin", "win32"],
|
|
460
|
+
min_python_version="3.10",
|
|
461
|
+
python_packages=["kubiya-sdk"],
|
|
462
|
+
required_env_vars=[],
|
|
463
|
+
notes="Optional env vars: KUBIYA_API_KEY, KUBIYA_API_URL for workflow execution"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# Auto-register this skill
|
|
468
|
+
from .registry import register_skill
|
|
469
|
+
register_skill(WorkflowExecutorSkill())
|