ddtrace 0.40.0 → 0.45.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.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +148 -130
  3. data/.circleci/images/primary/Dockerfile-3.0.0 +73 -0
  4. data/.github/workflows/add-milestone-to-pull-requests.yml +42 -0
  5. data/.github/workflows/create-next-milestone.yml +20 -0
  6. data/.simplecov +3 -0
  7. data/Appraisals +414 -135
  8. data/CHANGELOG.md +1112 -342
  9. data/CONTRIBUTING.md +2 -2
  10. data/Gemfile +4 -2
  11. data/README.md +1 -0
  12. data/Rakefile +231 -29
  13. data/ddtrace.gemspec +8 -8
  14. data/docker-compose.yml +30 -0
  15. data/docs/DevelopmentGuide.md +12 -2
  16. data/docs/GettingStarted.md +187 -16
  17. data/lib/ddtrace.rb +10 -0
  18. data/lib/ddtrace/auto_instrument.rb +3 -0
  19. data/lib/ddtrace/auto_instrument_base.rb +6 -0
  20. data/lib/ddtrace/buffer.rb +259 -52
  21. data/lib/ddtrace/configuration.rb +19 -0
  22. data/lib/ddtrace/configuration/options.rb +3 -1
  23. data/lib/ddtrace/configuration/settings.rb +9 -3
  24. data/lib/ddtrace/context.rb +18 -0
  25. data/lib/ddtrace/context_provider.rb +17 -5
  26. data/lib/ddtrace/contrib/action_cable/integration.rb +7 -0
  27. data/lib/ddtrace/contrib/action_pack/integration.rb +7 -0
  28. data/lib/ddtrace/contrib/action_view/event.rb +0 -4
  29. data/lib/ddtrace/contrib/action_view/events/render_partial.rb +1 -0
  30. data/lib/ddtrace/contrib/action_view/events/render_template.rb +1 -0
  31. data/lib/ddtrace/contrib/action_view/integration.rb +7 -0
  32. data/lib/ddtrace/contrib/active_record/events/sql.rb +4 -0
  33. data/lib/ddtrace/contrib/active_record/integration.rb +7 -0
  34. data/lib/ddtrace/contrib/active_record/utils.rb +67 -21
  35. data/lib/ddtrace/contrib/active_support/cache/instrumentation.rb +104 -3
  36. data/lib/ddtrace/contrib/active_support/cache/patcher.rb +21 -0
  37. data/lib/ddtrace/contrib/active_support/ext.rb +3 -0
  38. data/lib/ddtrace/contrib/active_support/integration.rb +7 -1
  39. data/lib/ddtrace/contrib/active_support/notifications/event.rb +10 -0
  40. data/lib/ddtrace/contrib/active_support/notifications/subscription.rb +2 -2
  41. data/lib/ddtrace/contrib/auto_instrument.rb +48 -0
  42. data/lib/ddtrace/contrib/aws/instrumentation.rb +6 -1
  43. data/lib/ddtrace/contrib/aws/patcher.rb +0 -1
  44. data/lib/ddtrace/contrib/aws/services.rb +1 -0
  45. data/lib/ddtrace/contrib/configurable.rb +2 -0
  46. data/lib/ddtrace/contrib/configuration/resolvers/pattern_resolver.rb +6 -5
  47. data/lib/ddtrace/contrib/cucumber/configuration/settings.rb +38 -0
  48. data/lib/ddtrace/contrib/cucumber/ext.rb +19 -0
  49. data/lib/ddtrace/contrib/cucumber/formatter.rb +104 -0
  50. data/lib/ddtrace/contrib/cucumber/instrumentation.rb +24 -0
  51. data/lib/ddtrace/contrib/cucumber/integration.rb +45 -0
  52. data/lib/ddtrace/contrib/cucumber/patcher.rb +23 -0
  53. data/lib/ddtrace/contrib/dalli/instrumentation.rb +4 -0
  54. data/lib/ddtrace/contrib/delayed_job/configuration/settings.rb +2 -0
  55. data/lib/ddtrace/contrib/delayed_job/ext.rb +2 -0
  56. data/lib/ddtrace/contrib/delayed_job/plugin.rb +39 -15
  57. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +4 -0
  58. data/lib/ddtrace/contrib/ethon/easy_patch.rb +10 -7
  59. data/lib/ddtrace/contrib/ethon/ext.rb +1 -0
  60. data/lib/ddtrace/contrib/ethon/multi_patch.rb +4 -0
  61. data/lib/ddtrace/contrib/excon/middleware.rb +11 -1
  62. data/lib/ddtrace/contrib/extensions.rb +27 -1
  63. data/lib/ddtrace/contrib/faraday/middleware.rb +4 -0
  64. data/lib/ddtrace/contrib/faraday/patcher.rb +1 -1
  65. data/lib/ddtrace/contrib/grape/configuration/settings.rb +7 -0
  66. data/lib/ddtrace/contrib/grape/endpoint.rb +53 -18
  67. data/lib/ddtrace/contrib/grape/ext.rb +1 -0
  68. data/lib/ddtrace/contrib/grpc/datadog_interceptor/client.rb +5 -1
  69. data/lib/ddtrace/contrib/grpc/datadog_interceptor/server.rb +4 -0
  70. data/lib/ddtrace/contrib/http/instrumentation.rb +6 -2
  71. data/lib/ddtrace/contrib/httpclient/configuration/settings.rb +32 -0
  72. data/lib/ddtrace/contrib/httpclient/ext.rb +17 -0
  73. data/lib/ddtrace/contrib/httpclient/instrumentation.rb +152 -0
  74. data/lib/ddtrace/contrib/httpclient/integration.rb +43 -0
  75. data/lib/ddtrace/contrib/httpclient/patcher.rb +35 -0
  76. data/lib/ddtrace/contrib/httprb/instrumentation.rb +6 -3
  77. data/lib/ddtrace/contrib/kafka/event.rb +1 -1
  78. data/lib/ddtrace/contrib/mongodb/subscribers.rb +4 -0
  79. data/lib/ddtrace/contrib/mysql2/instrumentation.rb +4 -0
  80. data/lib/ddtrace/contrib/patchable.rb +18 -7
  81. data/lib/ddtrace/contrib/presto/instrumentation.rb +3 -0
  82. data/lib/ddtrace/contrib/qless/configuration/settings.rb +35 -0
  83. data/lib/ddtrace/contrib/qless/ext.rb +20 -0
  84. data/lib/ddtrace/contrib/qless/integration.rb +38 -0
  85. data/lib/ddtrace/contrib/qless/patcher.rb +35 -0
  86. data/lib/ddtrace/contrib/qless/qless_job.rb +72 -0
  87. data/lib/ddtrace/contrib/qless/tracer_cleaner.rb +32 -0
  88. data/lib/ddtrace/contrib/que/configuration/settings.rb +1 -0
  89. data/lib/ddtrace/contrib/que/tracer.rb +2 -1
  90. data/lib/ddtrace/contrib/racecar/event.rb +4 -0
  91. data/lib/ddtrace/contrib/rack/integration.rb +7 -0
  92. data/lib/ddtrace/contrib/rack/middlewares.rb +1 -1
  93. data/lib/ddtrace/contrib/rack/request_queue.rb +6 -1
  94. data/lib/ddtrace/contrib/rails/auto_instrument_railtie.rb +10 -0
  95. data/lib/ddtrace/contrib/rails/patcher.rb +19 -5
  96. data/lib/ddtrace/contrib/rails/utils.rb +4 -0
  97. data/lib/ddtrace/contrib/rake/integration.rb +1 -1
  98. data/lib/ddtrace/contrib/redis/configuration/resolver.rb +3 -1
  99. data/lib/ddtrace/contrib/redis/configuration/settings.rb +5 -0
  100. data/lib/ddtrace/contrib/redis/ext.rb +1 -0
  101. data/lib/ddtrace/contrib/redis/patcher.rb +20 -3
  102. data/lib/ddtrace/contrib/redis/quantize.rb +27 -0
  103. data/lib/ddtrace/contrib/redis/tags.rb +9 -1
  104. data/lib/ddtrace/contrib/resque/configuration/settings.rb +1 -0
  105. data/lib/ddtrace/contrib/resque/integration.rb +1 -1
  106. data/lib/ddtrace/contrib/resque/resque_job.rb +1 -1
  107. data/lib/ddtrace/contrib/rest_client/request_patch.rb +4 -0
  108. data/lib/ddtrace/contrib/rspec/configuration/settings.rb +38 -0
  109. data/lib/ddtrace/contrib/rspec/example.rb +61 -0
  110. data/lib/ddtrace/contrib/rspec/example_group.rb +61 -0
  111. data/lib/ddtrace/contrib/rspec/ext.rb +19 -0
  112. data/lib/ddtrace/contrib/rspec/integration.rb +46 -0
  113. data/lib/ddtrace/contrib/rspec/patcher.rb +25 -0
  114. data/lib/ddtrace/contrib/sequel/database.rb +3 -1
  115. data/lib/ddtrace/contrib/sequel/dataset.rb +3 -2
  116. data/lib/ddtrace/contrib/sequel/ext.rb +1 -0
  117. data/lib/ddtrace/contrib/sequel/utils.rb +16 -5
  118. data/lib/ddtrace/contrib/shoryuken/configuration/settings.rb +1 -0
  119. data/lib/ddtrace/contrib/shoryuken/tracer.rb +4 -1
  120. data/lib/ddtrace/contrib/sidekiq/configuration/settings.rb +1 -0
  121. data/lib/ddtrace/contrib/sidekiq/server_tracer.rb +4 -1
  122. data/lib/ddtrace/contrib/sinatra/tracer_middleware.rb +2 -2
  123. data/lib/ddtrace/contrib/sneakers/configuration/settings.rb +1 -0
  124. data/lib/ddtrace/contrib/sneakers/tracer.rb +17 -20
  125. data/lib/ddtrace/contrib/status_code_matcher.rb +67 -0
  126. data/lib/ddtrace/ext/app_types.rb +1 -0
  127. data/lib/ddtrace/ext/ci.rb +297 -0
  128. data/lib/ddtrace/ext/distributed.rb +8 -2
  129. data/lib/ddtrace/ext/git.rb +11 -0
  130. data/lib/ddtrace/ext/integration.rb +8 -0
  131. data/lib/ddtrace/ext/runtime.rb +2 -0
  132. data/lib/ddtrace/ext/test.rb +24 -0
  133. data/lib/ddtrace/opentracer/distributed_headers.rb +1 -1
  134. data/lib/ddtrace/propagation/grpc_propagator.rb +18 -6
  135. data/lib/ddtrace/propagation/http_propagator.rb +17 -2
  136. data/lib/ddtrace/runtime/identity.rb +4 -5
  137. data/lib/ddtrace/runtime/metrics.rb +6 -2
  138. data/lib/ddtrace/sampler.rb +2 -2
  139. data/lib/ddtrace/sampling/rate_limiter.rb +65 -16
  140. data/lib/ddtrace/span.rb +152 -27
  141. data/lib/ddtrace/tracer.rb +25 -13
  142. data/lib/ddtrace/transport/http/adapters/net.rb +8 -2
  143. data/lib/ddtrace/transport/http/statistics.rb +14 -1
  144. data/lib/ddtrace/transport/traces.rb +7 -2
  145. data/lib/ddtrace/utils.rb +16 -13
  146. data/lib/ddtrace/utils/forking.rb +52 -0
  147. data/lib/ddtrace/version.rb +1 -1
  148. data/lib/ddtrace/workers/runtime_metrics.rb +7 -3
  149. data/lib/ddtrace/writer.rb +19 -1
  150. metadata +111 -19
