shipit-engine 0.28.0 → 0.32.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 (314) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -2
  3. data/Rakefile +4 -2
  4. data/app/assets/images/archive-solid.svg +1 -0
  5. data/app/assets/stylesheets/_pages/_stacks.scss +76 -3
  6. data/app/assets/stylesheets/_structure/_main.scss +2 -1
  7. data/app/assets/stylesheets/merge_status.scss +0 -3
  8. data/app/controllers/concerns/shipit/active_model_serializers_patch.rb +13 -0
  9. data/app/controllers/concerns/shipit/api/cacheable.rb +1 -0
  10. data/app/controllers/concerns/shipit/api/paginable.rb +3 -2
  11. data/app/controllers/concerns/shipit/api/rendering.rb +5 -4
  12. data/app/controllers/concerns/shipit/authentication.rb +3 -2
  13. data/app/controllers/concerns/shipit/pagination.rb +2 -1
  14. data/app/controllers/shipit/api/base_controller.rb +11 -6
  15. data/app/controllers/shipit/api/ccmenu_controller.rb +2 -1
  16. data/app/controllers/shipit/api/commits_controller.rb +2 -1
  17. data/app/controllers/shipit/api/deploys_controller.rb +4 -3
  18. data/app/controllers/shipit/api/hooks_controller.rb +6 -5
  19. data/app/controllers/shipit/api/locks_controller.rb +5 -4
  20. data/app/controllers/shipit/api/outputs_controller.rb +2 -1
  21. data/app/controllers/shipit/api/pull_requests_controller.rb +7 -6
  22. data/app/controllers/shipit/api/release_statuses_controller.rb +3 -2
  23. data/app/controllers/shipit/api/rollbacks_controller.rb +33 -0
  24. data/app/controllers/shipit/api/stacks_controller.rb +37 -5
  25. data/app/controllers/shipit/api/tasks_controller.rb +6 -5
  26. data/app/controllers/shipit/api_clients_controller.rb +50 -0
  27. data/app/controllers/shipit/ccmenu_url_controller.rb +4 -3
  28. data/app/controllers/shipit/commit_checks_controller.rb +2 -1
  29. data/app/controllers/shipit/commits_controller.rb +2 -1
  30. data/app/controllers/shipit/deploys_controller.rb +3 -2
  31. data/app/controllers/shipit/github_authentication_controller.rb +4 -3
  32. data/app/controllers/shipit/merge_status_controller.rb +19 -14
  33. data/app/controllers/shipit/pull_requests_controller.rb +3 -2
  34. data/app/controllers/shipit/release_statuses_controller.rb +3 -2
  35. data/app/controllers/shipit/rollbacks_controller.rb +3 -2
  36. data/app/controllers/shipit/shipit_controller.rb +2 -1
  37. data/app/controllers/shipit/stacks_controller.rb +78 -14
  38. data/app/controllers/shipit/status_controller.rb +2 -1
  39. data/app/controllers/shipit/tasks_controller.rb +6 -5
  40. data/app/controllers/shipit/webhooks_controller.rb +5 -132
  41. data/app/helpers/shipit/chunks_helper.rb +1 -0
  42. data/app/helpers/shipit/deploys_helper.rb +4 -3
  43. data/app/helpers/shipit/github_url_helper.rb +1 -0
  44. data/app/helpers/shipit/merge_status_helper.rb +1 -0
  45. data/app/helpers/shipit/shipit_helper.rb +1 -0
  46. data/app/helpers/shipit/stacks_helper.rb +5 -0
  47. data/app/helpers/shipit/tasks_helper.rb +1 -0
  48. data/app/jobs/shipit/background_job.rb +4 -0
  49. data/app/jobs/shipit/background_job/unique.rb +4 -1
  50. data/app/jobs/shipit/cache_deploy_spec_job.rb +1 -0
  51. data/app/jobs/shipit/chunk_rollup_job.rb +4 -0
  52. data/app/jobs/shipit/clear_git_cache_job.rb +1 -0
  53. data/app/jobs/shipit/continuous_delivery_job.rb +3 -1
  54. data/app/jobs/shipit/create_on_github_job.rb +6 -1
  55. data/app/jobs/shipit/create_release_statuses_job.rb +1 -0
  56. data/app/jobs/shipit/deferred_touch_job.rb +4 -0
  57. data/app/jobs/shipit/deliver_hook_job.rb +1 -0
  58. data/app/jobs/shipit/destroy_job.rb +1 -0
  59. data/app/jobs/shipit/destroy_stack_job.rb +3 -2
  60. data/app/jobs/shipit/emit_event_job.rb +2 -1
  61. data/app/jobs/shipit/fetch_commit_stats_job.rb +1 -0
  62. data/app/jobs/shipit/fetch_deployed_revision_job.rb +2 -1
  63. data/app/jobs/shipit/github_sync_job.rb +3 -2
  64. data/app/jobs/shipit/{mark_deploy_healty_job.rb → mark_deploy_healthy_job.rb} +1 -0
  65. data/app/jobs/shipit/merge_pull_requests_job.rb +1 -0
  66. data/app/jobs/shipit/perform_commit_checks_job.rb +1 -0
  67. data/app/jobs/shipit/perform_task_job.rb +14 -5
  68. data/app/jobs/shipit/purge_old_deliveries_job.rb +1 -0
  69. data/app/jobs/shipit/reap_dead_tasks_job.rb +21 -0
  70. data/app/jobs/shipit/refresh_check_runs_job.rb +1 -0
  71. data/app/jobs/shipit/refresh_github_user_job.rb +1 -0
  72. data/app/jobs/shipit/refresh_pull_request_job.rb +1 -0
  73. data/app/jobs/shipit/refresh_statuses_job.rb +1 -0
  74. data/app/jobs/shipit/setup_github_hook_job.rb +1 -0
  75. data/app/jobs/shipit/update_estimated_deploy_duration_job.rb +1 -0
  76. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +6 -3
  77. data/app/models/concerns/shipit/deferred_touch.rb +4 -3
  78. data/app/models/shipit/anonymous_user.rb +5 -0
  79. data/app/models/shipit/api_client.rb +3 -2
  80. data/app/models/shipit/application_record.rb +2 -1
  81. data/app/models/shipit/check_run.rb +4 -3
  82. data/app/models/shipit/command_line_user.rb +1 -0
  83. data/app/models/shipit/commit.rb +31 -12
  84. data/app/models/shipit/commit_checks.rb +1 -0
  85. data/app/models/shipit/commit_deployment.rb +17 -12
  86. data/app/models/shipit/commit_deployment_status.rb +8 -3
  87. data/app/models/shipit/commit_message.rb +1 -0
  88. data/app/models/shipit/delivery.rb +4 -3
  89. data/app/models/shipit/deploy.rb +40 -10
  90. data/app/models/shipit/deploy_spec.rb +22 -3
  91. data/app/models/shipit/deploy_spec/bundler_discovery.rb +2 -1
  92. data/app/models/shipit/deploy_spec/capistrano_discovery.rb +1 -0
  93. data/app/models/shipit/deploy_spec/file_system.rb +10 -3
  94. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +1 -0
  95. data/app/models/shipit/deploy_spec/lerna_discovery.rb +1 -0
  96. data/app/models/shipit/deploy_spec/npm_discovery.rb +5 -4
  97. data/app/models/shipit/deploy_spec/pypi_discovery.rb +1 -0
  98. data/app/models/shipit/deploy_spec/rubygems_discovery.rb +1 -0
  99. data/app/models/shipit/deploy_stats.rb +58 -0
  100. data/app/models/shipit/duration.rb +3 -2
  101. data/app/models/shipit/ephemeral_commit_checks.rb +1 -0
  102. data/app/models/shipit/github_hook.rb +2 -1
  103. data/app/models/shipit/github_status.rb +3 -2
  104. data/app/models/shipit/hook.rb +6 -5
  105. data/app/models/shipit/membership.rb +3 -2
  106. data/app/models/shipit/output_chunk.rb +7 -2
  107. data/app/models/shipit/pull_request.rb +13 -7
  108. data/app/models/shipit/record.rb +18 -0
  109. data/app/models/shipit/release_status.rb +3 -2
  110. data/app/models/shipit/repository.rb +43 -0
  111. data/app/models/shipit/rollback.rb +1 -0
  112. data/app/models/shipit/stack.rb +109 -50
  113. data/app/models/shipit/status.rb +3 -2
  114. data/app/models/shipit/status/common.rb +7 -6
  115. data/app/models/shipit/status/group.rb +1 -0
  116. data/app/models/shipit/status/missing.rb +2 -1
  117. data/app/models/shipit/status/unknown.rb +2 -1
  118. data/app/models/shipit/task.rb +64 -9
  119. data/app/models/shipit/task_definition.rb +1 -0
  120. data/app/models/shipit/team.rb +2 -1
  121. data/app/models/shipit/undeployed_commit.rb +1 -0
  122. data/app/models/shipit/unlimited_api_client.rb +1 -0
  123. data/app/models/shipit/user.rb +29 -5
  124. data/app/models/shipit/variable_definition.rb +1 -0
  125. data/app/models/shipit/webhooks.rb +33 -0
  126. data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +20 -0
  127. data/app/models/shipit/webhooks/handlers/handler.rb +41 -0
  128. data/app/models/shipit/webhooks/handlers/membership_handler.rb +46 -0
  129. data/app/models/shipit/webhooks/handlers/push_handler.rb +21 -0
  130. data/app/models/shipit/webhooks/handlers/status_handler.rb +27 -0
  131. data/app/serializers/concerns/shipit/conditional_attributes.rb +1 -0
  132. data/app/serializers/shipit/anonymous_user_serializer.rb +1 -0
  133. data/app/serializers/shipit/command_line_user_serializer.rb +1 -0
  134. data/app/serializers/shipit/commit_serializer.rb +1 -0
  135. data/app/serializers/shipit/deploy_serializer.rb +2 -1
  136. data/app/serializers/shipit/hook_serializer.rb +1 -0
  137. data/app/serializers/shipit/pull_request_serializer.rb +1 -0
  138. data/app/serializers/shipit/rollback_serializer.rb +1 -0
  139. data/app/serializers/shipit/short_commit_serializer.rb +1 -0
  140. data/app/serializers/shipit/stack_serializer.rb +7 -1
  141. data/app/serializers/shipit/tail_task_serializer.rb +1 -0
  142. data/app/serializers/shipit/task_serializer.rb +2 -17
  143. data/app/serializers/shipit/user_serializer.rb +6 -1
  144. data/app/validators/ascii_only_validator.rb +4 -3
  145. data/app/validators/subset_validator.rb +1 -0
  146. data/app/views/layouts/_head.html.erb +0 -0
  147. data/app/views/layouts/shipit.html.erb +6 -4
  148. data/app/views/shipit/_variables.html.erb +1 -1
  149. data/app/views/shipit/api_clients/index.html.erb +36 -0
  150. data/app/views/shipit/api_clients/new.html.erb +33 -0
  151. data/app/views/shipit/api_clients/show.html.erb +35 -0
  152. data/app/views/shipit/ccmenu/project.xml.builder +2 -1
  153. data/app/views/shipit/deploys/new.html.erb +17 -12
  154. data/app/views/shipit/deploys/show.html.erb +2 -2
  155. data/app/views/shipit/merge_status/logged_out.erb +1 -1
  156. data/app/views/shipit/stacks/_header.html.erb +27 -12
  157. data/app/views/shipit/stacks/_links.html.erb +1 -0
  158. data/app/views/shipit/stacks/all_tasks.html.erb +28 -0
  159. data/app/views/shipit/stacks/index.html.erb +7 -2
  160. data/app/views/shipit/stacks/settings.html.erb +19 -0
  161. data/app/views/shipit/stacks/statistics.html.erb +82 -0
  162. data/app/views/shipit/tasks/show.html.erb +1 -1
  163. data/config/initializers/inflections.rb +2 -1
  164. data/config/locales/en.yml +18 -5
  165. data/config/routes.rb +14 -2
  166. data/db/migrate/20191209231045_create_shipit_repositories.rb +12 -0
  167. data/db/migrate/20191209231307_add_repository_reference_to_stacks.rb +15 -0
  168. data/db/migrate/20191216162728_backfill_repository_data.rb +22 -0
  169. data/db/migrate/20191216163010_remove_repository_information_from_stacks.rb +20 -0
  170. data/db/migrate/20191219205202_add_archived_since_to_stacks.rb +6 -0
  171. data/db/migrate/20200102175621_optional_task_commits.rb +6 -0
  172. data/db/migrate/20200109132519_add_sha_to_commit_deployments.rb +5 -0
  173. data/db/migrate/20200226211925_add_index_to_tasks_status.rb +5 -0
  174. data/db/migrate/20200427135152_add_pull_request_head_sha_to_commit.rb +5 -0
  175. data/db/migrate/20200615181558_add_rollback_once_aborted_to.rb +5 -0
  176. data/lib/shipit.rb +18 -20
  177. data/lib/shipit/cast_value.rb +1 -0
  178. data/lib/shipit/command.rb +14 -18
  179. data/lib/shipit/commands.rb +5 -4
  180. data/lib/shipit/csv_serializer.rb +1 -0
  181. data/lib/shipit/deploy_commands.rb +1 -0
  182. data/lib/shipit/engine.rb +11 -2
  183. data/lib/shipit/environment_variables.rb +11 -1
  184. data/lib/shipit/first_parent_commits_iterator.rb +1 -0
  185. data/lib/shipit/flock.rb +1 -0
  186. data/lib/shipit/github_app.rb +60 -6
  187. data/lib/shipit/github_http_cache_middleware.rb +57 -0
  188. data/lib/shipit/null_serializer.rb +1 -0
  189. data/lib/shipit/octokit_check_runs.rb +3 -2
  190. data/lib/shipit/octokit_iterator.rb +3 -2
  191. data/lib/shipit/paginator.rb +3 -2
  192. data/lib/shipit/rollback_commands.rb +1 -0
  193. data/lib/shipit/same_site_cookie_middleware.rb +29 -0
  194. data/lib/shipit/simple_message_verifier.rb +1 -0
  195. data/lib/shipit/stack_commands.rb +6 -3
  196. data/lib/shipit/stat.rb +1 -0
  197. data/lib/shipit/task_commands.rb +22 -14
  198. data/lib/shipit/version.rb +2 -1
  199. data/lib/snippets/release-gem +5 -1
  200. data/lib/tasks/cron.rake +2 -0
  201. data/lib/tasks/dev.rake +3 -2
  202. data/lib/tasks/shipit.rake +16 -17
  203. data/lib/tasks/teams.rake +1 -0
  204. data/test/controllers/api/base_controller_test.rb +3 -2
  205. data/test/controllers/api/ccmenu_controller_test.rb +9 -8
  206. data/test/controllers/api/commits_controller_test.rb +3 -2
  207. data/test/controllers/api/deploys_controller_test.rb +15 -14
  208. data/test/controllers/api/hooks_controller_test.rb +8 -7
  209. data/test/controllers/api/locks_controller_test.rb +7 -6
  210. data/test/controllers/api/outputs_controller_test.rb +3 -2
  211. data/test/controllers/api/pull_requests_controller_test.rb +8 -7
  212. data/test/controllers/api/release_statuses_controller_test.rb +2 -1
  213. data/test/controllers/api/rollback_controller_test.rb +113 -0
  214. data/test/controllers/api/stacks_controller_test.rb +44 -15
  215. data/test/controllers/api/tasks_controller_test.rb +13 -12
  216. data/test/controllers/api_clients_controller_test.rb +104 -0
  217. data/test/controllers/ccmenu_controller_test.rb +4 -3
  218. data/test/controllers/commit_checks_controller_test.rb +4 -3
  219. data/test/controllers/commits_controller_test.rb +3 -2
  220. data/test/controllers/deploys_controller_test.rb +33 -22
  221. data/test/controllers/github_authentication_controller_test.rb +1 -0
  222. data/test/controllers/merge_status_controller_test.rb +27 -9
  223. data/test/controllers/pull_requests_controller_test.rb +4 -3
  224. data/test/controllers/release_statuses_controller_test.rb +3 -2
  225. data/test/controllers/rollbacks_controller_test.rb +9 -8
  226. data/test/controllers/stacks_controller_test.rb +64 -15
  227. data/test/controllers/status_controller_test.rb +1 -0
  228. data/test/controllers/tasks_controller_test.rb +20 -19
  229. data/test/controllers/webhooks_controller_test.rb +36 -9
  230. data/test/dummy/config/application.rb +1 -1
  231. data/test/dummy/config/environments/development.rb +24 -4
  232. data/test/dummy/config/environments/test.rb +2 -0
  233. data/test/dummy/db/schema.rb +25 -11
  234. data/test/dummy/db/seeds.rb +34 -17
  235. data/test/fixtures/shipit/commit_deployment_statuses.yml +4 -4
  236. data/test/fixtures/shipit/commit_deployments.yml +8 -8
  237. data/test/fixtures/shipit/commits.yml +38 -0
  238. data/test/fixtures/shipit/repositories.yml +27 -0
  239. data/test/fixtures/shipit/stacks.yml +190 -30
  240. data/test/fixtures/shipit/tasks.yml +66 -3
  241. data/test/fixtures/timeout +2 -1
  242. data/test/helpers/api_helper.rb +1 -0
  243. data/test/helpers/fixture_aliases_helper.rb +1 -0
  244. data/test/helpers/hooks_helper.rb +2 -1
  245. data/test/helpers/json_helper.rb +15 -11
  246. data/test/helpers/links_helper.rb +4 -3
  247. data/test/helpers/payloads_helper.rb +1 -0
  248. data/test/helpers/queries_helper.rb +3 -2
  249. data/test/jobs/cache_deploy_spec_job_test.rb +2 -1
  250. data/test/jobs/chunk_rollup_job_test.rb +1 -0
  251. data/test/jobs/deliver_hook_job_test.rb +2 -1
  252. data/test/jobs/destroy_stack_job_test.rb +10 -0
  253. data/test/jobs/emit_event_job_test.rb +2 -1
  254. data/test/jobs/fetch_commit_stats_job_test.rb +1 -0
  255. data/test/jobs/fetch_deployed_revision_job_test.rb +1 -0
  256. data/test/jobs/github_sync_job_test.rb +1 -0
  257. data/test/jobs/mark_deploy_healthy_job_test.rb +1 -0
  258. data/test/jobs/merge_pull_requests_job_test.rb +5 -4
  259. data/test/jobs/perform_task_job_test.rb +4 -3
  260. data/test/jobs/purge_old_deliveries_job_test.rb +1 -0
  261. data/test/jobs/reap_dead_tasks_job_test.rb +68 -0
  262. data/test/jobs/refresh_github_user_job_test.rb +1 -0
  263. data/test/jobs/refresh_status_job_test.rb +1 -0
  264. data/test/jobs/unique_job_test.rb +1 -0
  265. data/test/jobs/update_github_last_deployed_ref_job_test.rb +13 -11
  266. data/test/middleware/same_site_cookie_middleware_test.rb +52 -0
  267. data/test/models/api_client_test.rb +1 -0
  268. data/test/models/commit_checks_test.rb +1 -0
  269. data/test/models/commit_deployment_status_test.rb +34 -4
  270. data/test/models/commit_deployment_test.rb +9 -11
  271. data/test/models/commits_test.rb +99 -7
  272. data/test/models/delivery_test.rb +3 -2
  273. data/test/models/deploy_spec_test.rb +47 -42
  274. data/test/models/deploy_stats_test.rb +113 -0
  275. data/test/models/deploys_test.rb +60 -13
  276. data/test/models/duration_test.rb +1 -0
  277. data/test/models/github_hook_test.rb +1 -0
  278. data/test/models/hook_test.rb +20 -16
  279. data/test/models/membership_test.rb +1 -0
  280. data/test/models/output_chunk_test.rb +1 -0
  281. data/test/models/pull_request_test.rb +18 -11
  282. data/test/models/release_statuses_test.rb +1 -0
  283. data/test/models/rollbacks_test.rb +1 -0
  284. data/test/models/shipit/check_run_test.rb +1 -0
  285. data/test/models/shipit/repository_test.rb +77 -0
  286. data/test/models/shipit/wehbooks/handlers_test.rb +27 -0
  287. data/test/models/stacks_test.rb +110 -56
  288. data/test/models/status/group_test.rb +1 -0
  289. data/test/models/status/missing_test.rb +1 -0
  290. data/test/models/status_test.rb +1 -0
  291. data/test/models/task_definitions_test.rb +9 -8
  292. data/test/models/tasks_test.rb +18 -1
  293. data/test/models/team_test.rb +4 -2
  294. data/test/models/undeployed_commits_test.rb +14 -0
  295. data/test/models/users_test.rb +109 -1
  296. data/test/test_command_integration.rb +3 -2
  297. data/test/test_helper.rb +38 -34
  298. data/test/unit/anonymous_user_serializer_test.rb +14 -0
  299. data/test/unit/command_test.rb +12 -7
  300. data/test/unit/commands_test.rb +1 -0
  301. data/test/unit/commit_serializer_test.rb +16 -0
  302. data/test/unit/csv_serializer_test.rb +3 -2
  303. data/test/unit/deploy_commands_test.rb +14 -4
  304. data/test/unit/deploy_serializer_test.rb +17 -0
  305. data/test/unit/environment_variables_test.rb +5 -4
  306. data/test/unit/github_app_test.rb +165 -0
  307. data/test/unit/github_url_helper_test.rb +1 -0
  308. data/test/unit/rollback_commands_test.rb +2 -1
  309. data/test/unit/shipit_helper_test.rb +17 -0
  310. data/test/unit/shipit_test.rb +1 -0
  311. data/test/unit/user_serializer_test.rb +14 -0
  312. data/test/unit/variable_definition_test.rb +1 -0
  313. metadata +215 -157
  314. data/lib/shipit/strip_cache_control.rb +0 -40
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class DeploySpec
3
4
  module KubernetesDiscovery
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
 
