shipit-engine 0.16.0 → 0.17.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/assets/images/caret-down.svg +1 -0
  4. data/app/assets/javascripts/shipit/stacks.js.coffee +10 -0
  5. data/app/assets/stylesheets/_base/_banner.scss +7 -3
  6. data/app/assets/stylesheets/_base/_base.scss +0 -74
  7. data/app/assets/stylesheets/_base/_buttons.scss +7 -3
  8. data/app/assets/stylesheets/_base/_colors.scss +2 -0
  9. data/app/assets/stylesheets/_base/_icons.scss +8 -0
  10. data/app/assets/stylesheets/_base/_spacing.scss +21 -0
  11. data/app/assets/stylesheets/_pages/_commits.scss +41 -3
  12. data/app/assets/stylesheets/_structure/_layout.scss +8 -35
  13. data/app/assets/stylesheets/_structure/_main.scss +2 -2
  14. data/app/assets/stylesheets/_structure/_navigation.scss +89 -0
  15. data/app/assets/stylesheets/shipit.scss +3 -0
  16. data/app/controllers/concerns/shipit/api/rendering.rb +3 -6
  17. data/app/controllers/shipit/api/ccmenu_controller.rb +4 -0
  18. data/app/controllers/shipit/commits_controller.rb +18 -0
  19. data/app/jobs/shipit/destroy_stack_job.rb +25 -0
  20. data/app/jobs/shipit/github_sync_job.rb +12 -1
  21. data/app/jobs/shipit/merge_pull_requests_job.rb +3 -0
  22. data/app/models/shipit/commit.rb +14 -2
  23. data/app/models/shipit/deploy.rb +5 -0
  24. data/app/models/shipit/deploy_spec.rb +9 -6
  25. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +24 -2
  26. data/app/models/shipit/pull_request.rb +16 -1
  27. data/app/models/shipit/stack.rb +11 -4
  28. data/app/models/shipit/task_definition.rb +4 -0
  29. data/app/models/shipit/team.rb +1 -1
  30. data/app/models/shipit/undeployed_commit.rb +1 -2
  31. data/app/models/shipit/user.rb +1 -1
  32. data/app/models/shipit/variable_definition.rb +5 -0
  33. data/app/serializers/shipit/stack_serializer.rb +1 -1
  34. data/app/views/layouts/shipit.html.erb +2 -1
  35. data/app/views/shipit/commits/_commit.html.erb +9 -1
  36. data/app/views/shipit/deploys/rollback.html.erb +1 -1
  37. data/app/views/shipit/stacks/_header.html.erb +15 -3
  38. data/app/views/shipit/stacks/settings.html.erb +1 -1
  39. data/config/locales/en.yml +9 -5
  40. data/config/routes.rb +1 -0
  41. data/db/migrate/20170310164315_add_merged_at_on_pull_requests.rb +9 -0
  42. data/db/migrate/20170314145604_add_last_deployed_at_to_stack.rb +9 -0
  43. data/db/migrate/20170320124156_add_locked_to_commits.rb +5 -0
  44. data/lib/shipit/task_commands.rb +1 -0
  45. data/lib/shipit/version.rb +1 -1
  46. data/test/controllers/api/ccmenu_controller_test.rb +6 -0
  47. data/test/controllers/api/stacks_controller_test.rb +6 -0
  48. data/test/controllers/api/tasks_controller_test.rb +31 -0
  49. data/test/controllers/commits_controller_test.rb +18 -0
  50. data/test/dummy/db/development.sqlite3 +0 -0
  51. data/test/dummy/db/schema.rb +4 -1
  52. data/test/dummy/db/test.sqlite3 +0 -0
  53. data/test/dummy/db/test.sqlite3-journal +0 -0
  54. data/test/fixtures/shipit/commits.yml +1 -1
  55. data/test/fixtures/shipit/pull_requests.yml +33 -0
  56. data/test/fixtures/shipit/stacks.yml +4 -1
  57. data/test/jobs/github_sync_job_test.rb +49 -0
  58. data/test/jobs/merge_pull_requests_job_test.rb +18 -0
  59. data/test/models/commits_test.rb +71 -0
  60. data/test/models/deploy_spec_test.rb +15 -0
  61. data/test/models/deploys_test.rb +7 -0
  62. data/test/models/pull_request_test.rb +19 -1
  63. data/test/models/task_definitions_test.rb +9 -0
  64. data/test/models/undeployed_commits_test.rb +2 -7
  65. data/test/unit/deploy_commands_test.rb +7 -0
  66. data/test/unit/variable_definition_test.rb +10 -0
  67. metadata +15 -7
  68. data/app/assets/images/github.svg +0 -9
  69. data/app/assets/images/refresh.svg +0 -8
  70. data/app/assets/images/settings.svg +0 -33
  71. data/lib/snippets/deploy-to-gke +0 -161
