txgh-server 1.0.0.beta1
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.
- checksums.yaml +7 -0
- data/lib/txgh-server/application.rb +141 -0
- data/lib/txgh-server/download_handler.rb +85 -0
- data/lib/txgh-server/github_request_auth.rb +28 -0
- data/lib/txgh-server/response.rb +15 -0
- data/lib/txgh-server/response_helpers.rb +26 -0
- data/lib/txgh-server/stream_response.rb +37 -0
- data/lib/txgh-server/tgz_stream_response.rb +39 -0
- data/lib/txgh-server/transifex_request_auth.rb +53 -0
- data/lib/txgh-server/triggers/handler.rb +50 -0
- data/lib/txgh-server/triggers/pull_handler.rb +18 -0
- data/lib/txgh-server/triggers/push_handler.rb +18 -0
- data/lib/txgh-server/triggers.rb +7 -0
- data/lib/txgh-server/version.rb +3 -0
- data/lib/txgh-server/webhooks/github/delete_handler.rb +37 -0
- data/lib/txgh-server/webhooks/github/handler.rb +20 -0
- data/lib/txgh-server/webhooks/github/ping_handler.rb +18 -0
- data/lib/txgh-server/webhooks/github/push_handler.rb +124 -0
- data/lib/txgh-server/webhooks/github/request_handler.rb +113 -0
- data/lib/txgh-server/webhooks/github.rb +11 -0
- data/lib/txgh-server/webhooks/transifex/hook_handler.rb +94 -0
- data/lib/txgh-server/webhooks/transifex/request_handler.rb +78 -0
- data/lib/txgh-server/webhooks/transifex.rb +8 -0
- data/lib/txgh-server/webhooks.rb +6 -0
- data/lib/txgh-server/zip_stream_response.rb +19 -0
- data/lib/txgh-server.rb +23 -0
- data/spec/application_spec.rb +347 -0
- data/spec/download_handler_spec.rb +91 -0
- data/spec/github_request_auth_spec.rb +39 -0
- data/spec/helpers/github_payload_builder.rb +141 -0
- data/spec/helpers/integration_setup.rb +47 -0
- data/spec/integration/cassettes/github_l10n_hook_endpoint.yml +536 -0
- data/spec/integration/cassettes/pull.yml +47 -0
- data/spec/integration/cassettes/push.yml +544 -0
- data/spec/integration/cassettes/transifex_hook_endpoint.yml +221 -0
- data/spec/integration/config/tx.config +10 -0
- data/spec/integration/hooks_spec.rb +159 -0
- data/spec/integration/payloads/github_postbody.json +161 -0
- data/spec/integration/payloads/github_postbody_l10n.json +136 -0
- data/spec/integration/payloads/github_postbody_release.json +136 -0
- data/spec/integration/triggers_spec.rb +45 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tgz_stream_response_spec.rb +59 -0
- data/spec/transifex_request_auth_spec.rb +39 -0
- data/spec/webhooks/github/delete_handler_spec.rb +38 -0
- data/spec/webhooks/github/ping_handler_spec.rb +16 -0
- data/spec/webhooks/github/push_handler_spec.rb +106 -0
- data/spec/webhooks/transifex/hook_handler_spec.rb +136 -0
- data/spec/zip_stream_response_spec.rb +58 -0
- data/txgh-server.gemspec +24 -0
- metadata +170 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
module TxghServer
|
|
5
|
+
module Webhooks
|
|
6
|
+
module Github
|
|
7
|
+
class PushHandler < Handler
|
|
8
|
+
|
|
9
|
+
def execute
|
|
10
|
+
# Check if the branch in the hook data is the configured branch we want
|
|
11
|
+
logger.info("request github branch: #{branch}")
|
|
12
|
+
logger.info("config github branch: #{repo.github_config_branch}")
|
|
13
|
+
|
|
14
|
+
if should_process?
|
|
15
|
+
logger.info('found branch in github request')
|
|
16
|
+
|
|
17
|
+
tx_resources = tx_resources_for(branch)
|
|
18
|
+
|
|
19
|
+
modified_resources = added_and_modified_resources_for(tx_resources)
|
|
20
|
+
modified_resources += l10n_resources_for(tx_resources)
|
|
21
|
+
|
|
22
|
+
if repo.github_config_branch.include?('tags/')
|
|
23
|
+
modified_resources += tag_resources_for(tx_resources)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Handle DBZ 'L10N' special case
|
|
27
|
+
if branch.include?("L10N")
|
|
28
|
+
logger.info('processing L10N tag')
|
|
29
|
+
|
|
30
|
+
# Create a new branch off tag commit
|
|
31
|
+
if branch.include?('tags/L10N')
|
|
32
|
+
repo.api.create_ref(repo.name, 'heads/L10N', payload['head_commit']['id'])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
updater = Txgh::ResourceUpdater.new(project, repo, logger)
|
|
37
|
+
categories = { 'author' => payload['head_commit']['committer']['name'] }
|
|
38
|
+
ref = repo.api.get_ref(repo.name, branch)
|
|
39
|
+
|
|
40
|
+
modified_resources.each do |resource|
|
|
41
|
+
updater.update_resource(resource, ref[:object][:sha], categories)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
respond_with(200, true)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def tag_resources_for(tx_resources)
|
|
51
|
+
payload['head_commit']['modified'].each_with_object(Set.new) do |modified, ret|
|
|
52
|
+
logger.info("processing modified file: #{modified}")
|
|
53
|
+
|
|
54
|
+
if tx_resources.include?(modified)
|
|
55
|
+
ret << tx_resources[modified]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def l10n_resources_for(tx_resources)
|
|
61
|
+
payload['head_commit']['modified'].each_with_object(Set.new) do |modified, ret|
|
|
62
|
+
if tx_resources.include?(modified)
|
|
63
|
+
logger.info("setting new resource: #{tx_resources[modified].L10N_resource_slug}")
|
|
64
|
+
ret << tx_resources[modified]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# finds the resources that were updated in each commit
|
|
70
|
+
def added_and_modified_resources_for(tx_resources)
|
|
71
|
+
payload['commits'].each_with_object(Set.new) do |commit, ret|
|
|
72
|
+
logger.info('processing commit')
|
|
73
|
+
|
|
74
|
+
(commit['modified'] + commit['added']).each do |file|
|
|
75
|
+
logger.info("processing added/modified file: #{file}")
|
|
76
|
+
|
|
77
|
+
if tx_resources.include?(file)
|
|
78
|
+
ret << tx_resources[file]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Build an index of known Tx resources, by source file
|
|
85
|
+
def tx_resources_for(branch)
|
|
86
|
+
tx_config.resources.each_with_object({}) do |resource, ret|
|
|
87
|
+
logger.info('processing resource')
|
|
88
|
+
|
|
89
|
+
# If we're processing by branch, create a branch resource. Otherwise,
|
|
90
|
+
# use the original resource.
|
|
91
|
+
ret[resource.source_file] = if repo.process_all_branches?
|
|
92
|
+
Txgh::TxBranchResource.new(resource, branch) # maybe find instead?
|
|
93
|
+
else
|
|
94
|
+
resource
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def tx_config
|
|
100
|
+
@tx_config ||= Txgh::Config::TxManager.tx_config(project, repo, branch)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def branch
|
|
104
|
+
@ref ||= payload['ref'].sub(/^refs\//, '')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def should_process?
|
|
108
|
+
should_process_branch? && should_process_commit?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def should_process_branch?
|
|
112
|
+
repo.should_process_ref?(branch)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def should_process_commit?
|
|
116
|
+
# return false if 'after' commit sha is all zeroes (indicates branch
|
|
117
|
+
# has been deleted)
|
|
118
|
+
!(payload.fetch('after', '') =~ /\A0+\z/)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module TxghServer
|
|
4
|
+
module Webhooks
|
|
5
|
+
module Github
|
|
6
|
+
class RequestHandler
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
include ResponseHelpers
|
|
10
|
+
|
|
11
|
+
def handle_request(request, logger)
|
|
12
|
+
case request.env['HTTP_X_GITHUB_EVENT']
|
|
13
|
+
when 'push'
|
|
14
|
+
handle_push(request, logger)
|
|
15
|
+
when 'delete'
|
|
16
|
+
handle_delete(request, logger)
|
|
17
|
+
when 'ping'
|
|
18
|
+
handle_ping(request, logger)
|
|
19
|
+
else
|
|
20
|
+
handle_unexpected
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def handle_push(request, logger)
|
|
27
|
+
klass = TxghServer::Webhooks::Github::PushHandler
|
|
28
|
+
new(request, logger).handle(klass)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def handle_delete(request, logger)
|
|
32
|
+
klass = TxghServer::Webhooks::Github::DeleteHandler
|
|
33
|
+
new(request, logger).handle(klass)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handle_ping(request, logger)
|
|
37
|
+
klass = TxghServer::Webhooks::Github::PingHandler
|
|
38
|
+
new(request, logger).handle(klass)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_unexpected
|
|
42
|
+
respond_with_error(400, 'Unexpected event type')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
include ResponseHelpers
|
|
48
|
+
|
|
49
|
+
attr_reader :request, :logger
|
|
50
|
+
|
|
51
|
+
def initialize(request, logger)
|
|
52
|
+
@request = request
|
|
53
|
+
@logger = logger
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def handle(klass)
|
|
57
|
+
handle_safely do
|
|
58
|
+
handler = klass.new(
|
|
59
|
+
project: config.transifex_project,
|
|
60
|
+
repo: config.github_repo,
|
|
61
|
+
payload: payload,
|
|
62
|
+
logger: logger
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
handler.execute
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def handle_safely
|
|
72
|
+
if authentic_request?
|
|
73
|
+
yield
|
|
74
|
+
else
|
|
75
|
+
respond_with_error(401, 'Unauthorized')
|
|
76
|
+
end
|
|
77
|
+
rescue => e
|
|
78
|
+
respond_with_error(500, "Internal server error: #{e.message}", e)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def payload
|
|
82
|
+
@payload ||= if request.params[:payload]
|
|
83
|
+
JSON.parse(request.params[:payload])
|
|
84
|
+
else
|
|
85
|
+
JSON.parse(request.body.read)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def github_repo_name
|
|
90
|
+
payload['repository']['full_name']
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def config
|
|
94
|
+
@config ||= Txgh::Config::KeyManager.config_from_repo(github_repo_name)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def repo
|
|
98
|
+
config.github_repo
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def authentic_request?
|
|
102
|
+
if repo.webhook_protected?
|
|
103
|
+
GithubRequestAuth.authentic_request?(
|
|
104
|
+
request, repo.webhook_secret
|
|
105
|
+
)
|
|
106
|
+
else
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module TxghServer
|
|
2
|
+
module Webhooks
|
|
3
|
+
module Github
|
|
4
|
+
autoload :DeleteHandler, 'txgh-server/webhooks/github/delete_handler'
|
|
5
|
+
autoload :Handler, 'txgh-server/webhooks/github/handler'
|
|
6
|
+
autoload :PingHandler, 'txgh-server/webhooks/github/ping_handler'
|
|
7
|
+
autoload :PushHandler, 'txgh-server/webhooks/github/push_handler'
|
|
8
|
+
autoload :RequestHandler, 'txgh-server/webhooks/github/request_handler'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module TxghServer
|
|
4
|
+
module Webhooks
|
|
5
|
+
module Transifex
|
|
6
|
+
class HookHandler
|
|
7
|
+
include Txgh::CategorySupport
|
|
8
|
+
include ResponseHelpers
|
|
9
|
+
|
|
10
|
+
attr_reader :project, :repo, :resource_slug, :language, :logger
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@project = options.fetch(:project)
|
|
14
|
+
@repo = options.fetch(:repo)
|
|
15
|
+
@resource_slug = options.fetch(:resource_slug)
|
|
16
|
+
@language = options.fetch(:language)
|
|
17
|
+
@logger = options.fetch(:logger) { Logger.new(STDOUT) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def execute
|
|
21
|
+
logger.info(resource_slug)
|
|
22
|
+
|
|
23
|
+
check_error_response || begin
|
|
24
|
+
committer = Txgh::ResourceCommitter.new(project, repo, logger)
|
|
25
|
+
committer.commit_resource(tx_resource, branch, language)
|
|
26
|
+
respond_with(200, true)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def check_error_response
|
|
33
|
+
check_supported_language || check_tx_config || check_tx_resource
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def check_supported_language
|
|
37
|
+
respond_with(304, true) unless supported_language?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def check_tx_config
|
|
41
|
+
unless tx_config
|
|
42
|
+
respond_with_error(
|
|
43
|
+
404, "Could not find configuration for branch '#{branch}'"
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_tx_resource
|
|
49
|
+
unless tx_resource
|
|
50
|
+
respond_with_error(
|
|
51
|
+
404, "Could not find resource '#{resource_slug}' in config"
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def tx_config
|
|
57
|
+
@tx_config ||= Txgh::Config::TxManager.tx_config(project, repo, branch)
|
|
58
|
+
rescue ConfigNotFoundError, TxghError
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def branch
|
|
63
|
+
@branch ||= begin
|
|
64
|
+
branch_candidate = if process_all_branches?
|
|
65
|
+
resource = project.api.get_resource(project.name, resource_slug)
|
|
66
|
+
categories = deserialize_categories(Array(resource['categories']))
|
|
67
|
+
categories['branch']
|
|
68
|
+
else
|
|
69
|
+
repo.branch || 'master'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
Txgh::Utils.absolute_branch(branch_candidate)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def tx_resource
|
|
77
|
+
@tx_resource ||= if process_all_branches?
|
|
78
|
+
tx_config.resource(resource_slug, branch)
|
|
79
|
+
else
|
|
80
|
+
tx_config.resource(resource_slug)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def process_all_branches?
|
|
85
|
+
repo.process_all_branches?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def supported_language?
|
|
89
|
+
project.supported_language?(language)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module TxghServer
|
|
4
|
+
module Webhooks
|
|
5
|
+
module Transifex
|
|
6
|
+
class RequestHandler
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
def handle_request(request, logger)
|
|
10
|
+
new(request, logger).handle(TxghServer::Webhooks::Transifex::HookHandler)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
include ResponseHelpers
|
|
16
|
+
|
|
17
|
+
attr_reader :request, :logger
|
|
18
|
+
|
|
19
|
+
def initialize(request, logger)
|
|
20
|
+
@request = request
|
|
21
|
+
@logger = logger
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def handle(klass)
|
|
25
|
+
handle_safely do
|
|
26
|
+
handler = klass.new(
|
|
27
|
+
project: config.transifex_project,
|
|
28
|
+
repo: config.github_repo,
|
|
29
|
+
resource_slug: payload['resource'],
|
|
30
|
+
language: payload['language'],
|
|
31
|
+
logger: logger
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
handler.execute
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def handle_safely
|
|
41
|
+
if authentic_request?
|
|
42
|
+
yield
|
|
43
|
+
else
|
|
44
|
+
respond_with_error(401, 'Unauthorized')
|
|
45
|
+
end
|
|
46
|
+
rescue => e
|
|
47
|
+
respond_with_error(500, "Internal server error: #{e.message}", e)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def authentic_request?
|
|
51
|
+
if project.webhook_protected?
|
|
52
|
+
TransifexRequestAuth.authentic_request?(
|
|
53
|
+
request, project.webhook_secret
|
|
54
|
+
)
|
|
55
|
+
else
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def project
|
|
61
|
+
config.transifex_project
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def config
|
|
65
|
+
@config ||= Txgh::Config::KeyManager.config_from_project(payload['project'])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def payload
|
|
69
|
+
@payload ||= begin
|
|
70
|
+
request.body.rewind
|
|
71
|
+
Hash[URI.decode_www_form(request.body.read)]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'ext/zipline/output_stream'
|
|
2
|
+
|
|
3
|
+
module TxghServer
|
|
4
|
+
class ZipStreamResponse < StreamResponse
|
|
5
|
+
|
|
6
|
+
def write_to(stream)
|
|
7
|
+
Zipline::OutputStream.open(stream) do |zipfile|
|
|
8
|
+
enum.each do |file_name, contents|
|
|
9
|
+
zipfile.put_next_entry(file_name, contents.bytesize)
|
|
10
|
+
zipfile << contents
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def file_extension
|
|
16
|
+
'.zip'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/txgh-server.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'txgh'
|
|
2
|
+
|
|
3
|
+
module TxghServer
|
|
4
|
+
autoload :Application, 'txgh-server/application'
|
|
5
|
+
autoload :DownloadHandler, 'txgh-server/download_handler'
|
|
6
|
+
|
|
7
|
+
# @TODO: refactor/remove
|
|
8
|
+
autoload :WebhookEndpoints, 'txgh-server/application'
|
|
9
|
+
|
|
10
|
+
autoload :GithubRequestAuth, 'txgh-server/github_request_auth'
|
|
11
|
+
autoload :Response, 'txgh-server/response'
|
|
12
|
+
autoload :ResponseHelpers, 'txgh-server/response_helpers'
|
|
13
|
+
autoload :StreamResponse, 'txgh-server/stream_response'
|
|
14
|
+
autoload :TgzStreamResponse, 'txgh-server/tgz_stream_response'
|
|
15
|
+
autoload :TransifexRequestAuth, 'txgh-server/transifex_request_auth'
|
|
16
|
+
|
|
17
|
+
# @TODO: refactor/remove
|
|
18
|
+
autoload :TriggerEndpoints, 'txgh-server/application'
|
|
19
|
+
|
|
20
|
+
autoload :Triggers, 'txgh-server/triggers'
|
|
21
|
+
autoload :Webhooks, 'txgh-server/webhooks'
|
|
22
|
+
autoload :ZipStreamResponse, 'txgh-server/zip_stream_response'
|
|
23
|
+
end
|