3
4
  module Shipit
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
 
3
4
  module Shipit
4
5
  class DeploySpec
5
6
  module NpmDiscovery
6
7
  # https://docs.npmjs.com/cli/publish
7
- PUBLIC = 'public'.freeze
8
- PRIVATE = 'restricted'.freeze
8
+ PUBLIC = 'public'
9
+ PRIVATE = 'restricted'
9
10
  VALID_ACCESS = [PUBLIC, PRIVATE].freeze
10
- NPM_REGISTRY = "https://registry.npmjs.org/".freeze
11
+ NPM_REGISTRY = "https://registry.npmjs.org/"
11
12
 
12
13
  def discover_dependencies_steps
13
14
  discover_package_json || super
@@ -57,7 +58,7 @@ module Shipit
57
58
  # are treated as 'next' npm dist-tags.
58
59
  # An 1.0.0-beta.1 would be installable using both:
59
60
  # `yarn add package@1.0.0-beta.1` and `yarn add package@next`
60
- return 'next' if ['-beta', '-alpha', '-rc', '-next'].any? { |tag| version.include? tag }
61
+ return 'next' if ['-beta', '-alpha', '-rc', '-next'].any? { |tag| version.include?(tag) }
61
62
  'latest'
62
63
  end
63
64
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class DeploySpec
3
4
  module PypiDiscovery
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class DeploySpec
3
4
  module RubygemsDiscovery
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ module Shipit
3
+ class DeployStats
4
+ delegate :empty?, to: :@deploys
5
+
6
+ def initialize(deploys)
7
+ @deploys = deploys
8
+ @durations = @deploys.map { |d| d.duration&.value }.compact
9
+ end
10
+
11
+ def count
12
+ @deploys.length
13
+ end
14
+
15
+ def average_duration
16
+ return if empty?
17
+ @durations.sum / @durations.length.to_f
18
+ end
19
+
20
+ def max_duration
21
+ @durations.max
22
+ end
23
+
24
+ def min_duration
25
+ @durations.min
26
+ end
27
+
28
+ def median_duration
29
+ return if @durations.empty?
30
+ (sorted_durations[(@durations.length - 1) / 2] + sorted_durations[@durations.length / 2]) / 2.0
31
+ end
32
+
33
+ def success_rate
34
+ return if empty?
35
+ (@deploys.count(&:success?) / @deploys.length.to_f) * 100
36
+ end
37
+
38
+ def compare(compare_stats)
39
+ {
40
+ count: percent_change(compare_stats.count, count),
41
+ average_duration: percent_change(compare_stats.average_duration, average_duration),
42
+ median_duration: percent_change(compare_stats.median_duration, median_duration),
43
+ }
44
+ end
45
+
46
+ protected
47
+
48
+ def sorted_durations
49
+ @sorted ||= @durations.sort
50
+ end
51
+
52
+ def percent_change(from, to)
53
+ return if to.nil? || from.nil?
54
+ return to * 100 if from.zero?
55
+ ((to - from) / from.to_f) * 100
56
+ end
57
+ end
58
+ end
@@ -1,15 +1,16 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class Duration < ActiveSupport::Duration
3
4
  ParseError = Class.new(ArgumentError)
