exe/deis-rails in deis-rails-1.0.1 vs exe/deis-rails in deis-rails-1.0.3

- old
+ new

@@ -1,348 +1,4 @@ #!/usr/bin/env ruby -require 'yaml' -require 'bundler' -require 'active_support/all' -require 'pry' -require 'pty' -require 'benchmark' -require 'colorize' - -class DeisUtils < Struct.new(:args) - - TRUTHY_STRINGS = %w(t true y yes 1).flat_map do |str| - [str.downcase, str.upcase, str.capitalize] - end.uniq - - FALSEY_STRINGS = %w(f false n no 0).flat_map do |str| - [str.downcase, str.upcase, str.capitalize] - end.uniq - - NonZeroExitError = Class.new(StandardError) - - def run - command = args.shift - status = Helpers.run_util(command, *args) - exit status === false ? 1 : 0 - end - - module Helpers - - module_function def run_util(name, *args) - status = false - time = Benchmark.realtime do - status = get_runner(name).new(*args).run - end - STDOUT.puts "#{$0} #{name} #{args.join ' '}` took #{seconds_to_human time}".light_blue if debug? - status - end - - module_function def debug? - ENV['DEBUG'].in? TRUTHY_STRINGS - end - - module_function def get_runner(name) - DeisUtils.const_get(name.gsub('-', '_').camelize) - rescue NameError - STDERR.puts "command `#{name}` does not exist!".red - exit 1 - end - - def app_exists?(app) - !!deis_command('info', app: app) - rescue NonZeroExitError - false - end - - def status(msg) - msg = "| #{msg} |" - sep = ''.ljust(msg.size, '-') - puts "\n\e[0;92;49m#{[sep, msg, sep].join "\n"}\e[0m" - end - - def databases(app) - databases = deis_command('pg:info', app: app).split('===') - databases[1..-1].map do |db| - lines = db.lines - name = lines[0].split(' ')[0] - data = lines[1..-1].join - { 'Name' => name }.merge YAML.load(data) - end - end - - def info(app) - cmd_result = deis_command(:info, app: app) - data = JSON.load cmd_result.match(/Application\r\n(?<json>\{.*\})/m)[:json] - controller_host = data['url'].split('.').tap { |p| p[p.index app] = 'deis' }.join('.') - data['git_url'] = "ssh://git@#{controller_host}:2222/#{app}.git" - data['domains'] = cmd_result.match(/Domains\r\n(?<domains>.*)/m)[:domains].lines.map(&:strip).reject(&:empty?) - data.sort.to_h - end - - def shell(*commands) - flags = commands.last.is_a?(Hash) ? commands.pop : {} - command = commands.join ' ' - flags.each_with_object(command) do |(k, v), c| - c << case v - when TrueClass, FalseClass - v ? " --#{k}" : '' - when String, Fixnum - " --#{k} #{v}" - else - raise ArgumentError - end - end - result = nil - time = Benchmark.realtime do - result = capture_syscall command - end - STDOUT.puts "`#{command}` took #{seconds_to_human time}".light_black if debug? - result - end - - def deis_command(*cmds) - shell :deis, *cmds - end - - def git_clone(url, flags = {}) - shell "git clone #{url}", flags - end - - def deploy(url, options = {}) - ref = options.delete(:ref) || 'master' - shell "git push #{url} #{ref}:master", options - end - - def capture_syscall(command) - puts "\n#{command}".light_yellow if debug? - String.new.tap do |output| - threads = [] - PTY.spawn(command) do |p_out, p_in, pid| - threads << Thread.new { p_in.close } - threads << Thread.new { output << capture_output(p_out) } - Process.wait pid - threads.each(&:join) - end - raise NonZeroExitError, output unless $?.exitstatus == 0 - end - end - - def capture_output(io) - output = '' - io.each do |line| - output << line - STDOUT.puts "#{line.strip}".light_black unless line.nil? || line.strip.empty? if debug? - end - ensure - return output - end - - # Warn Slack for upcoming maintenance mode - def warn_for_maintenance(app) - Slacker.message(text: "WARNING: #{app} will go into maintenance mode shortly.", - username: "Deploy Bot", - channel: "#pulse-dev", - color: "warning", - icon_emoji: ":warning:") - end - - # Enable Maintenance Mode on Heroku - def enable_maintenance_mode(app) - get_units! - status "#{app} is going into maintenance mode!" - Slacker.message(text: "ALERT: #{app} is going into maintenance mode!", - username: "Deploy Bot", - channel: "#pulse-dev", - color: "danger", - icon_emoji: ":bangbang:") - scale app, units.keys.each_with_object({}) { |k, h| h[k] = 0 } - end - - # Disable Maintenance Mode on Heroku - def disable_maintenance_mode(app) - scale app, units - status "#{app} is no longer in maintenance mode!" - Slacker.message(text: "NOTICE: #{app} is no longer in maintenance mode.", - username: "Deploy Bot", - channel: "#pulse-dev", - color: "good", - icon_emoji: ":white_check_mark:") - end - - def scale(app, opts={}) - process_string = opts.map { |k, v| "#{k}=#{v}" }.join ' ' - deis_command "scale #{process_string}", app: app - end - - def get_units! - unit_string = deis_command('ps', app: app) - shell('foreman check').strip.sub(/.*\((.*)\)/, "\\1").split(', ').each do |unit| - units[unit] ||= unit_string.lines.select { |l| l.include? "#{unit}." }.count - end - end - - def units - @units ||= {} - end - - module_function def seconds_to_human(secs) - secs = secs.round - output = '' - seconds = secs % 60 - minutes = (secs - seconds) / 60 - output << "#{minutes}m" if minutes > 0 - output << "#{seconds}s" - end - - end - - class Info < Struct.new :app - include Helpers - - def run - h = info(app) - status "`#{app}` Information" - output_hash h - end - - def output_hash(hash, indent = 0) - hash.each do |k, v| - case v - when Hash - puts (' ' * indent) + k + ':' - output_hash(v, indent + 1) - when Array - puts (' ' * indent) + k + ':', *v.map { |i| (' ' * (indent + 1)) + i.to_s } - else - puts (' ' * indent) + "#{k}: #{v}" - end - end - end - end - - class Exists < Struct.new :app - include Helpers - - def run - app_exists?(app) - end - end - - class CopyConfig < Struct.new :source_app, :dest_app - include Helpers - - def run - system <<-sh - deis config --app #{source_app} --oneline | xargs deis config:set --app #{dest_app} - sh - end - end - - class Disable < Struct.new :app - include Helpers - - def run - status "Disabling App: #{app}" - units = shell('foreman check').strip.sub(/.*\((.*)\)/, "\\1").split(', ') - scale app, units.each_with_object({}) { |k, h| h[k] = 0 } - end - - end - - class Enable < Struct.new :app - include Helpers - - def run - status "Enabling App: #{app}" - get_units! - if units.any? { |_, v| v > 0 } - status "App Already enabled!" - return - end - scale app, units.keys.each_with_object({}) { |k, h| h[k] = 1 } - status "App enabled!" - end - - end - - # Deploy a deis repo - class Deploy < Struct.new(:app, :ref) - include Helpers - - LAST_MIGRATION_CMD = %{bundle exec rake db:migrate:status} - MIGRATION_REGEX = /up\s+(?<migration>[0-9]{8}[0-9])+/ - - attr_reader :worker_count - attr_reader :web_count - - def run - status "Deploying `#{ref}` to `#{app}` on Deis" - precheck_migrations! - output = deploy info['git_url'], ref: ref - if output.include? 'Another git push is ongoing' - sleep 60 # one minute - return run_util 'deploy', app, ref - end - run_migrations! if needs_migrations? - rescue NonZeroExitError => e - raise e unless e.message.include? 'Another git push is ongoing' - remove_instance_variable :@needs_migrations - sleep 60 - retry - end - - private - - def info - @info ||= super(app) - end - - def run_migrations! - # Todo: put the site in a readonly state but NEVER in maintenance mode - status 'Running Migrations' - deis_command('run rake "db:migrate"', app: app) - end - - def precheck_migrations! - needs_migrations? - end - - def needs_migrations? - return false if TRUTHY_STRINGS.include? ENV['SKIP_MIGRATIONS'] - return true if TRUTHY_STRINGS.include? ENV['FORCE_MIGRATIONS'] - @needs_migrations ||= begin - status 'Checking Migration Status' - (local_migration != remote_migration).tap do |val| - if val - puts 'Database out of date, Migrations are Required' - else - puts 'Database is up to date' - end - end - end - end - - def remote_migration - @remote_migration ||= sha_from_migration_status deis_command("run #{LAST_MIGRATION_CMD}", app: app) - rescue NonZeroExitError - 'error' - end - - def local_migration - @local_migration ||= sha_from_migration_status shell(LAST_MIGRATION_CMD) - rescue NonZeroExitError - 'error' - end - - def sha_from_migration_status(result) - lines = result.lines.reject { |line| line.include? '**** NO FILE ****' } - migration_lines = lines.map(&:strip).select { |line| line =~ MIGRATION_REGEX } - Digest::SHA2.hexdigest migration_lines.join - end - - end - -end - -Bundler.with_clean_env do - DeisUtils.new(ARGV).run -end +require 'bundler/setup' +require_relative '../lib/deis' +Deis::Runner.new(ARGV).run