elastic-apm 3.7.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/Jenkinsfile +139 -96
  3. data/.ci/packer_cache.sh +12 -10
  4. data/.rspec +0 -1
  5. data/CHANGELOG.asciidoc +80 -0
  6. data/Gemfile +4 -3
  7. data/bin/run-tests +4 -1
  8. data/docker-compose.yml +2 -0
  9. data/docs/configuration.asciidoc +38 -0
  10. data/docs/debugging.asciidoc +14 -0
  11. data/docs/supported-technologies.asciidoc +2 -1
  12. data/lib/elastic_apm/config.rb +36 -13
  13. data/lib/elastic_apm/config/options.rb +2 -1
  14. data/lib/elastic_apm/config/round_float.rb +31 -0
  15. data/lib/elastic_apm/config/wildcard_pattern_list.rb +13 -1
  16. data/lib/elastic_apm/context_builder.rb +1 -1
  17. data/lib/elastic_apm/grpc.rb +2 -2
  18. data/lib/elastic_apm/instrumenter.rb +10 -3
  19. data/lib/elastic_apm/metadata.rb +3 -1
  20. data/lib/elastic_apm/metadata/cloud_info.rb +128 -0
  21. data/lib/elastic_apm/metadata/service_info.rb +5 -2
  22. data/lib/elastic_apm/metadata/system_info.rb +5 -3
  23. data/lib/elastic_apm/metadata/system_info/container_info.rb +28 -4
  24. data/lib/elastic_apm/middleware.rb +8 -2
  25. data/lib/elastic_apm/opentracing.rb +47 -23
  26. data/lib/elastic_apm/span.rb +7 -3
  27. data/lib/elastic_apm/spies.rb +16 -14
  28. data/lib/elastic_apm/spies/delayed_job.rb +4 -2
  29. data/lib/elastic_apm/spies/dynamo_db.rb +58 -0
  30. data/lib/elastic_apm/spies/elasticsearch.rb +26 -2
  31. data/lib/elastic_apm/spies/mongo.rb +1 -1
  32. data/lib/elastic_apm/spies/net_http.rb +6 -2
  33. data/lib/elastic_apm/spies/sequel.rb +1 -1
  34. data/lib/elastic_apm/trace_context.rb +1 -1
  35. data/lib/elastic_apm/trace_context/traceparent.rb +2 -4
  36. data/lib/elastic_apm/trace_context/tracestate.rb +112 -9
  37. data/lib/elastic_apm/transaction.rb +26 -5
  38. data/lib/elastic_apm/transport/connection.rb +1 -0
  39. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +70 -0
  40. data/lib/elastic_apm/transport/filters/secrets_filter.rb +14 -56
  41. data/lib/elastic_apm/transport/serializers.rb +8 -6
  42. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +56 -23
  43. data/lib/elastic_apm/transport/serializers/span_serializer.rb +2 -1
  44. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +1 -0
  45. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  46. data/lib/elastic_apm/transport/worker.rb +5 -0
  47. data/lib/elastic_apm/util.rb +2 -0
  48. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  49. data/lib/elastic_apm/version.rb +1 -1
  50. metadata +12 -8
  51. data/.ci/downstreamTests.groovy +0 -192
@@ -8,7 +8,10 @@ runRspec(){
8
8
  if [ -n "${case}" ]; then
9
9
  bn="$(basename ${case} _spec.rb)/"
10
10
  fi
11
- bundle exec rspec -f progress -f JUnit -o spec/junit-reports/${bn}ruby-agent-junit.xml ${case}
11
+ bundle exec rspec \
12
+ -f progress \
13
+ -r yarjuf -f JUnit -o spec/junit-reports/${bn}ruby-agent-junit.xml \
14
+ ${case}
12
15
  }
13
16
  specific_spec=$1
14
17
 
@@ -26,11 +26,13 @@ services:
26
26
  - /tmp:exec,mode=1777
27
27
  depends_on:
28
28
  - mongodb
29
+ user: ${USER_ID}
29
30
 
30
31
  ruby_rspec:
31
32
  image: apm-agent-ruby:${RUBY_VERSION}