@@ -24,7 +24,7 @@ module Datadog
24
24
  span.service = datadog_pin.service
25
25
  span.resource = opts[:query]
26
26
  span.span_type = Datadog::Ext::SQL::TYPE
27
- Utils.set_analytics_sample_rate(span)
27
+ Utils.set_common_tags(span)
28
28
  span.set_tag(Ext::TAG_DB_VENDOR, adapter_name)
29
29
  response = super(sql, options)
30
30
  end
@@ -52,6 +52,8 @@ module Datadog
52
52
  elsif instance_variable_defined?(:@pool) && @pool
53
53
  @pool.db.opts
54
54
  end
55
+ sql = sql.is_a?(::Sequel::SQL::Expression) ? literal(sql) : sql.to_s
56
+
55
57
  Utils.parse_opts(sql, opts, db_opts)
56
58
  end
57
59
  end
@@ -38,15 +38,16 @@ module Datadog
38
38
  private
39
39
 
40
40
  def trace_execute(super_method, sql, options, &block)
41
- opts = Utils.parse_opts(sql, options, db.opts)
41
+ opts = Utils.parse_opts(sql, options, db.opts, self)
42
42
  response = nil
43
43
 
44
44
  datadog_pin.tracer.trace(Ext::SPAN_QUERY) do |span|
