Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/harness-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Harness Worker Image

on:
push:
branches: [main]
paths:
- "Dockerfile"
- "harness/**"
- "lib/**"
- "Gemfile"
- "Gemfile.lock"
- "conductor_ruby.gemspec"
- ".github/workflows/harness-image.yml"
release:
types: [published]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/conductor-oss/ruby-sdk/harness-worker
tags: |
type=raw,value=latest
type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: harness
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

dispatch-deploy:
if: github.event_name == 'release'
needs: build-and-push
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.CI_UTIL_DISPATCH_TOKEN }}
repository: conductor-oss/oss-ci-util
event-type: sdk_release
client-payload: |-
{"tag": "${{ github.event.release.tag_name || 'latest' }}", "repo": "${{ github.repository }}"}
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Metrics/MethodLength:
- 'examples/**/*'

Metrics/ClassLength:
Max: 400
Max: 500

Metrics/AbcSize:
Max: 70
Expand Down
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ruby:3.3-alpine AS build
RUN apk add --no-cache build-base git
WORKDIR /package
COPY Gemfile Gemfile.lock conductor_ruby.gemspec ./
COPY lib/conductor/version.rb lib/conductor/version.rb
RUN bundle config set --local without 'development' \
&& bundle install --jobs 4

COPY lib/ lib/
COPY harness/ harness/

FROM ruby:3.3-alpine AS harness
RUN adduser -D -u 65532 nonroot
WORKDIR /app
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /package/lib /app/lib
COPY --from=build /package/harness /app/harness
USER nonroot
ENTRYPOINT ["ruby", "harness/main.rb"]
50 changes: 50 additions & 0 deletions harness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Ruby SDK Docker Harness

A long-running worker harness built from the root `Dockerfile`.

## Worker Harness

A self-feeding worker that runs indefinitely. On startup it registers five simulated tasks (`ruby_worker_0` through `ruby_worker_4`) and the `ruby_simulated_tasks_workflow`, then runs two background services:

- **WorkflowGovernor** -- starts a configurable number of `ruby_simulated_tasks_workflow` instances per second (default 2), indefinitely.
- **SimulatedTaskWorkers** -- five task handlers, each with a codename and a default sleep duration. Each worker supports configurable delay types, failure simulation, and output generation via task input parameters. The workflow chains them in sequence: quickpulse (1s) → whisperlink (2s) → shadowfetch (3s) → ironforge (4s) → deepcrawl (5s).

```bash
docker build --target harness -t ruby-sdk-harness .

docker run -d \
-e CONDUCTOR_SERVER_URL=https://your-cluster.example.com/api \
-e CONDUCTOR_AUTH_KEY=$CONDUCTOR_AUTH_KEY \
-e CONDUCTOR_AUTH_SECRET=$CONDUCTOR_AUTH_SECRET \
-e HARNESS_WORKFLOWS_PER_SEC=4 \
ruby-sdk-harness
```

You can also run the harness locally without Docker:

```bash
export CONDUCTOR_SERVER_URL=https://your-cluster.example.com/api
export CONDUCTOR_AUTH_KEY=$CONDUCTOR_AUTH_KEY
export CONDUCTOR_AUTH_SECRET=$CONDUCTOR_AUTH_SECRET

ruby harness/main.rb
```

Override defaults with environment variables as needed:

```bash
HARNESS_WORKFLOWS_PER_SEC=4 HARNESS_BATCH_SIZE=10 ruby harness/main.rb
```

