shipit-engine 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -2
  3. data/app/assets/stylesheets/_pages/_deploy.scss +3 -2
  4. data/app/controllers/shipit/api/base_controller.rb +5 -0
  5. data/app/controllers/shipit/api/deploys_controller.rb +2 -1
  6. data/app/controllers/shipit/api/tasks_controller.rb +4 -1
  7. data/app/controllers/shipit/deploys_controller.rb +1 -1
  8. data/app/controllers/shipit/github_authentication_controller.rb +4 -2
  9. data/app/controllers/shipit/rollbacks_controller.rb +1 -1
  10. data/app/controllers/shipit/tasks_controller.rb +14 -2
  11. data/app/helpers/shipit/github_url_helper.rb +4 -2
  12. data/app/helpers/shipit/stacks_helper.rb +3 -3
  13. data/app/jobs/shipit/continuous_delivery_job.rb +12 -0
  14. data/app/jobs/shipit/create_on_github_job.rb +11 -0
  15. data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -1
  16. data/app/models/shipit/anonymous_user.rb +4 -0
  17. data/app/models/shipit/commit.rb +8 -10
  18. data/app/models/shipit/commit_deployment.rb +56 -0
  19. data/app/models/shipit/commit_deployment_status.rb +57 -0
  20. data/app/models/shipit/deploy.rb +32 -6
  21. data/app/models/shipit/deploy_spec.rb +8 -0
  22. data/app/models/shipit/deploy_spec/rubygems_discovery.rb +1 -1
  23. data/app/models/shipit/rollback.rb +10 -0
  24. data/app/models/shipit/stack.rb +30 -12
  25. data/app/models/shipit/status_group.rb +1 -1
  26. data/app/models/shipit/task.rb +1 -0
  27. data/app/models/shipit/task_definition.rb +20 -1
  28. data/app/models/shipit/user.rb +10 -2
  29. data/app/models/shipit/variable_definition.rb +2 -4
  30. data/app/serializers/shipit/commit_serializer.rb +19 -1
  31. data/app/serializers/shipit/deploy_serializer.rb +7 -1
  32. data/app/serializers/shipit/short_commit_serializer.rb +1 -1
  33. data/app/serializers/shipit/task_serializer.rb +17 -1
  34. data/app/views/shipit/_variables.html.erb +15 -0
  35. data/app/views/shipit/deploys/_concurrent_deploy_warning.html.erb +1 -1
  36. data/app/views/shipit/deploys/_deploy.html.erb +2 -2
  37. data/app/views/shipit/deploys/new.html.erb +3 -17
  38. data/app/views/shipit/deploys/rollback.html.erb +3 -17
  39. data/app/views/shipit/tasks/new.html.erb +12 -4
  40. data/config/locales/en.yml +11 -0
  41. data/db/migrate/20160210183823_add_allow_concurrency_to_tasks.rb +5 -0
  42. data/db/migrate/20160303163611_create_shipit_commit_deployments.rb +14 -0
  43. data/db/migrate/20160303170913_create_shipit_commit_deployment_statuses.rb +12 -0
  44. data/db/migrate/20160303203940_add_encrypted_token_to_users.rb +6 -0
  45. data/lib/shipit.rb +7 -1
  46. data/lib/shipit/engine.rb +3 -1
  47. data/lib/shipit/environment_variables.rb +34 -0
  48. data/lib/shipit/simple_message_verifier.rb +0 -1
  49. data/lib/shipit/task_commands.rb +1 -0
  50. data/lib/shipit/version.rb +1 -1
  51. data/test/controllers/api/deploys_controller_test.rb +15 -0
  52. data/test/controllers/api/tasks_controller_test.rb +15 -0
  53. data/test/controllers/github_authentication_controller_test.rb +23 -5
  54. data/test/controllers/tasks_controller_test.rb +27 -2
  55. data/test/controllers/webhooks_controller_test.rb +8 -2
  56. data/test/dummy/config/database.mysql.yml +1 -1
  57. data/test/dummy/config/secrets.example.yml +2 -2
  58. data/test/dummy/config/secrets.yml +2 -2
  59. data/test/dummy/data/stacks/byroot/junk/production/git/bar.txt +1 -0
  60. data/test/dummy/data/stacks/byroot/junk/production/git/dkfdsf +0 -0
  61. data/test/dummy/data/stacks/byroot/junk/production/git/dskjfsd +0 -0
  62. data/test/dummy/data/stacks/byroot/junk/production/git/dslkjfjsdf +0 -0
  63. data/test/dummy/data/stacks/byroot/junk/production/git/plopfizz +0 -0
  64. data/test/dummy/data/stacks/byroot/junk/production/git/sd +0 -0
  65. data/test/dummy/data/stacks/byroot/junk/production/git/sdkfjsdf +1 -0
  66. data/test/dummy/data/stacks/byroot/junk/production/git/sdlfjsdfdsfj +0 -0
  67. data/test/dummy/data/stacks/byroot/junk/production/git/sdlkfjsdlkfjsdlkfjdsfsdfksdfjsldkfjsdlkfjsdf +0 -0
  68. data/test/dummy/data/stacks/byroot/junk/production/git/shipit.yml +27 -0
  69. data/test/dummy/data/stacks/byroot/junk/production/git/toto.txt +2 -0
  70. data/test/dummy/db/development.sqlite3 +0 -0
  71. data/test/dummy/db/schema.rb +35 -7
  72. data/test/dummy/db/seeds.rb +3 -0
  73. data/test/dummy/db/test.sqlite3 +0 -0
  74. data/test/fixtures/shipit/commit_deployment_statuses.yml +19 -0
  75. data/test/fixtures/shipit/commit_deployments.yml +37 -0
  76. data/test/fixtures/shipit/commits.yml +1 -1
  77. data/test/fixtures/shipit/stacks.yml +12 -0
  78. data/test/fixtures/shipit/tasks.yml +4 -0
  79. data/test/fixtures/shipit/users.yml +2 -0
  80. data/test/jobs/fetch_deployed_revision_job_test.rb +3 -3
  81. data/test/models/commit_deployment_status_test.rb +27 -0
  82. data/test/models/commit_deployment_test.rb +37 -0
  83. data/test/models/commits_test.rb +7 -4
  84. data/test/models/deploys_test.rb +17 -1
  85. data/test/models/stacks_test.rb +13 -13
  86. data/test/models/task_definitions_test.rb +10 -0
  87. data/test/models/team_test.rb +8 -2
  88. data/test/models/users_test.rb +20 -2
  89. data/test/unit/deploy_spec_test.rb +29 -0
  90. data/test/unit/environment_variables_test.rb +36 -0
  91. data/test/unit/github_url_helper_test.rb +0 -8
  92. metadata +76 -20