45
45
  span.service = datadog_pin.service
46
46
  span.resource = opts[:query]
47
47
  span.span_type = Datadog::Ext::SQL::TYPE
48
- Utils.set_analytics_sample_rate(span)
48
+ Utils.set_common_tags(span)
49
49
  span.set_tag(Ext::TAG_DB_VENDOR, adapter_name)
50
+ span.set_tag(Ext::TAG_PREPARED_NAME, opts[:prepared_name]) if opts[:prepared_name]
50
51
  response = super_method.call(sql, options, &block)
51
52
  end
52
53
  response
@@ -12,6 +12,7 @@ module Datadog
12
12
  SERVICE_NAME = 'sequel'.freeze
13
13
  SPAN_QUERY = 'sequel.query'.freeze
14
14
  TAG_DB_VENDOR = 'sequel.db.vendor'.freeze
15
+ TAG_PREPARED_NAME = 'sequel.prepared.name'.freeze
15
16
  end
16
17
  end
17
18
  end
@@ -1,3 +1,5 @@
1
+ require 'ddtrace/ext/integration'
2
+
1
3
  module Datadog
2
4
  module Contrib
3
5
  module Sequel
@@ -26,21 +28,30 @@ module Datadog
26
28
  Datadog::Utils::Database.normalize_vendor(database.database_type.to_s)
27
29
  end
28
30
 
29
- def parse_opts(sql, opts, db_opts)
30
- if ::Sequel::VERSION >= '4.37.0' && !sql.is_a?(String)
31
- # In 4.37.0, sql was converted to a prepared statement object
32
- sql = sql.prepared_sql unless sql.is_a?(Symbol)
31
+ def parse_opts(sql, opts, db_opts, dataset = nil)
32
+ # Prepared statements don't provide their sql query in the +sql+ parameter.
33
+ unless sql.is_a?(String)
34
+ if dataset && dataset.respond_to?(:prepared_sql) && (resolved_sql = dataset.prepared_sql)
35
+ # The dataset contains the resolved SQL query and prepared statement name.
36
+ prepared_name = dataset.prepared_statement_name
37
+ sql = resolved_sql
38
+ end
33
39
  end
34
40
 
35
41
  {
36
42
  name: opts[:type],
37
43
  query: sql,
44
+ prepared_name: prepared_name,
38
45
  database: db_opts[:database],
39
46
  host: db_opts[:host]
40
47
  }
41
48
  end
42
49
 
43
- def set_analytics_sample_rate(span)
50
+ def set_common_tags(span)
51
+ # Tag as an external peer service
52
+ span.set_tag(Datadog::Ext::Integration::TAG_PEER_SERVICE, span.service)
53
+
54
+ # Set analytics sample rate
44
55
  Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
45
56
  end
46
57
 
@@ -22,6 +22,7 @@ module Datadog
22
22
  end
23
23
 
24
24
  option :service_name, default: Ext::SERVICE_NAME
25
+ option :error_handler, default: Datadog::Tracer::DEFAULT_ON_ERROR
25
26
  end
