fluent-plugin-azure-logs-ingestion 0.1.0 → 0.1.2

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: e5482480b26a287f04f3869ea12ddbe976dac865aa507f43538a99fcfc060c51
4
- data.tar.gz: 171a565a9d11e5a020e254682540a85adafa8b90eb46e2732819b1a63f67dab6
3
+ metadata.gz: 2d502da5879c0445acbdf411a24c2af70b7ad9016eb922474eda10f342d919c4
4
+ data.tar.gz: 2fee3972f2418f56c1cf5823bec48dc0abebe4e8738e108a7f727dca5e4d5b01
5
5
  SHA512:
6
- metadata.gz: 26314140847581088a5b2fa71e0fb4abeff7de0fae64ce31afcab42e40c5298d93df43def1809b1f0faf442075e57ebb4e796bd78a95989409550ead49da0037
7
- data.tar.gz: 5006e5ddbeff5b09a2a2f6d7e79a68e78d1a7912ccf8e390e6aa997c71f97f151a6e0fefeec6b295daae0bbf014f3c361d6bc4c901c3886be0da33a049700da5
6
+ metadata.gz: 1e64e5b98b5dde247d1a73b9bb437114d87ca39caf79f5bc1dff20264e7980b12d09cfd0e9b8ee748516f9e4e1cccb8c35a5103f281e7fad93454754e348861a
7
+ data.tar.gz: a48c818655f21c3292fbd5e36327df8870e001935f878fcdd76cae804fb5a2341bac47cbe6647f3f2e6830c41e0cb80630b77d0ba78f0e8bfeb51a3a340a21dd
data/Gemfile CHANGED
@@ -4,5 +4,8 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
+ gem 'fluentd', ENV['FLUENTD_VERSION'] if ENV['FLUENTD_VERSION']
8
+ gem 'csv', '< 3.2' if ENV['FLUENTD_VERSION'] && Gem::Version.new(ENV['FLUENTD_VERSION']) < Gem::Version.new('1.18')
9
+
7
10
  gem 'rake'
8
11
  gem 'test-unit'
data/README.md CHANGED
@@ -5,6 +5,11 @@ Fluentd output plugin that sends records to Log Analytics Workspace tables by us
5
5
  > [!WARNING]
6
6
  > This plugin is experimental and has not yet been sufficiently proven in serious production workloads.
7
7
 
8
+ ## Supported Environment
9
+
10
+ - Ruby 2.4 or later
11
+ - Fluentd 1.13.0 or later, before 2.0
12
+
8
13
  ## Installation
9
14
 
10
15
  ### RubyGems
@@ -30,7 +35,7 @@ Then run `bundle install`.
30
35
  Bundler can point directly at the GitHub repository. Specify `ref` when you want to pin a specific revision.
31
36
 
32
37
  ```ruby
33
- gem 'fluent-plugin-azure-logs-ingestion', git: 'https://github.com/fukasawah/fluent-plugin-azure-logs-ingestion.git', ref: 'abda3b5370ccd61282c8b234ca05042049e09d15'
38
+ gem 'fluent-plugin-azure-logs-ingestion', git: 'https://github.com/fukasawah/fluent-plugin-azure-logs-ingestion.git', ref: 'dbb0160a8048957dafb3002799dc3e8567da06e6'
34
39
  ```
35
40
 
36
41
  Then run `bundle install`.
@@ -207,3 +212,37 @@ The `time` in `<buffer time>` is the Fluentd event time, not a `time` field insi
207
212
  bundle install
208
213
  bundle exec rake test
