shipit-engine 0.8.9 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/shipit_bs.js.coffee +2 -0
  3. data/app/assets/javascripts/task.js.coffee +14 -5
  4. data/app/assets/javascripts/task/search_bar.js.coffee +52 -0
  5. data/app/assets/javascripts/task/stream.js.coffee +9 -2
  6. data/app/assets/javascripts/task/tty.js.coffee +75 -28
  7. data/app/assets/stylesheets/_base/_forms.scss +5 -0
  8. data/app/assets/stylesheets/_pages/_commits.scss +1 -1
  9. data/app/assets/stylesheets/_pages/_deploy.scss +56 -24
  10. data/app/assets/stylesheets/_pages/_settings.scss +5 -0
  11. data/app/assets/stylesheets/_structure/_layout.scss +1 -0
  12. data/app/assets/stylesheets/_structure/_main.scss +4 -0
  13. data/app/assets/stylesheets/shipit.scss +2 -0
  14. data/app/assets/stylesheets/shipit_bs.scss +22 -0
  15. data/app/controllers/shipit/deploys_controller.rb +5 -1
  16. data/app/controllers/shipit/shipit_controller.rb +10 -3
  17. data/app/controllers/shipit/stacks_controller.rb +12 -3
  18. data/app/controllers/shipit/tasks_controller.rb +4 -0
  19. data/app/helpers/shipit/shipit_helper.rb +18 -0
  20. data/app/helpers/shipit/stacks_helper.rb +1 -1
  21. data/app/jobs/shipit/cache_deploy_spec_job.rb +2 -0
  22. data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -0
  23. data/app/jobs/shipit/git_mirror_update_job.rb +2 -0
  24. data/app/jobs/shipit/perform_task_job.rb +1 -0
  25. data/app/models/shipit/commit.rb +2 -2
  26. data/app/models/shipit/deploy.rb +1 -1
  27. data/app/models/shipit/deploy_spec/bundler_discovery.rb +1 -1
  28. data/app/models/shipit/duration.rb +28 -0
  29. data/app/models/shipit/stack.rb +33 -11
  30. data/app/models/shipit/task.rb +26 -3
  31. data/app/serializers/shipit/task_serializer.rb +14 -1
  32. data/app/views/bootstrap/shipit/missing_settings.html.erb +97 -0
  33. data/app/views/bootstrap/shipit/stacks/new.html.erb +44 -0
  34. data/app/views/layouts/shipit.html.erb +1 -1
  35. data/app/views/layouts/shipit_bootstrap.html.erb +44 -0
  36. data/app/views/shipit/commits/_commit.html.erb +3 -2
  37. data/app/views/shipit/deploys/_deploy.html.erb +11 -2
  38. data/app/views/shipit/deploys/show.html.erb +1 -1
  39. data/app/views/shipit/stacks/new.html.erb +12 -12
  40. data/app/views/shipit/stacks/settings.html.erb +5 -0
  41. data/app/views/shipit/stacks/show.html.erb +1 -1
  42. data/app/views/shipit/tasks/_task.html.erb +10 -2
  43. data/app/views/shipit/tasks/_task_output.html.erb +11 -1
  44. data/app/views/shipit/tasks/show.html.erb +1 -1
  45. data/config/routes.rb +1 -0
  46. data/db/migrate/20160324155046_add_started_at_and_ended_at_on_tasks.rb +25 -0
  47. data/lib/shipit.rb +13 -0
  48. data/lib/shipit/command.rb +13 -9
  49. data/lib/shipit/engine.rb +8 -0
  50. data/lib/shipit/template_renderer_extension.rb +16 -0
  51. data/lib/shipit/version.rb +1 -1
  52. data/test/controllers/deploys_controller_test.rb +11 -0
  53. data/test/controllers/stacks_controller_test.rb +5 -0
  54. data/test/controllers/tasks_controller_test.rb +6 -0
  55. data/test/dummy/config/secrets.example.yml +4 -0
  56. data/test/dummy/config/secrets.yml +2 -0
  57. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/bar.txt +2 -0
  58. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/dkfdsf +0 -0
  59. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/dskjfsd +0 -0
  60. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/dslkjfjsdf +0 -0
  61. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/plopfizz +0 -0
  62. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sd +0 -0
  63. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sdkfjsdf +1 -0
  64. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sdlfjsdfdsfj +0 -0
  65. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/sdlkfjsdlkfjsdlkfjdsfsdfksdfjsldkfjsdlkfjsdf +0 -0
  66. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/shipit.yml +32 -0
  67. data/test/dummy/data/stacks/byroot/junk/production/deploys/83/toto.txt +2 -0
  68. data/test/dummy/data/stacks/byroot/junk/production/git/bar.txt +1 -0
  69. data/test/dummy/data/stacks/byroot/junk/production/git/shipit.yml +6 -1
  70. data/test/dummy/data/stacks/byroot/test/production/git/README.md +1 -0
  71. data/test/dummy/db/development.sqlite3 +0 -0
  72. data/test/dummy/db/schema.rb +3 -1
  73. data/test/dummy/db/seeds.rb +6 -0
  74. data/test/dummy/db/test.sqlite3 +0 -0
  75. data/test/dummy/db/test.sqlite3-journal +0 -0
  76. data/test/fixtures/shipit/tasks.yml +11 -0
  77. data/test/models/commits_test.rb +1 -1
  78. data/test/models/deploys_test.rb +40 -0
  79. data/test/models/duration_test.rb +13 -0
  80. data/test/models/stacks_test.rb +3 -4
  81. data/test/unit/command_test.rb +14 -0
  82. data/vendor/assets/javascripts/clusterize.js +327 -0
  83. data/vendor/assets/javascripts/mousetrap-global-bind.js +43 -0
  84. data/vendor/assets/javascripts/mousetrap.js +1021 -0
  85. data/vendor/assets/javascripts/string_includes.js +14 -0
  86. data/vendor/assets/stylesheets/clusterize.css +27 -0
  87. metadata +100 -3
  88. data/app/assets/javascripts/task/sticky_element.js.coffee +0 -16
