fluent-plugin-containiq 0.0.7 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6ef7dd782c3451829e0028ed1a850ea3a164431b4a64c24e878045a3c2f00c8
4
- data.tar.gz: 460a91ce8196907db67e79c0917cc8170dd3fcd33ba40858c2fa647aa89688e6
3
+ metadata.gz: 3887ee226b158277ad98bb86a53a3b9f77f5bd4ece17832f40b2f63e32b8209d
4
+ data.tar.gz: b2edcafdaf2fd9b0e781fe4f76ebc0c74e182e226ba02dd6eeb79453029feb12
5
5
  SHA512:
6
- metadata.gz: eb327c20b29510195e0b9c1cb514e88307de6108e00f9eacb20dd917d5c82dfd48acc3d391dd8a1fbe930ad8be4e98880137f8cc34ff869f44a8fb8b40971ecc
7
- data.tar.gz: 9de3741926511852e34a38549a601d6b0f881690b5e656a95b837b68f796d1653dc98495c753680ed89c3e79488231766c1feba4346c31746cd7ab92007efdcd
6
+ metadata.gz: a91f7c8f01f9ade710de5019729e28d8bd67e2a7571b9897f36b97fefa24bed5a4260359dd4b67f70c0ec4900f19481c6366075913a90c0562de34f31a3f78e4
7
+ data.tar.gz: c0940944e1a8c756db22f9deb7184124b6f308beeb2c9e69c2fc993a0035ca7cac2ef3c8b254a6150e9ad21dcb321c6c9bc419d1ff89650a76882868af5c00d9
@@ -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 this gem from `k8s/Gemfile`
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,7 +3,7 @@ $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.7"
6
+ spec.version = "1.0.2"
7
7
  spec.authors = ["ContainIQ"]
8
8
  spec.email = ["matt@containiq.com"]
9
9
 
@@ -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
@@ -4,6 +4,14 @@ metadata:
4
4
  name: containiq
5
5
  ---
6
6
  apiVersion: v1
7
+ kind: ConfigMap
8
+ metadata:
9
+ name: cluster-config
10
+ namespace: containiq
11
+ data:
12
+ cluster-name: your-cluster-name
13
+ ---
14
+ apiVersion: v1
7
15
  kind: ServiceAccount
8
16
  metadata:
9
17
  name: fluentd
@@ -71,7 +79,7 @@ spec:
71
79
  fieldPath: spec.nodeName
72
80
  # TODO: should we set this here or another k8s resource?
73
81
  - name: INGEST_LOGS_ENDPOINT_URL
74
- value: https://localhost
82
+ value: https://api.containiq.com/ingest/logs
75
83
  - name: NOTIFICATION_FILE_LOCATION
76
84
  value: /containiq/notification-config.yaml
77
85
  - name: FLUENT_KUBERNETES_METADATA_SKIP_LABELS
@@ -82,6 +90,15 @@ spec:
82
90
  value: 'true'
83
91
  - name: FLUENT_KUBERNETES_METADATA_SKIP_MASTER_URL
84
92
  value: 'true'
93
+ - name: FLUENT_CONTAINER_TAIL_EXCLUDE_PATH
94
+ value: /var/log/containers/fluent*
95
+ - name: FLUENT_CONTAINER_TAIL_PARSER_TYPE
96
+ value: /^(?<time>.+) (?<stream>stdout|stderr)( (?<logtag>.))? (?<log>.*)$/
97
+ - name: CLUSTER_NAME
98
+ valueFrom:
99
+ configMapKeyRef:
100
+ name: cluster-config
101
+ key: cluster-name
85
102
  resources:
86
103
  limits:
87
104
  memory: 200Mi
@@ -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: false # False for backward compatibility
12
+ config_param :gzip, :bool, default: true
11
13
 
12
- def start
14
+ def configure(conf)
13
15
  super
14
- require 'net/http/persistent'
15
16
 
