Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ec02cfd
Add OpenFeature component
Strech Oct 22, 2025
2b48256
Fir require of the OpenFeature component
Strech Oct 22, 2025
7bbe891
Add skeleton for evaluation API
Strech Oct 23, 2025
d6b0059
Add guard-clause to the Provider and Evaluator
Strech Oct 24, 2025
fb5bb0e
Add comment regarding GC
Strech Oct 24, 2025
e110fef
Update settings and component interface impl.
Strech Oct 24, 2025
fe004ed
Add RBS files for OpenFeature component
Strech Oct 24, 2025
52aaf77
Fix typing for Remote module
Strech Oct 24, 2025
57b2438
Add openfeature-sdk stub and fix component typing
Strech Oct 24, 2025
2e6993e
Fix settings RBS
Strech Oct 24, 2025
514df60
Wrap FFI code into a separate module
Strech Oct 27, 2025
db1c68a
Add tests for remote config
Strech Oct 27, 2025
46f6911
Add component tests
Strech Oct 27, 2025
24369bb
Add new environment configuration definition
Strech Oct 27, 2025
ac96647
Add component specs
Strech Oct 27, 2025
d909e2f
Add testing matrix and new group
Strech Oct 28, 2025
7585c4d
Adjust evaluator specs
Strech Oct 29, 2025
2557c2e
Add some basic tests for Provider
Strech Oct 29, 2025
815dbab
Extract Binding module
Strech Nov 4, 2025
378b50c
Merge 815dbab652b20a91d2af2b9b774e812d944bed66 into 333959d4248848722…
Strech Nov 4, 2025
04bd273
[🤖] Lock Dependency: https://github.com/DataDog/dd-trace-rb/actions/r…
dd-apm-ecosystems-autobot[bot] Nov 4, 2025
4a529b7
Rename Evaluator to be EvaluationEngine
Strech Nov 4, 2025
3339108
Update EvaluationEngine initialization interface
Strech Nov 4, 2025
d1a1224
Rework evaluation engine name in the component
Strech Nov 4, 2025
ad79d71
Rework evaluation engine name in the component
Strech Nov 4, 2025
e806b6c
Fix naming in provider
Strech Nov 4, 2025
77cae80
Add extention module with constants
Strech Nov 4, 2025
4676e60
Update component initialization
Strech Nov 4, 2025
c9a23bc
Add reconfiguration mutex to the evaluation engine
Strech Nov 5, 2025
68a7bad
Make engine dynamic inside provider
Strech Nov 7, 2025
0ad73e0
Add no-op evaluator to the engine
Strech Nov 7, 2025
51365e9
Update error and success results structure
Strech Nov 7, 2025
9cd236e
Introduce Binding::ResolutionDetails object
Strech Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Style:
- "spec/datadog/kit/**/**"
- "spec/datadog/profiling*"
- "spec/datadog/profiling/**/*"
- "spec/datadog/open_feature*"
- "spec/datadog/open_feature/**/*"
- "yard/**/*.rb"

Layout:
Expand Down
4 changes: 4 additions & 0 deletions Matrixfile
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@
'rails61-mysql2' => '❌ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ 3.5 / ❌ jruby',
'rails8-mysql2' => '❌ 2.5 / ❌ 2.6 / ❌ 2.7 / ❌ 3.0 / ❌ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ 3.5 / ❌ jruby',
},
'open_feature' => {
'openfeature-latest' => '❌ 2.5 / ❌ 2.6 / ❌ 2.7 / ❌ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ 3.5 / ❌ jruby',
'openfeature-min' => '❌ 2.5 / ❌ 2.6 / ❌ 2.7 / ❌ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ 3.5 / ❌ jruby',
},
}.each_with_object({}) do |(tasks, spec_metadata), hash|
# Explode arrays of task names into individual tasks
# e.g. ['rails', 'railsdisableenv'] => {'...'} becomes 'rails7' => {'...'}, 'railsdisableenv7' => {'...'}
Expand Down
12 changes: 9 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ namespace :spec do
:graphql, :graphql_unified_trace_patcher, :graphql_trace_patcher, :graphql_tracing_patcher,
:rails, :railsredis, :railsredis_activesupport, :railsactivejob,
:elasticsearch, :http, :redis, :sidekiq, :sinatra, :hanami, :hanami_autoinstrument,
:profiling, :core_with_libdatadog_api, :error_tracking]
:profiling, :core_with_libdatadog_api, :error_tracking, :open_feature]