209
214
  ```
215
+
216
+ This project uses a single `Gemfile`. Use rbenv and `FLUENTD_VERSION` when checking older Ruby / Fluentd combinations locally.
217
+
218
+ ```bash
219
+ rbenv init
220
+ source ~/.bashrc
221
+
222
+ (
223
+ export RBENV_VERSION=2.4.10 FLUENTD_VERSION=1.13.0
224
+ unset GEM_HOME GEM_PATH MY_RUBY_HOME
225
+ rbenv install "$RBENV_VERSION" -s
226
+ rbenv exec gem install bundler -v 2.3.27 --no-document
227
+ rbenv exec bundle _2.3.27_ install
228
+ rbenv exec bundle _2.3.27_ exec rake test
229
+ )
230
+
231
+ (
232
+ export RBENV_VERSION=2.7.8 FLUENTD_VERSION=1.18.0
233
+ unset GEM_HOME GEM_PATH MY_RUBY_HOME
234
+ rbenv install "$RBENV_VERSION" -s
235
+ rbenv exec gem install bundler -v 2.3.27 --no-document
236
+ rbenv exec bundle _2.3.27_ install
237
+ rbenv exec bundle _2.3.27_ exec rake test
238
+ )
239
+ ```
240
+
241
+ Publish one RubyGems artifact. Compatibility is checked with tests; build the distributable gem once with the current Ruby.
242
+
243
+ ```bash
244
+ bundle install
245
+ bundle exec rake test
246
+ bundle exec gem build fluent-plugin-azure-logs-ingestion.gemspec --strict
247
+ gem push fluent-plugin-azure-logs-ingestion-*.gem
248
+ ```
data/README_ja.md CHANGED
@@ -5,6 +5,11 @@ Azure Monitor Logs Ingestion API を使い、Log Analytics Workspace のテー
5
5
  > [!WARNING]
6
6
  > この plugin は試験的な実装であり、本格的な production workload での動作実績はまだ十分ではありません。
7
7
 
8
+ ## サポート環境
9
+
10
+ - Ruby 2.4 以上
11
+ - Fluentd 1.13.0 以上、2.0 未満
12
+
8
13
  ## インストール
9
14
 
10
15
  ### RubyGems
@@ -30,7 +35,7 @@ gem 'fluent-plugin-azure-logs-ingestion'
30
35
  Bundler で GitHub repository を直接指定でき、特定の revision に固定したい場合は、`ref` を指定します。
31
36
 
32
37
  ```ruby
33
- gem 'fluent-plugin-azure-logs-ingestion', git: 'https://github.com/fukasawah/fluent-plugin-azure-logs-ingestion.git', ref: 'abda3b5370ccd61282c8b234ca05042049e09d15'
38
+ gem 'fluent-plugin-azure-logs-ingestion', git: 'https://github.com/fukasawah/fluent-plugin-azure-logs-ingestion.git', ref: 'dbb0160a8048957dafb3002799dc3e8567da06e6'
34
39
  ```
35
40
 
36
41
  その後、`bundle install` を実行してください。
@@ -206,4 +211,38 @@ Log Analytics Workspace の Auxiliary tier へ送信し、DCR transformation で
206
211
  ```bash
207
212
  bundle install
208
213
  bundle exec rake test
209
- ```
214
+ ```
215
+
216
+ この project は 1 つの `Gemfile` を使います。古い Ruby / Fluentd version の互換性を手元で確認する場合は、rbenv と `FLUENTD_VERSION` を使います。
217
+
218
+ ```bash
219
+ rbenv init
220
+ source ~/.bashrc
221
+
222
+ (
223
+ export RBENV_VERSION=2.4.10 FLUENTD_VERSION=1.13.0
224
+ unset GEM_HOME GEM_PATH MY_RUBY_HOME
225
+ rbenv install "$RBENV_VERSION" -s
226
+ rbenv exec gem install bundler -v 2.3.27 --no-document
227
+ rbenv exec bundle _2.3.27_ install
228
+ rbenv exec bundle _2.3.27_ exec rake test
229
+ )
230
+
231
+ (
232
+ export RBENV_VERSION=2.7.8 FLUENTD_VERSION=1.18.0
233
+ unset GEM_HOME GEM_PATH MY_RUBY_HOME
234
+ rbenv install "$RBENV_VERSION" -s
235
+ rbenv exec gem install bundler -v 2.3.27 --no-document
236
+ rbenv exec bundle _2.3.27_ install
237
+ rbenv exec bundle _2.3.27_ exec rake test
238
+ )
239
+ ```
240
+
241
+ RubyGems へ公開する gem は 1 つだけです。互換性は test で確認し、配布する gem は現在の Ruby で build します。
242
+
243
+ ```bash
244
+ bundle install
245
+ bundle exec rake test
246
+ bundle exec gem build fluent-plugin-azure-logs-ingestion.gemspec --strict
247
+ gem push fluent-plugin-azure-logs-ingestion-*.gem
248
+ ```
@@ -10,7 +10,7 @@ module Fluent
10
10
  module Plugin
11
11
  module AzureLogsIngestion
12
12
  class Auth
13
- Token = Struct.new(:value, :expires_at, keyword_init: true)
13
+ Token = Struct.new(:value, :expires_at)
14
14
 
15
15
  IMDS_API_VERSION = '2018-02-01'
16
16
  APP_SERVICE_API_VERSION = '2019-08-01'
@@ -159,7 +159,7 @@ module Fluent
159
159
  def build_token_from_token_response(body)