4
5
 
5
- FORMAT = /
6
+ FORMAT = %r{
6
7
  \A
7
8
  (?<days>\d+d)?
8
9
  (?<hours>\d+h)?
9
10
  (?<minutes>\d+m)?
10
11
  (?<seconds>\d+s?)?
11
12
  \z
12
- /x
13
+ }x
13
14
  UNITS = {
14
15
  's' => :seconds,
15
16
  'm' => :minutes,
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class EphemeralCommitChecks
3
4
  FINAL_STATUSES = %w(failed error success).freeze
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
- class GithubHook < ActiveRecord::Base
3
+ class GithubHook < Record
3
4
  # TODO: app-migration, delete class
4
5
  belongs_to :stack, required: false # Required for fixtures
5
6
 
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  module GithubStatus
3
- CACHE_KEY = 'github::status'.freeze
4
+ CACHE_KEY = 'github::status'
4
5
 
5
6
  class << self
6
7
  def status
@@ -8,7 +9,7 @@ module Shipit
8
9
  end
9
10
 
10
11
  def refresh_status
11
- Rails.cache.write(CACHE_KEY, Shipit.github.api.github_status)
12
+ Rails.cache.write(CACHE_KEY, Shipit.github.api_status)
12
13
  rescue Faraday::Error, Octokit::ServerError
13
14
  end
14
15
  end
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
- class Hook < ActiveRecord::Base
3
+ class Hook < Record
3
4
  class DeliverySpec
4
5
  def initialize(event:, url:, content_type:, payload:)
5
6
  @event = event
@@ -19,7 +20,7 @@ module Shipit
19
20
  def http
20
21
  Faraday::Connection.new do |connection|
21
22
  connection.headers = headers
22
- connection.adapter Faraday.default_adapter
23
+ connection.adapter(Faraday.default_adapter)
23
24
  end
24
25
  end
25
26
 
@@ -57,9 +58,9 @@ module Shipit
57
58
  belongs_to :stack, required: false
58
59
  has_many :deliveries
59
60
 
60
- validates :delivery_url, presence: true, url: {no_local: true, allow_blank: true}
61
- validates :content_type, presence: true, inclusion: {in: CONTENT_TYPES.keys}
62
- validates :events, presence: true, subset: {of: EVENTS}
61
+ validates :delivery_url, presence: true, url: { no_local: true, allow_blank: true }
62
+ validates :content_type, presence: true, inclusion: { in: CONTENT_TYPES.keys }
63
+ validates :events, presence: true, subset: { of: EVENTS }
63
64
 
64
65
  serialize :events, Shipit::CSVSerializer
65
66
 
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
- class Membership < ActiveRecord::Base
3
+ class Membership < Record
3
4
  belongs_to :team, required: true
4
5
  belongs_to :user, required: true
5
6
 
6
- validates :user_id, uniqueness: {scope: :team_id}
7
+ validates :user_id, uniqueness: { scope: :team_id }
7
8
  end
8
9
  end
@@ -1,11 +1,16 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
- class OutputChunk < ActiveRecord::Base
3
+ class OutputChunk < Record
3
4
  belongs_to :task
4
5
 
5
6
  scope :tail, ->(start) { order(id: :asc).where('id > ?', start || 0) }
6
7
 
7
8
  def text=(string)
8
- super(string.force_encoding(Encoding::UTF_8).scrub)
9
+ if string.frozen?
10
+ super(string)
11
+ else
12
+ super(string.force_encoding(Encoding::UTF_8).scrub)
13
+ end
9
14
  end
10
15
  end
11
16
  end
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class PullRequest < ApplicationRecord
3
4
  include DeferredTouch
4
5
 
5
- MERGE_REQUEST_FIELD = 'Merge-Requested-By'.freeze
6
+ MERGE_REQUEST_FIELD = 'Merge-Requested-By'
6
7
 
7
8
  WAITING_STATUSES = %w(fetching pending).freeze
8
9
  QUEUED_STATUSES = %w(pending revalidating).freeze
9
- REJECTION_REASONS = %w(ci_failing merge_conflict requires_rebase).freeze
10
+ REJECTION_REASONS = %w(ci_missing ci_failing merge_conflict requires_rebase).freeze
10
11
  InvalidTransition = Class.new(StandardError)
11
12
  NotReady = Class.new(StandardError)
12
13
 
@@ -45,7 +46,7 @@ module Shipit
45
46
 
46
47
  deferred_touch stack: :updated_at
47
48
 
48
- validates :number, presence: true, uniqueness: {scope: :stack_id}
49
+ validates :number, presence: true, uniqueness: { scope: :stack_id }
49
50
 
50
51
  scope :waiting, -> { where(merge_status: WAITING_STATUSES) }
51
52
  scope :pending, -> { where(merge_status: 'pending') }
@@ -149,6 +150,7 @@ module Shipit
149
150
 
150
151
  def reject_unless_mergeable!
151
152
  return reject!('merge_conflict') if merge_conflict?
153
+ return reject!('ci_missing') if any_status_checks_missing?
152
154
  return reject!('ci_failing') if any_status_checks_failed?
153
155
  return reject!('requires_rebase') if stale?
154
156
  false
@@ -175,10 +177,10 @@ module Shipit
175
177
  # branch was already deleted somehow
176
178
  end
177
179
  complete!
178
- return true
180
+ true
179
181
  rescue Octokit::MethodNotAllowed # merge conflict
180
182
  reject!('merge_conflict')
181
- return false
183
+ false
182
184
  rescue Octokit::Conflict # shas didn't match, PR was updated.
183
185
  raise NotReady
184
186
  end
@@ -190,7 +192,11 @@ module Shipit
190
192
 
191
193
  def any_status_checks_failed?
192
194
  status = StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec)
