shipit-engine 0.29.0 → 0.30.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/app/assets/images/archive-solid.svg +1 -0
  4. data/app/assets/stylesheets/_pages/_stacks.scss +76 -0
  5. data/app/controllers/shipit/api/stacks_controller.rb +20 -1
  6. data/app/controllers/shipit/api_clients_controller.rb +49 -0
  7. data/app/controllers/shipit/merge_status_controller.rb +8 -4
  8. data/app/controllers/shipit/stacks_controller.rb +58 -9
  9. data/app/controllers/shipit/webhooks_controller.rb +2 -130
  10. data/app/helpers/shipit/stacks_helper.rb +4 -0
  11. data/app/jobs/shipit/background_job/unique.rb +3 -1
  12. data/app/jobs/shipit/continuous_delivery_job.rb +1 -0
  13. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  14. data/app/models/shipit/commit.rb +21 -9
  15. data/app/models/shipit/commit_deployment.rb +15 -11
  16. data/app/models/shipit/commit_deployment_status.rb +6 -2
  17. data/app/models/shipit/deploy.rb +48 -7
  18. data/app/models/shipit/deploy_stats.rb +57 -0
  19. data/app/models/shipit/repository.rb +38 -0
  20. data/app/models/shipit/stack.rb +41 -34
  21. data/app/models/shipit/task.rb +26 -4
  22. data/app/models/shipit/webhooks.rb +32 -0
  23. data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +19 -0
  24. data/app/models/shipit/webhooks/handlers/handler.rb +40 -0
  25. data/app/models/shipit/webhooks/handlers/membership_handler.rb +45 -0
  26. data/app/models/shipit/webhooks/handlers/push_handler.rb +20 -0
  27. data/app/models/shipit/webhooks/handlers/status_handler.rb +26 -0
  28. data/app/serializers/shipit/stack_serializer.rb +6 -1
  29. data/app/validators/ascii_only_validator.rb +3 -3
  30. data/app/views/layouts/_head.html.erb +0 -0
  31. data/app/views/layouts/shipit.html.erb +4 -2
  32. data/app/views/shipit/api_clients/index.html.erb +36 -0
  33. data/app/views/shipit/api_clients/new.html.erb +33 -0
  34. data/app/views/shipit/api_clients/show.html.erb +35 -0
  35. data/app/views/shipit/merge_status/logged_out.erb +1 -1
  36. data/app/views/shipit/stacks/_header.html.erb +12 -7
  37. data/app/views/shipit/stacks/_links.html.erb +1 -0
  38. data/app/views/shipit/stacks/index.html.erb +7 -2
  39. data/app/views/shipit/stacks/settings.html.erb +19 -0
  40. data/app/views/shipit/stacks/statistics.html.erb +82 -0
  41. data/config/locales/en.yml +14 -2
  42. data/config/routes.rb +4 -0
  43. data/db/migrate/20191209231045_create_shipit_repositories.rb +12 -0
  44. data/db/migrate/20191209231307_add_repository_reference_to_stacks.rb +15 -0
  45. data/db/migrate/20191216162728_backfill_repository_data.rb +22 -0
  46. data/db/migrate/20191216163010_remove_repository_information_from_stacks.rb +20 -0
  47. data/db/migrate/20191219205202_add_archived_since_to_stacks.rb +6 -0
  48. data/db/migrate/20200102175621_optional_task_commits.rb +6 -0
  49. data/db/migrate/20200109132519_add_sha_to_commit_deployments.rb +5 -0
  50. data/lib/shipit/github_app.rb +32 -3
  51. data/lib/shipit/task_commands.rb +10 -2
  52. data/lib/shipit/version.rb +1 -1
  53. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  54. data/test/controllers/api/stacks_controller_test.rb +14 -6
  55. data/test/controllers/api_clients_controller_test.rb +103 -0
  56. data/test/controllers/merge_status_controller_test.rb +21 -4
  57. data/test/controllers/stacks_controller_test.rb +35 -0
  58. data/test/controllers/webhooks_controller_test.rb +26 -0
  59. data/test/dummy/config/environments/development.rb +22 -4
  60. data/test/dummy/db/schema.rb +17 -6
  61. data/test/dummy/db/seeds.rb +20 -6
  62. data/test/fixtures/shipit/commit_deployment_statuses.yml +4 -4
  63. data/test/fixtures/shipit/commit_deployments.yml +8 -8
  64. data/test/fixtures/shipit/commits.yml +23 -0
  65. data/test/fixtures/shipit/repositories.yml +23 -0
  66. data/test/fixtures/shipit/stacks.yml +100 -16
  67. data/test/fixtures/shipit/tasks.yml +66 -3
  68. data/test/jobs/destroy_stack_job_test.rb +9 -0
  69. data/test/models/commit_deployment_status_test.rb +33 -4
  70. data/test/models/commit_deployment_test.rb +8 -11
  71. data/test/models/commits_test.rb +22 -2
  72. data/test/models/deploy_stats_test.rb +112 -0
  73. data/test/models/deploys_test.rb +55 -17
  74. data/test/models/pull_request_test.rb +1 -1
  75. data/test/models/shipit/repository_test.rb +76 -0
  76. data/test/models/shipit/wehbooks/handlers_test.rb +26 -0
  77. data/test/models/stacks_test.rb +44 -51
  78. data/test/models/undeployed_commits_test.rb +13 -0
  79. data/test/test_helper.rb +3 -1
  80. data/test/unit/deploy_commands_test.rb +9 -0
  81. data/test/unit/github_app_test.rb +136 -0
  82. metadata +161 -128
