txgh-server 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/lib/txgh-server/application.rb +141 -0
  3. data/lib/txgh-server/download_handler.rb +85 -0
  4. data/lib/txgh-server/github_request_auth.rb +28 -0
  5. data/lib/txgh-server/response.rb +15 -0
  6. data/lib/txgh-server/response_helpers.rb +26 -0
  7. data/lib/txgh-server/stream_response.rb +37 -0
  8. data/lib/txgh-server/tgz_stream_response.rb +39 -0
  9. data/lib/txgh-server/transifex_request_auth.rb +53 -0
  10. data/lib/txgh-server/triggers/handler.rb +50 -0
  11. data/lib/txgh-server/triggers/pull_handler.rb +18 -0
  12. data/lib/txgh-server/triggers/push_handler.rb +18 -0
  13. data/lib/txgh-server/triggers.rb +7 -0
  14. data/lib/txgh-server/version.rb +3 -0
  15. data/lib/txgh-server/webhooks/github/delete_handler.rb +37 -0
  16. data/lib/txgh-server/webhooks/github/handler.rb +20 -0
  17. data/lib/txgh-server/webhooks/github/ping_handler.rb +18 -0
  18. data/lib/txgh-server/webhooks/github/push_handler.rb +124 -0
  19. data/lib/txgh-server/webhooks/github/request_handler.rb +113 -0
  20. data/lib/txgh-server/webhooks/github.rb +11 -0
  21. data/lib/txgh-server/webhooks/transifex/hook_handler.rb +94 -0
  22. data/lib/txgh-server/webhooks/transifex/request_handler.rb +78 -0
  23. data/lib/txgh-server/webhooks/transifex.rb +8 -0
  24. data/lib/txgh-server/webhooks.rb +6 -0
  25. data/lib/txgh-server/zip_stream_response.rb +19 -0
  26. data/lib/txgh-server.rb +23 -0
  27. data/spec/application_spec.rb +347 -0
  28. data/spec/download_handler_spec.rb +91 -0
  29. data/spec/github_request_auth_spec.rb +39 -0
  30. data/spec/helpers/github_payload_builder.rb +141 -0
  31. data/spec/helpers/integration_setup.rb +47 -0
  32. data/spec/integration/cassettes/github_l10n_hook_endpoint.yml +536 -0
  33. data/spec/integration/cassettes/pull.yml +47 -0
  34. data/spec/integration/cassettes/push.yml +544 -0
  35. data/spec/integration/cassettes/transifex_hook_endpoint.yml +221 -0
  36. data/spec/integration/config/tx.config +10 -0
  37. data/spec/integration/hooks_spec.rb +159 -0
  38. data/spec/integration/payloads/github_postbody.json +161 -0
  39. data/spec/integration/payloads/github_postbody_l10n.json +136 -0
  40. data/spec/integration/payloads/github_postbody_release.json +136 -0
  41. data/spec/integration/triggers_spec.rb +45 -0
  42. data/spec/spec_helper.rb +26 -0
  43. data/spec/tgz_stream_response_spec.rb +59 -0
  44. data/spec/transifex_request_auth_spec.rb +39 -0
  45. data/spec/webhooks/github/delete_handler_spec.rb +38 -0
  46. data/spec/webhooks/github/ping_handler_spec.rb +16 -0
  47. data/spec/webhooks/github/push_handler_spec.rb +106 -0
  48. data/spec/webhooks/transifex/hook_handler_spec.rb +136 -0
  49. data/spec/zip_stream_response_spec.rb +58 -0
  50. data/txgh-server.gemspec +24 -0
  51. 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,8 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Transifex
4
+ autoload :HookHandler, 'txgh-server/webhooks/transifex/hook_handler'
5
+ autoload :RequestHandler, 'txgh-server/webhooks/transifex/request_handler'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ autoload :Github, 'txgh-server/webhooks/github'
4
+ autoload :Transifex, 'txgh-server/webhooks/transifex'
5
+ end
6
+ 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
@@ -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