@@ -3,12 +3,15 @@
3
3
 
4
4
  @import "_base/_media-queries";
5
5
  @import "_base/_utility";
6
+ @import "_base/_spacing";
6
7
  @import "_base/_colors";
7
8
  @import "_base/_base";
8
9
  @import "_base/_forms";
10
+ @import "_base/_icons";
9
11
  @import "_base/_buttons";
10
12
  @import "_base/_banner";
11
13
  @import "_structure/_layout";
14
+ @import "_structure/_navigation";
12
15
  @import "_structure/_main";
13
16
  @import "_base/status-items";
14
17
  @import "_pages/_commits";
@@ -10,15 +10,12 @@ module Shipit
10
10
 
11
11
  def render_resource(resource, options = {})
12
12
  if resource.destroyed?
13
- options[:status] = :no_content
14
- options[:text] = nil
13
+ head :no_content, options.reverse_merge(content_type: 'application/json')
15
14
  elsif resource.errors.any?
16
- options[:json] = {errors: resource.errors}
17
- options[:status] = :unprocessable_entity
15
+ render options.reverse_merge(status: :unprocessable_entity, json: {errors: resource.errors})
18
16
  else
19
- options[:json] = resource
17
+ render options.reverse_merge(json: resource)
20
18
  end
21
- render options
22
19
  end
23
20
  end
24
21
  end
@@ -11,6 +11,10 @@ module Shipit
11
11
  def ended_at
12
12
  Time.now.utc
13
13
  end
14
+
15
+ def running?
16
+ false
17
+ end
14
18
  end
15
19
 
16
20
  def show
@@ -0,0 +1,18 @@
1
+ module Shipit
2
+ class CommitsController < ShipitController
3
+ def update
4
+ commit.update(params.require(:commit).permit(:locked))
5
+ head :ok
6
+ end
7
+
8
+ private
9
+
10
+ def commit
11
+ @commit ||= stack.commits.find(params[:id])
12
+ end
13
+
14
+ def stack
15
+ @stack ||= Stack.from_param!(params[:stack_id])
16
+ end
17
+ end
18
+ end
@@ -2,7 +2,32 @@ module Shipit
2
2
  class DestroyStackJob < BackgroundJob
3
3
  queue_as :default
4
4
 
5
+ # stack
6
+ # +-- api_clients
7
+ # +-- commits
8
+ # | +-- commit_deployments
9
+ # | | +-- statuses
10
+ # | +-- statuses
11
+ # +-- github_hooks
12
+ # +-- hooks
13
+ # +-- pull_requests
14
+ # +-- tasks
15
+ # +-- chunks
16
+
5
17
  def perform(stack)
18
+ Shipit::ApiClient.where(stack_id: stack.id).delete_all
19
+ commits_ids = Shipit::Commit.where(stack_id: stack.id).pluck(:id)
20
+ commit_deployments_ids = Shipit::CommitDeployment.where(commit_id: commits_ids).pluck(:id)
21
+ Shipit::CommitDeploymentStatus.where(commit_deployment_id: commit_deployments_ids).delete_all
22
+ Shipit::CommitDeployment.where(id: commit_deployments_ids).delete_all
23
+ Shipit::Status.where(commit_id: commits_ids).delete_all
24
+ Shipit::Commit.where(id: commits_ids).delete_all
25
+ Shipit::GithubHook.where(stack_id: stack.id).destroy_all
26
+ Shipit::Hook.where(stack_id: stack.id).delete_all
27
+ Shipit::PullRequest.where(stack_id: stack.id).delete_all
28
+ tasks_ids = Shipit::Task.where(stack_id: stack.id).pluck(:id)
29
+ Shipit::OutputChunk.where(task_id: tasks_ids).delete_all
30
+ Shipit::Task.where(id: tasks_ids).delete_all
6
31
  stack.destroy!
7
32
  end
8
33
  end
@@ -17,13 +17,24 @@ module Shipit
17
17
  @stack.transaction do
