debox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +15 -0
  4. data/LICENSE +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +2 -0
  7. data/bin/debox +15 -0
  8. data/debox.gemspec +23 -0
  9. data/lib/debox.rb +9 -0
  10. data/lib/debox/api.rb +261 -0
  11. data/lib/debox/cli.rb +111 -0
  12. data/lib/debox/command.rb +48 -0
  13. data/lib/debox/command/apps.rb +14 -0
  14. data/lib/debox/command/auth.rb +29 -0
  15. data/lib/debox/command/base.rb +57 -0
  16. data/lib/debox/command/cap.rb +16 -0
  17. data/lib/debox/command/deploy.rb +18 -0
  18. data/lib/debox/command/key.rb +20 -0
  19. data/lib/debox/command/live.rb +14 -0
  20. data/lib/debox/command/log.rb +17 -0
  21. data/lib/debox/command/login.rb +18 -0
  22. data/lib/debox/command/logs.rb +27 -0
  23. data/lib/debox/command/recipes.rb +65 -0
  24. data/lib/debox/config.rb +59 -0
  25. data/lib/debox/utils.rb +77 -0
  26. data/lib/debox/version.rb +3 -0
  27. data/spec/integration/api/api_key_spec.rb +20 -0
  28. data/spec/integration/api/apps_spec.rb +9 -0
  29. data/spec/integration/api/cap/cap_spec.rb +58 -0
  30. data/spec/integration/api/live/log_spec.rb +12 -0
  31. data/spec/integration/api/logs/index_spec.rb +54 -0
  32. data/spec/integration/api/logs/show_spec.rb +27 -0
  33. data/spec/integration/api/public_key_spec.rb +15 -0
  34. data/spec/integration/api/recipes/create_spec.rb +10 -0
  35. data/spec/integration/api/recipes/destroy_spec.rb +14 -0
  36. data/spec/integration/api/recipes/index_spec.rb +17 -0
  37. data/spec/integration/api/recipes/new_spec.rb +10 -0
  38. data/spec/integration/api/recipes/show_spec.rb +10 -0
  39. data/spec/integration/api/recipes/update_spec.rb +11 -0
  40. data/spec/integration/api/users/create_spec.rb +8 -0
  41. data/spec/integration/api/users/delete_spec.rb +11 -0
  42. data/spec/integration/api/users/index_spec.rb +10 -0
  43. data/spec/integration/commands/apps_spec.rb +8 -0
  44. data/spec/integration/commands/cap_spec.rb +17 -0
  45. data/spec/integration/commands/key_spec.rb +24 -0
  46. data/spec/integration/commands/login_spec.rb +11 -0
  47. data/spec/integration/commands/logs/index_spec.rb +14 -0
  48. data/spec/integration/commands/logs/show_spec.rb +14 -0
  49. data/spec/integration/commands/recipes/delete_spec.rb +8 -0
  50. data/spec/integration/commands/recipes/edit_spec.rb +23 -0
  51. data/spec/integration/commands/recipes/index_spec.rb +8 -0
  52. data/spec/integration/commands/recipes/show_spec.rb +8 -0
  53. data/spec/spec_helper.rb +35 -0
  54. data/spec/support/herlpers.rb +43 -0
  55. 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
@@ -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
@@ -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