Functions Framework for Ruby

repository·main·Indexed 41 days ago

https://github.com/googlecloudplatform/functions-framework-ruby

An open source framework for writing lightweight, portable Ruby functions for serverless environments such as Google Cloud Run functions and Knative-based platforms. It supports Ruby 3.0+ and provides capabilities for defining HTTP functions via FunctionsFramework.http, automatic unmarshalling of CloudEvents, and local development server testing.

Tokens
15.1K
Snippets
55
Records
70
Agent score
51%

What's inside Functions Framework for Ruby

  1. What is the Functions Framework for Ruby?

    main

    The Functions Framework for Ruby is an open source framework designed for writing lightweight, portable Ruby functions that run in serverless environments.

    Functions written with this framework are portable and can run in:

    • Google Cloud Functions
    • Google Cloud Run
    • Any Knative-based environment
    • Your local development machine

    It abstracts away the need for manual HTTP server configuration or complex request handling logic, allowing you to focus on the function logic itself.

  2. Overview of Functions Framework for Ruby

    main

    Functions Framework for Ruby is an open source framework for writing lightweight, portable Ruby functions designed to run in serverless environments. It allows you to define functions using standard Ruby constructs that can be deployed to Google Cloud Run functions, any Knative-based environment, or run locally for development.

    Key capabilities include:

    • Defining named functions that respond to requests.
    • Automatic unmarshalling of CloudEvents spec events.
    • Automatic conversion of legacy Google Cloud events (e.g., Cloud Pub/Sub, Cloud Storage) to CloudEvents.
    • Integration with standard Ruby libraries like Rack and Minitest.
    • Local development server for testing.
  3. Use Sinatra with the Functions Framework for Ruby

    main

    You can use the Sinatra web framework to assist in writing HTTP functions within the Functions Framework. This is useful for leveraging Sinatra's routes, templates, and custom middleware.

    Best Practices:

    • Use the Sinatra "modular" interface by creating a subclass of Sinatra::Base. This allows you to call the Sinatra app explicitly from your function.
    • While the framework can serve large applications (like Ruby on Rails), it is primarily designed for lightweight functions. For large-scale applications, Google Cloud Run or App Engine are recommended.

    In this specific example, the function sinatra_example delegates requests to a Sinatra app defined in app.rb.

  4. How the automated release pipeline works

    main

    The project uses an automated release pipeline triggered by Conventional Commit messages pushed to the main branch.

    1. Release Pull Request: The release-please bot detects semantic changes (feat:, fix:, or breaking changes) and automatically creates a pull request containing the proposed new version and a changelog entry.
    2. Merging: A maintainer merges the pull request to accept the release. Important: Do not modify the version number in the pull request. Ensure the autorelease: pending label remains applied to the PR.
    3. Automated Tagging: After merging, a bot (running every 15 minutes) tags the release and creates a GitHub release. It then updates the label from autorelease: pending to autorelease: tagged.
    4. Automated Publishing: A final job builds and pushes the gem to Rubygems and the documentation to googleapis.dev, finally updating the label to autorelease: published.
  5. Configure logging and error verbosity

    main

    Detailed Errors

    Detailed errors are disabled by default. To enable them, set the FUNCTION_DETAILED_ERRORS environment variable to any non-empty string. If the variable is unset or empty, detailed errors are disabled.

    Logging Levels

    The logging level defaults to the value of the FUNCTION_LOGGING_LEVEL environment variable. If not set, it defaults to INFO.

    Valid values (corresponding to Ruby Logger::Severity):

    • DEBUG
    • INFO
    • WARN
    • ERROR
    • FATAL
    • UNKNOWN

    You can also control verbosity using command-line flags: --verbose to increase verbosity and --quiet to decrease it.

  6. Access the execution context and global data

    main

    When a function executes, the object context (self) is an instance of FunctionsFramework::Function::Callable. This context provides methods to interact with the runtime environment:

    • logger: A convenience method to obtain the logger (identical to FunctionsFramework.logger).
    • global(key): Accesses global shared data, which is a set of immutable key-value pairs available to every function invocation.

    By default, two keys are available in global:

    • :function_name: The String name of the running function.
    • :function_type: Either :http or :cloud_event.

    Note: While global data is immutable during function execution to prevent concurrency issues, it can be set during on_startup tasks.

    require "functions_framework"
    
    FunctionsFramework.cloud_event "hello" do |event|
      logger.info "Now running the function called #{global(:function_name)}"
    end
  7. Quickstart: Run a Hello World function locally

    main

    Follow these steps to set up a local development environment and run a simple HTTP function using the Functions Framework for Ruby.

    1. Create a Gemfile to manage your dependencies:

      source "https://rubygems.org"
      gem "functions_framework", "~> 1.0"
    2. Create an app.rb file to define your function. In this example, we define an HTTP function named hello:

      require "functions_framework"
      
      FunctionsFramework.http("hello") do |request|
        "Hello, world!\n"
      end
    3. Install dependencies and start the server using Bundler. You must specify the --target flag to tell the framework which function to execute:

      bundle install
      bundle exec functions-framework-ruby --target hello
    4. Test the function by sending a request via curl in a separate terminal:

      curl http://localhost:8080
      # Output: Hello, world!
    bundle exec functions-framework-ruby --target hello
  8. Run the Sinatra function locally

    main

    To perform integration tests locally, start the server in one shell and send requests from another.

    1. Start the server:

      toys server
    2. Send requests: In a separate shell, use toys request to hit the server. You can specify a path using the --path flag.

      toys request
      # or
      toys request --path /hello/Sinatra

    Use CTRL+C to stop the server.

    toys server
    # In another shell:
    toys request --path /hello/Sinatra
  9. Share resources across function invocations

    main

    To avoid the overhead of re-establishing connections (e.g., to a database) for every invocation, you can share resources across multiple functions running on the same Ruby instance.

    Recommended Pattern:

    1. Initialize the resource in a FunctionsFramework.on_startup block.
    2. Store the resource in the global shared data using set_global.
    3. Access the resource within your function using global(key).

    Warning: Ensure any shared resource is thread-safe, as functions may run concurrently in different threads. Additionally, do not rely on cleanup hooks (like at_exit), as serverless runtimes may throttle CPU or terminate the process without allowing cleanup tasks to run.

    require "functions_framework"
    
    # Use an on_startup block to initialize a shared client and store it in
    # the global shared data.
    FunctionsFramework.on_startup do
      require "google/cloud/storage"
      set_global :storage_client, Google::Cloud::Storage.new
    end
    
    # The shared storage_client can be accessed by all function invocations
    # via the global shared data.
    FunctionsFramework.http "storage_example" do |request|
      bucket = global(:storage_client).bucket "my-bucket"
      file = bucket.file "path/to/my-file.txt"
      file.download.to_s
    end
  10. Deploy the Echo example to Cloud Run

    main

    You can deploy the example functions to Cloud Run using the toys run deploy command.

    Requirements:

    • A Google Cloud project with billing enabled.
    • Cloud Build and Cloud Run APIs enabled (the command may prompt you to enable these).

    Deployment Command: Provide the function name and the project ID. If you have a default project set via gcloud config, the --project flag is optional.

    toys run deploy http_example --project=[PROJECT NAME]

    Testing the deployed function: Once deployment is complete, the command will output a hostname. Use toys run request or toys run event followed by that hostname to interact with the live service.

    Example:

    toys run request echo-abcdefghij-uc.a.run.app
    toys run deploy http_example --project=[PROJECT NAME]
    
    # After deployment, use the provided hostname:
    toys run request [HOSTNAME]
  11. Handle errors in HTTP functions

    main

    If an HTTP function raises a StandardError, the Functions Framework catches it and returns a 500 (server error) response.

    If you require custom error responses (e.g., specific status codes or custom JSON error bodies), you should use a begin/rescue block within your function to manually construct a Rack-compatible response array.

    require "functions_framework"
    
    FunctionsFramework.http "error_reporter" do |request|
      begin
        raise "whoops!"
      rescue RuntimeError => e
        [500, {}, ["Uh, oh, got an error message: #{e.message}."]]
      end
    end