@@ -8,12 +8,32 @@ module Shipit
8
8
  after_transition to: :success, do: :schedule_continuous_delivery
9
9
  after_transition to: :success, do: :update_undeployed_commits_count
10
10
  after_transition to: :aborted, do: :trigger_revert_if_required
11
+ after_transition any => any, do: :update_commit_deployments
12
+ end
13
+
14
+ has_many :commit_deployments, inverse_of: :task, foreign_key: :task_id do
15
+ GITHUB_STATUSES = {
16
+ 'pending' => 'pending',
17
+ 'failed' => 'failed',
18
+ 'success' => 'success',
19
+ 'error' => 'error',
20
+ 'aborted' => 'error',
21
+ }
22
+
23
+ def append_status(task_status)
24
+ if github_status = GITHUB_STATUSES[task_status]
25
+ each do |deployment|
26
+ deployment.statuses.create!(status: github_status)
27
+ end
28
+ end
29
+ end
11
30
  end
12
31
 
13
32
  before_create :denormalize_commit_stats
33
+ after_create :create_commit_deployments
14
34
  after_commit :broadcast_update
15
35
 
16
- delegate :broadcast_update, to: :stack
36
+ delegate :broadcast_update, :filter_deploy_envs, to: :stack
17
37
 
18
38
  def build_rollback(user = nil, env: nil)