193
- status.failure? || status.error? || status.missing?
195
+ status.failure? || status.error?
196
+ end
197
+
198
+ def any_status_checks_missing?
199
+ StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).missing?
194
200
  end
195
201
 
196
202
  def waiting?
@@ -284,7 +290,7 @@ module Shipit
284
290
 
285
291
  def find_or_create_commit_from_github_by_sha!(sha, attributes)
286
292
  if commit = stack.commits.by_sha(sha)
287
- return commit
293
+ commit
288
294
  else
289
295
  github_commit = Shipit.github.api.commit(stack.github_repo_name, sha)
290
296
  stack.commits.create_from_github!(github_commit, attributes)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Shipit
3
+ class Record < ActiveRecord::Base
4
+ self.abstract_class = true
5
+
6
+ class << self
7
+ def serializer_class
8
+ if defined? @serializer_class
9
+ @serializer_class
10
+ else
11
+ @serializer_class = "#{name}Serializer".safe_constantize
12
+ end
13
+ end
14
+ end
15
+
16
+ delegate :serializer_class, to: :class
17
+ end
18
+ end
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
- class ReleaseStatus < ActiveRecord::Base
3
+ class ReleaseStatus < Record
3
4
  MAX_DESCRIPTION_LENGTH = 140
4
5
  include DeferredTouch
