fluent-plugin-google-cloud 0.8.6 → 0.8.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44d38e36606e310f5ec6dae2931125f139b1e40c8c2b5eafd45c3497e6aeb1c9
4
- data.tar.gz: b2aecc00518d8787a9d3a2fcd2fad880246cf63cfddc3b4967277eb07baa2e5b
3
+ metadata.gz: 851c496e93becdfb6a735f9c71aceecfdeccfff14519ce8311bb3dd42c7ed382
4
+ data.tar.gz: d893b865df5a2b10bac43c65381abe1b95e27333ff77ac45c6cb988c2f446036
5
5
  SHA512:
6
- metadata.gz: c4b3ead22e4a4ee3bcb20cbd5102f4847be31bf99606e1072b668a75176b452feb5cf84fd6d19297739ca76fba6c261fa33e562faabc07553587510554e8c867
7
- data.tar.gz: c5659ca5819695d2799331bb22fe5f05fe39434b7f744e324ad8aa43eeb3b917f5e530868e9c11d2916b896ae2c1f2dee74880a11b0f278da1a0742db3fd6b82
6
+ metadata.gz: 6f840e9c06e995be5dd4abd0dcd9d04042c6d78507896fc842ab2b1fb8cb82eac4535fdb6259656cc703e99955e074e64c1ec0292c31d77647cbc95d54e7b919
7
+ data.tar.gz: c1573f562d3691a83b8b3dd2b8e8a0ed1585edac5f67df6179bc8c62af4a3728ce97b90be8827008718921069806eb6e0d2e81fd5c7f6de4c6603a1c2738774e
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.8.6)
4
+ fluent-plugin-google-cloud (0.8.7)
5
5
  fluentd (= 1.7.4)
6
6
  google-api-client (= 0.30.8)
7
7
  google-cloud-logging (= 1.6.6)
@@ -18,7 +18,7 @@ GEM
18
18
  specs:
19
19
  addressable (2.7.0)
20
20
  public_suffix (>= 2.0.2, < 5.0)
21
- ast (2.4.0)
21
+ ast (2.4.1)
22
22
  concurrent-ruby (1.1.6)
23
23
  cool.io (1.6.0)
24
24
  coveralls (0.8.23)
@@ -10,7 +10,7 @@ eos
10
10
  gem.homepage =
11
11
  'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
12
12
  gem.license = 'Apache-2.0'
13
- gem.version = '0.8.6'
13
+ gem.version = '0.8.7'
14
14
  gem.authors = ['Stackdriver Agents Team']
15
15
  gem.email = ['stackdriver-agents@google.com']
16
16
  gem.required_ruby_version = Gem::Requirement.new('>= 2.2')
