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,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Example demonstrating Prometheus metrics collection and HTTP endpoint exposure.
|
|
5
|
+
#
|
|
6
|
+
# This example shows how to:
|
|
7
|
+
# - Enable Prometheus metrics collection for task execution
|
|
8
|
+
# - Expose metrics via HTTP endpoint for scraping
|
|
9
|
+
# - Track task poll times, execution times, errors, and more
|
|
10
|
+
# - Integrate with Prometheus monitoring
|
|
11
|
+
#
|
|
12
|
+
# Metrics collected (canonical harmonized set):
|
|
13
|
+
# - task_poll_total: Total number of task polls
|
|
14
|
+
# - task_poll_time_seconds: Task poll duration (with status label)
|
|
15
|
+
# - task_poll_error_total: Poll errors by exception type
|
|
16
|
+
# - task_execution_started_total: Tasks dispatched to worker function
|
|
17
|
+
# - task_execute_time_seconds: Task execution duration (with status label)
|
|
18
|
+
# - task_execute_error_total: Execution errors by exception
|
|
19
|
+
# - task_result_size_bytes: Task result payload size
|
|
20
|
+
# - task_update_error_total: Failed task updates (CRITICAL)
|
|
21
|
+
# - task_update_time_seconds: Task result update latency
|
|
22
|
+
# - task_paused_total: Workers paused
|
|
23
|
+
# - active_workers: Current active worker count (gauge)
|
|
24
|
+
# - workflow_start_error_total: Workflow start failures
|
|
25
|
+
# - workflow_input_size_bytes: Workflow input payload size
|
|
26
|
+
# - http_api_client_request_seconds: HTTP API client latency
|
|
27
|
+
# - thread_uncaught_exceptions_total: Uncaught thread exceptions
|
|
28
|
+
#
|
|
29
|
+
# Requirements:
|
|
30
|
+
# gem 'prometheus-client'
|
|
31
|
+
#
|
|
32
|
+
# Usage:
|
|
33
|
+
# 1. Run this example: ruby metrics_example.rb
|
|
34
|
+
# 2. View metrics: curl http://localhost:9090/metrics
|
|
35
|
+
# 3. Configure Prometheus to scrape: http://localhost:9090/metrics
|
|
36
|
+
#
|
|
37
|
+
# Environment variables:
|
|
38
|
+
# CONDUCTOR_SERVER_URL - Conductor server URL (default: http://localhost:8080/api)
|
|
39
|
+
# CONDUCTOR_AUTH_KEY - Authentication key (optional)
|
|
40
|
+
# CONDUCTOR_AUTH_SECRET - Authentication secret (optional)
|
|
41
|
+
# METRICS_PORT - Port for metrics HTTP server (default: 9090)
|
|
42
|
+
#
|
|
43
|
+
|
|
44
|
+
require 'bundler/setup'
|
|
45
|
+
require 'conductor'
|
|
46
|
+
|
|
47
|
+
# Check if prometheus-client is available
|
|
48
|
+
begin
|
|
49
|
+
require 'prometheus/client'
|
|
50
|
+
PROMETHEUS_AVAILABLE = true
|
|
51
|
+
rescue LoadError
|
|
52
|
+
PROMETHEUS_AVAILABLE = false
|
|
53
|
+
puts 'WARNING: prometheus-client gem not installed.'
|
|
54
|
+
puts 'Install with: gem install prometheus-client'
|
|
55
|
+
puts "Or add to Gemfile: gem 'prometheus-client'"
|
|
56
|
+
puts
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#
|
|
60
|
+
# Example worker tasks
|
|
61
|
+
#
|
|
62
|
+
|
|
63
|
+
# Async HTTP simulation worker
|
|
64
|
+
Conductor::Worker.define('async_http_task', poll_interval: 100, thread_count: 10) do |task|
|
|
65
|
+
url = task.input_data['url'] || 'https://api.example.com/data'
|
|
66
|
+
delay = task.input_data['delay'] || 0.1
|
|
67
|
+
|
|
68
|
+
# Simulate async HTTP request
|
|
69
|
+
sleep(delay)
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
url: url,
|
|
73
|
+
status: 'success',
|
|
74
|
+
timestamp: Time.now.iso8601
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Data processor worker
|
|
79
|
+
Conductor::Worker.define('async_data_processor', poll_interval: 100, thread_count: 10) do |task|
|
|
80
|
+
data = task.input_data['data'] || 'sample data'
|
|
81
|
+
process_time = task.input_data['process_time'] || 0.5
|
|
82
|
+
|
|
83
|
+
# Simulate data processing
|
|
84
|
+
sleep(process_time)
|
|
85
|
+
|
|
86
|
+
# Process the data
|
|
87
|
+
processed = data.upcase
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
original: data,
|
|
91
|
+
processed: processed,
|
|
92
|
+
length: processed.length,
|
|
93
|
+
processed_at: Time.now.iso8601
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Batch processor worker
|
|
98
|
+
Conductor::Worker.define('async_batch_processor', poll_interval: 100, thread_count: 5) do |task|
|
|
99
|
+
items = task.input_data['items'] || []
|
|
100
|
+
|
|
101
|
+
# Process all items
|
|
102
|
+
results = items.map do |item|
|
|
103
|
+
sleep(0.01) # Simulate I/O operation
|
|
104
|
+
"processed_#{item}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
{
|
|
108
|
+
input_count: items.size,
|
|
109
|
+
results: results,
|
|
110
|
+
completed_at: Time.now.iso8601
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# CPU-bound worker
|
|
115
|
+
Conductor::Worker.define('sync_cpu_task', poll_interval: 100, thread_count: 5) do |task|
|
|
116
|
+
n = task.input_data['n'] || 100_000
|
|
117
|
+
|
|
118
|
+
# CPU-bound calculation
|
|
119
|
+
result = (0...n).sum { |i| i * i }
|
|
120
|
+
|
|
121
|
+
{ result: result }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def main
|
|
125
|
+
metrics_port = (ENV['METRICS_PORT'] || 9090).to_i
|
|
126
|
+
|
|
127
|
+
# Configure Conductor connection
|
|
128
|
+
config = Conductor::Configuration.new(
|
|
129
|
+
server_api_url: ENV['CONDUCTOR_SERVER_URL'] || 'http://localhost:8080/api',
|
|
130
|
+
auth_key: ENV.fetch('CONDUCTOR_AUTH_KEY', nil),
|
|
131
|
+
auth_secret: ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
puts '=' * 80
|
|
135
|
+
puts 'Metrics Collection Example'
|
|
136
|
+
puts '=' * 80
|
|
137
|
+
puts
|
|
138
|
+
|
|
139
|
+
if PROMETHEUS_AVAILABLE
|
|
140
|
+
# Create metrics collector with Prometheus backend
|
|
141
|
+
metrics = Conductor::Worker::Telemetry::MetricsCollector.create(backend: :prometheus)
|
|
142
|
+
|
|
143
|
+
# Start metrics HTTP server
|
|
144
|
+
metrics_server = Conductor::Worker::Telemetry::MetricsServer.new(port: metrics_port)
|
|
145
|
+
metrics_server.start
|
|
146
|
+
|
|
147
|
+
puts 'Metrics mode: Prometheus HTTP'
|
|
148
|
+
puts "Metrics HTTP endpoint: http://localhost:#{metrics_port}/metrics"
|
|
149
|
+
puts "Health check: http://localhost:#{metrics_port}/health"
|
|
150
|
+
else
|
|
151
|
+
# Fall back to null metrics (logging only)
|
|
152
|
+
metrics = Conductor::Worker::Telemetry::MetricsCollector.create(backend: :null)
|
|
153
|
+
metrics_server = nil
|
|
154
|
+
|
|
155
|
+
puts 'Metrics mode: Null (prometheus-client gem not installed)'
|
|
156
|
+
puts 'To enable Prometheus metrics, install: gem install prometheus-client'
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
puts
|
|
160
|
+
puts 'Workers available:'
|
|
161
|
+
puts ' - async_http_task: Simulated HTTP requests (I/O-bound)'
|
|
162
|
+
puts ' - async_data_processor: Data processing'
|
|
163
|
+
puts ' - async_batch_processor: Batch processing'
|
|
164
|
+
puts ' - sync_cpu_task: CPU-bound calculations'
|
|
165
|
+
puts
|
|
166
|
+
puts 'Try these commands:'
|
|
167
|
+
puts " curl http://localhost:#{metrics_port}/metrics"
|
|
168
|
+
puts " watch -n 1 'curl -s http://localhost:#{metrics_port}/metrics | grep task_poll_total'"
|
|
169
|
+
puts
|
|
170
|
+
puts 'Press Ctrl+C to stop...'
|
|
171
|
+
puts '=' * 80
|
|
172
|
+
puts
|
|
173
|
+
|
|
174
|
+
begin
|
|
175
|
+
# Create task handler with metrics enabled
|
|
176
|
+
handler = Conductor::Worker::TaskHandler.new(
|
|
177
|
+
configuration: config,
|
|
178
|
+
scan_for_annotated_workers: true,
|
|
179
|
+
event_listeners: [metrics]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Handle graceful shutdown
|
|
183
|
+
shutdown = false
|
|
184
|
+
Signal.trap('INT') do
|
|
185
|
+
puts "\nShutting down gracefully..."
|
|
186
|
+
shutdown = true
|
|
187
|
+
handler.stop
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
handler.start
|
|
191
|
+
handler.join unless shutdown
|
|
192
|
+
rescue Interrupt
|
|
193
|
+
puts 'Interrupted!'
|
|
194
|
+
rescue StandardError => e
|
|
195
|
+
puts "Error: #{e.message}"
|
|
196
|
+
puts e.backtrace.first(5).join("\n")
|
|
197
|
+
ensure
|
|
198
|
+
metrics_server&.stop
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
puts "\nWorkers stopped. Goodbye!"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Alternative: Custom metrics backend example
|
|
205
|
+
class CustomMetricsBackend
|
|
206
|
+
def initialize
|
|
207
|
+
@counters = Hash.new(0)
|
|
208
|
+
@histograms = Hash.new { |h, k| h[k] = [] }
|
|
209
|
+
@mutex = Mutex.new
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def increment(name, labels: {})
|
|
213
|
+
key = "#{name}:#{labels.sort.to_h}"
|
|
214
|
+
@mutex.synchronize { @counters[key] += 1 }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def observe(name, value, labels: {})
|
|
218
|
+
key = "#{name}:#{labels.sort.to_h}"
|
|
219
|
+
@mutex.synchronize { @histograms[key] << value }
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def set(name, value, labels: {})
|
|
223
|
+
key = "#{name}:#{labels.sort.to_h}"
|
|
224
|
+
@mutex.synchronize { @counters[key] = value }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def report
|
|
228
|
+
@mutex.synchronize do
|
|
229
|
+
puts "\n--- Custom Metrics Report ---"
|
|
230
|
+
puts 'Counters:'
|
|
231
|
+
@counters.each { |k, v| puts " #{k}: #{v}" }
|
|
232
|
+
puts 'Histograms:'
|
|
233
|
+
@histograms.each do |k, values|
|
|
234
|
+
next if values.empty?
|
|
235
|
+
|
|
236
|
+
avg = values.sum / values.size
|
|
237
|
+
puts " #{k}: count=#{values.size}, avg=#{avg.round(2)}, min=#{values.min.round(2)}, max=#{values.max.round(2)}"
|
|
238
|
+
end
|
|
239
|
+
puts '-----------------------------'
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def main_with_custom_backend
|
|
245
|
+
# Example using a custom metrics backend
|
|
246
|
+
config = Conductor::Configuration.new(
|
|
247
|
+
server_api_url: ENV['CONDUCTOR_SERVER_URL'] || 'http://localhost:8080/api'
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
custom_backend = CustomMetricsBackend.new
|
|
251
|
+
metrics = Conductor::Worker::Telemetry::MetricsCollector.create(backend: custom_backend)
|
|
252
|
+
|
|
253
|
+
puts 'Using custom metrics backend...'
|
|
254
|
+
|
|
255
|
+
handler = Conductor::Worker::TaskHandler.new(
|
|
256
|
+
configuration: config,
|
|
257
|
+
scan_for_annotated_workers: true,
|
|
258
|
+
event_listeners: [metrics]
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Report metrics periodically
|
|
262
|
+
reporter = Thread.new do
|
|
263
|
+
loop do
|
|
264
|
+
sleep 30
|
|
265
|
+
custom_backend.report
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
Signal.trap('INT') do
|
|
270
|
+
reporter.kill
|
|
271
|
+
handler.stop
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
handler.start
|
|
275
|
+
handler.join
|
|
276
|
+
|
|
277
|
+
custom_backend.report
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
if __FILE__ == $PROGRAM_NAME
|
|
281
|
+
# Run with Prometheus by default
|
|
282
|
+
# Use main_with_custom_backend for custom backend example
|
|
283
|
+
main
|
|
284
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example demonstrating the new Ruby-idiomatic DSL for Conductor workflows
|
|
4
|
+
#
|
|
5
|
+
# This example shows how to use the new DSL which provides:
|
|
6
|
+
# - Method-per-type approach: simple(), http(), parallel(), etc.
|
|
7
|
+
# - Auto-generated reference names
|
|
8
|
+
# - Hash-style [] for output references
|
|
9
|
+
# - wf[:param] for workflow inputs
|
|
10
|
+
# - Block-based control flow
|
|
11
|
+
|
|
12
|
+
require 'bundler/setup'
|
|
13
|
+
require 'conductor'
|
|
14
|
+
|
|
15
|
+
# Define a workflow using the new DSL
|
|
16
|
+
# Note: executor is optional - only needed for .register() and .execute()
|
|
17
|
+
workflow = Conductor.workflow :order_processing_demo, version: 1 do
|
|
18
|
+
# Access workflow inputs with wf[:param_name]
|
|
19
|
+
# Returns: "${workflow.input.param_name}"
|
|
20
|
+
order_id_input = wf[:order_id]
|
|
21
|
+
user_email_input = wf[:user_email]
|
|
22
|
+
|
|
23
|
+
# Simple tasks return TaskRef objects
|
|
24
|
+
# Access task outputs with task[:field_name]
|
|
25
|
+
user = simple :get_user, user_id: order_id_input
|
|
26
|
+
order = simple :validate_order,
|
|
27
|
+
order_id: order_id_input,
|
|
28
|
+
user_email: user[:email]
|
|
29
|
+
|
|
30
|
+
# HTTP tasks with fluent syntax
|
|
31
|
+
api_response = http :call_payment_api,
|
|
32
|
+
url: 'https://api.payment.example.com/charge',
|
|
33
|
+
method: :post,
|
|
34
|
+
body: {
|
|
35
|
+
amount: order[:total],
|
|
36
|
+
currency: 'USD',
|
|
37
|
+
customer: user[:id]
|
|
38
|
+
},
|
|
39
|
+
headers: {
|
|
40
|
+
'Authorization' => 'Bearer ${workflow.input.api_key}',
|
|
41
|
+
'Content-Type' => 'application/json'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Parallel execution - tasks run concurrently
|
|
45
|
+
parallel do
|
|
46
|
+
simple :ship_order,
|
|
47
|
+
order_id: order[:id],
|
|
48
|
+
address: order[:shipping_address]
|
|
49
|
+
|
|
50
|
+
simple :send_confirmation_email,
|
|
51
|
+
to: user_email_input,
|
|
52
|
+
order_id: order[:id]
|
|
53
|
+
|
|
54
|
+
simple :update_inventory,
|
|
55
|
+
product_id: order[:product_id],
|
|
56
|
+
quantity: order[:quantity]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Switch/decision based on output value
|
|
60
|
+
decide order[:region] do
|
|
61
|
+
on 'US' do
|
|
62
|
+
simple :calculate_us_tax, amount: order[:total]
|
|
63
|
+
simple :apply_us_shipping
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
on 'EU' do
|
|
67
|
+
simple :calculate_vat, amount: order[:total]
|
|
68
|
+
simple :apply_eu_shipping
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
on 'UK' do
|
|
72
|
+
simple :calculate_uk_tax, amount: order[:total]
|
|
73
|
+
simple :apply_uk_shipping
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
otherwise do
|
|
77
|
+
simple :apply_international_shipping
|
|
78
|
+
terminate :completed, 'International order processed with standard shipping'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Set workflow variables
|
|
83
|
+
set(
|
|
84
|
+
order_status: 'completed',
|
|
85
|
+
processed_at: '${CPEWF_EPOCH}',
|
|
86
|
+
processor_id: wf[:worker_id]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Define workflow outputs
|
|
90
|
+
output(
|
|
91
|
+
order_id: order[:id],
|
|
92
|
+
status: 'processed',
|
|
93
|
+
total: order[:total],
|
|
94
|
+
payment_result: api_response[:transaction_id]
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Display workflow information
|
|
99
|
+
puts "Workflow created: #{workflow.name} (version #{workflow.version})"
|
|
100
|
+
puts "Task count: #{workflow.builder.tasks.size}"
|
|
101
|
+
|
|
102
|
+
# Convert to WorkflowDef to see the generated structure
|
|
103
|
+
workflow_def = workflow.to_workflow_def
|
|
104
|
+
puts "\nGenerated WorkflowDef:"
|
|
105
|
+
puts " Name: #{workflow_def.name}"
|
|
106
|
+
puts " Version: #{workflow_def.version}"
|
|
107
|
+
puts " Tasks: #{workflow_def.tasks.size}"
|
|
108
|
+
puts "\nFirst 3 tasks:"
|
|
109
|
+
workflow_def.tasks.take(3).each_with_index do |task, idx|
|
|
110
|
+
puts " #{idx + 1}. #{task.type}: #{task.task_reference_name}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
puts "\nOutput parameters: #{workflow_def.output_parameters.keys.join(', ')}"
|
|
114
|
+
|
|
115
|
+
puts "\n✓ DSL example completed successfully!"
|
|
116
|
+
puts "\nTo register and execute this workflow, provide an executor:"
|
|
117
|
+
puts <<~EXAMPLE
|
|
118
|
+
|
|
119
|
+
# Create executor with configuration
|
|
120
|
+
config = Conductor::Configuration.new
|
|
121
|
+
config.server_url = 'http://localhost:8080/api'
|
|
122
|
+
executor = Conductor::Workflow::WorkflowExecutor.new(config)
|
|
123
|
+
|
|
124
|
+
# Define workflow with executor
|
|
125
|
+
workflow = Conductor.workflow :order_processing_demo, version: 1, executor: executor do
|
|
126
|
+
# ... workflow definition ...
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Register the workflow
|
|
130
|
+
workflow.register(overwrite: true)
|
|
131
|
+
|
|
132
|
+
# Execute the workflow
|
|
133
|
+
result = workflow.execute(input: {
|
|
134
|
+
order_id: '12345',
|
|
135
|
+
user_email: 'customer@example.com',
|
|
136
|
+
api_key: 'secret_key',
|
|
137
|
+
worker_id: 'worker-001'
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
puts "Workflow status: \#{result.status}"
|
|
141
|
+
EXAMPLE
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# HTTP Poll Task Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates using HTTP_POLL task to poll an external API
|
|
7
|
+
# until a condition is met or timeout occurs.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bundle exec ruby examples/orkes/http_poll.rb
|
|
11
|
+
|
|
12
|
+
require_relative '../../lib/conductor'
|
|
13
|
+
|
|
14
|
+
include Conductor::Workflow
|
|
15
|
+
|
|
16
|
+
def create_http_poll_workflow(workflow_client, workflow_executor)
|
|
17
|
+
workflow = ConductorWorkflow.new(
|
|
18
|
+
workflow_client,
|
|
19
|
+
'http_poll_workflow_ruby',
|
|
20
|
+
version: 1,
|
|
21
|
+
executor: workflow_executor
|
|
22
|
+
)
|
|
23
|
+
workflow.description('Workflow demonstrating HTTP polling')
|
|
24
|
+
|
|
25
|
+
# HTTP Poll task - polls until condition is met
|
|
26
|
+
poll_task = HttpPollTask.new('poll_status', {
|
|
27
|
+
'uri' => 'https://httpbin.org/json',
|
|
28
|
+
'method' => 'GET',
|
|
29
|
+
'connectionTimeOut' => 5000,
|
|
30
|
+
'readTimeOut' => 5000
|
|
31
|
+
})
|
|
32
|
+
poll_task.input('terminalCondition', '$.slideshow != null')
|
|
33
|
+
poll_task.input('pollingInterval', 2)
|
|
34
|
+
poll_task.input('pollingStrategy', 'FIXED')
|
|
35
|
+
|
|
36
|
+
# Process the result
|
|
37
|
+
process = SimpleTask.new('process_result', 'process_ref')
|
|
38
|
+
.input('data', poll_task.output('response'))
|
|
39
|
+
|
|
40
|
+
workflow >> poll_task >> process
|
|
41
|
+
workflow.output_parameter('result', process.output)
|
|
42
|
+
|
|
43
|
+
workflow
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def main
|
|
47
|
+
config = Conductor::Configuration.new
|
|
48
|
+
|
|
49
|
+
puts '=' * 70
|
|
50
|
+
puts 'HTTP Poll Task Example'
|
|
51
|
+
puts '=' * 70
|
|
52
|
+
puts "Server: #{config.server_url}"
|
|
53
|
+
puts
|
|
54
|
+
|
|
55
|
+
clients = Conductor::Orkes::OrkesClients.new(config)
|
|
56
|
+
workflow_executor = clients.get_workflow_executor
|
|
57
|
+
workflow_client = clients.get_workflow_client
|
|
58
|
+
|
|
59
|
+
workflow = create_http_poll_workflow(workflow_client, workflow_executor)
|
|
60
|
+
workflow_executor.register_workflow(workflow, overwrite: true)
|
|
61
|
+
puts "Registered workflow: #{workflow.name}"
|
|
62
|
+
|
|
63
|
+
puts "\nExecuting HTTP poll workflow..."
|
|
64
|
+
result = workflow_executor.execute(
|
|
65
|
+
workflow.name,
|
|
66
|
+
input: {},
|
|
67
|
+
wait_for_seconds: 30
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
puts "Status: #{result.status}"
|
|
71
|
+
puts "Output: #{result.output.inspect}"
|
|
72
|
+
puts "\nView at: #{config.ui_host}/execution/#{result.workflow_id}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if __FILE__ == $PROGRAM_NAME
|
|
76
|
+
begin
|
|
77
|
+
main
|
|
78
|
+
rescue Conductor::ApiError => e
|
|
79
|
+
puts "API Error: #{e.message}"
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
puts "Error: #{e.message}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Secrets Management Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates using the SecretClient to manage sensitive data
|
|
7
|
+
# like API keys, passwords, and tokens.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bundle exec ruby examples/orkes/secrets_example.rb
|
|
11
|
+
|
|
12
|
+
require_relative '../../lib/conductor'
|
|
13
|
+
|
|
14
|
+
def main
|
|
15
|
+
config = Conductor::Configuration.new
|
|
16
|
+
clients = Conductor::Orkes::OrkesClients.new(config)
|
|
17
|
+
secret_client = clients.get_secret_client
|
|
18
|
+
|
|
19
|
+
puts '=' * 70
|
|
20
|
+
puts 'Secrets Management Example'
|
|
21
|
+
puts '=' * 70
|
|
22
|
+
puts "Server: #{config.server_url}"
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
secret_name = "test_api_key_ruby_#{Time.now.to_i}"
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
# Create a secret
|
|
29
|
+
puts "Creating secret: #{secret_name}"
|
|
30
|
+
secret_client.put_secret(secret_name, 'super-secret-value-12345')
|
|
31
|
+
puts 'Secret created successfully'
|
|
32
|
+
|
|
33
|
+
# List all secrets (names only - values are never exposed)
|
|
34
|
+
puts "\nListing all secrets:"
|
|
35
|
+
secrets = secret_client.list_all_secret_names
|
|
36
|
+
secrets.first(5).each { |s| puts " - #{s}" }
|
|
37
|
+
puts " ... (#{secrets.length} total)"
|
|
38
|
+
|
|
39
|
+
# Check if secret exists
|
|
40
|
+
exists = secret_client.secret_exists?(secret_name)
|
|
41
|
+
puts "\nSecret '#{secret_name}' exists: #{exists}"
|
|
42
|
+
|
|
43
|
+
# Update the secret
|
|
44
|
+
puts "\nUpdating secret..."
|
|
45
|
+
secret_client.put_secret(secret_name, 'updated-secret-value-67890')
|
|
46
|
+
puts 'Secret updated'
|
|
47
|
+
ensure
|
|
48
|
+
# Clean up
|
|
49
|
+
puts "\nDeleting secret..."
|
|
50
|
+
begin
|
|
51
|
+
secret_client.delete_secret(secret_name)
|
|
52
|
+
puts 'Secret deleted'
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
puts "Could not delete: #{e.message}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
puts "\nSecrets example complete!"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if __FILE__ == $PROGRAM_NAME
|
|
62
|
+
begin
|
|
63
|
+
main
|
|
64
|
+
rescue Conductor::ApiError => e
|
|
65
|
+
puts "API Error: #{e.message}"
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
puts "Error: #{e.message}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Wait for Webhook Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates using the WAIT_FOR_WEBHOOK task to pause workflow
|
|
7
|
+
# execution until an external webhook is received.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bundle exec ruby examples/orkes/wait_for_webhook.rb
|
|
11
|
+
|
|
12
|
+
require_relative '../../lib/conductor'
|
|
13
|
+
|
|
14
|
+
include Conductor::Workflow
|
|
15
|
+
|
|
16
|
+
def create_webhook_workflow(workflow_client, workflow_executor)
|
|
17
|
+
workflow = ConductorWorkflow.new(
|
|
18
|
+
workflow_client,
|
|
19
|
+
'webhook_workflow_ruby',
|
|
20
|
+
version: 1,
|
|
21
|
+
executor: workflow_executor
|
|
22
|
+
)
|
|
23
|
+
workflow.description('Workflow that waits for external webhook')
|
|
24
|
+
|
|
25
|
+
# Initial task
|
|
26
|
+
init = SimpleTask.new('init_process', 'init_ref')
|
|
27
|
+
.input('order_id', workflow.input('order_id'))
|
|
28
|
+
|
|
29
|
+
# Wait for webhook - pauses until external signal received
|
|
30
|
+
wait_webhook = WaitForWebhookTask.new('wait_for_payment')
|
|
31
|
+
.input('matches', {
|
|
32
|
+
'$.[?(@.order_id == "${workflow.input.order_id}")]' => true
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
# Process after webhook received
|
|
36
|
+
process = SimpleTask.new('process_payment', 'process_ref')
|
|
37
|
+
.input('payment_data', '${wait_for_payment.output}')
|
|
38
|
+
|
|
39
|
+
workflow >> init >> wait_webhook >> process
|
|
40
|
+
|
|
41
|
+
workflow.output_parameter('result', '${process_ref.output}')
|
|
42
|
+
workflow
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def main
|
|
46
|
+
config = Conductor::Configuration.new
|
|
47
|
+
|
|
48
|
+
puts '=' * 70
|
|
49
|
+
puts 'Wait for Webhook Example'
|
|
50
|
+
puts '=' * 70
|
|
51
|
+
puts "Server: #{config.server_url}"
|
|
52
|
+
puts
|
|
53
|
+
|
|
54
|
+
clients = Conductor::Orkes::OrkesClients.new(config)
|
|
55
|
+
workflow_executor = clients.get_workflow_executor
|
|
56
|
+
workflow_client = clients.get_workflow_client
|
|
57
|
+
|
|
58
|
+
workflow = create_webhook_workflow(workflow_client, workflow_executor)
|
|
59
|
+
workflow_executor.register_workflow(workflow, overwrite: true)
|
|
60
|
+
puts "Registered workflow: #{workflow.name}"
|
|
61
|
+
|
|
62
|
+
# Start workflow
|
|
63
|
+
workflow_id = workflow_executor.start_workflow(
|
|
64
|
+
Conductor::Http::Models::StartWorkflowRequest.new(
|
|
65
|
+
name: workflow.name,
|
|
66
|
+
version: 1,
|
|
67
|
+
input: { 'order_id' => 'ORD-12345' }
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
puts "Started workflow: #{workflow_id}"
|
|
72
|
+
puts
|
|
73
|
+
puts 'Workflow is now waiting for webhook...'
|
|
74
|
+
puts
|
|
75
|
+
puts 'To complete the workflow, send a webhook:'
|
|
76
|
+
puts " POST #{config.server_url}/webhook/#{workflow.name}"
|
|
77
|
+
puts ' Body: {"order_id": "ORD-12345", "payment_status": "completed"}'
|
|
78
|
+
puts
|
|
79
|
+
puts "Monitor at: #{config.ui_host}/execution/#{workflow_id}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if __FILE__ == $PROGRAM_NAME
|
|
83
|
+
begin
|
|
84
|
+
main
|
|
85
|
+
rescue Conductor::ApiError => e
|
|
86
|
+
puts "API Error: #{e.message}"
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
puts "Error: #{e.message}"
|
|
89
|
+
end
|
|
90
|
+
end
|