af 0.3.12

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.
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