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.
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