andreimaxim/guard-unicorn

View on GitHub
lib/guard/unicorn.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'guard'

module Guard
  class Unicorn < Plugin

    # Sensible defaults for Rails projects
    DEFAULT_PID_PATH    = File.join("tmp", "pids", "unicorn.pid")
    DEFAULT_CONFIG_PATH = File.join("config", "unicorn.rb")
    DEFAULT_PORT        = 8080 # Unicorn defaults to 8080
    DEFAULT_ENVIRONMENT = "development"

    # Initialize a Guard.
    # @param [Hash] options the custom Guard options
    # @param Hash[:watchers] [<Guard::Watcher>] watchers the Guard file watchers
    def initialize(options = {})
      if options[:watchers].empty?
        options[:watchers] << Watcher.new( /^app\/(controllers|models|helpers)\/.+\.rb$/ )
        options[:watchers] << Watcher.new( /^lib\/.+\.rb$/ )
      end

      @run_as_daemon  = options.fetch(:daemonize, false)
      @enable_bundler = options.fetch(:bundler, true) 
      @pid_file       = options.fetch(:pid_file, DEFAULT_PID_PATH)
      @config_file    = options.fetch(:config_file, DEFAULT_CONFIG_PATH)
      @preloading     = options.fetch(:preloading, false)
      @port           = options.fetch(:port, DEFAULT_PORT)
      @environment    = options.fetch(:environment, DEFAULT_ENVIRONMENT)
      @socket         = options.fetch(:socket, nil)
      @unicorn_rails  = options.fetch(:unicorn_rails, false) 

      super
    end

    # Call once when Guard starts. Please override initialize method to init stuff.
    # @raise [:task_has_failed] when start has failed
    def start
      # Make sure unicorn is stopped
      stop

      cmd = []
      cmd << "bundle exec" if @enable_bundler
      cmd << (@unicorn_rails ? 'unicorn_rails' : 'unicorn')
      cmd << "-c #{@config_file}"
      cmd << "-p #{@port}" if @port
      cmd << "-l #{@socket}" if @socket
      cmd << "-E #{@environment}"
      cmd << "-D" if @run_as_daemon 

      @pid = spawn "#{cmd.join " "}"

      success "Unicorn started."
    end

    # Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits).
    # @raise [:task_has_failed] when stop has failed
    def stop
      return unless pid

      begin
        ::Process.kill("QUIT", pid) if ::Process.getpgid(pid)

        # Unicorn won't always shut down right away, so we're waiting for
        # the getpgid method to raise an Errno::ESRCH that will tell us
        # the process is not longer active.
        sleep 1 while ::Process.getpgid(pid)
        success "Unicorn stopped."
      rescue Errno::ESRCH
        # Don't do anything, the process does not exist
      end
    end

    # Called when `reload|r|z + enter` is pressed.
    # This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
    # @raise [:task_has_failed] when reload has failed
    def reload
      # If the `preload_app` directive is false, then the workers will pick up
      # any code changes using a `HUP` signal, but if the application is
      # preloaded, then a `USR2 + QUIT` signal must be used.
      #
      # For now, let's rely on the fact that the user does know how to write
      # a good unicorn configuration and he will have a block of code that
      # will properly handle `USR2` signals.
      if @preloading
        oldpid = pid
        UI.debug "Sending USR2 to unicorn with pid #{oldpid}"
        ::Process.kill 'USR2', oldpid
        UI.debug "Sending QUIT to unicorn with pid #{oldpid}"
        ::Process.kill 'QUIT', oldpid
      else
        ::Process.kill 'HUP', pid
      end

      UI.info "Done reloading unicorn."
      success "Unicorn reloaded"
    end

    # Called when just `enter` is pressed
    # This method should be principally used for long action like running all specs/tests/...
    # @raise [:task_has_failed] when run_all has failed
    def run_all
    end

    # Called on file(s) modifications that the Guard watches.
    # @param [Array<String>] paths the changes files or paths
    # @raise [:task_has_failed] when run_on_change has failed
    def run_on_modifications(paths)
      reload
    end

    private
    def pid
      # Favor the pid in the pidfile, since some processes
      # might daemonize properly and fork twice.
      if File.exists?(@pid_file)
        @pid = File.open(@pid_file) { |f| f.gets.to_i } 
      end

      @pid
    end

    def info(msg)
      UI.info(msg)
    end

    def pending message
      notify message, :image => :pending
    end

    def success message
      notify message, :image => :success
    end

    def failed message
      notify message, :image => :failed
    end

    def notify(message, options = {})
      Notifier.notify(message, options)
    end
  end
end