desc '' # "Explicitly hiding from `rake -T`"
RSpec::Core::RakeTask.new(:main) do |t, args|
t.pattern = 'spec/**/*_spec.rb'
t.exclude_pattern = 'spec/**/{appsec/integration,contrib,benchmark,redis,auto_instrument,opentelemetry,profiling,crashtracking,error_tracking,rubocop}/**/*_spec.rb,' \
' spec/**/{auto_instrument,opentelemetry,process_discovery,stable_config,ddsketch}_spec.rb,' \
t.exclude_pattern = 'spec/**/{appsec/integration,contrib,benchmark,redis,auto_instrument,opentelemetry,open_feature,profiling,crashtracking,error_tracking,rubocop}/**/*_spec.rb,' \
' spec/**/{auto_instrument,opentelemetry,open_feature,process_discovery,stable_config,ddsketch}_spec.rb,' \
' spec/datadog/gem_packaging_spec.rb'
t.rspec_opts = args.to_a.join(' ')
end
Expand Down Expand Up @@ -125,6 +125,12 @@ namespace :spec do
t.rspec_opts = args.to_a.join(' ')
end

desc '' # "Explicitly hiding from `rake -T`"
RSpec::Core::RakeTask.new(:open_feature) do |t, args|
t.pattern = 'spec/datadog/open_feature/**/*_spec.rb,spec/datadog/open_feature_spec.rb'
t.rspec_opts = args.to_a.join(' ')
end

desc '' # "Explicitly hiding from `rake -T`"
RSpec::Core::RakeTask.new(:rails) do |t, args|
t.pattern = 'spec/datadog/tracing/contrib/rails/**/*_spec.rb'
Expand Down
1 change: 1 addition & 0 deletions appraisal/ruby-3.1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
build_coverage_matrix('dalli', [2])
build_coverage_matrix('karafka', min: '2.3.0')
build_coverage_matrix('devise', min: '3.2.1')
build_coverage_matrix('openfeature', min: '0.3.1', gem: 'openfeature-sdk')

appraise 'relational_db' do
gem 'activerecord', '~> 7'
Expand Down
1 change: 1 addition & 0 deletions appraisal/ruby-3.2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
build_coverage_matrix('dalli', [2])
build_coverage_matrix('karafka', min: '2.3.0')
build_coverage_matrix('devise', min: '3.2.1')
build_coverage_matrix('openfeature', min: '0.3.1', gem: 'openfeature-sdk')

appraise 'relational_db' do
gem 'activerecord', '~> 7'
Expand Down
1 change: 1 addition & 0 deletions appraisal/ruby-3.3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
build_coverage_matrix('dalli', [2])
build_coverage_matrix('karafka', min: '2.3.0')
build_coverage_matrix('devise', min: '3.2.1')
build_coverage_matrix('openfeature', min: '0.3.1', gem: 'openfeature-sdk')

appraise 'relational_db' do
gem 'activerecord', '~> 7'
Expand Down
3 changes: 2 additions & 1 deletion appraisal/ruby-3.4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
build_coverage_matrix('dalli', [2])
build_coverage_matrix('karafka', min: '2.3.0')
build_coverage_matrix('devise', min: '3.2.1')
build_coverage_matrix('openfeature', min: '0.3.1', gem: 'openfeature-sdk')

appraise 'relational_db' do
# ActiveRecord locked because tests are failing with 7.1, which was attempted as a part of Ruby 3.4 testing in CI.
Expand Down Expand Up @@ -186,7 +187,7 @@
gem 'resque'
gem 'roda', '>= 2.0.0'
gem 'semantic_logger', '~> 4.0'
# Note: Sidekiq 8 uses different timestamp formatting compared to prior versions. As long as
# NOTE: Sidekiq 8 uses different timestamp formatting compared to prior versions. As long as
# versions <8 are supported, make sure there's some CI running both older and newer versions.
gem 'sidekiq', '~> 8'
gem 'sneakers', '>= 2.12.0'
Expand Down
1 change: 1 addition & 0 deletions appraisal/ruby-3.5.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
build_coverage_matrix('dalli', [2])
build_coverage_matrix('karafka', min: '2.3.0')
build_coverage_matrix('devise', min: '3.2.1')
build_coverage_matrix('openfeature', min: '0.3.1', gem: 'openfeature-sdk')

appraise 'relational_db' do
# ActiveRecord locked because tests are failing with 7.1, which was attempted as a part of Ruby 3.4 testing in CI.
Expand Down
38 changes: 38 additions & 0 deletions gemfiles/ruby_3.3_openfeature_latest.gemfile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions gemfiles/ruby_3.3_openfeature_min.gemfile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/datadog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative 'datadog/profiling'
require_relative 'datadog/appsec'
require_relative 'datadog/di'
require_relative 'datadog/open_feature'

# Line probes will not work on Ruby < 2.6 because of lack of :script_compiled
# trace point. Activate DI automatically on supported Ruby versions but
Expand Down
3 changes: 3 additions & 0 deletions lib/datadog/core/configuration/components.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require_relative '../../profiling/component'
require_relative '../../appsec/component'
require_relative '../../di/component'
require_relative '../../open_feature/component'
require_relative '../../error_tracking/component'
require_relative '../crashtracking/component'
require_relative '../environment/agent_info'
Expand Down Expand Up @@ -90,6 +91,7 @@ def build_crashtracker(settings, agent_settings, logger:)
:error_tracking,
:dynamic_instrumentation,
:appsec,
:open_feature,
:agent_info