160
160
  token = body.fetch('access_token')
161
161
  expires_at = parse_token_expiry(body)
162
- Token.new(value: token, expires_at: expires_at)
162
+ Token.new(token, expires_at)
163
163
  end
164
164
 
165
165
  def parse_token_expiry(body)
@@ -15,8 +15,7 @@ module Fluent
15
15
  :content_length,
16
16
  :raw_size,
17
17
  :gzip_size,
18
- :record_count,
19
- keyword_init: true
18
+ :record_count
20
19
  ) do
21
20
  def close!
22
21
  io.close!
@@ -65,14 +64,7 @@ module Fluent
65
64
 
66
65
  validate!(raw_size: raw_size, gzip_size: gzip_size)
67
66
 
68
- Result.new(
69
- io: io,
70
- content_encoding: content_encoding,
71
- content_length: content_length,
72
- raw_size: raw_size,
73
- gzip_size: gzip_size,
74
- record_count: record_count
75
- )
67
+ Result.new(io, content_encoding, content_length, raw_size, gzip_size, record_count)
76
68
  rescue StandardError
77
69
  gzip_file.close! if gzip_file
78
70
  raw_file.close! if raw_file
@@ -3,7 +3,7 @@
3
3
  module Fluent
4
4
  module Plugin
5
5
  module AzureLogsIngestion
6
- VERSION = '0.1.0'
6
+ VERSION = '0.1.2'
7
7
  end
8
8
  end
9
9
  end
data/test/helper.rb CHANGED
@@ -1,5 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if ENV['COVERAGE']
4
+ require 'simplecov'
5
+ SimpleCov.enable_coverage :branch if SimpleCov.respond_to?(:enable_coverage)
6
+ SimpleCov.start do
7
+ add_filter '/test/'
8
+ add_filter '/vendor/'
9
+ track_files 'lib/**/*.rb'
10
+ end
11
+ end
3
12
  require 'test/unit'
4
13
  require 'fluent/test'
5
14
 
@@ -4,7 +4,7 @@ require 'socket'
4
4
  require 'thread'
5
5
 
6
6
  class FakeAzureServer
7
- Request = Struct.new(:method, :path, :headers, :body, keyword_init: true)
7
+ Request = Struct.new(:method, :path, :headers, :body)
8
8
 
9
9
  attr_reader :requests
10
10
 
@@ -61,7 +61,7 @@ class FakeAzureServer
61
61
  end
62
62
 
63
63
  body = read_body(socket, headers)
64
- request = Request.new(method: method, path: path, headers: headers, body: body)
64
+ request = Request.new(method, path, headers, body)
65
65
  @mutex.synchronize { @requests << request }
66
66
  status, response_headers, response_body = @handler.call(request)
67
67
  write_response(socket, status, response_headers || {}, response_body || '')
@@ -5,10 +5,21 @@ require 'time'
5
5
  require 'uri'
6
6
 
7
7
  class TestLogger
8
- def debug(*) = nil
9
- def info(*) = nil
10
- def warn(*) = nil
11
- def error(*) = nil
8
+ def debug(*)
9
+ nil
10
+ end
11
+
12
+ def info(*)
13
+ nil
14
+ end
15
+
16
+ def warn(*)
17
+ nil
18
+ end
19
+
20
+ def error(*)
21
+ nil
22
+ end
12
23
  end
13
24
 
14
25
  FakeChunk = Struct.new(:events, :chunk_id) do
@@ -13,32 +13,101 @@ class AuthManagedIdentityTest < Test::Unit::TestCase
13
13
  [200, { 'Content-Type' => 'application/json' }, { access_token: 'msi-token', expires_on: (Time.now.to_i + 3600).to_s }.to_json]
14
14
  end.start
15
15
 
