Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ruby relay server (FF-3496) #96

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
12 changes: 12 additions & 0 deletions .github/workflows/test-sdk-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,15 @@ jobs:
sdkName: 'eppo/node-server-sdk'
sdkRelayDir: 'node-sdk-relay'
secrets: inherit

test-ruby-sdk:
strategy:
fail-fast: false
matrix:
platform: ['linux']
uses: ./.github/workflows/test-server-sdk.yml
with:
platform: ${{ matrix.platform }}
sdkName: 'eppo/ruby-sdk'
sdkRelayDir: 'ruby-sdk-relay'
secrets: inherit
11 changes: 11 additions & 0 deletions package-testing/python-sdk-relay/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.12

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy the source code
COPY src/ ./src/

CMD ["python", "/app/src/server.py"]
29 changes: 29 additions & 0 deletions package-testing/python-sdk-relay/docker-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash

# Default is to use the latest build
VERSION="${1:-latest}"

echo "Starting deployment with version: $VERSION"

if [ -e .env ]; then
echo "Loading environment variables from .env file"
source .env
fi

echo "Stopping existing container..."
docker stop python-relay
echo "Removing existing container..."
docker remove python-relay

echo "Building new image..."
docker build . -t Eppo-exp/python-sdk-relay:$VERSION

echo "Starting new container..."
docker run -p $SDK_RELAY_PORT:$SDK_RELAY_PORT \
--add-host host.docker.internal:host-gateway \
-e SDK_REF \
-e EPPO_BASE_URL \
-e SDK_RELAY_PORT \
--name python-relay \
--rm \
-t Eppo-exp/python-sdk-relay:$VERSION;
3 changes: 3 additions & 0 deletions package-testing/ruby-sdk-relay/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
vendor/
.bundle/
7 changes: 7 additions & 0 deletions package-testing/ruby-sdk-relay/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
source 'https://rubygems.org'

gem 'sinatra', '~> 4.1'
gem 'eppo-server-sdk', '~> 3.3'

