fluent-plugin-google-cloud 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.2.3)
4
+ fluent-plugin-google-cloud (0.2.4)
5
5
  fluentd (>= 0.10)
6
6
  google-api-client (>= 0.8)
7
+ googleauth (~> 0.4)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -64,10 +65,14 @@ GEM
64
65
  little-plugger (~> 1.1)
65
66
  multi_json (~> 1.10)
66
67
  memoist (0.12.0)
68
+ metaclass (0.0.4)
67
69
  minitest (5.6.1)
70
+ mocha (1.1.0)
71
+ metaclass (~> 0.0.1)
68
72
  msgpack (0.5.11)
69
73
  multi_json (1.11.0)
70
74
  multipart-post (2.0.0)
75
+ power_assert (0.2.3)
71
76
  rake (10.4.2)
72
77
  retriable (1.4.1)
73
78
  safe_yaml (1.0.4)
@@ -79,6 +84,8 @@ GEM
79
84
  jwt (~> 1.0)
80
85
  multi_json (~> 1.10)
81
86
  string-scrub (0.0.5)
87
+ test-unit (3.0.9)
88
+ power_assert
82
89
  thread_safe (0.3.5)
83
90
  tzinfo (1.2.2)
84
91
  thread_safe (~> 0.1)
@@ -94,5 +101,7 @@ PLATFORMS
94
101
 
95
102
  DEPENDENCIES
96
103
  fluent-plugin-google-cloud!
104
+ mocha (~> 1.1)
97
105
  rake (>= 10.3.2)
106
+ test-unit (~> 3.0.2)
98
107
  webmock (>= 1.17.0)
