conductor_ruby 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/CHANGELOG.md +142 -0
- data/LICENSE +190 -0
- data/README.md +517 -0
- data/examples/agentic_workflows/llm_chat.rb +106 -0
- data/examples/dynamic_workflow.rb +177 -0
- data/examples/event_handler.rb +94 -0
- data/examples/event_listener_examples.rb +430 -0
- data/examples/helloworld/greetings_worker.rb +24 -0
- data/examples/helloworld/helloworld.rb +99 -0
- data/examples/kitchensink.rb +213 -0
- data/examples/metadata_journey.rb +189 -0
- data/examples/metrics_example.rb +284 -0
- data/examples/new_dsl_demo.rb +141 -0
- data/examples/orkes/http_poll.rb +83 -0
- data/examples/orkes/secrets_example.rb +69 -0
- data/examples/orkes/wait_for_webhook.rb +90 -0
- data/examples/prompt_journey.rb +245 -0
- data/examples/rag_workflow.rb +167 -0
- data/examples/schedule_journey.rb +244 -0
- data/examples/simple_worker.rb +125 -0
- data/examples/simple_workflow.rb +89 -0
- data/examples/task_context_example.rb +257 -0
- data/examples/task_listener_example.rb +192 -0
- data/examples/worker_configuration_example.rb +282 -0
- data/examples/workflow_dsl.rb +316 -0
- data/examples/workflow_ops.rb +305 -0
- data/lib/conductor/client/authorization_client.rb +238 -0
- data/lib/conductor/client/integration_client.rb +108 -0
- data/lib/conductor/client/metadata_client.rb +139 -0
- data/lib/conductor/client/prompt_client.rb +58 -0
- data/lib/conductor/client/scheduler_client.rb +132 -0
- data/lib/conductor/client/schema_client.rb +32 -0
- data/lib/conductor/client/secret_client.rb +48 -0
- data/lib/conductor/client/task_client.rb +168 -0
- data/lib/conductor/client/workflow_client.rb +242 -0
- data/lib/conductor/configuration/authentication_settings.rb +17 -0
- data/lib/conductor/configuration.rb +103 -0
- data/lib/conductor/exceptions.rb +86 -0
- data/lib/conductor/http/api/application_resource_api.rb +107 -0
- data/lib/conductor/http/api/authorization_resource_api.rb +56 -0
- data/lib/conductor/http/api/event_resource_api.rb +133 -0
- data/lib/conductor/http/api/gateway_auth_resource_api.rb +48 -0
- data/lib/conductor/http/api/group_resource_api.rb +76 -0
- data/lib/conductor/http/api/integration_resource_api.rb +145 -0
- data/lib/conductor/http/api/metadata_resource_api.rb +231 -0
- data/lib/conductor/http/api/prompt_resource_api.rb +81 -0
- data/lib/conductor/http/api/role_resource_api.rb +60 -0
- data/lib/conductor/http/api/scheduler_resource_api.rb +211 -0
- data/lib/conductor/http/api/schema_resource_api.rb +82 -0
- data/lib/conductor/http/api/secret_resource_api.rb +134 -0
- data/lib/conductor/http/api/task_resource_api.rb +321 -0
- data/lib/conductor/http/api/token_resource_api.rb +42 -0
- data/lib/conductor/http/api/user_resource_api.rb +59 -0
- data/lib/conductor/http/api/workflow_bulk_resource_api.rb +91 -0
- data/lib/conductor/http/api/workflow_resource_api.rb +451 -0
- data/lib/conductor/http/api_client.rb +437 -0
- data/lib/conductor/http/models/authentication_config.rb +67 -0
- data/lib/conductor/http/models/authorization_request.rb +39 -0
- data/lib/conductor/http/models/base_model.rb +162 -0
- data/lib/conductor/http/models/bulk_response.rb +39 -0
- data/lib/conductor/http/models/conductor_application.rb +39 -0
- data/lib/conductor/http/models/conductor_user.rb +53 -0
- data/lib/conductor/http/models/create_or_update_application_request.rb +24 -0
- data/lib/conductor/http/models/create_or_update_role_request.rb +27 -0
- data/lib/conductor/http/models/event_handler.rb +130 -0
- data/lib/conductor/http/models/generate_token_request.rb +27 -0
- data/lib/conductor/http/models/group.rb +36 -0
- data/lib/conductor/http/models/integration.rb +70 -0
- data/lib/conductor/http/models/integration_api.rb +53 -0
- data/lib/conductor/http/models/integration_api_update.rb +43 -0
- data/lib/conductor/http/models/integration_update.rb +36 -0
- data/lib/conductor/http/models/permission.rb +24 -0
- data/lib/conductor/http/models/poll_data.rb +33 -0
- data/lib/conductor/http/models/prompt_template.rb +59 -0
- data/lib/conductor/http/models/prompt_template_test_request.rb +43 -0
- data/lib/conductor/http/models/rerun_workflow_request.rb +37 -0
- data/lib/conductor/http/models/role.rb +27 -0
- data/lib/conductor/http/models/schema_def.rb +59 -0
- data/lib/conductor/http/models/search_result.rb +187 -0
- data/lib/conductor/http/models/skip_task_request.rb +27 -0
- data/lib/conductor/http/models/start_workflow_request.rb +68 -0
- data/lib/conductor/http/models/subject_ref.rb +35 -0
- data/lib/conductor/http/models/tag_object.rb +36 -0
- data/lib/conductor/http/models/target_ref.rb +39 -0
- data/lib/conductor/http/models/task.rb +156 -0
- data/lib/conductor/http/models/task_def.rb +95 -0
- data/lib/conductor/http/models/task_exec_log.rb +30 -0
- data/lib/conductor/http/models/task_result.rb +115 -0
- data/lib/conductor/http/models/task_result_status.rb +24 -0
- data/lib/conductor/http/models/token.rb +33 -0
- data/lib/conductor/http/models/upsert_group_request.rb +30 -0
- data/lib/conductor/http/models/upsert_user_request.rb +39 -0
- data/lib/conductor/http/models/workflow.rb +202 -0
- data/lib/conductor/http/models/workflow_def.rb +73 -0
- data/lib/conductor/http/models/workflow_schedule.rb +100 -0
- data/lib/conductor/http/models/workflow_state_update.rb +30 -0
- data/lib/conductor/http/models/workflow_status_constants.rb +57 -0
- data/lib/conductor/http/models/workflow_task.rb +169 -0
- data/lib/conductor/http/models/workflow_test_request.rb +67 -0
- data/lib/conductor/http/rest_client.rb +211 -0
- data/lib/conductor/orkes/models/access_key.rb +56 -0
- data/lib/conductor/orkes/models/granted_permission.rb +27 -0
- data/lib/conductor/orkes/models/metadata_tag.rb +15 -0
- data/lib/conductor/orkes/models/rate_limit_tag.rb +15 -0
- data/lib/conductor/orkes/orkes_clients.rb +69 -0
- data/lib/conductor/version.rb +5 -0
- data/lib/conductor/worker/events/conductor_event.rb +40 -0
- data/lib/conductor/worker/events/global_dispatcher.rb +37 -0
- data/lib/conductor/worker/events/http_events.rb +25 -0
- data/lib/conductor/worker/events/listener_registry.rb +40 -0
- data/lib/conductor/worker/events/listeners.rb +34 -0
- data/lib/conductor/worker/events/sync_event_dispatcher.rb +78 -0
- data/lib/conductor/worker/events/task_runner_events.rb +271 -0
- data/lib/conductor/worker/events/workflow_events.rb +49 -0
- data/lib/conductor/worker/fiber_executor.rb +532 -0
- data/lib/conductor/worker/ractor_task_runner.rb +501 -0
- data/lib/conductor/worker/task_context.rb +114 -0
- data/lib/conductor/worker/task_definition_registrar.rb +322 -0
- data/lib/conductor/worker/task_handler.rb +360 -0
- data/lib/conductor/worker/task_in_progress.rb +60 -0
- data/lib/conductor/worker/task_runner.rb +538 -0
- data/lib/conductor/worker/telemetry/metrics_collector.rb +196 -0
- data/lib/conductor/worker/telemetry/prometheus_backend.rb +224 -0
- data/lib/conductor/worker/worker.rb +355 -0
- data/lib/conductor/worker/worker_config.rb +154 -0
- data/lib/conductor/worker/worker_registry.rb +71 -0
- data/lib/conductor/workflow/dsl/input_ref.rb +37 -0
- data/lib/conductor/workflow/dsl/output_ref.rb +44 -0
- data/lib/conductor/workflow/dsl/parallel_builder.rb +49 -0
- data/lib/conductor/workflow/dsl/switch_builder.rb +74 -0
- data/lib/conductor/workflow/dsl/task_ref.rb +178 -0
- data/lib/conductor/workflow/dsl/workflow_builder.rb +1016 -0
- data/lib/conductor/workflow/dsl/workflow_definition.rb +150 -0
- data/lib/conductor/workflow/llm/chat_message.rb +47 -0
- data/lib/conductor/workflow/llm/embedding_model.rb +19 -0
- data/lib/conductor/workflow/llm/tool_call.rb +43 -0
- data/lib/conductor/workflow/llm/tool_spec.rb +46 -0
- data/lib/conductor/workflow/task_type.rb +68 -0
- data/lib/conductor/workflow/timeout_policy.rb +31 -0
- data/lib/conductor/workflow/workflow_executor.rb +373 -0
- data/lib/conductor.rb +192 -0
- metadata +359 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require_relative '../configuration'
|
|
5
|
+
require_relative '../http/api_client'
|
|
6
|
+
require_relative '../http/api/workflow_resource_api'
|
|
7
|
+
|
|
8
|
+
module Conductor
|
|
9
|
+
module Client
|
|
10
|
+
# WorkflowClient - High-level client for workflow operations
|
|
11
|
+
class WorkflowClient
|
|
12
|
+
attr_reader :workflow_api
|
|
13
|
+
|
|
14
|
+
# Initialize WorkflowClient
|
|
15
|
+
# @param [Configuration] configuration Optional configuration
|
|
16
|
+
def initialize(configuration = nil)
|
|
17
|
+
@configuration = configuration || Configuration.new
|
|
18
|
+
api_client = Http::ApiClient.new(configuration: @configuration)
|
|
19
|
+
@workflow_api = Http::Api::WorkflowResourceApi.new(api_client)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Start a new workflow
|
|
23
|
+
# @param [StartWorkflowRequest] request Start workflow request
|
|
24
|
+
# @return [String] Workflow ID
|
|
25
|
+
def start_workflow(request)
|
|
26
|
+
@workflow_api.start_workflow(request)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Start a workflow with name and input, or with a StartWorkflowRequest
|
|
30
|
+
# @param [String, StartWorkflowRequest] name_or_request Workflow name or StartWorkflowRequest
|
|
31
|
+
# @param [Hash] input Workflow input data (default: {}, ignored if request object passed)
|
|
32
|
+
# @param [Integer] version Workflow version (optional, ignored if request object passed)
|
|
33
|
+
# @param [String] correlation_id Correlation ID (optional, ignored if request object passed)
|
|
34
|
+
# @return [String] Workflow ID
|
|
35
|
+
def start(name_or_request, input: {}, version: nil, correlation_id: nil)
|
|
36
|
+
# Handle both StartWorkflowRequest objects and simple name/input arguments
|
|
37
|
+
if name_or_request.is_a?(Http::Models::StartWorkflowRequest)
|
|
38
|
+
start_workflow(name_or_request)
|
|
39
|
+
else
|
|
40
|
+
request = Http::Models::StartWorkflowRequest.new(
|
|
41
|
+
name: name_or_request,
|
|
42
|
+
input: input,
|
|
43
|
+
version: version,
|
|
44
|
+
correlation_id: correlation_id
|
|
45
|
+
)
|
|
46
|
+
start_workflow(request)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get workflow execution status
|
|
51
|
+
# @param [String] workflow_id Workflow ID
|
|
52
|
+
# @param [Boolean] include_tasks Include task details (default: true)
|
|
53
|
+
# @return [Workflow] Workflow object
|
|
54
|
+
def get_workflow(workflow_id, include_tasks: true)
|
|
55
|
+
@workflow_api.get_execution_status(workflow_id, include_tasks: include_tasks)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Delete a workflow
|
|
59
|
+
# @param [String] workflow_id Workflow ID
|
|
60
|
+
# @param [Boolean] archive_workflow Archive workflow before deleting (default: true)
|
|
61
|
+
# @return [void]
|
|
62
|
+
def delete_workflow(workflow_id, archive_workflow: true)
|
|
63
|
+
@workflow_api.delete(workflow_id, archive_workflow: archive_workflow)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Terminate a running workflow
|
|
67
|
+
# @param [String] workflow_id Workflow ID
|
|
68
|
+
# @param [String] reason Termination reason (optional)
|
|
69
|
+
# @return [void]
|
|
70
|
+
def terminate_workflow(workflow_id, reason: nil)
|
|
71
|
+
@workflow_api.terminate(workflow_id, reason: reason)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Pause a workflow
|
|
75
|
+
# @param [String] workflow_id Workflow ID
|
|
76
|
+
# @return [void]
|
|
77
|
+
def pause_workflow(workflow_id)
|
|
78
|
+
@workflow_api.pause_workflow(workflow_id)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Resume a paused workflow
|
|
82
|
+
# @param [String] workflow_id Workflow ID
|
|
83
|
+
# @return [void]
|
|
84
|
+
def resume_workflow(workflow_id)
|
|
85
|
+
@workflow_api.resume_workflow(workflow_id)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Restart a completed workflow
|
|
89
|
+
# @param [String] workflow_id Workflow ID
|
|
90
|
+
# @param [Boolean] use_latest_def Use latest workflow definition (default: false)
|
|
91
|
+
# @return [void]
|
|
92
|
+
def restart_workflow(workflow_id, use_latest_def: false)
|
|
93
|
+
@workflow_api.restart(workflow_id, use_latest_def: use_latest_def)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Retry a failed workflow
|
|
97
|
+
# @param [String] workflow_id Workflow ID
|
|
98
|
+
# @param [Boolean] resume_subworkflow_tasks Resume subworkflow tasks (default: false)
|
|
99
|
+
# @return [void]
|
|
100
|
+
def retry_workflow(workflow_id, resume_subworkflow_tasks: false)
|
|
101
|
+
@workflow_api.retry(workflow_id, resume_subworkflow_tasks: resume_subworkflow_tasks)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Rerun a workflow from a specific task
|
|
105
|
+
# @param [String] workflow_id Workflow ID
|
|
106
|
+
# @param [RerunWorkflowRequest] rerun_request Rerun request
|
|
107
|
+
# @return [String] New workflow ID
|
|
108
|
+
def rerun_workflow(workflow_id, rerun_request)
|
|
109
|
+
@workflow_api.rerun(workflow_id, rerun_request)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get workflows by correlation ID
|
|
113
|
+
# @param [String] name Workflow name
|
|
114
|
+
# @param [String] correlation_id Correlation ID
|
|
115
|
+
# @param [Boolean] include_closed Include closed workflows (default: false)
|
|
116
|
+
# @param [Boolean] include_tasks Include task details (default: false)
|
|
117
|
+
# @return [Array<Workflow>] List of workflows
|
|
118
|
+
def get_by_correlation_id(name, correlation_id, include_closed: false, include_tasks: false)
|
|
119
|
+
@workflow_api.get_workflows(name, correlation_id, include_closed: include_closed, include_tasks: include_tasks)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Get running workflows by name
|
|
123
|
+
# @param [String] name Workflow name
|
|
124
|
+
# @param [Integer] version Workflow version (optional)
|
|
125
|
+
# @param [Integer] start_time Start time in epoch millis (optional)
|
|
126
|
+
# @param [Integer] end_time End time in epoch millis (optional)
|
|
127
|
+
# @return [Array<String>] List of workflow IDs
|
|
128
|
+
def get_running_workflows(name, version: nil, start_time: nil, end_time: nil)
|
|
129
|
+
@workflow_api.get_running_workflow(name, version: version, start_time: start_time, end_time: end_time)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Register a workflow definition
|
|
133
|
+
# @param [WorkflowDef] workflow_def Workflow definition to register
|
|
134
|
+
# @param [Boolean] overwrite Overwrite existing definition (default: false)
|
|
135
|
+
# @return [void]
|
|
136
|
+
def register_workflow(workflow_def, overwrite: false)
|
|
137
|
+
@workflow_api.register_workflow(workflow_def, overwrite: overwrite)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Get a workflow definition
|
|
141
|
+
# @param [String] name Workflow name
|
|
142
|
+
# @param [Integer] version Workflow version (optional)
|
|
143
|
+
# @return [WorkflowDef] Workflow definition
|
|
144
|
+
def get_workflow_def(name, version: nil)
|
|
145
|
+
@workflow_api.get_workflow_def(name, version: version)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Delete a workflow definition
|
|
149
|
+
# @param [String] name Workflow name
|
|
150
|
+
# @param [Integer] version Workflow version
|
|
151
|
+
# @return [void]
|
|
152
|
+
def unregister_workflow(name, version:)
|
|
153
|
+
@workflow_api.unregister_workflow(name, version: version)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Execute a workflow synchronously and wait for completion
|
|
157
|
+
# @param [StartWorkflowRequest] request Start workflow request
|
|
158
|
+
# @param [String] request_id Unique request ID (optional, auto-generated)
|
|
159
|
+
# @param [String] wait_until_task_ref Wait until task ref (optional)
|
|
160
|
+
# @param [Integer] wait_for_seconds Max wait time (default: 30)
|
|
161
|
+
# @return [WorkflowRun] Workflow run result
|
|
162
|
+
def execute_workflow(request, request_id: nil, wait_until_task_ref: nil, wait_for_seconds: 30)
|
|
163
|
+
request_id ||= SecureRandom.uuid
|
|
164
|
+
@workflow_api.execute_workflow(
|
|
165
|
+
request,
|
|
166
|
+
name: request.name,
|
|
167
|
+
version: request.version || 1,
|
|
168
|
+
request_id: request_id,
|
|
169
|
+
wait_until_task_ref: wait_until_task_ref,
|
|
170
|
+
wait_for_seconds: wait_for_seconds
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Search for workflows
|
|
175
|
+
# @param [Integer] start Start index (default: 0)
|
|
176
|
+
# @param [Integer] size Page size (default: 100)
|
|
177
|
+
# @param [String] free_text Free text search (default: '*')
|
|
178
|
+
# @param [String] query Query string (optional)
|
|
179
|
+
# @return [SearchResult] Search results
|
|
180
|
+
def search(start: 0, size: 100, free_text: '*', query: nil)
|
|
181
|
+
@workflow_api.search(start: start, size: size, free_text: free_text, query: query)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Get workflows by multiple correlation IDs (batch)
|
|
185
|
+
# @param [String] name Workflow name
|
|
186
|
+
# @param [Array<String>] correlation_ids Correlation IDs
|
|
187
|
+
# @param [Boolean] include_closed Include closed workflows (default: false)
|
|
188
|
+
# @param [Boolean] include_tasks Include task details (default: false)
|
|
189
|
+
# @return [Hash<String, Array<Workflow>>]
|
|
190
|
+
def get_by_correlation_ids(name, correlation_ids, include_closed: false, include_tasks: false)
|
|
191
|
+
@workflow_api.get_workflows_batch(name, correlation_ids, include_closed: include_closed,
|
|
192
|
+
include_tasks: include_tasks)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Update workflow variables
|
|
196
|
+
# @param [String] workflow_id Workflow ID
|
|
197
|
+
# @param [Hash] variables Variables to update
|
|
198
|
+
# @return [Workflow]
|
|
199
|
+
def update_variables(workflow_id, variables)
|
|
200
|
+
@workflow_api.update_workflow_state(workflow_id, variables)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Update workflow and task state
|
|
204
|
+
# @param [String] workflow_id Workflow ID
|
|
205
|
+
# @param [WorkflowStateUpdate] state_update State update request
|
|
206
|
+
# @param [String] wait_until_task_ref Wait until task ref (optional)
|
|
207
|
+
# @param [Integer] wait_for_seconds Wait time (default: 10)
|
|
208
|
+
# @return [WorkflowRun]
|
|
209
|
+
def update_state(workflow_id, state_update, wait_until_task_ref: nil, wait_for_seconds: 10)
|
|
210
|
+
@workflow_api.update_workflow_and_task_state(
|
|
211
|
+
workflow_id, state_update,
|
|
212
|
+
wait_until_task_ref: wait_until_task_ref,
|
|
213
|
+
wait_for_seconds: wait_for_seconds
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Test a workflow with mocked task outputs
|
|
218
|
+
# @param [WorkflowTestRequest] request Test request
|
|
219
|
+
# @return [Workflow]
|
|
220
|
+
def test_workflow(request)
|
|
221
|
+
@workflow_api.test_workflow(request)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Skip a task in a running workflow
|
|
225
|
+
# @param [String] workflow_id Workflow ID
|
|
226
|
+
# @param [String] task_reference_name Task reference name
|
|
227
|
+
# @param [SkipTaskRequest] request Skip task request (optional)
|
|
228
|
+
# @return [void]
|
|
229
|
+
def skip_task_from_workflow(workflow_id, task_reference_name, request: nil)
|
|
230
|
+
@workflow_api.skip_task_from_workflow(workflow_id, task_reference_name, request: request)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Remove (permanently delete) a workflow
|
|
234
|
+
# @param [String] workflow_id Workflow ID
|
|
235
|
+
# @param [Boolean] archive_workflow Archive before deleting (default: true)
|
|
236
|
+
# @return [void]
|
|
237
|
+
def remove_workflow(workflow_id, archive_workflow: true)
|
|
238
|
+
@workflow_api.delete(workflow_id, archive_workflow: archive_workflow)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Conductor
|
|
4
|
+
# Authentication settings for Conductor server
|
|
5
|
+
class AuthenticationSettings
|
|
6
|
+
attr_accessor :key_id, :key_secret
|
|
7
|
+
|
|
8
|
+
def initialize(key_id: nil, key_secret: nil)
|
|
9
|
+
@key_id = key_id || ENV.fetch('CONDUCTOR_AUTH_KEY', nil)
|
|
10
|
+
@key_secret = key_secret || ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configured?
|
|
14
|
+
!key_id.nil? && !key_secret.nil? && !key_id.empty? && !key_secret.empty?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'configuration/authentication_settings'
|
|
4
|
+
|
|
5
|
+
module Conductor
|
|
6
|
+
# Configuration for Conductor client
|
|
7
|
+
class Configuration
|
|
8
|
+
# Class-level auth token cache (shared across instances, like Python SDK)
|
|
9
|
+
@auth_token = nil
|
|
10
|
+
@token_update_time = 0
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor :auth_token, :token_update_time
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_accessor :base_url, :server_api_url, :debug, :authentication_settings,
|
|
17
|
+
:verify_ssl, :ssl_ca_cert, :cert_file, :key_file,
|
|
18
|
+
:proxy, :auth_token_ttl_min
|
|
19
|
+
|
|
20
|
+
attr_reader :host, :ui_host
|
|
21
|
+
|
|
22
|
+
def initialize(base_url: nil, server_api_url: nil, debug: false,
|
|
23
|
+
authentication_settings: nil, auth_key: nil, auth_secret: nil,
|
|
24
|
+
auth_token_ttl_min: 45, verify_ssl: true)
|
|
25
|
+
@debug = debug
|
|
26
|
+
@verify_ssl = verify_ssl
|
|
27
|
+
@ssl_ca_cert = nil
|
|
28
|
+
@cert_file = nil
|
|
29
|
+
@key_file = nil
|
|
30
|
+
@proxy = nil
|
|
31
|
+
@auth_token_ttl_min = auth_token_ttl_min
|
|
32
|
+
|
|
33
|
+
# Resolve server URL
|
|
34
|
+
@host = resolve_host(server_api_url, base_url)
|
|
35
|
+
@ui_host = resolve_ui_host
|
|
36
|
+
|
|
37
|
+
# Resolve authentication
|
|
38
|
+
@authentication_settings = resolve_auth_settings(authentication_settings, auth_key, auth_secret)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def auth_token_ttl_msec
|
|
42
|
+
@auth_token_ttl_min * 60 * 1000
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def auth_configured?
|
|
46
|
+
@authentication_settings&.configured? || false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def disable_auth!
|
|
50
|
+
@authentication_settings = nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def update_token(token)
|
|
54
|
+
self.class.auth_token = token
|
|
55
|
+
self.class.token_update_time = (Time.now.to_f * 1000).to_i
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def auth_token
|
|
59
|
+
self.class.auth_token
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def token_update_time
|
|
63
|
+
self.class.token_update_time
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Alias for server URL (used in some places)
|
|
67
|
+
def server_url
|
|
68
|
+
@host
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def resolve_host(server_api_url, base_url)
|
|
74
|
+
return server_api_url if server_api_url
|
|
75
|
+
return "#{base_url}/api" if base_url
|
|
76
|
+
|
|
77
|
+
# Fall back to environment variable or default
|
|
78
|
+
env_url = ENV.fetch('CONDUCTOR_SERVER_URL', nil)
|
|
79
|
+
return env_url if env_url
|
|
80
|
+
|
|
81
|
+
'http://localhost:8080/api'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_ui_host
|
|
85
|
+
env_ui_url = ENV.fetch('CONDUCTOR_UI_SERVER_URL', nil)
|
|
86
|
+
return env_ui_url if env_ui_url
|
|
87
|
+
|
|
88
|
+
# Derive UI host from API host
|
|
89
|
+
@host.sub(%r{/api$}, '')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def resolve_auth_settings(auth_settings, auth_key, auth_secret)
|
|
93
|
+
return auth_settings if auth_settings
|
|
94
|
+
|
|
95
|
+
# Try explicit parameters first
|
|
96
|
+
return AuthenticationSettings.new(key_id: auth_key, key_secret: auth_secret) if auth_key && auth_secret
|
|
97
|
+
|
|
98
|
+
# Try environment variables
|
|
99
|
+
settings = AuthenticationSettings.new
|
|
100
|
+
settings.configured? ? settings : nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Conductor
|
|
4
|
+
# Base exception for all Conductor errors
|
|
5
|
+
class ConductorError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Configuration error (invalid settings, missing dependencies)
|
|
8
|
+
class ConfigurationError < ConductorError; end
|
|
9
|
+
|
|
10
|
+
# API-level exception (HTTP errors from server)
|
|
11
|
+
class ApiError < ConductorError
|
|
12
|
+
attr_reader :status, :code, :reason, :body, :headers
|
|
13
|
+
|
|
14
|
+
def initialize(message = nil, status: nil, code: nil, reason: nil, body: nil, headers: nil)
|
|
15
|
+
@status = status
|
|
16
|
+
@code = code || status
|
|
17
|
+
@reason = reason
|
|
18
|
+
@body = body
|
|
19
|
+
@headers = headers
|
|
20
|
+
super(message || build_message)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def not_found?
|
|
24
|
+
@code == 404
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def build_message
|
|
30
|
+
msg = "(#{@status}) #{@reason}"
|
|
31
|
+
msg += "\nBody: #{@body}" if @body
|
|
32
|
+
msg
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Authorization error (401/403 with special token handling)
|
|
37
|
+
class AuthorizationError < ApiError
|
|
38
|
+
attr_reader :error_code
|
|
39
|
+
|
|
40
|
+
def initialize(message = nil, status: nil, body: nil, headers: nil)
|
|
41
|
+
@error_code = parse_error_code(body)
|
|
42
|
+
super(message, status: status, body: body, headers: headers)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def token_expired?
|
|
46
|
+
@error_code == 'EXPIRED_TOKEN'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def invalid_token?
|
|
50
|
+
@error_code == 'INVALID_TOKEN'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def parse_error_code(body)
|
|
56
|
+
return nil unless body
|
|
57
|
+
|
|
58
|
+
data = begin
|
|
59
|
+
JSON.parse(body)
|
|
60
|
+
rescue StandardError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
data&.dig('error') || ''
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def build_message
|
|
67
|
+
msg = "Authorization error: #{@error_code} (status: #{@status})"
|
|
68
|
+
msg += "\nReason: #{@reason}" if @reason
|
|
69
|
+
msg += "\nBody: #{@body}" if @body
|
|
70
|
+
msg
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Non-retryable worker error (terminal failure)
|
|
75
|
+
class NonRetryableError < ConductorError; end
|
|
76
|
+
|
|
77
|
+
# Task in progress (not an error - for long-running tasks)
|
|
78
|
+
class TaskInProgress
|
|
79
|
+
attr_reader :callback_after_seconds, :output
|
|
80
|
+
|
|
81
|
+
def initialize(callback_after: 60, output: {})
|
|
82
|
+
@callback_after_seconds = callback_after
|
|
83
|
+
@output = output
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../api_client'
|
|
4
|
+
|
|
5
|
+
module Conductor
|
|
6
|
+
module Http
|
|
7
|
+
module Api
|
|
8
|
+
# ApplicationResourceApi - API for application management operations (Orkes)
|
|
9
|
+
class ApplicationResourceApi
|
|
10
|
+
attr_accessor :api_client
|
|
11
|
+
|
|
12
|
+
def initialize(api_client = nil)
|
|
13
|
+
@api_client = api_client || ApiClient.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Create an application
|
|
17
|
+
def create_application(body)
|
|
18
|
+
@api_client.call_api('/applications', 'POST', body: body, return_type: 'ConductorApplication',
|
|
19
|
+
return_http_data_only: true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get an application by ID
|
|
23
|
+
def get_application(id)
|
|
24
|
+
@api_client.call_api('/applications/{id}', 'GET', path_params: { id: id },
|
|
25
|
+
return_type: 'ConductorApplication', return_http_data_only: true)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# List all applications
|
|
29
|
+
def list_applications
|
|
30
|
+
@api_client.call_api('/applications', 'GET', return_type: 'Array<ConductorApplication>',
|
|
31
|
+
return_http_data_only: true)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Update an application
|
|
35
|
+
def update_application(body, id)
|
|
36
|
+
@api_client.call_api('/applications/{id}', 'PUT', path_params: { id: id }, body: body,
|
|
37
|
+
return_type: 'ConductorApplication', return_http_data_only: true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Delete an application
|
|
41
|
+
def delete_application(id)
|
|
42
|
+
@api_client.call_api('/applications/{id}', 'DELETE', path_params: { id: id }, return_http_data_only: true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Add a role to an application
|
|
46
|
+
def add_role_to_application_user(application_id, role)
|
|
47
|
+
@api_client.call_api('/applications/{applicationId}/roles/{role}', 'POST',
|
|
48
|
+
path_params: { applicationId: application_id, role: role }, return_http_data_only: true)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Remove a role from an application
|
|
52
|
+
def remove_role_from_application_user(application_id, role)
|
|
53
|
+
@api_client.call_api('/applications/{applicationId}/roles/{role}', 'DELETE',
|
|
54
|
+
path_params: { applicationId: application_id, role: role }, return_http_data_only: true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Set tags for an application
|
|
58
|
+
def put_tags_for_application(tags, id)
|
|
59
|
+
@api_client.call_api('/applications/{id}/tags', 'PUT', path_params: { id: id }, body: tags,
|
|
60
|
+
return_http_data_only: true)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get tags for an application
|
|
64
|
+
def get_tags_for_application(id)
|
|
65
|
+
@api_client.call_api('/applications/{id}/tags', 'GET', path_params: { id: id },
|
|
66
|
+
return_type: 'Array<TagObject>', return_http_data_only: true)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Delete tags for an application
|
|
70
|
+
def delete_tags_for_application(tags, id)
|
|
71
|
+
@api_client.call_api('/applications/{id}/tags', 'DELETE', path_params: { id: id }, body: tags,
|
|
72
|
+
return_http_data_only: true)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Create an access key for an application
|
|
76
|
+
def create_access_key(id)
|
|
77
|
+
@api_client.call_api('/applications/{id}/accessKeys', 'POST', path_params: { id: id }, return_type: 'Object',
|
|
78
|
+
return_http_data_only: true)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get access keys for an application
|
|
82
|
+
def get_access_keys(id)
|
|
83
|
+
@api_client.call_api('/applications/{id}/accessKeys', 'GET', path_params: { id: id },
|
|
84
|
+
return_type: 'Array<Object>', return_http_data_only: true)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Toggle access key status
|
|
88
|
+
def toggle_access_key_status(application_id, key_id)
|
|
89
|
+
@api_client.call_api('/applications/{applicationId}/accessKeys/{keyId}/status', 'POST',
|
|
90
|
+
path_params: { applicationId: application_id, keyId: key_id }, return_type: 'Object', return_http_data_only: true)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Delete an access key
|
|
94
|
+
def delete_access_key(application_id, key_id)
|
|
95
|
+
@api_client.call_api('/applications/{applicationId}/accessKeys/{keyId}', 'DELETE',
|
|
96
|
+
path_params: { applicationId: application_id, keyId: key_id }, return_http_data_only: true)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get application by access key ID
|
|
100
|
+
def get_app_by_access_key_id(access_key_id)
|
|
101
|
+
@api_client.call_api('/applications/key/{accessKeyId}', 'GET', path_params: { accessKeyId: access_key_id },
|
|
102
|
+
return_type: 'Object', return_http_data_only: true)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../api_client'
|
|
4
|
+
|
|
5
|
+
module Conductor
|
|
6
|
+
module Http
|
|
7
|
+
module Api
|
|
8
|
+
# AuthorizationResourceApi - API for permission management operations (Orkes)
|
|
9
|
+
class AuthorizationResourceApi
|
|
10
|
+
attr_accessor :api_client
|
|
11
|
+
|
|
12
|
+
def initialize(api_client = nil)
|
|
13
|
+
@api_client = api_client || ApiClient.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Grant permissions
|
|
17
|
+
# @param [AuthorizationRequest] body Authorization request
|
|
18
|
+
# @return [void]
|
|
19
|
+
def grant_permissions(body)
|
|
20
|
+
@api_client.call_api(
|
|
21
|
+
'/auth/authorization',
|
|
22
|
+
'POST',
|
|
23
|
+
body: body,
|
|
24
|
+
return_http_data_only: true
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get permissions for a target
|
|
29
|
+
# @param [String] type Target type
|
|
30
|
+
# @param [String] id Target ID
|
|
31
|
+
# @return [Hash]
|
|
32
|
+
def get_permissions(type, id)
|
|
33
|
+
@api_client.call_api(
|
|
34
|
+
'/auth/authorization/{type}/{id}',
|
|
35
|
+
'GET',
|
|
36
|
+
path_params: { type: type, id: id },
|
|
37
|
+
return_type: 'Object',
|
|
38
|
+
return_http_data_only: true
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Remove permissions
|
|
43
|
+
# @param [AuthorizationRequest] body Authorization request
|
|
44
|
+
# @return [void]
|
|
45
|
+
def remove_permissions(body)
|
|
46
|
+
@api_client.call_api(
|
|
47
|
+
'/auth/authorization',
|
|
48
|
+
'DELETE',
|
|
49
|
+
body: body,
|
|
50
|
+
return_http_data_only: true
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|