All resource names use a `ruby_` prefix so multiple SDK harnesses (Python, Java, Go, C#, etc.) can coexist on the same cluster.

### Environment Variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `CONDUCTOR_SERVER_URL` | yes | -- | Conductor API base URL |
| `CONDUCTOR_AUTH_KEY` | no | -- | Orkes auth key |
| `CONDUCTOR_AUTH_SECRET` | no | -- | Orkes auth secret |
| `HARNESS_WORKFLOWS_PER_SEC` | no | 2 | Workflows to start per second |
| `HARNESS_BATCH_SIZE` | no | 20 | Number of tasks each worker polls per batch |
| `HARNESS_POLL_INTERVAL_MS` | no | 100 | Milliseconds between poll cycles |
114 changes: 114 additions & 0 deletions harness/main.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# frozen_string_literal: true

# Load the SDK from source (relative to repo root)
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
require 'conductor'
require_relative 'simulated_task_worker'
require_relative 'workflow_governor'

module Harness
WORKFLOW_NAME = 'ruby_simulated_tasks_workflow'

SIMULATED_WORKERS = [
{ task_name: 'ruby_worker_0', codename: 'quickpulse', sleep_seconds: 1 },
{ task_name: 'ruby_worker_1', codename: 'whisperlink', sleep_seconds: 2 },
{ task_name: 'ruby_worker_2', codename: 'shadowfetch', sleep_seconds: 3 },
{ task_name: 'ruby_worker_3', codename: 'ironforge', sleep_seconds: 4 },
{ task_name: 'ruby_worker_4', codename: 'deepcrawl', sleep_seconds: 5 }
].freeze

def self.env_int(name, default)
val = ENV.fetch(name, nil)
val ? Integer(val) : default
rescue ArgumentError
default
end

def self.main
$stdout.sync = true

workflows_per_sec = env_int('HARNESS_WORKFLOWS_PER_SEC', 2)
batch_size = env_int('HARNESS_BATCH_SIZE', 20)
poll_interval_ms = env_int('HARNESS_POLL_INTERVAL_MS', 100)

configuration = Conductor::Configuration.new
register_metadata(configuration)

workers = SIMULATED_WORKERS.map do |def_entry|
sim = SimulatedTaskWorker.new(
def_entry[:task_name],
def_entry[:codename],
def_entry[:sleep_seconds],
batch_size: batch_size,
poll_interval_ms: poll_interval_ms
)

Conductor::Worker::Worker.new(
def_entry[:task_name],
sim.method(:execute),
poll_interval: poll_interval_ms,
thread_count: batch_size,
worker_id: sim.worker_id
)
end

task_handler = Conductor::Worker::TaskHandler.new(
workers: workers,
configuration: configuration,
scan_for_annotated_workers: false
)
task_handler.start

workflow_executor = Conductor::Workflow::WorkflowExecutor.new(configuration)
governor = WorkflowGovernor.new(workflow_executor, WORKFLOW_NAME, workflows_per_sec)
governor.start

shutdown = proc do
puts 'Shutting down...'
governor.stop
task_handler.stop
exit(0)
end

trap('INT', &shutdown)
trap('TERM', &shutdown)

task_handler.join
end

def self.register_metadata(configuration)
metadata_client = Conductor::Client::MetadataClient.new(configuration)

task_defs = SIMULATED_WORKERS.map do |entry|
Conductor::Http::Models::TaskDef.new(
name: entry[:task_name],
description: "Ruby SDK harness simulated task (#{entry[:codename]}, default delay #{entry[:sleep_seconds]}s)",
retry_count: 1,
timeout_seconds: 300,
response_timeout_seconds: 300
)
end
metadata_client.register_task_defs(task_defs)

workflow_tasks = SIMULATED_WORKERS.map do |entry|
Conductor::Http::Models::WorkflowTask.new(
name: entry[:task_name],
task_reference_name: entry[:codename],
type: 'SIMPLE'
)
end

workflow_def = Conductor::Http::Models::WorkflowDef.new(
name: WORKFLOW_NAME,
version: 1,
description: 'Ruby SDK harness simulated task workflow',
owner_email: 'ruby-sdk-harness@conductor.io',
tasks: workflow_tasks
)
metadata_client.update_workflow_def(workflow_def)

puts "Registered workflow #{WORKFLOW_NAME} with #{SIMULATED_WORKERS.size} tasks"
end
end

Harness.main
Loading
Loading