shipit-engine 0.7.0 → 0.8.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 (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 %>