Skip to content

Commit

Permalink
migrate patchable and configurable logic for integration from tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
anmarchenko committed Sep 18, 2023
1 parent 4831625 commit e7a1218
Show file tree
Hide file tree
Showing 24 changed files with 278 additions and 106 deletions.
19 changes: 12 additions & 7 deletions lib/datadog/ci/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,24 @@ def self.add_settings!(base)
return unless enabled

integration = fetch_integration(integration_name)
return unless integration.class.compatible?
integration.configure(options, &block)

return unless integration.default_configuration.enabled
integration.configure(:default, options, &block)
return unless integration.configuration.enabled

return if integration.patcher.patched?
integration.patcher.patch
patch_results = integration.patch
next if patch_results == true

error_message = <<-ERROR
Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]},
Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}"
ERROR
Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})")
end

define_method(:[]) do |integration_name, key = :default|
define_method(:[]) do |integration_name|
integration = fetch_integration(integration_name)

integration.resolve(key) unless integration.nil?
integration.configuration unless integration.nil?
end

# TODO: Deprecate in the next major version, as `instrument` better describes this method's purpose
Expand Down
5 changes: 2 additions & 3 deletions lib/datadog/ci/contrib/cucumber/configuration/settings.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require_relative "../ext"

require "datadog/tracing/contrib/configuration/settings"
require_relative "../../settings"

module Datadog
module CI
Expand All @@ -11,7 +10,7 @@ module Cucumber
module Configuration
# Custom settings for the Cucumber integration
# TODO: mark as `@public_api` when GA
class Settings < Datadog::Tracing::Contrib::Configuration::Settings
class Settings < Datadog::CI::Contrib::Settings
option :enabled do |o|
o.type :bool
o.env Ext::ENV_ENABLED
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/cucumber/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Integration

MINIMUM_VERSION = Gem::Version.new("3.0.0")

register_as :cucumber, auto_patch: true
register_as :cucumber

def self.version
Gem.loaded_specs["cucumber"] \
Expand Down
133 changes: 121 additions & 12 deletions lib/datadog/ci/contrib/integration.rb
Original file line number Diff line number Diff line change
@@ -1,36 +1,145 @@
# frozen_string_literal: true

require "datadog/tracing/contrib/configurable"
require "datadog/tracing/contrib/patchable"
require_relative "settings"

module Datadog
module CI
module Contrib
module Integration
@registry = {}

RegisteredIntegration = Struct.new(:name, :integration, :options)
RegisteredIntegration = Struct.new(:name, :integration)

def self.included(base)
base.extend(ClassMethods)
base.include(InstanceMethods)
end

def self.register(klass, name)
registry[name] = RegisteredIntegration.new(name, klass.new)
end

base.include(Datadog::Tracing::Contrib::Patchable)
base.include(Datadog::Tracing::Contrib::Configurable)
def self.registry
@registry
end

# Class-level methods for Integration
module ClassMethods
def register_as(name, options = {})
Integration.register(self, name, options)
def register_as(name)
Integration.register(self, name)
end

# Version of the integration target code in the environment.
#
# This is the gem version, when the instrumentation target is a Ruby gem.
#
# If the target for instrumentation has concept of versioning, override {.version},
# otherwise override {.available?} and implement a custom target presence check.
# @return [Object] the target version
def version
nil
end
end

def self.register(klass, name, options)
registry[name] = RegisteredIntegration.new(name, klass.new, options)
# Is the target available to be instrumented? (e.g. gem installed?)
#
# The target doesn't have to be loaded (e.g. `require`) yet, but needs to be able
# to be loaded before instrumentation can commence.
#
# By default, {.available?} checks if {.version} returned a non-nil object.
#
# If the target for instrumentation has concept of versioning, override {.version},
# otherwise override {.available?} and implement a custom target presence check.
# @return [Boolean] is the target available for instrumentation in this Ruby environment?
def available?
!version.nil?
end

# Is the target loaded into the application? (e.g. gem required? Constant defined?)
#
# The target's objects should be ready to be referenced by the instrumented when {.loaded}
# returns `true`.
#
# @return [Boolean] is the target ready to be referenced during instrumentation?
def loaded?
true
end

# Is this instrumentation compatible with the available target? (e.g. minimum version met?)
# @return [Boolean] is the available target compatible with this instrumentation?
def compatible?
available?
end

# Can the patch for this integration be applied?
#
# By default, this is equivalent to {#available?}, {#loaded?}, and {#compatible?}
# all being truthy.
def patchable?
available? && loaded? && compatible?
end
end

def self.registry
@registry
module InstanceMethods
# returns the configuration instance.
def configuration
@configuration ||= new_configuration
end

def configure(options = {}, &block)
configuration.configure(options, &block)
configuration
end

# Resets all configuration options
def reset_configuration!
@configuration = nil
end

# The patcher module to inject instrumented objects into the instrumentation target.
#
# {Contrib::Patcher} includes the basic functionality of a patcher. `include`ing
# {Contrib::Patcher} into a new module is the recommend way to create a custom patcher.
#
# @return [Contrib::Patcher] a module that `include`s {Contrib::Patcher}
def patcher
nil
end

# @!visibility private
def patch
# @type var patcher_klass: untyped
patcher_klass = patcher
if !self.class.patchable? || patcher_klass.nil?
return {
available: self.class.available?,
loaded: self.class.loaded?,
compatible: self.class.compatible?,
patchable: self.class.patchable?
}
end

patcher_klass.patch
true
end

# Can the patch for this integration be applied automatically?
# @return [Boolean] can the tracer activate this instrumentation without explicit user input?
def auto_instrument?
true
end