18
18
  shared_parent.try!(:detach_children!)
19
19
  new_commits.each do |gh_commit|
20
- @stack.commits.create_from_github!(gh_commit)
20
+ append_commit(gh_commit)
21
21
  end
22
22
  end
23
23
  end
24
24
  CacheDeploySpecJob.perform_later(@stack)
25
25
  end
26
26
 
27
+ def append_commit(gh_commit)
28
+ appended_commit = @stack.commits.create_from_github!(gh_commit)
29
+ if appended_commit.revert?
30
+ impacted_commits = @stack.undeployed_commits.reverse.drop_while { |c| !appended_commit.revert_of?(c) }
31
+ impacted_commits.pop # appended_commit
32
+ impacted_commits.each do |impacted_commit|
33
+ impacted_commit.update!(locked: true)
34
+ end
35
+ end
36
+ end
37
+
27
38
  def fetch_missing_commits(&block)
28
39
  commits = []
29
40
  iterator = Shipit::FirstParentCommitsIterator.new(&block)
@@ -8,6 +8,9 @@ module Shipit
8
8
  pull_requests.each do |pull_request|
9
9
  pull_request.refresh!
10
10
  pull_request.reject_unless_mergeable!
11
+ if pull_request.closed?
12
+ pull_request.merged_upstream? ? pull_request.complete! : pull_request.cancel!
13
+ end
11
14
  end
12
15
 
13
16
  return false unless stack.allows_merges?
@@ -106,7 +106,7 @@ module Shipit
106
106
  delegate :pending?, :success?, :error?, :failure?, :state, to: :status
107
107
 
108
108
  def deployable?
109
- success? || stack.ignore_ci?
109
+ !locked? && (success? || stack.ignore_ci?)
110
110
  end
111
111
 
112
112
  def children
@@ -126,13 +126,25 @@ module Shipit
126
126
  end
127
127
 
128
128
  def title
129
- pull_request_title || message
129
+ pull_request_title || message_header
130
+ end
131
+
132
+ def message_header
133
+ message.lines.first.strip
130
134
  end
131
135
 
132
136
  def pull_request_title # TODO: remove in a few versions when it is assumed the commits table was backfilled
133
137
  super || message_parser.pull_request_title
134
138
  end
135
139
 
140
+ def revert?
141
+ title.start_with?('Revert "') && title.end_with?('"')
142
+ end
143
+
144
+ def revert_of?(commit)
145
+ title == %(Revert "#{commit.title}") || title == %(Revert "#{commit.message_header}")
146
+ end
147
+
136
148
  def short_sha
137
149
  sha[0..9]
138
150
  end
@@ -10,6 +10,7 @@ module Shipit
10
10
  after_transition to: :success, do: :update_undeployed_commits_count
11
11
  after_transition to: :aborted, do: :trigger_revert_if_required
12
12
  after_transition any => any, do: :update_commit_deployments
13
+ after_transition any => any, do: :update_last_deploy_time
13
14
  end
14
15
 
15
16
  has_many :commit_deployments, dependent: :destroy, inverse_of: :task, foreign_key: :task_id do
@@ -180,5 +181,9 @@ module Shipit
180
181
  def update_undeployed_commits_count
181
182
  stack.update_undeployed_commits_count(until_commit)
182
183
  end
184
+
185
+ def update_last_deploy_time
186
+ stack.update(last_deployed_at: ended_at)
187
+ end
183
188
  end
184
189
  end
@@ -108,21 +108,20 @@ module Shipit
108
108
  end
109
109
 
110
110
  def task_definitions
111
- (config('tasks') || {}).map { |name, definition| TaskDefinition.new(name, coerce_task_definition(definition)) }
111
+ discover_task_definitions.merge(config('tasks') || {}).map do |name, definition|
112
+ TaskDefinition.new(name, coerce_task_definition(definition))
113
+ end
112
114
  end
113
115
 
114
116
  def find_task_definition(id)
115
- TaskDefinition.new(id, coerce_task_definition(config('tasks', id)) || task_not_found!(id))
117
+ definition = config('tasks', id) || discover_task_definitions[id]
118
+ TaskDefinition.new(id, coerce_task_definition(definition) || task_not_found!(id))
116
119
  end
117
120
 
118
121
  def filter_deploy_envs(env)
119
122
  EnvironmentVariables.with(env).permit(deploy_variables)
120
123
  end
