ship_it 0.0.1

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 (143) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/Gemfile +16 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +46 -0
  6. data/Rakefile +6 -0
  7. data/app/assets/images/.keep +0 -0
  8. data/app/assets/javascripts/application.js +28 -0
  9. data/app/assets/javascripts/polling.coffee +44 -0
  10. data/app/assets/stylesheets/application.css +24 -0
  11. data/app/controllers/application_controller.rb +34 -0
  12. data/app/controllers/branches_controller.rb +2 -0
  13. data/app/controllers/commits_controller.rb +2 -0
  14. data/app/controllers/concerns/.keep +0 -0
  15. data/app/controllers/deploy_options_controller.rb +38 -0
  16. data/app/controllers/deployments_controller.rb +70 -0
  17. data/app/controllers/environments_controller.rb +23 -0
  18. data/app/controllers/projects_controller.rb +51 -0
  19. data/app/controllers/sessions_controller.rb +31 -0
  20. data/app/controllers/users_controller.rb +2 -0
  21. data/app/helpers/application_helper.rb +70 -0
  22. data/app/mailers/.keep +0 -0
  23. data/app/models/.keep +0 -0
  24. data/app/models/branch.rb +15 -0
  25. data/app/models/commit.rb +8 -0
  26. data/app/models/concerns/.keep +0 -0
  27. data/app/models/deploy_option.rb +11 -0
  28. data/app/models/deployment.rb +137 -0
  29. data/app/models/environment.rb +29 -0
  30. data/app/models/project.rb +79 -0
  31. data/app/models/user.rb +18 -0
  32. data/app/views/deploy_options/new.html.slim +15 -0
  33. data/app/views/deployments/_current.html.slim +15 -0
  34. data/app/views/deployments/_deployment.html.slim +15 -0
  35. data/app/views/deployments/_details.html.slim +26 -0
  36. data/app/views/deployments/_progress.html.slim +2 -0
  37. data/app/views/deployments/in_progress.js.erb +1 -0
  38. data/app/views/deployments/show.html.slim +38 -0
  39. data/app/views/deployments/show.js.erb +1 -0
  40. data/app/views/environments/_changes.html.slim +24 -0
  41. data/app/views/environments/changes.js.erb +1 -0
  42. data/app/views/environments/show.html.slim +36 -0
  43. data/app/views/layouts/application.html.slim +30 -0
  44. data/app/views/layouts/homepage.html.slim +10 -0
  45. data/app/views/projects/edit.html.slim +27 -0
  46. data/app/views/projects/index.html.slim +22 -0
  47. data/app/views/projects/new.html.slim +11 -0
  48. data/app/views/projects/show.html.slim +29 -0
  49. data/app/views/sessions/homepage.html.slim +6 -0
  50. data/app/views/sessions/new.html.slim +7 -0
  51. data/bin/bundle +3 -0
  52. data/bin/rails +4 -0
  53. data/bin/rake +4 -0
  54. data/bin/ship_it +44 -0
  55. data/config.ru +4 -0
  56. data/config/application.rb +23 -0
  57. data/config/boot.rb +4 -0
  58. data/config/environment.rb +5 -0
  59. data/config/environments/development.rb +29 -0
  60. data/config/environments/production.rb +80 -0
  61. data/config/environments/test.rb +36 -0
  62. data/config/initializers/backtrace_silencers.rb +7 -0
  63. data/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/config/initializers/inflections.rb +16 -0
  65. data/config/initializers/mime_types.rb +5 -0
  66. data/config/initializers/omniauth_providers_patch.rb +16 -0
  67. data/config/initializers/secret_token.rb +12 -0
  68. data/config/initializers/session_store.rb +3 -0
  69. data/config/initializers/wrap_parameters.rb +14 -0
  70. data/config/locales/en.yml +23 -0
  71. data/config/routes.rb +28 -0
  72. data/db/migrate/20130828221203_create_projects.rb +9 -0
  73. data/db/migrate/20130828232058_create_deployments.rb +16 -0
  74. data/db/migrate/20130828234740_create_users.rb +9 -0
  75. data/db/migrate/20130828234748_create_deploy_options.rb +11 -0
  76. data/db/migrate/20130828234808_create_branches.rb +9 -0
  77. data/db/migrate/20130828234814_create_commits.rb +10 -0
  78. data/db/migrate/20130828234822_create_environments.rb +9 -0
  79. data/db/schema.rb +80 -0
  80. data/db/seeds.rb +7 -0
  81. data/lib/assets/.keep +0 -0
  82. data/lib/ship_it.rb +9 -0
  83. data/lib/ship_it/rails_app.rb +12 -0
  84. data/lib/ship_it/version.rb +3 -0
  85. data/lib/tasks/.keep +0 -0
  86. data/lib/tasks/ship_it.rake +76 -0
  87. data/log/.keep +0 -0
  88. data/public/404.html +58 -0
  89. data/public/422.html +58 -0
  90. data/public/500.html +57 -0
  91. data/public/favicon.ico +0 -0
  92. data/public/robots.txt +5 -0
  93. data/ship_it.gemspec +38 -0
  94. data/templates/project/.gitignore +6 -0
  95. data/templates/project/Gemfile +6 -0
  96. data/templates/project/config.ru +8 -0
  97. data/templates/project/config/database.yml +23 -0
  98. data/templates/project/db/.gitkeep +0 -0
  99. data/templates/project/db/schema.rb +75 -0
  100. data/templates/project/lib/ship_it/railtie.rb +20 -0
  101. data/templates/project/log/.gitkeep +0 -0
  102. data/test/controllers/.keep +0 -0
  103. data/test/controllers/branches_controller_test.rb +7 -0
  104. data/test/controllers/commits_controller_test.rb +7 -0
  105. data/test/controllers/configurations_controller_test.rb +7 -0
  106. data/test/controllers/deploy_options_controller_test.rb +7 -0
  107. data/test/controllers/deployments_controller_test.rb +7 -0
  108. data/test/controllers/environments_controller_test.rb +7 -0
  109. data/test/controllers/projects_controller_test.rb +7 -0
  110. data/test/controllers/users_controller_test.rb +7 -0
  111. data/test/fixtures/.keep +0 -0
  112. data/test/fixtures/branches.yml +11 -0
  113. data/test/fixtures/commits.yml +11 -0
  114. data/test/fixtures/configurations.yml +11 -0
  115. data/test/fixtures/deploy_options.yml +11 -0
  116. data/test/fixtures/deployments.yml +11 -0
  117. data/test/fixtures/environments.yml +11 -0
  118. data/test/fixtures/projects.yml +11 -0
  119. data/test/fixtures/users.yml +11 -0
  120. data/test/helpers/.keep +0 -0
  121. data/test/helpers/branches_helper_test.rb +4 -0
  122. data/test/helpers/commits_helper_test.rb +4 -0
  123. data/test/helpers/configurations_helper_test.rb +4 -0
  124. data/test/helpers/deploy_options_helper_test.rb +4 -0
  125. data/test/helpers/deployments_helper_test.rb +4 -0
  126. data/test/helpers/environments_helper_test.rb +4 -0
  127. data/test/helpers/projects_helper_test.rb +4 -0
  128. data/test/helpers/users_helper_test.rb +4 -0
  129. data/test/integration/.keep +0 -0
  130. data/test/mailers/.keep +0 -0
  131. data/test/models/.keep +0 -0
  132. data/test/models/branch_test.rb +7 -0
  133. data/test/models/commit_test.rb +7 -0
  134. data/test/models/configuration_test.rb +7 -0
  135. data/test/models/deploy_option_test.rb +7 -0
  136. data/test/models/deployment_test.rb +7 -0
  137. data/test/models/environment_test.rb +7 -0
  138. data/test/models/project_test.rb +7 -0
  139. data/test/models/user_test.rb +7 -0
  140. data/test/test_helper.rb +15 -0
  141. data/vendor/assets/javascripts/.keep +0 -0
  142. data/vendor/assets/stylesheets/.keep +0 -0
  143. metadata +431 -0