5
6
 
@@ -13,7 +14,7 @@ module Shipit
13
14
  scope :to_be_created, -> { where(github_id: nil).order(id: :asc) }
14
15
 
15
16
  STATES = %w(pending success failure error).freeze
16
- validates :state, presence: true, inclusion: {in: STATES}
17
+ validates :state, presence: true, inclusion: { in: STATES }
17
18
 
18
19
  def create_status_on_github!
19
20
  return true if github_id?
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ module Shipit
3
+ class Repository < ApplicationRecord
4
+ OWNER_MAX_SIZE = 39
5
+ private_constant :OWNER_MAX_SIZE
6
+
7
+ NAME_MAX_SIZE = 100
8
+ private_constant :NAME_MAX_SIZE
9
+
10
+ validates :name, uniqueness: { scope: %i(owner), case_sensitive: false,
11
+ message: 'cannot be used more than once' }
12
+ validates :owner, :name, presence: true, ascii_only: true
13
+ validates :owner, format: { with: /\A[a-z0-9_\-\.]+\z/ }, length: { maximum: OWNER_MAX_SIZE }
14
+ validates :name, format: { with: /\A[a-z0-9_\-\.]+\z/ }, length: { maximum: NAME_MAX_SIZE }
15
+
16
+ has_many :stacks, dependent: :destroy
17
+
18
+ def self.from_github_repo_name(github_repo_name)
19
+ repo_owner, repo_name = github_repo_name.downcase.split('/')
20
+ find_by(owner: repo_owner, name: repo_name)
21
+ end
22
+
23
+ def name=(n)
24
+ super(n&.downcase)
25
+ end
26
+
27
+ def owner=(o)
28
+ super(o&.downcase)
29
+ end
30
+
31
+ def http_url
32
+ Shipit.github.url(full_name)
33
+ end
34
+
35
+ def full_name
36
+ "#{owner}/#{name}"
37
+ end
38
+
39
+ def git_url
40
+ "https://#{Shipit.github.domain}/#{owner}/#{name}.git"
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Shipit
2
3
  class Rollback < Deploy
