shipit-engine 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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 %>