fluent-plugin-github-activities 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -5
- data/fluent-plugin-github-activities.gemspec +3 -2
- data/lib/fluent/plugin/github-activities.rb +5 -3
- data/lib/fluent/plugin/github-activities/crawler.rb +260 -249
- data/lib/fluent/plugin/github-activities/users_manager.rb +45 -65
- data/lib/fluent/plugin/in_github-activities.rb +96 -78
- data/test/fixture/piroor-events.json +47 -0
- data/test/fixture/users.txt +2 -0
- data/test/plugin/test_in_github_activity.rb +85 -0
- data/test/run-test.rb +1 -0
- data/test/test_crawler.rb +132 -109
- metadata +26 -7
- data/lib/fluent/plugin/github-activities/safe_file_writer.rb +0 -46
@@ -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
|
27
|
-
|
28
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
77
|
-
|
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
|
-
|
85
|
-
|
62
|
+
def save_position_for(user, params)
|
63
|
+
position = @pos_storage.get(user) || {}
|
86
64
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
return unless @pos_file.exist?
|
65
|
+
if params[:entity_tag]
|
66
|
+
position["entity_tag"] = params[:entity_tag]
|
67
|
+
end
|
91
68
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
93
|
-
def prepare_users_list
|
94
|
-
@users ||= ""
|
95
|
-
users = @users.split(",")
|
116
|
+
private
|
96
117
|
|
97
|
-
|
98
|
-
|
99
|
-
if users_list
|
100
|
-
|
101
|
-
|
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
|
-
|
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,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
|