tools/tools
#!/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