@@ -0,0 +1,381 @@
1
+ # Copyright 2020 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Common
16
+ # Constants for service names, resource types and etc.
17
+ module ServiceConstants
18
+ APPENGINE_CONSTANTS = {
19
+ service: 'appengine.googleapis.com',
20
+ resource_type: 'gae_app',
21
+ metadata_attributes: %w(gae_backend_name gae_backend_version)
22
+ }.freeze
23
+ COMPUTE_CONSTANTS = {
24
+ service: 'compute.googleapis.com',
25
+ resource_type: 'gce_instance'
26
+ }.freeze
27
+ GKE_CONSTANTS = {
28
+ service: 'container.googleapis.com',
29
+ resource_type: 'gke_container',
30
+ extra_resource_labels: %w(namespace_id pod_id container_name),
31
+ extra_common_labels: %w(namespace_name pod_name),
32
+ metadata_attributes: %w(cluster-name cluster-location),
33
+ stream_severity_map: {
34
+ 'stdout' => 'INFO',
35
+ 'stderr' => 'ERROR'
36
+ }
37
+ }.freeze
38
+ K8S_CONTAINER_CONSTANTS = {
39
+ resource_type: 'k8s_container'
40
+ }.freeze
41
+ K8S_POD_CONSTANTS = {
42
+ resource_type: 'k8s_pod'
43
+ }.freeze
44
+ K8S_NODE_CONSTANTS = {
45
+ resource_type: 'k8s_node'
46
+ }.freeze
47
+ DATAFLOW_CONSTANTS = {
48
+ service: 'dataflow.googleapis.com',
49
+ resource_type: 'dataflow_step',
50
+ extra_resource_labels: %w(region job_name job_id step_id)
51
+ }.freeze
52
+ DATAPROC_CONSTANTS = {
53
+ service: 'cluster.dataproc.googleapis.com',
54
+ resource_type: 'cloud_dataproc_cluster',
55
+ metadata_attributes: %w(dataproc-cluster-uuid dataproc-cluster-name)
56
+ }.freeze
57
+ EC2_CONSTANTS = {
58
+ service: 'ec2.amazonaws.com',
59
+ resource_type: 'aws_ec2_instance'
60
+ }.freeze
61
+ ML_CONSTANTS = {
62
+ service: 'ml.googleapis.com',
63
+ resource_type: 'ml_job',
64
+ extra_resource_labels: %w(job_id task_name)
65
+ }.freeze
66
+
67
+ # The map between a subservice name and a resource type.
68
+ SUBSERVICE_MAP =
69
+ [APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAFLOW_CONSTANTS,
70
+ DATAPROC_CONSTANTS, ML_CONSTANTS]
71
+ .map { |consts| [consts[:service], consts[:resource_type]] }.to_h
72
+ # Default back to GCE if invalid value is detected.
73
+ SUBSERVICE_MAP.default = COMPUTE_CONSTANTS[:resource_type]
74
+ SUBSERVICE_MAP.freeze
75
+
76
+ # The map between a resource type and expected subservice attributes.
77
+ SUBSERVICE_METADATA_ATTRIBUTES =
78
+ [APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAPROC_CONSTANTS].map do |consts|
79
+ [consts[:resource_type], consts[:metadata_attributes].to_set]
80
+ end.to_h.freeze
81
+ end
82
+
83
+ # Name of the the Google cloud logging write scope.
84
+ LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'.freeze
85
+
86
+ # Address of the metadata service.
87
+ METADATA_SERVICE_ADDR = '169.254.169.254'.freeze
88
+
89
+ # "enum" of Platform values
90
+ module Platform
91
+ OTHER = 0 # Other/unkown platform
92
+ GCE = 1 # Google Compute Engine
93
+ EC2 = 2 # Amazon EC2
94
+ end
95
+
96
+ # Utilities for managing the resource used when writing to the
97
+ # Google API.
98
+ class Utils
99
+ include Common::ServiceConstants
100
+
101
+ def initialize(log)
102
+ @log = log
103
+ end
104
+
105
+ # Determine what platform we are running on by consulting the metadata
106
+ # service (unless the user has explicitly disabled using that).
107
+ def detect_platform(use_metadata_service)
108
+ unless use_metadata_service
109
+ @log.info 'use_metadata_service is false; not detecting platform'
110
+ return Platform::OTHER
111
+ end
112
+
113
+ begin
114
+ open('http://' + METADATA_SERVICE_ADDR, proxy: false) do |f|
115
+ if f.meta['metadata-flavor'] == 'Google'
116
+ @log.info 'Detected GCE platform'
117
+ return Platform::GCE
118
+ end
119
+ if f.meta['server'] == 'EC2ws'
120
+ @log.info 'Detected EC2 platform'
121
+ return Platform::EC2
122
+ end
123
+ end
124
+ rescue StandardError => e
125
+ @log.error 'Failed to access metadata service: ', error: e
126
+ end
127
+
128
+ @log.info 'Unable to determine platform'
129
+ Platform::OTHER
130
+ end
131
+
132
+ def fetch_gce_metadata(platform, metadata_path)
133
+ raise "Called fetch_gce_metadata with platform=#{platform}" unless
134
+ platform == Platform::GCE
135
+ # See https://cloud.google.com/compute/docs/metadata
136
+ open('http://' + METADATA_SERVICE_ADDR + '/computeMetadata/v1/' +
137
+ metadata_path, 'Metadata-Flavor' => 'Google', :proxy => false,
138
+ &:read)
139
+ end
140
+
141
+ # EC2 Metadata server returns everything in one call. Store it after the
142
+ # first fetch to avoid making multiple calls.
143
+ def ec2_metadata(platform)
144
+ raise "Called ec2_metadata with platform=#{platform}" unless
145
+ platform == Platform::EC2
146
+ unless @ec2_metadata
147
+ # See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
148
+ open('http://' + METADATA_SERVICE_ADDR +
149
+ '/latest/dynamic/instance-identity/document', proxy: false) do |f|
150
+ contents = f.read
151
+ @ec2_metadata = JSON.parse(contents)
152
+ end
153
+ end
154
+
155
+ @ec2_metadata
156
+ end
157
+
158
+ # Check required variables like @project_id, @vm_id, @vm_name and @zone.
159
+ def check_required_metadata_variables(platform, project_id, zone, vm_id)
160
+ missing = []
161
+ missing << 'project_id' unless project_id
162
+ if platform != Platform::OTHER
163
+ missing << 'zone' unless zone
164
+ missing << 'vm_id' unless vm_id
165
+ end
166
+ return if missing.empty?
167
+ raise Fluent::ConfigError,
168
+ "Unable to obtain metadata parameters: #{missing.join(' ')}"
169
+ end
170
+
171
+ # 1. Return the value if it is explicitly set in the config already.
172
+ # 2. If not, try to retrieve it by calling metadata server directly.
173
+ # 3. If still not set, try to obtain it from the credentials.
174
+ def get_project_id(platform, project_id)
175
+ project_id ||= CredentialsInfo.project_id
176
+ project_id ||= fetch_gce_metadata(platform, 'project/project-id') if
177
+ platform == Platform::GCE
178
+ project_id
179
+ end
180
+
181
+ # 1. Return the value if it is explicitly set in the config already.
182
+ # 2. If not, try to retrieve it by calling metadata servers directly.
183
+ def get_vm_id(platform, vm_id)
184
+ vm_id ||= fetch_gce_metadata(platform, 'instance/id') if
185
+ platform == Platform::GCE
186
+ vm_id ||= ec2_metadata(platform)['instanceId'] if
187
+ platform == Platform::EC2
188
+ vm_id
189
+ rescue StandardError => e
190
+ @log.error 'Failed to obtain vm_id: ', error: e
191
+ end
192
+
193
+ # 1. Return the value if it is explicitly set in the config already.
194
+ # 2. If not, try to retrieve it locally.
195
+ def get_vm_name(vm_name)
196
+ vm_name ||= Socket.gethostname
197
+ vm_name
198
+ rescue StandardError => e
199
+ @log.error 'Failed to obtain vm name: ', error: e
200
+ end
201
+
202
+ # 1. Return the value if it is explicitly set in the config already.
203
+ # 2. If not, try to retrieve it locally.
204
+ def get_location(platform, zone, use_aws_availability_zone)
205
+ # Response format: "projects/<number>/zones/<zone>"
206
+ zone ||= fetch_gce_metadata(platform,
207
+ 'instance/zone').rpartition('/')[2] if
208
+ platform == Platform::GCE
209
+ aws_location_key = if use_aws_availability_zone
210
+ 'availabilityZone'
211
+ else
212
+ 'region'
213
+ end
214
+ zone ||= 'aws:' + ec2_metadata(platform)[aws_location_key] if
215
+ platform == Platform::EC2 &&
216
+ ec2_metadata(platform).key?(aws_location_key)
217
+ zone
218
+ rescue StandardError => e
219
+ @log.error 'Failed to obtain location: ', error: e
220
+ end
221
+
222
+ # Retrieve monitored resource via the legacy way.
223
+ #
224
+ # Note: This is just a failover plan if we fail to get metadata from
225
+ # Metadata Agent. Thus it should be equivalent to what Metadata Agent
226
+ # returns.
227
+ def determine_agent_level_monitored_resource_via_legacy(
228
+ platform, subservice_name, detect_subservice, vm_id, zone)
229
+ resource = Google::Apis::LoggingV2::MonitoredResource.new(
230
+ labels: {})
231
+ resource.type = determine_agent_level_monitored_resource_type(
232
+ platform, subservice_name, detect_subservice)
233
+ resource.labels = determine_agent_level_monitored_resource_labels(
234
+ platform, resource.type, vm_id, zone)
235
+ resource
236
+ end
237
+
238
+ # Determine agent level monitored resource type.
239
+ def determine_agent_level_monitored_resource_type(
240
+ platform, subservice_name, detect_subservice)
241
+ case platform
242
+ when Platform::OTHER
243
+ # Unknown platform will be defaulted to GCE instance.
244
+ return COMPUTE_CONSTANTS[:resource_type]
245
+
246
+ when Platform::EC2
247
+ return EC2_CONSTANTS[:resource_type]
248
+
249
+ when Platform::GCE
250
+ # Resource types determined by subservice_name config.
251
+ return SUBSERVICE_MAP[subservice_name] if subservice_name
252
+
253
+ # Resource types determined by detect_subservice config.
254
+ if detect_subservice
255
+ begin
256
+ attributes = fetch_gce_metadata(platform,
257
+ 'instance/attributes/').split.to_set
258
+ SUBSERVICE_METADATA_ATTRIBUTES.each do |resource_type, expected|
259
+ return resource_type if attributes.superset?(expected)
260
+ end
261
+ rescue StandardError => e
262
+ @log.error 'Failed to detect subservice: ', error: e
263
+ end
264
+ end
265
+
266
+ # GCE instance.
267
+ return COMPUTE_CONSTANTS[:resource_type]
268
+ end
269
+ end
270
+
271
+ # Determine agent level monitored resource labels based on the resource
272
+ # type. Each resource type has its own labels that need to be filled in.
273
+ def determine_agent_level_monitored_resource_labels(
274
+ platform, type, vm_id, zone)
275
+ case type
276
+ # GAE app.
277
+ when APPENGINE_CONSTANTS[:resource_type]
278
+ return {
279
+ 'module_id' =>
280
+ fetch_gce_metadata(platform,
281
+ 'instance/attributes/gae_backend_name'),
282
+ 'version_id' =>
283
+ fetch_gce_metadata(platform,
284
+ 'instance/attributes/gae_backend_version')
285
+ }
286
+
287
+ # GCE.
288
+ when COMPUTE_CONSTANTS[:resource_type]
289
+ raise "Cannot construct a #{type} resource without vm_id and zone" \
290
+ unless vm_id && zone
291
+ return {
292
+ 'instance_id' => vm_id,
293
+ 'zone' => zone
294
+ }
295
+
296
+ # GKE container.
297
+ when GKE_CONSTANTS[:resource_type]
298
+ raise "Cannot construct a #{type} resource without vm_id and zone" \
299
+ unless vm_id && zone
300
+ return {
301
+ 'instance_id' => vm_id,
302
+ 'zone' => zone,
303
+ 'cluster_name' =>
304
+ fetch_gce_metadata(platform, 'instance/attributes/cluster-name')
305
+ }
306
+
307
+ # Cloud Dataproc.
308
+ when DATAPROC_CONSTANTS[:resource_type]
309
+ return {
310
+ 'cluster_uuid' =>
311
+ fetch_gce_metadata(platform,
312
+ 'instance/attributes/dataproc-cluster-uuid'),
313
+ 'cluster_name' =>
314
+ fetch_gce_metadata(platform,
315
+ 'instance/attributes/dataproc-cluster-name'),
316
+ 'region' =>
317
+ fetch_gce_metadata(platform,
318
+ 'instance/attributes/dataproc-region')
319
+ }
320
+
321
+ # EC2.
322
+ when EC2_CONSTANTS[:resource_type]
323
+ raise "Cannot construct a #{type} resource without vm_id and zone" \
324
+ unless vm_id && zone
325
+ labels = {
326
+ 'instance_id' => vm_id,
327
+ 'region' => zone
328
+ }
329
+ labels['aws_account'] = ec2_metadata(platform)['accountId'] if
330
+ ec2_metadata(platform).key?('accountId')
331
+ return labels
332
+ end
333
+
334
+ {}
335
+ rescue StandardError => e
336
+ if [Platform::GCE, Platform::EC2].include?(platform)
337
+ @log.error "Failed to set monitored resource labels for #{type}: ",
338
+ error: e
339
+ end
340
+ {}
341
+ end
342
+
343
+ # TODO: This functionality should eventually be available in another
344
+ # library, but implement it ourselves for now.
345
+ module CredentialsInfo
346
+ # Determine the project ID from the credentials, if possible.
347
+ # Returns the project ID (as a string) on success, or nil on failure.
348
+ def self.project_id
349
+ creds = Google::Auth.get_application_default(LOGGING_SCOPE)
350
+ if creds.respond_to?(:project_id)
351
+ return creds.project_id if creds.project_id
352
+ end
353
+ if creds.issuer
354
+ id = extract_project_id(creds.issuer)
355
+ return id unless id.nil?
356
+ end
357
+ if creds.client_id
358
+ id = extract_project_id(creds.client_id)
359
+ return id unless id.nil?
360
+ end
361
+ nil
362
+ end
363
+
364
+ # Extracts the project id (either name or number) from str and returns
365
+ # it (as a string) on success, or nil on failure.
366
+ #
367
+ # Recognizes IAM format (account@project-name.iam.gserviceaccount.com)
368
+ # as well as the legacy format with a project number at the front of the
369
+ # string, terminated by a dash (-) which is not part of the ID, i.e.:
370
+ # <PROJECT_ID>-<OTHER_PARTS>.apps.googleusercontent.com
371
+ def self.extract_project_id(str)
372
+ [/^.*@(?<project_id>.+)\.iam\.gserviceaccount\.com/,
373
+ /^(?<project_id>\d+)-/].each do |exp|
374
+ match_data = exp.match(str)
375
+ return match_data['project_id'] unless match_data.nil?
376
+ end
377
+ nil
378
+ end
379
+ end
380
+ end
381
+ end
@@ -28,6 +28,7 @@ require 'google/logging/v2/logging_services_pb'
28
28
  require 'google/logging/v2/log_entry_pb'
