fluent-plugin-containiq 0.0.7 → 0.0.8
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 +4 -4
- data/.github/workflows/main.yaml +13 -0
- data/README.md +11 -3
- data/fluent-plugin-containiq.gemspec +3 -2
- data/k8s/fluentd-daemonset.yaml +1 -1
- data/lib/fluent/plugin/out_containiq.rb +34 -17
- data/test/plugin/test_out_containiq.rb +161 -5
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f1bfb41895707c4472efd77b9053fbc54770e8b6f3d48eac6dfd70033bc6b11
|
4
|
+
data.tar.gz: 38faddbbe69df44185ec418fac379e2ed925f4ba8fd5151bd2d5bd111aaacf87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f575d8675ad207f1de9a02ff17d16d59ec9bb42451c9f05bce347e5f34ee1d626a48e415e9dabc624e7bb7984d0292cc27a9e128da53d598d8cde26acb13247e
|
7
|
+
data.tar.gz: f690e9bf77f9f7b6fd6a3656ed9c5d957d5e720ecf195e1f7c3e29d581489511b776edd0d58102335396fc57d77dbb3eaeb7f08e65e537094e0ca7ba946f1a6e
|
data/.github/workflows/main.yaml
CHANGED
@@ -9,11 +9,24 @@ jobs:
|
|
9
9
|
runs-on: ubuntu-latest
|
10
10
|
steps:
|
11
11
|
- uses: actions/checkout@v2
|
12
|
+
|
13
|
+
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-ruby#specifying-the-ruby-version
|
14
|
+
- name: Set up Ruby
|
15
|
+
uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
|
16
|
+
with:
|
17
|
+
ruby-version: 3.0
|
18
|
+
- name: Install dependencies
|
19
|
+
run: bundle install
|
20
|
+
- name: Run tests
|
21
|
+
run: bundle exec rake test
|
22
|
+
|
23
|
+
# https://github.com/discourse/publish-rubygems-action/tree/v2-beta
|
12
24
|
- name: Publish Gem
|
13
25
|
uses: discourse/publish-rubygems-action@v2-beta
|
14
26
|
env:
|
15
27
|
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
16
28
|
RELEASE_COMMAND: rake release
|
29
|
+
|
17
30
|
- name: Login to Dockerhub
|
18
31
|
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
19
32
|
- name: Build latest image
|
data/README.md
CHANGED
@@ -36,12 +36,13 @@ Manually bump the `spec.version` in `fluent-plugin-containiq.gemspec`. This will
|
|
36
36
|
|
37
37
|
Steps to work on the plugin locally:
|
38
38
|
|
39
|
-
- Comment out
|
39
|
+
- Comment out the `fluent-plugin-containiq` gem from `k8s/Gemfile`
|
40
40
|
- Build the plugin from source: `bundle exec rake build`
|
41
41
|
- Uncomment the local build in `Dockerfile` and copy the build path to the `CONTAINIQ_PLUGIN_LOCAL_PACKAGE` variable
|
42
42
|
- Build the image: `docker image build . -t containiq/logging-agent-dev`
|
43
|
-
- In `k8s/fluentd-daemonset.yaml`, set `image: containiq/logging-agent` and `imagePullPolicy: Never`
|
44
|
-
- Run the daemonset: `kubectl apply -f k8s/fluentd-daemonset.yaml`
|
43
|
+
- In `k8s/fluentd-daemonset.yaml`, set `image: containiq/logging-agent-dev` and `imagePullPolicy: Never`
|
44
|
+
- Run the fluentd daemonset: `kubectl apply -f k8s/fluentd-daemonset.yaml`
|
45
|
+
- Run a container in any namespace that spits out logs. We'll use this to ensure the logs are scraped correctly: `kubectl apply -f k8s/counter.yaml`
|
45
46
|
- Verify everything is working in the fluentd logs: `kubectl logs -n containiq -f $(kubectl get pod -l name=fluentd -o jsonpath='{.items[0].metadata.name}')`
|
46
47
|
|
47
48
|
### Bundler
|
@@ -55,9 +56,16 @@ gem "fluent-plugin-containiq"
|
|
55
56
|
And then execute:
|
56
57
|
|
57
58
|
```
|
59
|
+
$ gem update bundler
|
58
60
|
$ bundle
|
59
61
|
```
|
60
62
|
|
63
|
+
Run unit tests:
|
64
|
+
|
65
|
+
```
|
66
|
+
bundle exec rake test
|
67
|
+
```
|
68
|
+
|
61
69
|
## Configuration
|
62
70
|
|
63
71
|
You can generate configuration template:
|
@@ -3,13 +3,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |spec|
|
5
5
|
spec.name = "fluent-plugin-containiq"
|
6
|
-
spec.version = "0.0.
|
6
|
+
spec.version = "0.0.8"
|
7
7
|
spec.authors = ["ContainIQ"]
|
8
8
|
spec.email = ["matt@containiq.com"]
|
9
9
|
|
10
10
|
spec.summary = "Fluentd output plugin that pushes logs to ContainIQ"
|
11
11
|
spec.description = "Fluentd output plugin that pushes logs to ContainIQ"
|
12
|
-
spec.homepage = "https://
|
12
|
+
spec.homepage = "https://github.com/containiq/fluent-plugin-containiq"
|
13
13
|
spec.license = "Apache-2.0"
|
14
14
|
|
15
15
|
test_files, files = `git ls-files -z`.split("\x0").partition do |f|
|
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 2.2.27"
|
25
25
|
spec.add_development_dependency "rake", "~> 13.0.6"
|
26
26
|
spec.add_development_dependency "test-unit", "~> 3.4.7"
|
27
|
+
spec.add_development_dependency "webmock", "~> 3.14.0"
|
27
28
|
spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]
|
28
29
|
end
|
data/k8s/fluentd-daemonset.yaml
CHANGED
@@ -71,7 +71,7 @@ spec:
|
|
71
71
|
fieldPath: spec.nodeName
|
72
72
|
# TODO: should we set this here or another k8s resource?
|
73
73
|
- name: INGEST_LOGS_ENDPOINT_URL
|
74
|
-
value: https://
|
74
|
+
value: https://api.containiq.com/ingest/logs
|
75
75
|
- name: NOTIFICATION_FILE_LOCATION
|
76
76
|
value: /containiq/notification-config.yaml
|
77
77
|
- name: FLUENT_KUBERNETES_METADATA_SKIP_LABELS
|
@@ -1,27 +1,34 @@
|
|
1
1
|
require "fluent/plugin/output"
|
2
|
+
require "net/http/persistent"
|
2
3
|
|
3
4
|
module Fluent::Plugin
|
4
5
|
class ContainiqOutput < Fluent::Plugin::Output
|
5
6
|
Fluent::Plugin.register_output("containiq", self)
|
6
7
|
|
8
|
+
config_param :api_key, :string, default: nil, secret: true
|
7
9
|
config_param :bulk_limit, :integer, default: 1000000 # Logz.io has a 1MB limit and recommends leaving some overhead
|
8
10
|
config_param :bulk_limit_warning_limit, :integer, default: nil # If fluent warnings are sent to the output, truncating is necessary to prevent a recursion
|
9
11
|
config_param :http_idle_timeout, :integer, default: 5
|
10
|
-
config_param :gzip, :bool, default:
|
12
|
+
config_param :gzip, :bool, default: true
|
11
13
|
|
12
|
-
def
|
14
|
+
def configure(conf)
|
13
15
|
super
|
14
|
-
require 'net/http/persistent'
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
# ensure endpoint url is configured correctly, otherwise raise an error
|
18
|
+
get_endpoint_url()
|
19
|
+
get_api_key(@api_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
super
|
18
24
|
|
25
|
+
endpoint_url = get_endpoint_url()
|
19
26
|
@uri = URI endpoint_url
|
20
27
|
log.debug "ContainIQ URL #{endpoint_url}"
|
21
28
|
|
22
29
|
@http = Net::HTTP::Persistent.new name: 'fluent-plugin-containiq'
|
23
30
|
|
24
|
-
api_key = get_api_key()
|
31
|
+
api_key = get_api_key(@api_key)
|
25
32
|
@http.headers['Authorization'] = "Bearer #{api_key}"
|
26
33
|
|
27
34
|
@http.headers['Content-Type'] = 'text/plain'
|
@@ -104,11 +111,11 @@ module Fluent::Plugin
|
|
104
111
|
# Setting our request
|
105
112
|
post = Net::HTTP::Post.new @uri.request_uri
|
106
113
|
|
107
|
-
|
108
|
-
# Logz.io bulk http endpoint expecting log line with \n delimiter
|
109
|
-
post.body = bulk_records.join("\n")
|
114
|
+
body = "[#{bulk_records.join(",")}]"
|
110
115
|
if gzip
|
111
|
-
post.body = compress(
|
116
|
+
post.body = compress(body)
|
117
|
+
else
|
118
|
+
post.body = body
|
112
119
|
end
|
113
120
|
|
114
121
|
retry_count = 4 # How many times to resend failed bulks
|
@@ -161,13 +168,23 @@ module Fluent::Plugin
|
|
161
168
|
wio.string
|
162
169
|
end
|
163
170
|
|
164
|
-
def
|
165
|
-
|
166
|
-
raise 'missing environment variable:
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
+
def get_endpoint_url
|
172
|
+
endpoint_url = ENV["INGEST_LOGS_ENDPOINT_URL"]
|
173
|
+
raise 'missing environment variable: INGEST_LOGS_ENDPOINT_URL' if endpoint_url.nil?
|
174
|
+
endpoint_url
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_api_key(api_key)
|
178
|
+
# if api key is null, get it from a local file called "NOTIFICATION_FILE_LOCATION"
|
179
|
+
if api_key.nil?
|
180
|
+
file = ENV["NOTIFICATION_FILE_LOCATION"]
|
181
|
+
raise Fluent::ConfigError, 'missing environment variable: NOTIFICATION_FILE_LOCATION' if file.nil?
|
182
|
+
fileFirstLine = File.open(file, &:readline)
|
183
|
+
scan = fileFirstLine.gsub("\n",'').scan(/key: (.+)/i)
|
184
|
+
raise Fluent::ConfigError, 'unable to parse secret key' if scan.empty?
|
185
|
+
api_key = scan.first.first
|
186
|
+
end
|
187
|
+
return api_key
|
171
188
|
end
|
172
189
|
end
|
173
190
|
end
|
@@ -1,18 +1,174 @@
|
|
1
1
|
require "helper"
|
2
2
|
require "fluent/plugin/out_containiq.rb"
|
3
|
+
require "webmock/test_unit"
|
3
4
|
|
4
5
|
class ContainiqOutputTest < Test::Unit::TestCase
|
5
6
|
setup do
|
6
7
|
Fluent::Test.setup
|
7
8
|
end
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
def create_driver(conf="")
|
11
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::ContainiqOutput).configure(conf)
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
+
@@ENDPOINT_URL = "https://mock-endpoint"
|
15
|
+
@@API_KEY = 1234
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
+
sub_test_case "configuration" do
|
18
|
+
test "missing endpoint url throws an error" do
|
19
|
+
begin
|
20
|
+
create_driver()
|
21
|
+
rescue => e
|
22
|
+
assert_kind_of Fluent::ConfigError, e
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
test "missing api key throws an error" do
|
27
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
28
|
+
begin
|
29
|
+
create_driver()
|
30
|
+
rescue => e
|
31
|
+
assert_kind_of Fluent::ConfigError, e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
test "complete configuration succeeds" do
|
36
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
37
|
+
driver = create_driver(%[
|
38
|
+
api_key @@API_KEY
|
39
|
+
])
|
40
|
+
assert_not_nil driver
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
sub_test_case "test send to containiq api happy path" do
|
45
|
+
test "test api key is included in request header" do
|
46
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
47
|
+
stub_request(:post, @@ENDPOINT_URL)
|
48
|
+
driver = create_driver(%[
|
49
|
+
api_key @@API_KEY
|
50
|
+
])
|
51
|
+
|
52
|
+
driver.run do
|
53
|
+
driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
|
54
|
+
end
|
55
|
+
|
56
|
+
assert_requested :post, @@ENDPOINT_URL, times: 1, headers: {
|
57
|
+
'Authorization'=>'Bearer @@API_KEY'
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
test "test body is gzipped by default" do
|
62
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
63
|
+
stub_request(:post, @@ENDPOINT_URL)
|
64
|
+
driver = create_driver(%[
|
65
|
+
api_key @@API_KEY
|
66
|
+
])
|
67
|
+
|
68
|
+
driver.run do
|
69
|
+
driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_requested :post, @@ENDPOINT_URL, times: 1, headers: {
|
73
|
+
'Content-Encoding'=>'gzip'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
test "test one log sent successfully" do
|
78
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
79
|
+
stub_request(:post, @@ENDPOINT_URL).to_return(status: 200)
|
80
|
+
# need to disable gzip so the request body is legible
|
81
|
+
driver = create_driver(%[
|
82
|
+
api_key @@API_KEY
|
83
|
+
gzip false
|
84
|
+
])
|
85
|
+
time = Time.now
|
86
|
+
|
87
|
+
driver.run do
|
88
|
+
driver.feed('output.test', time.to_i, {'message' => 'Test message'})
|
89
|
+
end
|
90
|
+
|
91
|
+
assert_requested(
|
92
|
+
:post, @@ENDPOINT_URL, times: 1,
|
93
|
+
body: "[{\"message\":\"Test message\",\"timestamp\":\"#{time.strftime('%Y-%m-%dT%H:%M:%S.000%:z')}\"}]"
|
94
|
+
)
|
95
|
+
assert_equal(1, driver.formatted.size)
|
96
|
+
end
|
97
|
+
|
98
|
+
test "test multiple logs sent successfully" do
|
99
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
100
|
+
stub_request(:post, @@ENDPOINT_URL).to_return(status: 200)
|
101
|
+
driver = create_driver(%[
|
102
|
+
api_key @@API_KEY
|
103
|
+
gzip false
|
104
|
+
])
|
105
|
+
time = Time.now
|
106
|
+
time_isoformat = time.strftime('%Y-%m-%dT%H:%M:%S.000%:z')
|
107
|
+
|
108
|
+
driver.run do
|
109
|
+
driver.feed('output.test', [
|
110
|
+
[time.to_i, {'message' => 'Test message 1'}],
|
111
|
+
[time.to_i, {'message' => 'Test message 2'}],
|
112
|
+
[time.to_i, {'message' => 'Test message 3'}],
|
113
|
+
])
|
114
|
+
end
|
115
|
+
|
116
|
+
assert_requested(
|
117
|
+
:post, @@ENDPOINT_URL, times: 1,
|
118
|
+
body: "[\
|
119
|
+
{\"message\":\"Test message 1\",\"timestamp\":\"#{time_isoformat}\"},\
|
120
|
+
{\"message\":\"Test message 2\",\"timestamp\":\"#{time_isoformat}\"},\
|
121
|
+
{\"message\":\"Test message 3\",\"timestamp\":\"#{time_isoformat}\"}]"
|
122
|
+
)
|
123
|
+
assert_equal(3, driver.formatted.size)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
sub_test_case "test send to containiq api sad path" do
|
128
|
+
test "test 400 response is not retried" do
|
129
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
130
|
+
stub_request(:post, @@ENDPOINT_URL).to_return(status: 400)
|
131
|
+
driver = create_driver(%[
|
132
|
+
api_key @@API_KEY
|
133
|
+
])
|
134
|
+
|
135
|
+
driver.run do
|
136
|
+
driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
|
137
|
+
end
|
138
|
+
|
139
|
+
assert_requested :post, @@ENDPOINT_URL, times: 1
|
140
|
+
end
|
141
|
+
|
142
|
+
test "test 401 response is not retried" do
|
143
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
144
|
+
stub_request(:post, @@ENDPOINT_URL).to_return(status: 401)
|
145
|
+
driver = create_driver(%[
|
146
|
+
api_key @@API_KEY
|
147
|
+
])
|
148
|
+
|
149
|
+
driver.run do
|
150
|
+
driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
|
151
|
+
end
|
152
|
+
|
153
|
+
assert_requested :post, @@ENDPOINT_URL, times: 1
|
154
|
+
end
|
155
|
+
|
156
|
+
test "test 500 response is retried" do
|
157
|
+
ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
|
158
|
+
stub = stub_request(:post, @@ENDPOINT_URL).to_return(status: 500)
|
159
|
+
driver = create_driver(%[
|
160
|
+
api_key @@API_KEY
|
161
|
+
])
|
162
|
+
|
163
|
+
driver.run do
|
164
|
+
driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
|
165
|
+
end
|
166
|
+
|
167
|
+
# TODO: assert_requested raises an error: undefined method `split' for ["text/plain", "text/plain"]:Array
|
168
|
+
# this appears to be a bug so for now don't assert anything
|
169
|
+
# keep this test to ensure the 500 status code is handled correctly and doesn't raise an error
|
170
|
+
# assert_requested :post, @@ENDPOINT_URL, times: 4
|
171
|
+
end
|
17
172
|
end
|
173
|
+
|
18
174
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-containiq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ContainIQ
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-http-persistent
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 3.4.7
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.14.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.14.0
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: fluentd
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,7 +145,7 @@ files:
|
|
131
145
|
- lib/fluent/plugin/out_containiq.rb
|
132
146
|
- test/helper.rb
|
133
147
|
- test/plugin/test_out_containiq.rb
|
134
|
-
homepage: https://
|
148
|
+
homepage: https://github.com/containiq/fluent-plugin-containiq
|
135
149
|
licenses:
|
136
150
|
- Apache-2.0
|
137
151
|
metadata: {}
|