cocaine/cocaine-framework-ruby

View on GitHub
tools/tools

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby

require 'thor'
require 'json'

require 'rainbow/ext/string'

require_relative '../lib/cocaine'

def stage(prompt)
  puts "[  --  ] #{prompt} ..."
  result, magic = yield
  puts "\e[A\r[#{'  OK  '.color :green}] #{prompt}#{result ? '    ' : ": #{magic.to_s.color :green}"}"
  return result
rescue Exception => err
  puts "\e[A\r[#{' FAIL '.color :red}] #{prompt}: #{err.to_s.color :red}"
  exit(1)
end

module CocaineTools
  module App
    def self.upload(path, name=nil)
      manifest = stage "Locating and reading manifest from '#{path}'" do
        MessagePack.pack JSON.parse File.read File.join path, 'manifest.json'
      end

      name ||= File.basename File.absolute_path path

      tmpdir = Dir.mktmpdir 'cocaine-app-archive'
      tmpfile = File.join(tmpdir, "#{name}.tar.gz")
      archive = stage "Creating archive from '#{path}'" do
        begin
          `tar -czf #{tmpfile} -C #{path} .`
          MessagePack.pack File.read tmpfile
        ensure
          FileUtils.remove_entry_secure tmpdir
        end
      end

      storage = stage 'Connecting to the Cocaine' do
        Cocaine::Service.new :storage
      end

      stage 'Uploading manifest' do
        storage.write :manifests, name, manifest, [:app]
      end

      stage "Uploading '#{name}' application" do
        storage.write :apps, name, archive, [:app]
      end
    end

    def self.restart(app, profile)
      node = stage 'Connecting to the Cocaine' do
        Cocaine::Service.new :node
      end

      stage "Stopping application '#{app}'" do
        tx, rx = node.pause_app app
        [nil, rx.recv]
      end

      stage "Starting application '#{app}' with profile '#{profile}'" do
        tx, rx = node.start_app app, profile
        [nil, rx.recv]
      end
    end
  end
end

class App < Thor
  desc 'list', 'apps list'
  def list
    storage = stage 'Connecting to the Cocaine' do
      Cocaine::Service.new :storage
    end

    stage 'Fetching app list' do
      tx, rx = storage.find :manifests, [:app]
      [nil, rx.recv]
    end
  end

  desc 'upload PATH', 'upload application from PATH'
  option :name
  def upload(path=Dir.pwd)
    CocaineTools::App::upload path, options[:name]
  rescue Exception => err
    puts err
    exit(1)
  end

  desc 'restart APP PROFILE', 'restart APP with PROFILE'
  def restart(app, profile)
    CocaineTools::App::restart app, profile
  end
end

class Profile < Thor
  desc 'list', 'profiles list'
  def list
    storage = stage 'Connecting to the Cocaine' do
      Cocaine::Service.new :storage
    end

    stage 'Fetching profile list' do
      tx, rx = storage.find :profiles, [:profile]
      [nil, rx.recv]
    end
  end

  desc 'new NAME [--force] [--empty]', 'create a new profile with given NAME and upload it after editing in interactive editor'
  option :force, :type => :boolean, :default => false
  option :empty, :type => :boolean
  def new(name)
    storage = stage 'Connect to the Cocaine' do
      Cocaine::Service.new :storage
    end

    unless options[:force]
      stage "Check profile '#{name}' existence" do
        tx, rx = storage.find :profiles, [:profile]
        id, list = rx.recv
        case id
          when :value
            if list[0].include? name
              raise 'found'
            end
          else
            raise 'unable to fetch profile list'
        end

        [nil, 'not found']
      end
    end

    if options[:empty]
      stage "Upload empty profile '#{name}'" do
        content = MessagePack::pack({})
        storage.write :profiles, name, content, [:profile]
      end
      exit(0)
    end

    file = Tempfile.new %w(profile .json)
    File.open file.path, 'w' do |f|
      f.write '{}'
    end

    system("vim #{file.path}")
    content = JSON.parse File.open(file.path).readlines.join('\n')

    stage "Upload profile '#{name}'" do
      content = MessagePack::pack(content)
      storage.write :profiles, name, content, [:profile]
    end
  end

  desc 'upload NAME PATH', 'upload NAME profile from PATH'
  def upload(name, path)
    content = stage "Reading profile from '#{path}'" do
      MessagePack.pack JSON.parse File.read path
    end

    storage = stage 'Connecting to the Cocaine' do
      Cocaine::Service.new :storage
    end

    stage "Uploading '#{name}' profile" do
      storage.write :profiles, name, content, [:profile]
    end
  rescue Exception => err
    puts err
    exit(1)
  end

  desc 'edit NAME', 'edit NAME runlist'
  def edit(name)
    storage = stage 'Connecting to the Cocaine' do
      Cocaine::Service.new :storage
    end

    profile = stage "Reading '#{name}' profile" do
      tx, rx = storage.read :profiles, name
      id, profile = rx.recv
      profile
    end

    file = Tempfile.new('profile')
    begin
      file.write JSON.generate MessagePack::unpack profile
    ensure
      file.close
    end

    begin
      system ENV['EDITOR'] || 'vim', file.path
      file.open
      content = MessagePack.pack JSON.parse file.read
      stage "Uploading '#{name}' profile" do
        storage.write :profiles, name, content, [:profile]
      end
    ensure
      file.close
      file.unlink
    end
  end
end

class Runlist < Thor
  desc 'edit NAME', 'edit NAME runlist'
  def edit(name)
    storage = stage 'Connecting to the Cocaine' do
      Cocaine::Service.new :storage
    end

    runlist = stage "Reading '#{name}' runlist" do
      tx, rx = storage.read :runlists, name
      id, runlist = rx.recv
      runlist
    end

    file = Tempfile.new('runlist')
    begin
      file.write JSON.generate MessagePack::unpack runlist
    ensure
      file.close
    end

    begin
      system ENV['EDITOR'] || 'vim', file.path
      file.open
      content = MessagePack.pack JSON.parse file.read
      stage "Uploading '#{name}' runlist" do
        storage.write :runlists, name, content, [:runlist]
      end
    ensure
      file.close
      file.unlink
    end
  end
end

class CocaineToolsCLI < Thor
  desc 'info APP', 'Fetch APP info'
  def info(app)
    service = Cocaine::Service.new app
    loop do
      tx, rx = service.info
      id, info = rx.recv
      puts :info => info
      sleep 0.1
    end
  end

  desc 'app SUBCOMMAND', 'Application specific tools'
  subcommand :app, App

  desc 'profile SUBCOMMAND', 'Profile specific tools'
  subcommand :profile, Profile

  desc 'runlist SUBCOMMAND', 'Runlist specific tools'
  subcommand :runlist, Runlist
end

# Select the verbosity level.
Celluloid.logger = nil
Cocaine::LOG.level = Logger::ERROR

ARGV.select do |v|
  /^-(?<level>v+)$/ =~ v
  if level
    case level.count('v')
      when 1
        Celluloid.logger = nil
        Cocaine::LOG.level = Logger::WARN
      when 2
        Celluloid.logger = nil
        Cocaine::LOG.level = Logger::INFO
      when 3
        Celluloid.logger = nil
        Cocaine::LOG.level = Logger::DEBUG
      else
        Cocaine::LOG.level = Logger::DEBUG
    end
  end
end

ARGV.reject! do |v|
  v =~ /^-(?<level>v+)$/
end

CocaineToolsCLI.start