debox 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/debox +15 -0
- data/debox.gemspec +23 -0
- data/lib/debox.rb +9 -0
- data/lib/debox/api.rb +261 -0
- data/lib/debox/cli.rb +111 -0
- data/lib/debox/command.rb +48 -0
- data/lib/debox/command/apps.rb +14 -0
- data/lib/debox/command/auth.rb +29 -0
- data/lib/debox/command/base.rb +57 -0
- data/lib/debox/command/cap.rb +16 -0
- data/lib/debox/command/deploy.rb +18 -0
- data/lib/debox/command/key.rb +20 -0
- data/lib/debox/command/live.rb +14 -0
- data/lib/debox/command/log.rb +17 -0
- data/lib/debox/command/login.rb +18 -0
- data/lib/debox/command/logs.rb +27 -0
- data/lib/debox/command/recipes.rb +65 -0
- data/lib/debox/config.rb +59 -0
- data/lib/debox/utils.rb +77 -0
- data/lib/debox/version.rb +3 -0
- data/spec/integration/api/api_key_spec.rb +20 -0
- data/spec/integration/api/apps_spec.rb +9 -0
- data/spec/integration/api/cap/cap_spec.rb +58 -0
- data/spec/integration/api/live/log_spec.rb +12 -0
- data/spec/integration/api/logs/index_spec.rb +54 -0
- data/spec/integration/api/logs/show_spec.rb +27 -0
- data/spec/integration/api/public_key_spec.rb +15 -0
- data/spec/integration/api/recipes/create_spec.rb +10 -0
- data/spec/integration/api/recipes/destroy_spec.rb +14 -0
- data/spec/integration/api/recipes/index_spec.rb +17 -0
- data/spec/integration/api/recipes/new_spec.rb +10 -0
- data/spec/integration/api/recipes/show_spec.rb +10 -0
- data/spec/integration/api/recipes/update_spec.rb +11 -0
- data/spec/integration/api/users/create_spec.rb +8 -0
- data/spec/integration/api/users/delete_spec.rb +11 -0
- data/spec/integration/api/users/index_spec.rb +10 -0
- data/spec/integration/commands/apps_spec.rb +8 -0
- data/spec/integration/commands/cap_spec.rb +17 -0
- data/spec/integration/commands/key_spec.rb +24 -0
- data/spec/integration/commands/login_spec.rb +11 -0
- data/spec/integration/commands/logs/index_spec.rb +14 -0
- data/spec/integration/commands/logs/show_spec.rb +14 -0
- data/spec/integration/commands/recipes/delete_spec.rb +8 -0
- data/spec/integration/commands/recipes/edit_spec.rb +23 -0
- data/spec/integration/commands/recipes/index_spec.rb +8 -0
- data/spec/integration/commands/recipes/show_spec.rb +8 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/herlpers.rb +43 -0
- metadata +209 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'debox/utils'
|
3
|
+
require 'debox/api'
|
4
|
+
require "highline/import"
|
5
|
+
|
6
|
+
module Debox
|
7
|
+
|
8
|
+
module Command
|
9
|
+
include Debox::Utils
|
10
|
+
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
def self.run(command, options={}, args=[])
|
14
|
+
begin
|
15
|
+
command_instance = command[:klass].new(args, options)
|
16
|
+
method = command[:method]
|
17
|
+
command_instance.send(method)
|
18
|
+
rescue Debox::DeboxServerException => error
|
19
|
+
puts error
|
20
|
+
rescue Interrupt, StandardError=> error
|
21
|
+
puts error
|
22
|
+
puts error.backtrace
|
23
|
+
rescue SystemExit => error
|
24
|
+
puts "Bye bye"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.commands
|
29
|
+
@@commands ||= {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.register_command(command)
|
33
|
+
commands[command[:command]] = command
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.get_command(cmd)
|
37
|
+
commands[cmd]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Require all commands
|
41
|
+
def self.load
|
42
|
+
Dir[File.join(File.dirname(__FILE__), "command", "*.rb")].each do |file|
|
43
|
+
require file
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Apps < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
help :index, "List apps and envs"
|
7
|
+
def index
|
8
|
+
Debox::API.apps.each do |app|
|
9
|
+
name = app[:app]
|
10
|
+
envs = app[:envs].join ', '
|
11
|
+
notice "* #{name} [#{envs}]"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Users < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
help :index, "List all users in the debox server"
|
7
|
+
def index
|
8
|
+
users = Debox::API.users
|
9
|
+
users.each do |user|
|
10
|
+
notice user
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
help :new, 'Create user'
|
15
|
+
def new
|
16
|
+
email = args.first unless args.empty?
|
17
|
+
email = ask_email unless email
|
18
|
+
password = ask_password_with_confirmation
|
19
|
+
respose = Debox::API.users_create user: email, password: password
|
20
|
+
end
|
21
|
+
|
22
|
+
help :delete, params: ['email'], text: 'Delete user with a given email'
|
23
|
+
def delete
|
24
|
+
email = args.first
|
25
|
+
Debox::API.users_delete user: email
|
26
|
+
notice 'User deleted'
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Debox::Command::Base
|
2
|
+
include Debox::Utils
|
3
|
+
|
4
|
+
attr_reader :args
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
def initialize(args=[], options={})
|
8
|
+
@args = args
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
# Register help for given namespace
|
13
|
+
def self.namespace_help(help_txt)
|
14
|
+
@@namespace_help = help_txt
|
15
|
+
end
|
16
|
+
|
17
|
+
# Register help for given method
|
18
|
+
def self.help(method, options)
|
19
|
+
if options.is_a? Hash
|
20
|
+
help_text = options[:text]
|
21
|
+
params = options[:params] || []
|
22
|
+
opt_params = options[:opt_params] || []
|
23
|
+
else
|
24
|
+
help_text = options
|
25
|
+
params = []
|
26
|
+
opt_params = []
|
27
|
+
end
|
28
|
+
help_methods[method] = { text: help_text, params: params, opt_params: opt_params }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Help for methods
|
32
|
+
def self.help_methods
|
33
|
+
@@help_methods ||= { }
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def self.namespace
|
39
|
+
self.to_s.split("::").last.downcase
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.method_added(method)
|
43
|
+
return if self == Debox::Command::Base
|
44
|
+
return if private_method_defined?(method)
|
45
|
+
return if protected_method_defined?(method)
|
46
|
+
resolved_method = (method.to_s == "index") ? nil : method.to_s
|
47
|
+
command = [ self.namespace, resolved_method ].compact.join(":")
|
48
|
+
cmd = {
|
49
|
+
klass: self,
|
50
|
+
method: method,
|
51
|
+
command: command,
|
52
|
+
help: help_methods[method]
|
53
|
+
}
|
54
|
+
Debox::Command.register_command cmd
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Cap < Debox::Command::Base
|
4
|
+
|
5
|
+
help :index, params: ['task', 'application'], opt_params: ['environment'], text: 'Deploy application'
|
6
|
+
def index
|
7
|
+
deploy_params = { task: args[0], app: args[1] }
|
8
|
+
deploy_params[:env] = args[2] if args.count == 3
|
9
|
+
cap_request = Debox::API.cap(deploy_params)
|
10
|
+
sleep 1
|
11
|
+
Debox::API.live(deploy_params) do |chunk|
|
12
|
+
puts chunk
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Deploy < Debox::Command::Base
|
4
|
+
|
5
|
+
help :index, params: ['application', 'enviroment'], text: 'Deploy application'
|
6
|
+
def index
|
7
|
+
deploy_params = { app: args[0], env: args[1] }
|
8
|
+
if args.length > 2
|
9
|
+
deploy_params[:task] = args[2]
|
10
|
+
end
|
11
|
+
Debox::API.deploy(deploy_params)
|
12
|
+
sleep 1
|
13
|
+
Debox::API.live_log(deploy_params) do |chunk|
|
14
|
+
puts chunk
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Key < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
help :show, 'Show the server ssh public key'
|
7
|
+
def show
|
8
|
+
notice Debox::API.public_key
|
9
|
+
end
|
10
|
+
|
11
|
+
help :copy, params: ['target_host'], text: 'Copy the server ssh public key to the target host'
|
12
|
+
def copy
|
13
|
+
host = args.first
|
14
|
+
error_and_exit 'No target host' unless host
|
15
|
+
key = Debox::API.public_key
|
16
|
+
str = %{echo "#{key}" | ssh #{host} "umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys" || exit 1 }
|
17
|
+
system str
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Live < Debox::Command::Base
|
4
|
+
|
5
|
+
help :index, params: ['application', 'enviroment'], text: 'Live log for application'
|
6
|
+
def index
|
7
|
+
opt = { app: args[0] }
|
8
|
+
opt[:env] = args[1] if args[1]
|
9
|
+
Debox::API.live(opt) do |chunk|
|
10
|
+
puts chunk
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Log < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
help :index, params: ['application'],opt_params: ['environment', 'index'],
|
7
|
+
text: "Show log. Last by default"
|
8
|
+
|
9
|
+
def index
|
10
|
+
opt = { app: args[0] }
|
11
|
+
opt[:env] = args[1] if args.length > 1
|
12
|
+
opt[:index] = args[2] if args.length > 2
|
13
|
+
|
14
|
+
puts Debox::API.log opt
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Login < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
namespace_help 'Login.'
|
7
|
+
|
8
|
+
help :index, 'Login in the debox server. Require -h param.'
|
9
|
+
def index
|
10
|
+
email = options[:user] || ask_email
|
11
|
+
password = ask_password
|
12
|
+
response = Debox::API.api_key user: email, password: password
|
13
|
+
error_and_exit "Invalid login." unless response.code == "200"
|
14
|
+
Debox.config[:api_key] = response.body
|
15
|
+
Debox::Config.update_login_config
|
16
|
+
notice 'Login successful'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Logs < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
help :index, params: ['application'], opt_params: ['environment'],text: "List logs for application and env"
|
7
|
+
def index
|
8
|
+
opt = { app: args[0] }
|
9
|
+
opt[:env] = args[1] if args[1]
|
10
|
+
|
11
|
+
Debox::API.logs(opt).each do |log|
|
12
|
+
puts format_log_info(log)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def format_log_info(log)
|
19
|
+
l = []
|
20
|
+
l << log[:job_id]
|
21
|
+
l << log[:status]
|
22
|
+
l << log[:task]
|
23
|
+
l << DateTime.parse(log[:time]).to_s
|
24
|
+
l << log[:error]
|
25
|
+
l.join "\t"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'debox/command/base'
|
2
|
+
|
3
|
+
class Debox::Command::Recipes < Debox::Command::Base
|
4
|
+
include Debox::Utils
|
5
|
+
|
6
|
+
help :index, params: ['application'], text: 'List recipes for the application'
|
7
|
+
def index
|
8
|
+
app = args.first
|
9
|
+
Debox::API.recipes(app: app).each do |recipe|
|
10
|
+
puts recipe
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
help :show, params: ['application', 'environment'], text: 'Show a new capistrano recipe'
|
15
|
+
def show
|
16
|
+
app = args.first
|
17
|
+
env = args.last
|
18
|
+
puts Debox::API.recipes_show app: app, env: env
|
19
|
+
end
|
20
|
+
|
21
|
+
help :new, params: ['application', 'environment'], text: 'Create a new capistrano recipe'
|
22
|
+
def new
|
23
|
+
app = args.first
|
24
|
+
env = args.last
|
25
|
+
new_recipe = Debox::API.recipes_new app: app, env: env
|
26
|
+
md5 = md5_str new_recipe
|
27
|
+
edited_recipe = edit_file new_recipe
|
28
|
+
edited_md5 = md5_str edited_recipe
|
29
|
+
if md5 == edited_md5
|
30
|
+
exit_ok "No changes detected, nothing to update."
|
31
|
+
end
|
32
|
+
|
33
|
+
# Send changes back to the server
|
34
|
+
notice "Updating changes on the server"
|
35
|
+
Debox::API.recipes_create app: app, env: env, content: edited_recipe
|
36
|
+
exit_ok "Recipe created"
|
37
|
+
end
|
38
|
+
|
39
|
+
help :edit, params: ['application', 'environment'], text: 'Edit a capistrano recipe'
|
40
|
+
def edit
|
41
|
+
app = args.first
|
42
|
+
env = args.last
|
43
|
+
recipe = Debox::API.recipes_show app: app, env: env
|
44
|
+
md5 = md5_str recipe
|
45
|
+
edited_recipe = edit_file recipe
|
46
|
+
edited_md5 = md5_str edited_recipe
|
47
|
+
if md5 == edited_md5
|
48
|
+
exit_ok "No changes detected, nothing to update."
|
49
|
+
end
|
50
|
+
|
51
|
+
# Send changes back to the server
|
52
|
+
notice "Updating changes on the server"
|
53
|
+
Debox::API.recipes_update app: app, env: env, content: edited_recipe
|
54
|
+
exit_ok "Recipe updated"
|
55
|
+
end
|
56
|
+
|
57
|
+
help :delete, params: ['application', 'environment'], text: 'Delete a capistrano recipe'
|
58
|
+
def delete
|
59
|
+
app = args.first
|
60
|
+
env = args.last
|
61
|
+
Debox::API.recipes_destroy app: app, env: env
|
62
|
+
exit_ok "Recipe deleted"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/debox/config.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Debox
|
4
|
+
|
5
|
+
# Convenient shortcut to Debox::Config.config
|
6
|
+
def self.config
|
7
|
+
Debox::Config.config
|
8
|
+
end
|
9
|
+
|
10
|
+
module Config
|
11
|
+
|
12
|
+
CONFIG_FILE_NAME=".deboxrc"
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
@@config ||= read_config
|
16
|
+
end
|
17
|
+
|
18
|
+
# Merge given options with current config
|
19
|
+
def self.merge_command_line_options!(options)
|
20
|
+
config.merge! options
|
21
|
+
end
|
22
|
+
|
23
|
+
# Update login values in the config
|
24
|
+
def self.update_login_config
|
25
|
+
current = read_config
|
26
|
+
current.merge! host: config[:host], port: config[:port], user: config[:user], api_key: config[:api_key]
|
27
|
+
save_config current
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return true if the user has login information
|
31
|
+
def self.logged_in?
|
32
|
+
config[:api_key] && config[:api_key].length > 0
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Read the config from the filesystem
|
38
|
+
def self.read_config
|
39
|
+
return defaults unless File.exists? user_config_file
|
40
|
+
YAML.load_file user_config_file
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.save_config(current=config)
|
44
|
+
f = File.open(user_config_file, 'w')
|
45
|
+
f.write(current.to_yaml)
|
46
|
+
f.close
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return defaults values
|
50
|
+
def self.defaults
|
51
|
+
{ host: 'localhost', port: 80 }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return config file full path
|
55
|
+
def self.user_config_file
|
56
|
+
File.join ENV['HOME'], CONFIG_FILE_NAME
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/debox/utils.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module Debox
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
def email_regex
|
5
|
+
/^([^@\s]+)@((?:[-a-z0-9_]+\.)+[a-z]{2,})$/i
|
6
|
+
end
|
7
|
+
|
8
|
+
# md5 and hash
|
9
|
+
#----------------------------------------------------------------------
|
10
|
+
|
11
|
+
def md5_str(str)
|
12
|
+
Digest::MD5.hexdigest str
|
13
|
+
end
|
14
|
+
|
15
|
+
def md5_file(filename)
|
16
|
+
Digest::MD5.file(filename)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# file edit
|
21
|
+
#----------------------------------------------------------------------
|
22
|
+
def edit_file(content)
|
23
|
+
tmp_file = Tempfile.new "debox"
|
24
|
+
tmp_file.write content
|
25
|
+
tmp_file.close
|
26
|
+
editor = ENV['EDITOR'] || 'nano'
|
27
|
+
system "#{editor} #{tmp_file.path}"
|
28
|
+
edited_file = File.open tmp_file.path
|
29
|
+
edited_file.read
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ask for console input
|
33
|
+
#----------------------------------------------------------------------
|
34
|
+
|
35
|
+
def ask_email
|
36
|
+
ask("email: ") do |q|
|
37
|
+
q.validate = email_regex
|
38
|
+
q.responses[:not_valid] = "<%= color('Email is not well formed', :red) %>"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ask_password
|
43
|
+
ask("password: "){ |q| q.echo = '*' }
|
44
|
+
end
|
45
|
+
|
46
|
+
def ask_password_with_confirmation
|
47
|
+
ask("password: ") do |q|
|
48
|
+
q.echo = '*'
|
49
|
+
q.verify_match = true
|
50
|
+
q.gather = {"password: " => '',
|
51
|
+
"password confirmation: " => ''}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Errors and warnings
|
57
|
+
#----------------------------------------------------------------------
|
58
|
+
|
59
|
+
def notice(msg)
|
60
|
+
puts msg
|
61
|
+
end
|
62
|
+
|
63
|
+
def alert(msg)
|
64
|
+
end
|
65
|
+
|
66
|
+
def error_and_exit(msg)
|
67
|
+
puts msg
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def exit_ok(msg=nil)
|
72
|
+
puts msg if msg
|
73
|
+
exit 0
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|