16
- endpoint_url = ENV["INGEST_LOGS_ENDPOINT_URL"]
17
- raise 'missing environment variable: INGEST_LOGS_ENDPOINT_URL' if endpoint_url.nil?
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'
@@ -64,8 +71,13 @@ module Fluent::Plugin
64
71
  def encode_chunk(chunk)
65
72
  records = []
66
73
  bulk_size = 0
74
+ cluster_name = ENV["CLUSTER_NAME"]
75
+ if cluster_name.nil?
76
+ cluster_name = "default"
77
+ end
67
78
  chunk.each { |tag, time, record|
68
79
  record['timestamp'] ||= Time.at(time).iso8601(3)
80
+ record['cluster'] ||= cluster_name
69
81
 
70
82
  begin
71
83
  json_record = Yajl.dump(record)
@@ -104,11 +116,11 @@ module Fluent::Plugin
104
116
  # Setting our request
105
117
  post = Net::HTTP::Post.new @uri.request_uri
106
118
 
107
- # TODO: not sure we need this; Logz.io included it with the following comment:
108
- # Logz.io bulk http endpoint expecting log line with \n delimiter
109
- post.body = bulk_records.join("\n")
119
+ body = "[#{bulk_records.join(",")}]"
110
120
  if gzip
111
- post.body = compress(post.body)
121
+ post.body = compress(body)
122
+ else
123
+ post.body = body
112
124
  end
113
125
 
114
126
  retry_count = 4 # How many times to resend failed bulks
@@ -161,13 +173,23 @@ module Fluent::Plugin
161
173
  wio.string
162
174
  end
163
175
 
164
- def get_api_key
165
- file = ENV["NOTIFICATION_FILE_LOCATION"]
166
- raise 'missing environment variable: NOTIFICATION_FILE_LOCATION' if file.nil?
167
- fileFirstLine = File.open(file, &:readline)
168
- scan = fileFirstLine.gsub("\n",'').scan(/key: (.+)/i)
169
- raise 'unable to parse secret key' if scan.empty?
170
- api_key = scan.first.first
176
+ def get_endpoint_url
177
+ endpoint_url = ENV["INGEST_LOGS_ENDPOINT_URL"]
178
+ raise 'missing environment variable: INGEST_LOGS_ENDPOINT_URL' if endpoint_url.nil?
179
+ endpoint_url
180
+ end
181
+
182
+ def get_api_key(api_key)
183
+ # if api key is null, get it from a local file called "NOTIFICATION_FILE_LOCATION"
184
+ if api_key.nil?
185
+ file = ENV["NOTIFICATION_FILE_LOCATION"]
186
+ raise Fluent::ConfigError, 'missing environment variable: NOTIFICATION_FILE_LOCATION' if file.nil?
187
+ fileFirstLine = File.open(file, &:readline)
188
+ scan = fileFirstLine.gsub("\n",'').scan(/key: (.+)/i)
189
+ raise Fluent::ConfigError, 'unable to parse secret key' if scan.empty?
190
+ api_key = scan.first.first
191
+ end
192
+ return api_key
171
193
  end
172
194
  end
173
195
  end
@@ -0,0 +1,31 @@
1
+ ## Change description
2
+
3
+ > Description here
4
+
5
+ ## Type of change
6
+ - [ ] Bug fix (fixes an issue)
7
+ - [ ] New feature (adds functionality)
8
+ - [ ] Other (repo management, deps, refactor, etc)
9
+
10
+ ## Related issues
11
+
12
+ > Fix [#1]()
13
+
14
+ ## Checklists
15
+
16
+ ### Development
17
+
18
+ - [ ] Lint rules pass locally
19
+ - [ ] Application changes have been tested thoroughly
20
+
21
+ ### Security
22
+
23
+ - [ ] Security impact of change has been considered
24
+ - [ ] Code follows company security practices and guidelines
25
+
26
+ ### Code review
27
+
28
+ - [ ] Pull request has a descriptive title and context useful to a reviewer. Screenshots or screencasts are attached as necessary
29
+ - [ ] "Ready for review" label attached and reviewers assigned when applicable
30
+ - [ ] Changes have been reviewed by at least one other contributor when applicable
31
+ - [ ] Pull request linked to task tracker where applicable
@@ -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
- test "failure" do
10
- flunk
10
+ def create_driver(conf="")
11
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::ContainiqOutput).configure(conf)
11
12
  end
12
13
 
13
- private
14
+ @@ENDPOINT_URL = "https://mock-endpoint"
15
+ @@CLUSTER_NAME = "default"
16
+ @@API_KEY = 1234
14
17
 
15
- def create_driver(conf)
16
- Fluent::Test::Driver::Output.new(Fluent::Plugin::ContainiqOutput).configure(conf)
18
+ sub_test_case "configuration" do
19
+ test "missing endpoint url throws an error" do
20
+ begin
21
+ create_driver()
22
+ rescue => e
23
+ assert_kind_of Fluent::ConfigError, e
24
+ end
25
+ end
26
+
27
+ test "missing api key throws an error" do
28
+ ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
29
+ begin
30
+ create_driver()
31
+ rescue => e
32
+ assert_kind_of Fluent::ConfigError, e
33
+ end
34
+ end
35
+
36
+ test "complete configuration succeeds" do
37
+ ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
38
+ driver = create_driver(%[
39
+ api_key @@API_KEY
40
+ ])
41
+ assert_not_nil driver
42
+ end
17
43
  end
44
+
45
+ sub_test_case "test send to containiq api happy path" do
46
+ test "test api key is included in request header" do
47
+ ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
48
+ stub_request(:post, @@ENDPOINT_URL)
49
+ driver = create_driver(%[
50
+ api_key @@API_KEY
51
+ ])
52
+
53
+ driver.run do
54
+ driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
55
+ end
56
+
57
+ assert_requested :post, @@ENDPOINT_URL, times: 1, headers: {
58
+ 'Authorization'=>'Bearer @@API_KEY'
59
+ }
60
+ end
61
+
62
+ test "test body is gzipped by default" do
63
+ ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
64
+ stub_request(:post, @@ENDPOINT_URL)
65
+ driver = create_driver(%[
66
+ api_key @@API_KEY
67
+ ])
68
+
69
+ driver.run do
70
+ driver.feed('output.test', Time.now.to_i, {'message' => 'Test message'})
71
+ end
72
+
73
+ assert_requested :post, @@ENDPOINT_URL, times: 1, headers: {
74
+ 'Content-Encoding'=>'gzip'
75
+ }
76
+ end
77
+
78
+ test "test one log sent successfully" do
79
+ ENV["INGEST_LOGS_ENDPOINT_URL"] = @@ENDPOINT_URL
80
+ stub_request(:post, @@ENDPOINT_URL).to_return(status: 200)
81
+ # need to disable gzip so the request body is legible
82
+ driver = create_driver(%[
83
+ api_key @@API_KEY
84
+ gzip false
85
+ ])
86
+ time = Time.now
87
+
88
+ driver.run do
89
+ driver.feed('output.test', time.to_i, {'message' => 'Test message'})
90
+ end
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' )}\",\"cluster\":\"default\"}]"
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}\",\"cluster\":\"default\"},\
120
+ {\"message\":\"Test message 2\",\"timestamp\":\"#{time_isoformat}\",\"cluster\":\"default\"},\
121
+ {\"message\":\"Test message 3\",\"timestamp\":\"#{time_isoformat}\",\"cluster\":\"default\"}]"
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
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.7
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - ContainIQ
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-10 00:00:00.000000000 Z
11
+ date: 2021-12-17 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
@@ -129,6 +143,7 @@ files:
129
143
  - k8s/plugins/parser_kubernetes.rb
130
144
  - k8s/plugins/parser_multiline_kubernetes.rb
131
145
  - lib/fluent/plugin/out_containiq.rb
146
+ - pull_request_template.md
132
147
  - test/helper.rb
133
148
  - test/plugin/test_out_containiq.rb
134
149
  homepage: https://www.containiq.com/