@@ -1,4 +1,6 @@
1
1
  //= require 'ansi_stream'
2
+ //= require 'clusterize'
3
+
2
4
  @import "_base/_media-queries";
3
5
  @import "_base/_utility";
4
6
  @import "_base/_colors";
@@ -0,0 +1,22 @@
1
+ // Custom bootstrap variables must be set or import before bootstrap itself.
2
+ @import "bootstrap";
3
+
4
+ body {
5
+ padding-top: 5rem;
6
+ }
7
+
8
+ .navbar-brand {
9
+ background: asset-data-url('anchor.svg') center center no-repeat;
10
+ display: block;
11
+ width: 40px;
12
+ height: 40px;
13
+ transition: transform .3s ease-in-out;
14
+ text-indent: -9999px;
15
+ top: 50%;
16
+ right: 100%;
17
+ margin-right: 1em;
18
+
19
+ &:hover {
20
+ transform: rotate(-25deg);
21
+ }
22
+ }
@@ -9,10 +9,14 @@ module Shipit
9
9
  def new
10
10
  @commit = @stack.commits.by_sha!(params[:sha])
11
11
  @commit.checks.schedule if @stack.checks?
12
- @deploy = @stack.deploys.new(until_commit: @commit, since_commit: @stack.last_deployed_commit)
12
+ @deploy = @stack.build_deploy(@commit, current_user)
13
13
  end
14
14
 
15
15
  def show
16
+ respond_to do |format|
17
+ format.html
18
+ format.text { render plain: @deploy.chunk_output }
19
+ end
16
20
  end
17
21
 
18
22
  def create
@@ -11,9 +11,12 @@ module Shipit
11
11
  helper Shipit::Engine.routes.url_helpers
12
12
  include Shipit::Engine.routes.url_helpers
13
13
 
14
- before_action :ensure_required_settings,
15
- :force_github_authentication,
16
- :set_variant
14
+ before_action(
15
+ :toogle_bootstrap_feature,
16
+ :ensure_required_settings,
17
+ :force_github_authentication,
18
+ :set_variant,
19
+ )
17
20
 
18
21
  # Respond to HTML by default
19
22
  respond_to :html
@@ -24,6 +27,10 @@ module Shipit
24
27
 
25
28
  private
26
29
 
30
+ def toogle_bootstrap_feature
31
+ prepend_view_path(Shipit.bootstrap_view_path) if Shipit.feature_bootstrap?
32
+ end
33
+
27
34
  def ensure_required_settings
28
35
  return if Shipit.all_settings_present?
29
36
 
@@ -24,8 +24,16 @@ module Shipit
24
24
  @commits = @commits.to_a
25
25
  end
26
26
 
27
+ def lookup
28
+ @stack = Stack.find(params[:id])
29
+ redirect_to stack_url(@stack)
30
+ end
31
+
27
32
  def create