@@ -4,7 +4,7 @@ Gem::Specification.new do |gem|
4
4
  gem.summary = %q{Fluentd plugin to stream logs to the Google Cloud Platform's logging API}
5
5
  gem.homepage = 'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
6
6
  gem.license = 'Apache 2.0'
7
- gem.version = '0.2.3'
7
+ gem.version = '0.2.4'
8
8
  gem.authors = ['Todd Derr', 'Alex Robinson']
9
9
  gem.email = ['salty@google.com']
10
10
 
@@ -14,6 +14,9 @@ Gem::Specification.new do |gem|
14
14
 
15
15
  gem.add_runtime_dependency 'fluentd', '>= 0.10'
16
16
  gem.add_runtime_dependency 'google-api-client', '>= 0.8'
17
+ gem.add_runtime_dependency 'googleauth', '~> 0.4'
17
18
  gem.add_development_dependency "rake", '>= 10.3.2'
18
19
  gem.add_development_dependency "webmock", '>= 1.17.0'
20
+ gem.add_development_dependency "test-unit", "~> 3.0.2"
21
+ gem.add_development_dependency "mocha", "~> 1.1"
19
22
  end
@@ -21,21 +21,29 @@ module Fluent
21
21
  COMPUTE_SERVICE = 'compute.googleapis.com'
22
22
  DATAFLOW_SERVICE = 'dataflow.googleapis.com'
23
23
 
24
- # Legal values:
25
- # 'compute_engine_service_account' - Use the service account automatically
26
- # available on Google Compute Engine VMs. Note that this requires that
27
- # the logs.writeonly API scope is enabled on the VM, and scopes can
28
- # only be enabled at the time that a VM is created.
29
- # 'private_key' - Use the service account credentials (email, private key
30
- # local file path, and file passphrase) provided below.
31
- config_param :auth_method, :string,
32
- :default => 'compute_engine_service_account'
33
-
34
- # Parameters necessary to use the private_key auth_method.
24
+ # Name of the the Google cloud logging write scope.
25
+ LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
26
+
27
+ # DEPRECATED: auth_method (and support for 'private_key') is deprecated in
28
+ # favor of Google Application Default Credentials as documented at:
29
+ # https://developers.google.com/identity/protocols/application-default-credentials
30
+ # 'private_key' is still accepted to support existing users; any other
31
+ # value is ignored.
32
+ config_param :auth_method, :string, :default => nil
33
+
34
+ # DEPRECATED: Parameters necessary to use the private_key auth_method.
35
35
  config_param :private_key_email, :string, :default => nil
36
36
  config_param :private_key_path, :string, :default => nil
37
37
  config_param :private_key_passphrase, :string, :default => 'notasecret'
38
38
 
39
+ # If fetch_gce_metadata is set to true, we obtain the project_id, zone,
40
+ # and vm_id from the GCE metadata service. Otherwise, those parameters
41
+ # must be specified in the config file explicitly.
42
+ config_param :fetch_gce_metadata, :bool, :default => true
43
+ config_param :project_id, :string, :default => nil
44
+ config_param :zone, :string, :default => nil
45
+ config_param :vm_id, :string, :default => nil
46
+
39
47
  # TODO: Add a log_name config option rather than just using the tag?
40
48
 
41
49
  # Expose attr_readers to make testing of metadata more direct than only
@@ -54,30 +62,35 @@ module Fluent
54
62
  require 'cgi'
55
63
  require 'google/api_client'
56
64
  require 'google/api_client/auth/compute_service_account'
65
+ require 'googleauth'
57
66
  require 'open-uri'
58
67
  end
59
68
 
60
69
  def configure(conf)
61
70
  super
62
71
 
63
- case @auth_method
64
- when 'private_key'
65
- if !@private_key_email
66
- raise Fluent::ConfigError, ('"private_key_email" must be ' +
67
- 'specified if auth_method is "private_key"')
68
- elsif !@private_key_path
69
- raise Fluent::ConfigError, ('"private_key_path" must be ' +
70
- 'specified if auth_method is "private_key"')
71
- elsif !@private_key_passphrase
72
- raise Fluent::ConfigError, ('"private_key_passphrase" must be ' +
73
- 'specified if auth_method is "private_key"')
72
+ if !@auth_method.nil?
73
+ $log.warn ('auth_method is deprecated; please migrate to using ' +
74
+ 'Application Default Credentials.')
75
+ if @auth_method == 'private_key'
76
+ if !@private_key_email
77
+ raise Fluent::ConfigError, ('"private_key_email" must be ' +
78
+ 'specified if auth_method is "private_key"')
79
+ elsif !@private_key_path
80
+ raise Fluent::ConfigError, ('"private_key_path" must be ' +
81
+ 'specified if auth_method is "private_key"')
82
+ elsif !@private_key_passphrase
83
+ raise Fluent::ConfigError, ('"private_key_passphrase" must be ' +
84
+ 'specified if auth_method is "private_key"')
85
+ end
86
+ end
87
+ end
88
+
89
+ unless @fetch_gce_metadata
90
+ unless @project_id && @zone && @vm_id
91
+ raise Fluent::ConfigError,
92
+ ('Please specify "project_id", "zone" and "vm_id" if you set "fetch_gce_metadata" to false')
74
93
  end
75
- when 'compute_engine_service_account'
76
- # pass
77
- else
78
- raise Fluent::ConfigError,
79
- ('Unrecognized "auth_method" parameter. Please specify either ' +
80
- '"compute_engine_service_account" or "private_key".')
81
94
  end
82
95
  end
83
96
 
@@ -88,18 +101,21 @@ module Fluent
88
101
 
89
102
  @successful_call = false
90
103
 
91
- # Grab metadata about the Google Compute Engine instance that we're on.
92
- @project_id = fetch_metadata('project/project-id')
93
- fully_qualified_zone = fetch_metadata('instance/zone')
94
- @zone = fully_qualified_zone.rpartition('/')[2]
95
- @vm_id = fetch_metadata('instance/id')
104
+ if @fetch_gce_metadata
105
+ # Grab metadata about the Google Compute Engine instance that we're on.
106
+ @project_id = fetch_metadata('project/project-id')
107
+ fully_qualified_zone = fetch_metadata('instance/zone')
108
+ @zone = fully_qualified_zone.rpartition('/')[2]
109
+ @vm_id = fetch_metadata('instance/id')
110
+ end
96
111
  # TODO: Send instance tags and/or hostname with the logs as well?
97
112
  @common_labels = {}
98
113
 
99
114
  # If this is running on a Managed VM, grab the relevant App Engine
100
115
  # metadata as well.
101
116
  # TODO: Add config options for these to allow for running outside GCE?
102
- attributes_string = fetch_metadata('instance/attributes/')
117
+ attributes_string = @fetch_gce_metadata ?
118
+ fetch_metadata('instance/attributes/') : ""
103
119
  attributes = attributes_string.split
104
120
  if (attributes.include?('gae_backend_name') &&
105
121
  attributes.include?('gae_backend_version'))
@@ -319,19 +335,19 @@ module Fluent
319
335
  def init_api_client
320
336
  @client = Google::APIClient.new(
321
337
  :application_name => 'Fluentd Google Cloud Logging plugin',
322
- :application_version => '0.2.3',
338
+ :application_version => '0.2.4',
323
339
  :retries => 1)
324
340
 
325
341
  if @auth_method == 'private_key'
326
342
  key = Google::APIClient::PKCS12.load_key(@private_key_path,
327
343
  @private_key_passphrase)
328
344
  jwt_asserter = Google::APIClient::JWTAsserter.new(
329
- @private_key_email, 'https://www.googleapis.com/auth/logging.write',
330
- key)
345
+ @private_key_email, LOGGING_SCOPE, key)
331
346
  @client.authorization = jwt_asserter.to_authorization