26
27
  end
27
28
  end
@@ -8,10 +8,13 @@ module Datadog
8
8
  def initialize(options = {})
9
9
  @tracer = options[:tracer] || configuration[:tracer]
10
10
  @shoryuken_service = options[:service_name] || configuration[:service_name]
11
+ @error_handler = options[:error_handler] || configuration[:error_handler]
11
12
  end
12
13
 
13
14
  def call(worker_instance, queue, sqs_msg, body)
14
- @tracer.trace(Ext::SPAN_JOB, service: @shoryuken_service, span_type: Datadog::Ext::AppTypes::WORKER) do |span|
15
+ @tracer.trace(Ext::SPAN_JOB, service: @shoryuken_service, span_type: Datadog::Ext::AppTypes::WORKER,
16
+ on_error: @error_handler) do |span|
17
+
15
18
  # Set analytics sample rate
16
19
  if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
17
20
  Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
@@ -29,6 +29,7 @@ module Datadog
29
29
 
30
30
  option :service_name, default: Ext::SERVICE_NAME
31
31
  option :client_service_name, default: Ext::CLIENT_SERVICE_NAME
32
+ option :error_handler, default: Datadog::Tracer::DEFAULT_ON_ERROR
32
33
  end
33
34
  end
34
35
  end
@@ -11,6 +11,7 @@ module Datadog
11
11
  def initialize(options = {})
12
12
  super
13
13
  @sidekiq_service = options[:service_name] || configuration[:service_name]
14
+ @error_handler = options[:error_handler] || configuration[:error_handler]
14
15
  end
15
16
 
16
17
  def call(worker, job, queue)
@@ -19,7 +20,9 @@ module Datadog
19
20
  service = worker_config(resource, :service_name) || @sidekiq_service
20
21
  tag_args = worker_config(resource, :tag_args) || configuration[:tag_args]
21
22
 
22
- @tracer.trace(Ext::SPAN_JOB, service: service, span_type: Datadog::Ext::AppTypes::WORKER) do |span|
23
+ @tracer.trace(Ext::SPAN_JOB, service: service, span_type: Datadog::Ext::AppTypes::WORKER,
24
+ on_error: @error_handler) do |span|
25
+
23
26
  span.resource = resource
24
27
  # Set analytics sample rate
25
28
  if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
@@ -8,9 +8,9 @@ module Datadog
8
8
  module Sinatra
9
9
  # Middleware used for automatically tagging configured headers and handle request span
10
10
  class TracerMiddleware
11
- def initialize(app, app_instance: nil)
11
+ def initialize(app, opt = {})
12
12
  @app = app
13
- @app_instance = app_instance
13
+ @app_instance = opt[:app_instance]
14
14
  end
15
15
 
16
16
  # rubocop:disable Metrics/AbcSize
@@ -24,6 +24,7 @@ module Datadog
24
24
  end
25
25
 
26
26
  option :service_name, default: Ext::SERVICE_NAME
27
+ option :error_handler, default: Datadog::Tracer::DEFAULT_ON_ERROR
27
28
  option :tag_body, default: false
28
29
  end
29
30
  end
@@ -15,32 +15,29 @@ module Datadog
15
15
  def call(deserialized_msg, delivery_info, metadata, handler)
16
16
  trace_options = {
17
17
  service: configuration[:service_name],
18
- span_type: Datadog::Ext::AppTypes::WORKER
18
+ span_type: Datadog::Ext::AppTypes::WORKER,
19
+ on_error: configuration[:error_handler]
19
20
  }
20
- request_span = tracer.trace(Ext::SPAN_JOB, trace_options)
21
21
 
22
- # Set analytics sample rate
23
- if Datadog::Contrib::Analytics.enabled?(configuration[:analytics_enabled])
24
- Datadog::Contrib::Analytics.set_sample_rate(request_span, configuration[:analytics_sample_rate])
25
- end
22
+ tracer.trace(Ext::SPAN_JOB, trace_options) do |request_span|
23
+ # Set analytics sample rate
24
+ if Datadog::Contrib::Analytics.enabled?(configuration[:analytics_enabled])
25
+ Datadog::Contrib::Analytics.set_sample_rate(request_span, configuration[:analytics_sample_rate])
26
+ end
26
27
 
27
- # Measure service stats
28
- Contrib::Analytics.set_measured(request_span)
28
+ # Measure service stats
29
+ Contrib::Analytics.set_measured(request_span)
29
30
 
30
- request_span.resource = @app.to_proc.binding.eval('self.class').to_s
31
- request_span.set_tag(Ext::TAG_JOB_ROUTING_KEY, delivery_info.routing_key)
32
- request_span.set_tag(Ext::TAG_JOB_QUEUE, delivery_info.consumer.queue.name)
31
+ request_span.resource = @app.to_proc.binding.eval('self.class').to_s
32
+ request_span.set_tag(Ext::TAG_JOB_ROUTING_KEY, delivery_info.routing_key)
33
+ request_span.set_tag(Ext::TAG_JOB_QUEUE, delivery_info.consumer.queue.name)
33
34
 
34
- if configuration[:tag_body]
35
- request_span.set_tag(Ext::TAG_JOB_BODY, deserialized_msg)
36
- end
35
+ if configuration[:tag_body]
36
+ request_span.set_tag(Ext::TAG_JOB_BODY, deserialized_msg)
37
+ end
37
38
 
