fluent-plugin-github-activities 0.6.1 → 0.7.0

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.
@@ -17,86 +17,66 @@
17
17
  # License along with fluent-plugin-github-activities. If not, see
18
18
  # <http://www.gnu.org/licenses/>.
19
19
 
20
- require "pathname"
21
20
  require "json"
22
21
 
23
- require "fluent/plugin/github-activities/safe_file_writer"
24
-
25
22
  module Fluent
26
- module GithubActivities
27
- class UsersManager
28
- DEFAULT_LAST_EVENT_TIMESTAMP = -1
29
-
30
- def initialize(params={})
31
- @users = params[:users]
23
+ module Plugin
24
+ module GithubActivities
25
+ class UsersManager
26
+ DEFAULT_LAST_EVENT_TIMESTAMP = -1
32
27
 
33
- @positions = {}
34
- @pos_file = params[:pos_file]
35
- @pos_file = Pathname(@pos_file) if @pos_file
36
- end
37
-
38
- def generate_initial_requests
39
- @users.collect do |user|
40
- new_events_request(user)
28
+ def initialize(params={})
29
+ @users = params[:users]
30
+ @pos_storage = params[:pos_storage]
41
31
  end
42
- end
43
32
 
44
- def new_events_request(user, options={})
45
- request = {
46
- :type => TYPE_EVENTS,
47
- :user => user,
48
- }
49
- response = options[:previous_response]
50
- if response
51
- now = options[:now] || Time.now
52
- interval = response["X-Poll-Interval"].to_i
53
- time_to_process = now.to_i + interval
54
- request[:previous_entity_tag] = response["ETag"] ||
55
- options[:previous_entity_tag]
56
- request[:process_after] = time_to_process
57
- else
58
- request[:previous_entity_tag] = options[:previous_entity_tag] if options.key?(:previous_entity_tag)
33
+ def generate_initial_requests
34
+ @users.collect do |user|
35
+ new_events_request(user)
36
+ end
59
37
  end
60
- request
61
- end
62
-
63
- def position_for(user)
64
- load_positions
65
- @positions[user]
66
- end
67
38
 
68
- def save_position_for(user, params)
69
- load_positions
70
- @positions[user] ||= {}
71
-
72
- if params[:entity_tag]
73
- @positions[user]["entity_tag"] = params[:entity_tag]
39
+ def new_events_request(user, options={})
40
+ request = {
41
+ type: TYPE_EVENTS,
42
+ user: user,
43
+ }
44
+ response = options[:previous_response]
45
+ if response
46
+ now = options[:now] || Time.now
47
+ interval = response["X-Poll-Interval"].to_i
48
+ time_to_process = now.to_i + interval
49
+ request[:previous_entity_tag] = response["ETag"] ||
50
+ options[:previous_entity_tag]
51
+ request[:process_after] = time_to_process
52
+ else
53
+ request[:previous_entity_tag] = options[:previous_entity_tag] if options.key?(:previous_entity_tag)
54
+ end
55
+ request
74
56
  end
75
57
 
76
- if params[:last_event_timestamp] and
77
- params[:last_event_timestamp] != DEFAULT_LAST_EVENT_TIMESTAMP
78
- old_timestamp = @positions[user]["last_event_timestamp"]
79
- if old_timestamp.nil? or old_timestamp < params[:last_event_timestamp]
80
- @positions[user]["last_event_timestamp"] = params[:last_event_timestamp]
81
- end
58
+ def position_for(user)
59
+ @pos_storage.get(user)
82
60
  end
83
61
 
84
- save_positions
85
- end
62
+ def save_position_for(user, params)
63
+ position = @pos_storage.get(user) || {}
86
64
 
87
- private
88
- def load_positions
89
- return unless @pos_file
90
- return unless @pos_file.exist?
65
+ if params[:entity_tag]
66
+ position["entity_tag"] = params[:entity_tag]
67
+ end
91
68
 
92
- @positions = JSON.parse(@pos_file.read)
93
- rescue
94
- @positions = {}
95
- end
69
+ if params[:last_event_timestamp] and
70
+ params[:last_event_timestamp] != DEFAULT_LAST_EVENT_TIMESTAMP
71
+ old_timestamp = position["last_event_timestamp"]
72
+ if old_timestamp.nil? or old_timestamp < params[:last_event_timestamp]
73
+ position["last_event_timestamp"] = params[:last_event_timestamp]
74
+ end
75
+ end
96
76
 