29
29
  require 'googleauth'
30
30
 
31
+ require_relative 'common'
31
32
  require_relative 'monitoring'
32
33
  require_relative 'statusz'
33
34
 
@@ -90,73 +91,6 @@ end
90
91
  module Fluent
91
92
  # fluentd output plugin for the Stackdriver Logging API
92
93
  class GoogleCloudOutput < BufferedOutput
93
- # Constants for service names, resource types and etc.
94
- module ServiceConstants
95
- APPENGINE_CONSTANTS = {
96
- service: 'appengine.googleapis.com',
97
- resource_type: 'gae_app',
98
- metadata_attributes: %w(gae_backend_name gae_backend_version)
99
- }.freeze
100
- COMPUTE_CONSTANTS = {
101
- service: 'compute.googleapis.com',
102
- resource_type: 'gce_instance'
103
- }.freeze
104
- GKE_CONSTANTS = {
105
- service: 'container.googleapis.com',
106
- resource_type: 'gke_container',
107
- extra_resource_labels: %w(namespace_id pod_id container_name),
108
- extra_common_labels: %w(namespace_name pod_name),
109
- metadata_attributes: %w(cluster-name cluster-location),
110
- stream_severity_map: {
111
- 'stdout' => 'INFO',
112
- 'stderr' => 'ERROR'
113
- }
114
- }.freeze
115
- K8S_CONTAINER_CONSTANTS = {
116
- resource_type: 'k8s_container'
117
- }.freeze
118
- K8S_POD_CONSTANTS = {
119
- resource_type: 'k8s_pod'
120
- }.freeze
121
- K8S_NODE_CONSTANTS = {
122
- resource_type: 'k8s_node'
123
- }.freeze
124
- DATAFLOW_CONSTANTS = {
125
- service: 'dataflow.googleapis.com',
126
- resource_type: 'dataflow_step',
127
- extra_resource_labels: %w(region job_name job_id step_id)
128
- }.freeze
129
- DATAPROC_CONSTANTS = {
130
- service: 'cluster.dataproc.googleapis.com',
131
- resource_type: 'cloud_dataproc_cluster',
132
- metadata_attributes: %w(dataproc-cluster-uuid dataproc-cluster-name)
133
- }.freeze
134
- EC2_CONSTANTS = {
135
- service: 'ec2.amazonaws.com',
136
- resource_type: 'aws_ec2_instance'
137
- }.freeze
138
- ML_CONSTANTS = {
139
- service: 'ml.googleapis.com',
140
- resource_type: 'ml_job',
141
- extra_resource_labels: %w(job_id task_name)
142
- }.freeze
143
-
144
- # The map between a subservice name and a resource type.
145
- SUBSERVICE_MAP =
146
- [APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAFLOW_CONSTANTS,
147
- DATAPROC_CONSTANTS, ML_CONSTANTS]
148
- .map { |consts| [consts[:service], consts[:resource_type]] }.to_h
149
- # Default back to GCE if invalid value is detected.
150
- SUBSERVICE_MAP.default = COMPUTE_CONSTANTS[:resource_type]
151
- SUBSERVICE_MAP.freeze
152
-
153
- # The map between a resource type and expected subservice attributes.
154
- SUBSERVICE_METADATA_ATTRIBUTES =
155
- [APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAPROC_CONSTANTS].map do |consts|
156
- [consts[:resource_type], consts[:metadata_attributes].to_set]
157
- end.to_h.freeze
158
- end
159
-
160
94
  # Constants for configuration.