19
39
  Rollback.new(
@@ -117,6 +137,16 @@ module Shipit
117
137
 
118
138
  private
119
139
 
140
+ def create_commit_deployments
141
+ commits.each do |commit|
142
+ commit_deployments.create!(commit: commit)
143
+ end
144
+ end
145
+
146
+ def update_commit_deployments
147
+ commit_deployments.append_status(status)
148
+ end
149
+
120
150
  def trigger_revert_if_required
121
151
  return unless rollback_once_aborted?
122
152
  return unless supports_rollback?
@@ -135,11 +165,7 @@ module Shipit
135
165
 
136
166
  def schedule_continuous_delivery
137
167
  return unless stack.continuous_deployment?
138
-
139
- to_deploy = stack.commits.order(:id).newer_than(until_commit).successful.last
140
- return unless to_deploy
141
-
142
- stack.trigger_deploy(to_deploy, to_deploy.committer)
168
+ ContinuousDeliveryJob.perform_later(stack)
143
169
  end
144
170
 
145
171
  def last_successful_deploy
@@ -90,6 +90,14 @@ module Shipit
90
90
  TaskDefinition.new(id, coerce_task_definition(config('tasks', id)) || task_not_found!(id))
91
91
  end
92
92
 
93
+ def filter_deploy_envs(env)
94
+ EnvironmentVariables.with(env).permit(deploy_variables)
95
+ end
96
+
97
+ def filter_task_envs(id, env)
98
+ find_task_definition(id).filter_envs(env)
99
+ end
100
+
93
101
  def review_checklist
94
102
  (config('review', 'checklist') || discover_review_checklist || []).map(&:strip).select(&:present?)
95
103
  end
@@ -15,7 +15,7 @@ module Shipit
15
15
 
16
16
  def discover_gem_checklist
17
17
  [%(<strong>Don't forget to add a tag before deploying!</strong> You can do this with:
18
- git tag -a -m "Version <strong>x.y.z</strong>" v<strong>x.y.z</strong> && git push --tags)] if gem?
18
+ git tag v<strong>x.y.z</strong> && git push --tags)] if gem?
19
19
  end
20
20
 
21
21
  def gem?
@@ -27,5 +27,15 @@ module Shipit
27
27
  def to_partial_path
28
28
  'deploys/deploy'
29
29
  end
30
+
31
+ private
32
+
33
+ def create_commit_deployments
34
+ # Rollback events are confusing in GitHub
35
+ end
36
+
37
+ def update_commit_deployments
38
+ # Rollback events are confusing in GitHub
39
+ end
30
40
  end
31
41
  end
@@ -55,13 +55,14 @@ module Shipit
55
55
  undeployed_commits_count > 0
56
56
  end
57
57
 
58
- def trigger_task(definition_id, user)
58
+ def trigger_task(definition_id, user, env: nil)
59
59
  commit = last_deployed_commit
60
60
  task = tasks.create(
61
61
  user_id: user.id,
62
62
  definition: find_task_definition(definition_id),
63
63
  until_commit_id: commit.id,
64
64
  since_commit_id: commit.id,
65
+ env: filter_task_envs(definition_id, (env || {})),
65
66
  )
66
67
  task.enqueue
67
68
  task
@@ -74,18 +75,26 @@ module Shipit
74
75
  user_id: user.id,
75
76
  until_commit: until_commit,
76
77
  since_commit: since_commit,
77
- env: env || {},
78
+ env: filter_deploy_envs(env || {}),
78
79
  )
79
80
  deploy.enqueue
80
81
  deploy
81
82
  end
82
83
 
84
+ def trigger_continuous_deploy
85
+ return unless deployable?
86
+ if commit = last_deployable_commit
87
+ return if commit.deployed?
88
+ trigger_deploy(commit, commit.committer)
89
+ end
90
+ end
91
+
83
92
  def async_refresh_deployed_revision
84
93
  FetchDeployedRevisionJob.perform_later(self)
85
94
  end
86
95
 
87
96
  def update_deployed_revision(sha)
88
- return if deploying?
97
+ return if active_task?
89
98
 
90
99
  last_deploy = deploys_and_rollbacks.last
91
100
  actual_deployed_commit = commits.reachable.by_sha!(sha)
@@ -108,7 +117,7 @@ module Shipit
108
117
  end
109
118
 
110
119
  def status
111
- return :deploying if deploying?
120
+ return :deploying if active_task?
112
121
  :default
113
122
  end
114
123
 
@@ -124,6 +133,10 @@ module Shipit
124
133
  end
125
134
  end
126
135
 
136
+ def last_deployable_commit
137
+ commits.order(id: :desc).newer_than(last_deployed_commit).reachable.preload(:statuses).to_a.find(&:deployable?)
138
+ end
139
+
127
140
  def filter_visible_statuses(statuses)
128
141
  statuses.reject { |s| hidden_statuses.include?(s.context) }
129
142
  end
@@ -133,7 +146,7 @@ module Shipit
133
146
  end
134
147
 
135
148
  def deployable?
136
- !locked? && !deploying?
149
+ !locked? && !active_task?
137
150
  end
138
151
 
139
152
  def repo_name=(name)
@@ -190,13 +203,13 @@ module Shipit
190
203
  Shipit.github_api.commits(github_repo_name, sha: branch)
191
204
  end
192
205
 
