txgh 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +64 -0
  4. data/lib/ext/zipline/output_stream.rb +62 -0
  5. data/lib/txgh.rb +53 -0
  6. data/lib/txgh/app.rb +135 -0
  7. data/lib/txgh/category_support.rb +31 -0
  8. data/lib/txgh/config.rb +11 -0
  9. data/lib/txgh/config/config_pair.rb +36 -0
  10. data/lib/txgh/config/key_manager.rb +54 -0
  11. data/lib/txgh/config/provider_instance.rb +20 -0
  12. data/lib/txgh/config/provider_support.rb +26 -0
  13. data/lib/txgh/config/providers.rb +9 -0
  14. data/lib/txgh/config/providers/file_provider.rb +19 -0
  15. data/lib/txgh/config/providers/git_provider.rb +58 -0
  16. data/lib/txgh/config/providers/raw_provider.rb +19 -0
  17. data/lib/txgh/config/tx_config.rb +77 -0
  18. data/lib/txgh/config/tx_manager.rb +15 -0
  19. data/lib/txgh/diff_calculator.rb +90 -0
  20. data/lib/txgh/empty_resource_contents.rb +43 -0
  21. data/lib/txgh/errors.rb +9 -0
  22. data/lib/txgh/github_api.rb +83 -0
  23. data/lib/txgh/github_repo.rb +88 -0
  24. data/lib/txgh/github_request_auth.rb +28 -0
  25. data/lib/txgh/handlers.rb +12 -0
  26. data/lib/txgh/handlers/download_handler.rb +84 -0
  27. data/lib/txgh/handlers/github.rb +10 -0
  28. data/lib/txgh/handlers/github/delete_handler.rb +65 -0
  29. data/lib/txgh/handlers/github/handler.rb +20 -0
  30. data/lib/txgh/handlers/github/push_handler.rb +108 -0
  31. data/lib/txgh/handlers/github/request_handler.rb +106 -0
  32. data/lib/txgh/handlers/response.rb +17 -0
  33. data/lib/txgh/handlers/stream_response.rb +39 -0
  34. data/lib/txgh/handlers/tgz_stream_response.rb +41 -0
  35. data/lib/txgh/handlers/transifex.rb +8 -0
  36. data/lib/txgh/handlers/transifex/hook_handler.rb +77 -0
  37. data/lib/txgh/handlers/transifex/request_handler.rb +78 -0
  38. data/lib/txgh/handlers/triggers.rb +9 -0
  39. data/lib/txgh/handlers/triggers/handler.rb +66 -0
  40. data/lib/txgh/handlers/triggers/pull_handler.rb +29 -0
  41. data/lib/txgh/handlers/triggers/push_handler.rb +21 -0
  42. data/lib/txgh/handlers/zip_stream_response.rb +21 -0
  43. data/lib/txgh/merge_calculator.rb +74 -0
  44. data/lib/txgh/parse_config.rb +24 -0
  45. data/lib/txgh/resource_committer.rb +39 -0
  46. data/lib/txgh/resource_contents.rb +118 -0
  47. data/lib/txgh/resource_downloader.rb +141 -0
  48. data/lib/txgh/resource_updater.rb +104 -0
  49. data/lib/txgh/response_helpers.rb +30 -0
  50. data/lib/txgh/transifex_api.rb +165 -0
  51. data/lib/txgh/transifex_project.rb +37 -0
  52. data/lib/txgh/transifex_request_auth.rb +53 -0
  53. data/lib/txgh/tx_branch_resource.rb +59 -0
  54. data/lib/txgh/tx_logger.rb +12 -0
  55. data/lib/txgh/tx_resource.rb +66 -0
  56. data/lib/txgh/utils.rb +44 -0
  57. data/lib/txgh/version.rb +3 -0
  58. data/spec/app_spec.rb +346 -0
  59. data/spec/category_support_spec.rb +43 -0
  60. data/spec/config/config_pair_spec.rb +47 -0
  61. data/spec/config/key_manager_spec.rb +48 -0
  62. data/spec/config/provider_instance_spec.rb +30 -0
  63. data/spec/config/provider_support_spec.rb +55 -0
  64. data/spec/config/tx_config_spec.rb +49 -0
  65. data/spec/config/tx_manager_spec.rb +57 -0
  66. data/spec/diff_calculator_spec.rb +90 -0
  67. data/spec/github_api_spec.rb +148 -0
  68. data/spec/github_repo_spec.rb +178 -0
  69. data/spec/github_request_auth_spec.rb +39 -0
  70. data/spec/handlers/download_handler_spec.rb +81 -0
  71. data/spec/handlers/github/delete_handler_spec.rb +71 -0
  72. data/spec/handlers/github/push_handler_spec.rb +76 -0
  73. data/spec/handlers/tgz_stream_response_spec.rb +59 -0
  74. data/spec/handlers/transifex/hook_handler_spec.rb +115 -0
  75. data/spec/handlers/zip_stream_response_spec.rb +58 -0
  76. data/spec/helpers/github_payload_builder.rb +141 -0
  77. data/spec/helpers/integration_setup.rb +47 -0
  78. data/spec/helpers/nil_logger.rb +10 -0
  79. data/spec/helpers/standard_txgh_setup.rb +92 -0
  80. data/spec/helpers/test_provider.rb +12 -0
  81. data/spec/integration/cassettes/github_l10n_hook_endpoint.yml +536 -0
  82. data/spec/integration/cassettes/pull.yml +47 -0
  83. data/spec/integration/cassettes/push.yml +544 -0
  84. data/spec/integration/cassettes/transifex_hook_endpoint.yml +560 -0
  85. data/spec/integration/config/tx.config +10 -0
  86. data/spec/integration/hooks_spec.rb +158 -0
  87. data/spec/integration/payloads/github_postbody.json +161 -0
  88. data/spec/integration/payloads/github_postbody_l10n.json +136 -0
  89. data/spec/integration/payloads/github_postbody_release.json +136 -0
  90. data/spec/integration/triggers_spec.rb +45 -0
  91. data/spec/merge_calculator_spec.rb +112 -0
  92. data/spec/parse_config_spec.rb +52 -0
  93. data/spec/resource_committer_spec.rb +42 -0
  94. data/spec/resource_contents_spec.rb +212 -0
  95. data/spec/resource_downloader_spec.rb +205 -0
  96. data/spec/resource_updater_spec.rb +147 -0
  97. data/spec/spec_helper.rb +32 -0
  98. data/spec/transifex_api_spec.rb +345 -0
  99. data/spec/transifex_project_spec.rb +45 -0
  100. data/spec/transifex_request_auth_spec.rb +39 -0
  101. data/spec/tx_branch_resource_spec.rb +99 -0
  102. data/spec/tx_resource_spec.rb +47 -0
  103. data/spec/utils_spec.rb +58 -0
  104. data/txgh.gemspec +29 -0
  105. metadata +296 -0