332
347
  @client.authorization.expiry = 3600 # 3600s is the max allowed value
333
348
  else
334
- @client.authorization = Google::APIClient::ComputeServiceAccount.new
349
+ @client.authorization = Google::Auth.get_application_default(
350
+ LOGGING_SCOPE)
335
351
  end
336
352
  end
337
353
 
@@ -0,0 +1,8 @@
1
+ {
2
+ "private_key_id": "cbedb7568906086cab57859bbfc1748749cc46c4",
3
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEcAASCAmEwggJdAgEAAoGBAKizy6B+aJ0Wua0e\njZ3pkHV0a2Ce1prJGhzGL5NpkbUjk6J11Kwp1yvPikTwALyy4PtUIZ+23D/unVRM\nHlKa2MkHIGjJg+mykX5Bd7eRJOxdJ0iu+eRWh7HiH+mdDntHwaz4xXihJBog71qS\n+9N+r2hy1hicybechchMiXHhmWPbAgMBAAECgYEAnSzeI4qCZxEcLtnPcXeBWpz7\nycpTAWUpycMvsjTiRxR9YRhM65YT3cJ//VhqJ2S1ThOcPCt/KqViuX4tpiKUo7qA\nH1AI9APbTo66wiGpgy+qG0wPJkKIQC8PpITNNcHqcbbAsIr3/XQduihsqxP2W2mT\na0nk5XJghs1Wa0xt28ECQQDgMqZjVDcDQyqM+bcBKJUUc/247KusjpdK70r6sx2o\nkZJGy/w9exlM5QrB6DLpw34/p5x4MoecZ7lS3yHdmaEhAkEAwKHsV4k5SXTUp4+J\nWK6GlQVvnwc+PQdX5gzt4/gWSY0Op5EQ+YD6cC7Lkz+GzXUzvmdp35c0ahS93D1/\nZLTZewJBAIjOc3cHMNadyr5BtulPEUE0ro+EY/GlBS8lu/QlDmkJg2AOI3qEvliM\nvza58S9yKny/U5yJAPVw2cZ3ABxQHeECQDyBX8PrBURuXvE2o5RoVTtvlqziAi3X\nJaPLwdkOLqnxlX3KkgNcoM0l1amtlYDpZcRVcSs0+9TqKOyJoH8YUwsCQA4cJmv3\n119xcijXPM2HZOB5cCxTHj59MRtQlLboNZ2witDCJ20eG9AC3ZcH7csS0H9dz8Jr\nXGEoQMPD2ck4T0U\u003d\n-----END PRIVATE KEY-----\n",
4
+ "client_email": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl@developer.gserviceaccount.com",
5
+ "client_id": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl.apps.googleusercontent.com",
6
+ "type": "service_account"
7
+ }
8
+
@@ -0,0 +1,8 @@
1
+ {
2
+ "private_key_id": "cbedb7568906086cab57859bbfc1748749cc46c4",
3
+ "private_key": "-----BEGIN PRIVATE KEY-----\nCeci n'est pas une cle\n-----END PRIVATE KEY-----\n",
4
+ "client_email": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl@developer.gserviceaccount.com",
5
+ "client_id": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl.apps.googleusercontent.com",
6
+ "type": "service_account"
7
+ }
8
+
@@ -14,50 +14,34 @@
14
14
 
15
15
  require 'helper'
16
16
  require 'json'
17
+ require 'mocha/test_unit'
17
18
  require 'webmock/test_unit'
18
19
 
19
20
  class GoogleCloudOutputTest < Test::Unit::TestCase
20
21
  def setup
21
22
  Fluent::Test.setup
