shipit-engine 0.5.2 → 0.6.0

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/shipit/api/cacheable.rb +13 -0
  3. data/app/controllers/concerns/shipit/api/paginable.rb +37 -0
  4. data/app/controllers/concerns/shipit/api/rendering.rb +25 -0
  5. data/app/controllers/concerns/{api/paginable.rb → shipit/pagination.rb} +5 -13
  6. data/app/controllers/shipit/api/base_controller.rb +68 -0
  7. data/app/controllers/shipit/api/deploys_controller.rb +17 -0
  8. data/app/controllers/shipit/api/hooks_controller.rb +53 -0
  9. data/app/controllers/shipit/api/locks_controller.rb +32 -0
  10. data/app/controllers/shipit/api/outputs_controller.rb +17 -0
  11. data/app/controllers/shipit/api/stacks_controller.rb +21 -0
  12. data/app/controllers/shipit/api/tasks_controller.rb +20 -0
  13. data/app/controllers/shipit/commit_checks_controller.rb +26 -0
  14. data/app/controllers/shipit/deploys_controller.rb +47 -0
  15. data/app/controllers/shipit/github_authentication_controller.rb +27 -0
  16. data/app/controllers/shipit/rollbacks_controller.rb +26 -0
  17. data/app/controllers/shipit/shipit_controller.rb +62 -0
  18. data/app/controllers/shipit/stacks_controller.rb +81 -0
  19. data/app/controllers/shipit/status_controller.rb +7 -0
  20. data/app/controllers/shipit/tasks_controller.rb +48 -0
  21. data/app/controllers/shipit/webhooks_controller.rb +101 -0
  22. data/app/helpers/shipit/chunks_helper.rb +8 -0
  23. data/app/helpers/shipit/deploys_helper.rb +28 -0
  24. data/app/helpers/shipit/github_url_helper.rb +48 -0
  25. data/app/helpers/shipit/shipit_helper.rb +64 -0
  26. data/app/helpers/shipit/stacks_helper.rb +78 -0
  27. data/app/helpers/shipit/tasks_helper.rb +11 -0
  28. data/app/jobs/shipit/background_job.rb +24 -0
  29. data/app/jobs/shipit/background_job/unique.rb +28 -0
  30. data/app/jobs/shipit/cache_deploy_spec_job.rb +12 -0
  31. data/app/jobs/shipit/chunk_rollup_job.rb +21 -0
  32. data/app/jobs/shipit/clear_git_cache_job.rb +9 -0
  33. data/app/jobs/shipit/deliver_hook_job.rb +9 -0
  34. data/app/jobs/shipit/destroy_stack_job.rb +9 -0
  35. data/app/jobs/shipit/emit_event_job.rb +10 -0
  36. data/app/jobs/shipit/fetch_commit_stats_job.rb +9 -0
  37. data/app/jobs/shipit/fetch_deployed_revision_job.rb +23 -0
  38. data/app/jobs/shipit/git_mirror_update_job.rb +12 -0
  39. data/app/jobs/shipit/github_sync_job.rb +55 -0
  40. data/app/jobs/shipit/perform_commit_checks_job.rb +7 -0
  41. data/app/jobs/shipit/perform_task_job.rb +57 -0
  42. data/app/jobs/shipit/refresh_github_user_job.rb +9 -0
  43. data/app/jobs/shipit/refresh_statuses_job.rb +14 -0
  44. data/app/jobs/shipit/setup_github_hook_job.rb +11 -0
  45. data/app/models/shipit/anonymous_user.rb +43 -0
  46. data/app/models/shipit/api_client.rb +44 -0
  47. data/app/models/shipit/commit.rb +209 -0
  48. data/app/models/shipit/commit_checks.rb +90 -0
  49. data/app/models/shipit/delivery.rb +47 -0
  50. data/app/models/shipit/deploy.rb +153 -0
  51. data/app/models/shipit/deploy_spec.rb +150 -0
  52. data/app/models/shipit/deploy_spec/bundler_discovery.rb +61 -0
  53. data/app/models/shipit/deploy_spec/capistrano_discovery.rb +29 -0
  54. data/app/models/shipit/deploy_spec/file_system.rb +64 -0
  55. data/app/models/shipit/deploy_spec/pypi_discovery.rb +34 -0
  56. data/app/models/shipit/deploy_spec/rubygems_discovery.rb +34 -0
  57. data/app/models/shipit/github_hook.rb +148 -0
  58. data/app/models/shipit/hook.rb +86 -0
  59. data/app/models/shipit/membership.rb +8 -0
  60. data/app/models/shipit/missing_status.rb +21 -0
  61. data/app/models/shipit/output_chunk.rb +11 -0
  62. data/app/models/shipit/rollback.rb +31 -0
  63. data/app/models/shipit/stack.rb +308 -0
  64. data/app/models/shipit/status.rb +44 -0
  65. data/app/models/shipit/status_group.rb +35 -0
  66. data/app/models/shipit/task.rb +201 -0
  67. data/app/models/shipit/task_definition.rb +38 -0
  68. data/app/models/shipit/team.rb +69 -0
  69. data/app/models/shipit/unknown_status.rb +43 -0
  70. data/app/models/shipit/user.rb +83 -0
  71. data/app/models/shipit/variable_definition.rb +21 -0
  72. data/app/serializers/concerns/shipit/conditional_attributes.rb +22 -0
  73. data/app/serializers/shipit/anonymous_user_serializer.rb +4 -0
  74. data/app/serializers/shipit/commit_serializer.rb +8 -0
  75. data/app/serializers/shipit/deploy_serializer.rb +15 -0
  76. data/app/serializers/shipit/hook_serializer.rb +12 -0
  77. data/app/serializers/shipit/rollback_serializer.rb +7 -0
  78. data/app/serializers/shipit/short_commit_serializer.rb +9 -0
  79. data/app/serializers/shipit/stack_serializer.rb +33 -0
  80. data/app/serializers/shipit/tail_task_serializer.rb +39 -0
  81. data/app/serializers/shipit/task_serializer.rb +30 -0
  82. data/app/serializers/shipit/user_serializer.rb +5 -0
  83. data/app/views/{commits → shipit/commits}/_commit.html.erb +1 -1
  84. data/app/views/{commits → shipit/commits}/_commit_author.html.erb +0 -0
  85. data/app/views/{deploys → shipit/deploys}/_checklist.html.erb +0 -0
  86. data/app/views/{deploys → shipit/deploys}/_checks.html.erb +0 -0
  87. data/app/views/{deploys → shipit/deploys}/_concurrent_deploy_warning.html.erb +0 -0
  88. data/app/views/{deploys → shipit/deploys}/_deploy.html.erb +1 -1
  89. data/app/views/{deploys → shipit/deploys}/_monitoring.html.erb +0 -0
  90. data/app/views/{deploys → shipit/deploys}/_summary.html.erb +0 -0
  91. data/app/views/{deploys → shipit/deploys}/new.html.erb +3 -3
  92. data/app/views/{deploys → shipit/deploys}/rollback.html.erb +2 -2
  93. data/app/views/{deploys → shipit/deploys}/show.html.erb +1 -1
  94. data/app/views/{github_authentication → shipit/github_authentication}/failed.html.erb +0 -0
  95. data/app/views/{stacks → shipit/stacks}/_header.html.erb +0 -0
  96. data/app/views/{stacks → shipit/stacks}/index.html.erb +0 -0
  97. data/app/views/{stacks → shipit/stacks}/new.html.erb +0 -0
  98. data/app/views/{stacks → shipit/stacks}/settings.html.erb +1 -1
  99. data/app/views/{stacks → shipit/stacks}/show.html.erb +2 -2
  100. data/app/views/{statuses → shipit/statuses}/_group.html.erb +1 -1
  101. data/app/views/{statuses → shipit/statuses}/_status.html.erb +0 -0
  102. data/app/views/{tasks → shipit/tasks}/_task.html.erb +1 -1
  103. data/app/views/{tasks → shipit/tasks}/_task_output.html.erb +1 -1
  104. data/app/views/{tasks → shipit/tasks}/index.html.erb +1 -1
  105. data/app/views/{tasks → shipit/tasks}/new.html.erb +1 -1
  106. data/app/views/{tasks → shipit/tasks}/show.html.erb +1 -1
  107. data/db/migrate/20160104151742_increase_tasks_type_size_back.rb +5 -0
  108. data/db/migrate/20160104151833_convert_sti_columns.rb +10 -0
  109. data/lib/shipit.rb +11 -10
  110. data/lib/shipit/command.rb +171 -0
  111. data/lib/shipit/commands.rb +25 -0
  112. data/lib/shipit/deploy_commands.rb +21 -0
  113. data/lib/shipit/engine.rb +3 -0
  114. data/lib/shipit/rollback_commands.rb +7 -0
  115. data/lib/shipit/stack_commands.rb +60 -0
  116. data/lib/shipit/task_commands.rb +68 -0
  117. data/lib/shipit/version.rb +1 -1
  118. data/lib/tasks/cron.rake +3 -3
  119. data/test/controllers/api/base_controller_test.rb +18 -14
  120. data/test/controllers/api/deploys_controller_test.rb +56 -52
  121. data/test/controllers/api/hooks_controller_test.rb +62 -58
  122. data/test/controllers/api/locks_controller_test.rb +38 -34
  123. data/test/controllers/api/outputs_controller_test.rb +15 -11
  124. data/test/controllers/api/stacks_controller_test.rb +56 -52
  125. data/test/controllers/api/tasks_controller_test.rb +30 -26
  126. data/test/controllers/commit_checks_controller_test.rb +29 -27
  127. data/test/controllers/deploys_controller_test.rb +68 -66
  128. data/test/controllers/github_authentication_controller_test.rb +9 -7
  129. data/test/controllers/rollbacks_controller_test.rb +43 -41
  130. data/test/controllers/stacks_controller_test.rb +131 -128
  131. data/test/controllers/status_controller_test.rb +8 -6
  132. data/test/controllers/tasks_controller_test.rb +70 -68
  133. data/test/controllers/webhooks_controller_test.rb +127 -125
  134. data/test/dummy/db/development.sqlite3 +0 -0
  135. data/test/dummy/db/schema.rb +2 -2
  136. data/test/dummy/db/seeds.rb +133 -131
  137. data/test/dummy/db/test.sqlite3 +0 -0
  138. data/test/fixtures/{api_clients.yml → shipit/api_clients.yml} +0 -0
  139. data/test/fixtures/{commits.yml → shipit/commits.yml} +0 -0
  140. data/test/fixtures/{deliveries.yml → shipit/deliveries.yml} +0 -0
  141. data/test/fixtures/{github_hooks.yml → shipit/github_hooks.yml} +4 -4
  142. data/test/fixtures/{hooks.yml → shipit/hooks.yml} +0 -0
  143. data/test/fixtures/{memberships.yml → shipit/memberships.yml} +0 -0
  144. data/test/fixtures/{output_chunks.yml → shipit/output_chunks.yml} +0 -0
  145. data/test/fixtures/{stacks.yml → shipit/stacks.yml} +0 -0
  146. data/test/fixtures/{statuses.yml → shipit/statuses.yml} +0 -0
  147. data/test/fixtures/{tasks.yml → shipit/tasks.yml} +8 -8
  148. data/test/fixtures/{teams.yml → shipit/teams.yml} +0 -0
  149. data/test/fixtures/{users.yml → shipit/users.yml} +0 -0
  150. data/test/helpers/api_helper.rb +1 -1
  151. data/test/helpers/fixture_aliases_helper.rb +4 -4
  152. data/test/jobs/cache_deploy_spec_job_test.rb +15 -13
  153. data/test/jobs/chunk_rollup_job_test.rb +30 -28
  154. data/test/jobs/deliver_hook_job_test.rb +11 -9
  155. data/test/jobs/destroy_stack_job_test.rb +11 -9
  156. data/test/jobs/emit_event_job_test.rb +10 -8
  157. data/test/jobs/fetch_commit_stats_job_test.rb +10 -8
  158. data/test/jobs/fetch_deployed_revision_job_test.rb +24 -22
  159. data/test/jobs/github_sync_job_test.rb +51 -49
  160. data/test/jobs/perform_task_job_test.rb +78 -76
  161. data/test/jobs/refresh_github_user_job_test.rb +10 -8
  162. data/test/jobs/refresh_status_job_test.rb +14 -12
  163. data/test/jobs/unique_job_test.rb +18 -15
  164. data/test/models/api_client_test.rb +20 -18
  165. data/test/models/commit_checks_test.rb +63 -61
  166. data/test/models/commits_test.rb +317 -314
  167. data/test/models/delivery_test.rb +29 -27
  168. data/test/models/deploys_test.rb +289 -287
  169. data/test/models/github_hook_test.rb +45 -43
  170. data/test/models/hook_test.rb +44 -42
  171. data/test/models/membership_test.rb +9 -7
  172. data/test/models/missing_status_test.rb +16 -14
  173. data/test/models/output_chunk_test.rb +14 -12
  174. data/test/models/rollbacks_test.rb +14 -12
  175. data/test/models/stacks_test.rb +272 -270
  176. data/test/models/status_group_test.rb +18 -16
  177. data/test/models/status_test.rb +42 -40
  178. data/test/models/task_definitions_test.rb +27 -25
  179. data/test/models/team_test.rb +39 -37
  180. data/test/models/users_test.rb +61 -59
  181. data/test/unit/command_test.rb +43 -41
  182. data/test/unit/commands_test.rb +8 -6
  183. data/test/unit/csv_serializer_test.rb +28 -26
  184. data/test/unit/deploy_commands_test.rb +179 -176
  185. data/test/unit/deploy_spec_test.rb +237 -235
  186. data/test/unit/github_url_helper_test.rb +19 -17
  187. data/test/unit/shipit_test.rb +44 -42
  188. metadata +139 -137
  189. data/app/controllers/api/base_controller.rb +0 -66
  190. data/app/controllers/api/deploys_controller.rb +0 -15
  191. data/app/controllers/api/hooks_controller.rb +0 -51
  192. data/app/controllers/api/locks_controller.rb +0 -30
  193. data/app/controllers/api/outputs_controller.rb +0 -15
  194. data/app/controllers/api/stacks_controller.rb +0 -19
  195. data/app/controllers/api/tasks_controller.rb +0 -18
  196. data/app/controllers/commit_checks_controller.rb +0 -24
  197. data/app/controllers/concerns/api/cacheable.rb +0 -11
  198. data/app/controllers/concerns/api/rendering.rb +0 -23
  199. data/app/controllers/concerns/pagination.rb +0 -25
  200. data/app/controllers/deploys_controller.rb +0 -45
  201. data/app/controllers/github_authentication_controller.rb +0 -25
  202. data/app/controllers/rollbacks_controller.rb +0 -24
  203. data/app/controllers/shipit_controller.rb +0 -54
  204. data/app/controllers/stacks_controller.rb +0 -79
  205. data/app/controllers/status_controller.rb +0 -5
  206. data/app/controllers/tasks_controller.rb +0 -46
  207. data/app/controllers/webhooks_controller.rb +0 -99
  208. data/app/helpers/chunks_helper.rb +0 -6
  209. data/app/helpers/deploys_helper.rb +0 -26
  210. data/app/helpers/github_url_helper.rb +0 -46
  211. data/app/helpers/shipit_helper.rb +0 -62
  212. data/app/helpers/stacks_helper.rb +0 -76
  213. data/app/helpers/tasks_helper.rb +0 -9
  214. data/app/jobs/background_job.rb +0 -22
  215. data/app/jobs/background_job/unique.rb +0 -26
  216. data/app/jobs/cache_deploy_spec_job.rb +0 -10
  217. data/app/jobs/chunk_rollup_job.rb +0 -19
  218. data/app/jobs/clear_git_cache_job.rb +0 -7
  219. data/app/jobs/deliver_hook_job.rb +0 -7
  220. data/app/jobs/destroy_stack_job.rb +0 -7
  221. data/app/jobs/emit_event_job.rb +0 -8
  222. data/app/jobs/fetch_commit_stats_job.rb +0 -7
  223. data/app/jobs/fetch_deployed_revision_job.rb +0 -21
  224. data/app/jobs/git_mirror_update_job.rb +0 -10
  225. data/app/jobs/github_sync_job.rb +0 -53
  226. data/app/jobs/perform_commit_checks_job.rb +0 -5
  227. data/app/jobs/perform_task_job.rb +0 -55
  228. data/app/jobs/refresh_github_user_job.rb +0 -7
  229. data/app/jobs/refresh_statuses_job.rb +0 -12
  230. data/app/jobs/setup_github_hook_job.rb +0 -9
  231. data/app/models/anonymous_user.rb +0 -41
  232. data/app/models/api_client.rb +0 -42
  233. data/app/models/commit.rb +0 -207
  234. data/app/models/commit_checks.rb +0 -88
  235. data/app/models/delivery.rb +0 -45
  236. data/app/models/deploy.rb +0 -151
  237. data/app/models/deploy_spec.rb +0 -148
  238. data/app/models/deploy_spec/bundler_discovery.rb +0 -59
  239. data/app/models/deploy_spec/capistrano_discovery.rb +0 -27
  240. data/app/models/deploy_spec/file_system.rb +0 -62
  241. data/app/models/deploy_spec/pypi_discovery.rb +0 -32
  242. data/app/models/deploy_spec/rubygems_discovery.rb +0 -32
  243. data/app/models/github_hook.rb +0 -144
  244. data/app/models/hook.rb +0 -84
  245. data/app/models/membership.rb +0 -6
  246. data/app/models/missing_status.rb +0 -18
  247. data/app/models/output_chunk.rb +0 -9
  248. data/app/models/rollback.rb +0 -29
  249. data/app/models/stack.rb +0 -306
  250. data/app/models/status.rb +0 -42
  251. data/app/models/status_group.rb +0 -33
  252. data/app/models/task.rb +0 -197
  253. data/app/models/task_definition.rb +0 -36
  254. data/app/models/team.rb +0 -67
  255. data/app/models/unknown_status.rb +0 -41
  256. data/app/models/user.rb +0 -81
  257. data/app/models/variable_definition.rb +0 -19
  258. data/app/serializers/anonymous_user_serializer.rb +0 -2
  259. data/app/serializers/commit_serializer.rb +0 -6
  260. data/app/serializers/concerns/conditional_attributes.rb +0 -20
  261. data/app/serializers/deploy_serializer.rb +0 -13
  262. data/app/serializers/hook_serializer.rb +0 -10
  263. data/app/serializers/rollback_serializer.rb +0 -5
  264. data/app/serializers/short_commit_serializer.rb +0 -7
  265. data/app/serializers/stack_serializer.rb +0 -31
  266. data/app/serializers/tail_task_serializer.rb +0 -37
  267. data/app/serializers/task_serializer.rb +0 -28
  268. data/app/serializers/user_serializer.rb +0 -3
  269. data/lib/command.rb +0 -169
  270. data/lib/commands.rb +0 -23
  271. data/lib/deploy_commands.rb +0 -19
  272. data/lib/rollback_commands.rb +0 -5
  273. data/lib/stack_commands.rb +0 -58
  274. data/lib/task_commands.rb +0 -66
