datadog 2.39.0 → 2.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +269 -44
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +256 -193
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +267 -171
- data/ext/datadog_profiling_native_extension/collectors_stack.h +17 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +307 -217
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +67 -99
- data/ext/datadog_profiling_native_extension/heap_recorder.c +353 -257
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +853 -496
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +42 -15
- data/ext/datadog_profiling_native_extension/profiling.c +30 -23
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +4 -80
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -7
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.c +116 -89
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -5
- data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +15 -15
- data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
- data/ext/libdatadog_api/datadog_ruby_common.h +7 -10
- data/ext/libdatadog_api/di.c +43 -3
- data/ext/libdatadog_api/extconf.rb +6 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +9 -1
- data/ext/libdatadog_api/otel_thread_context.c +274 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/process_discovery.c +15 -1
- data/ext/libdatadog_api/trace_exporter.c +2078 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +14 -9
- data/lib/datadog/ai_guard/component.rb +17 -8
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +13 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +8 -64
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +36 -14
- data/lib/datadog/ai_guard/ext.rb +4 -1
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +7 -5
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +58 -57
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/appsec/configuration.rb +11 -2
- data/lib/datadog/appsec/context.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
- data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +30 -3
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +15 -3
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
- data/lib/datadog/appsec/response.rb +1 -1
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
- data/lib/datadog/appsec/security_engine/engine.rb +5 -4
- data/lib/datadog/core/configuration/components.rb +50 -19
- data/lib/datadog/core/configuration/components_state.rb +8 -1
- data/lib/datadog/core/configuration/option_definition.rb +1 -1
- data/lib/datadog/core/configuration/options.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +38 -43
- data/lib/datadog/core/configuration/supported_configurations.rb +18 -2
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
- data/lib/datadog/core/environment/gc.rb +1 -1
- data/lib/datadog/core/environment/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +2 -2
- data/lib/datadog/core/metrics/helpers.rb +1 -1
- data/lib/datadog/core/metrics/options.rb +2 -2
- data/lib/datadog/core/process_discovery.rb +6 -2
- data/lib/datadog/core/remote/client/capabilities.rb +32 -21
- data/lib/datadog/core/remote/client.rb +45 -4
- data/lib/datadog/core/remote/component.rb +30 -7
- data/lib/datadog/core/remote/configuration/repository.rb +2 -2
- data/lib/datadog/core/remote/dispatcher.rb +47 -6
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
- data/lib/datadog/core/telemetry/event/log.rb +2 -2
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
- data/lib/datadog/core/telemetry/metric.rb +2 -2
- data/lib/datadog/core/telemetry/request.rb +2 -2
- data/lib/datadog/core/transport/http.rb +1 -1
- data/lib/datadog/core/transport/transport.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +170 -18
- data/lib/datadog/core/utils/sequence.rb +3 -6
- data/lib/datadog/core/utils/time.rb +16 -54
- data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +45 -14
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
- data/lib/datadog/di/component.rb +108 -3
- data/lib/datadog/di/configuration.rb +53 -2
- data/lib/datadog/di/context.rb +7 -2
- data/lib/datadog/di/contrib/active_record.rb +8 -2
- data/lib/datadog/di/el/compiler.rb +88 -12
- data/lib/datadog/di/el/evaluator.rb +82 -2
- data/lib/datadog/di/el/expression.rb +13 -2
- data/lib/datadog/di/evaluation_failure.rb +28 -0
- data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
- data/lib/datadog/di/guardrails_telemetry.rb +203 -0
- data/lib/datadog/di/instrumenter.rb +106 -15
- data/lib/datadog/di/logger.rb +32 -10
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_builder.rb +7 -6
- data/lib/datadog/di/probe_manager.rb +56 -8
- data/lib/datadog/di/probe_notification_builder.rb +31 -28
- data/lib/datadog/di/probe_notifier_worker.rb +56 -6
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/di/remote.rb +49 -5
- data/lib/datadog/di/serializer.rb +172 -55
- data/lib/datadog/di/snapshot_event.rb +38 -0
- data/lib/datadog/di/transport/http.rb +13 -3
- data/lib/datadog/di/transport/input.rb +32 -7
- data/lib/datadog/error_tracking/component.rb +1 -1
- data/lib/datadog/kit/appsec/events/v2.rb +2 -2
- data/lib/datadog/open_feature/activation.rb +268 -0
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +85 -13
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +124 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
- data/lib/datadog/open_feature/exposures/event.rb +15 -8
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +20 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +315 -10
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/open_feature/transport.rb +5 -0
- data/lib/datadog/open_feature.rb +31 -1
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +10 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/profiling/collectors/info.rb +40 -13
- data/lib/datadog/profiling/collectors/thread_context.rb +6 -17
- data/lib/datadog/profiling/component.rb +33 -39
- data/lib/datadog/profiling/exporter.rb +10 -10
- data/lib/datadog/profiling/tasks/exec.rb +1 -1
- data/lib/datadog/profiling.rb +4 -0
- data/lib/datadog/symbol_database/extractor.rb +89 -107
- data/lib/datadog/tracing/component.rb +44 -2
- data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
- data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
- data/lib/datadog/tracing/configuration/ext.rb +4 -0
- data/lib/datadog/tracing/configuration/settings.rb +36 -0
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +17 -5
- data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
- data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
- data/lib/datadog/tracing/contrib/ext.rb +3 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/patchable.rb +1 -1
- data/lib/datadog/tracing/contrib/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
- data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
- data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +50 -63
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +108 -3
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/correlation.rb +2 -2
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -2
- data/lib/datadog/tracing/distributed/baggage.rb +1 -3
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/metadata/tagging.rb +1 -1
- data/lib/datadog/tracing/otel_thread_context.rb +97 -0
- data/lib/datadog/tracing/remote.rb +236 -37
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/span.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +1 -1
- data/lib/datadog/tracing/sync_writer.rb +8 -3
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +28 -7
- data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
- data/lib/datadog/tracing/transport/http/traces.rb +2 -0
- data/lib/datadog/tracing/transport/io/traces.rb +1 -1
- data/lib/datadog/tracing/transport/native/response.rb +71 -0
- data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
- data/lib/datadog/tracing/transport/native.rb +395 -0
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/telemetry.rb +62 -0
- data/lib/datadog/tracing/transport/trace_formatter.rb +14 -4
- data/lib/datadog/tracing/transport/traces.rb +85 -38
- data/lib/datadog/tracing/writer.rb +17 -4
- data/lib/datadog/version.rb +1 -1
- metadata +51 -13
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
- data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "agentless/configuration_source"
|
|
4
|
+
require_relative "component"
|
|
5
|
+
require_relative "configuration/agentless_endpoint"
|
|
6
|
+
require_relative "configuration/source"
|
|
7
|
+
require_relative "remote"
|
|
8
|
+
|
|
9
|
+
module Datadog
|
|
10
|
+
module OpenFeature
|
|
11
|
+
# Owns eager Remote Configuration and lazy agentless delivery activation.
|
|
12
|
+
class Activation
|
|
13
|
+
attr_reader :component, :failure
|
|
14
|
+
|
|
15
|
+
def initialize(settings, agent_settings, remote, logger:, telemetry:)
|
|
16
|
+
@settings = settings
|
|
17
|
+
@agent_settings = agent_settings
|
|
18
|
+
@remote = remote
|
|
19
|
+
@logger = logger
|
|
20
|
+
@telemetry = telemetry
|
|
21
|
+
@component = nil
|
|
22
|
+
@configuration_source = nil
|
|
23
|
+
@delivery_source = nil
|
|
24
|
+
@remote_receivers = nil
|
|
25
|
+
@failure = nil
|
|
26
|
+
@providers = {}.compare_by_identity
|
|
27
|
+
@activated = false
|
|
28
|
+
@delivery_started = false
|
|
29
|
+
@shutdown = false
|
|
30
|
+
@mutex = Mutex.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def activate(provider)
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
return if @shutdown
|
|
36
|
+
return unless open_feature_available?
|
|
37
|
+
|
|
38
|
+
@providers[provider] = true
|
|
39
|
+
return @component if @activated && @delivery_started
|
|
40
|
+
return if @activated
|
|
41
|
+
|
|
42
|
+
resolution = Configuration::Source.resolve(@settings, logger: @logger)
|
|
43
|
+
activate_delivery(resolution)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def startup!
|
|
48
|
+
start!
|
|
49
|
+
OpenFeature.reattach(self)
|
|
50
|
+
rescue => e
|
|
51
|
+
# Feature Flags is optional and must never interrupt library startup.
|
|
52
|
+
report_failure(e, "Feature Flags delivery failed to start")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start!
|
|
56
|
+
@mutex.synchronize do
|
|
57
|
+
return if @shutdown || @activated
|
|
58
|
+
return unless open_feature_available?
|
|
59
|
+
|
|
60
|
+
resolution = Configuration::Source.resolve(@settings, logger: @logger)
|
|
61
|
+
return unless resolution.enabled? && resolution.source == Configuration::Source::REMOTE_CONFIG
|
|
62
|
+
|
|
63
|
+
activate_delivery(resolution)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def activated?
|
|
68
|
+
@mutex.synchronize { @activated }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def providers
|
|
72
|
+
@mutex.synchronize { @providers.keys }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def after_fork
|
|
76
|
+
inherited_providers, component = @mutex.synchronize do
|
|
77
|
+
@shutdown ? [[], nil] : [@providers.keys, @component]
|
|
78
|
+
end
|
|
79
|
+
inherited_providers.each { |provider| provider.send(:after_fork, component) }
|
|
80
|
+
|
|
81
|
+
configuration_source = @mutex.synchronize do
|
|
82
|
+
if @shutdown || !@delivery_started
|
|
83
|
+
nil
|
|
84
|
+
else
|
|
85
|
+
(@delivery_source == Configuration::Source::REMOTE_CONFIG) ? @remote : @configuration_source
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
configuration_source&.start
|
|
90
|
+
nil
|
|
91
|
+
rescue => e
|
|
92
|
+
# Feature Flags is optional and must never interrupt other post-fork handlers.
|
|
93
|
+
report_failure(e, "Feature Flags delivery failed to restart after fork")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def deactivate(provider)
|
|
97
|
+
configuration_source, component = @mutex.synchronize do
|
|
98
|
+
return if @shutdown
|
|
99
|
+
return unless @providers.delete(provider)
|
|
100
|
+
return unless @providers.empty?
|
|
101
|
+
if @delivery_source == Configuration::Source::REMOTE_CONFIG && @delivery_started
|
|
102
|
+
# Remote Configuration is process-scoped and may already hold configuration needed by the next provider.
|
|
103
|
+
[nil, nil]
|
|
104
|
+
else
|
|
105
|
+
previous_source = @configuration_source
|
|
106
|
+
previous_component = @component
|
|
107
|
+
@component = nil
|
|
108
|
+
@configuration_source = nil
|
|
109
|
+
@delivery_source = nil
|
|
110
|
+
@activated = false
|
|
111
|
+
@delivery_started = false
|
|
112
|
+
@failure = nil
|
|
113
|
+
[previous_source, previous_component]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
configuration_source&.stop
|
|
118
|
+
component&.shutdown!
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def shutdown!
|
|
122
|
+
configuration_source, component = @mutex.synchronize do
|
|
123
|
+
return if @shutdown
|
|
124
|
+
|
|
125
|
+
@shutdown = true
|
|
126
|
+
[@configuration_source, @component]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
configuration_source&.stop
|
|
130
|
+
configuration_received = @mutex.synchronize do
|
|
131
|
+
!@providers.empty? && !!component&.configuration_received?
|
|
132
|
+
end
|
|
133
|
+
configuration_changed(Component::CONFIGURATION_LOST) if configuration_received
|
|
134
|
+
component&.shutdown!
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def report_failure(error, description)
|
|
140
|
+
@logger.error("#{description}: #{error.class}: #{error.message}")
|
|
141
|
+
@telemetry.report(error, description: description)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def open_feature_available?
|
|
145
|
+
@settings.respond_to?(:open_feature) && @settings.respond_to?(:feature_flags)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def activate_delivery(resolution)
|
|
149
|
+
@activated = true
|
|
150
|
+
unless resolution.enabled?
|
|
151
|
+
@failure = "Feature Flags are disabled"
|
|
152
|
+
return
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
component = Component.build(
|
|
156
|
+
@settings,
|
|
157
|
+
@agent_settings,
|
|
158
|
+
resolution: resolution,
|
|
159
|
+
on_configuration_change: ->(event) { configuration_changed(event) },
|
|
160
|
+
logger: @logger,
|
|
161
|
+
telemetry: @telemetry,
|
|
162
|
+
)
|
|
163
|
+
unless component
|
|
164
|
+
@failure = "Feature Flags are unavailable on this runtime"
|
|
165
|
+
return
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
@component = component
|
|
169
|
+
@delivery_source = resolution.source
|
|
170
|
+
@delivery_started = start_delivery(resolution.source)
|
|
171
|
+
return component if @delivery_started
|
|
172
|
+
|
|
173
|
+
component.shutdown!
|
|
174
|
+
@component = nil
|
|
175
|
+
@delivery_source = nil
|
|
176
|
+
nil
|
|
177
|
+
rescue
|
|
178
|
+
configuration_source = @configuration_source
|
|
179
|
+
failed_component = @component
|
|
180
|
+
@component = nil
|
|
181
|
+
@configuration_source = nil
|
|
182
|
+
@delivery_source = nil
|
|
183
|
+
@activated = false
|
|
184
|
+
@delivery_started = false
|
|
185
|
+
@failure = nil
|
|
186
|
+
begin
|
|
187
|
+
configuration_source&.stop
|
|
188
|
+
ensure
|
|
189
|
+
failed_component&.shutdown!
|
|
190
|
+
end
|
|
191
|
+
raise
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def start_delivery(source)
|
|
195
|
+
case source
|
|
196
|
+
when Configuration::Source::AGENTLESS
|
|
197
|
+
start_agentless
|
|
198
|
+
when Configuration::Source::REMOTE_CONFIG
|
|
199
|
+
start_remote_configuration
|
|
200
|
+
else
|
|
201
|
+
@failure = "Feature Flags configuration source is unavailable"
|
|
202
|
+
false
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def start_agentless
|
|
207
|
+
component = @component
|
|
208
|
+
return false unless component
|
|
209
|
+
|
|
210
|
+
endpoint = Configuration::AgentlessEndpoint.build(
|
|
211
|
+
site: @settings.site,
|
|
212
|
+
environment: @settings.env,
|
|
213
|
+
base_url: @settings.feature_flags.agentless.base_url,
|
|
214
|
+
logger: @logger,
|
|
215
|
+
)
|
|
216
|
+
unless endpoint
|
|
217
|
+
@failure = "Feature Flags agentless endpoint is unavailable"
|
|
218
|
+
return false
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
configuration_source = Agentless::ConfigurationSource.build(
|
|
222
|
+
@settings,
|
|
223
|
+
endpoint: endpoint,
|
|
224
|
+
apply: ->(configuration) { component.reconfigure!(configuration) },
|
|
225
|
+
logger: @logger,
|
|
226
|
+
)
|
|
227
|
+
unless configuration_source
|
|
228
|
+
@failure = "Feature Flags agentless delivery could not start"
|
|
229
|
+
return false
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
@configuration_source = configuration_source
|
|
233
|
+
started = configuration_source.start
|
|
234
|
+
@failure = "Feature Flags agentless delivery could not start" unless started
|
|
235
|
+
started
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def start_remote_configuration
|
|
239
|
+
remote = @remote
|
|
240
|
+
unless remote
|
|
241
|
+
@failure = "Feature Flags Remote Configuration is unavailable"
|
|
242
|
+
@logger.warn(
|
|
243
|
+
"#{@failure}. To enable Remote Configuration, " \
|
|
244
|
+
"see https://docs.datadoghq.com/remote_configuration/."
|
|
245
|
+
)
|
|
246
|
+
return false
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
receivers = @remote_receivers ||= Remote.receivers(
|
|
250
|
+
@telemetry,
|
|
251
|
+
component_provider: -> { @mutex.synchronize { @component } },
|
|
252
|
+
)
|
|
253
|
+
remote.register(
|
|
254
|
+
capabilities: Remote.capabilities,
|
|
255
|
+
products: Remote.products,
|
|
256
|
+
receivers: receivers,
|
|
257
|
+
)
|
|
258
|
+
remote.start
|
|
259
|
+
true
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def configuration_changed(event)
|
|
263
|
+
providers = @mutex.synchronize { @providers.keys }
|
|
264
|
+
providers.each { |provider| provider.send(:configuration_changed, event) }
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../core/workers/polling"
|
|
4
|
+
require_relative "../evaluation_engine"
|
|
5
|
+
require_relative "parser"
|
|
6
|
+
require_relative "transport"
|
|
7
|
+
|
|
8
|
+
module Datadog
|
|
9
|
+
module OpenFeature
|
|
10
|
+
module Agentless
|
|
11
|
+
# Polls the agentless endpoint and applies accepted configuration.
|
|
12
|
+
class ConfigurationSource
|
|
13
|
+
MAX_ATTEMPTS = 3
|
|
14
|
+
FIRST_RETRY_MIN_SECONDS = 2.0
|
|
15
|
+
FIRST_RETRY_MAX_SECONDS = 10.0
|
|
16
|
+
SECOND_RETRY_MIN_SECONDS = 5.0
|
|
17
|
+
SECOND_RETRY_MAX_SECONDS = 30.0
|
|
18
|
+
RETRY_JITTER = 0.2
|
|
19
|
+
|
|
20
|
+
include Core::Workers::Polling
|
|
21
|
+
|
|
22
|
+
attr_reader :etag
|
|
23
|
+
|
|
24
|
+
def self.build(settings, endpoint:, apply:, logger: Datadog.logger)
|
|
25
|
+
api_key = settings.api_key.to_s.strip
|
|
26
|
+
if endpoint.managed? && api_key.empty?
|
|
27
|
+
logger.warn("Feature Flags agentless delivery requires DD_API_KEY; agentless delivery is disabled")
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
new(
|
|
32
|
+
endpoint: endpoint,
|
|
33
|
+
api_key: api_key.empty? ? nil : api_key,
|
|
34
|
+
poll_interval_seconds: settings.feature_flags.agentless.poll_interval_seconds,
|
|
35
|
+
request_timeout_seconds: settings.feature_flags.agentless.request_timeout_seconds,
|
|
36
|
+
apply: apply,
|
|
37
|
+
logger: logger,
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(
|
|
42
|
+
endpoint:,
|
|
43
|
+
api_key:,
|
|
44
|
+
poll_interval_seconds:,
|
|
45
|
+
request_timeout_seconds:,
|
|
46
|
+
apply:,
|
|
47
|
+
logger:,
|
|
48
|
+
transport: nil,
|
|
49
|
+
random: nil,
|
|
50
|
+
retry_wait: nil
|
|
51
|
+
)
|
|
52
|
+
@transport = transport || Transport.new(
|
|
53
|
+
endpoint: endpoint,
|
|
54
|
+
api_key: api_key,
|
|
55
|
+
timeout_seconds: request_timeout_seconds,
|
|
56
|
+
)
|
|
57
|
+
@apply = apply
|
|
58
|
+
@logger = logger
|
|
59
|
+
@poll_interval_seconds = poll_interval_seconds
|
|
60
|
+
@random = random || -> { Kernel.rand }
|
|
61
|
+
@retry_wait = retry_wait || ->(delay) { wait_for_retry(delay) }
|
|
62
|
+
@etag = nil
|
|
63
|
+
@warned = {}
|
|
64
|
+
@lifecycle_mutex = Mutex.new
|
|
65
|
+
@started = false
|
|
66
|
+
@stopped = false
|
|
67
|
+
|
|
68
|
+
self.enabled = true
|
|
69
|
+
self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
|
|
70
|
+
self.loop_base_interval = poll_interval_seconds
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def start
|
|
74
|
+
@lifecycle_mutex.synchronize do
|
|
75
|
+
return false if @stopped
|
|
76
|
+
return true if @started && !forked?
|
|
77
|
+
|
|
78
|
+
perform
|
|
79
|
+
@started = true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stop(force_stop = true, timeout = Core::Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT)
|
|
86
|
+
@lifecycle_mutex.synchronize do
|
|
87
|
+
return false if @stopped
|
|
88
|
+
|
|
89
|
+
@stopped = true
|
|
90
|
+
self.enabled = false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
super
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def perform
|
|
98
|
+
poll
|
|
99
|
+
# An exception escaping here ends the unsupervised worker thread and freezes configuration.
|
|
100
|
+
rescue => error
|
|
101
|
+
warn_once(:unexpected) do
|
|
102
|
+
@logger.error("Feature Flags agentless poll failed with #{error.class}; polling continues")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def poll
|
|
107
|
+
attempt = 1
|
|
108
|
+
|
|
109
|
+
while attempt <= MAX_ATTEMPTS
|
|
110
|
+
return if stopped?
|
|
111
|
+
|
|
112
|
+
response = @transport.get(@etag)
|
|
113
|
+
unless retryable?(response)
|
|
114
|
+
apply_response(response)
|
|
115
|
+
return
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if attempt == MAX_ATTEMPTS
|
|
119
|
+
warn_failure(response, attempt)
|
|
120
|
+
return
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
return if wait_before_retry(retry_delay(attempt))
|
|
124
|
+
|
|
125
|
+
attempt += 1
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
protected
|
|
130
|
+
|
|
131
|
+
def after_fork
|
|
132
|
+
# The child must report its own failures, while the inherited ETag still matches its configuration.
|
|
133
|
+
@warned = {}
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def stopped?
|
|
140
|
+
@lifecycle_mutex.synchronize { @stopped }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def retryable?(response)
|
|
144
|
+
status = response.status
|
|
145
|
+
return true unless status
|
|
146
|
+
|
|
147
|
+
retryable_status?(status)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def retryable_status?(status)
|
|
151
|
+
status == 408 || status == 429 || status.between?(500, 599)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def retry_delay(attempt)
|
|
155
|
+
base = if attempt == 1
|
|
156
|
+
clamp(@poll_interval_seconds / 6.0, FIRST_RETRY_MIN_SECONDS, FIRST_RETRY_MAX_SECONDS)
|
|
157
|
+
else
|
|
158
|
+
clamp(@poll_interval_seconds / 3.0, SECOND_RETRY_MIN_SECONDS, SECOND_RETRY_MAX_SECONDS)
|
|
159
|
+
end
|
|
160
|
+
random = @random.call
|
|
161
|
+
jitter = 1 - RETRY_JITTER + (random * RETRY_JITTER * 2)
|
|
162
|
+
[1.0, base * jitter].max
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def clamp(value, minimum, maximum)
|
|
166
|
+
value.clamp(minimum, maximum)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def wait_before_retry(delay)
|
|
170
|
+
@retry_wait.call(delay)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def wait_for_retry(delay)
|
|
174
|
+
mutex.synchronize do
|
|
175
|
+
return true if stopped?
|
|
176
|
+
|
|
177
|
+
shutdown.wait(mutex, delay)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
stopped? || !run_loop?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def apply_response(response)
|
|
184
|
+
case response.status
|
|
185
|
+
when 200
|
|
186
|
+
apply_configuration(response)
|
|
187
|
+
when 304
|
|
188
|
+
nil
|
|
189
|
+
when 401, 403
|
|
190
|
+
warn_once(:authentication) do
|
|
191
|
+
@logger.warn(
|
|
192
|
+
"Feature Flags agentless endpoint returned HTTP #{response.status}; verify endpoint authentication"
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
else
|
|
196
|
+
warn_failure(response, 1)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def apply_configuration(response)
|
|
201
|
+
configuration = response.body && Parser.parse(response.body)
|
|
202
|
+
unless configuration
|
|
203
|
+
warn_once(:malformed) do
|
|
204
|
+
@logger.error("Feature Flags agentless endpoint returned malformed UFC payload")
|
|
205
|
+
end
|
|
206
|
+
return
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
@apply.call(configuration)
|
|
210
|
+
etag = response.etag.to_s.strip
|
|
211
|
+
@etag = etag.empty? ? nil : etag
|
|
212
|
+
rescue EvaluationEngine::ReconfigurationError => error
|
|
213
|
+
warn_once(:application) do
|
|
214
|
+
@logger.warn("Feature Flags agentless UFC payload could not be applied: #{error.class}: #{error.message}")
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def warn_failure(response, attempts)
|
|
219
|
+
status = response.status
|
|
220
|
+
category = if status.nil?
|
|
221
|
+
:request
|
|
222
|
+
elsif retryable_status?(status)
|
|
223
|
+
:http_retryable
|
|
224
|
+
else
|
|
225
|
+
:http_unexpected
|
|
226
|
+
end
|
|
227
|
+
warn_once(category) do
|
|
228
|
+
if status
|
|
229
|
+
@logger.warn("Feature Flags agentless endpoint returned HTTP #{status} after #{attempts} attempt(s)")
|
|
230
|
+
else
|
|
231
|
+
error_class = response.error ? response.error.class : StandardError
|
|
232
|
+
@logger.warn("Feature Flags agentless request failed after #{attempts} attempt(s): #{error_class}")
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def warn_once(category)
|
|
238
|
+
return if @warned[category]
|
|
239
|
+
|
|
240
|
+
@warned[category] = true
|
|
241
|
+
yield
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module Agentless
|
|
8
|
+
# Parses the JSON:API envelope returned by agentless delivery.
|
|
9
|
+
module Parser
|
|
10
|
+
RESOURCE_TYPE = "universal-flag-configuration"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def parse(body)
|
|
15
|
+
payload = JSON.parse(body)
|
|
16
|
+
return unless payload.is_a?(Hash)
|
|
17
|
+
|
|
18
|
+
data = payload["data"]
|
|
19
|
+
return unless data.is_a?(Hash) && data["type"] == RESOURCE_TYPE
|
|
20
|
+
|
|
21
|
+
attributes = data["attributes"]
|
|
22
|
+
return unless valid_attributes?(attributes)
|
|
23
|
+
|
|
24
|
+
JSON.generate(attributes)
|
|
25
|
+
rescue JSON::ParserError, JSON::GeneratorError, TypeError
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def valid_attributes?(attributes)
|
|
30
|
+
return false unless attributes.is_a?(Hash)
|
|
31
|
+
|
|
32
|
+
environment = attributes["environment"]
|
|
33
|
+
attributes["format"].is_a?(String) &&
|
|
34
|
+
attributes["createdAt"].is_a?(String) &&
|
|
35
|
+
environment.is_a?(Hash) &&
|
|
36
|
+
environment["name"].is_a?(String) &&
|
|
37
|
+
attributes["flags"].is_a?(Hash)
|
|
38
|
+
end
|
|
39
|
+
private_class_method :valid_attributes?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module OpenFeature
|
|
5
|
+
module Agentless
|
|
6
|
+
# Result of one agentless configuration request.
|
|
7
|
+
class Response
|
|
8
|
+
attr_reader :status, :etag, :body, :error
|
|
9
|
+
|
|
10
|
+
def initialize(status: nil, etag: nil, body: nil, error: nil)
|
|
11
|
+
@status = status
|
|
12
|
+
@etag = etag
|
|
13
|
+
@body = body
|
|
14
|
+
@error = error
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "stringio"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "uri"
|
|
7
|
+
require "zlib"
|
|
8
|
+
|
|
9
|
+
require_relative "../../core/environment/identity"
|
|
10
|
+
require_relative "../../core/transport/ext"
|
|
11
|
+
require_relative "response"
|
|
12
|
+
|
|
13
|
+
module Datadog
|
|
14
|
+
module OpenFeature
|
|
15
|
+
module Agentless
|
|
16
|
+
# HTTP transport for agentless configuration delivery.
|
|
17
|
+
class Transport
|
|
18
|
+
def initialize(endpoint:, api_key:, timeout_seconds:)
|
|
19
|
+
@endpoint = endpoint
|
|
20
|
+
@api_key = endpoint.managed? ? api_key : nil
|
|
21
|
+
@timeout_seconds = timeout_seconds
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get(etag)
|
|
25
|
+
request = Net::HTTP::Get.new(@endpoint.uri.request_uri, headers(etag))
|
|
26
|
+
apply_custom_authentication(request)
|
|
27
|
+
response = request(request)
|
|
28
|
+
status = response.code.to_i
|
|
29
|
+
|
|
30
|
+
Response.new(
|
|
31
|
+
status: status,
|
|
32
|
+
etag: response["ETag"],
|
|
33
|
+
# Decoding another status can erase the status the poller needs to classify the response.
|
|
34
|
+
body: (status == 200) ? response_body(response) : nil,
|
|
35
|
+
)
|
|
36
|
+
# Net::HTTP uses several version-specific exception types. None may end
|
|
37
|
+
# the delivery worker; the poller classifies every transport failure.
|
|
38
|
+
rescue => error
|
|
39
|
+
Response.new(error: error)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def apply_custom_authentication(request)
|
|
45
|
+
return if @endpoint.managed?
|
|
46
|
+
|
|
47
|
+
uri = @endpoint.uri
|
|
48
|
+
username = uri.user
|
|
49
|
+
return unless username
|
|
50
|
+
|
|
51
|
+
request.basic_auth(
|
|
52
|
+
URI::RFC2396_PARSER.unescape(username),
|
|
53
|
+
URI::RFC2396_PARSER.unescape(uri.password.to_s),
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def headers(etag)
|
|
58
|
+
headers = {
|
|
59
|
+
"Accept-Encoding" => "gzip",
|
|
60
|
+
"DD-Client-Library-Language" => Core::Environment::Identity.lang,
|
|
61
|
+
"DD-Client-Library-Version" => Core::Environment::Identity.gem_datadog_version_semver2,
|
|
62
|
+
Core::Transport::Ext::HTTP::HEADER_DD_INTERNAL_UNTRACED_REQUEST => "1",
|
|
63
|
+
}
|
|
64
|
+
api_key = @api_key
|
|
65
|
+
headers[Core::Transport::Ext::HTTP::HEADER_DD_API_KEY] = api_key if api_key
|
|
66
|
+
headers["If-None-Match"] = etag if etag
|
|
67
|
+
headers
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def request(request)
|
|
71
|
+
uri = @endpoint.uri
|
|
72
|
+
hostname = uri.hostname
|
|
73
|
+
raise ArgumentError, "Feature Flags agentless endpoint must have a host" unless hostname
|
|
74
|
+
|
|
75
|
+
# Agentless delivery requires public egress, so use Ruby's standard proxy discovery.
|
|
76
|
+
http = Net::HTTP.new(hostname, uri.port)
|
|
77
|
+
http.max_retries = 0
|
|
78
|
+
http.use_ssl = uri.scheme == "https"
|
|
79
|
+
http.open_timeout = @timeout_seconds
|
|
80
|
+
http.read_timeout = @timeout_seconds
|
|
81
|
+
http.write_timeout = @timeout_seconds if http.respond_to?(:write_timeout=)
|
|
82
|
+
|
|
83
|
+
# Bounds total request time, including DNS resolution, which the per-phase timeouts do not cover.
|
|
84
|
+
Timeout.timeout(@timeout_seconds) do
|
|
85
|
+
http.start { |connection| connection.request(request) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def response_body(response)
|
|
90
|
+
body = response.body.to_s
|
|
91
|
+
return body unless response["Content-Encoding"].to_s.strip.casecmp("gzip") == 0
|
|
92
|
+
|
|
93
|
+
reader = Zlib::GzipReader.new(StringIO.new(body))
|
|
94
|
+
begin
|
|
95
|
+
reader.read.to_s
|
|
96
|
+
ensure
|
|
97
|
+
reader.close
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|