@@ -16,8 +16,8 @@ module Shipit
16
16
  belongs_to :user, optional: true
17
17
  belongs_to :aborted_by, class_name: 'User', optional: true
18
18
  belongs_to :stack, counter_cache: true
19
- belongs_to :until_commit, class_name: 'Commit'
20
- belongs_to :since_commit, class_name: 'Commit'
19
+ belongs_to :until_commit, class_name: 'Commit', required: false
20
+ belongs_to :since_commit, class_name: 'Commit', required: false
21
21
 
22
22
  deferred_touch stack: :updated_at
23
23
 
@@ -29,14 +29,18 @@ module Shipit
29
29
  scope :success, -> { where(status: 'success') }
30
30
  scope :completed, -> { where(status: COMPLETED_STATUSES) }
31
31
  scope :active, -> { where(status: ACTIVE_STATUSES) }
32
+ scope :not_active, -> { where(status: COMPLETED_STATUSES + UNSUCCESSFUL_STATUSES) }
32
33
  scope :exclusive, -> { where(allow_concurrency: false) }
33
34
  scope :unsuccessful, -> { where(status: UNSUCCESSFUL_STATUSES) }
35
+ scope :last_seven_days, -> { where("created_at > ?", 7.days.ago) }
36
+ scope :previous_seven_days, -> { where(created_at: 14.days.ago..7.days.ago) }
34
37
 
35
38
  scope :due_for_rollup, -> { completed.where(rolled_up: false).where('created_at <= ?', 1.hour.ago) }
36
39
 
37
40
  after_save :record_status_change
38
41
  after_create :prevent_concurrency, unless: :allow_concurrency?
39
- after_commit :emit_hooks
42
+ after_commit :emit_hooks, on: :create
43
+ after_commit :emit_hooks_if_status_changed, on: :update
40
44
 
41
45
  class << self
42
46
  def durations
@@ -303,9 +307,13 @@ module Shipit
303
307
  end
304
308
  end
305
309
 
306
- def emit_hooks
310
+ def emit_hooks_if_status_changed
307
311
  return unless @status_changed
308
312
  @status_changed = nil
313
+ emit_hooks
314
+ end
315
+
316
+ def emit_hooks
309
317
  Hook.emit(hook_event, stack, hook_event => self, status: status, stack: stack)
310
318
  end
311
319
 
@@ -327,6 +335,20 @@ module Shipit
327
335
  end
328
336
  end
329
337
 
338
+ def commit_range?
339
+ since_commit && until_commit
340
+ end
341
+
342
+ def includes_commit?(commit)
343
+ return false unless commit_range?
344
+
345
+ if since_commit == until_commit
346
+ commit.id == since_commit.id
347
+ else
348
+ commit.id > since_commit.id && commit.id <= until_commit.id
349
+ end
350
+ end
351
+
330
352
  private
331
353
 
332
354
  def prevent_concurrency