3
4
  belongs_to :deploy, foreign_key: :parent_id, inverse_of: false
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  require 'fileutils'
2
3
 
3
4
  module Shipit
4
- class Stack < ActiveRecord::Base
5
+ class Stack < Record
5
6
  module NoDeployedCommit
6
7
  extend self
7
8
 
@@ -22,8 +23,6 @@ module Shipit
22
23
  end
23
24
  end
24
25
 
25
- REPO_OWNER_MAX_SIZE = 39
26
- REPO_NAME_MAX_SIZE = 100
27
26
  ENVIRONMENT_MAX_SIZE = 50
28
27
  REQUIRED_HOOKS = %i(push status).freeze
29
28
 
@@ -40,6 +39,27 @@ module Shipit
40
39
  has_many :hooks, dependent: :destroy
41
40
  has_many :api_clients, dependent: :destroy
42
41
  belongs_to :lock_author, class_name: :User, optional: true
42
+ belongs_to :repository
43
+ validates_associated :repository
44
+
45
+ scope :not_archived, -> { where(archived_since: nil) }
46
+
47
+ default_scope { preload(:repository) }
48
+
49
+ def env
50
+ {
51
+ 'ENVIRONMENT' => environment,
52
+ 'LAST_DEPLOYED_SHA' => last_deployed_commit.sha,
53
+ 'GITHUB_REPO_OWNER' => repository.owner,
54
+ 'GITHUB_REPO_NAME' => repository.name,
55
+ 'DEPLOY_URL' => deploy_url,
56
+ 'BRANCH' => branch,
57
+ }
58
+ end
59
+
60
+ def repository
61
+ super || build_repository
62
+ end
43
63
 