28
- @stack = Stack.create(create_params)
33
+ @stack = Stack.new(create_params)
34
+ unless @stack.save
35
+ flash[:warning] = @stack.errors.full_messages.to_sentence
36
+ end
29
37
  respond_with(@stack)
30
38
  end
31
39
 
@@ -41,7 +49,7 @@ module Shipit
41
49
  RefreshStatusesJob.perform_later(stack_id: @stack.id)
42
50
  GithubSyncJob.perform_later(stack_id: @stack.id)
43
51
  flash[:success] = 'Refresh scheduled'
44
- redirect_to :back
52
+ redirect_to request.referer ? :back : stack_path(@stack)
45
53
  end
46
54
 
47
55
  def update
@@ -73,7 +81,8 @@ module Shipit
73
81
  end
74
82
 
75
83
  def update_params
76
- params.require(:stack).permit(:deploy_url, :lock_reason, :continuous_deployment, :ignore_ci).tap do |params|
84
+ params.require(:stack).permit(:deploy_url, :lock_reason, :environment,
85
+ :continuous_deployment, :ignore_ci).tap do |params|
77
86
  params[:lock_author_id] = params[:lock_reason].present? ? current_user.id : nil
78
87
  end
79
88
  end
@@ -19,6 +19,10 @@ module Shipit
19
19
 
20
20
  def show
21
21
  task
22
+ respond_to do |format|
23
+ format.html
24
+ format.text { render plain: @task.chunk_output }
25
+ end
22
26
  end
23
27
 
24
28
  def create
@@ -1,5 +1,23 @@
1
1
  module Shipit
2
2
  module ShipitHelper
3
+ def emojify(content)
4
+ h(content).to_str.gsub(/:([\w+-]+):/) do |match|
5
+ if emoji = Emoji.find_by_alias($1)
6
+ %(
7
+ <img
8
+ alt="##{$1}"
9
+ src="#{image_path("emoji/#{emoji.image_filename}")}"
10
+ style="vertical-align:middle"
11
+ width="20"
12
+ height="20"
13
+ />
14
+ )
15
+ else
16
+ match
17
+ end
18
+ end.html_safe if content.present?
19
+ end
20
+
3
21
  def include_plugins(stack)
4
22
  stack.plugins.flat_map do |plugin, config|
5
23
  plugin_tags(plugin, config)
@@ -37,7 +37,7 @@ module Shipit
37
37
 
38
38
  def render_commit_message(commit)
39
39
  message = commit.pull_request_title || commit.message
40
- content_tag(:span, message.truncate(COMMIT_TITLE_LENGTH), class: 'event-message')
40
+ content_tag(:span, emojify(message.truncate(COMMIT_TITLE_LENGTH)), class: 'event-message')
41
41
  end
42
42
 
43
43
  def render_commit_message_with_link(commit)
@@ -3,6 +3,8 @@ module Shipit
3
3
  include BackgroundJob::Unique
4
4
 
5
5
  def perform(stack)
6
+ return if stack.inaccessible?
7
+
6
8
  commands = Commands.for(stack)
7
9
  commands.with_temporary_working_directory(commit: stack.commits.last) do |path|
8
10
  stack.update!(cached_deploy_spec: DeploySpec::FileSystem.new(path, stack.environment))
@@ -4,6 +4,7 @@ module Shipit
4
4
 
5
5
  def perform(stack)
6
6
  return if stack.active_task?
7
+ return if stack.inaccessible?
7
8
 
8
9
  commands = StackCommands.new(stack)
9
10
 
@@ -3,6 +3,8 @@ module Shipit
3
3
  queue_as :default
4
4
 
5
5
  def perform(stack)
6
+ return if stack.inaccessible?
7
+
6
8
  commands = StackCommands.new(stack)
7
9
  stack.acquire_git_cache_lock do
8
10
  commands.fetch.run
@@ -20,6 +20,7 @@ module Shipit
20
20
  perform_task
21
21
  @task.complete!
22
22
  rescue Command::Error => error
23
+ @task.write("\n#{error.message}\n")
23
24
  @task.report_failure!(error)
24
25
  rescue StandardError => error
25
26
  @task.report_error!(error)
@@ -23,12 +23,12 @@ module Shipit
23
23
 
24
24
  def self.newer_than(commit)
25
25
  return all unless commit
26
- where('id > ?', commit.is_a?(Commit) ? commit.id : commit)
26
+ where('id > ?', commit.try(:id) || commit)
27
27
  end