97
- def save_positions
98
- return unless @pos_file
99
- SafeFileWriter.write(@pos_file, JSON.pretty_generate(@positions))
77
+ @pos_storage.put(user, position)
78
+ @pos_storage.save
79
+ end
100
80
  end
101
81
  end
102
82
  end
@@ -17,98 +17,116 @@
17
17
  # License along with fluent-plugin-github-activities. If not, see
18
18
  # <http://www.gnu.org/licenses/>.
19
19
 
20
- module Fluent
21
- class GithubActivitiesInput < Input
22
- DEFAULT_BASE_TAG = "github-activity"
23
- DEFAULT_CLIENTS = 4
24
-
25
- Plugin.register_input("github-activities", self)
26
-
27
- config_param :access_token, :string, :default => nil, :secret => true
28
- config_param :users, :string, :default => nil
29
- config_param :users_list, :string, :default => nil
30
- config_param :include_commits_from_pull_request, :bool, :default => false
31
- config_param :include_foreign_commits, :bool, :default => false
32
- config_param :base_tag, :string, :default => DEFAULT_BASE_TAG
33
- config_param :pos_file, :string, :default => nil
34
- config_param :clients, :integer, :default => DEFAULT_CLIENTS
35
- config_param :interval, :integer, :default => 1
36
-
37
- def initialize
38
- super
39
-
40
- require "thread"
41
- require "pathname"
42
- require "fluent/plugin/github-activities"
43
- end
44
-
45
- def start
46
- @base_tag = @base_tag.sub(/\.\z/, "")
47
-
48
- users = prepare_users_list
49
- n_clients = [@clients, users.size].min
50
- @interval = @interval * n_clients
20
+ require "thread"
21
+ require "pathname"
22
+ require "fluent/plugin/input"
23
+ require "fluent/plugin/github-activities"
51
24
 
52
- @client_threads = []
53
- @request_queue = Queue.new
25
+ module Fluent
26
+ module Plugin
27
+ class GithubActivitiesInput < Fluent::Plugin::Input
28
+ DEFAULT_BASE_TAG = "github-activity"
29
+ DEFAULT_CLIENTS = 4
30
+ DEFAULT_STORAGE_TYPE = "local"
31
+
32
+ helpers :thread, :storage
33
+
34
+ Fluent::Plugin.register_input("github-activities", self)
35
+
36
+ config_param :access_token, :string, default: nil, secret: true
37
+ config_param :users, :array, default: []
38
+ config_param :users_list, :string, default: nil
39
+ config_param :include_commits_from_pull_request, :bool, default: false
40
+ config_param :include_foreign_commits, :bool, default: false
41
+ config_param :base_tag, :string, default: DEFAULT_BASE_TAG
42
+ config_param :pos_file, :string, default: nil, obsoleted: "Use storage instead."
43
+ config_param :clients, :integer, default: DEFAULT_CLIENTS
44
+ config_param :interval, :integer, default: 1
45
+
46
+ config_section :storage do
47
+ config_set_default :usage, "in-github-activities"
48
+ config_set_default :@type, DEFAULT_STORAGE_TYPE
49
+ end
54
50
 
55
- users_manager_params = {
56
- :users => users,
57
- :pos_file => @pos_file,
58
- }
59
- users_manager = ::Fluent::GithubActivities::UsersManager.new(users_manager_params)
60
- users_manager.generate_initial_requests.each do |request|
61
- @request_queue.push(request)
51
+ def configure(conf)
52
+ super
53
+
54
+ @base_tag = @base_tag.sub(/\.\z/, "")
55
+ @users += load_users_list
56
+ @n_clients = [@clients, @users.size].min
57
+ @interval = @interval * @n_clients
58
+ raise Fluent::ConfigError, "You can define <storage> section at once" unless @storage_configs.size == 1
59
+ storage_section = @storage_configs.first
60
+ storage_config = storage_section.corresponding_config_element
61
+ @pos_storage = storage_create(usage: storage_section.usage,
62
+ conf: storage_config,
63
+ default_type: DEFAULT_STORAGE_TYPE)
62
64
  end
63
65
 
64
- n_clients.times do
65
- @client_threads << Thread.new do
66
- crawler_options = {
67
- :access_token => @access_token,
68
- :watching_users => users,
69
- :include_commits_from_pull_request => @include_commits_from_pull_request,
70
- :include_foreign_commits => @include_foreign_commits,
71
- :pos_file => @pos_file,
72
- :request_queue => @request_queue,
73
- :default_interval => @interval,
74
- }
75
- crawler = ::Fluent::GithubActivities::Crawler.new(crawler_options)
76
- crawler.on_emit = lambda do |tag, record|
77
- router.emit("#{@base_tag}.#{tag}", Engine.now, record)
78
- end
66
+ def start
67
+ super
68
+
69
+ @request_queue = Queue.new
70
+ @crawlers = []
79
71
 
