Skip to content

Commit

Permalink
feat: Add support for unix socket path and secure connection (#8)
Browse files Browse the repository at this point in the history
Signed-off-by: Jose Colella <jose.colella@gusto.com>
Signed-off-by: Jose Miguel Colella <josecolella@yahoo.com>
Co-authored-by: Skye Gill <gill.skye95@gmail.com>
Co-authored-by: Todd Baert <toddbaert@gmail.com>
Co-authored-by: Josh Nichols <josh@technicalpickles.com>
  • Loading branch information
4 people authored Jan 23, 2023
1 parent 98b695b commit 88436c7
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 34 deletions.
56 changes: 46 additions & 10 deletions providers/openfeature-flagd-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,58 @@ gem install openfeature-flagd-provider

## Usage

The provider allows for configuration of host, port, socket_path, and tls connection.
The `OpenFeature::FlagD` supports multiple configuration options that dictate how the SDK communicates with flagd.
Options can be defined in the constructor or as environment variables, with constructor options having the highest precedence.

### Available options

| Option name | Environment variable name | Type | Default |
| ----------- | ------------------------- | ------- | --------- |
| host | FLAGD_HOST | string | localhost |
| port | FLAGD_PORT | number | 8013 |
| tls | FLAGD_TLS | boolean | false |
| unix_socket_path | FLAGD_SOCKET_PATH | string | nil |
| root_cert_path | FLAGD_SERVER_CERT_PATH | string | nil |

### Example using TCP

```ruby
OpenFeature::FlagD::Provider.configure do |config|
config.host = "localhost"
config.port = 8013
config.tls = false
OpenFeature::SDK.configure do |config|
# your provider of choice
config.provider = OpenFeature::FlagD::Provider.configure do |provider_config|
provider_config.host = "localhost"
provider_config.port = 8013
provider_config.tls = false
end
end
```

If no configurations are provided, the provider will be initialized with the following environment variables:
### Example using a Unix socket

```ruby
OpenFeature::SDK.configure do |config|
# your provider of choice
config.provider = OpenFeature::FlagD::Provider.configure do |provider_config|
provider_config.unix_socket_path = "tmp/flagd.sock"
end
end
```


### Example using a secure connection

```ruby
OpenFeature::SDK.configure do |config|
# your provider of choice
config.provider = OpenFeature::FlagD::Provider.configure do |provider_config|
provider_config.host = "localhost"
provider_config.port = 8013
provider_config.tls = true
provider_config.root_cert_path = './ca.pem'
end
end
```

> FLAGD_HOST
> FLAGD_PORT
> FLAGD_TLS
> FLAGD_SOCKET_PATH

If no environment variables are set the [default configuration](./lib/openfeature/flagd/provider/configuration.rb) is set

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "grpc"

require_relative "provider/configuration"
require_relative "provider/client"

Expand All @@ -15,7 +17,7 @@ module FlagD
# config.port = 8379
# config.tls = false
# end
# The Provider providers the following methods and attributes:
# The Provider provides the following methods and attributes:
#
# * <tt>metadata</tt> - Returns the associated provider metadata with the name
#
Expand All @@ -29,7 +31,7 @@ module FlagD
# manner; <tt>client.resolve_float_value(flag_key: 'float-flag', default_value: 2.0)</tt>
#
# * <tt>resolve_string_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_string_value(flag_key: 'string-flag', default_value: 'some-defau;t-value')</tt>
# manner; <tt>client.resolve_string_value(flag_key: 'string-flag', default_value: 'some-default-value')</tt>
#
# * <tt>resolve_object_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_object_value(flag_key: 'object-flag', default_value: { default_value: 'value'})</tt>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
# frozen_string_literal: true

require "grpc"

require_relative "schema/v1/schema_services_pb"
require_relative "configuration"

module OpenFeature
module FlagD
module Provider
# Client represents a wrapper for the GRPC stub that allows for resolution of boolean, string, number, and object
# values. The implementation follows the details specified in https://docs.openfeature.dev/docs/specification/sections/providers
#
#
# Within the Client instance, the following methods are available:
# The Client provides the following methods and attributes:
#
# * <tt>metadata</tt> - Returns the associated provider metadata with the name
#
# * <tt>resolve_boolean_value(flag_key:, default_value:, context: nil)</tt> -
# Resolves the boolean value of the flag_key
# * <tt>resolve_boolean_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_boolean(flag_key: 'boolean-flag', default_value: false)</tt>
#
# * <tt>resolve_integer_value(flag_key:, default_value:, context: nil)</tt> - Resolves the
# manner; <tt>client.resolve_boolean = File.read('path/to/filename.png')</tt>

# * <tt>resolve_integer_value</tt> - Allows you to specify any header field in your email such
# as <tt>headers['X-No-Spam'] = 'True'</tt>. Note that declaring a header multiple times
# will add many fields of the same name. Read #headers doc for more information.
# * <tt>resolve_integer_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_integer_value(flag_key: 'integer-flag', default_value: 2)</tt>
#
# * <tt>resolve_float_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_float_value(flag_key: 'float-flag', default_value: 2.0)</tt>
#
# * <tt>resolve_string_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_string_value(flag_key: 'string-flag', default_value: 'some-default-value')</tt>
#
# * <tt>resolve_object_value(flag_key:, default_value:, context: nil)</tt>
# manner; <tt>client.resolve_object_value(flag_key: 'flag', default_value: { default_value: 'value'})</tt>
class Client
attr_reader :metadata

def initialize(configuration: nil)
@configuration = Configuration.default_config
.merge(Configuration.environment_variables_config)
.merge(configuration)
@configuration = configuration.freeze
@metadata = Metadata.new(PROVIDER_NAME).freeze
@grpc_client = Grpc::Service::Stub.new(
"#{@configuration.host}:#{@configuration.port}",
:this_channel_is_insecure
).freeze

@grpc_client = build_client(configuration)
end

PROVIDER_NAME = "flagd Provider"
Expand Down Expand Up @@ -74,6 +76,26 @@ def resolve_#{type}_value(flag_key:, default_value:, context: nil)
def error_response(error_code, error_message)
ResolutionDetails.new(error_code, error_message, "ERROR", nil, nil).to_h.freeze
end

def grpc_client(configuration)
return @grpc_client unless defined?(@grpc_client)

options = :this_channel_is_insecure
if configuration.tls
options = GRPC::Core::ChannelCredentials.new(
configuration.root_certs
)
end
@grpc_client = Grpc::Service::Stub.new(build_server_address(configuration), options).freeze
end

def server_address
@server_address ||= if @configuration.unix_socket_path
"unix://#{configuration.unix_socket_path}"
else
"#{@configuration.host}:#{@configuration.port}"
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ module Provider
# This class is not meant to be interacted with directly but instead through the
# <tt>OpenFeature::FlagD::Provider.configure</tt> method
class Configuration
attr_accessor :host, :port, :tls
attr_accessor :host, :port, :tls, :unix_socket_path, :root_cert_path

ENVIRONMENT_CONFIG_NAME = {
host: "FLAGD_HOST",
port: "FLAGD_PORT",
tls: "FLAGD_TLS"
tls: "FLAGD_TLS",
unix_socket_path: "FLAGD_SOCKET_PATH",
root_cert_path: "FLAGD_SERVER_CERT_PATH"
}.freeze

def merge(other_configuration)
Expand All @@ -21,6 +23,13 @@ def merge(other_configuration)
@host = other_configuration.host if !other_configuration.host.nil? && @host.nil?
@port = other_configuration.port if !other_configuration.port.nil? && @port.nil?
@tls = other_configuration.tls if !other_configuration.tls.nil? && @tls.nil?
if !other_configuration.unix_socket_path.nil? && @unix_socket_path.nil?
@unix_socket_path = other_configuration.unix_socket_path
end
if !other_configuration.root_cert_path.nil? && @root_cert.nil?
@root_cert = File.read(other_configuration.root_cert_path)
end

self
end

Expand All @@ -38,6 +47,14 @@ def self.environment_variables_config
configuration.tls = ENV.fetch(ENVIRONMENT_CONFIG_NAME[:tls],
nil) == "true"
end
unless ENV[ENVIRONMENT_CONFIG_NAME[:unix_socket_path]].nil?
configuration.unix_socket_path = ENV.fetch(ENVIRONMENT_CONFIG_NAME[:unix_socket_path],
nil)
end
unless ENV[ENVIRONMENT_CONFIG_NAME[:root_cert_path]].nil?
root_cert_path = ENV.fetch(ENVIRONMENT_CONFIG_NAME[:root_cert_path], nil)
configuration.root_cert = File.read(root_cert_path)
end

configuration
end
Expand All @@ -47,6 +64,8 @@ def self.default_config
configuration.host = "localhost"
configuration.port = 8013
configuration.tls = false
configuration.unix_socket_path = nil
configuration.root_cert_path = nil
configuration
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

# https://docs.openfeature.dev/docs/specification/sections/providers
RSpec.describe OpenFeature::FlagD::Provider::Client do
subject(:client) { described_class.new }
let(:configuration) { OpenFeature::FlagD::Provider::Configuration.default_config }
subject(:client) { described_class.new(configuration: configuration) }

context "https://docs.openfeature.dev/docs/specification/sections/providers#requirement-211" do
it do
expect(client).to respond_to(:metadata)
expect(client.metadata).to respond_to(:name)
expect(client.metadata.name).to eq("Flagd Provider")
expect(client.metadata.name).to eq("flagd Provider")
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
it "metadata name is defined" do
expect(described_class).to respond_to(:metadata)
expect(described_class.metadata).to respond_to(:name)
expect(described_class.metadata.name).to eq("Flagd Provider")
expect(described_class.metadata.name).to eq("flagd Provider")
end
end
end

0 comments on commit 88436c7

Please sign in to comment.