161
95
  module ConfigConstants
162
96
  # Default values for JSON payload keys to set the "httpRequest",
@@ -247,7 +181,7 @@ module Fluent
247
181
  .freeze
248
182
  end
249
183
 
250
- include self::ServiceConstants
184
+ include Common::ServiceConstants
251
185
  include self::ConfigConstants
252
186
  include self::InternalConstants
253
187
 
@@ -278,12 +212,6 @@ module Fluent
278
212
  end
279
213
  end.freeze
280
214
 
281
- # Name of the the Google cloud logging write scope.
282
- LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'.freeze
283
-
284
- # Address of the metadata service.
285
- METADATA_SERVICE_ADDR = '169.254.169.254'.freeze
286
-
287
215
  # Disable this warning to conform to fluentd config_param conventions.
288
216
  # rubocop:disable Style/HashSyntax
289
217
 
@@ -539,7 +467,9 @@ module Fluent
539
467
 
540
468
  set_regexp_patterns
541
469
 
542
- @platform = detect_platform
470
+ @utils = Common::Utils.new(@log)
471
+
472
+ @platform = @utils.detect_platform(@use_metadata_service)
543
473
 
544
474
  # Treat an empty setting of the credentials file path environment variable
545
475
  # as unset. This way the googleauth lib could fetch the credentials
