fluent-plugin-feedly 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /pkg/
4
+ /tmp/
5
+ /vendor/
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-feedly.gemspec
4
+ gemspec
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012- Kentaro Yoshida
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,135 @@
1
+ # fluent-plugin-feedly
2
+
3
+ ## Overview
4
+
5
+ Fluentd input plugin to fetch RSS/ATOM feed via Feedly Could.
6
+
7
+ ## Dependencies
8
+
9
+ * Ruby 1.9.3+
10
+ * Fluentd 0.10.54+
11
+
12
+ ## Installation
13
+
14
+ install with gem or fluent-gem command as:
15
+
16
+ `````
17
+ # for system installed fluentd
18
+ $ gem install fluent-plugin-feedly
19
+
20
+ # for td-agent
21
+ $ sudo /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-feedly
22
+ `````
23
+
24
+ ## Configuration
25
+
26
+ `````
27
+ <source>
28
+ type feedly
29
+
30
+ # Set feedly access token
31
+ # ref. https://feedly.com/v3/auth/dev
32
+ access_token ACCESS_TOKEN # Required
33
+
34
+ # Set file-path to store last fetched article position
35
+ state_file /var/log/td-agent/feedly.state # Required
36
+
37
+ # Set output tag
38
+ tag input.feedly # Required
39
+
40
+ # List subscribe categories in your feedly account with JSON Array
41
+ subscribe_categories ["global.all"] # Optional (default: global.all)
42
+
43
+ # Set update checking frequency
44
+ run_interval 30m # Optional (default: 10m)
45
+
46
+ # Set bulk read size
47
+ fetch_count 20 # Optional (default: 20)
48
+
49
+ # fetching range of time within 30d
50
+ fetch_time_range 3d # Optional (default: 3d)
51
+
52
+ # fetching range of time for initial startup within 30d
53
+ fetch_time_range_on_startup 2w # Optional (default: 2w)
54
+
55
+ # Using sandbox account
56
+ enable_sandbox false # Optional (default: false)
57
+
58
+ # Set log level for this plugin. To see debug level, set 'debug' for this value.
59
+ # it can see at stdout as like `$ tail -f /var/log/td-agent/td-agent.log`
60
+ log_level info        # Optional (default: info)
61
+ </source>
62
+ `````
63
+
64
+ **note** : The `subscribe_categories` is also supported with single or multi line configuration like below.
65
+
66
+ ```
67
+ # single line
68
+ subscribe_categories ["先端技術", "mysql"]
69
+
70
+ # multi line
71
+ subscribe_categories [
72
+ "先端技術",
73
+ "mysql"
74
+ ]
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ After installed this plugin, executing fluentd with following configuration.
80
+
81
+ ```
82
+ $ cat /etc/td-agent/td-agent.conf
83
+ <source>
84
+ type feedly
85
+ access_token YOUR_ACCESS_TOKEN
86
+ state_file /var/log/td-agent/feedly.state
87
+ tag input.feedly
88
+ run_interval 30m
89
+ fetch_time_range 1h
90
+ fetch_time_range_on_startup 3h
91
+ log_level debug
92
+ </source>
93
+
94
+ <match input.feedly>
95
+ type file
96
+ path /tmp/feedly*.json
97
+ symlink_path /tmp/feedly.json
98
+ format json
99
+ append true
100
+ </match>
101
+ ```
102
+
103
+ You can see the behavior about this plugin with this command.
104
+
105
+ ```
106
+ # to check stdout of this plugin
107
+ $ tail -f /var/log/td-agent/td-agent.log
108
+ 2014-10-16 14:47:01 +0900 [debug]: Feedly: fetched articles. articles=416 request_option={:count=>1000, :continuation=>"148cfb7f516:9371a3c:726280cf", :newerThan=>1412228787000}
109
+ 2014-10-16 15:02:02 +0900 [debug]: Feedly: fetched articles. articles=492 request_option={:count=>1000, :continuation=>nil, :newerThan=>1413428521000}
110
+ ```
111
+
112
+ ```
113
+ # to check fetched articles
114
+ $ tail -f /tmp/feedly.json | jq "."
115
+ ```
116
+
117
+ ## TODO
118
+
119
+ Pull requests are very welcome!!
120
+
121
+ ## Copyright
122
+
123
+ Copyright © 2014- Kentaro Yoshida ([@yoshi_ken](https://twitter.com/yoshi_ken))
124
+
125
+ ## License
126
+
127
+ Apache License, Version 2.0
128
+
129
+ ## Contributing
130
+
131
+ 1. Fork it ( https://github.com/[my-github-username]/fluent-plugin-feedly/fork )
132
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
133
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
134
+ 4. Push to the branch (`git push origin my-new-feature`)
135
+ 5. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ Rake::TestTask.new(:test) do |test|
4
+ test.libs << 'lib' << 'test'
5
+ test.pattern = 'test/**/test_*.rb'
6
+ test.verbose = true
7
+ end
8
+
9
+ task :default => :test
10
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fluent-plugin-feedly"
7
+ #spec.version = Fluent::Plugin::Feedly::VERSION
8
+ spec.version = "0.0.1"
9
+ spec.authors = ["Kentaro Yoshida"]
10
+ spec.email = ["y.ken.studio@gmail.com"]
11
+ spec.summary = %q{Fluentd input plugin to fetch RSS/ATOM feed via Feedly Could.}
12
+ #spec.description = %q{TODO: Write a longer description. Optional.}
13
+ spec.homepage = "https://github.com/y-ken/fluent-plugin-feedly"
14
+ spec.license = "Apache 2.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "fluentd"#, "~> 0.10.54"
22
+ spec.add_runtime_dependency "feedlr"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
@@ -0,0 +1,148 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Fluent
4
+ class FeedlyInput < Fluent::Input
5
+ Plugin.register_input('feedly', self)
6
+
7
+ config_param :access_token, :string
8
+ config_param :state_file, :string
9
+ config_param :tag, :string
10
+
11
+ config_param :subscribe_categories, :array, :default => ['global.all']
12
+ config_param :run_interval, :time, :default => 60*10 #10m
13
+ config_param :fetch_count, :integer, :default => 20
14
+ config_param :fetch_time_range, :time, :default => 60*60*24*3 #3d
15
+ config_param :fetch_time_range_on_startup, :time, :default => 60*60*24*14 #2w
16
+ config_param :enable_sandbox, :bool, :default => false
17
+
18
+ unless method_defined?(:log)
19
+ define_method(:log) { $log }
20
+ end
21
+
22
+ def initialize
23
+ require 'feedlr'
24
+ require 'digest/sha2'
25
+
26
+ super
27
+ end
28
+
29
+ def configure(conf)
30
+ super
31
+
32
+ if not @fetch_count >= 20 && @fetch_count <= 10000
33
+ raise Fluent::ConfigError, "Feedly: fetch_count param (#{@fetch_count}) should be between 20 and 10000."
34
+ end
35
+
36
+ @client = Feedlr::Client.new(
37
+ oauth_access_token: @access_token,
38
+ sandbox: @enable_sandbox,
39
+ )
40
+ end
41
+
42
+ def start
43
+ @profile_id = @client.user_profile.id
44
+ @state_store = StateStore.new(@state_file)
45
+ @thread = Thread.new(&method(:run))
46
+ end
47
+
48
+ def shutdown
49
+ Thread.kill(@thread)
50
+ end
51
+
52
+ def run
53
+ @initial_loop = true
54
+ loop do
55
+ begin
56
+ fetch
57
+ rescue => e
58
+ log.error "Feedly: unexpected error has occoured.", :error => e.message, :error_class => e.class
59
+ log.error_backtrace e.backtrace
60
+ sleep @run_interval
61
+ retry
62
+ end
63
+ sleep @run_interval
64
+ end
65
+ end
66
+
67
+ def fetch
68
+ @subscribe_categories.each do |category_name|
69
+ category_id = "user/#{@profile_id}/category/#{category_name}"
70
+ fetch_time_range = get_fetch_time_range
71
+ loop {
72
+ request_option = { count: @fetch_count, continuation: get_continuation_id, newerThan: fetch_time_range }
73
+ cursor = @client.stream_entries_contents(category_id, request_option)
74
+ cursor.items.each do |item|
75
+ Engine.emit(@tag, Engine.now, item)
76
+ end
77
+ log.debug "Feedly: fetched articles.", articles: cursor.items.size, request_option: request_option
78
+ set_continuation_id(cursor.continuation)
79
+ break if get_continuation_id.nil?
80
+ }
81
+ end
82
+ end
83
+
84
+ def get_fetch_time_range
85
+ if @initial_loop
86
+ @initial_loop = false
87
+ range = @fetch_time_range_on_startup
88
+ else
89
+ range = @fetch_time_range
90
+ end
91
+ return (Time.now.to_i - range ) * 1000
92
+ end
93
+
94
+ def subscribe_categories_hash
95
+ Digest::SHA512.digest(@subscribe_categories.sort.join(''))
96
+ end
97
+
98
+ def set_continuation_id(continuation_id)
99
+ @state_store.set("continuation", {
100
+ id: continuation_id,
101
+ subscribe_categories_hash: subscribe_categories_hash
102
+ })
103
+ @state_store.update!
104
+ end
105
+
106
+ def get_continuation_id
107
+ record = @state_store.get('continuation')
108
+ if subscribe_categories_hash == record[:subscribe_categories_hash]
109
+ return record[:id]
110
+ else
111
+ return nil
112
+ end
113
+ end
114
+
115
+ # implementation has copied from its code.
116
+ # https://github.com/fluent/fluent-plugin-sql/blob/master/lib/fluent/plugin/in_sql.rb
117
+ class StateStore
118
+ def initialize(path)
119
+ @path = path
120
+ if File.exists?(@path)
121
+ @data = YAML.load_file(@path)
122
+ if @data == false || @data == []
123
+ # this happens if an users created an empty file accidentally
124
+ @data = {}
125
+ elsif !@data.is_a?(Hash)
126
+ raise "state_file on #{@path.inspect} is invalid"
127
+ end
128
+ else
129
+ @data = {}
130
+ end
131
+ end
132
+
133
+ def set(key, data)
134
+ @data.store(key.to_sym, data)
135
+ end
136
+
137
+ def get(key)
138
+ @data[key.to_sym] ||= {}
139
+ end
140
+
141
+ def update!
142
+ File.open(@path, 'w') {|f|
143
+ f.write YAML.dump(@data)
144
+ }
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluent/plugin/in_feedly'
26
+
27
+ class Test::Unit::TestCase
28
+ end
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ class FeedlyInputTest < Test::Unit::TestCase
4
+ def setup
5
+ Fluent::Test.setup
6
+ end
7
+
8
+ CONFIG = %[
9
+ access_token YOUR_ACCESS_TOKEN
10
+ state_file /var/log/td-agent/feedly.state
11
+ tag input.feedly
12
+ run_interval 30m
13
+ ]
14
+
15
+ def create_driver(conf=CONFIG,tag='test')
16
+ Fluent::Test::OutputTestDriver.new(Fluent::FeedlyInput, tag).configure(conf)
17
+ end
18
+
19
+ def test_configure
20
+ assert_raise(Fluent::ConfigError) {
21
+ d = create_driver('')
22
+ }
23
+ d = create_driver(CONFIG)
24
+ assert_equal 'YOUR_ACCESS_TOKEN', d.instance.access_token
25
+ assert_equal '/var/log/td-agent/feedly.state', d.instance.state_file
26
+ assert_equal 1800, d.instance.run_interval
27
+ assert_equal 'input.feedly', d.instance.tag
28
+ end
29
+ end
30
+
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-feedly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kentaro Yoshida
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fluentd
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: feedlr
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.7'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '10.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '10.0'
78
+ description:
79
+ email:
80
+ - y.ken.studio@gmail.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .travis.yml
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - fluent-plugin-feedly.gemspec
92
+ - lib/fluent/plugin/in_feedly.rb
93
+ - test/helper.rb
94
+ - test/plugin/test_in_mysql.rb
95
+ homepage: https://github.com/y-ken/fluent-plugin-feedly
96
+ licenses:
97
+ - Apache 2.0
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 1.8.23
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: Fluentd input plugin to fetch RSS/ATOM feed via Feedly Could.
120
+ test_files:
121
+ - test/helper.rb
122
+ - test/plugin/test_in_mysql.rb