38
- @app.call(deserialized_msg, delivery_info, metadata, handler)
39
- rescue StandardError => e
40
- request_span.set_error(e) unless request_span.nil?
41
- raise e
42
- ensure
43
- request_span.finish if request_span
39
+ @app.call(deserialized_msg, delivery_info, metadata, handler)
40
+ end
44
41
  end
45
42
 
46
43
  private
@@ -0,0 +1,67 @@
1
+ require 'set'
2
+ require 'ddtrace/ext/http'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ # Contains methods helpful for tracing/annotating HTTP request libraries
7
+ class StatusCodeMatcher
8
+ REGEX_PARSER = /^\d{3}(?:-\d{3})?(?:,\d{3}(?:-\d{3})?)*$/
9
+
10
+ def initialize(range)
11
+ @error_response_range = range
12
+ set_range
13
+ end
14
+
15
+ def include?(exception_status)
16
+ set_range.include?(exception_status)
17
+ end
18
+
19
+ def to_s
20
+ @error_response_range.to_s
21
+ end
22
+
23
+ private
24
+
25
+ def set_range
26
+ @datadog_set ||= begin
27
+ set = Set.new
28
+ handle_statuses.each do |statuses|
29
+ status = statuses.to_s.split('-')
30
+ if status.length == 1
31
+ set.add(Integer(status[0]))
32
+ elsif status.length == 2
33
+ min, max = status.minmax
34
+ Array(min..max).each do |i|
35
+ set.add(Integer(i))
36
+ end
37
+ end
38
+ end
39
+ set
40
+ end
41
+ @datadog_set
42
+ end
43
+
44
+ def error_responses
45
+ return @error_response_range if @error_response_range.is_a?(String) && !@error_response_range.nil?
46
+ @error_response_range.join(',') if @error_response_range.is_a?(Array) && !@error_response_range.empty?
47
+ end
48
+
49
+ def handle_statuses
50
+ if error_responses
51
+ filter_error_responses = error_responses.gsub(/\s+/, '').split(',').select do |code|
52
+ if !code.to_s.match(REGEX_PARSER)
53
+ Datadog.logger.debug("Invalid config provided: #{code}. Must be formatted like '400-403,405,410-499'.")
54
+ next
55
+ else
56
+ true
57
+ end
58
+ end
59
+ filter_error_responses.empty? ? Datadog::Ext::HTTP::ERROR_RANGE.to_a : filter_error_responses
60
+ else
61
+ Datadog.logger.debug('No valid config was provided for :error_statuses - falling back to default.')
62
+ Datadog::Ext::HTTP::ERROR_RANGE.to_a
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -6,6 +6,7 @@ module Datadog
6
6
  CACHE = 'cache'.freeze
7
7
  WORKER = 'worker'.freeze
8
8
  CUSTOM = 'custom'.freeze
9
+ TEST = 'test'.freeze
9
10
  end
10
11
  end
11
12
  end