@@ -0,0 +1,32 @@
1
+ module Shipit
2
+ module Webhooks
3
+ class << self
4
+ def default_handlers
5
+ {
6
+ 'push' => [Handlers::PushHandler],
7
+ 'status' => [Handlers::StatusHandler],
8
+ 'membership' => [Handlers::MembershipHandler],
9
+ 'check_suite' => [Handlers::CheckSuiteHandler],
10
+ }
11
+ end
12
+
13
+ def handlers
14
+ @handlers ||= reset_handlers!
15
+ end
16
+
17
+ def reset_handlers!
18
+ @handlers = default_handlers
19
+ end
20
+
21
+ def register_handler(event, callable = nil, &block)
22
+ handlers[event] ||= []
23
+ handlers[event] << callable if callable
24
+ handlers[event] << block if block_given?
25
+ end
26
+
27
+ def for_event(event)
28
+ handlers.fetch(event) { [] }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Shipit
2
+ module Webhooks
3
+ module Handlers
4
+ class CheckSuiteHandler < Handler
5
+ params do
6
+ requires :check_suite do
7
+ requires :head_sha, String
8
+ requires :head_branch, String
9
+ end
10
+ end
11
+ def process
12
+ stacks.where(branch: params.check_suite.head_branch).each do |stack|
13
+ stack.commits.where(sha: params.check_suite.head_sha).each(&:schedule_refresh_check_runs!)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Shipit
2
+ module Webhooks
3
+ module Handlers
4
+ class Handler
5
+ class << self
6
+ attr_reader :param_parser
7
+
8
+ def params(&block)
9
+ @param_parser = ExplicitParameters::Parameters.define(&block)
10
+ end
11
+ end
12
+
13
+ def self.call(params)
14
+ new(params).process
15
+ end
16
+
17
+ attr_reader :params, :payload
18
+
19
+ def initialize(payload)
20
+ @payload = payload
21
+ @params = self.class.param_parser.parse!(payload)
22
+ end
23
+
24
+ def process
25
+ raise NotImplementedError
26
+ end
27
+
28
+ private
29
+
30
+ def stacks
31
+ @stacks ||= Repository.from_github_repo_name(repository_name)&.stacks || Stack.none
32
+ end
33
+
34
+ def repository_name
35
+ payload.dig('repository', 'full_name')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ module Shipit
2
+ module Webhooks
3
+ module Handlers
4
+ class MembershipHandler < Handler
5
+ params do
6
+ requires :action, String
7
+ requires :team do
8
+ requires :id, Integer
9
+ requires :name, String
10
+ requires :slug, String
11
+ requires :url, String
12
+ end
13
+ requires :organization do
14
+ requires :login, String
15
+ end
16
+ requires :member do
17
+ requires :login, String
18
+ end
19
+ end
20
+ def process
21
+ team = find_or_create_team!
22
+ member = User.find_or_create_by_login!(params.member.login)
23
+
24
+ case params.action
25
+ when 'added'
26
+ team.add_member(member)
27
+ when 'removed'
28
+ team.members.delete(member)
29
+ else
30
+ raise ArgumentError, "Don't know how to perform action: `#{action.inspect}`"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def find_or_create_team!
37
+ Team.find_or_create_by!(github_id: params.team.id) do |team|
38
+ team.github_team = params.team
39
+ team.organization = params.organization.login
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ module Shipit
2
+ module Webhooks
3
+ module Handlers
4
+ class PushHandler < Handler
5
+ params do
6
+ requires :ref
7
+ end
8
+ def process
9
+ stacks.where(branch: branch).each(&:sync_github)
10
+ end
11
+
12
+ private
13
+
14
+ def branch
15
+ params.ref.gsub('refs/heads/', '')
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module Shipit
2
+ module Webhooks
3
+ module Handlers
4
+ class StatusHandler < Handler
5
+ params do
6
+ requires :sha, String
7
+ requires :state, String
8
+ accepts :description, String
9
+ accepts :target_url, String
10
+ accepts :context, String
11
+ accepts :created_at, String
12
+
13
+ accepts :branches, Array do
14
+ requires :name, String
15
+ end
16
+ end
17
+
18
+ def process
19
+ Commit.where(sha: params.sha).each do |commit|
20
+ commit.create_status_from_github!(params)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -5,7 +5,8 @@ module Shipit
5
5
  has_one :lock_author
6
6
  attributes :id, :repo_owner, :repo_name, :environment, :html_url, :url, :tasks_url, :deploy_url, :pull_requests_url,
7
7
  :deploy_spec, :undeployed_commits_count, :is_locked, :lock_reason, :continuous_deployment, :created_at,
8
- :updated_at, :locked_since, :last_deployed_at, :branch, :merge_queue_enabled
8
+ :updated_at, :locked_since, :last_deployed_at, :branch, :merge_queue_enabled, :is_archived,
9
+ :archived_since
9
10
 
10
11
  def url
11
12
  api_stack_url(object)
@@ -39,6 +40,10 @@ module Shipit
39
40
  object.locked?
40
41
  end
41
42
 
43
+ def is_archived
44
+ object.archived?
45
+ end
46
+
42
47
  def deploy_spec
43
48
  object.cached_deploy_spec.cacheable.config
44
49
  end
@@ -1,7 +1,7 @@
1
1
  class AsciiOnlyValidator < ActiveModel::EachValidator