193
- def deploying?
194
- !!active_deploy
206
+ def active_task?
207
+ !!active_task
195
208
  end
196
209
 
197
- def active_deploy
198
- return @active_deploy if defined?(@active_deploy)
199
- @active_deploy ||= deploys_and_rollbacks.active.last
210
+ def active_task
211
+ return @active_task if defined?(@active_task)
212
+ @active_task ||= tasks.active.exclusive.last
200
213
  end
201
214
 
202
215
  def locked?
@@ -217,7 +230,7 @@ module Shipit
217
230
  end
218
231
 
219
232
  delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,
220
- :deploy_variables, to: :cached_deploy_spec
233
+ :deploy_variables, :filter_task_envs, :filter_deploy_envs, to: :cached_deploy_spec
221
234
 
222
235
  def monitoring?
223
236
  monitoring.present?
@@ -280,10 +293,15 @@ module Shipit
280
293
  inaccessible_since?
281
294
  end
282
295
 
296
+ def reload(*)
297
+ clear_cache
298
+ super
299
+ end
300
+
283
301
  private
284
302
 
285
303
  def clear_cache
286
- remove_instance_variable(:@active_deploy) if defined?(@active_deploy)
304
+ remove_instance_variable(:@active_task) if defined?(@active_task)
287
305
  end
288
306
 
289
307
  def sync_github
@@ -9,7 +9,7 @@ module Shipit
9
9
  @statuses = visible_statuses
10
10
  end
11
11
 
12
- delegate :success?, :state, to: :significant_status
12
+ delegate :pending?, :success?, :error?, :failure?, :state, to: :significant_status
13
13
 
14
14
  def description
15
15
  "#{success_count} / #{statuses.count} checks OK"
@@ -15,6 +15,7 @@ module Shipit
15
15
  scope :success, -> { where(status: 'success') }
16
16
  scope :completed, -> { where(status: %w(success error failed flapping aborted)) }
17
17
  scope :active, -> { where(status: %w(pending running aborting)) }
18
+ scope :exclusive, -> { where(allow_concurrency: false) }
18
19
 
19
20
  scope :due_for_rollup, -> { completed.where(rolled_up: false).where('created_at <= ?', 1.hour.ago) }
20
21
 
@@ -15,14 +15,21 @@ module Shipit
15
15
  end
16
16
  end
17
17
 
18
- attr_reader :id, :action, :description, :steps, :checklist
18
+ attr_reader :id, :action, :description, :steps, :checklist, :variables
19
+ alias_method :to_param, :id
19
20
 
20
21
  def initialize(id, config)
21
22
  @id = id
22
23
  @action = config['action']
23
24
  @description = config['description'] || ''
24
25
  @steps = config['steps'] || []
26
+ @variables = task_variables(config['variables'] || [])
25
27
  @checklist = config['checklist'] || []
28
+ @allow_concurrency = config['allow_concurrency'] || false
29
+ end
30
+
31
+ def allow_concurrency?
32
+ @allow_concurrency
26
33
  end
27
34
 
28
35
  def as_json
@@ -31,8 +38,20 @@ module Shipit
31
38
  action: action,
32
39
  description: description,
33
40
  steps: steps,
41
+ variables: variables.map(&:to_h),
34
42
  checklist: checklist,
43
+ allow_concurrency: allow_concurrency?,
35
44
  }
36
45
  end
46
+
47
+ def filter_envs(env)
48
+ EnvironmentVariables.with(env).permit(variables)
49
+ end
50
+
51
+ private
52
+
53
+ def task_variables(config_variables)
54
+ config_variables.map(&VariableDefinition.method(:new))
55
+ end
37
56
  end
38
57
  end
@@ -8,6 +8,8 @@ module Shipit
8
8
  has_many :commits, foreign_key: :committer_id, inverse_of: :committer
9
9
  has_many :tasks
10
10
 
11
+ attr_encrypted :github_access_token, key: Shipit.user_access_tokens_key
12
+
11
13
  def self.find_or_create_by_login!(login)
12
14
  find_or_create_by!(login: login) do |user|
13
15
  user.github_user = Shipit.github_api.user(login)
@@ -33,6 +35,12 @@ module Shipit
33
35
  end
34
36
  end
35
37
 
38
+ def github_api
39
+ return Shipit.github_api unless github_access_token
40
+
41
+ @github_api ||= Octokit::Client.new(access_token: github_access_token)
42
+ end
43
+
36
44
  def identifiers_for_ping
37
45
  {github_id: github_id, name: name, email: email, github_login: login}
