Skip to content

Commit 38d2bd9

Browse files
authored
Merge pull request #5024 from DataDog/ffl-1319-add-agent-communication-for-openfeature
[FFL-1318] Add open feature component [FFL-1319] Add open feature evaluation exposure events
2 parents e4ed440 + e572afc commit 38d2bd9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3169
-3
lines changed

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Style:
4747
- "spec/datadog/kit/**/**"
4848
- "spec/datadog/profiling*"
4949
- "spec/datadog/profiling/**/*"
50+
- "spec/datadog/open_feature*"
51+
- "spec/datadog/open_feature/**/*"
5052
- "yard/**/*.rb"
5153

5254
Layout:

appraisal/ruby-3.4.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
gem 'resque'
188188
gem 'roda', '>= 2.0.0'
189189
gem 'semantic_logger', '~> 4.0'
190-
# Note: Sidekiq 8 uses different timestamp formatting compared to prior versions. As long as
190+
# NOTE: Sidekiq 8 uses different timestamp formatting compared to prior versions. As long as
191191
# versions <8 are supported, make sure there's some CI running both older and newer versions.
192192
gem 'sidekiq', '~> 8'
193193
gem 'sneakers', '>= 2.12.0'

lib/datadog.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require_relative 'datadog/appsec'
1010
require_relative 'datadog/di'
1111
require_relative 'datadog/data_streams'
12+
require_relative 'datadog/open_feature'
1213

1314
# Line probes will not work on Ruby < 2.6 because of lack of :script_compiled
1415
# trace point. Activate DI automatically on supported Ruby versions but

lib/datadog/core/configuration/components.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
require_relative '../../profiling/component'
1616
require_relative '../../appsec/component'
1717
require_relative '../../di/component'
18+
require_relative '../../open_feature/component'
1819
require_relative '../../error_tracking/component'
1920
require_relative '../crashtracking/component'
2021
require_relative '../environment/agent_info'
@@ -106,7 +107,8 @@ def build_data_streams(settings, agent_settings, logger)
106107
:dynamic_instrumentation,
107108
:appsec,
108109
:agent_info,
109-
:data_streams
110+
:data_streams,
111+
:open_feature
110112

111113
def initialize(settings)
112114
@settings = settings
@@ -140,6 +142,7 @@ def initialize(settings)
140142
@runtime_metrics = self.class.build_runtime_metrics_worker(settings, @logger, telemetry)
141143
@health_metrics = self.class.build_health_metrics(settings, @logger, telemetry)
142144
@appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
145+
@open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
143146
@dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
144147
@error_tracking = Datadog::ErrorTracking::Component.build(settings, @tracer, @logger)
145148
@data_streams = self.class.build_data_streams(settings, agent_settings, @logger)
@@ -199,6 +202,9 @@ def shutdown!(replacement = nil)
199202
# Shutdown DI after remote, since remote config triggers DI operations.
200203
dynamic_instrumentation&.shutdown!
201204

205+
# Shutdown OpenFeature component
206+
open_feature&.shutdown!
207+
202208
# Decommission AppSec
203209
appsec&.shutdown!
204210

lib/datadog/core/configuration/supported_configurations.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module Configuration
4343
"DD_ENV" => {version: ["A"]},
4444
"DD_ERROR_TRACKING_HANDLED_ERRORS" => {version: ["A"]},
4545
"DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE" => {version: ["A"]},
46+
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED" => {version: ["A"]},
4647
"DD_GIT_COMMIT_SHA" => {version: ["A"]},
4748
"DD_GIT_REPOSITORY_URL" => {version: ["A"]},
4849
"DD_HEALTH_METRICS_ENABLED" => {version: ["A"]},

lib/datadog/core/remote/client/capabilities.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative '../../utils/base64'
44
require_relative '../../../appsec/remote'
55
require_relative '../../../tracing/remote'
6+
require_relative '../../../open_feature/remote'
67

78
module Datadog
89
module Core
@@ -38,6 +39,12 @@ def register(settings)
3839
register_receivers(Datadog::DI::Remote.receivers(@telemetry))
3940
end
4041

42+
if settings.respond_to?(:open_feature) && settings.open_feature.enabled
43+
register_capabilities(Datadog::OpenFeature::Remote.capabilities)
44+
register_products(Datadog::OpenFeature::Remote.products)
45+
register_receivers(Datadog::OpenFeature::Remote.receivers(@telemetry))
46+
end
47+
4148
register_capabilities(Datadog::Tracing::Remote.capabilities)
4249
register_products(Datadog::Tracing::Remote.products)
4350
register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))