80
- loop do
81
- crawler.process_request
82
- sleep(crawler.interval_for_next_request)
72
+ users_manager_params = {
73
+ users: @users,
74
+ pos_storage: @pos_storage,
75
+ }
76
+ users_manager = ::Fluent::Plugin::GithubActivities::UsersManager.new(users_manager_params)
77
+ users_manager.generate_initial_requests.each do |request|
78
+ @request_queue.push(request)
79
+ end
80
+ @n_clients.times do |n|
81
+ thread_create("in_github_activity_#{n}".to_sym) do
82
+ crawler_options = {
83
+ access_token: @access_token,
84
+ watching_users: @users,
85
+ include_commits_from_pull_request: @include_commits_from_pull_request,
86
+ include_foreign_commits: @include_foreign_commits,
87
+ pos_storage: @pos_storage,
88
+ request_queue: @request_queue,
89
+ default_interval: @interval,
90
+ log: log
91
+ }
92
+ crawler = ::Fluent::Plugin::GithubActivities::Crawler.new(crawler_options)
93
+ @crawlers << crawler
94
+ crawler.on_emit = lambda do |tag, record|
95
+ router.emit("#{@base_tag}.#{tag}", Engine.now, record)
96
+ end
97
+
98
+ loop do
99
+ crawler.process_request
100
+ break unless crawler.running
101
+ sleep(crawler.interval_for_next_request)
102
+ end
83
103
  end
84
104
  end
85
105
  end
86
- end
87
106
 
88
- def shutdown
89
- @client_threads.each(&:exit)
90
- end
107
+ def shutdown
108
+ until @request_queue.empty?
109
+ log.debug(queue_size: @request_queue.size)
110
+ sleep(@interval)
111
+ end
112
+ @crawlers.each(&:stop)
113
+ super
114
+ end
91
115
 
92
- private
93
- def prepare_users_list
94
- @users ||= ""
95
- users = @users.split(",")
116
+ private
96
117
 
97
- if @users_list
98
- users_list = Pathname(@users_list)
99
- if users_list.exist?
100
- list = users_list.read
101
- users += list.split("\n")
118
+ def load_users_list
119
+ users = []
120
+ if @users_list
121
+ users_list = Pathname(@users_list)
122
+ if users_list.exist?
123
+ list = users_list.read
124
+ users += list.split("\n")
125
+ end
102
126
  end
103
- end
104
127
 
105
- users = users.collect do |user|
106
- user.strip
107
- end.reject do |user|
108
- user.empty?
128
+ users.collect(&:strip).reject(&:empty?)
109
129
  end
110
-
111
- users
112
130
  end
113
131
  end
114
132
  end