2
2
  def validate_each(record, attribute, value)
3
- value.encode(Encoding::ASCII)
4
- rescue Encoding::UndefinedConversionError
5
- record.errors.add(attribute, :ascii)
3
+ if value && !value.ascii_only?
4
+ record.errors.add(attribute, :ascii)
5
+ end
6
6
  end
7
7
  end
File without changes
@@ -12,6 +12,8 @@
12
12
  <%= javascript_include_tag :shipit %>
13
13
  <%= yield :update_subscription %>
14
14
  <%= csrf_meta_tags %>
15
+
16
+ <%= render 'layouts/head' %>
15
17
  </head>
16
18
  <body>
17
19
  <% flash.each do |name, msg| %>
@@ -52,11 +54,11 @@
52
54
  <% end %>
53
55
 
54
56
  <header class="header">
55
- <%= link_to "Shipit v#{Shipit::VERSION}", "https://github.com/Shopify/shipit-engine/tree/v#{Shipit::VERSION}", class: 'powered-by' %>
57
+ <%= link_to "Powered by Shipit v#{Shipit::VERSION}", "https://github.com/Shopify/shipit-engine/tree/v#{Shipit::VERSION}", class: 'powered-by' %>
56
58
  <div class="wrapper">
57
59
  <div class="header__inner">
58
60
  <a href="/" class="logo">
59
- <span class="visually-hidden">Shipit</span>
61
+ <span class="visually-hidden"><%= Shipit.app_name %></span>
60
62
  </a>
61
63
  <div class="header__page-title">
62
64
  <%= yield :page_title %>
@@ -0,0 +1,36 @@
1
+
2
+ <div class="wrapper">
3
+ <section>
4
+ <%= link_to "Create new api client", new_api_client_path, class: 'btn'%>
5
+ </section>
6
+ </div>
7
+ <div class="wrapper">
8
+ <section>
9
+ <ul class="task-list">
10
+ <% @api_clients.each do |api_client|%>
11
+ <li>
12
+ <p>
13
+ <%= link_to api_client.name, api_client_path(api_client) %>
14
+ created by <%= api_client.creator.name %> on <%= api_client.created_at%>
15
+ </p>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ </section>
20
+ </div>
21
+
22
+ <div class="wrapper">
23
+ <section class="pagination">
24
+ <% if params[:since].present? %>
25
+ <%= link_to 'Start', @links[:first], class: 'header__btn btn' %>
26
+ <% else %>
27
+ <%= link_to 'Start', '', class: 'header__btn btn btn--disabled disabled' %>
28
+ <% end %>
29
+
30
+ <% if url = @links[:next] %>
31
+ <%= link_to 'Older', url, class: 'header__btn btn' %>
32
+ <% else %>
33
+ <%= link_to 'Older', '', class: 'header__btn btn btn--disabled disabled' %>
34
+ <% end %>
35
+ </section>
36
+ </div>
@@ -0,0 +1,33 @@
1
+ <div class="wrapper">
2
+ <section>
3
+ <header class="section-header">
4
+ <p>New Api Client</p>
5
+ </header>
6
+ </section>
7
+
8
+ <%= form_for @api_client, url: api_clients_path(@api_client) do |f| %>
9
+ <section>
10
+ <%= label_tag "App Name" %>
11
+ <%= f.text_field :name, placeholder: 'e.g. spy', required: true %>
12
+ </section>
13
+
14
+ <section>
15
+ <%= label_tag "Permissions" %>
16
+ <ul class="deploy-checklist">
17
+ <% Shipit::ApiClient::PERMISSIONS.each do |permission| %>
18
+ <li class="deploy-checklist__item">
19
+ <%= check_box_tag 'api_client[permissions][]', permission, false,
20
+ class: 'deploy-checklist__item__checkbox', id: "checkbox_" + permission %>
21
+ <label class="deploy-checklist__item__label" for="checkbox_<%= permission %>">
22
+ <%= permission %>
23
+ </label>
24
+ </li>
25
+ <% end %>
26
+ </ul>
27
+ </section>
28
+
29
+ <section class="submit-section">
30
+ <%= f.submit "Create", :class => ['btn', 'primary'] %>
31
+ </section>
32
+ <% end %>
33
+ </div>
@@ -0,0 +1,35 @@
1
+ <div class="wrapper">
2
+ <section>
3
+ <header class="section-header">
4
+ <h2>Api client: <b><%= @api_client.name %></b></h2>
5
+ </header>
6
+
7
+ <p>Created by <%= @api_client.creator.name %> on <%= @api_client.created_at %></p>
8
+ </section>
9
+
10
+ <section>
11
+ <h3>Authentication token:</h3>
12
+ <code style="background-color: yellow">
13
+ <b><%= @api_client.authentication_token %></b>
14
+ </code>
15
+ </section>
16
+
17
+ <section>
18
+ <%= form_for @api_client, url: api_client_path(@api_client) do |f| %>
19
+ <h3> Permissions </h3>
20
+ <ul class="deploy-checklist">
21
+ <% Shipit::ApiClient::PERMISSIONS.each do |permission| %>
22
+ <li class="deploy-checklist__item">
23
+ <%= check_box_tag 'api_client[permissions][]', permission, @api_client.permissions.include?(permission),
24
+ class: 'deploy-checklist__item__checkbox', id: "checkbox_" + permission %>
25
+ <label class="deploy-checklist__item__label" for="checkbox_<%= permission %>">
26
+ <%= permission %>
27
+ </label>
28
+ </li>
29
+ <% end %>
30
+ </ul>
31
+ <%= f.submit "Update", :class => ['btn', 'primary'] %>
32
+ <% end %>
33
+ </section>
34
+
35
+ </div>
@@ -3,7 +3,7 @@
3
3
  <%= render 'anchor', color: '#bd2c00' %>