lib/datadog/open_feature.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'core/configuration'
4+
require_relative 'open_feature/configuration'
5+
6+
module Datadog
7+
# A namespace for the OpenFeature component.
8+
module OpenFeature
9+
Core::Configuration::Settings.extend(Configuration::Settings)
10+
11+
def self.enabled?
12+
Datadog.configuration.open_feature.enabled
13+
end
14+
15+
def self.engine
16+
Datadog.send(:components).open_feature&.engine
17+
end
18+
end
19+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'evaluation_engine'
4+
require_relative 'exposures/buffer'
5+
require_relative 'exposures/worker'
6+
require_relative 'exposures/deduplicator'
7+
require_relative 'exposures/reporter'
8+
require_relative 'transport/http'
9+
10+
module Datadog
11+
module OpenFeature
12+
# This class is the entry point for the OpenFeature component
13+
class Component
14+
attr_reader :engine
15+
16+
def self.build(settings, agent_settings, logger:, telemetry:)
17+
return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
18+
19+
unless settings.respond_to?(:remote) && settings.remote.enabled
20+
message = 'OpenFeature could not be enabled as Remote Configuration is currently disabled. ' \
21+
'To enable Remote Configuration, see https://docs.datadoghq.com/agent/remote_config'
22+
logger.warn(message)
23+
24+
return
25+
end
26+
27+
new(settings, agent_settings, logger: logger, telemetry: telemetry)
28+
end
29+
30+
def initialize(settings, agent_settings, logger:, telemetry:)
31+
transport = Transport::HTTP.build(agent_settings: agent_settings, logger: logger)
32+
@worker = Exposures::Worker.new(settings: settings, transport: transport, telemetry: telemetry, logger: logger)
33+
34+
reporter = Exposures::Reporter.new(@worker, telemetry: telemetry, logger: logger)
35+
@engine = EvaluationEngine.new(reporter, telemetry: telemetry, logger: logger)
36+
end
37+
38+
def shutdown!
39+
@worker.graceful_shutdown
40+
end
41+
end
42+
end
43+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module OpenFeature
5+
module Configuration
6+
# A settings class for the OpenFeature component.
7+
module Settings
8+
def self.extended(base)
9+
base = base.singleton_class unless base.is_a?(Class)
10+
add_settings!(base)
11+
end
12+
13+
def self.add_settings!(base)
14+
base.class_eval do
15+
settings :open_feature do
16+
option :enabled do |o|
17+
o.type :bool
18+
o.env 'DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED'
19+
o.default false
20+
end
21+
end
22+
end
23+
end
24+
end
25+
end
26+
end
27+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'ext'
4+
require_relative 'noop_evaluator'
5+
require_relative 'resolution_details'
6+
7+
module Datadog
8+
module OpenFeature
9+
# This class performs the evaluation of the feature flag
10+
class EvaluationEngine
11+
ReconfigurationError = Class.new(StandardError)
12+
13+
ALLOWED_TYPES = %w[boolean string number float integer object].freeze
14+
15+
def initialize(reporter, telemetry:, logger:)
16+
@reporter = reporter
17+
@telemetry = telemetry
18+
@logger = logger
19+
20+
@evaluator = NoopEvaluator.new(nil)
21+
end
22+
23+
def fetch_value(flag_key:, default_value:, expected_type:, evaluation_context: nil)
24+
unless ALLOWED_TYPES.include?(expected_type)
25+
message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
26+
return ResolutionDetails.build_error(
27+
value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message
28+
)
29+
end
30+
31+
context = evaluation_context&.fields || {}
32+
result = @evaluator.get_assignment(flag_key, default_value, context, expected_type)
33+
34+
@reporter.report(result, flag_key: flag_key, context: evaluation_context)
35+
36+
result
37+
rescue => e
38+
@telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value')
39+
40+
ResolutionDetails.build_error(
41+
value: default_value, error_code: Ext::PROVIDER_FATAL, error_message: e.message
42+
)
43+
end
44+
45+
def reconfigure!(configuration)
46+
@logger.debug('OpenFeature: Removing configuration') if configuration.nil?
47+
48+
@evaluator = NoopEvaluator.new(configuration)
49+
rescue => e
50+
message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration'
51+
52+
@logger.error("#{message}, error #{e.inspect}")
53+
@telemetry.report(e, description: message)
54+
55+
raise ReconfigurationError, e.message
56+
end
57+
end
58+
end
59+
end

0 commit comments

Comments
 (0)