olympe 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +103 -0
  3. data/Rakefile +101 -0
  4. data/bin/olympe +6 -0
  5. data/caldecott_helper/Gemfile +10 -0
  6. data/caldecott_helper/Gemfile.lock +48 -0
  7. data/caldecott_helper/server.rb +43 -0
  8. data/config/clients.yml +17 -0
  9. data/config/micro/offline.conf +2 -0
  10. data/config/micro/paths.yml +22 -0
  11. data/config/micro/refresh_ip.rb +20 -0
  12. data/lib/cli/commands/admin.rb +80 -0
  13. data/lib/cli/commands/apps.rb +1208 -0
  14. data/lib/cli/commands/base.rb +233 -0
  15. data/lib/cli/commands/manifest.rb +56 -0
  16. data/lib/cli/commands/micro.rb +115 -0
  17. data/lib/cli/commands/misc.rb +140 -0
  18. data/lib/cli/commands/services.rb +217 -0
  19. data/lib/cli/commands/user.rb +65 -0
  20. data/lib/cli/config.rb +170 -0
  21. data/lib/cli/console_helper.rb +163 -0
  22. data/lib/cli/core_ext.rb +122 -0
  23. data/lib/cli/errors.rb +19 -0
  24. data/lib/cli/file_helper.rb +123 -0
  25. data/lib/cli/frameworks.rb +265 -0
  26. data/lib/cli/manifest_helper.rb +316 -0
  27. data/lib/cli/runner.rb +568 -0
  28. data/lib/cli/services_helper.rb +104 -0
  29. data/lib/cli/tunnel_helper.rb +336 -0
  30. data/lib/cli/usage.rb +125 -0
  31. data/lib/cli/version.rb +7 -0
  32. data/lib/cli/zip_util.rb +77 -0
  33. data/lib/cli.rb +48 -0
  34. data/lib/vmc/client.rb +558 -0
  35. data/lib/vmc/const.rb +27 -0
  36. data/lib/vmc/micro/switcher/base.rb +97 -0
  37. data/lib/vmc/micro/switcher/darwin.rb +19 -0
  38. data/lib/vmc/micro/switcher/dummy.rb +15 -0
  39. data/lib/vmc/micro/switcher/linux.rb +16 -0
  40. data/lib/vmc/micro/switcher/windows.rb +31 -0
  41. data/lib/vmc/micro/vmrun.rb +158 -0
  42. data/lib/vmc/micro.rb +56 -0
  43. data/lib/vmc.rb +3 -0
  44. metadata +279 -0
