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

feat: Add support for unix socket path and secure connection #8

Merged
merged 9 commits into from
Jan 23, 2023
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 determine now the SDK communicates with flagd.
josecolella marked this conversation as resolved.
Show resolved Hide resolved
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.socks"
josecolella marked this conversation as resolved.
Show resolved Hide resolved
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
technicalpickles marked this conversation as resolved.
Show resolved Hide resolved
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 providers the following methods and attributes:
josecolella marked this conversation as resolved.
Show resolved Hide resolved
#
# * <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,24 @@ 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 build_client(configuration)
options = :this_channel_is_insecure
if configuration.tls
options = GRPC::Core::ChannelCredentials.new(
configuration.root_certs
)
end
Grpc::Service::Stub.new(build_server_address(configuration), options).freeze
end
josecolella marked this conversation as resolved.
Show resolved Hide resolved

def build_server_address(configuration)
if configuration.unix_socket_path
"unix://#{configuration.unix_socket_path}"
else
"#{configuration.host}:#{configuration.port}"
end
end
josecolella marked this conversation as resolved.
Show resolved Hide resolved
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
technicalpickles marked this conversation as resolved.
Show resolved Hide resolved

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