@@ -548,12 +478,20 @@ module Fluent
548
478
  ENV[CREDENTIALS_PATH_ENV_VAR] == ''
549
479
 
550
480
  # Set required variables: @project_id, @vm_id, @vm_name and @zone.
551
- set_required_metadata_variables
481
+ @project_id = @utils.get_project_id(@platform, @project_id)
482
+ @vm_id = @utils.get_vm_id(@platform, @vm_id)
483
+ @vm_name = @utils.get_vm_name(@vm_name)
484
+ @zone = @utils.get_location(@platform, @zone, @use_aws_availability_zone)
485
+
486
+ # All metadata parameters must now be set.
487
+ @utils.check_required_metadata_variables(
488
+ @platform, @project_id, @zone, @vm_id)
552
489
 
553
490
  # Retrieve monitored resource.
554
491
  # Fail over to retrieve monitored resource via the legacy path if we fail
555
492
  # to get it from Metadata Agent.
556
- @resource ||= determine_agent_level_monitored_resource_via_legacy
493
+ @resource ||= @utils.determine_agent_level_monitored_resource_via_legacy(
494
+ @platform, @subservice_name, @detect_subservice, @vm_id, @zone)
557
495
 
558
496
  # If monitoring is enabled, register metrics in the default registry
559
497
  # and store metric objects for future use.
@@ -611,7 +549,7 @@ module Fluent
611
549
 
612
550
  # Determine the common labels that should be added to all log entries
613
551
  # processed by this logging agent.
614
- @common_labels = determine_agent_level_common_labels
552
+ @common_labels = determine_agent_level_common_labels(@resource)
615
553
 
616
554
  # The resource and labels are now set up; ensure they can't be modified
617
555
  # without first duping them.
@@ -627,7 +565,7 @@ module Fluent
627
565
  @write_request = method(:write_request_via_rest)
628
566
  end
629
567
 
630
- if [Platform::GCE, Platform::EC2].include?(@platform)
568
+ if [Common::Platform::GCE, Common::Platform::EC2].include?(@platform)
631
569
  # Log an informational message containing the Logs viewer URL
632
570
  @log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
633
571
  "viewer?project=#{@project_id}&resource=#{@resource.type}/",
@@ -1094,66 +1032,6 @@ module Fluent
1094
1032
  nil
1095
1033
  end
1096
1034
 
