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,265 @@
1
+ module JDC::Cli
2
+
3
+ class Framework
4
+
5
+ DEFAULT_FRAMEWORK = "http://b20nine.com/unknown"
6
+ DEFAULT_MEM = '256M'
7
+
8
+ FRAMEWORKS = {
9
+ 'Rails' => ['rails3', { :mem => '256M', :description => 'Rails Application', :console=>true}],
10
+ 'Spring' => ['spring', { :mem => '512M', :description => 'Java SpringSource Spring Application'}],
11
+ 'Grails' => ['grails', { :mem => '512M', :description => 'Java SpringSource Grails Application'}],
12
+ 'Lift' => ['lift', { :mem => '512M', :description => 'Scala Lift Application'}],
13
+ 'JavaWeb' => ['java_web',{ :mem => '512M', :description => 'Java Web Application'}],
14
+ 'Standalone' => ['standalone', { :mem => '64M', :description => 'Standalone Application'}],
15
+ 'Sinatra' => ['sinatra', { :mem => '128M', :description => 'Sinatra Application'}],
16
+ 'Node' => ['node', { :mem => '64M', :description => 'Node.js Application'}],
17
+ 'PHP' => ['php', { :mem => '128M', :description => 'PHP Application'}],
18
+ 'Erlang/OTP Rebar' => ['otp_rebar', { :mem => '64M', :description => 'Erlang/OTP Rebar Application'}],
19
+ 'WSGI' => ['wsgi', { :mem => '64M', :description => 'Python WSGI Application'}],
20
+ 'Django' => ['django', { :mem => '128M', :description => 'Python Django Application'}],
21
+ 'dotNet' => ['dotNet', { :mem => '128M', :description => '.Net Web Application'}],
22
+ 'Rack' => ['rack', { :mem => '128M', :description => 'Rack Application'}],
23
+ 'Play' => ['play', { :mem => '256M', :description => 'Play Framework Application'}]
24
+ }
25
+
26
+ class << self
27
+
28
+ def known_frameworks(available_frameworks)
29
+ frameworks = []
30
+ FRAMEWORKS.each do |key,fw|
31
+ frameworks << key if available_frameworks.include? [fw[0]]
32
+ end
33
+ frameworks
34
+ end
35
+
36
+ def lookup(name)
37
+ return create(*FRAMEWORKS[name])
38
+ end
39
+
40
+ def lookup_by_framework(name)
41
+ FRAMEWORKS.each do |key,fw|
42
+ return create(fw[0],fw[1]) if fw[0] == name
43
+ end
44
+ end
45
+
46
+ def create(name,opts)
47
+ if name == "standalone"
48
+ return StandaloneFramework.new(name, opts)
49
+ else
50
+ return Framework.new(name,opts)
51
+ end
52
+ end
53
+
54
+ def detect(path, available_frameworks)
55
+ if !File.directory? path
56
+ if path.end_with?('.war')
57
+ return detect_framework_from_war path
58
+ elsif path.end_with?('.zip')
59
+ return detect_framework_from_zip path, available_frameworks
60
+ elsif available_frameworks.include?(["standalone"])
61
+ return Framework.lookup('Standalone')
62
+ else
63
+ return nil
64
+ end
65
+ end
66
+ Dir.chdir(path) do
67
+ # Rails
68
+ if File.exist?('config/environment.rb')
69
+ return Framework.lookup('Rails')
70
+
71
+ # Rack
72
+ elsif File.exist?('config.ru') && available_frameworks.include?(["rack"])
73
+ return Framework.lookup('Rack')
74
+
75
+ # Java Web Apps
76
+ elsif Dir.glob('*.war').first
77
+ return detect_framework_from_war(Dir.glob('*.war').first)
78
+
79
+ elsif File.exist?('WEB-INF/web.xml')
80
+ return detect_framework_from_war
81
+
82
+ # Simple Ruby Apps
83
+ elsif !Dir.glob('*.rb').empty?
84
+ matched_file = nil
85
+ Dir.glob('*.rb').each do |fname|
86
+ next if matched_file
87
+ File.open(fname, 'r') do |f|
88
+ str = f.read # This might want to be limited
89
+ matched_file = fname if (str && str.match(/^\s*require[\s\(]*['"]sinatra(\/base)?['"]/))
90
+ end
91
+ end
92
+ if matched_file
93
+ # Sinatra apps
94
+ f = Framework.lookup('Sinatra')
95
+ f.exec = "ruby #{matched_file}"
96
+ return f
97
+ end
98
+
99
+ # PHP
100
+ elsif !Dir.glob('*.php').empty?
101
+ return Framework.lookup('PHP')
102
+
103
+ # Erlang/OTP using Rebar
104
+ elsif !Dir.glob('releases/*/*.rel').empty? && !Dir.glob('releases/*/*.boot').empty?
105
+ return Framework.lookup('Erlang/OTP Rebar')
106
+
107
+ # Python Django
108
+ # XXX: not all django projects keep settings.py in top-level directory
109
+ elsif File.exist?('manage.py') && File.exist?('settings.py')
110
+ return Framework.lookup('Django')
111
+
112
+ # Python
113
+ elsif !Dir.glob('wsgi.py').empty?
114
+ return Framework.lookup('WSGI')
115
+
116
+ # .Net
117
+ elsif !Dir.glob('web.config').empty?
118
+ return Framework.lookup('dotNet')
119
+
120
+ # Node.js
121
+ elsif !Dir.glob('*.js').empty?
122
+ if File.exist?('server.js') || File.exist?('app.js') || File.exist?('index.js') || File.exist?('main.js')
123
+ return Framework.lookup('Node')
124
+ end
125
+
126
+ # Play or Standalone Apps
127
+ elsif Dir.glob('*.zip').first
128
+ zip_file = Dir.glob('*.zip').first
129
+ return detect_framework_from_zip zip_file, available_frameworks
130
+ end
131
+
132
+ # Default to Standalone if no other match was made
133
+ return Framework.lookup('Standalone') if available_frameworks.include?(["standalone"])
134
+ end
135
+ end
136
+
137
+ def detect_framework_from_war(war_file=nil)
138
+ if war_file
139
+ contents = ZipUtil.entry_lines(war_file)
140
+ else
141
+ #assume we are working with current dir
142
+ contents = Dir['**/*'].join("\n")
143
+ end
144
+
145
+ # Spring/Lift Variations
146
+ if contents =~ /WEB-INF\/lib\/grails-web.*\.jar/
147
+ return Framework.lookup('Grails')
148
+ elsif contents =~ /WEB-INF\/lib\/lift-webkit.*\.jar/
149
+ return Framework.lookup('Lift')
150
+ elsif contents =~ /WEB-INF\/classes\/org\/springframework/
151
+ return Framework.lookup('Spring')
152
+ elsif contents =~ /WEB-INF\/lib\/spring-core.*\.jar/
153
+ return Framework.lookup('Spring')
154
+ elsif contents =~ /WEB-INF\/lib\/org\.springframework\.core.*\.jar/
155
+ return Framework.lookup('Spring')
156
+ else
157
+ return Framework.lookup('JavaWeb')
158
+ end
159
+ end
160
+
161
+ def detect_framework_from_zip(zip_file, available_frameworks)
162
+ contents = ZipUtil.entry_lines(zip_file)
163
+ detect_framework_from_zip_contents(contents, available_frameworks)
164
+ end
165
+
166
+ def detect_framework_from_zip_contents(contents, available_frameworks)
167
+ if available_frameworks.include?(["play"]) && contents =~ /lib\/play\..*\.jar/
168
+ return Framework.lookup('Play')
169
+ elsif available_frameworks.include?(["standalone"])
170
+ return Framework.lookup('Standalone')
171
+ end
172
+ end
173
+ end
174
+
175
+ attr_reader :name, :description, :console
176
+ attr_accessor :exec
177
+
178
+ def initialize(framework=nil, opts={})
179
+ @name = framework || DEFAULT_FRAMEWORK
180
+ @memory = opts[:mem] || DEFAULT_MEM
181
+ @description = opts[:description] || 'Unknown Application Type'
182
+ @exec = opts[:exec]
183
+ @console = opts[:console] || false
184
+ end
185
+
186
+ def to_s
187
+ description
188
+ end
189
+
190
+ def require_url?
191
+ true
192
+ end
193
+
194
+ def require_start_command?
195
+ false
196
+ end
197
+
198
+ def prompt_for_runtime?
199
+ false
200
+ end
201
+
202
+ def default_runtime(path)
203
+ nil
204
+ end
205
+
206
+ def memory(runtime=nil)
207
+ @memory
208
+ end
209
+
210
+ alias :mem :memory
211
+ end
212
+
213
+ class StandaloneFramework < Framework
214
+ def require_url?
215
+ false
216
+ end
217
+
218
+ def require_start_command?
219
+ true
220
+ end
221
+
222
+ def prompt_for_runtime?
223
+ true
224
+ end
225
+
226
+ def default_runtime(path)
227
+ if !File.directory? path
228
+ if path =~ /\.(jar|class)$/
229
+ return "java"
230
+ elsif path =~ /\.(rb)$/
231
+ return "ruby18"
232
+ elsif path =~ /\.(zip)$/
233
+ return detect_runtime_from_zip path
234
+ end
235
+ else
236
+ Dir.chdir(path) do
237
+ return "ruby18" if not Dir.glob('**/*.rb').empty?
238
+ if !Dir.glob('**/*.class').empty? || !Dir.glob('**/*.jar').empty?
239
+ return "java"
240
+ elsif Dir.glob('*.zip').first
241
+ zip_file = Dir.glob('*.zip').first
242
+ return detect_runtime_from_zip zip_file
243
+ end
244
+ end
245
+ end
246
+ return nil
247
+ end
248
+
249
+ def memory(runtime=nil)
250
+ default_mem = @memory
251
+ default_mem = '128M' if runtime =~ /\Aruby/ || runtime == "php"
252
+ default_mem = '512M' if runtime == "java" || runtime == "java7"
253
+ default_mem
254
+ end
255
+
256
+ private
257
+ def detect_runtime_from_zip(zip_file)
258
+ contents = ZipUtil.entry_lines(zip_file)
259
+ if contents =~ /\.(jar)$/
260
+ return "java"
261
+ end
262
+ end
263
+ end
264
+
265
+ end
@@ -0,0 +1,302 @@
1
+ require "set"
2
+
3
+ module JDC::Cli::ManifestHelper
4
+ include JDC::Cli::ServicesHelper
5
+
6
+ DEFAULTS = {
7
+ "url" => "${name}.${target-base}",
8
+ "mem" => "128M",
9
+ "instances" => 1
10
+ }
11
+
12
+ MANIFEST = "manifest.yml"
13
+
14
+ YES_SET = Set.new(["y", "Y", "yes", "YES"])
15
+
16
+ # take a block and call it once for each app to push/update.
17
+ # with @application and @app_info set appropriately
18
+ def each_app(panic=true)
19
+ if @manifest and all_apps = @manifest["applications"]
20
+ where = File.expand_path(@path)
21
+ single = false
22
+
23
+ all_apps.each do |path, info|
24
+ app = File.expand_path("../" + path, manifest_file)
25
+ if where.start_with?(app)
26
+ @application = app
27
+ @app_info = info
28
+ yield info["name"]
29
+ single = true
30
+ break
31
+ end
32
+ end
33
+
34
+ unless single
35
+ if where == File.expand_path("../", manifest_file)
36
+ ordered_by_deps(all_apps).each do |path, info|
37
+ app = File.expand_path("../" + path, manifest_file)
38
+ @application = app
39
+ @app_info = info
40
+ yield info["name"]
41
+ end
42
+ else
43
+ err "Path '#{@path}' is not known to manifest '#{manifest_file}'."
44
+ end
45
+ end
46
+ else
47
+ @application = @path
48
+ @app_info = @manifest
49
+ if @app_info
50
+ yield @app_info["name"]
51
+ elsif panic
52
+ err "No applications."
53
+ end
54
+ end
55
+
56
+ nil
57
+ ensure
58
+ @application = nil
59
+ @app_info = nil
60
+ end
61
+
62
+ def interact(many=false)
63
+ @manifest ||= {}
64
+ configure_app(many)
65
+ end
66
+
67
+ def target_manifest
68
+ @options[:manifest] || MANIFEST
69
+ end
70
+
71
+ def save_manifest(save_to = nil)
72
+ save_to ||= target_manifest
73
+
74
+ File.open(save_to, "w") do |f|
75
+ f.write @manifest.to_yaml
76
+ end
77
+
78
+ say "Manifest written to #{save_to}."
79
+ end
80
+
81
+ def configure_app(many=false)
82
+ name = manifest("name") ||
83
+ set(ask("Application Name", :default => manifest("name")), "name")
84
+
85
+
86
+
87
+ if manifest "framework"
88
+ framework = JDC::Cli::Framework.lookup_by_framework manifest("framework","name")
89
+ else
90
+ framework = detect_framework
91
+ set framework.name, "framework", "name"
92
+ set(
93
+ { "mem" => framework.mem,
94
+ "description" => framework.description,
95
+ "exec" => framework.exec
96
+ },
97
+ "framework",
98
+ "info"
99
+ )
100
+ end
101
+
102
+ default_runtime = manifest "runtime"
103
+ if not default_runtime
104
+ default_runtime = framework.default_runtime(@application)
105
+ set(detect_runtime(default_runtime), "runtime") if framework.prompt_for_runtime?
106
+ end
107
+ default_command = manifest "command"
108
+ set ask("Start Command", :default => default_command), "command" if framework.require_start_command?
109
+
110
+ url_template = manifest("url") || DEFAULTS["url"]
111
+ url_resolved = url_template.dup
112
+ resolve_lexically(url_resolved)
113
+
114
+ if !framework.require_url?
115
+ url_resolved = "None"
116
+ end
117
+ url = ask("Application Deployed URL", :default => url_resolved)
118
+
119
+ if url == url_resolved && url != "None"
120
+ url = url_template
121
+ end
122
+
123
+ # common error case is for prompted users to answer y or Y or yes or
124
+ # YES to this ask() resulting in an unintended URL of y. Special
125
+ # case this common error
126
+ url = url_resolved if YES_SET.member? url
127
+
128
+ if(url == "None")
129
+ url = nil
130
+ end
131
+
132
+ set url, "url"
133
+
134
+ default_mem = manifest("mem")
135
+ default_mem = framework.memory(manifest("runtime")) if not default_mem
136
+ set ask(
137
+ "Memory reservation",
138
+ :default =>
139
+ default_mem ||
140
+ DEFAULTS["mem"],
141
+ :choices => ["128M", "256M", "512M", "1G", "2G"]
142
+ ), "mem"
143
+
144
+ set ask(
145
+ "How many instances?",
146
+ :default => manifest("instances") || DEFAULTS["instances"]
147
+ ), "instances"
148
+
149
+ unless manifest "services"
150
+ user_services = client.services
151
+ user_services.sort! {|a, b| a[:name] <=> b[:name] }
152
+
153
+ unless user_services.empty?
154
+ if ask "Bind existing services to '#{name}'?", :default => false
155
+ bind_services(user_services)
156
+ end
157
+ end
158
+
159
+ services = client.services_info
160
+ unless services.empty?
161
+ if ask "Create services to bind to '#{name}'?", :default => false
162
+ create_services(services.values.collect(&:keys).flatten)
163
+ end
164
+ end
165
+ end
166
+
167
+ if many and ask("Configure for another application?", :default => false)
168
+ @application = ask "Application path?"
169
+ configure_app
170
+ end
171
+ end
172
+
173
+ def set(what, *where)
174
+ where.unshift "applications", @application
175
+
176
+ which = @manifest
177
+ where.each_with_index do |k, i|
178
+ if i + 1 == where.size
179
+ which[k] = what
180
+ else
181
+ which = (which[k] ||= {})
182
+ end
183
+ end
184
+
185
+ what
186
+ end
187
+
188
+ # Detect the appropriate framework.
189
+ def detect_framework(prompt_ok = true)
190
+ framework = JDC::Cli::Framework.detect(@application, frameworks_info)
191
+ framework_correct = ask("Detected a #{framework}, is this correct?", :default => true) if prompt_ok && framework
192
+ if prompt_ok && (framework.nil? || !framework_correct)
193
+ display "#{"[WARNING]".yellow} Can't determine the Application Type." unless framework
194
+ framework = nil if !framework_correct
195
+ framework = JDC::Cli::Framework.lookup(
196
+ ask(
197
+ "Select Application Type",
198
+ :indexed => true,
199
+ :default => framework,
200
+ :choices => JDC::Cli::Framework.known_frameworks(frameworks_info)
201
+ )
202
+ )
203
+ display "Selected #{framework}"
204
+ end
205
+
206
+ framework
207
+ end
208
+
209
+ # Detect the appropriate runtime.
210
+ def detect_runtime(default, prompt_ok=true)
211
+ runtime = nil
212
+ runtime_keys=[]
213
+ runtimes_info.keys.each {|runtime_key| runtime_keys << runtime_key.dup }
214
+ runtime_keys.sort!
215
+ if prompt_ok
216
+ runtime = ask(
217
+ "Select Runtime",
218
+ :indexed => true,
219
+ :default => default,
220
+ :choices => runtime_keys
221
+ )
222
+ display "Selected #{runtime}"
223
+ end
224
+ runtime
225
+ end
226
+
227
+ def bind_services(user_services, chosen = 0)
228
+ svcname = ask(
229
+ "Which one?",
230
+ :indexed => true,
231
+ :choices => user_services.collect { |p| p[:name] })
232
+
233
+ svc = user_services.find { |p| p[:name] == svcname }
234
+
235
+ set svc[:vendor], "services", svcname, "type"
236
+
237
+ if chosen + 1 < user_services.size && ask("Bind another?", :default => false)
238
+ bind_services(user_services, chosen + 1)
239
+ end
240
+ end
241
+
242
+ def create_services(services)
243
+ svcs = services.collect(&:to_s).sort!
244
+
245
+ configure_service(
246
+ ask(
247
+ "What kind of service?",
248
+ :indexed => true,
249
+ :choices => svcs
250
+ )
251
+ )
252
+
253
+ if ask "Create another?", :default => false
254
+ create_services(services)
255
+ end
256
+ end
257
+
258
+ def configure_service(vendor)
259
+ default_name = random_service_name(vendor)
260
+ name = ask "Specify the name of the service", :default => default_name
261
+
262
+ set vendor, "services", name, "type"
263
+ end
264
+
265
+ private
266
+ def ordered_by_deps(apps, abspaths = nil, processed = Set[])
267
+ unless abspaths
268
+ abspaths = {}
269
+ apps.each do |p, i|
270
+ ep = File.expand_path("../" + p, manifest_file)
271
+ abspaths[ep] = i
272
+ end
273
+ end
274
+
275
+ ordered = []
276
+ apps.each do |path, info|
277
+ epath = File.expand_path("../" + path, manifest_file)
278
+
279
+ if deps = info["depends-on"]
280
+ dep_apps = {}
281
+ deps.each do |dep|
282
+ edep = File.expand_path("../" + dep, manifest_file)
283
+
284
+ err "Circular dependency detected." if processed.include? edep
285
+
286
+ dep_apps[dep] = abspaths[edep]
287
+ end
288
+
289
+ processed.add(epath)
290
+
291
+ ordered += ordered_by_deps(dep_apps, abspaths, processed)
292
+ ordered << [path, info]
293
+ elsif not processed.include? epath
294
+ ordered << [path, info]
295
+ processed.add(epath)
296
+ end
297
+ end
298
+
299
+ ordered
300
+ end
301
+
302
+ end