@@ -0,0 +1,9 @@
1
+ module Shipit
2
+ class RefreshGithubUserJob < BackgroundJob
3
+ queue_as :default
4
+
5
+ def perform(user)
6
+ user.refresh_from_github!
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module Shipit
2
+ class RefreshStatusesJob < BackgroundJob
3
+ queue_as :default
4
+
5
+ def perform(params)
6
+ if params[:commit_id]
7
+ Commit.find(params[:commit_id]).refresh_statuses!
8
+ else
9
+ stack = Stack.find(params[:stack_id])
10
+ stack.commits.order(id: :desc).limit(30).each(&:refresh_statuses!)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Shipit
2
+ class SetupGithubHookJob < BackgroundJob
3
+ include BackgroundJob::Unique
4
+
5
+ queue_as :default
6
+
7
+ def perform(hook)
8
+ hook.setup!
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,43 @@
1
+ module Shipit
2
+ class AnonymousUser
3
+ def email
4
+ 'anonymous@example.com'
5
+ end
6
+
7
+ def login
8
+ 'anonymous'
9
+ end
10
+
11
+ def name
12
+ 'Anonymous'
13
+ end
14
+
15
+ def avatar_url
16
+ 'https://github.com/images/error/octocat_happy.gif'
17
+ end
18
+
19
+ def id
20
+ end
21
+
22
+ def logged_in?
23
+ false
24
+ end
25
+
26
+ def stacks_contributed_to
27
+ []
28
+ end
29
+
30
+ def avatar_uri
31
+ User::DEFAULT_AVATAR.dup
32
+ end
33
+
34
+ def created_at
35
+ Time.at(0).utc
36
+ end
37
+ alias_method :updated_at, :created_at
38
+
39
+ def read_attribute_for_serialization(attr)
40
+ public_send(attr)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ module Shipit
2
+ class ApiClient < ActiveRecord::Base
3
+ InsufficientPermission = Class.new(StandardError)
4
+
5
+ belongs_to :creator, class_name: 'User'
6
+ belongs_to :stack
7
+
8
+ validates :creator, :name, presence: true
9
+
10
+ serialize :permissions, Array
11
+ PERMISSIONS = %w(
12
+ read:stack
13
+ write:stack
14
+ deploy:stack
15
+ lock:stack
16
+ read:hook
17
+ write:hook
18
+ ).freeze
19
+ validates :permissions, subset: {of: PERMISSIONS}
20
+
21
+ class << self
22
+ def authenticate(token)
23
+ find_by_id(message_verifier.verify(token).to_i)
24
+ rescue Shipit::SimpleMessageVerifier::InvalidSignature
25
+ end
26
+
27
+ def message_verifier
28
+ @message_verifier ||= Shipit::SimpleMessageVerifier.new(Shipit.api_clients_secret)
29
+ end
30
+ end
31
+
32
+ def authentication_token
33
+ self.class.message_verifier.generate(id)
34
+ end
35
+
36
+ def check_permissions!(operation, scope)
37
+ required_permission = "#{operation}:#{scope}"
38
+ unless permissions.include?(required_permission)
39
+ raise InsufficientPermission, "This operation requires the `#{required_permission}` permission"
40
+ end
41
+ true
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,209 @@
1
+ module Shipit
2
+ class Commit < ActiveRecord::Base
3
+ AmbiguousRevision = Class.new(StandardError)
4
+
5
+ belongs_to :stack, touch: true
6
+ has_many :deploys
7
+ has_many :statuses, -> { order(created_at: :desc) }
8
+
9
+ after_commit { broadcast_update }
10
+ after_create { stack.update_undeployed_commits_count }
11
+
12
+ after_commit :schedule_refresh_statuses!, :schedule_fetch_stats!, on: :create
13
+
14
+ after_touch :touch_stack
15
+
16
+ belongs_to :author, class_name: 'User', inverse_of: :authored_commits
17
+ belongs_to :committer, class_name: 'User', inverse_of: :commits
18
+
19
+ scope :reachable, -> { where(detached: false) }
20
+
21
+ delegate :broadcast_update, :github_repo_name, to: :stack
22
+
23
+ def self.newer_than(commit)
24
+ return all unless commit
25
+ where('id > ?', commit.is_a?(Commit) ? commit.id : commit)
26
+ end
27
+
28
+ def self.until(commit)
29
+ return all unless commit
30
+ where('id <= ?', commit.is_a?(Commit) ? commit.id : commit)
31
+ end
32
+
33
+ def self.successful
34
+ preload(:statuses).to_a.select(&:success?)
35
+ end
36
+
37
+ def self.detach!
38
+ Commit.where(id: ids).update_all(detached: true)
39
+ end
40
+
41
+ def self.by_sha(sha)
42
+ if sha.to_s.size < 6
43
+ raise AmbiguousRevision, "Short SHA1 #{sha} is ambiguous (too short)"
44
+ end
45
+
46
+ commits = where('sha like ?', "#{sha}%").take(2)
47
+ raise AmbiguousRevision, "Short SHA1 #{sha} is ambiguous (matches multiple commits)" if commits.size > 1
48
+ commits.first
49
+ end
50
+
51
+ def self.by_sha!(sha)
52
+ by_sha(sha) || raise(ActiveRecord::RecordNotFound, "Couldn't find commit with sha #{sha}")
53
+ end
54
+
55
+ def self.from_github(commit)
56
+ new(
57
+ sha: commit.sha,
58
+ message: commit.commit.message,
59
+ author: User.find_or_create_from_github(commit.author || commit.commit.author),
60
+ committer: User.find_or_create_from_github(commit.committer || commit.commit.committer),
61
+ committed_at: commit.commit.committer.date,
62
+ authored_at: commit.commit.author.date,
63
+ additions: commit.stats.try!(:additions),
64
+ deletions: commit.stats.try!(:deletions),
65
+ )
66
+ end
67
+
68
+ def reload(*)
69
+ @last_statuses = nil
70
+ super
71
+ end
72
+
73
+ def self.create_from_github!(commit)
74
+ from_github(commit).save!
75
+ end
76
+
77
+ def schedule_refresh_statuses!
78
+ RefreshStatusesJob.perform_later(commit_id: id)
79
+ end
80
+
81
+ def refresh_statuses!
82
+ Shipit.github_api.statuses(github_repo_name, sha).each do |status|
83
+ statuses.replicate_from_github!(status)
84
+ end
85
+ end
86
+
87
+ def add_status(status_attributes)
88
+ previous_status = significant_status
89
+ statuses.create!(status_attributes)
90
+ reload # to get the statuses into the right order (since sorted :desc)
91
+ new_status = significant_status
92
+
93
+ payload = {commit: self, stack: stack, status: new_status.state}
94
+ Hook.emit(:commit_status, stack, payload.merge(commit_status: new_status)) if previous_status != new_status
95
+ if simple_state(previous_status) != simple_state(new_status) && !new_status.pending?
96
+ Hook.emit(:deployable_status, stack, payload.merge(deployable_status: new_status))
97
+ end
98
+ new_status
99
+ end
100
+
101
+ def checks
102
+ @checks ||= CommitChecks.new(self)
103
+ end
104
+
105
+ delegate :pending?, :success?, :error?, :failure?, :state, to: :status
106
+
107
+ def deployable?
108
+ success? || stack.ignore_ci?
109
+ end
110
+
111
+ def children
112
+ self.class.where(stack_id: stack_id).newer_than(self)
113
+ end
114
+
115
+ def detach_children!
116
+ children.detach!
117
+ end
118
+
119
+ def pull_request_url
120
+ parsed && Shipit.github_url("/#{stack.repo_owner}/#{stack.repo_name}/pull/#{pull_request_id}")
121
+ end
122
+
123
+ def pull_request_id
124
+ parsed && parsed['pr_id'].to_i
125
+ end
126
+
127
+ def pull_request_title
128
+ parsed && parsed['pr_title']
129
+ end
130
+
131
+ def pull_request?
132
+ !!parsed
133
+ end
134
+
135
+ def short_sha
136
+ sha[0..9]
137
+ end
138
+
139
+ def parsed
140
+ @parsed ||= message.match(/\AMerge pull request #(?<pr_id>\d+) from [\w\-.\/]+\n\n(?<pr_title>.*)/)
141
+ end
142
+
143
+ def schedule_continuous_delivery
144
+ return unless state == 'success' && stack.continuous_deployment?
145
+ return unless stack.deployable?
146
+ return if already_deployed?
147
+ stack.trigger_deploy(self, committer)
148
+ end
149
+
150
+ def github_commit
151
+ @github_commit ||= Shipit.github_api.commit(github_repo_name, sha)
152
+ end
153
+
154
+ def schedule_fetch_stats!
155
+ FetchCommitStatsJob.perform_later(self)
156
+ end
157
+
158
+ def fetch_stats!
159
+ update!(
160
+ additions: github_commit.stats.try!(:additions),
161
+ deletions: github_commit.stats.try!(:deletions),
162
+ )
163
+ end
164
+
165
+ def visible_statuses
166
+ stack.filter_visible_statuses(last_statuses)
167
+ end
168
+
169
+ def meaningful_statuses
170
+ stack.filter_meaningful_statuses(last_statuses)
171
+ end
172
+
173
+ def last_statuses
174
+ @last_statuses ||= statuses.to_a.uniq(&:context).sort_by(&:context).presence || [UnknownStatus.new(self)]
175
+ end
176
+
177
+ def status
178
+ visibles = visible_statuses
179
+ status = visibles.size > 1 ? StatusGroup.new(significant_status, visibles) : visibles.first
180
+ missing_statuses.empty? ? status : MissingStatus.new(status, missing_statuses)
181
+ end
182
+
183
+ def significant_status
184
+ statuses = meaningful_statuses
185
+ return UnknownStatus.new(self) if statuses.empty?
186
+ return statuses.first if statuses.all?(&:success?)
187
+ non_success_statuses = statuses.reject(&:success?)
188
+ non_success_statuses.reject(&:pending?).first || non_success_statuses.first || UnknownStatus.new(self)
189
+ end
190
+
191
+ private
192
+
193
+ def missing_statuses
194
+ stack.required_statuses - last_statuses.map(&:context)
195
+ end
196
+
197
+ def touch_stack
198
+ stack.touch
199
+ end
200
+
201
+ def already_deployed?
202
+ stack.last_deployed_commit.id >= id
203
+ end
204
+
205
+ def simple_state(status)
206
+ status.state == 'error' ? 'failure' : status.state
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,90 @@
1
+ module Shipit
2
+ class CommitChecks
3
+ OUTPUT_TTL = 10.minutes.to_i
4
+ FINAL_STATUSES = %w(failed error success)
5
+
6
+ def initialize(commit)
7
+ @commit = commit
8
+ end
9
+
10
+ def run
11
+ self.status = 'running'
12
+ commands = StackCommands.new(stack)
13
+ commands.with_temporary_working_directory(commit: commit) do |directory|
14
+ deploy_spec = DeploySpec::FileSystem.new(directory, stack.environment)
15
+ Bundler.with_clean_env do
16
+ capture_all(build_commands(deploy_spec.dependencies_steps, chdir: directory))
17
+ capture_all(build_commands(deploy_spec.review_checks, chdir: directory))
18
+ end
19
+ end
20
+ rescue Command::Error
21
+ self.status = 'failed'
22
+ rescue
23
+ self.status = 'error'
24
+ raise
25
+ else
26
+ self.status = 'success'
27
+ end
28
+
29
+ def schedule
30
+ if redis.set('output', '', ex: OUTPUT_TTL, nx: true)
31
+ self.status = 'scheduled'
32
+ PerformCommitChecksJob.perform_later(commit: commit)
33
+ end
34
+ end
35
+
36
+ def status
37
+ @status ||= redis.get('status')
38
+ end
39
+
40
+ def status=(status)
41
+ redis.set('status', status, ex: OUTPUT_TTL)
42
+ @status = status
43
+ end
44
+
45
+ def finished?
46
+ FINAL_STATUSES.include?(status)
47
+ end
48
+
49
+ def output(since: 0)
50
+ redis.getrange('output', since, -1)
51
+ end
52
+
53
+ def write(output)
54
+ redis.append('output', output)
55
+ end
56
+
57
+ private
58
+
59
+ def build_commands(commands, chdir:)
60
+ commands.map { |c| Command.new(c, env: Shipit.env, chdir: chdir) }
61
+ end
62
+
63
+ def capture_all(commands)
64
+ commands.map { |c| capture(c) }
65
+ end
66
+
67
+ def capture(command)
68
+ command.start
69
+ write("$ #{command}\n")
70
+ command.stream! do |line|
71
+ write(line)
72
+ end
73
+ rescue Command::Error => error
74
+ write(error.message)
75
+ raise
76
+ ensure
77
+ write("\n")
78
+ end
79
+
80
+ attr_reader :commit
81
+
82
+ def redis
83
+ @redis ||= Shipit.redis("commit:#{commit.id}:checks")
84
+ end
85
+
86
+ def stack
87
+ @stack ||= commit.stack
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,47 @@
1
+ module Shipit
2
+ class Delivery < ActiveRecord::Base
3
+ STATUSES = %w(pending scheduled sent).freeze
4
+ enum status: STATUSES.zip(STATUSES).to_h
5
+
6
+ belongs_to :hook
7
+
8
+ validates :url, presence: true, url: {no_local: true, allow_blank: true}
9
+ validates :content_type, presence: true
10
+
11
+ serialize :response_headers, JSON
12
+
13
+ def schedule!
14
+ DeliverHookJob.perform_later(self)
15
+ scheduled!
16
+ end
17
+
18
+ def send!
19
+ update!(response: http.post(url, payload), status: 'sent', delivered_at: Time.now)
20
+ end
21
+
22
+ def response=(response)
23
+ self.response_code = response.status
24
+ self.response_headers = response.headers
25
+ self.response_body = response.body
26
+ end
27
+
28
+ private
29
+
30
+ def http
31
+ Faraday::Connection.new do |connection|
32
+ connection.headers = headers
33
+ connection.adapter Faraday.default_adapter
34
+ end
35
+ end
36
+
37
+ def headers
38
+ {
39
+ 'User-Agent' => 'Shipit Webhook',
40
+ 'Content-Type' => content_type,
41
+ 'X-Shipit-Event' => event,
42
+ 'X-Shipit-Delivery' => id.to_s,
43
+ 'Accept' => '*/*',
44
+ }
45
+ end
46
+ end
47
+ end