28
28
 
29
29
  def self.until(commit)
30
30
  return all unless commit
31
- where('id <= ?', commit.is_a?(Commit) ? commit.id : commit)
31
+ where('id <= ?', commit.try(:id) || commit)
32
32
  end
33
33
 
34
34
  def self.successful
@@ -90,7 +90,7 @@ module Shipit
90
90
  end
91
91
 
92
92
  def currently_deployed?
93
- until_commit_id == stack.last_deployed_commit.try!(:id)
93
+ until_commit_id == stack.last_deployed_commit.id
94
94
  end
95
95
 
96
96
  def commits
@@ -26,7 +26,7 @@ module Shipit
26
26
  # Heroku apps often specify a ruby version.
27
27
  if /darwin/ =~ RUBY_PLATFORM
28
28
  # OSX is nitpicky about the -i.
29
- %q(sed -i '' '/^ruby\s/d' Gemfile)
29
+ %q(/usr/bin/sed -i '' '/^ruby\s/d' Gemfile)
30
30
  else
31
31
  %q(sed -i '/^ruby\s/d' Gemfile)
32
32
  end
@@ -0,0 +1,28 @@
1
+ module Shipit
2
+ class Duration
3
+ UNITS = {
4
+ 's' => :seconds,
5
+ 'm' => :minutes,
6
+ 'h' => :hours,
7
+ 'd' => :days,
8
+ }.freeze
9
+
10
+ def initialize(seconds)
11
+ @seconds = seconds
12
+ end
13
+
14
+ def to_i
15
+ @seconds.to_i
16
+ end
17
+
18
+ def to_s
19
+ seconds = to_i
20
+ days, seconds = seconds.divmod(1.day.to_i)
21
+ if days > 0
22
+ "#{days}d#{Time.at(seconds).utc.strftime('%Hh%Mm%Ss')}"
23
+ else
24
+ Time.at(seconds).utc.strftime('%Hh%Mm%Ss')[/[^0a-z]\w+/] || '0s'
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,22 @@ require 'fileutils'
2
2
 
3
3
  module Shipit
4
4
  class Stack < ActiveRecord::Base
5
+ module NoDeployedCommit
6
+ extend self
7
+
8
+ def id
9
+ -1
10
+ end
11
+
12
+ def sha
13
+ ''
14
+ end
15
+
16
+ def blank?
17
+ true
18
+ end
19
+ end
20
+
5
21
  REPO_OWNER_MAX_SIZE = 39
6
22
  REPO_NAME_MAX_SIZE = 100
7
23
  ENVIRONMENT_MAX_SIZE = 50
@@ -36,7 +52,8 @@ module Shipit
36
52
  after_commit :setup_hooks, :sync_github, on: :create
37
53
  after_touch :clear_cache
38
54
 
39
- validates :repo_name, uniqueness: {scope: %i(repo_owner environment)}
55
+ validates :repo_name, uniqueness: {scope: %i(repo_owner environment),
56
+ message: 'cannot be used more than once with this environment'}
40
57
  validates :repo_owner, :repo_name, :environment, presence: true, ascii_only: true
41
58
  validates :repo_owner, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: REPO_OWNER_MAX_SIZE}
42
59
  validates :repo_name, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: REPO_NAME_MAX_SIZE}
@@ -57,7 +74,7 @@ module Shipit
57
74
  end
58
75
 
59
76
  def trigger_task(definition_id, user, env: nil)
