FaaStRuby

Join the chat - Getting Started - API Documentation - Blog

Getting Started

This is a work in progress. Last update - 26.10.2018 @ 3:00PM ADT.
If you have any questions or comments, please join the chat.

Introduction

FaaStRuby is a serverless platform built for Ruby developers. You deploy functions to workspaces and trigger them via HTTP endpoints.

Workspaces are groups of functions and can be used to mimic environments. They are represented by the URL https://api.faastruby.io/WORKSPACE_NAME. Note that workspace names must be unique.
When you create a workspace you receive an API key/secret pair. You need to use those credentials to make changes to the workspace so store them somewhere safe.

You can upload functions to any existing workspaces you own. Functions can be triggered through their workspace URL. For example, a function named 'slack-bot' in the workspace 'catops-prod' will have the URL https://api.faastruby.io/catops-prod/slack-bot.

Creating a workspace

The first thing you need to do is create a workspace. The command below will send a request to create a workspace 'catops-prod' on FaaStRuby and write the credentials to '~/.faastruby'.

$ faastruby create-workspace catops-prod
◐ Requesting credentials... Done!
Writing credentials to /Users/paulo/.faastruby
~ f /Users/paulo/.faastruby
Workspace 'catops-prod' created

If you want to print the credentials to STDOUT instead of saving it to a file, use the option --stdout:

$ faastruby create-workspace catops-prod --stdout
◐ Requesting credentials... Done!
IMPORTANT: Please store the credentials below in a safe place. If you lose them you will not be able to manage your workspace.
API_KEY: 63a02778c9342993801936ef4a87412e
API_SECRET: voeIjDPY5sloCZ0oo68nIw==
Workspace 'catops-prod' created

You can also attach an email address to the workspace. Although it is not required, it is a good idea to do it so you can be contacted in case of any problems with your workspace.

$ faastruby create-workspace catops-prod -e you@example.com

Your first function

Let's create and deploy your first function. The function will take a JSON payload {"name": "Ruby"} and respond with the string "Hello, Ruby!". Let's call it 'hello-world':

$ faastruby new hello-world --blank
+ d ./hello-world
+ d ./hello-world/spec    # Put your tests in here
+ d ./hello-world/spec/helpers    # Spec helpers go here
+ f ./hello-world/spec/helpers/faastruby.rb    # FaaStRuby::SpecHelper
+ f ./hello-world/spec/handler_spec.rb    # Spec for handler.rb
+ f ./hello-world/spec/spec_helper.rb    # Helper to load FaaStRuby::SpecHelper
+ f ./hello-world/Gemfile    # Just a regular Gemfile
+ f ./hello-world/handler.rb    # The function handler. Must define a method 'handler'
+ f ./hello-world/faastruby.yml    # Your function configuration
◑ Installing gems... Done!

Let's take a look at 'faastruby.yml'

---
name: hello-world    # The function name
test_command: rspec    # The command to run tests
abort_build_when_tests_fail: true    # Abort package build when tests fail
abort_deploy_when_tests_fail: true    # Abort deploy when tests fail

A function must return a Hash, Array or String.
When a String is used, the response is not parsed before it is sent.
When the response is a Hash or Array, it gets serialized as JSON.

def handler event
  respond_with "Hello, World!"
end

You can also set the response status code and headers. The status code defaults to 200, and the headers parameter must be a Hash of string keys.

def handler event
  respond_with "Hello, World!", status: 200, headers: {'Content-Type' => 'text/plain'}
end

Now let's update the function handler so it will get the request body, parse it and respond with "Hello, _____!" or "Hello, World!" if no name is present.

The 'event' parameter

The function handler takes a parameter 'event'. This parameter is a Struct with the following attributes:

- event.body - The request body string. It is up to you to parse it. Ex: JSON.parse(event.body)
- event.query_params - A Hash with the URL query parameters. Ex: for 'foo=bar&lorem=ipsum', event.query_params['foo'] #=> "bar" and event.query_params['lorem'] #=> "ipsum"
- event.headers - A hash with the request headers.
- event.context - The execution context. Learn more.

Let's say we decide to send a JSON payload to the function with the following content:

{"name": "Ruby"}

Then we parse 'event.body' and modify the function response. Here is the code:

require 'json'

def handler event
  headers = {
    'Content-Type' => 'text/plain'
  }
  data = event.body ? JSON.parse(event.body) : {}
  response = "Hello, #{data['name'] || 'World'}!"
  respond_with response, headers: headers
end

Can't forget to write some tests! :)

Here is the content for 'spec/handler_spec.rb':

require 'spec_helper'
require 'handler'

describe 'handler(event)' do
  let(:event) {SpecHelper::Event.new(body: '{"name": "Ruby"}')}

  it 'should return Hash, String or Array' do
    body = handler(event).body
    expect([String, Hash, Array].include? body.class).to be true
  end
  it 'should add the name to the response string' do
    body = handler(event).body
    expect(body).to be == 'Hello, Ruby!'
  end
  it 'should say Hello, World! when name is not present' do
    event = SpecHelper::Event.new(body: nil)
    body = handler(event).body
    expect(body).to be == 'Hello, World!'
  end
end

Now let's deploy this function to the FaaStRuby servers. You need to specify the workspace in which your function will be deployed. Let's use our previously created 'catops-prod' workspace.

$ cd hello-world
$ faastruby deploy-to catops-prod
◐ Running tests... Passed!
...

Finished in 0.00519 seconds (files took 0.14255 seconds to load)
3 examples, 0 failures

◐ Building package... Done!
◒ Deploying to workspace 'catops-prod'... Done!

