jdc 0.1.1

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