jdc 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,228 @@
1
+ require 'rubygems'
2
+ require 'interact'
3
+ require 'terminal-table/import'
4
+
5
+ module JDC::Cli
6
+
7
+ module Command
8
+
9
+ class Base
10
+ include Interactive
11
+
12
+ attr_reader :no_prompt, :prompt_ok
13
+
14
+ MANIFEST = "manifest.yml"
15
+
16
+ def initialize(options={})
17
+ @options = options.dup
18
+ @no_prompt = @options[:noprompts]
19
+ @prompt_ok = !no_prompt
20
+
21
+ # Suppress colorize on Windows systems for now.
22
+ if WINDOWS
23
+ JDC::Cli::Config.colorize = false
24
+ end
25
+
26
+ @path = @options[:path] || '.'
27
+
28
+ load_manifest manifest_file if manifest_file
29
+ end
30
+
31
+ def manifest_file
32
+ return @options[:manifest] if @options[:manifest]
33
+ return @manifest_file if @manifest_file
34
+
35
+ where = File.expand_path(@path)
36
+ while true
37
+ if File.exists?(File.join(where, MANIFEST))
38
+ @manifest_file = File.join(where, MANIFEST)
39
+ break
40
+ elsif File.basename(where) == "/"
41
+ @manifest_file = nil
42
+ break
43
+ else
44
+ where = File.expand_path("../", where)
45
+ end
46
+ end
47
+
48
+ @manifest_file
49
+ end
50
+
51
+ def load_manifest_structure(file)
52
+ manifest = YAML.load_file file
53
+
54
+ Array(manifest["inherit"]).each do |p|
55
+ manifest = merge_parent(manifest, p)
56
+ end
57
+
58
+ if apps = manifest["applications"]
59
+ apps.each do |k, v|
60
+ abs = File.expand_path(k, file)
61
+ if Dir.pwd.start_with? abs
62
+ manifest = merge_manifest(manifest, v)
63
+ end
64
+ end
65
+ end
66
+
67
+ manifest
68
+ end
69
+
70
+ def resolve_manifest(manifest)
71
+ if apps = manifest["applications"]
72
+ apps.each_value do |v|
73
+ resolve_lexically(v, [manifest])
74
+ end
75
+ end
76
+
77
+ resolve_lexically(manifest, [manifest])
78
+ end
79
+
80
+ def load_manifest(file)
81
+ @manifest = load_manifest_structure(file)
82
+ resolve_manifest(@manifest)
83
+ end
84
+
85
+ def merge_parent(child, path)
86
+ file = File.expand_path("../" + path, manifest_file)
87
+ merge_manifest(child, load_manifest_structure(file))
88
+ end
89
+
90
+ def merge_manifest(child, parent)
91
+ merge = proc do |_, old, new|
92
+ if new.is_a?(Hash) and old.is_a?(Hash)
93
+ old.merge(new, &merge)
94
+ else
95
+ new
96
+ end
97
+ end
98
+
99
+ parent.merge(child, &merge)
100
+ end
101
+
102
+ def resolve_lexically(val, ctx = [@manifest])
103
+ case val
104
+ when Hash
105
+ val.each_value do |v|
106
+ resolve_lexically(v, [val] + ctx)
107
+ end
108
+ when Array
109
+ val.each do |v|
110
+ resolve_lexically(v, ctx)
111
+ end
112
+ when String
113
+ val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
114
+ resolve_symbol($1, ctx)
115
+ end
116
+ end
117
+
118
+ nil
119
+ end
120
+
121
+ def resolve_symbol(sym, ctx)
122
+ case sym
123
+ when "target-base"
124
+ target_base(ctx)
125
+
126
+ when "target-url"
127
+ target_url(ctx)
128
+
129
+ when "random-word"
130
+ "%04x" % [rand(0x0100000)]
131
+
132
+ else
133
+ found = find_symbol(sym, ctx)
134
+
135
+ if found
136
+ resolve_lexically(found, ctx)
137
+ found
138
+ else
139
+ err(sym, "Unknown symbol in manifest: ")
140
+ end
141
+ end
142
+ end
143
+
144
+ def find_symbol(sym, ctx)
145
+ ctx.each do |h|
146
+ if val = resolve_in(h, sym)
147
+ return val
148
+ end
149
+ end
150
+
151
+ nil
152
+ end
153
+
154
+ def resolve_in(hash, *where)
155
+ find_in_hash(hash, ["properties"] + where) ||
156
+ find_in_hash(hash, ["applications", @application] + where) ||
157
+ find_in_hash(hash, where)
158
+ end
159
+
160
+ def manifest(*where)
161
+ resolve_in(@manifest, *where)
162
+ end
163
+
164
+ def find_in_hash(hash, where)
165
+ what = hash
166
+ where.each do |x|
167
+ return nil unless what.is_a?(Hash)
168
+ what = what[x]
169
+ end
170
+
171
+ what
172
+ end
173
+
174
+ def target_url(ctx = [])
175
+ find_symbol("target", ctx) ||
176
+ (@client && @client.target) ||
177
+ JDC::Cli::Config.target_url
178
+ end
179
+
180
+ def target_base(ctx = [])
181
+ JDC::Cli::Config.base_of(find_symbol("target", ctx) || target_url)
182
+ end
183
+
184
+ # Inject a client to help in testing.
185
+ def client(cli=nil)
186
+ @client ||= cli
187
+ return @client if @client
188
+ @client = JDC::Client.new(target_url)
189
+ @client.trace = JDC::Cli::Config.trace if JDC::Cli::Config.trace
190
+ @client.proxy_for @options[:proxy] if @options[:proxy]
191
+ @client
192
+ end
193
+
194
+ def client_info
195
+ @client_info ||= client.info
196
+ end
197
+
198
+ #need to delete
199
+ def auth_token
200
+ @auth_token = JDC::Cli::Config.auth_token(@options[:token_file])
201
+ end
202
+
203
+ def runtimes_info
204
+ return @runtimes if @runtimes
205
+ info = client_info
206
+ @runtimes = {}
207
+ if info[:frameworks]
208
+ info[:frameworks].each_value do |f|
209
+ next unless f[:runtimes]
210
+ f[:runtimes].each { |r| @runtimes[r[:name]] = r}
211
+ end
212
+ end
213
+ @runtimes
214
+ end
215
+
216
+ def frameworks_info
217
+ return @frameworks if @frameworks
218
+ info = client_info
219
+ @frameworks = []
220
+ if info[:frameworks]
221
+ info[:frameworks].each_value { |f| @frameworks << [f[:name]] }
222
+ end
223
+ @frameworks
224
+ end
225
+ end
226
+ end
227
+ end
228
+
@@ -0,0 +1,56 @@
1
+ module JDC::Cli::Command
2
+ class Manifest < Base
3
+ include JDC::Cli::ManifestHelper
4
+
5
+ def initialize(options)
6
+ super
7
+
8
+ # don't resolve any of the manifest template stuff
9
+ if manifest_file
10
+ @manifest = load_manifest_structure manifest_file
11
+ else
12
+ @manifest = {}
13
+ end
14
+ end
15
+
16
+ def edit
17
+ build_manifest
18
+ save_manifest
19
+ end
20
+
21
+ def extend(which)
22
+ parent = load_manifest_structure which
23
+ @manifest = load_manifest_structure which
24
+
25
+ build_manifest
26
+
27
+ simplify(@manifest, parent)
28
+
29
+ @manifest["inherit"] ||= []
30
+ @manifest["inherit"] << which
31
+
32
+ save_manifest(ask("Save where?"))
33
+ end
34
+
35
+ private
36
+
37
+ def simplify(child, parent)
38
+ return unless child.is_a?(Hash) and parent.is_a?(Hash)
39
+
40
+ child.reject! do |k, v|
41
+ if v == parent[k]
42
+ puts "rejecting #{k}"
43
+ true
44
+ else
45
+ simplify(v, parent[k])
46
+ false
47
+ end
48
+ end
49
+ end
50
+
51
+ def build_manifest
52
+ @application = ask("Configure for which application?", :default => ".")
53
+ interact true
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,115 @@
1
+ module JDC::Cli::Command
2
+ class Micro < Base
3
+
4
+ def initialize(args)
5
+ super(args)
6
+ end
7
+
8
+ def offline(mode)
9
+ command('offline')
10
+ end
11
+
12
+ def online(mode)
13
+ command('online')
14
+ end
15
+
16
+ def status(mode)
17
+ command('status')
18
+ end
19
+
20
+ def command(cmd)
21
+ config = build_config
22
+ switcher(config).send(cmd)
23
+ store_config(config)
24
+ end
25
+
26
+ def switcher(config)
27
+ case Micro.platform
28
+ when :darwin
29
+ switcher = JDC::Micro::Switcher::Darwin.new(config)
30
+ when :linux
31
+ switcher = JDC::Micro::Switcher::Linux.new(config)
32
+ when :windows
33
+ switcher = JDC::Micro::Switcher::Windows.new(config)
34
+ when :dummy # for testing only
35
+ switcher = JDC::Micro::Switcher::Dummy.new(config)
36
+ else
37
+ err "unsupported platform: #{Micro.platform}"
38
+ end
39
+ end
40
+
41
+ # Returns the configuration needed to run the micro related subcommands.
42
+ # First loads saved config from file (if there is any), then overrides
43
+ # loaded values with command line arguments, and finally tries to guess
44
+ # in case neither was used:
45
+ # vmx location of micro.vmx file
46
+ # vmrun location of vmrun command
47
+ # password password for vcap user (in the guest vm)
48
+ # platform current platform
49
+ def build_config
50
+ conf = JDC::Cli::Config.micro # returns {} if there isn't a saved config
51
+
52
+ override(conf, 'vmx', true) do
53
+ locate_vmx(Micro.platform)
54
+ end
55
+
56
+ override(conf, 'vmrun', true) do
57
+ JDC::Micro::VMrun.locate(Micro.platform)
58
+ end
59
+
60
+ override(conf, 'password') do
61
+ @password = ask("Please enter your JingDong Cloud VM password (vcap user) password", :echo => "*")
62
+ end
63
+
64
+ conf['platform'] = Micro.platform
65
+
66
+ conf
67
+ end
68
+
69
+ # Save the cleartext password if --save is supplied.
70
+ # Note: it is due to vix we have to use a cleartext password :(
71
+ # Only if --password is used and not --save is the password deleted from the
72
+ # config file before it is stored to disk.
73
+ def store_config(config)
74
+ if @options[:save]
75
+ warn("cleartext password saved in: #{JDC::Cli::Config::MICRO_FILE}")
76
+ elsif @options[:password] || @password
77
+ config.delete('password')
78
+ end
79
+
80
+ JDC::Cli::Config.store_micro(config)
81
+ end
82
+
83
+ # override with command line arguments and yield the block in case the option isn't set
84
+ def override(config, option, escape=false, &blk)
85
+ # override if given on the command line
86
+ if opt = @options[option.to_sym]
87
+ opt = JDC::Micro.escape_path(opt) if escape
88
+ config[option] = opt
89
+ end
90
+ config[option] = yield unless config[option]
91
+ end
92
+
93
+ def locate_vmx(platform)
94
+ paths = YAML.load_file(JDC::Micro.config_file('paths.yml'))
95
+ vmx_paths = paths[platform.to_s]['vmx']
96
+ vmx = JDC::Micro.locate_file('micro.vmx', 'micro', vmx_paths)
97
+ err "Unable to locate micro.vmx, please supply --vmx option" unless vmx
98
+ vmx
99
+ end
100
+
101
+ def self.platform
102
+ case RUBY_PLATFORM
103
+ when /darwin/ # x86_64-darwin11.2.0
104
+ :darwin
105
+ when /linux/ # x86_64-linux
106
+ :linux
107
+ when /mingw|mswin32|cygwin/ # i386-mingw32
108
+ :windows
109
+ else
110
+ RUBY_PLATFORM
111
+ end
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,126 @@
1
+ module JDC::Cli::Command
2
+
3
+ class Misc < Base
4
+ def version
5
+ say "jdc #{JDC::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 = JDC::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 = JDC::Client.new(target_url)
31
+ unless client.target_valid?
32
+ if prompt_ok
33
+ display "Host is not available or is not valid: '#{target_url}'".red
34
+ show_response = ask "Would you like see the response?",
35
+ :default => false
36
+ display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response
37
+ end
38
+ exit(false)
39
+ else
40
+ JDC::Cli::Config.store_target(target_url)
41
+ say "Successfully targeted to [#{target_url}]".green
42
+ end
43
+ end
44
+
45
+ def info
46
+ info = client_info
47
+ return display JSON.pretty_generate(info) if @options[:json]
48
+
49
+ display "\n#{info[:description]}"
50
+ display ""
51
+ display "Target: #{target_url} (v#{info[:version]})"
52
+ display "Client: v#{JDC::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
+ return display JSON.pretty_generate(runtimes_info) if @options[:json]
72
+ return display "No Runtimes" if runtimes_info.empty?
73
+ rtable = table do |t|
74
+ t.headings = 'Name', 'Description', 'Version'
75
+ runtimes_info.each_value { |rt| t << [rt[:name], rt[:description], rt[:version]] }
76
+ end
77
+ display "\n"
78
+ display rtable
79
+ end
80
+
81
+ def frameworks
82
+ return display JSON.pretty_generate(frameworks_info) if @options[:json]
83
+ return display "No Frameworks" if frameworks_info.empty?
84
+ rtable = table do |t|
85
+ t.headings = ['Name']
86
+ frameworks_info.each { |f| t << f }
87
+ end
88
+ display "\n"
89
+ display rtable
90
+ end
91
+
92
+ def aliases
93
+ aliases = JDC::Cli::Config.aliases
94
+ return display JSON.pretty_generate(aliases) if @options[:json]
95
+ return display "No Aliases" if aliases.empty?
96
+ atable = table do |t|
97
+ t.headings = 'Alias', 'Command'
98
+ aliases.each { |k,v| t << [k, v] }
99
+ end
100
+ display "\n"
101
+ display atable
102
+ end
103
+
104
+ def alias(k, v=nil)
105
+ k,v = k.split('=') unless v
106
+ aliases = JDC::Cli::Config.aliases
107
+ aliases[k] = v
108
+ JDC::Cli::Config.store_aliases(aliases)
109
+ display "Successfully aliased '#{k}' to '#{v}'".green
110
+ end
111
+
112
+ def unalias(key)
113
+ aliases = JDC::Cli::Config.aliases
114
+ if aliases.has_key?(key)
115
+ aliases.delete(key)
116
+ JDC::Cli::Config.store_aliases(aliases)
117
+ display "Successfully unaliased '#{key}'".green
118
+ else
119
+ display "Unknown alias '#{key}'".red
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
@@ -0,0 +1,178 @@
1
+ require "uuidtools"
2
+
3
+ module JDC::Cli::Command
4
+
5
+ class Services < Base
6
+ include JDC::Cli::ServicesHelper
7
+ include JDC::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
+ unless no_prompt || service
24
+ services = client.services_info
25
+ err 'No services available to provision' if services.empty?
26
+ service = ask(
27
+ "Which service would you like to provision?",
28
+ { :indexed => true,
29
+ :choices =>
30
+ services.values.collect { |type|
31
+ type.keys.collect(&:to_s)
32
+ }.flatten
33
+ }
34
+ )
35
+ end
36
+ name = @options[:name] unless name
37
+ unless name
38
+ name = random_service_name(service)
39
+ picked_name = true
40
+ end
41
+ create_service_banner(service, name, picked_name)
42
+ appname = @options[:bind] unless appname
43
+ bind_service_banner(name, appname) if appname
44
+ end
45
+
46
+ def delete_service(service=nil)
47
+ unless no_prompt || service
48
+ user_services = client.services
49
+ err 'No services available to delete' if user_services.empty?
50
+ service = ask(
51
+ "Which service would you like to delete?",
52
+ { :indexed => true,
53
+ :choices => user_services.collect { |s| s[:name] }
54
+ }
55
+ )
56
+ end
57
+ err "Service name required." unless service
58
+ display "Deleting service [#{service}]: ", false
59
+ client.delete_service(service)
60
+ display 'OK'.green
61
+ end
62
+
63
+ def bind_service(service, appname)
64
+ bind_service_banner(service, appname)
65
+ end
66
+
67
+ def unbind_service(service, appname)
68
+ unbind_service_banner(service, appname)
69
+ end
70
+
71
+ def clone_services(src_app, dest_app)
72
+ begin
73
+ src = client.app_info(src_app)
74
+ dest = client.app_info(dest_app)
75
+ rescue
76
+ end
77
+
78
+ err "Application '#{src_app}' does not exist" unless src
79
+ err "Application '#{dest_app}' does not exist" unless dest
80
+
81
+ services = src[:services]
82
+ err 'No services to clone' unless services && !services.empty?
83
+ services.each { |service| bind_service_banner(service, dest_app, false) }
84
+ check_app_for_restart(dest_app)
85
+ end
86
+
87
+ def tunnel(service=nil, client_name=nil)
88
+ unless defined? Caldecott
89
+ display "To use `jdc tunnel', you must first install Caldecott:"
90
+ display ""
91
+ display "\tgem install caldecott"
92
+ display ""
93
+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
94
+ display "will provide one. If you're on Windows, try DevKit."
95
+ display ""
96
+ display "This manual step will be removed in the future."
97
+ display ""
98
+ err "Caldecott is not installed."
99
+ end
100
+
101
+ ps = client.services
102
+ err "No services available to tunnel to" if ps.empty?
103
+
104
+ unless service
105
+ choices = ps.collect { |s| s[:name] }.sort
106
+ service = ask(
107
+ "Which service to tunnel to?",
108
+ :choices => choices,
109
+ :indexed => true
110
+ )
111
+ end
112
+
113
+ info = ps.select { |s| s[:name] == service }.first
114
+
115
+ err "Unknown service '#{service}'" unless info
116
+
117
+ port = pick_tunnel_port(@options[:port] || 10000)
118
+
119
+ if not tunnel_pushed?
120
+ display "Deploying tunnel application '#{tunnel_appname}'."
121
+ auth = UUIDTools::UUID.random_create.to_s
122
+ push_caldecott(auth)
123
+ bind_service_banner(service, tunnel_appname, false)
124
+ start_caldecott
125
+ else
126
+ auth = tunnel_auth
127
+ end
128
+
129
+ if not tunnel_healthy?(auth)
130
+ display "Redeploying tunnel application '#{tunnel_appname}'."
131
+
132
+ # We don't expect caldecott not to be running, so take the
133
+ # most aggressive restart method.. delete/re-push
134
+ client.delete_app(tunnel_appname)
135
+ invalidate_tunnel_app_info
136
+
137
+ push_caldecott(auth)
138
+ bind_service_banner(service, tunnel_appname, false)
139
+ start_caldecott
140
+ end
141
+
142
+ if not tunnel_bound?(service)
143
+ bind_service_banner(service, tunnel_appname)
144
+ end
145
+
146
+ conn_info = tunnel_connection_info info[:vendor], service, auth
147
+ display_tunnel_connection_info(conn_info)
148
+ display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
149
+ start_tunnel(port, conn_info, auth)
150
+
151
+ clients = get_clients_for(info[:vendor])
152
+
153
+ if clients.empty?
154
+ client_name ||= "none"
155
+ else
156
+ client_name ||= ask(
157
+ "Which client would you like to start?",
158
+ :choices => ["none"] + clients.keys,
159
+ :indexed => true
160
+ )
161
+ end
162
+
163
+ if client_name == "none"
164
+ wait_for_tunnel_end
165
+ else
166
+ wait_for_tunnel_start(port)
167
+ unless start_local_prog(clients, client_name, conn_info, port)
168
+ err "'#{client_name}' execution failed; is it in your $PATH?"
169
+ end
170
+ end
171
+ end
172
+
173
+ def get_clients_for(type)
174
+ conf = JDC::Cli::Config.clients
175
+ conf[type] || {}
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,14 @@
1
+ module JDC::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
+ end
13
+
14
+ end