protected

# Returns a new configuration object for this integration.
#
# This method normally needs to be overridden for each integration
# as their settings, defaults and environment variables are
# specific for each integration.
#
# @return [Datadog::CI::Contrib::Settings] a new, integration-specific settings object
def new_configuration
Datadog::CI::Contrib::Settings.new
end
end
end
end
Expand Down
5 changes: 2 additions & 3 deletions lib/datadog/ci/contrib/minitest/configuration/settings.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require_relative "../ext"

require "datadog/tracing/contrib/configuration/settings"
require_relative "../../settings"

module Datadog
module CI
Expand All @@ -11,7 +10,7 @@ module Minitest
module Configuration
# Custom settings for the Minitest integration
# TODO: mark as `@public_api` when GA
class Settings < Datadog::Tracing::Contrib::Configuration::Settings
class Settings < Datadog::CI::Contrib::Settings
option :enabled do |o|
o.type :bool
o.env Ext::ENV_ENABLED
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/minitest/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Integration

MINIMUM_VERSION = Gem::Version.new("5.0.0")

register_as :minitest, auto_patch: true
register_as :minitest

def self.version
Gem.loaded_specs["minitest"] && Gem.loaded_specs["minitest"].version
Expand Down
5 changes: 2 additions & 3 deletions lib/datadog/ci/contrib/rspec/configuration/settings.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require_relative "../ext"

require "datadog/tracing/contrib/configuration/settings"
require_relative "../../settings"

module Datadog
module CI
Expand All @@ -11,7 +10,7 @@ module RSpec
module Configuration
# Custom settings for the RSpec integration
# TODO: mark as `@public_api` when GA
class Settings < Datadog::Tracing::Contrib::Configuration::Settings
class Settings < Datadog::CI::Contrib::Settings
option :enabled do |o|
o.type :bool
o.env Ext::ENV_ENABLED
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/rspec/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Integration

MINIMUM_VERSION = Gem::Version.new("3.0.0")

register_as :rspec, auto_patch: true
register_as :rspec

def self.version
Gem.loaded_specs["rspec-core"] \
Expand Down
33 changes: 33 additions & 0 deletions lib/datadog/ci/contrib/settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require "datadog/core/configuration/base"

module Datadog
module CI
module Contrib
# Common settings for all integrations
# @public_api
class Settings
include Core::Configuration::Base

option :enabled, default: true
option :service_name
option :operation_name

def configure(options = {})
self.class.options.each do |name, _value|
self[name] = options[name] if options.key?(name)
end

yield(self) if block_given?
end

def [](name)
respond_to?(name) ? send(name) : get_option(name)
end

def []=(name, value)
respond_to?("#{name}=") ? send("#{name}=", value) : set_option(name, value)
end
end
end
end
end
2 changes: 2 additions & 0 deletions sig/datadog/ci/configuration/settings.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Datadog
module Configuration
module Settings
extend Datadog::Core::Configuration::Options::ClassMethods
include Datadog::Core::Configuration::Options::InstanceMethods

extend Datadog::Core::Configuration::Base::ClassMethods

def self.extended: (untyped base) -> untyped
Expand Down
2 changes: 1 addition & 1 deletion sig/datadog/ci/contrib/cucumber/configuration/settings.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Datadog
module Contrib
module Cucumber
module Configuration
class Settings < Datadog::Tracing::Contrib::Configuration::Settings
class Settings < Datadog::CI::Contrib::Settings
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion sig/datadog/ci/contrib/cucumber/integration.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module Datadog
module Contrib
module Cucumber
class Integration
extend Datadog::Tracing::Contrib::Integration
extend Datadog::CI::Contrib::Integration::ClassMethods
include Datadog::CI::Contrib::Integration::InstanceMethods

MINIMUM_VERSION: Gem::Version

Expand Down
30 changes: 27 additions & 3 deletions sig/datadog/ci/contrib/integration.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,39 @@ module Datadog
self.@registry: Hash[Symbol, Struct[untyped]]

def self.included: (Module base) -> void

def self.register: (untyped integration, Symbol name) -> void

def self.registry: () -> Hash[Symbol, Struct[untyped]]

module ClassMethods
def register_as: (Symbol name, ?::Hash[Symbol, untyped] options) -> void
def register_as: (Symbol name) -> void

def version: () -> Gem::Version?

def available?: () -> bool

def loaded?: () -> bool

def compatible?: () -> bool

def patchable?: () -> bool
end

def self.register: (Object integration, Symbol name, ::Hash[Symbol, untyped] options) -> void
module InstanceMethods
extend ClassMethods
@configuration: Datadog::CI::Contrib::Settings?

def self.registry: () -> Hash[Symbol, Struct[untyped]]
def configuration: () -> Datadog::CI::Contrib::Settings

def configure: (?::Hash[Symbol, untyped] options) ?{ (Datadog::CI::Contrib::Settings) -> Datadog::CI::Contrib::Settings } -> Datadog::CI::Contrib::Settings

def reset_configuration!: () -> void

def patcher: () -> Datadog::Tracing::Contrib::Patcher?

def new_configuration: () -> Datadog::CI::Contrib::Settings
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion sig/datadog/ci/contrib/minitest/configuration/settings.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Datadog
module Contrib
module Minitest
module Configuration
class Settings < Datadog::Tracing::Contrib::Configuration::Settings
class Settings < Datadog::CI::Contrib::Settings
end
end
end
Expand Down
Loading

0 comments on commit e7a1218

Please sign in to comment.