44
64
  def lock_author(*)
45
65
  super || AnonymousUser.new
@@ -49,11 +69,6 @@ module Shipit
49
69
  super(user&.logged_in? ? user : nil)
50
70
  end
51
71
 
52
- def self.repo(full_name)
53
- repo_owner, repo_name = full_name.downcase.split('/')
54
- where(repo_owner: repo_owner, repo_name: repo_name)
55
- end
56
-
57
72
  before_validation :update_defaults
58
73
  before_destroy :clear_local_files
59
74
  before_save :set_locked_since
@@ -66,18 +81,17 @@ module Shipit
66
81
  after_commit :sync_github, on: :create
67
82
  after_commit :schedule_merges_if_necessary, on: :update
68
83
 
69
- validates :repo_name, uniqueness: {scope: %i(repo_owner environment),
70
- message: 'cannot be used more than once with this environment'}
71
- validates :repo_owner, :repo_name, :environment, presence: true, ascii_only: true
72
- validates :repo_owner, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: REPO_OWNER_MAX_SIZE}
73
- validates :repo_name, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: REPO_NAME_MAX_SIZE}
74
- validates :environment, format: {with: /\A[a-z0-9\-_\:]+\z/}, length: {maximum: ENVIRONMENT_MAX_SIZE}
75
- validates :deploy_url, format: {with: URI.regexp(%w(http https ssh))}, allow_blank: true
84
+ validates :repository, uniqueness: {
85
+ scope: %i(environment), case_sensitive: false,
86
+ message: 'cannot be used more than once with this environment. Check archived stacks.'
87
+ }
88
+ validates :environment, format: { with: /\A[a-z0-9\-_\:]+\z/ }, length: { maximum: ENVIRONMENT_MAX_SIZE }
89
+ validates :deploy_url, format: { with: URI.regexp(%w(http https ssh)) }, allow_blank: true
76
90
 
77
- validates :lock_reason, length: {maximum: 4096}
91
+ validates :lock_reason, length: { maximum: 4096 }
78
92
 
79
93
  serialize :cached_deploy_spec, DeploySpec
80
- delegate :find_task_definition, :supports_rollback?, :links, :release_status?, :release_status_delay,
94
+ delegate :find_task_definition, :supports_rollback?, :release_status?, :release_status_delay,
81
95
  :release_status_context, :supports_fetch_deployed_revision?, to: :cached_deploy_spec, allow_nil: true
82
96
 
83
97
  def self.refresh_deployed_revisions
@@ -129,9 +143,24 @@ module Shipit
129
143
  end
130
144
 
131
145
  def trigger_deploy(*args, **kwargs)
146
+ if changed?
147
+ # If this is the first deploy since the spec changed it's possible the record will be dirty here, meaning we
148
+ # cant lock. In this one case persist the changes, otherwise log a warning and let the lock raise, so we
149
+ # can debug what's going on here. We don't expect anything other than the deploy spec to dirty the model
150
+ # instance, because of how that field is serialised.
151
+ if changes.keys == ['cached_deploy_spec']
152
+ save!
153
+ else
154
+ Rails.logger.warning("#{changes.keys} field(s) were unexpectedly modified on stack #{id} while deploying")
155
+ end
156
+ end
157
+
132
158
  run_now = kwargs.delete(:run_now)
133
- deploy = build_deploy(*args, **kwargs)
134
- deploy.save!
159
+ deploy = with_lock do
160
+ deploy = build_deploy(*args, **kwargs)
161
+ deploy.save!
162
+ deploy
163
+ end
135
164
  run_now ? deploy.run_now! : deploy.enqueue
136
165
  continuous_delivery_resumed!
137
166
  deploy
@@ -150,14 +179,16 @@ module Shipit
150
179
  end
151
180
 
152
181
  def trigger_continuous_delivery
182
+ return if cached_deploy_spec.blank?
183
+
153
184
  commit = next_commit_to_deploy
154
185
 
155
- if !deployable? || deployed_too_recently? || commit.nil? || commit.deployed?
186
+ if should_resume_continuous_delivery?(commit)
156
187
  continuous_delivery_resumed!
157
188
  return
158
189
  end
159
190
 
160
- if commit.deploy_failed? || (checks? && !EphemeralCommitChecks.new(commit).run.success?)
191
+ if should_delay_continuous_delivery?(commit)
161
192
  continuous_delivery_delayed!
162
193
  return
163
194
  end
@@ -189,7 +220,7 @@ module Shipit
189
220
  def async_refresh_deployed_revision
190
221
  async_refresh_deployed_revision!
191
222
  rescue => error
192
- logger.warn "Failed to dispatch FetchDeployedRevisionJob: [#{error.class.name}] #{error.message}"
223
+ logger.warn("Failed to dispatch FetchDeployedRevisionJob: [#{error.class.name}] #{error.message}")
193
224
  end
194
225
 
195
226
  def async_refresh_deployed_revision!
@@ -257,8 +288,8 @@ module Shipit
257
288
  next if commits_to_lock.empty?
258
289
 
259
290
  affected_rows += commits
260
- .where(id: commits_to_lock.map(&:id).uniq)
261
- .lock_all(revert.author)
291
+ .where(id: commits_to_lock.map(&:id).uniq)
292
+ .lock_all(revert.author)
262
293
  end