@@ -0,0 +1,217 @@
1
+ require "uuidtools"
2
+
3
+ module VMC::Cli::Command
4
+
5
+ class Services < Base
6
+ include VMC::Cli::ServicesHelper
7
+ include VMC::Cli::TunnelHelper
8
+
9
+ def services
10
+ ss = client.services_info
11
+ ps = client.services
12
+ ps.sort! {|a, b| a[:name] <=> b[:name] }
13
+
14
+ if @options[:json]
15
+ services = { :system => ss, :provisioned => ps }
16
+ return display JSON.pretty_generate(services)
17
+ end
18
+ display_system_services(ss)
19
+ display_provisioned_services(ps)
20
+ end
21
+
22
+ def create_service(service=nil, name=nil, appname=nil)
23
+
24
+ unless no_prompt || service
25
+ services = client.services_info
26
+ err 'No services available to provision' if services.empty?
27
+ service = ask(
28
+ "Which service would you like to provision?",
29
+ { :indexed => true,
30
+ :choices =>
31
+ services.values.collect { |type|
32
+ type.keys.collect(&:to_s)
33
+ }.flatten
34
+ }
35
+ )
36
+ end
37
+ name = @options[:name] unless name
38
+ unless name
39
+ name = random_service_name(service)
40
+ picked_name = true
41
+ end
42
+
43
+ if client.infra_supported?
44
+ unless no_prompt || @options[:infra]
45
+ @options[:infra] = client.infra_name_for_description(
46
+ ask("Select Infrastructure",
47
+ :indexed => true, :choices => client.infra_descriptions))
48
+ end
49
+ end
50
+
51
+ create_service_banner(service, name, picked_name, @options[:infra])
52
+ appname = @options[:bind] unless appname
53
+ bind_service_banner(name, appname) if appname
54
+ end
55
+
56
+ def delete_service(service=nil)
57
+ unless no_prompt || service
58
+ user_services = client.services
59
+ err 'No services available to delete' if user_services.empty?
60
+ service = ask(
61
+ "Which service would you like to delete?",
62
+ { :indexed => true,
63
+ :choices => user_services.collect { |s| s[:name] }
64
+ }
65
+ )
66
+ end
67
+ err "Service name required." unless service
68
+ display "Deleting service [#{service}]: ", false
69
+ client.delete_service(service)
70
+ display 'OK'.green
71
+ end
72
+
73
+ def bind_service(service, appname)
74
+ bind_service_banner(service, appname)
75
+ end
76
+
77
+ def unbind_service(service, appname)
78
+ unbind_service_banner(service, appname)
79
+ end
80
+
81
+ def clone_services(src_app, dest_app)
82
+ begin
83
+ src = client.app_info(src_app)
84
+ dest = client.app_info(dest_app)
85
+ rescue
86
+ end
87
+
88
+ err "Application '#{src_app}' does not exist" unless src
89
+ err "Application '#{dest_app}' does not exist" unless dest
90
+
91
+ services = src[:services]
92
+ err 'No services to clone' unless services && !services.empty?
93
+ services.each { |service| bind_service_banner(service, dest_app, false) }
94
+ check_app_for_restart(dest_app)
95
+ end
96
+
97
+ def export_service(service)
98
+ display "Exporting data from '#{service}': ", false
99
+ export_info = client.export_service(service)
100
+ if export_info
101
+ display 'OK'.green
102
+ puts export_info[:uri]
103
+ else
104
+ err "Export data from '#{service}': failed"
105
+ end
106
+ end
107
+
108
+ def import_service(service,url)
109
+ display "Importing data into '#{service}': ", false
110
+ import_info = client.import_service(service,url)
111
+ if import_info
112
+ display 'OK'.green
113
+ else
114
+ err "Import data into '#{service}' failed"
115
+ end
116
+ end
117
+
118
+ def tunnel(service=nil, client_name=nil)
119
+ unless defined? Caldecott
120
+ display "To use `olympe tunnel', you must first install Caldecott:"
121
+ display ""
122
+ display "\tgem install caldecott"
123
+ display ""
124
+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
125
+ display "will provide one. If you're on Windows, try DevKit."
126
+ display ""
127
+ display "This manual step will be removed in the future."
128
+ display ""
129
+ err "Caldecott is not installed."
130
+ end
131
+
132
+ ps = client.services
133
+ err "No services available to tunnel to" if ps.empty?
134
+
135
+ unless service
136
+ choices = ps.collect { |s| s[:name] }.sort
137
+ service = ask(
138
+ "Which service to tunnel to?",
139
+ :choices => choices,
140
+ :indexed => true
141
+ )
142
+ end
143
+
144
+ info = ps.select { |s| s[:name] == service }.first
145
+
146
+ err "Unknown service '#{service}'" unless info
147
+
148
+ port = pick_tunnel_port(@options[:port] || 10000)
149
+
150
+ raise VMC::Client::AuthError unless client.logged_in?
151
+
152
+ infra_name = nil
153
+ if client.infra_supported?
154
+ infra_name = info[:infra] ? info[:infra][:name] : default_infra
155
+ err "Infra '#{infra_name}' is not valid" unless client.infra_valid?(infra_name)
156
+ end
157
+
158
+ if not tunnel_pushed?(infra_name)
159
+ display "Deploying tunnel application '#{tunnel_appname(infra_name)}'."
160
+ auth = UUIDTools::UUID.random_create.to_s
161
+ push_caldecott(auth,infra_name)
162
+ bind_service_banner(service, tunnel_appname(infra_name), false)
163
+ start_caldecott(infra_name)
164
+ else
165
+ auth = tunnel_auth(infra_name)
166
+ end
167
+
168
+ if not tunnel_healthy?(auth,infra_name)
169
+ display "Redeploying tunnel application '#{tunnel_appname(infra_name)}'."
170
+
171
+ # We don't expect caldecott not to be running, so take the
172
+ # most aggressive restart method.. delete/re-push
173
+ client.delete_app(tunnel_appname(infra_name))
174
+ invalidate_tunnel_app_info(infra_name)
175
+
176
+ push_caldecott(auth,infra_name)
177
+ bind_service_banner(service, tunnel_appname(infra_name), false)
178
+ start_caldecott(infra_name)
179
+ end
180
+
181
+ if not tunnel_bound?(service,infra_name)
182
+ bind_service_banner(service, tunnel_appname(infra_name))
183
+ end
184
+
185
+ conn_info = tunnel_connection_info info[:vendor], service, auth, infra_name
186
+ display_tunnel_connection_info(conn_info)
187
+ display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
188
+ start_tunnel(port, conn_info, auth, infra_name)
189
+
190
+ clients = get_clients_for(info[:vendor])
191
+
192
+ if clients.empty?
193
+ client_name ||= "none"
194
+ else
195
+ client_name ||= ask(
196
+ "Which client would you like to start?",
197
+ :choices => ["none"] + clients.keys,
198
+ :indexed => true
199
+ )
200
+ end
201
+
202
+ if client_name == "none"
203
+ wait_for_tunnel_end
204
+ else
205
+ wait_for_tunnel_start(port)
206
+ unless start_local_prog(clients, client_name, conn_info, port)
207
+ err "'#{client_name}' execution failed; is it in your $PATH?"
208
+ end
209
+ end
210
+ end
211
+
212
+ def get_clients_for(type)
213
+ conf = VMC::Cli::Config.clients
214
+ conf[type] || {}
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,65 @@
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
+
17
+ unless no_prompt
18
+ display "Attempting login to [#{target_url}]" if target_url
19
+ email ||= ask("Email")
20
+ password ||= ask("Password", :echo => "*")
21
+ end
22
+
23
+ err "Need a valid email" unless email
24
+ err "Need a password" unless password
25
+ login_and_save_token(email, password)
26
+ say "Successfully logged into [#{target_url}]".green
27
+ rescue VMC::Client::TargetError
28
+ display "Problem with login, invalid account or password when attempting to login to '#{target_url}'".red
29
+ retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
30
+ exit 1
31
+ rescue => e
32
+ display "Problem with login to '#{target_url}', #{e}, try again or register for an account.".red
33
+ exit 1
34
+ end
35
+
36
+ def logout
37
+ VMC::Cli::Config.remove_token_file
38
+ say "Successfully logged out of [#{target_url}]".green
39
+ end
40
+
41
+ def change_password(password=nil)
42
+ info = client_info
43
+ email = info[:user]
44
+ err "Need to be logged in to change password." unless email
45
+ say "Changing password for '#{email}'\n"
46
+ unless no_prompt
47
+ password = ask "New Password", :echo => "*"
48
+ password2 = ask "Verify Password", :echo => "*"
49
+ err "Passwords did not match, try again" if password != password2
50
+ end
51
+ err "Password required" unless password
52
+ client.change_password(password)
53
+ say "\nSuccessfully changed password".green
54
+ end
55
+
56
+ private
57
+
58
+ def login_and_save_token(email, password)
59
+ token = client.login(email, password)
60
+ VMC::Cli::Config.store_token(token, @options[:token_file])
61
+ end
62
+
63
+ end
64
+
65
+ end
data/lib/cli/config.rb ADDED
@@ -0,0 +1,170 @@
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 = 'api.cloud.olympe-network.com'
11
+
12
+ TARGET_FILE = '~/.olympe_target'
13
+ TOKEN_FILE = '~/.olympe_token'
14
+ INSTANCES_FILE = '~/.olympe_instances'
15
+ ALIASES_FILE = '~/.olympe_aliases'
16
+ CLIENTS_FILE = '~/.olympe_clients'
17
+ MICRO_FILE = '~/.olympe_micro'
18
+
19
+ STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
20
+
21
+ class << self
22
+ attr_accessor :colorize
23
+ attr_accessor :output
24
+ attr_accessor :trace
25
+ attr_accessor :nozip
26
+ attr_accessor :infra
27
+
28
+ def target_url
29
+ return @target_url if @target_url
30
+ target_file = File.expand_path(TARGET_FILE)
31
+ if File.exists? target_file
32
+ @target_url = lock_and_read(target_file).strip
33
+ else
34
+ @target_url = DEFAULT_TARGET
35
+ end
36
+ @target_url = "http://#{@target_url}" unless /^http?/ =~ @target_url
37
+ @target_url = @target_url.gsub(/\/+$/, '')
38
+ @target_url
39
+ end
40
+
41
+ def base_of(url)
42
+ url.sub(/^[^\.]+\./, "")
43
+ end
44
+
45
+ def store_target(target_host)
46
+ target_file = File.expand_path(TARGET_FILE)
47
+ lock_and_write(target_file, target_host)
48
+ end
49
+
50
+ def all_tokens(token_file_path=nil)
51
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
52
+ return nil unless File.exists? token_file
53
+ contents = lock_and_read(token_file).strip
54
+ JSON.parse(contents)
55
+ end
56
+
57
+ alias :targets :all_tokens
58
+
59
+ def auth_token(token_file_path=nil)
60
+ return @token if @token
61
+ tokens = all_tokens(token_file_path)
62
+ @token = tokens[target_url] if tokens
63
+ end
64
+
65
+ def remove_token_file
66
+ FileUtils.rm_f(File.expand_path(TOKEN_FILE))
67
+ end
68
+
69
+ def store_token(token, token_file_path=nil)
70
+ tokens = all_tokens(token_file_path) || {}
71
+ tokens[target_url] = token
72
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
73
+ lock_and_write(token_file, tokens.to_json)
74
+ end
75
+
76
+ def instances
77
+ instances_file = File.expand_path(INSTANCES_FILE)
78
+ return nil unless File.exists? instances_file
79
+ contents = lock_and_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
+ lock_and_write(instances_file, 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
+ def micro
104
+ micro_file = File.expand_path(MICRO_FILE)
105
+ return {} unless File.exists? micro_file
106
+ contents = lock_and_read(micro_file).strip
107
+ JSON.parse(contents)
108
+ end
109
+
110
+ def store_micro(micro)
111
+ micro_file = File.expand_path(MICRO_FILE)
112
+ lock_and_write(micro_file, micro.to_json)
113
+ end
114
+
115
+ def deep_merge(a, b)
116
+ merge = proc do |_, old, new|
117
+ if new.is_a?(Hash) and old.is_a?(Hash)
118
+ old.merge(new, &merge)
119
+ else
120
+ new
121
+ end
122
+ end
123
+
124
+ a.merge(b, &merge)
125
+ end
126
+
127
+ def clients
128
+ return @clients if @clients
129
+
130
+ stock = YAML.load_file(STOCK_CLIENTS)
131
+ clients = File.expand_path CLIENTS_FILE
132
+ if File.exists? clients
133
+ user = YAML.load_file(clients)
134
+ @clients = deep_merge(stock, user)
135
+ else
136
+ @clients = stock
137
+ end
138
+ end
139
+
140
+ def lock_and_read(file)
141
+ File.open(file, File::RDONLY) {|f|
142
+ if defined? JRUBY_VERSION
143
+ f.flock(File::LOCK_SH)
144
+ else
145
+ f.flock(File::LOCK_EX)
146
+ end
147
+ contents = f.read
148
+ f.flock(File::LOCK_UN)
149
+ contents
150
+ }
151
+ end
152
+
153
+ def lock_and_write(file, contents)
154
+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
155
+ f.flock(File::LOCK_EX)
156
+ f.rewind
157
+ f.puts contents
158
+ f.flush
159
+ f.truncate(f.pos)
160
+ f.flock(File::LOCK_UN)
161
+ }
162
+ end
163
+ end
164
+
165
+ def initialize(work_dir = Dir.pwd)
166
+ @work_dir = work_dir
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,163 @@
1
+ require 'net/telnet'
2
+ require 'readline'
3
+
4
+ module VMC::Cli
5
+ module ConsoleHelper
6
+
7
+ def console_connection_info(appname)
8
+ app = client.app_info(appname)
9
+ fw = VMC::Cli::Framework.lookup_by_framework(app[:staging][:model])
10
+ if !fw.console
11
+ err "'#{appname}' is a #{fw.name} application. " +
12
+ "Console access is not supported for #{fw.name} applications."
13
+ end
14
+ instances_info_envelope = client.app_instances(appname)
15
+ instances_info_envelope = {} if instances_info_envelope.is_a?(Array)
16
+
17
+ instances_info = instances_info_envelope[:instances] || []
18
+ err "No running instances for [#{appname}]" if instances_info.empty?
19
+
20
+ entry = instances_info[0]
21
+ if !entry[:console_port]
22
+ begin
23
+ client.app_files(appname, '/app/cf-rails-console')
24
+ err "Console port not provided for [#{appname}]. Try restarting the app."
25
+ rescue VMC::Client::TargetError, VMC::Client::NotFound
26
+ err "Console access not supported for [#{appname}]. " +
27
+ "Please redeploy your app to enable support."
28
+ end
29
+ end
30
+ conn_info = {
31
+ 'hostname' => entry[:console_ip],
32
+ 'port' => entry[:console_port]
33
+ }
34
+ end
35
+
36
+ def start_local_console(port, appname)
37
+ auth_info = console_credentials(appname)
38
+ display "Connecting to '#{appname}' console: ", false
39
+ prompt = console_login(auth_info, port)
40
+ display "OK".green
41
+ display "\n"
42
+ initialize_readline
43
+ run_console prompt
44
+ end
45
+
46
+ def console_login(auth_info, port)
47
+ if !auth_info["username"] || !auth_info["password"]
48
+ err "Unable to verify console credentials."
49
+ end
50
+ @telnet_client = telnet_client(port)
51
+ prompt = nil
52
+ err_msg = "Login attempt timed out."
53
+ 5.times do
54
+ begin
55
+ results = @telnet_client.login("Name"=>auth_info["username"],
56
+ "Password"=>auth_info["password"])
57
+ lines = results.sub("Login: Password: ", "").split("\n")
58
+ last_line = lines.pop
59
+ if last_line =~ /[$%#>] \z/n
60
+ prompt = last_line
61
+ elsif last_line =~ /Login failed/
62
+ err_msg = last_line
63
+ end
64
+ break
65
+ rescue TimeoutError
66
+ sleep 1
67
+ rescue EOFError
68
+ #This may happen if we login right syster app starts
69
+ close_console
70
+ sleep 5
71
+ @telnet_client = telnet_client(port)
72
+ end
73
+ display ".", false
74
+ end
75
+ unless prompt
76
+ close_console
77
+ err err_msg
78
+ end
79
+ prompt
80
+ end
81
+
82
+ def send_console_command(cmd)
83
+ results = @telnet_client.cmd(cmd)
84
+ results.split("\n")
85
+ end
86
+
87
+ def console_credentials(appname)
88
+ content = client.app_files(appname, '/app/cf-rails-console/.consoleaccess', '0')
89
+ YAML.load(content)
90
+ end
91
+
92
+ def close_console
93
+ @telnet_client.close
94
+ end
95
+
96
+ def console_tab_completion_data(cmd)
97
+ begin
98
+ results = @telnet_client.cmd("String"=> cmd + "\t", "Match"=>/\S*\n$/, "Timeout"=>10)
99
+ results.chomp.split(",")
100
+ rescue TimeoutError
101
+ [] #Just return empty results if timeout occurred on tab completion
102
+ end
103
+ end
104
+
105
+ private
106
+ def telnet_client(port)
107
+ Net::Telnet.new({"Port"=>port, "Prompt"=>/[$%#>] \z|Login failed/n, "Timeout"=>30, "FailEOF"=>true})
108
+ end
109
+
110
+ def readline_with_history(prompt)
111
+ line = Readline::readline(prompt)
112
+ return nil if line == nil || line == 'quit' || line == 'exit'
113
+ Readline::HISTORY.push(line) if not line =~ /^\s*$/ and Readline::HISTORY.to_a[-1] != line
114
+ line
115
+ end
116
+
117
+ def run_console(prompt)
118
+ prev = trap("INT") { |x| exit_console; prev.call(x); exit }
119
+ prev = trap("TERM") { |x| exit_console; prev.call(x); exit }
120
+ loop do
121
+ cmd = readline_with_history(prompt)
122
+ if(cmd == nil)
123
+ exit_console
124
+ break
125
+ end
126
+ prompt = send_console_command_display_results(cmd, prompt)
127
+ end
128
+ end
129
+
130
+ def exit_console
131
+ #TimeoutError expected, as exit doesn't return anything
132
+ @telnet_client.cmd("String"=>"exit","Timeout"=>1) rescue TimeoutError
133
+ close_console
134
+ end
135
+
136
+ def send_console_command_display_results(cmd, prompt)
137
+ begin
138
+ lines = send_console_command cmd
139
+ #Assumes the last line is a prompt
140
+ prompt = lines.pop
141
+ lines.each {|line| display line if line != cmd}
142
+ rescue TimeoutError
143
+ display "Timed out sending command to server.".red
144
+ rescue EOFError
145
+ err "The console connection has been terminated. Perhaps the app was stopped or deleted?"
146
+ end
147
+ prompt
148
+ end
149
+
150
+ def initialize_readline
151
+ if Readline.respond_to?("basic_word_break_characters=")
152
+ Readline.basic_word_break_characters= " \t\n`><=;|&{("
153
+ end
154
+ Readline.completion_append_character = nil
155
+ #Assumes that sending a String ending with tab will return a non-empty
156
+ #String of comma-separated completion options, terminated by a new line
157
+ #For example, "app.\t" might result in "to_s,nil?,etc\n"
158
+ Readline.completion_proc = proc {|s|
159
+ console_tab_completion_data s
160
+ }
161
+ end
162
+ end
163
+ end