3scale/apisonator

View on GitHub
bin/3scale_backend

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby
require 'gli'
require '3scale/backend/version'

class Runner
  class << self
    def do_exit(code, global, msg='Unknown error')
      Dir.chdir global[:original_dir]
      exit_now! msg, code
    end

    def verbose(global_options, out=STDOUT)
      out.puts(yield) if global_options[:verbose] || global_options[:'dry-run']
    end

    def exec(global_options)
      Process.exec(*yield) unless global_options[:'dry-run']
    end


    def build_cmdline(cmd, global_options, options, args)
      require '3scale/backend/server'
      server = ThreeScale::Backend::Server.get global_options[:server]
      server.send(cmd, global_options, options, args)
    rescue => e
      STDERR.puts "Error: #{e.message}#{"\n" + caller_locations(0).join("\n") if global_options[:verbose]}"
    end

    def do_command(cmd, global_options, options, args)
      argv = build_cmdline cmd, global_options, options, args
      do_exit 1, global_options, "can't build command line" unless argv
      argv << global_options[:'extra-args'] if global_options[:'extra-args']
      verbose(global_options) { "Executing: #{argv.join ' '}" }
      exec(global_options) { argv }
    end

    def add_instance_id_options(c)
      c.desc 'TCP port number where backend listens for connections'
      c.arg_name 'NUMBER'
      c.default_value '3000'
      c.flag :p, :port
      c.desc 'Filename where backend writes its PID'
      c.arg_name 'FILENAME'
      c.flag :pidfile
    end
  end

  extend GLI::App

  program_desc '3scale_backend launcher'

  version ThreeScale::Backend::VERSION

  CONFIG_FILE = '.backend_launcher.rc.yml'
  EXPANDED_ROOT_PATH = File.expand_path(File.join('..', '..'), __FILE__)

  config_file File.join(ENV['HOME'] || File.join('', 'tmp'), CONFIG_FILE)

  subcommand_option_handling :normal
  arguments :strict

  desc 'Load Bundler when running'
  default_value true
  switch :b, :bundler

  desc 'Do not actually run anything, just print what would be done'
  default_value false
  switch :n, :'dry-run'

  desc 'Verbose mode'
  default_value false
  switch :v, :verbose

  desc 'Directory where the app server will expect the code to be at'
  arg_name 'DIRNAME'
  flag :z, :directory

  desc "Application server to use with backend"
  default_value 'puma'
  arg_name 'SERVER'
  flag :s, :server

  desc 'Environment backend will use'
  arg_name 'ENVIRONMENT'
  default_value 'development'
  flag :e, :environment

  desc 'Extra command arguments'
  arg_name 'ARGS'
  flag :X, :'extra-args'

  pre do |global, command, options, args|
    # Pre logic here
    # Return true to proceed; false to abort and not call the
    # chosen command
    # Use skips_pre before a command to skip this block
    # on that command only
    global[:original_dir] = Dir.pwd
    if global[:directory]
      Dir.chdir global[:directory]
    end
    if global[:bundler]
      begin
        require 'bundler/setup'
        if !Bundler::SharedHelpers.in_bundle?
          # Gemfile not found, try with relative Gemfile from us
          ENV['BUNDLE_GEMFILE'] = File.join(EXPANDED_ROOT_PATH, 'Gemfile')
          require 'bundler'
          Bundler.setup
        end
      rescue LoadError, Bundler::BundlerError => e
        do_exit 64, global, "Unable to meet requirements: #{e.message}"
      end
    end
    # manifest loading is not strictly necessary, so make it optional
    begin
      require '3scale/backend/manifest'
      global[:manifest] = ThreeScale::Backend::Manifest.report
    rescue LoadError, NameError, NoMethodError
    end
    true
  end

  post do |global,command,options,args|
    # Post logic here
    # Use skips_post before a command to skip this
    # block on that command only
    Dir.chdir global[:original_dir]
  end

  on_error do |exception|
    # Error logic here
    # return false to skip default error handling
    true
  end

  desc 'Shows the capabilities of backend in its manifest'
  command :manifest do |c|
    c.action do |global_options, options, arg|
      manifest = global_options[:manifest]
      do_exit 65, global_options, "Could not load manifest: #{e.message}" unless manifest
      STDOUT.puts(manifest.map do |k, v|
        "#{"#{k[0..19]}:".ljust(21)} #{v.inspect}"
      end.join "\n")
    end
  end

  desc 'Starts the backend server'
  command :start do |c|
    c.desc 'Filename where backend will log requests to (default: stdout)'
    c.arg_name 'FILENAME'
    c.flag :l, :logfile
    c.desc 'Filename where backend will log errors to, defaulting to --logfile value'
    c.arg_name 'FILENAME'
    c.flag :x, :errorfile
    c.desc 'Daemonize the server'
    c.default_value false
    c.switch :d, :daemonize
    add_instance_id_options c

    c.action do |global_options, options, args|
      options[:logfile] = nil if options[:logfile] == '-'
      options[:errorfile] ||= options[:logfile]
      options[:errorfile] = nil if options[:errorfile] == '-'
      do_command :start, global_options, options, args
    end

    c.example '3scale_backend start -p 5001 -d --pidfile backend.pid -l /var/log/backend.log -x /var/log/backend.err.log',
              desc: 'Listen on port 5001, daemonize with pidfile, write separate logs for requests and errors'
  end

  desc 'Stops the backend server'
  command :stop do |c|
    add_instance_id_options c

    c.action do |global_options, options, args|
      do_command :stop, global_options, options, args
    end

    c.example '3scale_backend stop --pidfile backend.pid', desc: 'Stop a daemon with a pidfile'
  end

  desc 'Restarts the backend server'
  command :restart do |c|
    add_instance_id_options c

    c.desc 'Perform a phased-restart where available to avoid downtime'
    c.default_value true
    c.switch :"phased-restart"

    c.action do |global_options, options, args|
      do_command :restart, global_options, options, args
    end
  end

  desc 'Prints the status of the backend server'
  command :status do |c|
    add_instance_id_options c

    c.action do |global_options, options, args|
      do_command :status, global_options, options, args
    end
  end

  desc 'Prints statistics of the backend server'
  command :stats do |c|
    add_instance_id_options c

    c.action do |global_options, options, args|
      do_command :stats, global_options, options, args
    end
  end

  desc 'Print extra help from the application server'
  command :'help-server' do |c|
    c.action do |global_options, options, args|
      server = ThreeScale::Backend::Server.get global_options[:server]
      server.help(global_options, options, args)
    end
  end
end

exit Runner.run(ARGV)