263
294
 
264
295
  touch if affected_rows > 1
@@ -320,21 +351,12 @@ module Shipit
320
351
  cached_deploy_spec&.pull_request_merge_method || Shipit.default_merge_method
321
352
  end
322
353
 
323
- def repo_name=(name)
324
- super(name&.downcase)
325
- end
326
-
327
- def repo_owner=(name)
328
- super(name&.downcase)
329
- end
330
-
331
- def repo_http_url
332
- Shipit.github.url("#{repo_owner}/#{repo_name}")
333
- end
334
-
335
- def repo_git_url
336
- "https://#{Shipit.github.domain}/#{repo_owner}/#{repo_name}.git"
337
- end
354
+ delegate :name=, to: :repository, prefix: :repo
355
+ delegate :name, to: :repository, prefix: :repo
356
+ delegate :owner=, to: :repository, prefix: :repo
357
+ delegate :owner, to: :repository, prefix: :repo
358
+ delegate :http_url, to: :repository, prefix: :repo
359
+ delegate :git_url, to: :repository, prefix: :repo
338
360
 
339
361
  def base_path
340
362
  Rails.root.join('data', 'stacks', repo_owner, repo_name, environment)
@@ -354,8 +376,8 @@ module Shipit
354
376
 
355
377
  def clear_git_cache!
356
378
  tmp_path = "#{git_path}-#{SecureRandom.hex}"
379
+ return unless File.exist?(git_path)
357
380
  acquire_git_cache_lock do
358
- return unless File.exist?(git_path)
359
381
  File.rename(git_path, tmp_path)
360
382
  end
361
383
  FileUtils.rm_rf(tmp_path)
@@ -389,7 +411,7 @@ module Shipit
389
411
  if resource.try(:message) == 'Moved Permanently'
390
412
  resource = Shipit.github.api.get(resource.url)
391
413
  end
392
- update!(repo_owner: resource.owner.login, repo_name: resource.name)
414
+ repository.update!(owner: resource.owner.login, name: resource.name)
393
415
  end
394
416
 
395
417
  def active_task?
@@ -406,7 +428,7 @@ module Shipit
406
428
  end
407
429
 
408
430
  def lock(reason, user)
409
- params = {lock_reason: reason, lock_author: user}
431
+ params = { lock_reason: reason, lock_author: user }
410
432
  update!(params)
411
433
  end
412
434
 
@@ -414,6 +436,18 @@ module Shipit
414
436
  update!(lock_reason: nil, lock_author: nil, locked_since: nil)
415
437
  end
416
438
 
439
+ def archived?
440
+ archived_since.present?
441
+ end
442
+
443
+ def archive!(user)
444
+ update!(archived_since: Time.now, lock_reason: "Archived", lock_author: user)
445
+ end
446
+
447
+ def unarchive!
448
+ update!(archived_since: nil, lock_reason: nil, lock_author: nil, locked_since: nil)
449
+ end
450
+
417
451
  def to_param
418
452
  [repo_owner, repo_name, environment].join('/')
419
453
  end
@@ -429,11 +463,14 @@ module Shipit
429
463
 
430
464
  def self.from_param!(param)
431
465
  repo_owner, repo_name, environment = param.split('/')
432
- where(
433
- repo_owner: repo_owner.downcase,
434
- repo_name: repo_name.downcase,
435
- environment: environment,
436
- ).first!
466
+ includes(:repository)
467
+ .where(
468
+ repositories: {
469
+ owner: repo_owner.downcase,
470
+ name: repo_name.downcase,
471
+ },
472
+ environment: environment,
473
+ ).first!
437
474
  end
438
475
 
439
476
  delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,
@@ -463,13 +500,15 @@ module Shipit
463
500
  end
464
501
 
465
502
  def update_latest_deployed_ref
466
- UpdateGithubLastDeployedRefJob.perform_later(self)
503
+ if Shipit.update_latest_deployed_ref
504
+ UpdateGithubLastDeployedRefJob.perform_later(self)
505
+ end
467
506
  end
468
507
 
469
508
  def broadcast_update
470
509
  Pubsubstub.publish(
471
510
  "stack.#{id}",
472
- {id: id, updated_at: updated_at}.to_json,
511
+ { id: id, updated_at: updated_at }.to_json,
473
512
  name: 'update',
474
513
  )
475
514
  end
@@ -521,6 +560,13 @@ module Shipit
521
560
  GithubSyncJob.perform_later(stack_id: id)
522
561
  end
523
562
 
563
+ def links
564
+ links_spec = cached_deploy_spec&.links || {}
565
+ context = EnvironmentVariables.with(env)
566
+
567
+ links_spec.transform_values { |url| context.interpolate(url) }
568
+ end
569
+
524
570
  private
525
571
 
526
572
  def clear_cache
@@ -556,7 +602,7 @@ module Shipit
556
602
  return unless previous_changes.include?('lock_reason')
557
603
 
558
604
  lock_details = if previous_changes['lock_reason'].last.blank?
559
- {from: previous_changes['locked_since'].first, until: Time.zone.now}
605
+ { from: previous_changes['locked_since'].first, until: Time.zone.now }
560
606
  end
561
607
 
562
608
  Hook.emit(:lock, self, locked: locked?, lock_details: lock_details, stack: self)
@@ -582,5 +628,18 @@ module Shipit
582
628
  def ci_enabled_cache_key
583
629
  "stacks:#{id}:ci_enabled"
584
630
  end
631
+
632
+ def should_resume_continuous_delivery?(commit)
633
+ !deployable? ||
634
+ deployed_too_recently? ||
635
+ commit.nil? ||
636
+ commit.deployed?
637
+ end
638
+
639
+ def should_delay_continuous_delivery?(commit)
640
+ commit.deploy_failed? ||
641
+ (checks? && !EphemeralCommitChecks.new(commit).run.success?) ||
642
+ commit.recently_pushed?
643
+ end
585
644
  end
586
645
  end