16
- with_env(
17
- 'IDENTITY_ENDPOINT' => "#{server.url}/msi/token",
18
- 'IDENTITY_HEADER' => 'identity-header-value',
19
- 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => nil
20
- ) do
21
- auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
22
- use_msi: true,
23
- tenant_id: nil,
24
- client_id: 'user-assigned-client-id',
25
- client_secret: nil,
26
- authority_host: 'https://login.microsoftonline.com',
27
- logs_ingestion_scope: 'https://monitor.azure.com/.default',
28
- token_refresh_skew: 300,
29
- logger: TestLogger.new
30
- )
31
-
32
- assert_equal 'msi-token', auth.token
16
+ begin
17
+ with_env(
18
+ 'IDENTITY_ENDPOINT' => "#{server.url}/msi/token",
19
+ 'IDENTITY_HEADER' => 'identity-header-value',
20
+ 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => nil
21
+ ) do
22
+ auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
23
+ use_msi: true,
24
+ tenant_id: nil,
25
+ client_id: 'user-assigned-client-id',
26
+ client_secret: nil,
27
+ authority_host: 'https://login.microsoftonline.com',
28
+ logs_ingestion_scope: 'https://monitor.azure.com/.default',
29
+ token_refresh_skew: 300,
30
+ logger: TestLogger.new
31
+ )
32
+
33
+ assert_equal 'msi-token', auth.token
34
+ end
35
+
36
+ request = server.requests.first
37
+ query = URI.decode_www_form(URI(request.path).query).to_h
38
+ assert_equal 'identity-header-value', request.headers['x-identity-header']
39
+ assert_equal 'https://monitor.azure.com/', query['resource']
40
+ assert_equal 'user-assigned-client-id', query['client_id']
41
+ ensure
42
+ server&.stop
43
+ end
44
+ end
45
+
46
+ test 'uses app service managed identity secret when header is absent' do
47
+ server = FakeAzureServer.new do |_request|
48
+ [200, { 'Content-Type' => 'application/json' }, { access_token: 'msi-token', expires_on: (Time.now.to_i + 3600).to_s }.to_json]
49
+ end.start
50
+
51
+ begin
52
+ with_env(
53
+ 'IDENTITY_ENDPOINT' => "#{server.url}/msi/token?existing=1",
54
+ 'IDENTITY_HEADER' => nil,
55
+ 'MSI_SECRET' => 'msi-secret-value',
56
+ 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => nil
57
+ ) do
58
+ auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
59
+ use_msi: true,
60
+ tenant_id: nil,
61
+ client_id: '',
62
+ client_secret: nil,
63
+ authority_host: 'https://login.microsoftonline.com',
64
+ logs_ingestion_scope: 'https://monitor.azure.com/.default',
65
+ token_refresh_skew: 300,
66
+ logger: TestLogger.new
67
+ )
68
+
69
+ assert_equal 'msi-token', auth.token
70
+ end
71
+
72
+ request = server.requests.first
73
+ query = URI.decode_www_form(URI(request.path).query).to_h
74
+ assert_equal '1', query['existing']
75
+ assert_equal 'msi-secret-value', request.headers['secret']
76
+ assert_nil query['client_id']
77
+ ensure
78
+ server&.stop
33
79
  end
80
+ end
81
+
82
+ test 'rejects invalid app service managed identity json response' do
83
+ server = FakeAzureServer.new do |_request|
84
+ [200, { 'Content-Type' => 'application/json' }, '{']
85
+ end.start
86
+
87
+ begin
88
+ with_env(
89
+ 'IDENTITY_ENDPOINT' => "#{server.url}/msi/token",
90
+ 'IDENTITY_HEADER' => 'identity-header-value',
91
+ 'MSI_SECRET' => nil,
92
+ 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => nil
93
+ ) do
94
+ auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
95
+ use_msi: true,
96
+ tenant_id: nil,
97
+ client_id: nil,
98
+ client_secret: nil,
99
+ authority_host: 'https://login.microsoftonline.com',
100
+ logs_ingestion_scope: 'https://monitor.azure.com/.default',
101
+ token_refresh_skew: 300,
102
+ logger: TestLogger.new
103
+ )
34
104
 
35
- request = server.requests.first
36
- query = URI.decode_www_form(URI(request.path).query).to_h
37
- assert_equal 'identity-header-value', request.headers['x-identity-header']
38
- assert_equal 'https://monitor.azure.com/', query['resource']
39
- assert_equal 'user-assigned-client-id', query['client_id']
40
- ensure
41
- server&.stop
105
+ error = assert_raise(RuntimeError) { auth.token }
106
+ assert_match(/failed to parse App Service managed identity response/, error.message)
107
+ end
108
+ ensure
109
+ server&.stop
110
+ end
42
111
  end
43
112
 
44
113
  test 'uses IMDS endpoint when app service environment is absent' do
@@ -46,31 +115,93 @@ class AuthManagedIdentityTest < Test::Unit::TestCase
46
115
  [200, { 'Content-Type' => 'application/json' }, { access_token: 'imds-token', expires_on: (Time.now.to_i + 3600).to_s }.to_json]
47
116
  end.start
48
117
 
