fluent-plugin-google-cloud 0.8.6 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
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