60
- commit = last_deployed_commit
77
+ commit = last_deployed_commit.presence || commits.first
61
78
  task = tasks.create(
62
79
  user_id: user.id,
63
80
  definition: find_task_definition(definition_id),
@@ -69,15 +86,19 @@ module Shipit
69
86
  task
70
87
  end
71
88
 
72
- def trigger_deploy(until_commit, user, env: nil)
73
- since_commit = last_deployed_commit
74
-
75
- deploy = deploys.create(
89
+ def build_deploy(until_commit, user, env: nil)
90
+ since_commit = last_deployed_commit.presence || commits.first
91
+ deploys.build(
76
92
  user_id: user.id,
77
93
  until_commit: until_commit,
78
94
  since_commit: since_commit,
79
95
  env: filter_deploy_envs(env || {}),
80
96
  )
97
+ end
98
+
99
+ def trigger_deploy(*args)
100
+ deploy = build_deploy(*args)
101
+ deploy.save!
81
102
  deploy.enqueue
82
103
  deploy
83
104
  end
@@ -95,10 +116,11 @@ module Shipit
95
116
  end
96
117
 
97
118
  def update_deployed_revision(sha)
98
- return if active_task?
99
-
100
119
  last_deploy = deploys_and_rollbacks.last
101
- actual_deployed_commit = commits.reachable.by_sha!(sha)
120
+ return if last_deploy.try!(:active?)
121
+
122
+ actual_deployed_commit = commits.reachable.by_sha(sha)
123
+ return unless actual_deployed_commit
102
124
 
103
125
  if last_deploy && actual_deployed_commit == last_deploy.until_commit
104
126
  last_deploy.accept!
@@ -107,7 +129,7 @@ module Shipit
107
129
  else
108
130
  deploys.create!(
109
131
  until_commit: actual_deployed_commit,
110
- since_commit: last_deployed_commit,
132
+ since_commit: last_deployed_commit.presence || commits.first,
111
133
  status: 'success',
112
134
  )
113
135
  end
@@ -144,7 +166,7 @@ module Shipit
144
166
  if deploy = last_successful_deploy
145
167
  deploy.until_commit
146
168
  else
147
- commits.first
169
+ NoDeployedCommit
148
170
  end
149
171
  end
150
172
 
@@ -1,5 +1,8 @@
1
1
  module Shipit
2
2
  class Task < ActiveRecord::Base
3
+ ACTIVE_STATUSES = %w(pending running aborting).freeze
4
+ COMPLETED_STATUSES = %w(success error failed flapping aborted).freeze
5
+
3
6
  belongs_to :deploy, foreign_key: :parent_id, required: false # required for fixtures
4
7
 
5
8
  belongs_to :user
@@ -13,8 +16,8 @@ module Shipit
13
16
  serialize :env, Hash
14
17
 
15
18
  scope :success, -> { where(status: 'success') }
16
- scope :completed, -> { where(status: %w(success error failed flapping aborted)) }
17
- scope :active, -> { where(status: %w(pending running aborting)) }
19
+ scope :completed, -> { where(status: COMPLETED_STATUSES) }
20
+ scope :active, -> { where(status: ACTIVE_STATUSES) }
18
21
  scope :exclusive, -> { where(allow_concurrency: false) }
19
22
 
20
23
  scope :due_for_rollup, -> { completed.where(rolled_up: false).where('created_at <= ?', 1.hour.ago) }
@@ -23,6 +26,14 @@ module Shipit
23
26
  after_commit :emit_hooks
24
27
 
25
28
  state_machine :status, initial: :pending do
29
+ before_transition any => :running do |task|
30
+ task.started_at ||= Time.now.utc
31
+ end
32
+
33
+ before_transition any => %i(success failed error) do |task|
34
+ task.ended_at ||= Time.now.utc
35
+ end
36
+
26
37
  after_transition any => %i(success failed error) do |task|
27
38
  task.async_refresh_deployed_revision
28
39
  end
@@ -69,6 +80,10 @@ module Shipit
69
80
  state :flapping
70
81
  end
71
82
 
83
+ def active?
84
+ status.in?(ACTIVE_STATUSES)
85
+ end
86
+
72
87
  def report_failure!(_error)
73
88
  reload
74
89
  if aborting?
@@ -79,7 +94,7 @@ module Shipit
79
94
  end
80
95
 
81
96
  def report_error!(error)
82
- write("#{error.class}: #{error.message}\n\t#{error.backtrace.join("\t")}\n")
97
+ write("#{error.class}: #{error.message}\n\t#{error.backtrace.join("\n\t")}\n")
83
98
  error!
84
99
  end
85
100
 
@@ -87,6 +102,14 @@ module Shipit
87
102
 
88
103
  delegate :checklist, to: :definition
89
104
 
105
+ def duration?
106
+ started_at? && ended_at?
107
+ end
108
+
109
+ def duration
110
+ Duration.new(ended_at - started_at) if duration?
111
+ end
112
+
90
113
  def spec
91
114
  @spec ||= DeploySpec::FileSystem.new(working_directory, stack.environment)
92
115
  end
@@ -5,7 +5,20 @@ 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, :action, :description, :updated_at, :created_at
8
+ attributes(*%i(
9
+ id
10
+ url
11
+ html_url
12
+ output_url
13
+ type
14
+ status
15
+ action
16
+ description
17
+ started_at
18
+ ended_at
19
+ updated_at
20
+ created_at
21
+ ))
9
22
 
10
23
  def revision
11
24
  object.until_commit