af 0.3.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +92 -0
  3. data/Rakefile +17 -0
  4. data/bin/af +6 -0
  5. data/lib/cli.rb +30 -0
  6. data/lib/cli/commands/admin.rb +77 -0
  7. data/lib/cli/commands/apps.rb +940 -0
  8. data/lib/cli/commands/base.rb +79 -0
  9. data/lib/cli/commands/misc.rb +128 -0
  10. data/lib/cli/commands/services.rb +86 -0
  11. data/lib/cli/commands/user.rb +60 -0
  12. data/lib/cli/config.rb +110 -0
  13. data/lib/cli/core_ext.rb +119 -0
  14. data/lib/cli/errors.rb +19 -0
  15. data/lib/cli/frameworks.rb +109 -0
  16. data/lib/cli/runner.rb +490 -0
  17. data/lib/cli/services_helper.rb +78 -0
  18. data/lib/cli/usage.rb +104 -0
  19. data/lib/cli/version.rb +7 -0
  20. data/lib/cli/zip_util.rb +77 -0
  21. data/lib/vmc.rb +3 -0
  22. data/lib/vmc/client.rb +451 -0
  23. data/lib/vmc/const.rb +21 -0
  24. data/spec/assets/app_info.txt +9 -0
  25. data/spec/assets/app_listings.txt +9 -0
  26. data/spec/assets/bad_create_app.txt +9 -0
  27. data/spec/assets/delete_app.txt +9 -0
  28. data/spec/assets/global_service_listings.txt +9 -0
  29. data/spec/assets/good_create_app.txt +9 -0
  30. data/spec/assets/good_create_service.txt +9 -0
  31. data/spec/assets/info_authenticated.txt +27 -0
  32. data/spec/assets/info_return.txt +15 -0
  33. data/spec/assets/info_return_bad.txt +16 -0
  34. data/spec/assets/list_users.txt +13 -0
  35. data/spec/assets/login_fail.txt +9 -0
  36. data/spec/assets/login_success.txt +9 -0
  37. data/spec/assets/sample_token.txt +1 -0
  38. data/spec/assets/service_already_exists.txt +9 -0
  39. data/spec/assets/service_listings.txt +9 -0
  40. data/spec/assets/service_not_found.txt +9 -0
  41. data/spec/assets/user_info.txt +9 -0
  42. data/spec/spec_helper.rb +11 -0
  43. data/spec/unit/cli_opts_spec.rb +68 -0
  44. data/spec/unit/client_spec.rb +332 -0
  45. metadata +221 -0