def initialize(settings)
Expand Down Expand Up @@ -124,6 +126,7 @@ def initialize(settings)
@runtime_metrics = self.class.build_runtime_metrics_worker(settings, @logger, telemetry)
@health_metrics = self.class.build_health_metrics(settings, @logger, telemetry)
@appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
@open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
@dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
@error_tracking = Datadog::ErrorTracking::Component.build(settings, @tracer, @logger)
@environment_logger_extra[:dynamic_instrumentation_enabled] = !!@dynamic_instrumentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module Configuration
"DD_ENV" => {version: ["A"]},
"DD_ERROR_TRACKING_HANDLED_ERRORS" => {version: ["A"]},
"DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE" => {version: ["A"]},
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED" => {version: ["A"]},
"DD_GIT_COMMIT_SHA" => {version: ["A"]},
"DD_GIT_REPOSITORY_URL" => {version: ["A"]},
"DD_HEALTH_METRICS_ENABLED" => {version: ["A"]},
Expand Down
7 changes: 7 additions & 0 deletions lib/datadog/core/remote/client/capabilities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative '../../utils/base64'
require_relative '../../../appsec/remote'
require_relative '../../../tracing/remote'
require_relative '../../../open_feature/remote'

module Datadog
module Core
Expand Down Expand Up @@ -38,6 +39,12 @@ def register(settings)
register_receivers(Datadog::DI::Remote.receivers(@telemetry))
end

if settings.respond_to?(:open_feature) && settings.open_feature.enabled
register_capabilities(Datadog::OpenFeature::Remote.capabilities)
register_products(Datadog::OpenFeature::Remote.products)
register_receivers(Datadog::OpenFeature::Remote.receivers(@telemetry))
end

register_capabilities(Datadog::Tracing::Remote.capabilities)
register_products(Datadog::Tracing::Remote.products)
register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))
Expand Down
18 changes: 18 additions & 0 deletions lib/datadog/open_feature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require_relative 'open_feature/extensions'

module Datadog
# A namespace for the OpenFeature component.
module OpenFeature
Extensions.activate!

def self.enabled?
Datadog.configuration.open_feature.enabled
end

def self.engine
Datadog.send(:components).open_feature&.engine
end
end
end
11 changes: 11 additions & 0 deletions lib/datadog/open_feature/binding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Datadog
module OpenFeature
# A namespace for binding code
module Binding
end
end
end

require_relative 'binding/evaluator'
44 changes: 44 additions & 0 deletions lib/datadog/open_feature/binding/evaluator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require_relative 'resolution_details'

module Datadog
module OpenFeature
module Binding
class Evaluator
def initialize(ufc_json)
# NOTE: In real binding we will parse and create Configuration
@ufc_json = ufc_json
end

def get_assignment(_flag_key, _evaluation_context, expected_type, _time)
ResolutionDetails.new(
value: generate(expected_type),
reason: 'TARGETING_MATCH',
variant: 'hardcoded-variant',
allocation_key: 'hardcoded-allocation-key',
flag_metadata: {
'doLog' => true,
'allocationKey' => 'hardcoded-allocation-key'
},
do_log: true,
extra_logging: {}
)
end

private

def generate(expected_type)
case expected_type
when :boolean then true
when :string then 'hello'
when :number then 9000
when :integer then 42
when :float then 36.6
when :object then [1, 2, 3]
end
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/datadog/open_feature/binding/resolution_details.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Datadog
module OpenFeature
# A namespace for binding code
module Binding
ResolutionDetails = Struct.new(
:value,
:reason,
:variant,
:error_code,
:error_message,
:flag_metadata,
:allocation_key,
:do_log,
:extra_logging,
keyword_init: true
)
end
end
end
41 changes: 41 additions & 0 deletions lib/datadog/open_feature/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require_relative 'evaluation_engine'

module Datadog
module OpenFeature
# This class is the entry point for the OpenFeature component
class Component
attr_reader :telemetry, :engine

def self.build(settings, agent_settings, logger:, telemetry:)
return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled

unless settings.respond_to?(:remote) && settings.remote.enabled
logger.warn('OpenFeature: Could not be enabled without Remote Configuration Management available')

return
end

new(settings, agent_settings, logger: logger, telemetry: telemetry)
rescue
Datadog.logger.warn('OpenFeature is disabled, see logged errors above')

nil
end

def initialize(settings, agent_settings, logger:, telemetry:)
@settings = settings
@agent_settings = agent_settings
@logger = logger
@telemetry = telemetry

@engine = EvaluationEngine.new(telemetry, logger: logger)
end

def shutdown!
# no-op
end
end
end
end
10 changes: 10 additions & 0 deletions lib/datadog/open_feature/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require_relative 'configuration/settings'

module Datadog
module OpenFeature
module Configuration
end
end
end
Loading