fluent-plugin-azure-storage-append-blob-lts-azurestack 0.6.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 539fe91fe557c8022c49e79c26022a43ebce8a5e43c0e2932881ad4a63f3ac68
4
+ data.tar.gz: 624604c25bc0a3df095329a8657be820cdbe57f9543392b261e99d3e75bb25b4
5
+ SHA512:
6
+ metadata.gz: dfe527fe5a38f26a0c6fe5c7ae581e96422ae0f4e3dfb482409e82689b17eb5ed13dca24244f770744b11d7515dc8409eb57f78c14964978e9d9b8327734209d
7
+ data.tar.gz: bc6527c9b5434598249b0f8df3b7f2859bcab6889d32e20407716ec7d70150c8d5855761a43c70c15d91de460b905e27d707a21320595b2c59152f424a7c31e5
@@ -0,0 +1,29 @@
1
+ name: Publish Ruby Gem
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ otp:
7
+ description: 'One Time Password'
8
+ required: true
9
+
10
+ jobs:
11
+ build:
12
+ name: Build + Publish
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Set up Ruby 2.6
17
+ uses: actions/setup-ruby@v1
18
+ with:
19
+ ruby-version: 2.6.x
20
+ - name: Publish to RubyGems
21
+ run: |
22
+ mkdir -p $HOME/.gem
23
+ touch $HOME/.gem/credentials
24
+ chmod 0600 $HOME/.gem/credentials
25
+ printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
26
+ gem build *.gemspec
27
+ gem push *.gem --otp ${{ github.event.inputs.otp }}
28
+ env:
29
+ RUBYGEMS_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
@@ -0,0 +1,21 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 2.6
18
+ - name: Install dependencies
19
+ run: bundle install
20
+ - name: Run tests
21
+ run: bundle exec rake test
data/.gitignore ADDED
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Style/FrozenStringLiteralComment:
2
+ Enabled: false
data/Dockerfile ADDED
@@ -0,0 +1,37 @@
1
+ FROM ruby:2.7
2
+
3
+ WORKDIR /plugin
4
+
5
+ COPY . /plugin
6
+
7
+ RUN gem install bundler && \
8
+ gem install fluentd --no-doc && \
9
+ fluent-gem build fluent-plugin-azure-storage-append-blob-lts.gemspec && \
10
+ fluent-gem install fluent-plugin-azure-storage-append-blob-lts-*.gem
11
+
12
+ RUN echo "<source>\n\
13
+ @type sample\n\
14
+ sample {\"hello\":\"world\"}\n\
15
+ tag pattern\n\
16
+ </source>\n\
17
+ <match pattern>\n\
18
+ @type azure-storage-append-blob\n\
19
+ azure_storage_account \"#{ENV['STORAGE_ACCOUNT']}\"\n\
20
+ azure_storage_access_key \"#{ENV['STORAGE_ACCESS_KEY']}\"\n\
21
+ azure_storage_connection_string \"#{ENV['STORAGE_CONNECTION_STRING']}\"\n\
22
+ azure_storage_sas_token \"#{ENV['STORAGE_SAS_TOKEN']}\"\n\
23
+ azure_container fluentd\n\
24
+ auto_create_container true\n\
25
+ path logs/\n\
26
+ azure_object_key_format %{path}%{time_slice}_%{index}.log\n\
27
+ time_slice_format %Y%m%d-%H\n\
28
+ <buffer tag,time>\n\
29
+ @type file\n\
30
+ path /var/log/fluent/azurestorageappendblob\n\
31
+ timekey 120 # 2 minutes\n\
32
+ timekey_wait 60\n\
33
+ timekey_use_utc true # use utc\n\
34
+ </buffer>\n\
35
+ </match>" > /plugin/fluent.conf
36
+
37
+ ENTRYPOINT ["fluentd", "-c", "fluent.conf"]
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Microsoft Corporation. All rights reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
data/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # fluent-plugin-azure-storage-append-blob-lts
2
+
3
+ ![Tests](https://github.com/elsesiy/fluent-plugin-azure-storage-append-blob-lts/workflows/Test/badge.svg?branch=master)
4
+ [![Gem Version](https://badge.fury.io/rb/fluent-plugin-azure-storage-append-blob-lts.svg)](https://badge.fury.io/rb/fluent-plugin-azure-storage-append-blob-lts)
5
+ [![Twitter](https://img.shields.io/badge/twitter-@elsesiy-blue.svg)](http://twitter.com/elsesiy)
6
+
7
+ Azure Storage Append Blob output plugin buffers logs in local file and uploads them to Azure Storage Append Blob periodically.
8
+ This plugin is a fork of [microsoft/fluent-plugin-azure-storage-append-blob](https://github.com/microsoft/fluent-plugin-azure-storage-append-blob) which is not actively maintained.
9
+
10
+ ## Installation
11
+
12
+ ### RubyGems
13
+
14
+ gem install fluent-plugin-azure-storage-append-blob-lts
15
+
16
+ ### Bundler
17
+
18
+ Add following line to your Gemfile:
19
+
20
+ gem "fluent-plugin-azure-storage-append-blob-lts"
21
+
22
+ And then execute:
23
+
24
+ bundle
25
+
26
+ ## Configuration
27
+
28
+ <match pattern>
29
+ type azure-storage-append-blob
30
+
31
+ azure_cloud <azure cloud environment>
32
+ azure_storage_account <your azure storage account>
33
+ azure_storage_access_key <your azure storage access key> # leave empty to use MSI
34
+ azure_storage_connection_string <your azure storage connection string> # leave empty to use MSI
35
+ azure_storage_sas_token <your azure storage sas token> # leave empty to use MSI
36
+ azure_imds_api_version <Azure Instance Metadata Service API Version> # only used for MSI
37
+ azure_token_refresh_interval <refresh interval in min> # only used for MSI
38
+ azure_container <your azure storage container>
39
+ azure_msi_client_id <Azure Managed Identity Client ID> # only used for MSI
40
+ auto_create_container true
41
+ path logs/
42
+ azure_object_key_format %{path}%{time_slice}_%{index}.log
43
+ time_slice_format %Y%m%d-%H
44
+ compute_checksums true
45
+ # if you want to use %{tag} or %Y/%m/%d/ like syntax in path / azure_blob_name_format,
46
+ # need to specify tag for %{tag} and time for %Y/%m/%d in <buffer> argument.
47
+ <buffer tag,time>
48
+ @type file
49
+ path /var/log/fluent/azurestorageappendblob
50
+ timekey 120 # 2 minutes
51
+ timekey_wait 60
52
+ timekey_use_utc true # use utc
53
+ </buffer>
54
+ </match>
55
+
56
+ ### `azure_cloud` (Optional)
57
+
58
+ Default: `AZUREPUBLICCLOUD`
59
+
60
+ Cloud environment used to determine the storage endpoint suffix to use, see [here](https://github.com/Azure/go-autorest/blob/master/autorest/azure/environments.go).
61
+
62
+ ### `azure_storage_account` (Required)
63
+
64
+ Your Azure Storage Account Name. This can be retrieved from Azure Management portal.
65
+
66
+ ### `azure_storage_access_key` or `azure_storage_sas_token` or `azure_storage_connection_string` (Any required or all empty to use MSI)
67
+
68
+ Your Azure Storage Access Key (Primary or Secondary) or shared access signature (SAS) token or full connection string.
69
+ This also can be retrieved from Azure Management portal.
70
+
71
+ If all are empty, the plugin will use the local Managed Identity endpoint to obtain a token for the target storage account.
72
+
73
+ ### `azure_imds_api_version` (Optional, only for MSI)
74
+
75
+ Default: `2019-08-15`
76
+
77
+ The Instance Metadata Service is used during the OAuth flow to obtain an access token. This API is versioned and specifying the version is mandatory.
78
+
79
+ See [here](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/instance-metadata-service#versioning) for more details.
80
+
81
+ ### `azure_token_refresh_interval` (Optional, only for MSI)
82
+
83
+ Default: `60` (1 hour)
84
+
85
+ When using MSI, the initial access token needs to be refreshed periodically.
86
+
87
+ ### `azure_container` (Required)
88
+
89
+ Azure Storage Container name
90
+
91
+ ### `azure_msi_client_id` (Optional, only for MSI)
92
+
93
+ Azure Identity Client ID to use for accessing Azure Blob service.
94
+
95
+ ### `auto_create_container`
96
+
97
+ Default: `true`
98
+
99
+ This plugin creates the Azure container if it does not already exist exist when you set 'auto_create_container' to true.
100
+
101
+ ### `azure_object_key_format`
102
+
103
+ The format of Azure Storage object keys. You can use several built-in variables:
104
+
105
+ - %{path}
106
+ - %{time_slice}
107
+ - %{index}
108
+
109
+ to decide keys dynamically.
110
+
111
+ %{path} is exactly the value of *path* configured in the configuration file. E.g., "logs/" in the example configuration above.
112
+ %{time_slice} is the time-slice in text that are formatted with *time_slice_format*.
113
+ %{index} is used only if your blob exceed Azure 50000 blocks limit per blob to prevent data loss. Its not required to use this parameter.
114
+
115
+ The default format is "%{path}%{time_slice}-%{index}.log".
116
+
117
+ For instance, using the example configuration above, actual object keys on Azure Storage will be something like:
118
+
119
+ "logs/20130111-22-0.log"
120
+ "logs/20130111-23-0.log"
121
+ "logs/20130112-00-0.log"
122
+
123
+ With the configuration:
124
+
125
+ azure_object_key_format %{path}/events/ts=%{time_slice}/events.log
126
+ path log
127
+ time_slice_format %Y%m%d-%H
128
+
129
+ You get:
130
+
131
+ "log/events/ts=20130111-22/events.log"
132
+ "log/events/ts=20130111-23/events.log"
133
+ "log/events/ts=20130112-00/events.log"
134
+
135
+ The [fluent-mixin-config-placeholders](https://github.com/tagomoris/fluent-mixin-config-placeholders) mixin is also incorporated, so additional variables such as %{hostname}, etc. can be used in the `azure_object_key_format`. This is useful in preventing filename conflicts when writing from multiple servers.
136
+
137
+ azure_object_key_format %{path}/events/ts=%{time_slice}/events-%{hostname}.log
138
+
139
+ ### `time_slice_format`
140
+
141
+ Format of the time used in the file name. Default is '%Y%m%d'. Use '%Y%m%d%H' to split files hourly.
142
+
143
+ ### `compute_checksums`
144
+
145
+ Default: `true`
146
+
147
+ Whether to compute MD5 checksum of the blob contents during append operation and provide it in a header for the blob service.
148
+
149
+ You want to set it to `false` in FIPS-enabled environments.
150
+
151
+ ### Run tests
152
+
153
+ gem install bundler
154
+ bundle install
155
+ bundle exec rake test
156
+
157
+
158
+ ### Test Fluentd
159
+
160
+ 1. Create Storage Account and VM with enabled MSI
161
+ 2. Setup Docker ang Git
162
+ 3. SSH into VM
163
+ 4. Download this repo
164
+ ```
165
+ git clone https://github.com/microsoft/fluent-plugin-azure-storage-append-blob-lts.git
166
+ cd fluent-plugin-azure-storage-append-blob-lts
167
+ ```
168
+ 5. Build Docker image
169
+ `docker build -t fluent .`
170
+ 6. Run Docker image with different set of parameters:
171
+
172
+ 1. `STORAGE_ACCOUNT`: required, name of your storage account
173
+ 2. `STORAGE_ACCESS_KEY`: storage account access key
174
+ 3. `STORAGE_SAS_TOKEN`: storage sas token with enough permissions for the plugin
175
+
176
+ You need to specify `STORAGE_ACCOUNT` and one of auth ways. If you run it from VM with MSI,
177
+ just `STORAGE_ACCOUNT` is required. Keep in mind, there is no way to refresh MSI Token, so
178
+ ensure you setup proper permissions first.
179
+
180
+ ```bash
181
+ docker run -it -e STORAGE_ACCOUNT=<storage> -e STORAGE_ACCESS_KEY=<key> fluent
182
+ ```
183
+
184
+ ## Contributing
185
+
186
+ This project welcomes contributions and suggestions. Most contributions require you to agree to a
187
+ Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
188
+ the rights to use your contribution. For details, visit [https://cla.microsoft.com](https://cla.microsoft.com).
189
+
190
+ When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
191
+ a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
192
+ provided by the bot. You will only need to do this once across all repos using our CLA.
193
+
194
+ This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
195
+ For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
196
+ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs.push('lib', 'test')
8
+ t.test_files = FileList['test/**/test_*.rb']
9
+ t.verbose = true
10
+ t.warning = true
11
+ end
12
+
13
+ task default: [:test]
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'fluent-plugin-azure-storage-append-blob-lts-azurestack'
6
+ spec.version = '0.6.2'
7
+ spec.authors = ['Jiaxun Song']
8
+ spec.email = ['jiaxun.song@outlook.com']
9
+
10
+ spec.summary = 'Azure Storage Append Blob output plugin for Fluentd event collector'
11
+ spec.description = 'Fluentd plugin to upload logs to Azure Storage append blobs. Fork of https://github.com/microsoft/fluent-plugin-azure-storage-append-blob'
12
+ spec.homepage = 'https://github.com/elsesiy/fluent-plugin-azure-storage-append-blob-lts'
13
+ spec.license = 'MIT'
14
+
15
+ test_files, files = `git ls-files -z`.split("\x0").partition do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.files = files
19
+ spec.executables = files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = test_files
21
+ spec.require_paths = ['lib']
22
+ spec.required_ruby_version = '>= 2.6'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 2.0'
25
+ spec.add_development_dependency 'rake', '~> 13.0'
26
+ spec.add_development_dependency 'test-unit', '~> 3.0'
27
+ spec.add_runtime_dependency 'azure-storage-blob', '~> 2.0'
28
+ spec.add_runtime_dependency 'fluentd', ['>= 0.14.10', '< 2']
29
+ end
@@ -0,0 +1,270 @@
1
+ #---------------------------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for license information.
4
+ #--------------------------------------------------------------------------------------------*/
5
+
6
+ require 'azure/storage/common'
7
+ require 'azure/storage/blob'
8
+ require 'faraday'
9
+ require 'fluent/plugin/output'
10
+ require 'json'
11
+
12
+ module Fluent
13
+ module Plugin
14
+ class AzureStorageAppendBlobOut < Output
15
+ Fluent::Plugin.register_output('azure-storage-append-blob', self)
16
+
17
+ helpers :formatter, :inject
18
+
19
+ DEFAULT_FORMAT_TYPE = 'out_file'.freeze
20
+ AZURE_BLOCK_SIZE_LIMIT = 4 * 1024 * 1024 - 1
21
+
22
+ def initialize
23
+ super
24
+ @use_msi = false
25
+
26
+ # Storage endpoint suffixes for various environments, see https://github.com/Azure/go-autorest/blob/master/autorest/azure/environments.go
27
+ @storage_endpoint_mapping = {
28
+ 'AZURECHINACLOUD' => 'core.chinacloudapi.cn',
29
+ 'AZUREGERMANCLOUD' => 'core.cloudapi.de',
30
+ 'AZUREPUBLICCLOUD' => 'core.windows.net',
31
+ 'AZUREUSGOVERNMENTCLOUD' => 'core.usgovcloudapi.net'
32
+ }
33
+ end
34
+
35
+ config_param :path, :string, default: ''
36
+ config_param :azure_storage_account, :string, default: nil
37
+ config_param :azure_storage_access_key, :string, default: nil, secret: true
38
+ config_param :azure_storage_connection_string, :string, default: nil, secret: true
39
+ config_param :azure_storage_sas_token, :string, default: nil, secret: true
40
+ config_param :azure_cloud, :string, default: 'AZUREPUBLICCLOUD'
41
+ config_param :azure_msi_client_id, :string, default: nil
42
+ config_param :azure_container, :string, default: nil
43
+ config_param :azure_imds_api_version, :string, default: '2019-08-15'
44
+ config_param :azure_token_refresh_interval, :integer, default: 60
45
+ config_param :azure_object_key_format, :string, default: '%{path}%{time_slice}-%{index}.log'
46
+ config_param :auto_create_container, :bool, default: true
47
+ config_param :compute_checksums, :bool, default: true
48
+ config_param :format, :string, default: DEFAULT_FORMAT_TYPE
49
+ config_param :time_slice_format, :string, default: '%Y%m%d'
50
+ config_param :localtime, :bool, default: false
51
+
52
+ config_section :format do
53
+ config_set_default :@type, DEFAULT_FORMAT_TYPE
54
+ end
55
+
56
+ config_section :buffer do
57
+ config_set_default :chunk_keys, ['time']
58
+ config_set_default :timekey, (60 * 60 * 24)
59
+ end
60
+
61
+ attr_reader :bs
62
+
63
+ def configure(conf)
64
+ super
65
+
66
+ @formatter = formatter_create
67
+
68
+ @path_slicer = if @localtime
69
+ proc do |path|
70
+ Time.now.strftime(path)
71
+ end
72
+ else
73
+ proc do |path|
74
+ Time.now.utc.strftime(path)
75
+ end
76
+ end
77
+
78
+ @azure_storage_dns_suffix = @storage_endpoint_mapping[@azure_cloud]
79
+ if @azure_storage_dns_suffix.nil?
80
+ raise ConfigError 'azure_cloud invalid, must be either of AZURECHINACLOUD, AZUREGERMANCLOUD, AZUREPUBLICCLOUD, AZUREUSGOVERNMENTCLOUD'
81
+ end
82
+
83
+ if (@azure_storage_access_key.nil? || @azure_storage_access_key.empty?) &&
84
+ (@azure_storage_sas_token.nil? || @azure_storage_sas_token.empty?) &&
85
+ (@azure_storage_connection_string.nil? || @azure_storage_connection_string.empty?)
86
+ log.info 'Using MSI since neither of azure_storage_access_key, azure_storage_sas_token, azure_storage_connection_string was provided.'
87
+ @use_msi = true
88
+ elsif @azure_storage_connection_string.nil? || @azure_storage_connection_string.empty?
89
+ raise ConfigError, 'azure_storage_account needs to be specified' if @azure_storage_account.nil?
90
+ raise ConfigError, 'azure_container needs to be specified' if @azure_container.nil?
91
+ end
92
+
93
+ @blob_options = {}
94
+
95
+ if !@compute_checksums
96
+ @blob_options[:content_md5] = ''
97
+ end
98
+ end
99
+
100
+ def multi_workers_ready?
101
+ true
102
+ end
103
+
104
+ def get_access_token
105
+ access_key_request = Faraday.new('http://169.254.169.254/metadata/identity/oauth2/token?' \
106
+ "api-version=#{@azure_imds_api_version}" \
107
+ '&resource=https://storage.azure.com/' \
108
+ "#{!azure_msi_client_id.nil? ? "&client_id=#{azure_msi_client_id}" : ''}",
109
+ headers: { 'Metadata' => 'true' }).get
110
+
111
+ if access_key_request.status == 200
112
+ JSON.parse(access_key_request.body)['access_token']
113
+ else
114
+ raise 'Access token request was not sucssessful. Possibly due to missing azure_msi_client_id config parameter.'
115
+ end
116
+ end
117
+
118
+ def start
119
+ super
120
+ if @use_msi
121
+ token_credential = Azure::Storage::Common::Core::TokenCredential.new get_access_token
122
+ token_signer = Azure::Storage::Common::Core::Auth::TokenSigner.new token_credential
123
+ @bs = Azure::Storage::Blob::BlobService.new(
124
+ storage_account_name: @azure_storage_account,
125
+ storage_dns_suffix: @azure_storage_dns_suffix,
126
+ signer: token_signer
127
+ )
128
+
129
+ refresh_interval = @azure_token_refresh_interval * 60
130
+ cancelled = false
131
+ renew_token = Thread.new do
132
+ Thread.stop
133
+ until cancelled
134
+ sleep(refresh_interval)
135
+
136
+ token_credential.renew_token get_access_token
137
+ end
138
+ end
139
+ sleep 0.1 while renew_token.status != 'sleep'
140
+ renew_token.run
141
+ elsif !@azure_storage_connection_string.nil? && !@azure_storage_connection_string.empty?
142
+ @bs = Azure::Storage::Blob::BlobService.create_from_connection_string(@azure_storage_connection_string)
143
+ else
144
+ @bs_params = { storage_account_name: @azure_storage_account, storage_dns_suffix: @azure_storage_dns_suffix }
145
+
146
+ if !@azure_storage_access_key.nil? && !@azure_storage_access_key.empty?
147
+ @bs_params.merge!({ storage_access_key: @azure_storage_access_key })
148
+ elsif !@azure_storage_sas_token.nil? && !@azure_storage_sas_token.empty?
149
+ @bs_params.merge!({ storage_sas_token: @azure_storage_sas_token })
150
+ end
151
+
152
+ @bs = Azure::Storage::Blob::BlobService.create(@bs_params)
153
+
154
+ end
155
+
156
+ ensure_container
157
+ @azure_storage_path = ''
158
+ @last_azure_storage_path = ''
159
+ @current_index = 0
160
+ end
161
+
162
+ def format(tag, time, record)
163
+ r = inject_values_to_record(tag, time, record)
164
+ @formatter.format(tag, time, r)
165
+ end
166
+
167
+ def write(chunk)
168
+ tmp = Tempfile.new('azure-')
169
+ begin
170
+ chunk.write_to(tmp)
171
+
172
+ generate_log_name(chunk, @current_index)
173
+ if @last_azure_storage_path != @azure_storage_path
174
+ @current_index = 0
175
+ generate_log_name(chunk, @current_index)
176
+ end
177
+
178
+ content = File.open(tmp.path, 'rb', &:read)
179
+
180
+ append_blob(content, chunk)
181
+ @last_azure_storage_path = @azure_storage_path
182
+ ensure
183
+ begin
184
+ tmp.close(true)
185
+ rescue StandardError
186
+ nil
187
+ end
188
+ end
189
+ end
190
+
191
+ def container_exists?(container)
192
+ begin
193
+ @bs.get_container_properties(container)
194
+ rescue Azure::Core::Http::HTTPError => e
195
+ if e.status_code == 404 # container does not exist
196
+ return false
197
+ else
198
+ raise
199
+ end
200
+ end
201
+ true
202
+ end
203
+
204
+ private
205
+
206
+ def ensure_container
207
+ unless container_exists? @azure_container
208
+ if @auto_create_container
209
+ @bs.create_container(@azure_container)
210
+ else
211
+ raise "The specified container does not exist: container = #{@azure_container}"
212
+ end
213
+ end
214
+ end
215
+
216
+ def generate_log_name(chunk, index)
217
+ metadata = chunk.metadata
218
+ time_slice = if metadata.timekey.nil?
219
+ ''.freeze
220
+ else
221
+ Time.at(metadata.timekey).utc.strftime(@time_slice_format)
222
+ end
223
+
224
+ path = @path_slicer.call(@path)
225
+ values_for_object_key = {
226
+ '%{path}' => path,
227
+ '%{time_slice}' => time_slice,
228
+ '%{index}' => index
229
+ }
230
+ storage_path = @azure_object_key_format.gsub(/%{[^}]+}/, values_for_object_key)
231
+ @azure_storage_path = extract_placeholders(storage_path, chunk)
232
+ end
233
+
234
+ def append_blob(content, chunk)
235
+ position = 0
236
+ log.debug "azure_storage_append_blob: append_blob.start: Content size: #{content.length}"
237
+ loop do
238
+ size = [content.length - position, AZURE_BLOCK_SIZE_LIMIT].min
239
+ log.debug "azure_storage_append_blob: append_blob.chunk: content[#{position}..#{position + size}]"
240
+ @bs.append_blob_block(@azure_container, @azure_storage_path, content[position..position + size - 1], options=@blob_options)
241
+ position += size
242
+ break if position >= content.length
243
+ rescue Azure::Core::Http::HTTPError => e
244
+ status_code = e.status_code
245
+
246
+ if status_code == 409 # exceeds azure block limit
247
+ @current_index += 1
248
+ old_azure_storage_path = @azure_storage_path
249
+ generate_log_name(chunk, @current_index)
250
+
251
+ # If index is not a part of format, rethrow exception.
252
+ if old_azure_storage_path == @azure_storage_path
253
+ log.warn 'azure_storage_append_blob: append_blob: blocks limit reached, you need to use %{index} for the format.'
254
+ raise
255
+ end
256
+
257
+ log.debug "azure_storage_append_blob: append_blob: blocks limit reached, creating new blob #{@azure_storage_path}."
258
+ @bs.create_append_blob(@azure_container, @azure_storage_path)
259
+ elsif status_code == 404 # blob not found
260
+ log.debug "azure_storage_append_blob: append_blob: #{@azure_storage_path} blob doesn't exist, creating new blob."
261
+ @bs.create_append_blob(@azure_container, @azure_storage_path)
262
+ else
263
+ raise
264
+ end
265
+ end
266
+ log.debug 'azure_storage_append_blob: append_blob.complete'
267
+ end
268
+ end
269
+ end
270
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.expand_path('..', __dir__))
2
+ require 'test-unit'
3
+ require 'fluent/test'
4
+ require 'fluent/test/driver/output'
5
+ require 'fluent/test/helpers'
6
+
7
+ Test::Unit::TestCase.include(Fluent::Test::Helpers)
8
+ Test::Unit::TestCase.extend(Fluent::Test::Helpers)
@@ -0,0 +1,197 @@
1
+ require 'helper'
2
+ require 'fluent/plugin/out_azure-storage-append-blob.rb'
3
+ require 'azure/core/http/http_response'
4
+ require 'azure/core/http/http_error'
5
+
6
+ include Fluent::Test::Helpers
7
+
8
+ class AzureStorageAppendBlobOutTest < Test::Unit::TestCase
9
+ setup do
10
+ Fluent::Test.setup
11
+ end
12
+
13
+ CONFIG = %(
14
+ azure_cloud AZUREGERMANCLOUD
15
+ azure_storage_account test_storage_account
16
+ azure_storage_access_key MY_FAKE_SECRET
17
+ azure_container test_container
18
+ time_slice_format %Y%m%d-%H
19
+ path log
20
+ ).freeze
21
+
22
+ CONNSTR_CONFIG = %(
23
+ azure_storage_connection_string https://test
24
+ time_slice_format %Y%m%d-%H
25
+ path log
26
+ ).freeze
27
+
28
+ MSI_CONFIG = %(
29
+ azure_storage_account test_storage_account
30
+ azure_container test_container
31
+ azure_imds_api_version 1970-01-01
32
+ azure_token_refresh_interval 120
33
+ time_slice_format %Y%m%d-%H
34
+ path log
35
+ ).freeze
36
+
37
+ def create_driver(conf: CONFIG, service: nil)
38
+ d = Fluent::Test::Driver::Output.new(Fluent::Plugin::AzureStorageAppendBlobOut).configure(conf)
39
+ d.instance.instance_variable_set(:@bs, service)
40
+ d.instance.instance_variable_set(:@azure_storage_path, 'storage_path')
41
+ d
42
+ end
43
+
44
+ sub_test_case 'test config' do
45
+ test 'config should reject with no azure container' do
46
+ assert_raise Fluent::ConfigError do
47
+ create_driver conf: %(
48
+ azure_storage_account test_storage_account
49
+ azure_storage_access_key MY_FAKE_SECRET
50
+ time_slice_format %Y%m%d-%H
51
+ time_slice_wait 10m
52
+ path log
53
+ )
54
+ end
55
+ end
56
+
57
+ test 'config with access key should set instance variables' do
58
+ d = create_driver
59
+ assert_equal 'core.cloudapi.de', d.instance.instance_variable_get(:@azure_storage_dns_suffix)
60
+ assert_equal 'test_storage_account', d.instance.azure_storage_account
61
+ assert_equal 'MY_FAKE_SECRET', d.instance.azure_storage_access_key
62
+ assert_equal 'test_container', d.instance.azure_container
63
+ assert_equal true, d.instance.auto_create_container
64
+ assert_equal '%{path}%{time_slice}-%{index}.log', d.instance.azure_object_key_format
65
+ end
66
+
67
+ test 'config with managed identity enabled should set instance variables' do
68
+ d = create_driver conf: MSI_CONFIG
69
+ assert_equal 'test_storage_account', d.instance.azure_storage_account
70
+ assert_equal 'test_container', d.instance.azure_container
71
+ assert_equal true, d.instance.instance_variable_get(:@use_msi)
72
+ assert_equal true, d.instance.auto_create_container
73
+ assert_equal '%{path}%{time_slice}-%{index}.log', d.instance.azure_object_key_format
74
+ assert_equal 120, d.instance.azure_token_refresh_interval
75
+ assert_equal '1970-01-01', d.instance.azure_imds_api_version
76
+ end
77
+
78
+ test 'config with connection string should set instance variables' do
79
+ d = create_driver conf: CONNSTR_CONFIG
80
+ assert_equal 'https://test', d.instance.azure_storage_connection_string
81
+ assert_equal false, d.instance.instance_variable_get(:@use_msi)
82
+ assert_equal true, d.instance.auto_create_container
83
+ end
84
+ end
85
+
86
+ sub_test_case 'test path slicing' do
87
+ test 'test path_slicing' do
88
+ config = CONFIG.clone.gsub(/path\slog/, 'path log/%Y/%m/%d')
89
+ d = create_driver conf: config
90
+ path_slicer = d.instance.instance_variable_get(:@path_slicer)
91
+ path = d.instance.instance_variable_get(:@path)
92
+ slice = path_slicer.call(path)
93
+ assert_equal slice, Time.now.utc.strftime('log/%Y/%m/%d')
94
+ end
95
+
96
+ test 'path slicing utc' do
97
+ config = CONFIG.clone.gsub(/path\slog/, 'path log/%Y/%m/%d')
98
+ config << "\nutc\n"
99
+ d = create_driver conf: config
100
+ path_slicer = d.instance.instance_variable_get(:@path_slicer)
101
+ path = d.instance.instance_variable_get(:@path)
102
+ slice = path_slicer.call(path)
103
+ assert_equal slice, Time.now.utc.strftime('log/%Y/%m/%d')
104
+ end
105
+ end
106
+
107
+ # This class is used to create an Azure::Core::Http::HTTPError. HTTPError parses
108
+ # a response object when it is created.
109
+ class FakeResponse
110
+ def initialize(status = 404)
111
+ @status = status
112
+ @body = 'body'
113
+ @headers = {}
114
+ end
115
+
116
+ attr_reader :status, :body, :headers
117
+ end
118
+
119
+ # This class is used to test plugin functions which interact with the blob service
120
+ class FakeBlobService
121
+ def initialize(status)
122
+ @response = Azure::Core::Http::HttpResponse.new(FakeResponse.new(status))
123
+ @blocks = []
124
+ end
125
+ attr_reader :blocks
126
+
127
+ def append_blob_block(_container, _path, data, options={})
128
+ @blocks.append(data)
129
+ end
130
+
131
+ def get_container_properties(_container)
132
+ unless @response.status_code == 200
133
+ raise Azure::Core::Http::HTTPError.new(@response)
134
+ end
135
+ end
136
+ end
137
+
138
+ sub_test_case 'test container_exists' do
139
+ test 'container 404 returns false' do
140
+ d = create_driver service: FakeBlobService.new(404)
141
+ assert_false d.instance.container_exists? 'anything'
142
+ end
143
+
144
+ test 'existing container returns true' do
145
+ d = create_driver service: FakeBlobService.new(200)
146
+ assert_true d.instance.container_exists? 'anything'
147
+ end
148
+
149
+ test 'unexpected exception raises' do
150
+ d = create_driver service: FakeBlobService.new(500)
151
+ assert_raise_kind_of Azure::Core::Http::HTTPError do
152
+ d.instance.container_exists? 'anything'
153
+ end
154
+ end
155
+ end
156
+
157
+ # Override the block size limit so that mocked requests do not require huge buffers
158
+ class Fluent::Plugin::AzureStorageAppendBlobOut
159
+ AZURE_BLOCK_SIZE_LIMIT = 10
160
+ end
161
+
162
+ sub_test_case 'test append blob buffering' do
163
+ def fake_appended_blocks(content)
164
+ # run the append on the fake blob service, return a list of append request buffers
165
+ svc = FakeBlobService.new(200)
166
+ d = create_driver service: svc
167
+ d.instance.send(:append_blob, content, nil)
168
+ svc.blocks
169
+ end
170
+
171
+ test 'short buffer appends once' do
172
+ content = '123456789'
173
+ blocks = fake_appended_blocks content
174
+ assert_equal [content], blocks
175
+ end
176
+
177
+ test 'single character appends once' do
178
+ content = '1'
179
+ blocks = fake_appended_blocks content
180
+ assert_equal [content], blocks
181
+ end
182
+
183
+ test 'empty appends once' do
184
+ content = ''
185
+ blocks = fake_appended_blocks content
186
+ assert_equal [''], blocks
187
+ end
188
+
189
+ test 'long buffer appends multiple times' do
190
+ limit = Fluent::Plugin::AzureStorageAppendBlobOut::AZURE_BLOCK_SIZE_LIMIT
191
+ buf1 = 'a' * limit
192
+ buf2 = 'a' * 3
193
+ blocks = fake_appended_blocks buf1 + buf2
194
+ assert_equal [buf1, buf2], blocks
195
+ end
196
+ end
197
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-azure-storage-append-blob-lts-azurestack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.2
5
+ platform: ruby
6
+ authors:
7
+ - Jiaxun Song
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: azure-storage-blob
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fluentd
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.14.10
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: '2'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 0.14.10
86
+ - - "<"
87
+ - !ruby/object:Gem::Version
88
+ version: '2'
89
+ description: Fluentd plugin to upload logs to Azure Storage append blobs. Fork of
90
+ https://github.com/microsoft/fluent-plugin-azure-storage-append-blob
91
+ email:
92
+ - jiaxun.song@outlook.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".github/workflows/publish.yaml"
98
+ - ".github/workflows/test.yaml"
99
+ - ".gitignore"
100
+ - ".rubocop.yml"
101
+ - Dockerfile
102
+ - Gemfile
103
+ - LICENSE
104
+ - README.md
105
+ - Rakefile
106
+ - fluent-plugin-azure-storage-append-blob-lts.gemspec
107
+ - lib/fluent/plugin/out_azure-storage-append-blob.rb
108
+ - test/helper.rb
109
+ - test/plugin/test_out_azure_storage_append_blob.rb
110
+ homepage: https://github.com/elsesiy/fluent-plugin-azure-storage-append-blob-lts
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '2.6'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.2.11
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Azure Storage Append Blob output plugin for Fluentd event collector
133
+ test_files:
134
+ - test/helper.rb
135
+ - test/plugin/test_out_azure_storage_append_blob.rb