22
-
23
- # Create stubs for all the GCE metadata lookups the agent needs to make.
24
- stub_metadata_request('project/project-id', PROJECT_ID)
25
- stub_metadata_request('instance/zone', FULLY_QUALIFIED_ZONE)
26
- stub_metadata_request('instance/id', VM_ID)
27
- stub_metadata_request('instance/attributes/',
28
- "attribute1\nattribute2\nattribute3")
29
-
30
- stub_request(:post, 'https://accounts.google.com/o/oauth2/token').
31
- with(:body => hash_including({:grant_type => AUTH_GRANT_TYPE})).
32
- to_return(:body => "{\"access_token\": \"#{FAKE_AUTH_TOKEN}\"}",
33
- :status => 200,
34
- :headers => {'Content-Length' => FAKE_AUTH_TOKEN,
35
- 'Content-Type' => 'application/json' })
36
-
23
+ ENV.delete('GOOGLE_APPLICATION_CREDENTIALS')
24
+ setup_auth_stubs
37
25
  @logs_sent = []
38
26
  end
39
27
 
40
- def setup_logging_stubs
41
- [COMPUTE_PARAMS, VMENGINE_PARAMS].each do |params|
42
- stub_request(:post, uri_for_log(params)).to_return do |request|
43
- @logs_sent << JSON.parse(request.body)
44
- {:body => ''}
45
- end
46
- end
47
- end
48
-
49
28
  PROJECT_ID = 'test-project-id'
50
29
  ZONE = 'us-central1-b'
51
30
  FULLY_QUALIFIED_ZONE = 'projects/' + PROJECT_ID + '/zones/' + ZONE
52
31
  VM_ID = '9876543210'
53
32
 
33
+ CUSTOM_PROJECT_ID = 'test-custom-project-id'
34
+ CUSTOM_ZONE = 'us-custom-central1-b'
35
+ CUSTOM_FULLY_QUALIFIED_ZONE = 'projects/' + PROJECT_ID + '/zones/' + ZONE
36
+ CUSTOM_VM_ID = 'C9876543210'
37
+
54
38
  MANAGED_VM_BACKEND_NAME = 'default'
55
39
  MANAGED_VM_BACKEND_VERSION = 'guestbook2.0'
56
40
 
57
41
  AUTH_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
58
42
  FAKE_AUTH_TOKEN = 'abc123'
59
43
 
