fluent-plugin-cloud-feeds 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MThmZjRmNDA5N2I3OTJhNTI3OTljNDk0M2U1YjQ0ZTk5ZmY2NjY0OA==
5
+ data.tar.gz: !binary |-
6
+ MGEzYTNiMGRhZmViZGFjZTUwZTI1NTVhYzM5OTA5OTZiZTZkZTY4MQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MWYzOTExOGZjMDYwMGQyZjBkMDlmYTI2YTBmNDM0M2Y3YmJiNTcyZTY2MzBl
10
+ OTNjYWM5ZTc2MGFlNWIyZTg0M2IxY2Q0MDI1ZjQyZmIwYjM3YmVhYzA0YjJm
11
+ OWViY2UyOWI2ZjAyMzM5YTIzYWRlM2RmOGEwOTQ0YzE0YjE4Nzk=
12
+ data.tar.gz: !binary |-
13
+ M2ExNmUwZjRmZmVlODU2ZDc3NDY0M2ZkYjcyZDM0ZGFkOGEyYjZjZGFmNjM0
14
+ NTI1YzA0MzMwNTI4OTI2Yzk1OGVlMGRiN2NiMjFiNDg2ZTRhYTFmYmVhZmVj
15
+ ZjZkZmZiYjEyMWVmYjRhNmFlMmI2YTUwNTE4YjYwMjFkOWRkN2U=
@@ -0,0 +1,35 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ # Gemfile.lock
31
+ # .ruby-version
32
+ # .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Get our stuff from gemspec!
4
+ gemspec
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fluent-plugin-cloud-feeds (1.0.0)
5
+ fluentd (= 0.12.7)
6
+ net-http-persistent (>= 2.7)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.3.8)
12
+ cool.io (1.3.0)
13
+ crack (0.4.2)
14
+ safe_yaml (~> 1.0.0)
15
+ diff-lcs (1.2.5)
16
+ docile (1.1.5)
17
+ fluentd (0.12.7)
18
+ cool.io (>= 1.2.2, < 2.0.0)
19
+ http_parser.rb (>= 0.5.1, < 0.7.0)
20
+ json (>= 1.4.3)
21
+ msgpack (>= 0.5.11, < 0.6.0)
22
+ sigdump (~> 0.2.2)
23
+ string-scrub (>= 0.0.3)
24
+ tzinfo (>= 1.0.0)
25
+ tzinfo-data (>= 1.0.0)
26
+ yajl-ruby (~> 1.0)
27
+ http_parser.rb (0.6.0)
28
+ json (1.8.2)
29
+ msgpack (0.5.11)
30
+ net-http-persistent (2.9.4)
31
+ rspec (3.2.0)
32
+ rspec-core (~> 3.2.0)
33
+ rspec-expectations (~> 3.2.0)
34
+ rspec-mocks (~> 3.2.0)
35
+ rspec-core (3.2.3)
36
+ rspec-support (~> 3.2.0)
37
+ rspec-expectations (3.2.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.2.0)
40
+ rspec-mocks (3.2.1)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.2.0)
43
+ rspec-support (3.2.2)
44
+ safe_yaml (1.0.4)
45
+ sigdump (0.2.2)
46
+ simplecov (0.10.0)
47
+ docile (~> 1.1.0)
48
+ json (~> 1.8)
49
+ simplecov-html (~> 0.10.0)
50
+ simplecov-html (0.10.0)
51
+ string-scrub (0.0.5)
52
+ thread_safe (0.3.5)
53
+ tzinfo (1.2.2)
54
+ thread_safe (~> 0.1)
55
+ tzinfo-data (1.2015.3)
56
+ tzinfo (>= 1.0.0)
57
+ webmock (1.21.0)
58
+ addressable (>= 2.3.6)
59
+ crack (>= 0.3.2)
60
+ yajl-ruby (1.2.1)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ fluent-plugin-cloud-feeds!
67
+ rspec (= 3.2.0)
68
+ simplecov (= 0.10.0)
69
+ webmock (= 1.21.0)
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
@@ -0,0 +1,2 @@
1
+ # fluentd-feeds-output
2
+ Output for fluentd to an authenticated CloudFeeds endpoint
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,30 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "fluent-plugin-cloud-feeds"
5
+ s.version = "1.0.0"
6
+ s.authors = ["David Kowis", "Tyler Royal"]
7
+ s.email= ["david.kowis@rackspace.com", "tyler.royal@rackspace.com"]
8
+ s.homepage = "https://github.com/rackerlabs/fluentd-feeds-output"
9
+ s.summary = "Fluentd output plugin for output to Rackspace Cloud Feeds"
10
+ s.description = "Fluentd output plugin (fluentd.org) for output to Rackspace Cloud Feeds"
11
+
12
+ #TODO: maybe add rubyforge_project
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ # sticking to the same version of ruby as fluentd
19
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9.3")
20
+
21
+ # dependencies
22
+ s.add_dependency('net-http-persistent', '>= 2.7')
23
+
24
+ # for help from RubyMine!
25
+ s.add_runtime_dependency('fluentd', '0.12.7')
26
+
27
+ s.add_development_dependency('rspec', '3.2.0')
28
+ s.add_development_dependency('webmock', '1.21.0')
29
+ s.add_development_dependency('simplecov', '0.10.0')
30
+ end
@@ -0,0 +1,132 @@
1
+ =begin
2
+
3
+ Copyright (C) 2015 Rackspace
4
+
5
+ Licensed to the Apache Software Foundation (ASF) under one
6
+ or more contributor license agreements. See the NOTICE file
7
+ distributed with this work for additional information
8
+ regarding copyright ownership. The ASF licenses this file
9
+ to you under the Apache License, Version 2.0 (the
10
+ "License"); you may not use this file except in compliance
11
+ with the License. You may obtain a copy of the License at
12
+
13
+ http://www.apache.org/licenses/LICENSE-2.0
14
+
15
+ Unless required by applicable law or agreed to in writing,
16
+ software distributed under the License is distributed on an
17
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ KIND, either express or implied. See the License for the
19
+ specific language governing permissions and limitations
20
+ under the License.
21
+ =end
22
+
23
+ require 'date'
24
+
25
+ class Fluent::RackspaceCloudFeedsOutput < Fluent::Output
26
+ include Fluent::PluginLoggerMixin
27
+
28
+ Fluent::Plugin.register_output('rackspace_cloud_feeds', self)
29
+
30
+ config_param :identity_endpoint, :string, :default => nil
31
+ config_param :identity_username, :string, :default => nil
32
+ config_param :identity_password, :string, :default => nil
33
+
34
+ config_param :feeds_endpoint, :string, :default => nil
35
+
36
+
37
+ def configure(conf)
38
+ super
39
+ $log.debug(" Identity endpoint: #{@identity_endpoint}")
40
+ $log.debug(" Identity username: #{@identity_username}")
41
+ $log.debug("Cloud Feeds endpoint: #{@feeds_endpoint}")
42
+ end
43
+
44
+ def start
45
+ super
46
+ require 'net/http/persistent'
47
+
48
+ @feeds_uri = URI @feeds_endpoint
49
+ @feeds_http = Net::HTTP::Persistent.new 'fluent-feeds-output'
50
+
51
+ end
52
+
53
+ def shutdown
54
+ super
55
+ end
56
+
57
+ ##
58
+ # either get a token back from identity, or poop the pants
59
+ # noinspection RubyStringKeysInHashInspection
60
+ def authenticate_user
61
+ uri = URI @identity_endpoint
62
+ http = Net::HTTP.new(uri.host, uri.port)
63
+ if uri.scheme == 'https'
64
+ http.use_ssl = true
65
+ end
66
+ req = Net::HTTP::Post.new(uri.path)
67
+ content = {
68
+ 'auth' => {
69
+ 'passwordCredentials' => {
70
+ 'username' => @identity_username,
71
+ 'password' => @identity_password
72
+ }
73
+ }
74
+ }
75
+ req.body = content.to_json
76
+ req['content-type'] = 'application/json'
77
+ req['accept'] = 'application/json'
78
+ res = http.request(req)
79
+
80
+ case res
81
+ when Net::HTTPSuccess
82
+ # Get the token
83
+ JSON.parse(res.body)['access']['token']['id']
84
+ else
85
+ raise "Unable to authenticate with identity at #{@identity_endpoint} as #{@identity_username}"
86
+ end
87
+ end
88
+
89
+ ##
90
+ # putting content into an atom entry document
91
+ def atomic_wrapper(content, time)
92
+ # date format
93
+ now = DateTime.strptime(time.to_s, '%s').strftime("%FT%T.%LZ")
94
+
95
+ <<EOF
96
+ <entry xmlns="http://www.w3.org/2005/Atom">
97
+ <id>#{SecureRandom.uuid.to_s}</id>
98
+ <title type="text">User Access Event</title>
99
+ <author><name>Repose</name></author>
100
+ <updated>#{now}</updated>
101
+ <content type="text">#{content}</content>
102
+ </entry>
103
+ EOF
104
+ end
105
+
106
+
107
+ def emit(tag, es, chain)
108
+ es.each { |time, record|
109
+ # take the data, put it in to an abdera envelope
110
+ post = Net::HTTP::Post.new @feeds_uri.path
111
+ post.body = atomic_wrapper(record, time)
112
+ unless @auth_token
113
+ #get new auth token
114
+ @auth_token = authenticate_user
115
+ end
116
+ post['x-auth-token'] = @auth_token
117
+
118
+ begin
119
+ response = @feeds_http.request @feeds_uri, post
120
+ if response.code !~ /2\d\d/
121
+ @auth_token = nil
122
+ raise "NOT AUTHORIZED TO POST TO FEED ENDPOINT #{@feeds_endpoint}"
123
+ end
124
+ $log.debug "FEEDS RESPONSE CODE #{response.code}"
125
+ $log.error "FEEDS RESPONSE BODY: #{response.body}" if response.code !~ /2\d\d/
126
+ end
127
+ }
128
+
129
+ chain.next
130
+ end
131
+
132
+ end
@@ -0,0 +1,233 @@
1
+ require 'spec_helper'
2
+
3
+ require 'fluent/test'
4
+ require 'fluent/plugin/out_rackspace_feeds'
5
+
6
+
7
+ require 'webmock/rspec'
8
+ require 'rexml/document'
9
+ require 'json'
10
+
11
+
12
+ WebMock.disable_net_connect!
13
+
14
+ RSpec.describe('Rackspace Cloud Feeds output plugin') do
15
+
16
+ FEED_URL = 'https://www.feed.com/the/best/feed'
17
+ IDENTITY_URL = 'https://www.identity.com/authenticate/token'
18
+
19
+ before :example do
20
+ Fluent::Test.setup
21
+ @driver = nil
22
+
23
+ $log.out.logs
24
+ end
25
+
26
+ def driver(tag='test', conf='')
27
+ @driver ||= Fluent::Test::OutputTestDriver.new(Fluent::RackspaceCloudFeedsOutput, tag).configure(conf)
28
+ end
29
+
30
+ def simple_sample_payload
31
+ <<EOF
32
+ { "GUID" : "87669264-3fc2-4e6e-b225-2f79f17d14c9", "ServiceCode" : "", "Region" : "", "DataCenter" : "", "Cluster" : "cluster", "Node" : "node", "RequestorIp" : "127.0.0.1", "Timestamp" : "1429546010984", "CadfTimestamp" : "2015-04-20T11:06:50.984-05:00", "Request" : { "Method" : "GET", "MethodLabel" : "", "CadfMethod" : "read/get", "URL" : "", "TargetHost" : "", "QueryString" : "", "Parameters" : { }, "UserName" : "", "ImpersonatorName" : "", "DefaultProjectID" : "", "ProjectID" : [ ], "Roles" : [ ], "UserAgent" : "" }, "Response" : { "Code" : 200, "CadfOutcome" : "success", "Message" : "OK" } }
33
+ EOF
34
+ end
35
+
36
+ def less_simple_payload
37
+ <<EOF
38
+ { "GUID" : "9b2ac70c-16c9-493e-85be-d26a39319c2b", "ServiceCode" : "repose", "Region" : "USA", "DataCenter" : "DFW", "Timestamp" : "1429546468047", "Request" : { "Method" : "GET", "URL" : "http://localhost:10006/", "QueryString" : "", "Parameters" : { }, "UserName" : "", "ImpersonatorName" : "", "ProjectID" : [ ], "Roles" : [ ], "UserAgent" : "deproxy 0.21" }, "Response" : { "Code" : 200, "Message" : "OK" }, "MethodLabel" : "" }
39
+ EOF
40
+ end
41
+
42
+ def stub_atom_post(content = "")
43
+ stub_request(:post, FEED_URL).with do |request|
44
+ assert_proper_atom_payload(request.body, content)
45
+ end
46
+ end
47
+
48
+ def assert_proper_atom_payload(payload, content)
49
+ doc = REXML::Document.new(payload)
50
+
51
+ expect(REXML::XPath.first(doc, "/entry/id")).not_to be_nil
52
+ title = REXML::XPath.first(doc, "/entry/title").text
53
+ expect(title).to eq("User Access Event")
54
+
55
+ expect(REXML::XPath.first(doc, "/entry/author/name").text).to eq("Repose")
56
+ expect(REXML::XPath.first(doc, "/entry/updated").text).to match(/\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ/)
57
+ expect(REXML::XPath.first(doc, "/entry/content").text).to eq(content)
58
+ end
59
+
60
+ def print_logs
61
+ $log.out.logs.each do |l|
62
+ puts l
63
+ end
64
+ end
65
+
66
+ def stub_identity_auth_post(token)
67
+ stub_request(:post, IDENTITY_URL).with do |request|
68
+ payload = JSON.parse(request.body)
69
+
70
+ # NOTE: the header matching is case sensitive!
71
+ payload['auth']['passwordCredentials']['username'] != nil and
72
+ payload['auth']['passwordCredentials']['password'] != nil and
73
+ request.headers['Content-Type'] == 'application/json' and
74
+ request.headers['Accept'] == 'application/json'
75
+ end.to_return do |request|
76
+ {:body => {'access' => {'token' => {'id' => token}}}.to_json}
77
+ end
78
+ end
79
+
80
+
81
+ context 'a record from fluentd to cloud feeds' do
82
+
83
+ it "should register as rackspace_cloud_feeds" do
84
+ driver.configure("feeds_endpoint #{FEED_URL}")
85
+ expect($log.out.logs).to include(/.*registered output plugin 'rackspace_cloud_feeds'.*/)
86
+ end
87
+
88
+ it "authenticates with identity and posts a payload to feeds" do
89
+ driver.configure(<<EOF
90
+ identity_endpoint #{IDENTITY_URL}
91
+ identity_username fakeuser
92
+ identity_password best_password
93
+ feeds_endpoint #{FEED_URL}
94
+ EOF
95
+ )
96
+ driver.run
97
+
98
+ token = SecureRandom.uuid.to_s
99
+
100
+ stub_identity_auth_post(token)
101
+ stub_atom_post(simple_sample_payload)
102
+
103
+ driver.emit(simple_sample_payload)
104
+
105
+ expect(a_request(:post, IDENTITY_URL).with do |req|
106
+ parsed = JSON.parse(req.body)
107
+ parsed['auth']['passwordCredentials']['username'] == 'fakeuser' and
108
+ parsed['auth']['passwordCredentials']['password'] == 'best_password'
109
+ end).to have_been_made.once
110
+
111
+ expect(a_request(:post, FEED_URL).with do |req|
112
+ req.headers['X-Auth-Token'] == token
113
+ end).to have_been_made.once
114
+ end
115
+
116
+ it "will fail the post if the response is 4xx clearing the auth token" do
117
+ driver.configure(<<EOF
118
+ identity_endpoint #{IDENTITY_URL}
119
+ identity_username fakeuser
120
+ identity_password best_password
121
+ feeds_endpoint #{FEED_URL}
122
+ EOF
123
+ )
124
+ driver.run
125
+
126
+ token = "FIRST TOKEN"
127
+ stub_identity_auth_post(token)
128
+
129
+ stub_atom_post(simple_sample_payload)
130
+ driver.emit(simple_sample_payload)
131
+
132
+
133
+ expect(a_request(:post, FEED_URL).with do |req|
134
+ req.headers['X-Auth-Token'] == token
135
+ end).to have_been_made.once
136
+
137
+
138
+ #set up feeds with a 403 failure
139
+ stub_request(:post, FEED_URL).with do |request|
140
+ assert_proper_atom_payload(request.body, simple_sample_payload)
141
+ end.to_return do |req|
142
+ {:status => 403}
143
+ end
144
+
145
+ new_token = "SECOND TOKEN"
146
+
147
+ # simulate behavior from the bufferize plugin, which will retry the call eventually
148
+ expect {
149
+ driver.emit(simple_sample_payload)
150
+ }.to raise_exception(/NOT AUTHORIZED TO POST TO FEED ENDPOINT.+/)
151
+
152
+ #expect another request to identity
153
+ stub_identity_auth_post(new_token)
154
+ stub_atom_post(simple_sample_payload)
155
+
156
+ #simulate the thing being called again
157
+ driver.emit(simple_sample_payload)
158
+
159
+ expect(a_request(:post, IDENTITY_URL).with do |req|
160
+ parsed = JSON.parse(req.body)
161
+ parsed['auth']['passwordCredentials']['username'] == 'fakeuser' and
162
+ parsed['auth']['passwordCredentials']['password'] == 'best_password'
163
+ end).to have_been_made.twice
164
+
165
+ expect(a_request(:post, FEED_URL).with do |req|
166
+ req.headers['X-Auth-Token'] == new_token
167
+ end).to have_been_made.once
168
+
169
+ end
170
+
171
+ it "raises an exception if unable to authenticate with identity" do
172
+ driver.configure(<<EOF
173
+ identity_endpoint #{IDENTITY_URL}
174
+ identity_username fakeuser
175
+ identity_password best_password
176
+ feeds_endpoint #{FEED_URL}
177
+ EOF
178
+ )
179
+ driver.run
180
+
181
+ stub_request(:post, IDENTITY_URL).with do |request|
182
+ payload = JSON.parse(request.body)
183
+
184
+ # NOTE: the header matching is case sensitive!
185
+ payload['auth']['passwordCredentials']['username'] != nil and
186
+ payload['auth']['passwordCredentials']['password'] != nil and
187
+ request.headers['Content-Type'] == 'application/json' and
188
+ request.headers['Accept'] == 'application/json'
189
+ end.to_return do |request|
190
+ {:status => 400}
191
+ end
192
+
193
+ expect {
194
+ driver.emit(simple_sample_payload)
195
+ }.to raise_exception(/Unable to authenticate with identity at.+/)
196
+
197
+ expect(WebMock).not_to have_requested(:post, FEED_URL)
198
+ end
199
+
200
+ it "should publish the event using the time given when it entered fluentd" do
201
+ driver.configure(<<EOF
202
+ identity_endpoint #{IDENTITY_URL}
203
+ identity_username fakeuser
204
+ identity_password best_password
205
+ feeds_endpoint #{FEED_URL}
206
+ EOF
207
+ )
208
+ driver.run
209
+
210
+ token = "loltoken"
211
+ #stub an identity
212
+ stub_identity_auth_post(token)
213
+
214
+ # just a basic stub for the post to feeds
215
+ stub_request(:post, FEED_URL)
216
+
217
+
218
+ current_time = Time.now - 10000
219
+
220
+
221
+ driver.emit(simple_sample_payload, current_time)
222
+
223
+ expect(a_request(:post, FEED_URL).with do |req|
224
+ doc = REXML::Document.new(req.body)
225
+
226
+ updated = REXML::XPath.first(doc, "/entry/updated").text
227
+ expected_time = DateTime.strptime(current_time.to_i.to_s, '%s').strftime("%FT%T.%LZ")
228
+ updated == expected_time
229
+ end).to have_been_made.once
230
+
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,4 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_group "lib", "lib"
4
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-cloud-feeds
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Kowis
8
+ - Tyler Royal
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-04-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-http-persistent
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '2.7'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '2.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: fluentd
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '='
33
+ - !ruby/object:Gem::Version
34
+ version: 0.12.7
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '='
40
+ - !ruby/object:Gem::Version
41
+ version: 0.12.7
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '='
47
+ - !ruby/object:Gem::Version
48
+ version: 3.2.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '='
54
+ - !ruby/object:Gem::Version
55
+ version: 3.2.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: webmock
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '='
61
+ - !ruby/object:Gem::Version
62
+ version: 1.21.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.21.0
70
+ - !ruby/object:Gem::Dependency
71
+ name: simplecov
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '='
75
+ - !ruby/object:Gem::Version
76
+ version: 0.10.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '='
82
+ - !ruby/object:Gem::Version
83
+ version: 0.10.0
84
+ description: Fluentd output plugin (fluentd.org) for output to Rackspace Cloud Feeds
85
+ email:
86
+ - david.kowis@rackspace.com
87
+ - tyler.royal@rackspace.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - .gitignore
93
+ - .rspec
94
+ - Gemfile
95
+ - Gemfile.lock
96
+ - LICENSE
97
+ - README.md
98
+ - Rakefile
99
+ - fluent-plugin-cloud-feeds.gemspec
100
+ - lib/fluent/plugin/out_rackspace_feeds.rb
101
+ - spec/feeds_output_spec.rb
102
+ - spec/spec_helper.rb
103
+ homepage: https://github.com/rackerlabs/fluentd-feeds-output
104
+ licenses: []
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: 1.9.3
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.4.6
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Fluentd output plugin for output to Rackspace Cloud Feeds
126
+ test_files:
127
+ - spec/feeds_output_spec.rb
128
+ - spec/spec_helper.rb