@@ -0,0 +1,31 @@
1
+ class SessionsController < ApplicationController
2
+
3
+ layout 'homepage'
4
+ skip_before_filter :authorize
5
+
6
+ def homepage
7
+ redirect_to projects_path if authorized?
8
+ end
9
+
10
+ def new
11
+ end
12
+
13
+ def create
14
+ @user = User.find_or_create_from_auth_hash(auth_hash)
15
+ session[:user_id] = @user.id
16
+ session[:user_name] = @user.name
17
+ redirect_to projects_path
18
+ end
19
+
20
+ def destroy
21
+ reset_session
22
+ redirect_to root_path
23
+ end
24
+
25
+ protected
26
+
27
+ def auth_hash
28
+ request.env['omniauth.auth']
29
+ end
30
+
31
+ end
@@ -0,0 +1,2 @@
1
+ class UsersController < ApplicationController
2
+ end
@@ -0,0 +1,70 @@
1
+ module ApplicationHelper
2
+
3
+ def recent_failures
4
+ @recent_failures ||= Environment.includes(:last_deployment).map(&:last_deployment).compact.select(&:failed?)
5
+ end
6
+
7
+ def in_progress
8
+ @in_progress ||= Deployment.in_progress.all
9
+ end
10
+
11
+ def last_successful_duration(deployment)
12
+ deployment.environment.last_successful_deployment &&
13
+ deployment.environment.last_successful_deployment.duration
14
+ end
15
+
16
+ def progress_classes(deployment)
17
+ lsd = last_successful_duration(deployment)
18
+ ["progress", "progress-striped"].tap do |classes|
19
+ if !lsd
20
+ classes << "progress-warning"
21
+ elsif !deployment.started?
22
+ elsif deployment.duration > lsd
23
+ classes << "progress-danger"
24
+ else
25
+ classes << "progress-success"
26
+ end
27
+ classes << "active" unless deployment.completed?
28
+ end
29
+ end
30
+
31
+ def deployment_percent(deployment)
32
+ lsd = last_successful_duration(deployment)
33
+ if !lsd
34
+ return 100
35
+ elsif !deployment.started?
36
+ return 0
37
+ elsif deployment.duration > lsd
38
+ return 100
39
+ else
40
+ return deployment.duration * 100 / lsd
41
+ end
42
+ end
43
+
44
+ def time_remaining(deployment)
45
+ if last_successful_deployment = deployment.environment.last_successful_deployment
46
+ time_remaining = last_successful_deployment.duration - deployment.duration
47
+ if time_remaining < 0
48
+ "unknown"
49
+ elsif time_remaining < 60
50
+ "< 1 minute"
51
+ else
52
+ pluralize((time_remaining / 60).round, 'minute')
53
+ end
54
+ else
55
+ "unknown"
56
+ end
57
+ end
58
+
59
+ def duration_text(seconds)
60
+ minutes = (seconds / 60.floor)
61
+ seconds %= 60
62
+
63
+ string = ""
64
+ string << "#{minutes}m " if minutes
65
+ string << "#{seconds}s"
66
+
67
+ string
68
+ end
69
+
70
+ end
data/app/mailers/.keep ADDED
File without changes
data/app/models/.keep ADDED
File without changes
@@ -0,0 +1,15 @@
1
+ class Branch < ActiveRecord::Base
2
+
3
+ belongs_to :project
4
+ has_many :deployments, dependent: :destroy
5
+ validates :name, presence: true, uniqueness: {scope: :project_id}
6
+
7
+ def current_revision
8
+ project.git.revparse("remotes/origin/#{name}")
9
+ end
10
+
11
+ def last_commit
12
+ @last_commit ||= project.git.log.between("#{current_revision}^", current_revision).first
13
+ end
14
+
15
+ end
@@ -0,0 +1,8 @@
1
+ class Commit < ActiveRecord::Base
2
+
3
+ belongs_to :deployment
4
+
5
+ validates :deployment, presence: true
6
+ validates :sha, presence: true
7
+
8
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ class DeployOption < ActiveRecord::Base
2
+
3
+ belongs_to :project
4
+
5
+ validates :name, presence: true
6
+ validates :value, presence: true
7
+
8
+ def value
9
+ visible? ? read_attribute(:value) : "(not visible)"
10
+ end
11
+ end
@@ -0,0 +1,137 @@
1
+ class Deployment < ActiveRecord::Base
2
+
3
+ belongs_to :branch
4
+ belongs_to :environment
5
+ belongs_to :created_by, class_name: "User"
6
+ has_many :commits, dependent: :destroy
7
+
8
+ scope :pending, -> { where(started_at: nil) }
9
+ scope :awaiting_termination, -> { where(user_terminated: true, finished_at: nil) }
10
+
11
+ scope :in_progress, -> { where("started_at is not null and finished_at is null") }
12
+
13
+ delegate :project, to: :environment
14
+
15
+ before_validation :set_revision, on: :create
16
+ before_validation :set_created_by, on: :create
17
+ before_create :build_pending_commits
18
+ after_destroy :destroy_log
19
+
20
+ def started?
21
+ started_at.present?
22
+ end
23
+
24
+ def completed?
25
+ finished_at.present?
26
+ end
27
+
28
+ def failed?
29
+ completed? && !success?
30
+ end
31
+
32
+ def short_revision
33
+ revision.andand[0...8]
34
+ end
35
+
36
+ def percent_complete
37
+ return 100 if completed?
38
+ return 0 unless started?
39
+ last_duration = environment.deployments.last_successfull_deployment.andand.duration
40
+ return nil unless last_duration
41
+ duration * 100 / last_duration
42
+ end
43
+
44
+ def duration
45
+ return nil unless started?
46
+ (finished_at || Time.now).to_i - started_at.to_i
47
+ end
48
+
49
+ def status
50
+ if !started?
51
+ return "pending"
52
+ elsif !completed?
53
+ # still running
54
+ if user_terminated?
55
+ return "awaiting termination"
56
+ else
57
+ return "in progress"
58
+ end
59
+ elsif success?
60
+ return "succeeded"
61
+ elsif user_terminated?
62
+ return "terminated"
63
+ else
64
+ return "failed"
65
+ end
66
+ end
67
+
68
+ def log
69
+ # read log file
70
+ File.read(log_file) if log_file.present?
71
+ end
72
+
73
+ def terminate!
74
+ update_attributes(user_terminated: true)
75
+ end
76
+
77
+ def deploy!
78
+ log_file = File.join(ShipIt.log_dir, "#{branch.name}-#{environment.name}-#{Time.now.strftime("%Y%m%d%H%M%S")}.log")
79
+ update_attributes(log_file: log_file)
80
+
81
+ deploy_options = ["branch=#{revision}"] + project.deploy_options.map{|option| "#{option.name}=#{option.read_attribute(:value)}"}
82
+ deploy_options = deploy_options.map{|str| "-S #{str}"}.join(" ")
83
+
84
+ command = "bundle install --deployment --without development test && bundle exec cap #{environment.name} #{deploy_options} deploy:migrations"
85
+ #command = "bundle exec cap #{environment.name} #{deploy_options} deploy:migrations"
86
+ success = run_command(command, log_file)
87
+
88
+ update_attributes(finished_at: Time.now, success: success)
89
+ end
90
+
91
+ protected
92
+
93
+ def run_command(command, log_file)
94
+ pid = nil
95
+
96
+ puts "running command: #{command}"
97
+
98
+ Bundler.with_clean_env do
99
+ new_env = {"HOME" => ENV["HOME"]}
100
+ pid = Process.spawn(new_env, command, [:out, :err] => [log_file, "w"], chdir: project.workspace)
101
+
102
+ Process.wait(pid)
103
+ end
104
+
105
+ status = $?
106
+
107
+ return (status.exitstatus == 0)
108
+
109
+ rescue Exception => e
110
+ puts "deploy error: #{e.message}"
111
+ Process.kill("INT", pid) if pid
112
+
113
+ return false
114
+ end
115
+
116
+ def set_revision
117
+ self.revision ||= branch.current_revision
118
+ end
119
+
120
+ def set_created_by
121
+ self.created_by ||= User.current
122
+ end
123
+
124
+ def build_pending_commits
125
+ environment.pending_changes(branch).andand.each do |commit|
126
+ commits.build({
127
+ sha: commit.sha,
128
+ message: commit.message,
129
+ })
130
+ end
131
+ end
132
+
133
+ def destroy_log
134
+ File.unlink(log_file) if log_file.present? && File.exists?(log_file)
135
+ end
136
+
137
+ end
@@ -0,0 +1,29 @@
1
+ class Environment < ActiveRecord::Base
2
+
3
+ belongs_to :project
4
+ has_many :deployments, dependent: :destroy
5
+ has_one :last_deployment, order: "id desc", class_name: "Deployment"
6
+ has_one :last_successful_deployment, order: "id desc", class_name: "Deployment", conditions: {success: true}
7
+
8
+ validates :name, presence: true, uniqueness: {scope: :project_id}
9
+
10
+ def current_branch
11
+ last_successful_deployment.andand.branch
12
+ end
13
+
14
+ def pending_changes(branch = nil)
15
+ branch ||= current_branch
16
+ return nil unless current_branch
17
+
18
+ @pending_changes ||= begin
19
+ commits = []
20
+ current_rev = last_successful_deployment.revision
21
+ latest_rev = branch.current_revision
22
+
23
+ project.git.log.between(current_rev, latest_rev).each do |commit|
24
+ commits << commit
25
+ end
26
+ commits
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ require 'fileutils'
2
+ require 'git'
3
+
4
+ class Project < ActiveRecord::Base
5
+
6
+ validates :name, :git_repo_url, presence: true
7
+
8
+ has_many :branches, inverse_of: :project, dependent: :destroy
9
+ has_many :environments, inverse_of: :project, dependent: :destroy
10
+ has_many :deploy_options, inverse_of: :project, dependent: :destroy
11
+
12
+ after_destroy :cleanup_workspace
13
+
14
+ def cloned?
15
+ File.directory?(workspace)
16
+ end
17
+
18
+ def underscored_name
19
+ name.underscore
20
+ end
21
+
22
+ def workspace
23
+ File.join(ShipIt.workspace, underscored_name)
24
+ end
25
+
26
+ def git
27
+ @git ||= Git.open(workspace) if cloned?
28
+ end
29
+
30
+ def clone!
31
+ puts "cloning: #{git_repo_url} -> #{workspace}"
32
+ Git.clone(git_repo_url, underscored_name, path: ShipIt.workspace)
33
+ rescue => e
34
+ puts "error cloning: #{e.message}"
35
+ end
36
+
37
+ def pull!
38
+ puts "pulling"
39
+ git.fetch
40
+ git.lib.send(:command, "rebase")
41
+ end
42
+
43
+ def poll!
44
+ puts "in poll!"
45
+ clone! unless cloned?
46
+ pull!
47
+
48
+ # sync branches and environments
49
+ remotes = git.branches.remote.reject{|br| br.full.match("HEAD")}.map{|br| br.full.split("/").last}
50
+ branches.each do |branch|
51
+ branch.destroy unless remotes.include?(branch.name)
52
+ end
53
+ (remotes - branches.map(&:name)).each do |name|
54
+ branches.create({:name => name})
55
+ end
56
+
57
+ envs = []
58
+ Dir.glob(File.join(workspace, "config", "deploy", "*.rb")).each do |environment|
59
+ envs << File.basename(environment).gsub(/.rb$/, "")
60
+ end
61
+ environments.each do |environment|
62
+ environment.destroy unless envs.include?(environment.name)
63
+ end
64
+ (envs - environments.map(&:name)).each do |name|
65
+ environments.create({:name => name})
66
+ end
67
+ rescue => e
68
+ puts "error polling: #{e.message}"
69
+
70
+ pp e.backtrace
71
+ end
72
+
73
+ protected
74
+
75
+ def cleanup_workspace
76
+ FileUtils.rm_rf workspace
77
+ end
78
+
79
+ end
@@ -0,0 +1,18 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :deployments
3
+
4
+ class << self
5
+ attr_accessor :current
6
+
7
+ def find_or_create_from_auth_hash(auth_hash)
8
+ user = find_by_id(auth_hash["uid"])
9
+ return user if user
10
+
11
+ user = create({
12
+ :id => auth_hash["uid"],
13
+ :name => auth_hash["info"]["name"],
14
+ :email_address => auth_hash["info"]["email"],
15
+ })
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ h1 Add Deploy Option
2
+
3
+ = form_for [@project, @deploy_option] do |form|
4
+ = form.label :name
5
+ = form.text_field :name
6
+
7
+ = form.label :value
8
+ = form.text_field :value
9
+
10
+ = form.label :visible do
11
+ = form.check_box :visible
12
+ | Value visible?
13
+
14
+ .form-actions
15
+ = form.submit "Add Option", class: "btn btn-primary"
@@ -0,0 +1,15 @@
1
+ .current_deployments data-poll=in_progress_deployments_path
2
+ - if recent_failures.present?
3
+ h4 Recent Failures
4
+ ul.unstyled
5
+ = render recent_failures
6
+ br
7
+ hr
8
+ br
9
+
10
+ h4 Active Deployments
11
+ - if in_progress.present?
12
+ ul.unstyled
13
+ = render in_progress
14
+ - else
15
+ p.muted None.