1097
- # "enum" of Platform values
1098
- module Platform
1099
- OTHER = 0 # Other/unkown platform
1100
- GCE = 1 # Google Compute Engine
1101
- EC2 = 2 # Amazon EC2
1102
- end
1103
-
1104
- # Determine what platform we are running on by consulting the metadata
1105
- # service (unless the user has explicitly disabled using that).
1106
- def detect_platform
1107
- unless @use_metadata_service
1108
- @log.info 'use_metadata_service is false; not detecting platform'
1109
- return Platform::OTHER
1110
- end
1111
-
1112
- begin
1113
- open('http://' + METADATA_SERVICE_ADDR, proxy: false) do |f|
1114
- if f.meta['metadata-flavor'] == 'Google'
1115
- @log.info 'Detected GCE platform'
1116
- return Platform::GCE
1117
- end
1118
- if f.meta['server'] == 'EC2ws'
1119
- @log.info 'Detected EC2 platform'
1120
- return Platform::EC2
1121
- end
1122
- end
1123
- rescue StandardError => e
1124
- @log.error 'Failed to access metadata service: ', error: e
1125
- end
1126
-
1127
- @log.info 'Unable to determine platform'
1128
- Platform::OTHER
1129
- end
1130
-
1131
- def fetch_gce_metadata(metadata_path)
1132
- raise "Called fetch_gce_metadata with platform=#{@platform}" unless
1133
- @platform == Platform::GCE
1134
- # See https://cloud.google.com/compute/docs/metadata
1135
- open('http://' + METADATA_SERVICE_ADDR + '/computeMetadata/v1/' +
1136
- metadata_path, 'Metadata-Flavor' => 'Google', :proxy => false,
1137
- &:read)
1138
- end
1139
-
1140
- # EC2 Metadata server returns everything in one call. Store it after the
1141
- # first fetch to avoid making multiple calls.
1142
- def ec2_metadata
1143
- raise "Called ec2_metadata with platform=#{@platform}" unless
1144
- @platform == Platform::EC2
1145
- unless @ec2_metadata
1146
- # See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
1147
- open('http://' + METADATA_SERVICE_ADDR +
1148
- '/latest/dynamic/instance-identity/document', proxy: false) do |f|
1149
- contents = f.read
1150
- @ec2_metadata = JSON.parse(contents)
1151
- end
1152
- end
1153
-
1154
- @ec2_metadata
1155
- end
1156
-
1157
1035
  # Set regexp patterns to parse tags and logs.
1158
1036
  def set_regexp_patterns
1159
1037
  @compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp) if
@@ -1163,187 +1041,14 @@ module Fluent
1163
1041
  /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
1164
1042
  end
1165
1043
 