121
124
 
122
- def filter_task_envs(id, env)
123
- find_task_definition(id).filter_envs(env)
124
- end
125
-
126
125
  def review_checklist
127
126
  (config('review', 'checklist') || discover_review_checklist || []).map(&:strip).select(&:present?)
128
127
  end
@@ -199,6 +198,10 @@ module Shipit
199
198
  def discover_review_checklist
200
199
  end
201
200
 
201
+ def discover_task_definitions
202
+ {}
203
+ end
204
+
202
205
  def discover_dependencies_steps
203
206
  end
204
207
 
@@ -9,6 +9,20 @@ module Shipit
9
9
  discover_kubernetes || super
10
10
  end
11
11
 
12
+ def discover_task_definitions
13
+ if kube_config.present?
14
+ {
15
+ 'restart' => {
16
+ 'action' => "Restart application",
17
+ 'description' => "Simulates a rollout of Kubernetes deployments by using kubernetes-restart utility",
18
+ 'steps' => [kubernetes_restart_cmd],
19
+ },
20
+ }
21
+ else
22
+ super
23
+ end
24
+ end
25
+
12
26
  private
13
27
 
14
28
  def discover_kubernetes
@@ -20,8 +34,8 @@ module Shipit
20
34
  cmd << kube_config['template_dir']
21
35
  end
22
36
 
23
- cmd << kube_config['namespace']
24
- cmd << kube_config['context']
37
+ cmd << kube_config.fetch('namespace')
38
+ cmd << kube_config.fetch('context')
25
39
 
26
40
  [Shellwords.join(cmd)]
27
41
  end
@@ -29,6 +43,14 @@ module Shipit
29
43
  def kube_config
30
44
  @kube_config ||= config('kubernetes') || {}
31
45
  end
46
+
47
+ def kubernetes_restart_cmd
48
+ Shellwords.join([
49
+ "kubernetes-restart",
50
+ kube_config.fetch('namespace'),
51
+ kube_config.fetch('context'),
52
+ ])
53
+ end
32
54
  end
33
55
  end
34
56
  end
@@ -95,6 +95,10 @@ module Shipit
95
95
  before_transition any => :pending do |pr|
96
96
  pr.revalidated_at = Time.now.utc
97
97
  end
98
+
99
+ before_transition %i(pending) => :merged do |pr|
100
+ Stack.increment_counter(:undeployed_commits_count, pr.stack_id)
101
+ end
98
102
  end
99
103
 
100
104
  def self.schedule_merges
@@ -164,7 +168,9 @@ module Shipit
164
168
  merge_method: 'merge',
165
169
  )
166
170
  begin
167
- Shipit.github_api.delete_branch(stack.github_repo_name, branch)
171
+ if Shipit.github_api.pull_requests(stack.github_repo_name, base: branch).empty?
172
+ Shipit.github_api.delete_branch(stack.github_repo_name, branch)
173
+ end
168
174
  rescue Octokit::UnprocessableEntity
169
175
  # branch was already deleted somehow
170
176
  end
@@ -203,6 +209,14 @@ module Shipit
203
209
  RefreshPullRequestJob.perform_later(self)
204
210
  end
205
211
 
212
+ def closed?
213
+ state == "closed"
214
+ end
215
+
216
+ def merged_upstream?
217
+ closed? && merged_at
218
+ end
219
+
206
220
  def refresh!
207
221
  update!(github_pull_request: Shipit.github_api.pull_request(stack.github_repo_name, number))
208
222
  head.refresh_statuses!
@@ -219,6 +233,7 @@ module Shipit
219
233
  self.deletions = github_pull_request.deletions
220
234
  self.branch = github_pull_request.head.ref
221
235
  self.head = find_or_create_commit_from_github_by_sha!(github_pull_request.head.sha, detached: true)
236
+ self.merged_at = github_pull_request.merged_at
222
237
  end
223
238
 
224
239
  def merge_message
@@ -82,13 +82,20 @@ module Shipit
82
82
  end
83
83
 
84
84
  def trigger_task(definition_id, user, env: nil)
85
+ definition = find_task_definition(definition_id)
86
+ env = env.try!(:to_h) || {}
87
+
88
+ definition.variables_with_defaults.each do |variable|
89
+ env[variable.name] ||= variable.default
90
+ end
91
+
85
92
  commit = last_deployed_commit.presence || commits.first