@@ -0,0 +1,47 @@
1
+ [
2
+ {
3
+ "id": "2823041920",
4
+ "type": "PushEvent",
5
+ "actor": {
6
+ "id": 70062,
7
+ "login": "piroor",
8
+ "gravatar_id": "",
9
+ "url": "https://api.github.com/users/piroor",
10
+ "avatar_url": "https://avatars.githubusercontent.com/u/70062?"
11
+ },
12
+ "repo": {
13
+ "id": 35922279,
14
+ "name": "clear-code/fluent-plugin-github-activities",
15
+ "url": "https://api.github.com/repos/clear-code/fluent-plugin-github-activities"
16
+ },
17
+ "payload": {
18
+ "push_id": 670478406,
19
+ "size": 1,
20
+ "distinct_size": 1,
21
+ "ref": "refs/heads/master",
22
+ "head": "c908f319c7b6d5c5a69c8b675bde40dd990ee364",
23
+ "before": "e77acc068b5568f8ea55605db2e3a9005805c898",
24
+ "commits": [
25
+ {
26
+ "sha": "8e90721ff5d89f52b5b3adf0b86db01f03dc5588",
27
+ "author": {
28
+ "email": "shimoda@clear-code.com",
29
+ "name": "YUKI Hiroshi"
30
+ },
31
+ "message": "Add missing constant",
32
+ "distinct": true,
33
+ "url": "https://api.github.com/repos/clear-code/fluent-plugin-github-activities/commits/8e90721ff5d89f52b5b3adf0b86db01f03dc5588"
34
+ }
35
+ ]
36
+ },
37
+ "public": true,
38
+ "created_at": "2015-05-21T05:37:34Z",
39
+ "org": {
40
+ "id": 176515,
41
+ "login": "clear-code",
42
+ "gravatar_id": "",
43
+ "url": "https://api.github.com/orgs/clear-code",
44
+ "avatar_url": "https://avatars.githubusercontent.com/u/176515?"
45
+ }
46
+ }
47
+ ]
@@ -0,0 +1,2 @@
1
+ okkez
2
+ cosmo0920
@@ -0,0 +1,85 @@
1
+ require "fluent/plugin/in_github-activities"
2
+ require "fluent/test"
3
+ require "fluent/test/helpers"
4
+ require "fluent/test/driver/input"
5
+
6
+ class TestGithubActivitiesInput < Test::Unit::TestCase
7
+ include Fluent::Test::Helpers
8
+
9
+ setup do
10
+ Fluent::Test.setup
11
+ end
12
+
13
+ def create_driver(conf)
14
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::GithubActivitiesInput).configure(conf)
15
+ end
16
+
17
+ sub_test_case "configure" do
18
+ test "empty" do
19
+ d = create_driver(config_element)
20
+ plugin = d.instance
21
+ storage = plugin._storages["in-github-activities"].storage
22
+ assert { storage.path.nil? }
23
+ assert { !storage.persistent }
24
+ end
25
+
26
+ test "obsoleted pos_file" do
27
+ conf = config_element("ROOT", "", { "pos_file" => "/tmp/pos_file.json" })
28
+ assert_raise Fluent::ObsoletedParameterError do
29
+ create_driver(conf)
30
+ end
31
+ end
32
+
33
+ data("end with .json" => ["/tmp/test.json", "/tmp/test.json"],
34
+ "end with .pos" => ["/tmp/test.pos", "/tmp/test.pos/worker0/storage.json"])
35
+ test "persistent storage with path" do |(path, expected_path)|
36
+ storage_conf = config_element(
37
+ "storage",
38
+ "in-github-activities",
39
+ { "@type" => "local", "persistent" => true, "path" => path }
40
+ )
41
+ conf = config_element("ROOT", "", {}, [storage_conf])
42
+ d = create_driver(conf)
43
+ plugin = d.instance
44
+ storage = plugin._storages["in-github-activities"].storage
45
+ assert_equal(expected_path, storage.path)
46
+ assert { storage.persistent }
47
+ end
48
+
49
+ data(single: ["piroor", ["piroor"]],
50
+ multiple: ["okkez,cosmo0920", ["okkez", "cosmo0920"]])
51
+ test "users" do |(users, expected_users)|
52
+ config = config_element("ROOT", "", { "users" => users })
53
+ d = create_driver(config)
54
+ plugin = d.instance
55
+ assert_equal(expected_users, plugin.users)
56
+ end
57
+
58
+ test "users_list" do
59
+ config = config_element("ROOT", "", { "users_list" => fixture_path("users.txt") })
60
+ d = create_driver(config)
61
+ plugin = d.instance
62
+ assert_equal(["okkez", "cosmo0920"], plugin.users)
63
+ end
64
+ end
65
+
66
+ sub_test_case "emit" do
67
+ test "simple" do
68
+ user_events_template = Addressable::Template.new("https://api.github.com/users/{user}/events/public")
69
+ stub_request(:get, user_events_template)
70
+ .to_return(body: File.open(fixture_path("piroor-events.json")), status: 200)
71
+ commits_template = Addressable::Template.new("https://api.github.com/repos/{owner}/{project}/commits/{commit_hash}")
72
+ stub_request(:get, commits_template)
73
+ .to_return(body: File.open(fixture_path("commit.json")), status: 200)
74
+ config = config_element("ROOT", "", { "users" => "piroor", "@log_level" => "trace" })
75
+ d = create_driver(config)
76
+ d.run(timeout: 1, expect_emits: 2)
77
+ tag, _time, record = d.events[0]
78
+ assert_equal("github-activity.commit", tag)
79
+ assert_equal("8e90721ff5d89f52b5b3adf0b86db01f03dc5588", record["sha"])
80
+ tag, _time, record = d.events[1]
81
+ assert_equal("github-activity.push", tag)
82
+ assert_equal("2823041920", record["id"])
83
+ end
84
+ end
85
+ end