38
46
  end
@@ -60,8 +68,8 @@ module Shipit
60
68
  name: github_user.name || github_user.login, # Name is not mandatory on GitHub
61
69
  email: github_user.email,
62
70
  login: github_user.login,
63
- avatar_url: github_user.rels[:avatar].try(:href),
64
- api_url: github_user.rels[:self].try(:href),
71
+ avatar_url: github_user.avatar_url,
72
+ api_url: github_user.url,
65
73
  )
66
74
  end
67
75
 
@@ -1,19 +1,17 @@
1
1
  module Shipit
2
2
  class VariableDefinition
3
- attr_reader :name, :title, :value, :default
3
+ attr_reader :name, :title, :default
4
4
 
5
5
  def initialize(attributes)
6
6
  @name = attributes.fetch('name')
7
7
  @title = attributes['title']
8
- @value = attributes['value']
9
- @default = attributes['default']
8
+ @default = attributes['default'].to_s
10
9
  end
11
10
 
12
11
  def to_h
13
12
  {
14
13
  'name' => @name,
15
14
  'title' => @title,
16
- 'value' => @value,
17
15
  'default' => @default,
18
16
  }
19
17
  end
@@ -1,8 +1,26 @@
1
1
  module Shipit
2
2
  class CommitSerializer < ShortCommitSerializer
3
+ include GithubUrlHelper
4
+ include ConditionalAttributes
5
+
3
6
  has_one :author
4
7
  has_one :committer
5
8
 
6
- attributes :additions, :deletions, :authored_at, :committed_at
9
+ attributes :additions, :deletions, :authored_at, :committed_at, :html_url, :pull_request
10
+
11
+ def html_url
12
+ github_commit_url(object)
13
+ end
14
+
15
+ def pull_request
16
+ {
17
+ number: object.pull_request_number,
18
+ html_url: github_pull_request_url(object),
19
+ }
20
+ end
21
+
22
+ def include_pull_request?
23
+ object.pull_request?
24
+ end
7
25
  end
8
26
  end
@@ -1,13 +1,19 @@
1
1
  module Shipit
2
2
  class DeploySerializer < TaskSerializer
3
+ include GithubUrlHelper
4
+
3
5
  has_many :commits
4
6
 
5
- attributes :additions, :deletions
7
+ attributes :compare_url, :additions, :deletions
6
8
 
7
9
  def html_url
8
10
  stack_deploy_url(object.stack, object)
9
11
  end
10
12
 
13
+ def compare_url
14
+ github_commit_range_url(object.stack, object.since_commit, object.until_commit)
15
+ end
16
+
11
17
  def type
12
18
  :deploy
13
19
  end
@@ -3,7 +3,7 @@ module Shipit
3
3
  attributes :sha, :message
4
4
 
5
5
  def message
6
- object.pull_request? ? "#{object.pull_request_title} (##{object.pull_request_id})" : object.message
6
+ object.pull_request? ? object.pull_request_title : object.message
7
7
  end
8
8
  end
9
9
  end
@@ -5,7 +5,7 @@ module Shipit
5
5
  has_one :author
6
6
  has_one :revision, serializer: ShortCommitSerializer
7
7
 
8
- attributes :id, :url, :html_url, :output_url, :type, :status, :updated_at, :created_at
8
+ attributes :id, :url, :html_url, :output_url, :type, :status, :action, :description, :updated_at, :created_at
9
9
 
10
10
  def revision
11
11
  object.until_commit
@@ -26,5 +26,21 @@ module Shipit
26
26
  def type
27
27
  :task
28
28
  end
29
+
30
+ def action
31
+ object.definition.try!(:action)
32
+ end
33
+
34
+ def include_action?
35
+ type == :task
36
+ end
37
+
38
+ def description
39
+ object.definition.try!(:action)
40
+ end
41
+
42
+ def include_description?
43
+ type == :task
44
+ end
29
45
  end
30
46
  end
@@ -0,0 +1,15 @@
1
+ <% unless variables.empty? %>
2
+ <section>
3
+ <header class="section-header variables-header">
4
+ <h2><%= header %></h2>
5
+ </header>
6
+ <%= form.fields_for field_name do |field| %>
7
+ <% variables.each do |variable| %>
8
+ <p class="variables-fields">
9
+ <%= field.text_field variable.name, value: variable.default %>
10
+ <%= field.label variable.name, variable.title || variable.name %>
11
+ </p>
12
+ <% end %>
13
+ <% end %>
14
+ </section>
15
+ <% end %>