@@ -0,0 +1,79 @@
1
+
2
+ require 'rubygems'
3
+ require 'terminal-table/import'
4
+ require 'highline/import'
5
+
6
+ module VMC::Cli
7
+
8
+ module Command
9
+
10
+ class Base
11
+ attr_reader :no_prompt, :prompt_ok
12
+
13
+ def initialize(options={})
14
+ @options = options.dup
15
+ @no_prompt = @options[:noprompts]
16
+ @prompt_ok = !no_prompt
17
+
18
+ # Fix for system ruby and Highline (stdin) on MacOSX
19
+ if RUBY_PLATFORM =~ /darwin/ && RUBY_VERSION == '1.8.7' && RUBY_PATCHLEVEL <= 174
20
+ HighLine.track_eof = false
21
+ end
22
+
23
+ # Suppress colorize on Windows systems for now.
24
+ if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
25
+ VMC::Cli::Config.colorize = false
26
+ end
27
+
28
+ end
29
+
30
+ def client
31
+ return @client if @client
32
+ @client = VMC::Client.new(target_url, auth_token)
33
+ @client.trace = VMC::Cli::Config.trace if VMC::Cli::Config.trace
34
+ @client.proxy_for @options[:proxy] if @options[:proxy]
35
+ @client
36
+ end
37
+
38
+ def client_info
39
+ return @client_info if @client_info
40
+ @client_info = client.info
41
+ end
42
+
43
+ def target_url
44
+ return @target_url if @target_url
45
+ @target_url = VMC::Cli::Config.target_url
46
+ end
47
+
48
+ def auth_token
49
+ return @auth_token if @auth_token
50
+ @auth_token = VMC::Cli::Config.auth_token
51
+ end
52
+
53
+ def runtimes_info
54
+ return @runtimes if @runtimes
55
+ info = client_info
56
+ @runtimes = {}
57
+ if info[:frameworks]
58
+ info[:frameworks].each_value do |f|
59
+ next unless f[:runtimes]
60
+ f[:runtimes].each { |r| @runtimes[r[:name]] = r}
61
+ end
62
+ end
63
+ @runtimes
64
+ end
65
+
66
+ def frameworks_info
67
+ return @frameworks if @frameworks
68
+ info = client_info
69
+ @frameworks = []
70
+ if info[:frameworks]
71
+ info[:frameworks].each_value { |f| @frameworks << [f[:name]] }
72
+ end
73
+ @frameworks
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,128 @@
1
+ module VMC::Cli::Command
2
+
3
+ class Misc < Base
4
+ def version
5
+ say "vmc #{VMC::Cli::VERSION}"
6
+ end
7
+
8
+ def target
9
+ return display JSON.pretty_generate({:target => target_url}) if @options[:json]
10
+ banner "[#{target_url}]"
11
+ end
12
+
13
+ def targets
14
+ targets = VMC::Cli::Config.targets
15
+ return display JSON.pretty_generate(targets) if @options[:json]
16
+ return display 'None specified' if targets.empty?
17
+ targets_table = table do |t|
18
+ t.headings = 'Target', 'Authorization'
19
+ targets.each { |target, token| t << [target, token] }
20
+ end
21
+ display "\n"
22
+ display targets_table
23
+ end
24
+
25
+ alias :tokens :targets
26
+
27
+ def set_target(target_url)
28
+ target_url = "http://#{target_url}" unless /^https?/ =~ target_url
29
+ target_url = target_url.gsub(/\/+$/, '')
30
+ client = VMC::Client.new(target_url)
31
+ unless client.target_valid?
32
+ if prompt_ok
33
+ display "Host is not valid: '#{target_url}'".red
34
+ show_response = ask "Would you like see the response [yN]? "
35
+ display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response.upcase == 'Y'
36
+ end
37
+ exit(false)
38
+ else
39
+ VMC::Cli::Config.store_target(target_url)
40
+ say "Succesfully targeted to [#{target_url}]".green
41
+ end
42
+ end
43
+
44
+ def info
45
+ info = client_info
46
+ return display JSON.pretty_generate(info) if @options[:json]
47
+
48
+ display "\n#{info[:description]}"
49
+ display "For support visit #{info[:support]}"
50
+ display ""
51
+ display "Target: #{target_url} (v#{info[:version]})"
52
+ display "Client: v#{VMC::Cli::VERSION}"
53
+ if info[:user]
54
+ display ''
55
+ display "User: #{info[:user]}"
56
+ end
57
+ if usage = info[:usage] and limits = info[:limits]
58
+ tmem = pretty_size(limits[:memory]*1024*1024)
59
+ mem = pretty_size(usage[:memory]*1024*1024)
60
+ tser = limits[:services]
61
+ ser = usage[:services]
62
+ tapps = limits[:apps] || 0
63
+ apps = usage[:apps] || 0
64
+ display "Usage: Memory (#{mem} of #{tmem} total)"
65
+ display " Services (#{ser} of #{tser} total)"
66
+ display " Apps (#{apps} of #{tapps} total)" if limits[:apps]
67
+ end
68
+ end
69
+
70
+ def runtimes
71
+ raise VMC::Client::AuthError unless client.logged_in?
72
+ return display JSON.pretty_generate(runtimes_info) if @options[:json]
73
+ return display "No Runtimes" if runtimes_info.empty?
74
+ rtable = table do |t|
75
+ t.headings = 'Name', 'Description', 'Version'
76
+ runtimes_info.each_value { |rt| t << [rt[:name], rt[:description], rt[:version]] }
77
+ end
78
+ display "\n"
79
+ display rtable
80
+ end
81
+
82
+ def frameworks
83
+ raise VMC::Client::AuthError unless client.logged_in?
84
+ return display JSON.pretty_generate(frameworks_info) if @options[:json]
85
+ return display "No Frameworks" if frameworks_info.empty?
86
+ rtable = table do |t|
87
+ t.headings = ['Name']
88
+ frameworks_info.each { |f| t << f }
89
+ end
90
+ display "\n"
91
+ display rtable
92
+ end
93
+
94
+ def aliases
95
+ aliases = VMC::Cli::Config.aliases
96
+ return display JSON.pretty_generate(aliases) if @options[:json]
97
+ return display "No Aliases" if aliases.empty?
98
+ atable = table do |t|
99
+ t.headings = 'Alias', 'Command'
100
+ aliases.each { |k,v| t << [k, v] }
101
+ end
102
+ display "\n"
103
+ display atable
104
+ end
105
+
106
+ def alias(k, v=nil)
107
+ k,v = k.split('=') unless v
108
+ aliases = VMC::Cli::Config.aliases
109
+ aliases[k] = v
110
+ VMC::Cli::Config.store_aliases(aliases)
111
+ display "Successfully aliased '#{k}' to '#{v}'".green
112
+ end
113
+
114
+ def unalias(key)
115
+ aliases = VMC::Cli::Config.aliases
116
+ if aliases.has_key?(key)
117
+ aliases.delete(key)
118
+ VMC::Cli::Config.store_aliases(aliases)
119
+ display "Successfully unaliased '#{key}'".green
120
+ else
121
+ display "Unknown alias '#{key}'".red
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
@@ -0,0 +1,86 @@
1
+ module VMC::Cli::Command
2
+
3
+ class Services < Base
4
+ include VMC::Cli::ServicesHelper
5
+
6
+ def services
7
+ ss = client.services_info
8
+ ps = client.services
9
+ ps.sort! {|a, b| a[:name] <=> b[:name] }
10
+
11
+ if @options[:json]
12
+ services = { :system => ss, :provisioned => ps }
13
+ return display JSON.pretty_generate(services)
14
+ end
15
+ display_system_services(ss)
16
+ display_provisioned_services(ps)
17
+ end
18
+
19
+ def create_service(service=nil, name=nil, appname=nil)
20
+ unless no_prompt || service
21
+ services = client.services_info
22
+ err 'No services available to provision' if services.empty?
23
+ choose do |menu|
24
+ menu.prompt = 'Please select one you wish to provision: '
25
+ menu.select_by = :index_or_name
26
+ services.each do |service_type, value|
27
+ value.each do |vendor, version|
28
+ menu.choice(vendor.to_s) { service = vendor.to_s }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ name = @options[:name] unless name
34
+ unless name
35
+ name = random_service_name(service)
36
+ picked_name = true
37
+ end
38
+ create_service_banner(service, name, picked_name)
39
+ appname = @options[:bind] unless appname
40
+ bind_service_banner(name, appname) if appname
41
+ end
42
+
43
+ def delete_service(service=nil)
44
+ unless no_prompt || service
45
+ user_services = client.services
46
+ err 'No services available to delete' if user_services.empty?
47
+ choose do |menu|
48
+ menu.prompt = 'Please select one you wish to delete: '
49
+ menu.select_by = :index_or_name
50
+ user_services.each do |s|
51
+ menu.choice(s[:name]) { service = s[:name] }
52
+ end
53
+ end
54
+ end
55
+ err "Service name required." unless service
56
+ display "Deleting service [#{service}]: ", false
57
+ client.delete_service(service)
58
+ display 'OK'.green
59
+ end
60
+
61
+ def bind_service(service, appname)
62
+ bind_service_banner(service, appname)
63
+ end
64
+
65
+ def unbind_service(service, appname)
66
+ unbind_service_banner(service, appname)
67
+ end
68
+
69
+ def clone_services(src_app, dest_app)
70
+ begin
71
+ src = client.app_info(src_app)
72
+ dest = client.app_info(dest_app)
73
+ rescue
74
+ end
75
+
76
+ err "Application '#{src_app}' does not exist" unless src
77
+ err "Application '#{dest_app}' does not exist" unless dest
78
+
79
+ services = src[:services]
80
+ err 'No services to clone' unless services && !services.empty?
81
+ services.each { |service| bind_service_banner(service, dest_app, false) }
82
+ check_app_for_restart(dest_app)
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,60 @@
1
+ module VMC::Cli::Command
2
+
3
+ class User < Base
4
+
5
+ def info
6
+ info = client_info
7
+ username = info[:user] || 'N/A'
8
+ return display JSON.pretty_generate([username]) if @options[:json]
9
+ display "\n[#{username}]"
10
+ end
11
+
12
+ def login(email=nil)
13
+ email = @options[:email] unless email
14
+ password = @options[:password]
15
+ tries ||= 0
16
+ email = ask("Email: ") unless no_prompt || email
17
+ password = ask("Password: ") {|q| q.echo = '*'} unless no_prompt || password
18
+ err "Need a valid email" unless email
19
+ err "Need a password" unless password
20
+ login_and_save_token(email, password)
21
+ say "Successfully logged into [#{target_url}]".green
22
+ rescue VMC::Client::TargetError
23
+ display "Problem with login, invalid account or password.".red
24
+ retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
25
+ exit 1
26
+ rescue => e
27
+ display "Problem with login, #{e}, try again or register for an account.".red
28
+ exit 1
29
+ end
30
+
31
+ def logout
32
+ VMC::Cli::Config.remove_token_file
33
+ say "Successfully logged out of [#{target_url}]".green
34
+ end
35
+
36
+ def change_password(password=nil)
37
+ info = client_info
38
+ email = info[:user]
39
+ err "Need to be logged in to change password." unless email
40
+ say "Changing password for '#{email}'\n"
41
+ unless no_prompt
42
+ password = ask("New Password: ") {|q| q.echo = '*'}
43
+ password2 = ask("Verify Password: ") {|q| q.echo = '*'}
44
+ err "Passwords did not match, try again" if password != password2
45
+ end
46
+ err "Password required" unless password
47
+ client.change_password(password)
48
+ say "\nSuccessfully changed password".green
49
+ end
50
+
51
+ private
52
+
53
+ def login_and_save_token(email, password)
54
+ token = client.login(email, password)
55
+ VMC::Cli::Config.store_token(token)
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,110 @@
1
+ require "yaml"
2
+ require 'fileutils'
3
+
4
+ require 'rubygems'
5
+ require 'json/pure'
6
+
7
+ module VMC::Cli
8
+ class Config
9
+
10
+ DEFAULT_TARGET = 'aws.af.cm'
11
+ DEFAULT_SUGGEST = 'af.cm'
12
+
13
+ TARGET_FILE = '~/.vmc_target'
14
+ TOKEN_FILE = '~/.vmc_token'
15
+ INSTANCES_FILE = '~/.vmc_instances'
16
+ ALIASES_FILE = '~/.vmc_aliases'
17
+
18
+ class << self
19
+ attr_accessor :colorize
20
+ attr_accessor :output
21
+ attr_accessor :trace
22
+ attr_accessor :nozip
23
+ attr_reader :suggest_url
24
+
25
+ def target_url
26
+ return @target_url if @target_url
27
+ target_file = File.expand_path(TARGET_FILE)
28
+ if File.exists? target_file
29
+ @target_url = File.read(target_file).strip!
30
+ ha = @target_url.split('.')
31
+ ha.shift
32
+ @suggest_url = ha.join('.')
33
+ @suggest_url = DEFAULT_SUGGEST if @suggest_url.empty?
34
+ else
35
+ @target_url = DEFAULT_TARGET
36
+ @suggest_url = DEFAULT_SUGGEST
37
+ end
38
+ @target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
39
+ @target_url = @target_url.gsub(/\/+$/, '')
40
+ @target_url
41
+ end
42
+
43
+ def store_target(target_host)
44
+ target_file = File.expand_path(TARGET_FILE)
45
+ File.open(target_file, 'w+') { |f| f.puts target_host }
46
+ FileUtils.chmod 0600, target_file
47
+ end
48
+
49
+ def all_tokens
50
+ token_file = File.expand_path(TOKEN_FILE)
51
+ return nil unless File.exists? token_file
52
+ contents = File.read(token_file).strip
53
+ JSON.parse(contents)
54
+ end
55
+
56
+ alias :targets :all_tokens
57
+
58
+ def auth_token
59
+ return @token if @token
60
+ tokens = all_tokens
61
+ @token = tokens[target_url] if tokens
62
+ end
63
+
64
+ def remove_token_file
65
+ FileUtils.rm_f(File.expand_path(TOKEN_FILE))
66
+ end
67
+
68
+ def store_token(token)
69
+ tokens = all_tokens || {}
70
+ tokens[target_url] = token
71
+ token_file = File.expand_path(TOKEN_FILE)
72
+ File.open(token_file, 'w+') { |f| f.write(tokens.to_json) }
73
+ FileUtils.chmod 0600, token_file
74
+ end
75
+
76
+ def instances
77
+ instances_file = File.expand_path(INSTANCES_FILE)
78
+ return nil unless File.exists? instances_file
79
+ contents = File.read(instances_file).strip
80
+ JSON.parse(contents)
81
+ end
82
+
83
+ def store_instances(instances)
84
+ instances_file = File.expand_path(INSTANCES_FILE)
85
+ File.open(instances_file, 'w') { |f| f.write(instances.to_json) }
86
+ end
87
+
88
+ def aliases
89
+ aliases_file = File.expand_path(ALIASES_FILE)
90
+ # bacward compatible
91
+ unless File.exists? aliases_file
92
+ old_aliases_file = File.expand_path('~/.vmc-aliases')
93
+ FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
94
+ end
95
+ aliases = YAML.load_file(aliases_file) rescue {}
96
+ end
97
+
98
+ def store_aliases(aliases)
99
+ aliases_file = File.expand_path(ALIASES_FILE)
100
+ File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
101
+ end
102
+
103
+ end
104
+
105
+ def initialize(work_dir = Dir.pwd)
106
+ @work_dir = work_dir
107
+ end
108
+
109
+ end
110
+ end