gem "rackup", "~> 2.2"
gem "puma", "~> 6.5"
51 changes: 51 additions & 0 deletions package-testing/ruby-sdk-relay/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
GEM
remote: https://rubygems.org/
specs:
base64 (0.2.0)
eppo-server-sdk (3.3.0-aarch64-linux)
eppo-server-sdk (3.3.0-aarch64-linux-musl)
eppo-server-sdk (3.3.0-arm-linux)
eppo-server-sdk (3.3.0-arm64-darwin)
eppo-server-sdk (3.3.0-x86_64-linux)
eppo-server-sdk (3.3.0-x86_64-linux-musl)
logger (1.6.3)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
nio4r (2.7.4)
puma (6.5.0)
nio4r (~> 2.0)
rack (3.1.8)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.0.0)
rack (>= 3.0.0)
rackup (2.2.1)
rack (>= 3)
ruby2_keywords (0.0.5)
sinatra (4.1.1)
logger (>= 1.6.0)
mustermann (~> 3.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.1.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
tilt (2.4.0)

PLATFORMS
aarch64-linux
aarch64-linux-musl
arm-linux
arm64-darwin
x86_64-linux
x86_64-linux-musl

DEPENDENCIES
eppo-server-sdk (~> 3.3)
puma (~> 6.5)
rackup (~> 2.2)
sinatra (~> 4.1)

BUNDLED WITH
2.5.9
29 changes: 29 additions & 0 deletions package-testing/ruby-sdk-relay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Ruby Testing Server

Post test case files to this server and check the results against what's expected.

## Running locally with Docker

Build the docker image:

```shell
docker build -t Eppo-exp/ruby-sdk-relay .
```

Run the docker container:

```shell
./docker-run.sh
```

## Development

1. Install dependencies:
```shell
bundle install
```

2. Run the server:
```shell
bundle exec ruby src/server.rb
```
63 changes: 63 additions & 0 deletions package-testing/ruby-sdk-relay/build-and-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash

# Set default values for vars
: "${SDK_REF:=main}"
: "${SDK_RELAY_HOST:=localhost}"
: "${SDK_RELAY_PORT:=4000}"
SDK="https://github.com/Eppo-exp/eppo-multiplatform.git"

# Check if Ruby is installed
if ! command -v ruby &> /dev/null; then
echo "Ruby is not installed. Please install Ruby before running this script."
echo "You can install Ruby using your package manager or a version manager like rbenv or rvm."
exit 1
fi

# Check if Bundler is installed, install if not
if ! command -v bundle &> /dev/null; then
echo "Bundler is not installed. Installing Bundler..."
gem install bundler || { echo "Failed to install Bundler"; exit 1; }
fi

# Install Ruby dependencies
echo "Installing Ruby dependencies..."
bundle install || { echo "Failed to install Ruby dependencies"; exit 1; }

# checkout the specified ref of the SDK repo, build it, and then insert it into vendors here.
rm -rf tmp
mkdir -p tmp

echo "Cloning ${SDK}@${SDK_REF}"
git clone -b ${SDK_REF} --depth 1 --single-branch ${SDK} tmp || { echo "Cloning repo failed"; exit 1; }

# Build and install the Ruby gem with Rust components
echo "Building and installing the Ruby SDK with Rust components..."
if [ -d "tmp/ruby-sdk" ]; then
cd tmp/ruby-sdk

# Install dependencies
bundle install || { echo "Failed to install Ruby SDK dependencies"; exit 1; }

# Build the gem with Rust components
bundle exec rake build || { echo "Failed to build the Ruby gem"; exit 1; }

# Install the built gem
GEM_FILE=$(ls pkg/*.gem 2>/dev/null | head -n 1)
if [ -n "$GEM_FILE" ]; then
gem install $GEM_FILE || { echo "Failed to install the gem"; exit 1; }
else
echo "Error: Gem file not found"
exit 1
fi
cd ../..
else
echo "Error: Ruby SDK directory not found at expected location (tmp/ruby-sdk)"
exit 1
fi

# cleanup
rm -rf tmp

# start the relay server
echo "Listening on ${SDK_RELAY_HOST}:${SDK_RELAY_PORT}"
SDK_RELAY_HOST=${SDK_RELAY_HOST} SDK_RELAY_PORT=${SDK_RELAY_PORT} bundle exec ruby src/server.rb
133 changes: 133 additions & 0 deletions package-testing/ruby-sdk-relay/src/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
require 'bundler/setup'
require 'sinatra'
require 'json'
require 'eppo_client'

class LocalAssignmentLogger < EppoClient::AssignmentLogger
def log_assignment(assignment)
puts "Assignment: #{assignment}"
end
end

def initialize_client_and_wait
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💪

puts "Initializing client"
api_key = ENV['EPPO_API_KEY'] || 'NOKEYSPECIFIED'
base_url = ENV['EPPO_BASE_URL'] || 'http://localhost:5000/api'

client_config = EppoClient::Config.new(
api_key,
base_url: base_url,
assignment_logger: LocalAssignmentLogger.new
)

EppoClient::init(client_config)
client = EppoClient::Client.instance
sleep(3)
puts "Client initialized"
end

# Health check endpoint
get '/' do
"OK"
end

# SDK reset endpoint
post '/sdk/reset' do
initialize_client_and_wait
"Reset complete"
end

# SDK details endpoint
get '/sdk/details' do
content_type :json
{
sdkName: "ruby-sdk",
sdkVersion: "3.3.0",
supportsBandits: false,
supportsDynamicTyping: false
}.to_json
end

# Assignment endpoint
post '/flags/v1/assignment' do
content_type :json
data = JSON.parse(request.body.read)

client = EppoClient::Client.instance

begin
result = case data['assignmentType']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby looks pretty slick. I like this select-expression-return. Kotlin has this as well.

when 'BOOLEAN'
client.get_boolean_assignment(
data['flag'],
data['subjectKey'],
data['subjectAttributes'],
data['defaultValue']
)
when 'INTEGER'
client.get_integer_assignment(
data['flag'],
data['subjectKey'],
data['subjectAttributes'],
data['defaultValue'].to_i
)
when 'STRING'
client.get_string_assignment(
data['flag'],
data['subjectKey'],
data['subjectAttributes'],
data['defaultValue']
)
when 'NUMERIC'
client.get_numeric_assignment(
data['flag'],
data['subjectKey'],
data['subjectAttributes'],
data['defaultValue'].to_f
)
when 'JSON'
client.get_json_assignment(
data['flag'],
data['subjectKey'],
data['subjectAttributes'],
data['defaultValue']
)
end

{
result: result,
assignmentLog: [],
banditLog: [],
error: nil
}.to_json
rescue => e
puts "Error processing assignment: #{e}"
{
result: nil,
assignmentLog: [],
banditLog: [],
error: e.message
}.to_json
end
end

# Bandit endpoint
post '/bandits/v1/action' do
content_type :json
data = JSON.parse(request.body.read)

# TODO: Implement bandit logic
{
result: "action",
assignmentLog: [],
banditLog: [],
error: nil
}.to_json
end

initialize_client_and_wait

host = ENV['SDK_RELAY_HOST'] || '0.0.0.0'
port = ENV['SDK_RELAY_PORT'] || 7001
set :port, port
set :bind, host
Loading