86
93
  task = tasks.create(
87
94
  user_id: user.id,
88
- definition: find_task_definition(definition_id),
95
+ definition: definition,
89
96
  until_commit_id: commit.id,
90
97
  since_commit_id: commit.id,
91
- env: filter_task_envs(definition_id, (env.try!(:to_h) || {})),
98
+ env: definition.filter_envs(env),
92
99
  )
93
100
  task.enqueue
94
101
  task
@@ -372,8 +379,8 @@ module Shipit
372
379
 
373
380
  def update_undeployed_commits_count(after_commit = nil)
374
381
  after_commit ||= last_deployed_commit
375
- undeployed_commits = commits.reachable.newer_than(after_commit).select('count(*) as count')
376
- self.class.where(id: id).update_all("undeployed_commits_count = (#{undeployed_commits.to_sql})")
382
+ undeployed_commits = commits.reachable.newer_than(after_commit).count
383
+ update(undeployed_commits_count: undeployed_commits)
377
384
  end
378
385
 
379
386
  def broadcast_update
@@ -48,6 +48,10 @@ module Shipit
48
48
  EnvironmentVariables.with(env).permit(variables)
49
49
  end
50
50
 
51
+ def variables_with_defaults
52
+ @variables_with_defaults ||= variables.select(&:default_provided?)
53
+ end
54
+
51
55
  private
52
56
 
53
57
  def task_variables(config_variables)
@@ -2,8 +2,8 @@ module Shipit
2
2
  class Team < ActiveRecord::Base
3
3
  REQUIRED_HOOKS = %i(membership).freeze
4
4
 
5
- has_many :members, class_name: :User, through: :memberships, source: :user
6
5
  has_many :memberships
6
+ has_many :members, class_name: :User, through: :memberships, source: :user
7
7
 
8
8
  has_many :github_hooks,
9
9
  -> { where(event: REQUIRED_HOOKS) },
@@ -11,7 +11,7 @@ module Shipit
11
11
  state = deployable? ? 'allowed' : status.state
12
12
  unless bypass_safeties
13
13
  state = 'deploying' if stack.active_task?
14
- state = 'locked' if stack.locked?
14
+ state = 'locked' if locked?
15
15
  end
16
16
  state
17
17
  end
@@ -19,7 +19,6 @@ module Shipit
19
19
  def redeploy_state(bypass_safeties = false)
20
20
  state = 'allowed'
21
21
  unless bypass_safeties
22
- state = 'locked' if stack.locked?
23
22
  state = 'deploying' if stack.active_task?
24
23
  end
25
24
  state
@@ -2,8 +2,8 @@ module Shipit
2
2
  class User < ActiveRecord::Base
3
3
  DEFAULT_AVATAR = URI.parse('https://avatars.githubusercontent.com/u/583231?')
4
4
 
5
- has_many :teams, through: :memberships
6
5
  has_many :memberships
6
+ has_many :teams, through: :memberships
7
7
  has_many :authored_commits, class_name: :Commit, foreign_key: :author_id, inverse_of: :author
8
8
  has_many :commits, foreign_key: :committer_id, inverse_of: :committer
9
9
  has_many :tasks
@@ -6,9 +6,14 @@ module Shipit
6
6
  @name = attributes.fetch('name')
7
7
  @title = attributes['title']
8
8
  @default = attributes['default'].to_s
9
+ @default_provided = attributes.key?('default')
9
10
  @select = attributes['select'].presence
10
11
  end
11
12
 
13
+ def default_provided?
14
+ @default_provided
15
+ end
16
+
12
17
  def to_h
13
18
  {
14
19
  'name' => @name,
@@ -5,7 +5,7 @@ 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
8
+ :updated_at, :locked_since, :last_deployed_at
9
9
 
10
10
  def url
11
11
  api_stack_url(object)
@@ -1,7 +1,8 @@
1
1
  <!DOCTYPE html>
2
- <html data-controller="<%= controller_name %>" data-action="<%= action_name %>">
2
+ <html lang="<%= I18n.locale %>" data-controller="<%= controller_name %>" data-action="<%= action_name %>">
3
3
  <head>
4
4
  <title><%= [Shipit.app_name, @stack.try!(:repo_name)].compact.join(' - ') %></title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
6
  <%= favicon_link_tag %>
6
7
  <%= stylesheet_link_tag :shipit, media: 'all' %>
7
8
  <%= javascript_include_tag :shipit %>