@@ -0,0 +1,297 @@
1
+ require 'ddtrace/ext/git'
2
+
3
+ module Datadog
4
+ module Ext
5
+ # Defines constants for CI tags
6
+ # rubocop:disable Metrics/ModuleLength:
7
+ module CI
8
+ TAG_STAGE_NAME = 'ci.stage.name'.freeze
9
+ TAG_JOB_NAME = 'ci.job.name'.freeze
10
+ TAG_JOB_URL = 'ci.job.url'.freeze
11
+ TAG_PIPELINE_ID = 'ci.pipeline.id'.freeze
12
+ TAG_PIPELINE_NAME = 'ci.pipeline.name'.freeze
13
+ TAG_PIPELINE_NUMBER = 'ci.pipeline.number'.freeze
14
+ TAG_PIPELINE_URL = 'ci.pipeline.url'.freeze
15
+ TAG_PROVIDER_NAME = 'ci.provider.name'.freeze
16
+ TAG_WORKSPACE_PATH = 'ci.workspace_path'.freeze
17
+
18
+ PROVIDERS = [
19
+ ['APPVEYOR'.freeze, :extract_appveyor],
20
+ ['TF_BUILD'.freeze, :extract_azure_pipelines],
21
+ ['BITBUCKET_COMMIT'.freeze, :extract_bitbucket],
22
+ ['BUILDKITE'.freeze, :extract_buildkite],
23
+ ['CIRCLECI'.freeze, :extract_circle_ci],
24
+ ['GITHUB_SHA'.freeze, :extract_github_actions],
25
+ ['GITLAB_CI'.freeze, :extract_gitlab],
26
+ ['JENKINS_URL'.freeze, :extract_jenkins],
27
+ ['TEAMCITY_VERSION'.freeze, :extract_teamcity],
28
+ ['TRAVIS'.freeze, :extract_travis],
29
+ ['BITRISE_BUILD_SLUG'.freeze, :extract_bitrise]
30
+ ].freeze
31
+
32
+ module_function
33
+
34
+ def tags(env)
35
+ provider = PROVIDERS.find { |c| env.key? c[0] }
36
+ return {} if provider.nil?
37
+
38
+ tags = send(provider[1], env)
39
+
40
+ tags[Git::TAG_TAG] = normalize_ref(tags[Git::TAG_TAG])
41
+ tags.delete(Git::TAG_BRANCH) unless tags[Git::TAG_TAG].nil?
42
+ tags[Git::TAG_BRANCH] = normalize_ref(tags[Git::TAG_BRANCH])
43
+ tags[Git::TAG_REPOSITORY_URL] = filter_sensitive_info(tags[Git::TAG_REPOSITORY_URL])
44
+
45
+ # Expand ~
46
+ workspace_path = tags[TAG_WORKSPACE_PATH]
47
+ if !workspace_path.nil? && (workspace_path == '~' || workspace_path.start_with?('~/'))
48
+ tags[TAG_WORKSPACE_PATH] = File.expand_path(workspace_path)
49
+ end
50
+ tags.reject { |_, v| v.nil? }
51
+ end
52
+
53
+ def normalize_ref(name)
54
+ refs = %r{^refs/(heads/)?}
55
+ origin = %r{^origin/}
56
+ tags = %r{^tags/}
57
+ name.gsub(refs, '').gsub(origin, '').gsub(tags, '') unless name.nil?
58
+ end
59
+
60
+ def filter_sensitive_info(url)
61
+ url.gsub(%r{(https?://)[^/]*@}, '\1') unless url.nil?
62
+ end
63
+
64
+ # CI providers
65
+
66
+ def extract_appveyor(env)
67
+ url = "https://ci.appveyor.com/project/#{env['APPVEYOR_REPO_NAME']}/builds/#{env['APPVEYOR_BUILD_ID']}"
68
+
69
+ if env['APPVEYOR_REPO_PROVIDER'] == 'github'
70
+ repository = "https://github.com/#{env['APPVEYOR_REPO_NAME']}.git"
71
+ commit = env['APPVEYOR_REPO_COMMIT']
72
+ branch = (env['APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH'] || env['APPVEYOR_REPO_BRANCH'])
73
+ tag = env['APPVEYOR_REPO_TAG_NAME']
74
+ end
75
+
76
+ {
77
+ TAG_PROVIDER_NAME => 'appveyor',
78
+ Git::TAG_REPOSITORY_URL => repository,
79
+ Git::TAG_COMMIT_SHA => commit,
80
+ TAG_WORKSPACE_PATH => env['APPVEYOR_BUILD_FOLDER'],
81
+ TAG_PIPELINE_ID => env['APPVEYOR_BUILD_ID'],
82
+ TAG_PIPELINE_NAME => env['APPVEYOR_REPO_NAME'],
83
+ TAG_PIPELINE_NUMBER => env['APPVEYOR_BUILD_NUMBER'],
84
+ TAG_PIPELINE_URL => url,
85
+ TAG_JOB_URL => url,
86
+ Git::TAG_BRANCH => branch,
87
+ Git::TAG_TAG => tag
88
+ }
89
+ end
90
+
91
+ def extract_azure_pipelines(env)
92
+ if env['SYSTEM_TEAMFOUNDATIONSERVERURI'] && env['SYSTEM_TEAMPROJECTID'] && env['BUILD_BUILDID']
93
+ base_url = "#{env['SYSTEM_TEAMFOUNDATIONSERVERURI']}#{env['SYSTEM_TEAMPROJECTID']}" \
94
+ "/_build/results?buildId=#{env['BUILD_BUILDID']}"
95
+ pipeline_url = base_url
96
+ job_url = base_url + "&view=logs&j=#{env['SYSTEM_JOBID']}&t=#{env['SYSTEM_TASKINSTANCEID']}"
97
+ else
98
+ pipeline_url = job_url = nil
99
+ end
100
+ branch_or_tag = (
101
+ env['SYSTEM_PULLREQUEST_SOURCEBRANCH'] || env['BUILD_SOURCEBRANCH'] || env['BUILD_SOURCEBRANCHNAME']
102
+ )
103
+ if branch_or_tag.include? 'tags/'
104
+ branch = nil
105
+ tag = branch_or_tag
106
+ else
107
+ branch = branch_or_tag
108
+ tag = nil
109
+ end
110
+ {
111
+ TAG_PROVIDER_NAME => 'azurepipelines',
112
+ TAG_WORKSPACE_PATH => env['BUILD_SOURCESDIRECTORY'],
113
+ TAG_PIPELINE_ID => env['BUILD_BUILDID'],
114
+ TAG_PIPELINE_NAME => env['BUILD_DEFINITIONNAME'],
115
+ TAG_PIPELINE_NUMBER => env['BUILD_BUILDID'],
116
+ TAG_PIPELINE_URL => pipeline_url,
117
+ TAG_JOB_URL => job_url,
118
+ Git::TAG_REPOSITORY_URL => (env['SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI'] || env['BUILD_REPOSITORY_URI']),
119
+ Git::TAG_COMMIT_SHA => (env['SYSTEM_PULLREQUEST_SOURCECOMMITID'] || env['BUILD_SOURCEVERSION']),
120
+ Git::TAG_BRANCH => branch,
121
+ Git::TAG_TAG => tag
122
+ }
123
+ end
124
+
125
+ def extract_bitbucket(env)
126
+ url = "https://bitbucket.org/#{env['BITBUCKET_REPO_FULL_NAME']}/addon/pipelines/home#" \
127
+ "!/results/#{env['BITBUCKET_BUILD_NUMBER']}"
128
+ {
129
+ Git::TAG_BRANCH => env['BITBUCKET_BRANCH'],
130
+ Git::TAG_COMMIT_SHA => env['BITBUCKET_COMMIT'],
131
+ Git::TAG_REPOSITORY_URL => env['BITBUCKET_GIT_SSH_ORIGIN'],
132
+ Git::TAG_TAG => env['BITBUCKET_TAG'],
133
+ TAG_JOB_URL => url,
134
+ TAG_PIPELINE_ID => env['BITBUCKET_PIPELINE_UUID'] ? env['BITBUCKET_PIPELINE_UUID'].tr('{}', '') : None,
135
+ TAG_PIPELINE_NAME => env['BITBUCKET_REPO_FULL_NAME'],
136
+ TAG_PIPELINE_NUMBER => env['BITBUCKET_BUILD_NUMBER'],
137
+ TAG_PIPELINE_URL => url,
138
+ TAG_PROVIDER_NAME => 'bitbucket',
139
+ TAG_WORKSPACE_PATH => env['BITBUCKET_CLONE_DIR']
140
+ }
141
+ end
142
+
143
+ def extract_buildkite(env)
144
+ {
145
+ Git::TAG_BRANCH => env['BUILDKITE_BRANCH'],
146
+ Git::TAG_COMMIT_SHA => env['BUILDKITE_COMMIT'],
147
+ Git::TAG_REPOSITORY_URL => env['BUILDKITE_REPO'],
148
+ Git::TAG_TAG => env['BUILDKITE_TAG'],
149
+ TAG_PIPELINE_ID => env['BUILDKITE_BUILD_ID'],
150
+ TAG_PIPELINE_NAME => env['BUILDKITE_PIPELINE_SLUG'],
151
+ TAG_PIPELINE_NUMBER => env['BUILDKITE_BUILD_NUMBER'],
152
+ TAG_PIPELINE_URL => env['BUILDKITE_BUILD_URL'],
153
+ TAG_JOB_URL => "#{env['BUILDKITE_BUILD_URL']}##{env['BUILDKITE_JOB_ID']}",
154
+ TAG_PROVIDER_NAME => 'buildkite',
155
+ TAG_WORKSPACE_PATH => env['BUILDKITE_BUILD_CHECKOUT_PATH']
156
+ }
157
+ end
158
+
159
+ def extract_circle_ci(env)
160
+ {
161
+ Git::TAG_BRANCH => env['CIRCLE_BRANCH'],
162
+ Git::TAG_COMMIT_SHA => env['CIRCLE_SHA1'],
163
+ Git::TAG_REPOSITORY_URL => env['CIRCLE_REPOSITORY_URL'],
164
+ Git::TAG_TAG => env['CIRCLE_TAG'],
165
+ TAG_PIPELINE_ID => env['CIRCLE_WORKFLOW_ID'],
166
+ TAG_PIPELINE_NAME => env['CIRCLE_PROJECT_REPONAME'],
167
+ TAG_PIPELINE_NUMBER => env['CIRCLE_BUILD_NUM'],
168
+ TAG_PIPELINE_URL => env['CIRCLE_BUILD_URL'],
169
+ TAG_JOB_URL => env['CIRCLE_BUILD_URL'],
170
+ TAG_PROVIDER_NAME => 'circleci',
171
+ TAG_WORKSPACE_PATH => env['CIRCLE_WORKING_DIRECTORY']
172
+ }
173
+ end
174
+
175
+ def extract_github_actions(env)
176
+ branch_or_tag = (env['GITHUB_HEAD_REF'] || env['GITHUB_REF'])
177
+ if branch_or_tag.include? 'tags/'
178
+ branch = nil
179
+ tag = branch_or_tag
180
+ else
181
+ branch = branch_or_tag
182
+ tag = nil
183
+ end
184
+ {
185
+ Git::TAG_BRANCH => branch,
186
+ Git::TAG_COMMIT_SHA => env['GITHUB_SHA'],
187
+ Git::TAG_REPOSITORY_URL => "https://github.com/#{env['GITHUB_REPOSITORY']}.git",
188
+ Git::TAG_TAG => tag,
189
+ TAG_JOB_URL => "https://github.com/#{env['GITHUB_REPOSITORY']}/commit/#{env['GITHUB_SHA']}/checks",
190
+ TAG_PIPELINE_ID => env['GITHUB_RUN_ID'],
191
+ TAG_PIPELINE_NAME => env['GITHUB_WORKFLOW'],
192
+ TAG_PIPELINE_NUMBER => env['GITHUB_RUN_NUMBER'],
193
+ TAG_PIPELINE_URL => "https://github.com/#{env['GITHUB_REPOSITORY']}/commit/#{env['GITHUB_SHA']}/checks",
194
+ TAG_PROVIDER_NAME => 'github',
195
+ TAG_WORKSPACE_PATH => env['GITHUB_WORKSPACE']
196
+ }
197
+ end
198
+
199
+ def extract_gitlab(env)
200
+ url = env['CI_PIPELINE_URL']
201
+ url = url.gsub(%r{/-/pipelines/}, '/pipelines/') unless url.nil?
202
+ {
203
+ Git::TAG_BRANCH => env['CI_COMMIT_BRANCH'],
204
+ Git::TAG_COMMIT_SHA => env['CI_COMMIT_SHA'],
205
+ Git::TAG_REPOSITORY_URL => env['CI_REPOSITORY_URL'],
206
+ Git::TAG_TAG => env['CI_COMMIT_TAG'],
207
+ TAG_STAGE_NAME => env['CI_JOB_STAGE'],
208
+ TAG_JOB_NAME => env['CI_JOB_NAME'],
209
+ TAG_JOB_URL => env['CI_JOB_URL'],
210
+ TAG_PIPELINE_ID => env['CI_PIPELINE_ID'],
211
+ TAG_PIPELINE_NAME => env['CI_PROJECT_PATH'],
212
+ TAG_PIPELINE_NUMBER => env['CI_PIPELINE_IID'],
213
+ TAG_PIPELINE_URL => url,
214
+ TAG_PROVIDER_NAME => 'gitlab',
215
+ TAG_WORKSPACE_PATH => env['CI_PROJECT_DIR']
216
+ }
217
+ end
218
+
219
+ def extract_jenkins(env)
220
+ branch_or_tag = env['GIT_BRANCH']
221
+ if branch_or_tag.include? 'tags/'
222
+ branch = nil
223
+ tag = branch_or_tag
224
+ else
225
+ branch = branch_or_tag
226
+ tag = nil
227
+ end
228
+ name = env['JOB_NAME']
229
+ name = name.gsub("/#{normalize_ref(branch)}", '') unless name.nil? || branch.nil?
230
+ name = name.split('/').reject { |v| v.nil? || v.include?('=') }.join('/') unless name.nil?
231
+ {
232
+ Git::TAG_BRANCH => branch,
233
+ Git::TAG_COMMIT_SHA => env['GIT_COMMIT'],
234
+ Git::TAG_REPOSITORY_URL => env['GIT_URL'],
235
+ Git::TAG_TAG => tag,
236
+ TAG_PIPELINE_ID => env['BUILD_TAG'],
237
+ TAG_PIPELINE_NAME => name,
238
+ TAG_PIPELINE_NUMBER => env['BUILD_NUMBER'],
239
+ TAG_PIPELINE_URL => env['BUILD_URL'],
240
+ TAG_PROVIDER_NAME => 'jenkins',
241
+ TAG_WORKSPACE_PATH => env['WORKSPACE']
242
+ }
243
+ end
244
+
245
+ def extract_teamcity(env)
246
+ {
247
+ TAG_PROVIDER_NAME => 'teamcity',
248
+ Git::TAG_REPOSITORY_URL => env['BUILD_VCS_URL'],
249
+ Git::TAG_COMMIT_SHA => env['BUILD_VCS_NUMBER'],
250
+ TAG_WORKSPACE_PATH => env['BUILD_CHECKOUTDIR'],
251
+ TAG_PIPELINE_ID => env['BUILD_ID'],
252
+ TAG_PIPELINE_NUMBER => env['BUILD_NUMBER'],
253
+ TAG_PIPELINE_URL => (
254
+ env['SERVER_URL'] && env['BUILD_ID'] ? "#{env['SERVER_URL']}/viewLog.html?buildId=#{env['SERVER_URL']}" : nil
255
+ )
256
+ }
257
+ end
258
+
259
+ def extract_travis(env)
260
+ {
261
+ Git::TAG_BRANCH => (env['TRAVIS_PULL_REQUEST_BRANCH'] || env['TRAVIS_BRANCH']),
262
+ Git::TAG_COMMIT_SHA => env['TRAVIS_COMMIT'],
263
+ Git::TAG_REPOSITORY_URL => "https://github.com/#{env['TRAVIS_REPO_SLUG']}.git",
264
+ Git::TAG_TAG => env['TRAVIS_TAG'],
265
+ TAG_JOB_URL => env['TRAVIS_JOB_WEB_URL'],
266
+ TAG_PIPELINE_ID => env['TRAVIS_BUILD_ID'],
267
+ TAG_PIPELINE_NAME => env['TRAVIS_REPO_SLUG'],
268
+ TAG_PIPELINE_NUMBER => env['TRAVIS_BUILD_NUMBER'],
269
+ TAG_PIPELINE_URL => env['TRAVIS_BUILD_WEB_URL'],
270
+ TAG_PROVIDER_NAME => 'travisci',
271
+ TAG_WORKSPACE_PATH => env['TRAVIS_BUILD_DIR']
272
+ }
273
+ end
274
+
275
+ def extract_bitrise(env)
276
+ commit = (
277
+ env['BITRISE_GIT_COMMIT'] || env['GIT_CLONE_COMMIT_HASH']
278
+ )
279
+ branch = (
280
+ env['BITRISEIO_GIT_BRANCH_DEST'] || env['BITRISE_GIT_BRANCH']
281
+ )
282
+ {
283
+ TAG_PROVIDER_NAME => 'bitrise',
284
+ TAG_PIPELINE_ID => env['BITRISE_BUILD_SLUG'],
285
+ TAG_PIPELINE_NAME => env['BITRISE_APP_TITLE'],
286
+ TAG_PIPELINE_NUMBER => env['BITRISE_BUILD_NUMBER'],
287
+ TAG_PIPELINE_URL => env['BITRISE_BUILD_URL'],
288
+ TAG_WORKSPACE_PATH => env['BITRISE_SOURCE_DIR'],
289
+ Git::TAG_REPOSITORY_URL => env['GIT_REPOSITORY_URL'],
290
+ Git::TAG_COMMIT_SHA => commit,
291
+ Git::TAG_BRANCH => branch,
292
+ Git::TAG_TAG => env['BITRISE_GIT_TAG']
293
+ }
294
+ end
295
+ end
296
+ end
297
+ end