1166
- # Set required variables like @project_id, @vm_id, @vm_name and @zone.
1167
- def set_required_metadata_variables
1168
- set_project_id
1169
- set_vm_id
1170
- set_vm_name
1171
- set_location
1172
-
1173
- # All metadata parameters must now be set.
1174
- missing = []
1175
- missing << 'project_id' unless @project_id
1176
- if @platform != Platform::OTHER
1177
- missing << 'zone' unless @zone
1178
- missing << 'vm_id' unless @vm_id
1179
- end
1180
- return if missing.empty?
1181
- raise Fluent::ConfigError,
1182
- "Unable to obtain metadata parameters: #{missing.join(' ')}"
1183
- end
1184
-
1185
- # 1. Return the value if it is explicitly set in the config already.
1186
- # 2. If not, try to retrieve it by calling metadata server directly.
1187
- # 3. If still not set, try to obtain it from the credentials.
1188
- def set_project_id
1189
- @project_id ||= CredentialsInfo.project_id
1190
- @project_id ||= fetch_gce_metadata('project/project-id') if
1191
- @platform == Platform::GCE
1192
- end
1193
-
1194
- # 1. Return the value if it is explicitly set in the config already.
1195
- # 2. If not, try to retrieve it by calling metadata servers directly.
1196
- def set_vm_id
1197
- @vm_id ||= fetch_gce_metadata('instance/id') if @platform == Platform::GCE
1198
- @vm_id ||= ec2_metadata['instanceId'] if @platform == Platform::EC2
1199
- rescue StandardError => e
1200
- @log.error 'Failed to obtain vm_id: ', error: e
1201
- end
1202
-
1203
- # 1. Return the value if it is explicitly set in the config already.
1204
- # 2. If not, try to retrieve it locally.
1205
- def set_vm_name
1206
- @vm_name ||= Socket.gethostname
1207
- rescue StandardError => e
1208
- @log.error 'Failed to obtain vm name: ', error: e
1209
- end
1210
-
1211
- # 1. Return the value if it is explicitly set in the config already.
1212
- # 2. If not, try to retrieve it locally.
1213
- def set_location
1214
- # Response format: "projects/<number>/zones/<zone>"
1215
- @zone ||= fetch_gce_metadata('instance/zone').rpartition('/')[2] if
1216
- @platform == Platform::GCE
1217
- aws_location_key = if @use_aws_availability_zone
1218
- 'availabilityZone'
1219
- else
1220
- 'region'
1221
- end
1222
- @zone ||= 'aws:' + ec2_metadata[aws_location_key] if
1223
- @platform == Platform::EC2 && ec2_metadata.key?(aws_location_key)
1224
- rescue StandardError => e
1225
- @log.error 'Failed to obtain location: ', error: e
1226
- end
1227
-
1228
- # Retrieve monitored resource via the legacy way.
1229
- #
1230
- # Note: This is just a failover plan if we fail to get metadata from
1231
- # Metadata Agent. Thus it should be equivalent to what Metadata Agent
1232
- # returns.
1233
- def determine_agent_level_monitored_resource_via_legacy
1234
- resource = Google::Apis::LoggingV2::MonitoredResource.new(
1235
- labels: {})
1236
- resource.type = determine_agent_level_monitored_resource_type
1237
- resource.labels = determine_agent_level_monitored_resource_labels(
1238
- resource.type)
1239
- resource
1240
- end
1241
-
1242
- # Determine agent level monitored resource type.
1243
- def determine_agent_level_monitored_resource_type
1244
- case @platform
1245
- when Platform::OTHER
1246
- # Unknown platform will be defaulted to GCE instance.
1247
- return COMPUTE_CONSTANTS[:resource_type]
1248
-
1249
- when Platform::EC2
1250
- return EC2_CONSTANTS[:resource_type]
1251
-
1252
- when Platform::GCE
1253
- # Resource types determined by @subservice_name config.
1254
- return SUBSERVICE_MAP[@subservice_name] if @subservice_name
1255
-
1256
- # Resource types determined by @detect_subservice config.
1257
- if @detect_subservice
1258
- begin
1259
- attributes = fetch_gce_metadata('instance/attributes/').split.to_set
1260
- SUBSERVICE_METADATA_ATTRIBUTES.each do |resource_type, expected|
1261
- return resource_type if attributes.superset?(expected)
1262
- end
1263
- rescue StandardError => e
1264
- @log.error 'Failed to detect subservice: ', error: e
1265
- end
1266
- end
1267
-
1268
- # GCE instance.
1269
- return COMPUTE_CONSTANTS[:resource_type]
1270
- end
1271
- end
1272
-
1273
- # Determine agent level monitored resource labels based on the resource
1274
- # type. Each resource type has its own labels that need to be filled in.
1275
- def determine_agent_level_monitored_resource_labels(type)
1276
- case type
1277
- # GAE app.
1278
- when APPENGINE_CONSTANTS[:resource_type]
1279
- return {
1280
- 'module_id' =>
1281
- fetch_gce_metadata('instance/attributes/gae_backend_name'),
1282
- 'version_id' =>
1283
- fetch_gce_metadata('instance/attributes/gae_backend_version')
1284
- }
1285
-
1286
- # GCE.
1287
- when COMPUTE_CONSTANTS[:resource_type]
1288
- raise "Cannot construct a #{type} resource without vm_id and zone" \
1289
- unless @vm_id && @zone
1290
- return {
1291
- 'instance_id' => @vm_id,
1292
- 'zone' => @zone
1293
- }
1294
-
1295
- # GKE container.
1296
- when GKE_CONSTANTS[:resource_type]
1297
- raise "Cannot construct a #{type} resource without vm_id and zone" \
1298
- unless @vm_id && @zone
1299
- return {
1300
- 'instance_id' => @vm_id,
1301
- 'zone' => @zone,
1302
- 'cluster_name' =>
1303
- fetch_gce_metadata('instance/attributes/cluster-name')
1304
- }
1305
-
1306
- # Cloud Dataproc.
1307
- when DATAPROC_CONSTANTS[:resource_type]
1308
- return {
1309
- 'cluster_uuid' =>
1310
- fetch_gce_metadata('instance/attributes/dataproc-cluster-uuid'),
1311
- 'cluster_name' =>
1312
- fetch_gce_metadata('instance/attributes/dataproc-cluster-name'),
1313
- 'region' =>
1314
- fetch_gce_metadata('instance/attributes/dataproc-region')
1315
- }
1316
-
1317
- # EC2.
1318
- when EC2_CONSTANTS[:resource_type]
1319
- raise "Cannot construct a #{type} resource without vm_id and zone" \
1320
- unless @vm_id && @zone
1321
- labels = {
1322
- 'instance_id' => @vm_id,
1323
- 'region' => @zone
1324
- }
1325
- labels['aws_account'] = ec2_metadata['accountId'] if
1326
- ec2_metadata.key?('accountId')
1327
- return labels
1328
- end
1329
-
1330
- {}
1331
- rescue StandardError => e
1332
- if [Platform::GCE, Platform::EC2].include?(@platform)
1333
- @log.error "Failed to set monitored resource labels for #{type}: ",
1334
- error: e
1335
- end
1336
- {}
1337
- end
1338
-
1339
1044
  # Determine the common labels that should be added to all log entries
1340
1045
  # processed by this logging agent.
1341
- def determine_agent_level_common_labels
1046
+ def determine_agent_level_common_labels(resource)
1342
1047
  labels = {}
1343
1048
  # User can specify labels via config. We want to capture those as well.
1344
1049
  labels.merge!(@labels) if @labels
1345
1050
 
1346
- case @resource.type
1051
+ case resource.type
1347
1052
  # GAE, Cloud Dataflow, Cloud Dataproc and Cloud ML.