@@ -0,0 +1,108 @@
1
+ require 'base64'
2
+
3
+ module Txgh
4
+ module Handlers
5
+ module Github
6
+ class PushHandler < Handler
7
+
8
+ def execute
9
+ # Check if the branch in the hook data is the configured branch we want
10
+ logger.info("request github branch: #{branch}")
11
+ logger.info("config github branch: #{repo.github_config_branch}")
12
+
13
+ if repo.should_process_ref?(branch)
14
+ logger.info('found branch in github request')
15
+
16
+ tx_resources = tx_resources_for(branch)
17
+ modified_resources = added_and_modified_resources_for(tx_resources)
18
+ modified_resources.merge!(l10n_resources_for(tx_resources))
19
+
20
+ if repo.github_config_branch.include?('tags/')
21
+ modified_resources.merge!(tag_resources_for(tx_resources))
22
+ end
23
+
24
+ # Handle DBZ 'L10N' special case
25
+ if branch.include?("L10N")
26
+ logger.info('processing L10N tag')
27
+
28
+ # Create a new branch off tag commit
29
+ if branch.include?('tags/L10N')
30
+ repo.api.create_ref(repo.name, 'heads/L10N', payload['head_commit']['id'])
31
+ end
32
+ end
33
+
34
+ updater = ResourceUpdater.new(project, repo, logger)
35
+ categories = { 'author' => payload['head_commit']['committer']['name'] }
36
+
37
+ modified_resources.each_pair do |resource, commit_sha|
38
+ updater.update_resource(resource, commit_sha, categories)
39
+ end
40
+ end
41
+
42
+ respond_with(200, true)
43
+ end
44
+
45
+ private
46
+
47
+ def tag_resources_for(tx_resources)
48
+ payload['head_commit']['modified'].each_with_object({}) do |modified, ret|
49
+ logger.info("processing modified file: #{modified}")
50
+
51
+ if tx_resources.include?(modified)
52
+ ret[tx_resources[modified]] = payload['head_commit']['id']
53
+ end
54
+ end
55
+ end
56
+
57
+ def l10n_resources_for(tx_resources)
58
+ payload['head_commit']['modified'].each_with_object({}) do |modified, ret|
59
+ if tx_resources.include?(modified)
60
+ logger.info("setting new resource: #{tx_resources[modified].L10N_resource_slug}")
61
+ ret[tx_resources[modified]] = payload['head_commit']['id']
62
+ end
63
+ end
64
+ end
65
+
66
+ # Finds the updated resources and maps the most recent commit in which
67
+ # each was modified
68
+ def added_and_modified_resources_for(tx_resources)
69
+ payload['commits'].each_with_object({}) do |commit, ret|
70
+ logger.info('processing commit')
71
+
72
+ (commit['modified'] + commit['added']).each do |file|
73
+ logger.info("processing added/modified file: #{file}")
74
+
75
+ if tx_resources.include?(file)
76
+ ret[tx_resources[file]] = commit['id']
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Build an index of known Tx resources, by source file
83
+ def tx_resources_for(branch)
84
+ tx_config.resources.each_with_object({}) do |resource, ret|
85
+ logger.info('processing resource')
86
+
87
+ # If we're processing by branch, create a branch resource. Otherwise,
88
+ # use the original resource.
89
+ ret[resource.source_file] = if repo.process_all_branches?
90
+ TxBranchResource.new(resource, branch) # maybe find instead?
91
+ else
92
+ resource
93
+ end
94
+ end
95
+ end
96
+
97
+ def tx_config
98
+ @tx_config ||= Txgh::Config::TxManager.tx_config(project, repo, branch)
99
+ end
100
+
101
+ def branch
102
+ @ref ||= payload['ref'].sub(/^refs\//, '')
103
+ end
104
+
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,106 @@
1
+ require 'json'
2
+
3
+ module Txgh
4
+ module Handlers
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
+ else
18
+ handle_unexpected
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def handle_push(request, logger)
25
+ klass = Txgh::Handlers::Github::PushHandler
26
+ new(request, logger).handle(klass)
27
+ end
28
+
29
+ def handle_delete(request, logger)
30
+ klass = Txgh::Handlers::Github::DeleteHandler
31
+ new(request, logger).handle(klass)
32
+ end
33
+
34
+ def handle_unexpected
35
+ respond_with_error(400, 'Unexpected event type')
36
+ end
37
+
38
+ end
39
+
40
+ include ResponseHelpers
41
+
42
+ attr_reader :request, :logger
43
+
44
+ def initialize(request, logger)
45
+ @request = request
46
+ @logger = logger
47
+ end
48
+
49
+ def handle(klass)
50
+ handle_safely do
51
+ handler = klass.new(
52
+ project: config.transifex_project,
53
+ repo: config.github_repo,
54
+ payload: payload,
55
+ logger: logger
56
+ )
57
+
58
+ handler.execute
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def handle_safely
65
+ if authentic_request?
66
+ yield
67
+ else
68
+ respond_with_error(401, 'Unauthorized')
69
+ end
70
+ rescue => e
71
+ respond_with_error(500, "Internal server error: #{e.message}", e)
72
+ end
73
+
74
+ def payload
75
+ @payload ||= if request.params[:payload]
76
+ JSON.parse(request.params[:payload])
77
+ else
78
+ JSON.parse(request.body.read)
79
+ end
80
+ end
81
+
82
+ def github_repo_name
83
+ payload['repository']['full_name']
84
+ end
85
+
86
+ def config
87
+ @config ||= Txgh::Config::KeyManager.config_from_repo(github_repo_name)
88
+ end
89
+
90
+ def repo
91
+ config.github_repo
92
+ end
93
+
94
+ def authentic_request?
95
+ if repo.webhook_protected?
96
+ GithubRequestAuth.authentic_request?(
97
+ request, repo.webhook_secret
98
+ )
99
+ else
100
+ true
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,17 @@
1
+ module Txgh
2
+ module Handlers
3
+ class Response
4
+ attr_reader :status, :body, :error
5
+
6
+ def initialize(status, body, error = nil)
7
+ @status = status
8
+ @body = body
9
+ @error = error
10
+ end
11
+
12
+ def streaming?
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'mime/types'
2
+
3
+ module Txgh
4
+ module Handlers
5
+ class StreamResponse
6
+ attr_reader :attachment, :enum
7
+
8
+ def initialize(attachment, enum)
9
+ @attachment = attachment
10
+ @enum = enum
11
+ end
12
+
13
+ def write_to(stream)
14
+ raise NotImplementedError,
15
+ "please implement #{__method__} in derived classes"
16
+ end
17
+
18
+ def file_extension
19
+ raise NotImplementedError,
20
+ "please implement #{__method__} in derived classes"
21
+ end
22
+
23
+ def headers
24
+ @headers ||= {
25
+ 'Content-Disposition' => "attachment; filename=\"#{attachment}#{file_extension}\"",
26
+ 'Content-Type' => MIME::Types.type_for(file_extension).first.content_type
27
+ }
28
+ end
29
+
30
+ def streaming?
31
+ true
32
+ end
33
+
34
+ def error
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems/package'
2
+ require 'stringio'
3
+ require 'zlib'
4
+
5
+ module Txgh
6
+ module Handlers
7
+ class TgzStreamResponse < StreamResponse
8
+ PERMISSIONS = 0644
9
+
10
+ def write_to(stream)
11
+ Zlib::GzipWriter.wrap(stream) do |gz|
12
+ pipe = StringIO.new('', 'wb')
13
+ tar = Gem::Package::TarWriter.new(pipe)
14
+
15
+ enum.each do |file_name, contents|
16
+ tar.add_file(file_name, PERMISSIONS) do |f|
17
+ f.write(contents)
18
+ end
19
+
20
+ flush(tar, pipe, gz)
21
+ stream.flush
22
+ end
23
+
24
+ flush(tar, pipe, gz)
25
+ end
26
+ end
27
+
28
+ def file_extension
29
+ '.tgz'
30
+ end
31
+
32
+ private
33
+
34
+ def flush(tar, pipe, gz)
35
+ tar.flush
36
+ gz.write(pipe.string)
37
+ pipe.reopen('')
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ module Txgh
2
+ module Handlers
3
+ module Transifex
4
+ autoload :HookHandler, 'txgh/handlers/transifex/hook_handler'
5
+ autoload :RequestHandler, 'txgh/handlers/transifex/request_handler'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,77 @@
1
+ require 'logger'
2
+
3
+ module Txgh
4
+ module Handlers
5
+ module Transifex
6
+ class HookHandler
7
+ include 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
+ if tx_config
24
+ if tx_resource
25
+ committer = ResourceCommitter.new(project, repo, logger)
26
+ committer.commit_resource(tx_resource, branch, language)
27
+
28
+ respond_with(200, true)
29
+ else
30
+ respond_with_error(
31
+ 404, "Could not find resource '#{resource_slug}' in config"
32
+ )
33
+ end
34
+ else
35
+ respond_with_error(
36
+ 404, "Could not find configuration for branch '#{branch}'"
37
+ )
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def tx_config
44
+ @tx_config ||= Txgh::Config::TxManager.tx_config(project, repo, branch)
45
+ rescue ConfigNotFoundError, TxghError
46
+ nil
47
+ end
48
+
49
+ def branch
50
+ @branch ||= begin
51
+ branch_candidate = if process_all_branches?
52
+ resource = project.api.get_resource(project.name, resource_slug)
53
+ categories = deserialize_categories(Array(resource['categories']))
54
+ categories['branch']
55
+ else
56
+ repo.branch || 'master'
57
+ end
58
+
59
+ Utils.absolute_branch(branch_candidate)
60
+ end
61
+ end
62
+
63
+ def tx_resource
64
+ @tx_resource ||= if process_all_branches?
65
+ tx_config.resource(resource_slug, branch)
66
+ else
67
+ tx_config.resource(resource_slug)
68
+ end
69
+ end
70
+
71
+ def process_all_branches?
72
+ repo.process_all_branches?
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,78 @@
1
+ require 'uri'
2
+
3
+ module Txgh
4
+ module Handlers
5
+ module Transifex
6
+ class RequestHandler
7
+ class << self
8
+
9
+ def handle_request(request, logger)
10
+ new(request, logger).handle(Txgh::Handlers::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