32
33
  environment:
33
34
  MONGODB_URL: 'mongodb:27017'
35
+ user: ${USER_ID}
34
36
 
35
37
  volumes:
36
38
  vendor:
@@ -287,6 +287,16 @@ If your service handles data like this, we advise to only enable this feature wi
287
287
 
288
288
  Whether or not to attach the request headers to transactions and errors.
289
289
 
290
+ [float]
291
+ [[config-capture-elasticsearch-queries]]
292
+ ==== `capture_elasticsearch_queries`
293
+ |============
294
+ | Environment | `Config` key | Default
295
+ | `ELASTIC_APM_CAPTURE_ELASTICSEARCH_QUERIES` | `capture_elasticsearch_queries` | `false`
296
+ |============
297
+
298
+ Whether or not to capture the body from requests in Elasticsearch.
299
+
290
300
  [float]
291
301
  [[config-capture-env]]
292
302
  ==== `capture_env`
@@ -707,6 +717,23 @@ NOTE: The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]
707
717
  In layman's terms: Your service name must only contain characters from the ASCII
708
718
  alphabet, numbers, dashes, underscores and spaces.
709
719
 
720
+ [float]
721
+ [[config-service-node-name]]
722
+ ==== `service_node_name`
723
+
724
+ [options="header"]
725
+ |============
726
+ | Environment | `Config` key | Default | Example
727
+ | `ELASTIC_APM_SERVICE_NODE_NAME` | `service_node_name` | `nil` | `"my-app-1"`
728
+ |============
729
+
730
+ The name of the given service node. This is optional, and if omitted, the APM
731
+ Server will fall back on `system.container.id` if available, and finally
732
+ `host.name` if necessary.
733
+
734
+ This option allows you to set the node name manually to ensure uniqueness and
735
+ meaningfulness.
736
+
710
737
  [float]
711
738
  [[config-service-version]]
712
739
  ==== `service_version`
@@ -826,6 +853,7 @@ To reduce overhead and storage requirements, you can set the sample rate to a va
826
853
  between `0.0` and `1.0`.
827
854
  We still record overall time and the result for unsampled transactions, but no
828
855
  context information, tags, or spans.
856
+ Note that the sample rate will be rounded to 4 digits of precision.
829
857
 
830
858
  [float]
831
859
  [[config-use-experimental-sql-parser]]
@@ -890,3 +918,13 @@ The unit is provided as suffix directly after the number, without any separation
890
918
  * `gb` (gigabytes)
891
919
 
892
920
  NOTE: we use the power-of-two sizing convention, e.g. `1 kilobyte == 1024 bytes`
921
+
922
+ [float]
923
+ [[special-configuration]]
924
+ === Special configuration
925
+
926
+ Elastic APM patches `Kernel#require` to auto-detect and instrument supported third party libraries. It does so with the utmost care but in rare cases it can conflict with some libraries.
927
+
928
+ To get around this patch, set the environment variable `ELASTIC_APM_SKIP_REQUIRE_PATCH` to `"1"`.
929
+
930
+ If you choose to do so, the agent might need some additional tweaking to make sure the third party libraries are picked up and instrumented. Make sure you require the agent _after_ you require your other dependencies.
@@ -28,3 +28,17 @@ Things to consider:
28
28
  - Experiencing high load? The agent can spawn multiple instances of its Workers that pick off the queue by changing the option `pool_size` (default is `1`).
29
29
  - If you have high load you may also consider setting `transaction_sample_rate` to something smaller than `1.0`. This determines whether to include _spans_ for every _transaction_. If you have enough traffic, skipping some (probably) identical spans won't have a noticeable effect on your data.
30
30
 
31
+ [float]
32
+ [[disable-agent]]
33
+ === Disable the Agent
34
+
35
+ In the unlikely event the agent causes disruptions to a production application,
36
+ you can disable the agent while you troubleshoot.
37
+
38
+ If you have access to <<dynamic-configuration,dynamic configuration>>,
39
+ you can disable the recording of events by setting <<config-recording,`recording`>> to `false`.
40
+ When changed at runtime from a supported source, there's no need to restart your application.
41
+
42
+ If that doesn't work, or you don't have access to dynamic configuration, you can disable the agent by setting
43
+ <<config-enabled,`enabled`>> to `false`.
44
+ You'll need to restart your application for the changes to apply.
@@ -60,6 +60,7 @@ See <<getting-started-grape>>.
60
60
  We automatically instrument database actions using:
61
61
 
62
62
  - ActiveRecord (v4.2+)
63
+ - DynamoDB (v1.0+)
63
64
  - Elasticsearch (v0.9+)
64
65
  - Mongo (v2.1+)
65
66
  - Redis (v3.1+)
@@ -151,4 +152,4 @@ To instrument a server, add the `ElasticAPM::GRPC::ServerInterceptor`.
151
152
  [source,ruby]
152
153
  ----
153
154
  GRPC::RpcServer.new(interceptors: [ElasticAPM::GRPC::ServerInterceptor.new])
154
- ----
155
+ ----
@@ -17,9 +17,10 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'elastic_apm/config/options'
21
- require 'elastic_apm/config/duration'
22
20
  require 'elastic_apm/config/bytes'
21
+ require 'elastic_apm/config/duration'
22
+ require 'elastic_apm/config/options'
23
+ require 'elastic_apm/config/round_float'
23
24
  require 'elastic_apm/config/regexp_list'
24
25
  require 'elastic_apm/config/wildcard_pattern_list'
25
26
 
@@ -30,6 +31,11 @@ module ElasticAPM
30
31
 
31
32
  DEPRECATED_OPTIONS = %i[].freeze
32
33
 
34
+ # DEPRECATED: To align with other agents, change on next major bump to:
35
+ # "password, passwd, pwd, secret, *key, *token*, *session*, *credit*, *card*, authorization, set-cookie"
36
+ SANITIZE_FIELD_NAMES_DEFAULT =
37
+ %w[*password* *passwd* *pwd* *secret* *key* *token* *session* *credit* *card* *authorization* *set-cookie*]
38
+
33
39
  # rubocop:disable Metrics/LineLength, Layout/ExtraSpacing
34
40
  option :config_file, type: :string, default: 'config/elastic_apm.yml'
35
41
  option :server_url, type: :url, default: 'http://localhost:8200'
@@ -42,8 +48,10 @@ module ElasticAPM
42
48
  option :breakdown_metrics, type: :bool, default: true
43
49
  option :capture_body, type: :string, default: 'off'
44
50
  option :capture_headers, type: :bool, default: true
51
+ option :capture_elasticsearch_queries, type: :bool, default: false
45
52
  option :capture_env, type: :bool, default: true
46
53
  option :central_config, type: :bool, default: true
54
+ option :cloud_provider, type: :string, default: 'auto'
47
55
  option :current_user_email_method, type: :string, default: 'email'
48
56
  option :current_user_id_method, type: :string, default: 'id'
49
57
  option :current_user_username_method, type: :string, default: 'username'
@@ -75,9 +83,11 @@ module ElasticAPM
75
83
  option :proxy_port, type: :int
76
84
  option :proxy_username, type: :string
77
85
  option :recording, type: :bool, default: true
78
- option :sanitize_field_names, type: :list, default: [], converter: WildcardPatternList.new
86
+ option :sanitize_field_names, type: :list,
87
+ default: SANITIZE_FIELD_NAMES_DEFAULT, converter: WildcardPatternList.new
79
88
  option :server_ca_cert, type: :string
80
89
  option :service_name, type: :string
90
+ option :service_node_name, type: :string
81
91
  option :service_version, type: :string
82
92
  option :source_lines_error_app_frames, type: :int, default: 5
83
93
  option :source_lines_error_library_frames, type: :int, default: 0
@@ -85,8 +95,9 @@ module ElasticAPM
85
95
  option :source_lines_span_library_frames, type: :int, default: 0
86
96
  option :span_frames_min_duration, type: :float, default: '5ms', converter: Duration.new(default_unit: 'ms')
87
97
  option :stack_trace_limit, type: :int, default: 999_999