1348
1053
  when APPENGINE_CONSTANTS[:resource_type],
1349
1054
  DATAFLOW_CONSTANTS[:resource_type],
@@ -1548,44 +1253,6 @@ module Fluent
1548
1253
  [resource, common_labels]
1549
1254
  end
1550
1255
 
1551
- # TODO: This functionality should eventually be available in another
1552
- # library, but implement it ourselves for now.
1553
- module CredentialsInfo
1554
- # Determine the project ID from the credentials, if possible.
1555
- # Returns the project ID (as a string) on success, or nil on failure.
1556
- def self.project_id
1557
- creds = Google::Auth.get_application_default(LOGGING_SCOPE)
1558
- if creds.respond_to?(:project_id)
1559
- return creds.project_id if creds.project_id
1560
- end
1561
- if creds.issuer
1562
- id = extract_project_id(creds.issuer)
1563
- return id unless id.nil?
1564
- end
1565
- if creds.client_id
1566
- id = extract_project_id(creds.client_id)
1567
- return id unless id.nil?
1568
- end
1569
- nil
1570
- end
1571
-
1572
- # Extracts the project id (either name or number) from str and returns
1573
- # it (as a string) on success, or nil on failure.
1574
- #
1575
- # Recognizes IAM format (account@project-name.iam.gserviceaccount.com)
1576
- # as well as the legacy format with a project number at the front of the
1577
- # string, terminated by a dash (-) which is not part of the ID, i.e.:
1578
- # <PROJECT_ID>-<OTHER_PARTS>.apps.googleusercontent.com
1579
- def self.extract_project_id(str)
1580
- [/^.*@(?<project_id>.+)\.iam\.gserviceaccount\.com/,
1581
- /^(?<project_id>\d+)-/].each do |exp|
1582
- match_data = exp.match(str)
1583
- return match_data['project_id'] unless match_data.nil?
1584
- end
1585
- nil
1586
- end
1587
- end
1588
-
1589
1256
  def time_or_nil(ts_secs, ts_nanos)
1590
1257
  Time.at((Integer ts_secs), (Integer ts_nanos) / 1_000.0)
1591
1258
  rescue ArgumentError, TypeError
@@ -2081,7 +1748,7 @@ module Fluent
2081
1748
  Google::Apis::ClientOptions.default.application_version = PLUGIN_VERSION
2082
1749
  @client = Google::Apis::LoggingV2::LoggingService.new
2083
1750
  @client.authorization = Google::Auth.get_application_default(
2084
- LOGGING_SCOPE)
1751
+ Common::LOGGING_SCOPE)
2085
1752
  end
2086
1753
  end
2087
1754
 
@@ -2310,10 +1977,10 @@ module Fluent
2310
1977
  @k8s_cluster_location = nil if @k8s_cluster_location == ''
2311
1978
 
2312
1979
  begin
2313
- @k8s_cluster_name ||= fetch_gce_metadata(
2314
- 'instance/attributes/cluster-name')
2315
- @k8s_cluster_location ||= fetch_gce_metadata(
2316
- 'instance/attributes/cluster-location')
1980
+ @k8s_cluster_name ||= @utils.fetch_gce_metadata(
1981
+ @platform, 'instance/attributes/cluster-name')
1982
+ @k8s_cluster_location ||= @utils.fetch_gce_metadata(
1983
+ @platform, 'instance/attributes/cluster-location')
2317
1984
  rescue StandardError => e
2318
1985
  @log.error 'Failed to retrieve k8s cluster name and location.', \
2319
1986
  error: e
@@ -107,7 +107,7 @@ module BaseTest
107
107
 
108
108
  def test_configure_metadata_missing_parts_on_other_platforms
109
109
  setup_no_metadata_service_stubs
110
- Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
110
+ Common::Utils::CredentialsInfo.stubs(:project_id).returns(nil)
111
111
  [[CONFIG_MISSING_METADATA_PROJECT_ID, ['project_id'], false],
112
112
  [CONFIG_MISSING_METADATA_ZONE, [], true],
113
113
  [CONFIG_MISSING_METADATA_VM_ID, [], true],
@@ -45,7 +45,7 @@ end
45
45
 
46
46
  # Constants used by unit tests for Google Cloud Logging plugin.
47
47
  module Constants
48
- include Fluent::GoogleCloudOutput::ServiceConstants
48
+ include Common::ServiceConstants
49
49
  include Fluent::GoogleCloudOutput::ConfigConstants
50
50
  include Fluent::GoogleCloudOutput::InternalConstants
51
51
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-google-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stackdriver Agents Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-04 00:00:00.000000000 Z
11
+ date: 2020-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -266,6 +266,7 @@ files:
266
266
  - README.rdoc
267
267
  - Rakefile
268
268
  - fluent-plugin-google-cloud.gemspec
269
+ - lib/fluent/plugin/common.rb
269
270
  - lib/fluent/plugin/filter_add_insert_ids.rb
270
271
  - lib/fluent/plugin/filter_analyze_config.rb
271
272
  - lib/fluent/plugin/in_object_space_dump.rb