Here is what happened:
1. The tests run locally.
2. A deployment package was built with the contents of the hello-world function.
3. The package was uploaded to FaaStRuby.
4. On the server side, the function is unpacked and the gems are installed (the group 'test' is included).
5. The tests run again on the server to make sure the functions will work once deployed.
6. The gems from the group 'test' are removed and the function is deployed.

Time to test it out!
First, let's run the function with 'faastruby run':

$ faastruby run catops-prod --json '{"name":"Ruby"}'
Hello, Ruby!

It works! Now let's try again with CURL. If you run the same command with the flag '--curl', the curl request is generated for you:

$ faastruby run catops-prod --json '{"name":"Ruby"}' --curl
curl -X POST -H 'Content-Type: application/json' -d '{"name":"Ruby"}' 'https://api.faastruby.io/catops-prod/hello-world'
$ curl -X POST -H 'Content-Type: application/json' -d '{"name":"Ruby"}' 'https://api.faastruby.io/catops-prod/hello-world'
Hello, Ruby!

Now let's try without a request body. The response should be 'Hello, World!'.

$ faastruby run catops-prod
Hello, World!

Or with CURL:

$ curl 'https://api.faastruby.io/catops-prod/hello-world'
Hello, World!

Writing temporary files

For security reasons, FaaStRuby functions cannot write to disk. You can, however, create temporary files up to 5MB of size. Example:

require 'tmpdir'
def handler event
  temp_dir = Dir.mktmpdir('tmp')
  # ...
end

Execution Context

Sometimes you want your function to know about some data that you don't want to commit to source control or pass it on each request. With contexts you can have pre-loaded data available to your function on every run.

Execution context data is encrypted at rest, decrypted at runtime and passed to your function via 'event.context'.

To add an execution context to a function, you use the 'update-context' command and pass the function's workspace name:

$ faastruby update-context catops-prod --data '{"super_secret":"abc123"}'
◐ Uploading context data to 'catops-prod'... Done!

You can also read from stdin:

$ echo '{"super_secret":"abc123"}' | faastruby update-context catops-prod --stdin

Every time you run this command, the previous data is replaced with the new one. The data is handed to your function as-is, so it's up to you to parse it within your function.

require 'json'

def handler event
  context = JSON.parse(event.context)
  context["super_secret"] #=> "abc123"
  respond_with context
end

Custom responses: HTML, YAML, etc

Your function can respond with HTML, YAML, etc - All you need to do is setup the correct response headers.
Example:

require 'yaml'

def handler event
  headers = {
    'Content-Type' => 'application/x-yaml'
  }
  response = {foo: 'bar', far: 'boo'}.to_yaml
  respond_with response, status: 200, headers: headers
end

Scheduling functions

You can schedule your functions to run periodically or at any time in the future.
Functions can run on multiple schedules and you configure them inside "faastruby.yml". Example:

...
schedule:
  job1:
    when: every 2 hours
    body: {"foo": "bar"}
    method: POST
    query_params: {"param": "value"}
    headers: {"Content-Type": "application/json"}
  job2:
...

The schedule configuration takes the following keys:

when
Accepts plain English or the Cron syntax. Examples:
- every 3 hours
- 0 14 * * *
- every day at 2am
- every Tuesday at noon
- every 5 days
- every Sunday at 23:00

body
The request body to be passed to your function. Defaults to nil.

method
The request method to be used. Defaults are: GET when "body" is nil, and POST when "body" is not nil. Available as "event.body" within the function.

query_params
The URL query params to be passed to your function, in JSON format. Defaults to {}.

headers
The headers to be passed to your function. They will be available as "event.headers" and default to {}.
IMPORTANT NOTICE: If you don't specify a "Content-Type" header, the body will be processed as form data (key=value pairs).

CLI Reference

$ faastruby help
FaaStRuby CLI - Manage workspaces and functions hosted at faastruby.io

help, -h, --help     # Displays this help
-v                   # Print version and exit

Workspaces:
  create-workspace WORKSPACE_NAME [--stdout|-c, --credentials-file CREDENTIALS_FILE] [-e, --email YOUR_EMAIL_ADDRESS]
  destroy-workspace WORKSPACE_NAME [-y, --yes]
  list-workspace WORKSPACE_NAME

Functions:
  new FUNCTION_NAME [--blank] [--force]
    --blank              # Create a blank function
    --force              # Continue if directory already exists and overwrite files
  deploy-to WORKSPACE_NAME
  remove-from WORKSPACE_NAME [-y, --yes]
  update-context WORKSPACE_NAME [-d, --data 'STRING'] [--stdin]
  build [-s, --source SOURCE_DIR] [-o, --output-file OUTPUT_FILE]
  test
  run WORKSPACE_NAME [OPTIONS]
    Options:
    -b, --body 'DATA'              # The request body
    --stdin                        # Read the request body from STDIN
    -m, --method METHOD            # The request method
    -h, --header 'Header: Value'   # Set a header. Can be used multiple times.
    -f, --form 'a=1&b=2'           # Send form data and set header 'Content-Type: application/x-www-form-urlencoded'
    -j, --json '{"a":"1"}'         # Send JSON data and set header 'Content-Type: application/json'
    -t, --time                     # Return function run time in the response
    -q, --query 'foo=bar'          # Set a query parameter for the request. Can be used multiple times.
    --curl                         # Return the CURL command equivalent for the request

Credentials:
  add-credentials WORKSPACE_NAME -k API_KEY -s API_SECRET [-c CREDENTIALS_FILE]
  list-credentials [-c CREDENTIALS_FILE]

... This is a work in progress ...