98
+ option :transaction_ignore_urls, type: :list, default: [], converter: WildcardPatternList.new
88
99
  option :transaction_max_spans, type: :int, default: 500
89
- option :transaction_sample_rate, type: :float, default: 1.0
100
+ option :transaction_sample_rate, type: :float, default: 1.0, converter: RoundFloat.new
90
101
  option :use_elastic_traceparent_header, type: :bool, default: true
91
102
  option :use_legacy_sql_parser, type: :bool, default: false
92
103
  option :verify_server_cert, type: :bool, default: true
@@ -129,6 +140,7 @@ module ElasticAPM
129
140
  %w[
130
141
  action_dispatch
131
142
  delayed_job
143
+ dynamo_db
132
144
  elasticsearch
133
145
  faraday
134
146
  http
@@ -158,13 +170,12 @@ module ElasticAPM
158
170
  end
159
171
 
160
172
  def replace_options(new_options)
161
- return unless new_options
173
+ return if new_options.nil? || new_options.empty?
162
174
  options_copy = @options.dup
163
175
  new_options.each do |key, value|
164
176
  options_copy.fetch(key.to_sym).set(value)
165
177
  end
166
178
  @options = options_copy
167
- set_log_level(logger)
168
179
  end
169
180
 
170
181
  def app=(app)
@@ -186,6 +197,14 @@ module ElasticAPM
186
197
  metrics_interval > 0
187
198
  end
188
199
 
200
+ # DEPRECATED: Remove this in next major version
201
+ def sanitize_field_names=(value)
202
+ list = WildcardPatternList.new.call(value)
203
+ defaults = WildcardPatternList.new.call(SANITIZE_FIELD_NAMES_DEFAULT)
204
+ get(:sanitize_field_names).value =
205
+ defaults.concat(list).uniq(&:pattern) # use regex pattern for comparisons
206
+ end
207
+
189
208
  def span_frames_min_duration?
190
209
  span_frames_min_duration != 0
191
210
  end
@@ -232,6 +251,15 @@ module ElasticAPM
232
251
  self.default_labels = value
233
252
  end
234
253
 
254
+ def ignore_url_patterns=(value)
255
+ unless value == self.class.schema[:ignore_url_patterns][:default]
256
+ warn '[DEPRECATED] The option ignore_url_patterns is being removed. ' \
257
+ 'Consider using transaction_ignore_urls instead.'
258
+ end
259
+
260
+ set(:ignore_url_patterns, value)
261
+ end
262
+
235
263
  def custom_key_filters=(value)
236
264
  unless value == self.class.schema[:custom_key_filters][:default]
237
265
  warn '[DEPRECATED] The option custom_key_filters is being removed. ' \
@@ -286,15 +314,10 @@ module ElasticAPM
286
314
 
287
315
  def build_logger
288
316
  Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
289
- set_log_level(logger)
317
+ logger.level = log_level
290
318
  end
291
319
  end
292
320
 
293
- def set_log_level(logger)
294
- return unless logger
295
- logger.level = log_level
296
- end
297
-
298
321
  def app_type?(app)
299
322
  if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
300
323
  return :rails
@@ -321,7 +344,7 @@ module ElasticAPM
321
344
  self.logger ||= ::Rails.logger
322
345
 
323
346
  self.__root_path = ::Rails.root.to_s
324
- self.__view_paths = app.config.paths['app/views'].existent
347
+ self.__view_paths = app.config.paths['app/views'].existent + [::Rails.root.to_s]
325
348
  end
326
349
 
327
350
  def rails_app_name(app)
@@ -38,7 +38,8 @@ module ElasticAPM
38
38
  set(value || default)
39
39
  end
40
40
 
41
- attr_reader :key, :value, :default, :type
41
+ attr_reader :key, :default, :type, :converter
42
+ attr_accessor :value
42
43
 
43
44
  def set(value)
44
45
  @value = normalize(value)
@@ -0,0 +1,31 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require 'elastic_apm/util/precision_validator'
21
+
22
+ module ElasticAPM
23
+ class Config
24
+ # @api private
25
+ class RoundFloat
26
+ def call(value)
27
+ Util::PrecisionValidator.validate(value, precision: 4, minimum: 0.0001)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -27,6 +27,8 @@ module ElasticAPM
27
27
  @pattern = convert(str)
28
28
  end
29
29
 
30
+ attr_reader :pattern
31
+
30
32
  def match?(other)
31
33
  !!@pattern.match(other)
32
34
  end
@@ -36,12 +38,22 @@ module ElasticAPM
36
38
  private
37
39
 
38
40
  def convert(str)
41
+ case_sensitive = false
42
+
43
+ if str.start_with?('(?-i)')
44
+ str = str.gsub(/^\(\?-\i\)/, '')
45
+ case_sensitive = true
46
+ end
47
+
39
48
  parts =
40
49
  str.chars.each_with_object([]) do |char, arr|
41
50
  arr << (char == '*' ? '.*' : Regexp.escape(char))
42
51
  end
43
52
 
44
- Regexp.new('\A' + parts.join + '\Z', Regexp::IGNORECASE)
53
+ Regexp.new(
54
+ '\A' + parts.join + '\Z',
55
+ case_sensitive ? nil : Regexp::IGNORECASE
56
+ )
45
57
  end
46
58
  end
47
59
 
@@ -76,7 +76,7 @@ module ElasticAPM
76
76
  else
77
77
  body = req.body.read
78
78
  req.body.rewind
79
- body.byteslice(0, MAX_BODY_LENGTH).force_encoding('utf-8')
79
+ body.byteslice(0, MAX_BODY_LENGTH).force_encoding('utf-8').scrub
80
80
  end
81
81
  end
82
82
 
@@ -71,7 +71,7 @@ module ElasticAPM
71
71
  transaction.done 'success'
72
72
  rescue ::Exception => e
73
73
  ElasticAPM.report(e, handled: false)
74
- transaction.done 'error'
74
+ transaction.done 'error' if transaction
75
75
  raise
76
76
  ensure
77
77
  ElasticAPM.end_transaction
@@ -91,7 +91,7 @@ module ElasticAPM
91
91
  def trace_context(call)
92
92
  TraceContext.parse(metadata: call.metadata)
93
93
  rescue TraceContext::InvalidTraceparentHeader
94
- warn "Couldn't parse invalid trace context header: #{header.inspect}"
94
+ warn "Couldn't parse invalid trace context header: #{call.metadata}"
95
95
  nil
96
96
  end
97
97
  end
@@ -119,7 +119,13 @@ module ElasticAPM
119
119
  "Already inside #{transaction.inspect}"
120
120
  end
121
121
 
122
- sampled = trace_context ? trace_context.recorded? : random_sample?(config)
122
+ if trace_context
123
+ sampled = trace_context.recorded?
124
+ sample_rate = trace_context.tracestate.sample_rate
125
+ else
126
+ sampled = random_sample?(config)
127
+ sample_rate = config.transaction_sample_rate
128
+ end
123
129
 
124
130
  transaction =
125
131
  Transaction.new(
@@ -128,6 +134,7 @@ module ElasticAPM
128
134
  context: context,
129
135
  trace_context: trace_context,
130
136
  sampled: sampled,
137
+ sample_rate: sample_rate,
131
138
  config: config
132
139
  )
133
140
 
@@ -259,7 +266,7 @@ module ElasticAPM
259
266
  end
260
267
 
261
268
  def update_transaction_metrics(transaction)
262
- return unless transaction.collect_metrics
269
+ return unless transaction.collect_metrics?
263
270
 
264
271
  tags = {
265
272
  'transaction.name': transaction.name,
@@ -298,7 +305,7 @@ module ElasticAPM
298
305
  end
299
306
 
300
307
  def update_span_metrics(span)
301
- return unless span.transaction.breakdown_metrics
308
+ return unless span.transaction.collect_metrics?
302
309
 
303
310
  tags = {
304
311
  'span.type': span.type,
@@ -25,12 +25,14 @@ module ElasticAPM
25
25
  @process = ProcessInfo.new(config)
26
26
  @system = SystemInfo.new(config)
27
27
  @labels = config.global_labels
28
+ @cloud = CloudInfo.new(config).fetch!
28
29
  end
29
30
 
30
- attr_reader :service, :process, :system, :labels
31
+ attr_reader :service, :process, :system, :cloud, :labels
31
32
  end
32
33
  end
33
34
 
34
35
  require 'elastic_apm/metadata/service_info'
35
36
  require 'elastic_apm/metadata/system_info'
36
37
  require 'elastic_apm/metadata/process_info'
38
+ require 'elastic_apm/metadata/cloud_info'
@@ -0,0 +1,128 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ require "http"
21
+
22
+ module ElasticAPM
23
+ class Metadata
24
+ # @api private
25
+ class CloudInfo
26
+ include Logging
27
+
28
+ AWS_URI = "http://169.254.169.254/latest/dynamic/instance-identity/document"
29
+ GCP_URI = "http://metadata.google.internal/computeMetadata/v1/?recursive=true"
30
+ AZURE_URI = "http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15"
31
+
32
+ def initialize(config)
33
+ @config = config
34
+ @client = HTTP.timeout(0.1)
35
+ end
36
+
37
+ attr_reader :config
38
+
39
+ attr_accessor(
40
+ :account_id,
41
+ :account_name,
42
+ :instance_id,
43
+ :instance_name,
44
+ :machine_type,
45
+ :project_id,
46
+ :project_name,
47
+ :availability_zone,
48
+ :provider,
49
+ :region
50
+ )
51
+
52
+ def fetch!
53
+ case config.cloud_provider
54
+ when "aws"
55
+ fetch_aws
56
+ when "gcp"
57
+ fetch_gcp
58
+ when "azure"
59
+ fetch_azure
60
+ when "auto"
61
+ fetch_aws || fetch_gcp || fetch_azure
62
+ when "none"
63
+ nil
64
+ else
65
+ error("Unknown setting for cloud_provider '#{config.cloud_provider}'")
66
+ end
67
+
68
+ self
69
+ end
70
+
71
+ private
72
+
73
+ def fetch_aws
74
+ resp = @client.get(AWS_URI)
75
+
76
+ return unless resp.status === 200
77
+ return unless (metadata = JSON.parse(resp.body))
78
+
79
+ self.provider = "aws"
80
+ self.account_id = metadata["accountId"]
81
+ self.instance_id = metadata["instanceId"]
82
+ self.availability_zone = metadata["availabilityZone"]
83
+ self.machine_type = metadata["instanceType"]
84
+ self.region = metadata["region"]
85
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
86
+ nil
87
+ end
88
+
89
+ def fetch_gcp
90
+ resp = @client.headers("Metadata-Flavor" => "Google").get(GCP_URI)
91
+
92
+ return unless resp.status === 200
93
+ return unless (metadata = JSON.parse(resp.body))
94
+
95
+ zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
96
+
97
+ self.provider = "gcp"
98
+ self.instance_id = metadata["instance"]["id"]
99
+ self.instance_name = metadata["instance"]["name"]
100
+ self.project_id = metadata["project"]["numericProjectId"]
101
+ self.project_name = metadata["project"]["projectId"]
102
+ self.availability_zone = zone
103
+ self.region = zone.split("-")[0..-2].join("-")
104
+ self.machine_type = metadata["instance"]["machineType"]
105
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
106
+ nil
107
+ end
108
+
109
+ def fetch_azure
110
+ resp = @client.headers("Metadata" => "true").get(AZURE_URI)
111
+
112
+ return unless resp.status === 200
113
+ return unless (metadata = JSON.parse(resp.body))
114
+
115
+ self.provider = 'azure'
116
+ self.account_id = metadata["subscriptionId"]
117
+ self.instance_id = metadata["vmId"]
118
+ self.instance_name = metadata["name"]
119
+ self.project_name = metadata["resourceGroupName"]
120
+ self.availability_zone = metadata["zone"]
121
+ self.machine_type = metadata["vmSize"]
122
+ self.region = metadata["location"]
123
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
124
+ nil
125
+ end
126
+ end
127
+ end
128
+ end