49
- with_env(
50
- 'IDENTITY_ENDPOINT' => nil,
51
- 'IDENTITY_HEADER' => nil,
52
- 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => "#{server.url}/metadata/identity/oauth2/token"
53
- ) do
54
- auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
55
- use_msi: true,
56
- tenant_id: nil,
57
- client_id: 'user-assigned-client-id',
58
- client_secret: nil,
59
- authority_host: 'https://login.microsoftonline.com',
60
- logs_ingestion_scope: 'https://monitor.azure.com/.default',
61
- token_refresh_skew: 300,
62
- logger: TestLogger.new
63
- )
64
-
65
- assert_equal 'imds-token', auth.token
118
+ begin
119
+ with_env(
120
+ 'IDENTITY_ENDPOINT' => nil,
121
+ 'IDENTITY_HEADER' => nil,
122
+ 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => "#{server.url}/metadata/identity/oauth2/token"
123
+ ) do
124
+ auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
125
+ use_msi: true,
126
+ tenant_id: nil,
127
+ client_id: 'user-assigned-client-id',
128
+ client_secret: nil,
129
+ authority_host: 'https://login.microsoftonline.com',
130
+ logs_ingestion_scope: 'https://monitor.azure.com/.default',
131
+ token_refresh_skew: 300,
132
+ logger: TestLogger.new
133
+ )
134
+
135
+ assert_equal 'imds-token', auth.token
136
+ end
137
+
138
+ request = server.requests.first
139
+ query = URI.decode_www_form(URI(request.path).query).to_h
140
+ assert_equal 'true', request.headers['metadata']
141
+ assert_equal 'https://monitor.azure.com/', query['resource']
142
+ assert_equal 'user-assigned-client-id', query['client_id']
143
+ ensure
144
+ server&.stop
145
+ end
146
+ end
147
+
148
+ test 'keeps retryable managed identity status retryable' do
149
+ server = FakeAzureServer.new do |_request|
150
+ [404, { 'Content-Type' => 'application/json' }, '{"error":"not ready"}']
151
+ end.start
152
+
153
+ begin
154
+ with_env(
155
+ 'IDENTITY_ENDPOINT' => nil,
156
+ 'IDENTITY_HEADER' => nil,
157
+ 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => "#{server.url}/metadata/identity/oauth2/token"
158
+ ) do
159
+ auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
160
+ use_msi: true,
161
+ tenant_id: nil,
162
+ client_id: nil,
163
+ client_secret: nil,
164
+ authority_host: 'https://login.microsoftonline.com',
165
+ logs_ingestion_scope: 'https://monitor.azure.com/.default',
166
+ token_refresh_skew: 300,
167
+ logger: TestLogger.new
168
+ )
169
+
170
+ error = assert_raise(RuntimeError) { auth.token }
171
+ assert_match(/404/, error.message)
172
+ end
173
+ ensure
174
+ server&.stop
66
175
  end
176
+ end
177
+
178
+ test 'raises unrecoverable error for managed identity client error' do
179
+ server = FakeAzureServer.new do |_request|
180
+ [401, { 'Content-Type' => 'application/json' }, '{"error":"unauthorized"}']
181
+ end.start
67
182
 
68
- request = server.requests.first
69
- query = URI.decode_www_form(URI(request.path).query).to_h
70
- assert_equal 'true', request.headers['metadata']
71
- assert_equal 'https://monitor.azure.com/', query['resource']
72
- assert_equal 'user-assigned-client-id', query['client_id']
73
- ensure
74
- server&.stop
183
+ begin
184
+ with_env(
185
+ 'IDENTITY_ENDPOINT' => nil,
186
+ 'IDENTITY_HEADER' => nil,
187
+ 'AZURE_LOGS_INGESTION_IMDS_ENDPOINT' => "#{server.url}/metadata/identity/oauth2/token"
188
+ ) do
189
+ auth = Fluent::Plugin::AzureLogsIngestion::Auth.new(
190
+ use_msi: true,
191
+ tenant_id: nil,
192
+ client_id: nil,
193
+ client_secret: nil,
194
+ authority_host: 'https://login.microsoftonline.com',
195
+ logs_ingestion_scope: 'https://monitor.azure.com/.default',
196
+ token_refresh_skew: 300,
197
+ logger: TestLogger.new
198
+ )
199
+
200
+ error = assert_raise(Fluent::UnrecoverableError) { auth.token }
201
+ assert_match(/401/, error.message)
202
+ end
203
+ ensure
204
+ server&.stop
205
+ end
75
206
  end
76
207
  end