60
- COMPUTE_ENGINE_SERVICE_ACCOUNT_CONFIG = %[
44
+ APPLICATION_DEFAULT_CONFIG = %[
61
45
  ]
62
46
 
63
47
  PRIVATE_KEY_CONFIG = %[
@@ -66,16 +50,25 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
66
50
  private_key_path test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
67
51
  ]
68
52
 
69
- INVALID_CONFIG1 = %[
53
+ CUSTOM_METADATA_CONFIG = %[
54
+ fetch_gce_metadata false
55
+ project_id #{CUSTOM_PROJECT_ID}
56
+ zone #{CUSTOM_ZONE}
57
+ vm_id #{CUSTOM_VM_ID}
58
+ ]
59
+
60
+ INVALID_CONFIG_MISSING_PRIVATE_KEY_PATH = %[
70
61
  auth_method private_key
71
62
  private_key_email nobody@example.com
72
63
  ]
73
- INVALID_CONFIG2 = %[
64
+ INVALID_CONFIG_MISSING_PRIVATE_KEY_EMAIL = %[
74
65
  auth_method private_key
75
66
  private_key_path /fake/path/to/key
76
67
  ]
77
- INVALID_CONFIG3 = %[
78
- auth_method service_account
68
+ INVALID_CONFIG_MISSING_METADATA_VM_ID = %[
69
+ fetch_gce_metadata false
70
+ project_id #{CUSTOM_PROJECT_ID}
71
+ zone #{CUSTOM_ZONE}
79
72
  ]
80
73
 
81
74
  COMPUTE_SERVICE_NAME = 'compute.googleapis.com'
@@ -84,6 +77,8 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
84
77
  COMPUTE_PARAMS = {
85
78
  'service_name' => COMPUTE_SERVICE_NAME,
86
79
  'log_name' => 'test',
80
+ 'project_id' => PROJECT_ID,
81
+ 'zone' => ZONE,
87
82
  'labels' => {
88
83
  "#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
89
84
  "#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID
@@ -93,6 +88,8 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
93
88
  VMENGINE_PARAMS = {
94
89
  'service_name' => APPENGINE_SERVICE_NAME,
95
90
  'log_name' => "#{APPENGINE_SERVICE_NAME}%2Ftest",
91
+ 'project_id' => PROJECT_ID,
92
+ 'zone' => ZONE,
96
93
  'labels' => {
97
94
  "#{APPENGINE_SERVICE_NAME}/module_id" => MANAGED_VM_BACKEND_NAME,
98
95
  "#{APPENGINE_SERVICE_NAME}/version_id" => MANAGED_VM_BACKEND_VERSION,
@@ -101,44 +98,73 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
101
98
  }
102
99
  }
103
100
 
104
- def create_driver(conf=PRIVATE_KEY_CONFIG)
101
+ CUSTOM_PARAMS = {
102
+ 'service_name' => COMPUTE_SERVICE_NAME,
103
+ 'log_name' => 'test',
104
+ 'project_id' => CUSTOM_PROJECT_ID,
105
+ 'zone' => CUSTOM_ZONE,
106
+ 'labels' => {
107
+ "#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
108
+ "#{COMPUTE_SERVICE_NAME}/resource_id" => CUSTOM_VM_ID
109
+ }
110
+ }
111
+
112
+ def create_driver(conf=APPLICATION_DEFAULT_CONFIG)
105
113
  Fluent::Test::BufferedOutputTestDriver.new(
106
114
  Fluent::GoogleCloudOutput).configure(conf)
107
115
  end
108
116
 
109
- def test_configure_service_account
110
- d = create_driver(COMPUTE_ENGINE_SERVICE_ACCOUNT_CONFIG)
111
- assert_equal 'compute_engine_service_account', d.instance.auth_method
117
+ def test_configure_service_account_application_default
118
+ setup_gce_metadata_stubs
119
+ d = create_driver(APPLICATION_DEFAULT_CONFIG)
120
+ assert d.instance.auth_method.nil?
112
121
  end
113
122
 
114
- def test_configure_service_account
123
+ def test_configure_service_account_private_key
124
+ setup_gce_metadata_stubs
115
125
  d = create_driver(PRIVATE_KEY_CONFIG)
116
126
  assert_equal 'private_key', d.instance.auth_method
117
127
  end
118
128
 
129
+ def test_configure_custom_metadata
130
+ d = create_driver(CUSTOM_METADATA_CONFIG)
131
+ assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
132
+ assert_equal CUSTOM_ZONE, d.instance.zone
133
+ assert_equal CUSTOM_VM_ID, d.instance.vm_id
134
+ end
135
+
119
136
  def test_configure_invalid_configs
137
+ exception_count = 0
120
138
  begin
121
- d = create_driver(INVALID_CONFIG1)
122
- assert false
139
+ d = create_driver(INVALID_CONFIG_MISSING_PRIVATE_KEY_PATH)
123
140
  rescue Fluent::ConfigError => error
124
141
  assert error.message.include? 'private_key_path'
142
+ exception_count += 1
125
143
  end
144
+ assert_equal 1, exception_count
145
+
146
+ exception_count = 0
126
147
  begin
127
- d = create_driver(INVALID_CONFIG2)
128
- assert false
148
+ d = create_driver(INVALID_CONFIG_MISSING_PRIVATE_KEY_EMAIL)
129
149
  rescue Fluent::ConfigError => error
130
150
  assert error.message.include? 'private_key_email'
151
+ exception_count += 1
131
152
  end
153
+ assert_equal 1, exception_count
154
+
155
+ exception_count = 0
132
156
  begin
133
- d = create_driver(INVALID_CONFIG3)
134
- assert false
157
+ d = create_driver(INVALID_CONFIG_MISSING_METADATA_VM_ID)
135
158
  rescue Fluent::ConfigError => error
136
- assert error.message.include? 'auth_method'
159
+ assert error.message.include? 'fetch_gce_metadata'
160
+ exception_count += 1
137
161
  end
162
+ assert_equal 1, exception_count
138
163
  end
139
164
 
140
165
  def test_metadata_loading
141
- d = create_driver(PRIVATE_KEY_CONFIG)
166
+ setup_gce_metadata_stubs
167
+ d = create_driver()
142
168
  d.run
143
169
  assert_equal PROJECT_ID, d.instance.project_id
144
170
  assert_equal ZONE, d.instance.zone
@@ -147,8 +173,9 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
147
173
  end
148
174
 
149
175
  def test_managed_vm_metadata_loading
176
+ setup_gce_metadata_stubs
150
177
  setup_managed_vm_metadata_stubs
151
- d = create_driver(PRIVATE_KEY_CONFIG)
178
+ d = create_driver()
152
179
  d.run
153
180
  assert_equal PROJECT_ID, d.instance.project_id
154
181
  assert_equal ZONE, d.instance.zone
@@ -158,7 +185,53 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
158
185
  assert_equal MANAGED_VM_BACKEND_VERSION, d.instance.gae_backend_version
159
186
  end
160
187
 
188
+ def test_gce_metadata_does_not_load_when_fetch_gce_metadata_is_false
189
+ Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
190
+ d = create_driver(CUSTOM_METADATA_CONFIG)
191
+ d.run
192
+ assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
193
+ assert_equal CUSTOM_ZONE, d.instance.zone
194
+ assert_equal CUSTOM_VM_ID, d.instance.vm_id
195
+ assert_equal false, d.instance.running_on_managed_vm
196
+ end
197
+
161
198
  def test_one_log
199
+ setup_gce_metadata_stubs
200
+ setup_logging_stubs
201
+ d = create_driver()
202
+ d.emit({'message' => log_entry(0)})
203
+ d.run
204
+ verify_log_entries(1, COMPUTE_PARAMS)
205
+ end
206
+
207
+ def test_one_log_with_json_credentials
208
+ setup_gce_metadata_stubs
209
+ setup_logging_stubs
210
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = 'test/plugin/data/credentials.json'
211
+ d = create_driver()
212
+ d.emit({'message' => log_entry(0)})
213
+ d.run
214
+ verify_log_entries(1, COMPUTE_PARAMS)
215
+ end
216
+
217
+ def test_one_log_with_invalid_json_credentials
218
+ setup_gce_metadata_stubs
219
+ setup_logging_stubs
220
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = 'test/plugin/data/invalid_credentials.json'
221
+ d = create_driver()
222
+ d.emit({'message' => log_entry(0)})
223
+ exception_count = 0
224
+ begin
225
+ d.run
226
+ rescue RuntimeError => error
227
+ assert error.message.include? 'Unable to read the credential file'
228
+ exception_count += 1
229
+ end
230
+ assert_equal 1, exception_count
231
+ end
232
+
233
+ def test_one_log_private_key
234
+ setup_gce_metadata_stubs
162
235
  setup_logging_stubs
163
236
  d = create_driver(PRIVATE_KEY_CONFIG)
164
237
  d.emit({'message' => log_entry(0)})
@@ -166,9 +239,20 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
166
239
  verify_log_entries(1, COMPUTE_PARAMS)
167
240
  end
168
241
 
242
+ def test_one_log_custom_metadata
243
+ Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
244
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = 'test/plugin/data/credentials.json'
245
+ setup_logging_stubs
246
+ d = create_driver(CUSTOM_METADATA_CONFIG)
247
+ d.emit({'message' => log_entry(0)})
248
+ d.run
249
+ verify_log_entries(1, CUSTOM_PARAMS)
250
+ end
251
+
169
252
  def test_struct_payload_log
253
+ setup_gce_metadata_stubs
170
254
  setup_logging_stubs
171
- d = create_driver(PRIVATE_KEY_CONFIG)
255
+ d = create_driver()
172
256
  d.emit({'msg' => log_entry(0), 'tag2' => 'test', 'data' => 5000})
173
257
  d.run
174
258
  verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
@@ -180,8 +264,9 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
180
264
  end
181
265
 
182
266
  def test_timestamps
267
+ setup_gce_metadata_stubs
183
268
  setup_logging_stubs
184
- d = create_driver(PRIVATE_KEY_CONFIG)
269
+ d = create_driver()
185
270
  expected_ts = []
186
271
  emit_index = 0
187
272
  [Time.at(123456.789), Time.at(0), Time.now].each do |ts|
@@ -208,8 +293,9 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
208
293
  end
209
294
 
210
295
  def test_severities
296
+ setup_gce_metadata_stubs
211
297
  setup_logging_stubs
212
- d = create_driver(PRIVATE_KEY_CONFIG)
298
+ d = create_driver()
213
299
  expected_severity = []
214
300
  emit_index = 0
215
301
  # Array of pairs of [parsed_severity, expected_severity]
@@ -229,8 +315,9 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
229
315
  end
230
316
 
231
317
  def test_multiple_logs
318
+ setup_gce_metadata_stubs
232
319
  setup_logging_stubs
233
- d = create_driver(PRIVATE_KEY_CONFIG)
320
+ d = create_driver()
234
321
  # Only test a few values because otherwise the test can take minutes.
235
322
  [2, 3, 5, 11, 50].each do |n|
236
323
  # The test driver doesn't clear its buffer of entries after running, so
@@ -244,11 +331,12 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
244
331
  end
245
332
 
246
333
  def test_client_error
334
+ setup_gce_metadata_stubs
247
335
  # The API Client should not retry this and the plugin should consume
248
336
  # the exception.
249
337
  stub_request(:post, uri_for_log(COMPUTE_PARAMS)).to_return(
250
338
  :status => 400, :body => "Bad Request")
251
- d = create_driver(PRIVATE_KEY_CONFIG)
339
+ d = create_driver()
252
340
  d.emit({'message' => log_entry(0)})
253
341
  d.run
254
342
  assert_requested(:post, uri_for_log(COMPUTE_PARAMS), :times => 1)
@@ -256,9 +344,10 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
256
344
 
257
345
  # helper for the ClientError retriable special cases below.
258
346
  def client_error_helper(message)
347
+ setup_gce_metadata_stubs
259
348
  stub_request(:post, uri_for_log(COMPUTE_PARAMS)).to_return(
260
349
  :status => 401, :body => message)
261
- d = create_driver(PRIVATE_KEY_CONFIG)
350
+ d = create_driver()
262
351
  d.emit({'message' => log_entry(0)})
263
352
  exception_count = 0
264
353
  begin
@@ -292,11 +381,12 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
292
381
  end
293
382
 
294
383
  def test_server_error
384
+ setup_gce_metadata_stubs
295
385
  # The API client should retry this once, then throw an exception which
296
386
  # gets propagated through the plugin.
297
387
  stub_request(:post, uri_for_log(COMPUTE_PARAMS)).to_return(
298
388
  :status => 500, :body => "Server Error")
299
- d = create_driver(PRIVATE_KEY_CONFIG)
389
+ d = create_driver()
300
390
  d.emit({'message' => log_entry(0)})
301
391
  exception_count = 0
302
392
  begin
@@ -310,18 +400,20 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
310
400
  end
311
401
 
312
402
  def test_one_managed_vm_log
403
+ setup_gce_metadata_stubs
313
404
  setup_managed_vm_metadata_stubs
314
405
  setup_logging_stubs
315
- d = create_driver(PRIVATE_KEY_CONFIG)
406
+ d = create_driver()
316
407
  d.emit({'message' => log_entry(0)})
317
408
  d.run
318
409
  verify_log_entries(1, VMENGINE_PARAMS)
319
410
  end
320
411
 
321
412
  def test_multiple_managed_vm_logs
413
+ setup_gce_metadata_stubs
322
414
  setup_managed_vm_metadata_stubs
323
415
  setup_logging_stubs
324
- d = create_driver(PRIVATE_KEY_CONFIG)
416
+ d = create_driver()
325
417
  [2, 3, 5, 11, 50].each do |n|
326
418
  # The test driver doesn't clear its buffer of entries after running, so
327
419
  # do it manually here.
@@ -414,7 +506,7 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
414
506
  private
415
507
 
416
508
  def uri_for_log(config)
417
- 'https://logging.googleapis.com/v1beta3/projects/' + PROJECT_ID +
509
+ 'https://logging.googleapis.com/v1beta3/projects/' + config['project_id'] +
418
510
  '/logs/' + config['log_name'] + '/entries:write'
419
511
  end
420
512
 
@@ -424,6 +516,53 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
424
516
  :headers => {'Content-Length' => response_body.length})
425
517
  end
426
518
 
519
+ def setup_gce_metadata_stubs
520
+ # Create stubs for all the GCE metadata lookups the agent needs to make.
521
+ stub_metadata_request('project/project-id', PROJECT_ID)
522
+ stub_metadata_request('instance/zone', FULLY_QUALIFIED_ZONE)
523
+ stub_metadata_request('instance/id', VM_ID)
524
+ stub_metadata_request('instance/attributes/',
525
+ "attribute1\nattribute2\nattribute3")
526
+
527
+ # Used by 'googleauth' to test whether we're running on GCE.
528
+ # It only cares about the request succeeding with Metdata-Flavor: Google.
529
+ stub_request(:get, 'http://169.254.169.254').
530
+ to_return(:status => 200, :headers => {'Metadata-Flavor' => 'Google'})
531
+
532
+ # Used by 'googleauth' to fetch the default service account credentials.
533
+ stub_request(:get, 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token').
534
+ to_return(:body => "{\"access_token\": \"#{FAKE_AUTH_TOKEN}\"}",
535
+ :status => 200,
536
+ :headers => {'Content-Length' => FAKE_AUTH_TOKEN.length,
537
+ 'Content-Type' => 'application/json' })
538
+ end
539
+
540
+ def setup_logging_stubs
541
+ [COMPUTE_PARAMS, VMENGINE_PARAMS, CUSTOM_PARAMS].each do |params|
542
+ stub_request(:post, uri_for_log(params)).to_return do |request|
543
+ @logs_sent << JSON.parse(request.body)
544
+ {:body => ''}
545
+ end
546
+ end
547
+ end
548
+
549
+ def setup_auth_stubs
550
+ # Used when loading credentials from a JSON file.
551
+ stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token').
552
+ with(:body => hash_including({:grant_type => AUTH_GRANT_TYPE})).
553
+ to_return(:body => "{\"access_token\": \"#{FAKE_AUTH_TOKEN}\"}",
554
+ :status => 200,
555
+ :headers => {'Content-Length' => FAKE_AUTH_TOKEN.length,
556
+ 'Content-Type' => 'application/json' })
557
+ # Used for 'private_key' auth.
558
+ stub_request(:post, 'https://accounts.google.com/o/oauth2/token').
559
+ with(:body => hash_including({:grant_type => AUTH_GRANT_TYPE})).
560
+ to_return(:body => "{\"access_token\": \"#{FAKE_AUTH_TOKEN}\"}",
561
+ :status => 200,
562
+ :headers => {'Content-Length' => FAKE_AUTH_TOKEN.length,
563
+ 'Content-Type' => 'application/json' })
564
+ end
565
+
427
566
  def setup_managed_vm_metadata_stubs
428
567
  stub_metadata_request(
429
568
  'instance/attributes/',
@@ -462,7 +601,7 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
462
601
  assert_equal "test log entry #{i}", entry['textPayload'], batch
463
602
  end
464
603
 
465
- assert_equal ZONE, entry['metadata']['zone']
604
+ assert_equal params['zone'], entry['metadata']['zone']
466
605
  assert_equal params['service_name'], entry['metadata']['serviceName']
467
606
  check_labels entry, batch['commonLabels'], params['labels']
468
607
  if (block_given?)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-google-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -44,6 +44,22 @@ dependencies:
44
44
  - - ! '>='
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0.8'
47
+ - !ruby/object:Gem::Dependency
48
+ name: googleauth
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.4'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '0.4'
47
63
  - !ruby/object:Gem::Dependency
48
64
  name: rake
49
65
  requirement: !ruby/object:Gem::Requirement
@@ -76,6 +92,38 @@ dependencies:
76
92
  - - ! '>='
77
93
  - !ruby/object:Gem::Version
78
94
  version: 1.17.0
95
+ - !ruby/object:Gem::Dependency
96
+ name: test-unit
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: 3.0.2
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 3.0.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: mocha
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ~>
117
+ - !ruby/object:Gem::Version
118
+ version: '1.1'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ~>
125
+ - !ruby/object:Gem::Version
126
+ version: '1.1'
79
127
  description: Fluentd plugin to stream logs to the Google Cloud Platform's logging
80
128
  API, which will make them viewable in the Developer Console's log viewer and can
81
129
  optionally store them in Google Cloud Storage and/or BigQuery. This is an official
@@ -86,17 +134,19 @@ executables: []
86
134
  extensions: []
87
135
  extra_rdoc_files: []
88
136
  files:
89
- - lib/fluent/plugin/out_google_cloud.rb
90
- - Gemfile.lock
91
- - CONTRIBUTING
92
- - README.rdoc
93
137
  - test/helper.rb
94
- - test/plugin/test_out_google_cloud.rb
95
138
  - test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
139
+ - test/plugin/data/credentials.json
140
+ - test/plugin/data/invalid_credentials.json
141
+ - test/plugin/test_out_google_cloud.rb
96
142
  - LICENSE
97
143
  - Rakefile
98
- - Gemfile
99
144
  - fluent-plugin-google-cloud.gemspec
145
+ - lib/fluent/plugin/out_google_cloud.rb
146
+ - CONTRIBUTING
147
+ - Gemfile.lock
148
+ - Gemfile
149
+ - README.rdoc
100
150
  homepage: https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud
101
151
  licenses:
102
152
  - Apache 2.0
@@ -124,5 +174,7 @@ specification_version: 3
124
174
  summary: Fluentd plugin to stream logs to the Google Cloud Platform's logging API
125
175
  test_files:
126
176
  - test/helper.rb
127
- - test/plugin/test_out_google_cloud.rb
128
177
  - test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
178
+ - test/plugin/data/credentials.json
179
+ - test/plugin/data/invalid_credentials.json
180
+ - test/plugin/test_out_google_cloud.rb