4
4
  </div>
5
5
  <h4 class="status-heading text-red">
6
- Please log in to <%= link_to 'Shipit', '/', target: '_blank', rel: 'noopener' %>
6
+ Please log in to <%= link_to Shipit.app_name, '/', target: '_blank', rel: 'noopener' %>
7
7
  </h4>
8
8
  <span class="status-meta">
9
9
  Here Comes The Walrus is unable to check merge status.
@@ -5,23 +5,26 @@
5
5
 
6
6
 
7
7
  <% content_for :primary_navigation do %>
8
- <%= link_to 'Refresh statuses & commits', stack_refresh_path(stack), method: 'post', class: "header__btn btn" %>
8
+ <%= link_to t('stack.nav.refresh'), stack_refresh_path(stack), method: 'post', class: "header__btn btn" %>
9
9
  <% end %>
10
10
 
11
11
  <% content_for :secondary_navigation do %>
12
12
  <ul class="nav__list nav__list--primary">
13
13
  <li class="nav__list__item">
14
- <%= link_to "Commits & Deploys", stack_path(stack) %>
14
+ <%= link_to t('stack.nav.commits'), stack_path(stack) %>
15
15
  </li>
16
16
  <li class="nav__list__item">
17
- <%= link_to 'Settings', stack_settings_path(stack) %>
17
+ <%= link_to t('stack.nav.settings'), stack_settings_path(stack) %>
18
18
  </li>
19
19
  <li class="nav__list__item">
20
- <%= link_to 'Timeline', index_stack_tasks_path(stack) %>
20
+ <%= link_to t('stack.nav.timeline'), index_stack_tasks_path(stack) %>
21
+ </li>
22
+ <li class="nav__list__item">
23
+ <%= link_to t('stack.nav.statistics'), stack_statistics_path(stack) %>
21
24
  </li>
22
25
  <% if stack.merge_queue_enabled? %>
23
26
  <li class="nav__list__item">
24
- <%= link_to "Merge Queue (#{stack.pull_requests.queued.count})", stack_pull_requests_path(stack) %>
27
+ <%= link_to t('stack.nav.merge_queue', count: stack.pull_requests.queued.count), stack_pull_requests_path(stack) %>
25
28
  </li>
26
29
  <% end %>
27
30
 
@@ -38,6 +41,8 @@
38
41
  </ul>
39
42
  </li>
40
43
  <% end %>
44
+
45
+ <%= render partial: 'shipit/stacks/links', locals: { stack: stack } %>
41
46
  </ul>
42
47
 
43
48
  <ul class="nav__list nav__list--secondary">
@@ -49,11 +54,11 @@
49
54
  <% end %>
50
55
  <% end %>
51
56
  <li class="nav__list__item">
52
- <%= link_to 'View on GitHub', github_repo_url(stack.repo_owner, stack.repo_name) %>
57
+ <%= link_to t('stack.nav.view_on_github'), github_repo_url(stack.repo_owner, stack.repo_name) %>
53
58
  </li>
54
59
  <% if stack.deploy_url.present? %>
55
60
  <li class="nav__list__item">
56
- <%= sanitize link_to 'View website', stack.deploy_url, :target => '_blank', :class => 'deploy-url' %>
61
+ <%= sanitize link_to t('stack.nav.deploy_link'), stack.deploy_url, :target => '_blank', :class => 'deploy-url' %>
57
62
  </li>
58
63
  <% end %>
59
64
  </ul>