dister 0.1.2 → 0.1.3
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.
- data/.gitignore +2 -0
- data/.yardopts +7 -0
- data/Changelog +7 -2
- data/README.rdoc +40 -21
- data/Rakefile +5 -11
- data/dister.gemspec +4 -2
- data/lib/dister.rb +2 -1
- data/lib/dister/cli.rb +17 -9
- data/lib/dister/core.rb +81 -51
- data/lib/dister/db_adapter.rb +18 -7
- data/lib/dister/downloader.rb +8 -2
- data/lib/dister/options.rb +11 -1
- data/lib/dister/utils.rb +8 -1
- data/lib/dister/version.rb +1 -1
- data/test/cli_test.rb +2 -3
- data/test/core_test.rb +16 -0
- data/test/test_helper.rb +1 -0
- data/test/utils_test.rb +12 -0
- metadata +69 -41
data/.gitignore
CHANGED
data/.yardopts
ADDED
data/Changelog
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
Thu Apr 12 10:32:24 CEST 2012 Flavio Castelli <flavio@castelli.name>
|
2
|
+
|
3
|
+
* release version 0.1.3:
|
4
|
+
- add support for SLE11 SP2 base systems.
|
5
|
+
|
1
6
|
Fri May 06 16:55:23 CEST 2011 Flavio Castelli <flavio@castelli.name>
|
2
7
|
|
3
8
|
* Fixed a method missing error.
|
@@ -6,8 +11,8 @@ Fri May 06 16:46:10 CEST 2011 Flavio Castelli <flavio@castelli.name>
|
|
6
11
|
|
7
12
|
* appliance building: ask the user what to do when there's already an
|
8
13
|
image with the same version.
|
9
|
-
* release version
|
14
|
+
* release version 0.1.1
|
10
15
|
|
11
16
|
Wed May 04 16:00:00 CEST 2011 Flavio Castelli <flavio@castelli.name>
|
12
17
|
|
13
|
-
* released first version
|
18
|
+
* released first version 0.0.1
|
data/README.rdoc
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
= Dister: an Heroku like solution for SUSE Studio
|
2
2
|
|
3
3
|
{SUSE Studio}[http://susestudio.com] is an online Linux image creation tool.
|
4
|
-
Creating an appliance to run your
|
5
|
-
there are extra repositories to add, gem dependencies to satisfy, a database to
|
6
|
-
setup,
|
4
|
+
Creating an appliance to run your Rails application can be quite annoying:
|
5
|
+
there are extra repositories to add, gem dependencies to satisfy, a database to
|
6
|
+
setup, Apache and Passenger to configure, overlay files to add and so on.
|
7
7
|
|
8
8
|
You can save some time cloning {this}[http://susegallery.com/a/CZ0T0D/rails-in-a-box]
|
9
9
|
appliance shared on {SUSE Gallery}[http://susegallery.com/], but some efforts
|
10
10
|
are still required.
|
11
11
|
|
12
|
-
|
12
|
+
Currently the easiest solution to deploy a Rails application in the cloud is
|
13
13
|
{Heroku}[http://heroku.com/].
|
14
14
|
|
15
|
-
Dister is a command line tool similar to the one used by Heroku. Within a few
|
16
|
-
steps you can create a SUSE Studio appliance running your
|
15
|
+
Dister is a command line tool similar to the one used by Heroku. Within a few
|
16
|
+
steps you can create a SUSE Studio appliance running your Rails application,
|
17
17
|
download it and run into your private or public cloud.
|
18
18
|
|
19
|
-
SUSE Studio
|
19
|
+
SUSE Studio currently supports the following appliance formats:
|
20
20
|
- oem: it can be run inside KVM or it can be installed to a hard disk/usb pen.
|
21
21
|
- iso and preload iso: you can create a live dvd or an installation dvd.:
|
22
22
|
- vmx and ovf: use these formats if you want to run your appliance inside of
|
@@ -25,12 +25,15 @@ SUSE Studio currenlty supports the following appliance formats:
|
|
25
25
|
hypervisor.
|
26
26
|
- ec2: jumping into Amazon's cloud has never been so easy.
|
27
27
|
|
28
|
-
More formats are coming. Checkout {SUSE Studio}[http://susestudio.com] for
|
28
|
+
More formats are coming. Checkout {SUSE Studio}[http://susestudio.com] for
|
29
29
|
more details.
|
30
30
|
|
31
|
+
Currently only MRI Ruby 1.8 is supported, but support for 1.9 and REE is
|
32
|
+
forthcoming.
|
33
|
+
|
31
34
|
== Common workflow
|
32
35
|
|
33
|
-
This section will show the common workflow required to create a SUSE Studio
|
36
|
+
This section will show the common workflow required to create a SUSE Studio
|
34
37
|
appliance running a standard Rails application.
|
35
38
|
|
36
39
|
All the following commands must be executed inside of the root directory of
|
@@ -39,8 +42,8 @@ your rails application.
|
|
39
42
|
=== Create
|
40
43
|
You can create your SUSE Studio appliance using the following command:
|
41
44
|
dister create APPLIANCE_NAME
|
42
|
-
|
43
|
-
|
45
|
+
This creates a 32bit appliance based on the latest version of openSUSE supported
|
46
|
+
by SUSE Studio. The appliance will use the
|
44
47
|
{JeOS}[http://en.wikipedia.org/wiki/Just_enough_operating_system] template.
|
45
48
|
|
46
49
|
You can change the default behaviour using the following command line options:
|
@@ -48,16 +51,17 @@ You can change the default behaviour using the following command line options:
|
|
48
51
|
- <tt>--template</tt>: to use something different from the JeOS template.
|
49
52
|
- <tt>--arch</tt>: to build a 64bit appliance.
|
50
53
|
|
51
|
-
By default all the appliances created by dister have the
|
52
|
-
repository. This repository contains all
|
53
|
-
It's actively maintained by the openSUSE community
|
54
|
+
By default all the appliances created by dister have the
|
55
|
+
<em>devel:language:ruby:extensions</em> repository. This repository contains all
|
56
|
+
the Ruby-related packages. It's actively maintained by the openSUSE community
|
57
|
+
and by some Novell employee.
|
54
58
|
|
55
59
|
The following packages are automatically added to all appliances:
|
56
|
-
- devel_C_C
|
57
|
-
- rubygem-bundler
|
58
|
-
- rubygem-passenger-apache2
|
59
|
-
|
60
|
-
installed by SUSE Studio because they are dependencies of rubygem-passenger-apache2
|
60
|
+
- <tt>devel_C_C++</tt> and +devel_Ruby+: these are needed in order to build native gems.
|
61
|
+
- +rubygem-bundler+: this package provides latest version of bundler.
|
62
|
+
- +rubygem-passenger-apache2+: this package is required in order to deploy your
|
63
|
+
Rails application using Apache. All the Apache packages will be automatically
|
64
|
+
installed by SUSE Studio because they are dependencies of +rubygem-passenger-apache2+.
|
61
65
|
|
62
66
|
The create task takes care of uploading some custom build and boot scripts.
|
63
67
|
These scripts take care of initializing your appliance. You can inspect them
|
@@ -70,15 +74,30 @@ In order to add your rails application to your SUSE Studio appliance just execut
|
|
70
74
|
This will automatically create (or update) your local bundle and upload it to
|
71
75
|
SUSE Studio.
|
72
76
|
Your bundle contains:
|
73
|
-
- all the gems needed by your
|
77
|
+
- all the gems needed by your Rails appliance (this is done using bundler).
|
74
78
|
- all your code
|
75
|
-
-
|
79
|
+
- Apache configuration
|
76
80
|
|
77
81
|
=== Build
|
78
82
|
Building can be triggered using the following command:
|
79
83
|
dister build
|
80
84
|
A nice progress bar will be shown.
|
81
85
|
|
86
|
+
=== Testdrive
|
87
|
+
After your build has completed, you can testdrive your appliance:
|
88
|
+
dister testdrive
|
89
|
+
|
90
|
+
Currently you have to access your testdrive using VNC, but support for the
|
91
|
+
web-based Flash interface is on the way.
|
92
|
+
|
93
|
+
=== Download
|
94
|
+
If you're happy with what you saw during your testdrive, you can download the
|
95
|
+
appliance:
|
96
|
+
dister download
|
97
|
+
|
98
|
+
You can then deploy it. We want to support direct deployment to EC2 in future
|
99
|
+
versions.
|
100
|
+
|
82
101
|
TODO: write more documentation.
|
83
102
|
|
84
103
|
== License
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
require 'rake'
|
3
|
-
require 'rake/rdoctask'
|
4
3
|
require 'rake/testtask'
|
5
4
|
Bundler::GemHelper.install_tasks
|
6
5
|
|
@@ -13,14 +12,9 @@ Rake::TestTask.new(:test) do |test|
|
|
13
12
|
test.verbose = true
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
rdoc.rdoc_files.include('README.rdoc')
|
22
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
begin
|
16
|
+
require 'yard'
|
17
|
+
YARD::Rake::YardocTask.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Yard not available. To generate documentation install it with: gem install yard"
|
23
20
|
end
|
24
|
-
|
25
|
-
desc "Clean files generated by rake tasks"
|
26
|
-
task :clobber => [:clobber_rdoc]
|
data/dister.gemspec
CHANGED
@@ -15,15 +15,17 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.rubyforge_project = "dister"
|
16
16
|
|
17
17
|
s.add_dependency "curb"
|
18
|
+
s.add_dependency "highline", "~>1.6.1"
|
18
19
|
s.add_dependency "progressbar"
|
19
|
-
s.add_dependency "studio_api", "~>3.
|
20
|
+
s.add_dependency "studio_api", "~>3.2"
|
20
21
|
s.add_dependency "thor", "~>0.14.0"
|
21
22
|
|
22
|
-
s.add_development_dependency "bundler"
|
23
|
+
s.add_development_dependency "bundler"
|
23
24
|
s.add_development_dependency "fakefs"
|
24
25
|
s.add_development_dependency "mocha"
|
25
26
|
s.add_development_dependency "test-unit", "1.2.3"
|
26
27
|
s.add_development_dependency "shoulda"
|
28
|
+
s.add_development_dependency "yard"
|
27
29
|
s.files = `git ls-files`.split("\n")
|
28
30
|
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
29
31
|
s.require_path = 'lib'
|
data/lib/dister.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'erb'
|
2
3
|
require 'thor'
|
3
4
|
require 'studio_api'
|
4
5
|
require 'yaml'
|
5
6
|
require 'progressbar'
|
6
|
-
require 'bundler'
|
7
7
|
require 'curb'
|
8
|
+
require 'highline/import'
|
8
9
|
|
9
10
|
require File.expand_path('../dister/cli', __FILE__)
|
10
11
|
require File.expand_path('../dister/constants', __FILE__)
|
data/lib/dister/cli.rb
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
module Dister
|
2
2
|
|
3
|
+
# This is the public facing command line interface which is available through
|
4
|
+
# the +dister+ command line tool. Use +dister --help+ for usage instructions.
|
3
5
|
class Cli < Thor
|
6
|
+
|
4
7
|
include Thor::Actions
|
5
8
|
|
6
|
-
# Returns Dister's root directory.
|
7
9
|
# NOTE: Some of Thor's actions require this method to be defined.
|
10
|
+
# @return [String] Dister's root directory.
|
8
11
|
def self.source_root
|
9
12
|
File.expand_path('../../',__FILE__)
|
10
13
|
end
|
11
14
|
|
15
|
+
desc "version", "Show dister version"
|
16
|
+
def version
|
17
|
+
require "dister/version"
|
18
|
+
puts "dister version #{Dister::VERSION}"
|
19
|
+
end
|
20
|
+
|
12
21
|
desc "config OPTION VALUE", "set OPTION value to VALUE"
|
13
22
|
method_option :local,
|
14
23
|
:type => :boolean, :default => false, :required => false
|
@@ -36,14 +45,13 @@ module Dister
|
|
36
45
|
# attempt to find latest version of openSUSE
|
37
46
|
basesystem = basesystems.find_all{|a| a =~ /\d+\.\d+/}.sort.last
|
38
47
|
if basesystem.nil?
|
39
|
-
# apparently this server doesn't offer openSUSE basesystem
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
basesystem = basesystems[choice.to_i - 1]
|
48
|
+
# apparently this server doesn't offer openSUSE basesystem, so we
|
49
|
+
# present the user with a menu with available choices
|
50
|
+
basesystem = choose do |menu|
|
51
|
+
menu.header = "Available base systems"
|
52
|
+
menu.choices *basesystems
|
53
|
+
menu.prompt = "Which base system do you want to use?"
|
54
|
+
end
|
47
55
|
end
|
48
56
|
else
|
49
57
|
basesystem = options[:basesystem]
|
data/lib/dister/core.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
require 'digest/md5'
|
2
|
-
require 'erb'
|
3
2
|
|
4
3
|
module Dister
|
5
4
|
|
5
|
+
# Core functionality
|
6
6
|
class Core
|
7
7
|
|
8
8
|
attr_reader :options, :shell
|
9
9
|
|
10
|
+
# Absolute path to the root of the current application
|
10
11
|
APP_ROOT = File.expand_path('.')
|
11
12
|
|
12
13
|
# Connect to SUSE Studio and verify the user's credentials.
|
13
|
-
# Sets
|
14
|
+
# Sets +@options+, +@shell+ and +@connection+ for further use.
|
14
15
|
def initialize
|
15
16
|
@options ||= Options.new
|
16
17
|
@shell = Thor::Shell::Basic.new
|
@@ -33,10 +34,10 @@ module Dister
|
|
33
34
|
true
|
34
35
|
rescue ActiveResource::UnauthorizedAccess
|
35
36
|
puts 'A connection to SUSE Studio could not be established.'
|
36
|
-
keep_trying = @shell.
|
37
|
+
keep_trying = @shell.yes?(
|
37
38
|
'Would you like to re-enter your credentials and try again? (y/n)'
|
38
39
|
)
|
39
|
-
if keep_trying
|
40
|
+
if keep_trying
|
40
41
|
update_credentials
|
41
42
|
retry
|
42
43
|
else
|
@@ -45,7 +46,13 @@ module Dister
|
|
45
46
|
end
|
46
47
|
|
47
48
|
# Creates a new appliance.
|
48
|
-
#
|
49
|
+
#
|
50
|
+
# @param [String] name
|
51
|
+
# @param [String] template
|
52
|
+
# @param [String] basesystem
|
53
|
+
# @param [String] arch
|
54
|
+
#
|
55
|
+
# @return [StudioApi::Appliance] the new appliance
|
49
56
|
def create_appliance(name, template, basesystem, arch)
|
50
57
|
match = check_template_and_basesystem_availability(template, basesystem)
|
51
58
|
exit 1 if match.nil?
|
@@ -57,15 +64,12 @@ module Dister
|
|
57
64
|
end
|
58
65
|
@options.appliance_id = app.id
|
59
66
|
ensure_devel_languages_ruby_extensions_repo_is_added
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
self.add_package p
|
67
|
-
end
|
68
|
-
end
|
67
|
+
|
68
|
+
default_packages = %w(devel_C_C++ devel_ruby
|
69
|
+
rubygem-bundler rubygem-passenger-apache2)
|
70
|
+
|
71
|
+
self.add_packages(default_packages)
|
72
|
+
self.add_packages(@db_adapter.packages) unless @db_adapter.nil?
|
69
73
|
|
70
74
|
Utils::execute_printing_progress "Uploading build scripts" do
|
71
75
|
upload_configurations_scripts
|
@@ -75,6 +79,10 @@ module Dister
|
|
75
79
|
app
|
76
80
|
end
|
77
81
|
|
82
|
+
# Builds the appliance
|
83
|
+
#
|
84
|
+
# @param [Hash] build_options
|
85
|
+
# @option build_options [Boolean] :force
|
78
86
|
def build build_options = {}
|
79
87
|
verify_status
|
80
88
|
#TODO:
|
@@ -91,8 +99,7 @@ module Dister
|
|
91
99
|
build = StudioApi::RunningBuild.create(params)
|
92
100
|
rescue StudioApi::ImageAlreadyExists
|
93
101
|
@shell.say 'An image with the same version already exists'
|
94
|
-
|
95
|
-
if overwrite == 'y'
|
102
|
+
if @shell.yes? 'Do you want to overwrite it? (y/n)'
|
96
103
|
force = true
|
97
104
|
retry
|
98
105
|
else
|
@@ -108,9 +115,7 @@ module Dister
|
|
108
115
|
puts "Your build is queued. It will be automatically processed by "\
|
109
116
|
"SUSE Studio. You can keep waiting or you can exit from dister."
|
110
117
|
puts "Exiting from dister won't remove your build from the queue."
|
111
|
-
shell
|
112
|
-
keep_waiting = @shell.ask('Do you want to keep waiting (y/n)')
|
113
|
-
if keep_waiting == 'n'
|
118
|
+
if @shell.no?('Do you want to keep waiting (y/n)')
|
114
119
|
exit 0
|
115
120
|
end
|
116
121
|
|
@@ -134,7 +139,8 @@ module Dister
|
|
134
139
|
build.state == 'finished'
|
135
140
|
end
|
136
141
|
|
137
|
-
#
|
142
|
+
# Finds the appliance for the current app
|
143
|
+
# @return [StudioApi::Appliance] the app's appliance (or nil if none exist).
|
138
144
|
def appliance
|
139
145
|
if @appliance.nil?
|
140
146
|
begin
|
@@ -150,6 +156,8 @@ module Dister
|
|
150
156
|
end
|
151
157
|
end
|
152
158
|
|
159
|
+
# Finds all builds
|
160
|
+
# @return [Array<StudioApi::Build>]
|
153
161
|
def builds
|
154
162
|
StudioApi::Build.find(:all, :params => {:appliance_id => @options.appliance_id})
|
155
163
|
end
|
@@ -165,6 +173,8 @@ module Dister
|
|
165
173
|
end
|
166
174
|
end
|
167
175
|
|
176
|
+
# Find available base systems
|
177
|
+
# @return [Array<String>] a list of available base systems
|
168
178
|
def basesystems
|
169
179
|
templates.collect(&:basesystem).uniq
|
170
180
|
end
|
@@ -188,15 +198,21 @@ module Dister
|
|
188
198
|
end
|
189
199
|
|
190
200
|
# Uploads a file identified by filename to a SuSE Studio Appliance
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
201
|
+
#
|
202
|
+
# @param [String] filename name of file to upload
|
203
|
+
# @param [Hash] upload_options upload options (all parameters are optional)
|
204
|
+
# @option upload_options [String] filename The name of the file in the
|
205
|
+
# filesystem
|
206
|
+
# @option upload_options [String] path The path where the file will be stored
|
207
|
+
# @option upload_options [String] owner The owner of the file
|
208
|
+
# @option upload_options [String] group The group of the file
|
209
|
+
# @option upload_options [String] permissions The permissions of the file
|
210
|
+
# @option upload_options [String] enabled Used to enable/disable this file
|
211
|
+
# for the builds
|
212
|
+
# @option upload_options [String] url The url of the file to add from the
|
213
|
+
# internet (HTTP and FTP are supported) when using the web upload method
|
214
|
+
#
|
215
|
+
# @return [Boolean] true if the file has been successfully uploaded
|
200
216
|
def file_upload filename, upload_options={}
|
201
217
|
if File.exists? filename
|
202
218
|
# Delete existing (obsolete) file.
|
@@ -231,7 +247,11 @@ module Dister
|
|
231
247
|
puts 'Packaging gems...'
|
232
248
|
system "cd #{APP_ROOT}"
|
233
249
|
system "rm -R vendor/cache" if File.exists?("#{APP_ROOT}/vendor/cache")
|
234
|
-
system 'bundle package'
|
250
|
+
success = system 'bundle package'
|
251
|
+
unless success
|
252
|
+
STDERR.puts "`bundle package` failed, exiting"
|
253
|
+
exit 1
|
254
|
+
end
|
235
255
|
puts "Done!"
|
236
256
|
end
|
237
257
|
|
@@ -280,6 +300,8 @@ module Dister
|
|
280
300
|
self.file_upload("#{APP_ROOT}/.dister/create_db_user.sql", upload_options)
|
281
301
|
end
|
282
302
|
|
303
|
+
# Add a package to the appliance
|
304
|
+
# @param [String] package the name of the package
|
283
305
|
def add_package package
|
284
306
|
appliance_basesystem = appliance.basesystem
|
285
307
|
result = appliance.search_software(package)#.find{|s| s.name == package }
|
@@ -288,9 +310,9 @@ module Dister
|
|
288
310
|
if result.empty? #it is not found in available repos
|
289
311
|
puts "'#{package}' has not been found in the repositories currently "\
|
290
312
|
"added to your appliance."
|
291
|
-
keep_trying = @shell.
|
313
|
+
keep_trying = @shell.yes?('Would you like to search for this package '\
|
292
314
|
'inside other repositories? (y/n)')
|
293
|
-
if keep_trying
|
315
|
+
if keep_trying
|
294
316
|
matches = appliance.search_software(package, :all_repos => true)\
|
295
317
|
.find_all { |s| s.name == package }
|
296
318
|
repositories = matches.map do |r|
|
@@ -330,6 +352,14 @@ module Dister
|
|
330
352
|
end
|
331
353
|
end
|
332
354
|
|
355
|
+
# Add a list of packages at once
|
356
|
+
# @param [Array<String>] packages
|
357
|
+
def add_packages(packages)
|
358
|
+
packages.each { |package| self.add_package(package) }
|
359
|
+
end
|
360
|
+
|
361
|
+
# Remove a package from the appliance
|
362
|
+
# @param [String] package the name of the package
|
333
363
|
def rm_package package
|
334
364
|
Utils::execute_printing_progress "Removing #{package} package" do
|
335
365
|
appliance.remove_package(package)
|
@@ -337,6 +367,7 @@ module Dister
|
|
337
367
|
end
|
338
368
|
|
339
369
|
# Uploads our configuration scripts
|
370
|
+
# @return [true] if the scripts are successfully uploaded
|
340
371
|
def upload_configurations_scripts
|
341
372
|
rails_root = "/srv/www/#{@options.app_name}"
|
342
373
|
|
@@ -359,7 +390,7 @@ module Dister
|
|
359
390
|
end
|
360
391
|
|
361
392
|
# Asks Studio to mirror a repository.
|
362
|
-
#
|
393
|
+
# @return [StudioApi::Repository]
|
363
394
|
def import_repository url, name
|
364
395
|
StudioApi::Repository.import url, name
|
365
396
|
end
|
@@ -390,6 +421,9 @@ module Dister
|
|
390
421
|
when "SLED11_SP1", "SLES11_SP1", "SLES11_SP1_VMware"
|
391
422
|
url += "SLE_11_SP1"
|
392
423
|
name += " SLE11 SP1"
|
424
|
+
when "SLES11_SP2", "SLES11_SP2"
|
425
|
+
url += "SLE_11_SP2"
|
426
|
+
name += " SLE11 SP2"
|
393
427
|
else
|
394
428
|
STDERR.puts "#{appliance.basesystem}: unknown base system"
|
395
429
|
exit 1
|
@@ -418,6 +452,7 @@ module Dister
|
|
418
452
|
end
|
419
453
|
end
|
420
454
|
|
455
|
+
# @param [Array] build_set
|
421
456
|
def testdrive(build_set)
|
422
457
|
build = build_set[0] # for now we just take the first available build
|
423
458
|
testdrive = Utils::execute_printing_progress "Starting testdrive" do
|
@@ -435,37 +470,29 @@ module Dister
|
|
435
470
|
puts "Password: #{vnc.password}"
|
436
471
|
end
|
437
472
|
|
473
|
+
# @param [Array] build_set
|
438
474
|
def download(build_set)
|
439
475
|
# Choose the build(s) to download.
|
440
476
|
to_download = []
|
441
477
|
if build_set.size == 1
|
442
478
|
to_download << build_set.first
|
443
479
|
else
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
begin
|
450
|
-
choice = @shell.ask "Which appliance do you want to download? [1-#{build_set.size+1}]"
|
451
|
-
end while (choice.to_i > (build_set.size+2))
|
452
|
-
if choice.to_i == (build_set.size+2)
|
453
|
-
# none selected
|
454
|
-
exit 0
|
455
|
-
elsif choice.to_i == (build_set.size+1)
|
456
|
-
# all selected
|
457
|
-
to_download = build_set
|
458
|
-
else
|
459
|
-
to_download << build_set[choice.to_i-1]
|
480
|
+
to_download = choose do |menu|
|
481
|
+
menu.choices *build_set do |i| [i] end # wrap choice in an array
|
482
|
+
menu.choice("All of them.") { build_set }
|
483
|
+
menu.choice("None.") { exit 0 }
|
484
|
+
menu.prompt = "Which appliance do you want to download?"
|
460
485
|
end
|
461
486
|
end
|
487
|
+
|
462
488
|
# Download selected builds.
|
463
489
|
to_download.each do |b|
|
464
490
|
puts "Going to download #{b.to_s}"
|
465
491
|
d = Downloader.new(b.download_url.sub("https:", "http:"),"Downloading")
|
466
492
|
if File.exists? d.filename
|
467
|
-
|
468
|
-
|
493
|
+
if @shell.no?("Do you want to overwrite file #{d.filename}? (y/n)")
|
494
|
+
exit 0
|
495
|
+
end
|
469
496
|
end
|
470
497
|
begin
|
471
498
|
d.start
|
@@ -492,6 +519,7 @@ module Dister
|
|
492
519
|
@options.api_key = @shell.ask("API key:\t")
|
493
520
|
end
|
494
521
|
|
522
|
+
# @return [Dister::DbAdapter]
|
495
523
|
def get_db_adapter
|
496
524
|
db_config_file = "#{APP_ROOT}/config/database.yml"
|
497
525
|
if !File.exists?(db_config_file)
|
@@ -504,5 +532,7 @@ module Dister
|
|
504
532
|
Dister::DbAdapter.new db_config_file
|
505
533
|
end
|
506
534
|
end
|
535
|
+
|
507
536
|
end
|
537
|
+
|
508
538
|
end
|
data/lib/dister/db_adapter.rb
CHANGED
@@ -1,31 +1,40 @@
|
|
1
|
-
require 'erb'
|
2
|
-
|
3
1
|
module Dister
|
2
|
+
|
3
|
+
# Wrapper class for the database adapter specified in the application's
|
4
|
+
# +database.yml+. Also handles database credentials, and dump/restore.
|
5
|
+
# Currently this only works with ActiveRecord.
|
4
6
|
class DbAdapter
|
5
7
|
|
8
|
+
# Initialize a new DbAdapter. May raise exceptions if the input is erronous.
|
9
|
+
#
|
10
|
+
# @param [String] db_config_file path to the database configuration (.yml)
|
11
|
+
# @param [String] dump filename of dump
|
6
12
|
def initialize db_config_file, dump=nil
|
7
13
|
config = YAML.load_file(db_config_file)
|
8
14
|
if !config.has_key?("production")
|
9
15
|
STDERR.puts "There's no configuration for the production environment"
|
10
16
|
end
|
11
|
-
|
17
|
+
|
12
18
|
@adapter = config["production"]["adapter"]
|
13
19
|
@user = config["production"]["username"]
|
14
|
-
@password = config["production"]["password"]
|
15
|
-
@dbname = config["production"]["adapter"]
|
20
|
+
@password = config["production"]["password"]
|
21
|
+
@dbname = config["production"]["adapter"]
|
16
22
|
@dump = dump
|
17
|
-
|
23
|
+
|
18
24
|
filename = File.expand_path("../../adapters/#{@adapter}.yml", __FILE__)
|
19
25
|
raise "There's no adapter for #{@adapter}" if !File.exists?(filename)
|
20
26
|
|
21
27
|
@adapter_config = YAML.load_file(filename)
|
22
28
|
end
|
23
29
|
|
30
|
+
# Checks if there is a db dump
|
31
|
+
#
|
32
|
+
# @return [Boolean] true if there is a dump file, false otherwise
|
24
33
|
def has_dump?
|
25
34
|
return false if @dump.nil?
|
26
35
|
return File.exists? @dump
|
27
36
|
end
|
28
|
-
|
37
|
+
|
29
38
|
def cmdline_tool
|
30
39
|
@adapter_config["cmdline_tool"]
|
31
40
|
end
|
@@ -53,5 +62,7 @@ module Dister
|
|
53
62
|
erb = ERB.new cmd
|
54
63
|
erb.result(binding)
|
55
64
|
end
|
65
|
+
|
56
66
|
end
|
67
|
+
|
57
68
|
end
|
data/lib/dister/downloader.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
module Dister
|
2
|
+
|
3
|
+
# Curl wrapper for downloading appliance builds.
|
2
4
|
class Downloader
|
3
5
|
attr_reader :filename
|
4
6
|
|
5
|
-
|
7
|
+
# @param [String] url URL of file to be downloaded
|
8
|
+
# @param [String] message Message to be displayed while downloading
|
6
9
|
def initialize url, message
|
7
10
|
@filename = File.basename(url)
|
8
11
|
@message = message
|
9
|
-
|
12
|
+
|
10
13
|
# setup curl
|
11
14
|
@curl = Curl::Easy.new
|
12
15
|
@curl.url = url
|
@@ -21,6 +24,7 @@ module Dister
|
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
27
|
+
# Starts the download
|
24
28
|
def start
|
25
29
|
@file = File.open(@filename, "wb")
|
26
30
|
@pbar = ProgressBar.new(@message, 100)
|
@@ -53,5 +57,7 @@ module Dister
|
|
53
57
|
@file.close
|
54
58
|
end
|
55
59
|
end
|
60
|
+
|
56
61
|
end
|
62
|
+
|
57
63
|
end
|
data/lib/dister/options.rb
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
3
|
module Dister
|
4
|
+
|
5
|
+
# Class for handling user- and application-specific settings
|
4
6
|
class Options
|
5
7
|
|
8
|
+
# Default API path, used unless a custom path is specified
|
6
9
|
SUSE_STUDIO_DOT_COM_API_PATH = 'https://susestudio.com/api/v2/user'
|
10
|
+
# Path to global (per-user) options file
|
7
11
|
GLOBAL_PATH = "#{File.expand_path('~')}/.dister"
|
12
|
+
# Path to app-specific options file
|
8
13
|
LOCAL_PATH = "#{Dister::Core::APP_ROOT}/.dister/options.yml"
|
9
14
|
|
10
15
|
attr_reader :use_only_local
|
11
16
|
|
12
17
|
# Read options from file.
|
18
|
+
#
|
19
|
+
# @param [Boolean] use_only_local Use only local options?
|
13
20
|
def initialize use_only_local=false
|
14
21
|
@use_only_local = use_only_local
|
15
22
|
reload
|
@@ -27,7 +34,8 @@ module Dister
|
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
30
|
-
# Read
|
37
|
+
# Read +@global+ and +@local+ option files. Run this method if their
|
38
|
+
# contents has changed.
|
31
39
|
def reload
|
32
40
|
if @use_only_local
|
33
41
|
@global = {}
|
@@ -104,6 +112,8 @@ module Dister
|
|
104
112
|
# Returns a hash consisting of both global and local options.
|
105
113
|
# All options can be read through this method.
|
106
114
|
# NOTE: Local options override global options.
|
115
|
+
#
|
116
|
+
# @return [Hash] a hash consisting of both global and local options
|
107
117
|
def provide
|
108
118
|
@global.merge(@local)
|
109
119
|
end
|
data/lib/dister/utils.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Dister
|
2
|
+
|
3
|
+
# Shared utility methods
|
2
4
|
module Utils
|
5
|
+
|
3
6
|
module_function
|
7
|
+
|
4
8
|
# Shows message and prints a dot per second until the block code
|
5
9
|
# terminates its execution.
|
6
10
|
# Exceptions raised by the block are displayed and program exists with
|
@@ -31,7 +35,10 @@ module Dister
|
|
31
35
|
MEGA_SIZE = 1048576.0
|
32
36
|
KILO_SIZE = 1024.0
|
33
37
|
|
34
|
-
#
|
38
|
+
# @param [Number] size Size to be converted
|
39
|
+
# @param [Number] precision Number of decimals desired
|
40
|
+
#
|
41
|
+
# @return [String] Return the file size with a readable style.
|
35
42
|
def readable_file_size(size, precision)
|
36
43
|
case
|
37
44
|
when size == 1 then "1 Byte"
|
data/lib/dister/version.rb
CHANGED
data/test/cli_test.rb
CHANGED
@@ -131,8 +131,7 @@ class CliTest < Test::Unit::TestCase
|
|
131
131
|
end
|
132
132
|
|
133
133
|
should "ask the user which base system to use" do
|
134
|
-
|
135
|
-
Thor::Shell::Color.any_instance.expects(:ask).returns(1)
|
134
|
+
HighLine.any_instance.stubs(:choose).returns("SLED10_SP2")
|
136
135
|
fake_app = mock()
|
137
136
|
fake_app.stubs(:edit_url).returns("http://susestudio.com")
|
138
137
|
Dister::Core.any_instance.expects(:create_appliance).\
|
@@ -143,7 +142,7 @@ class CliTest < Test::Unit::TestCase
|
|
143
142
|
Dister::Cli.start(['create', 'foo'])
|
144
143
|
end
|
145
144
|
end
|
146
|
-
|
145
|
+
|
147
146
|
should "not ask the user which base system to use if there's a preference" do
|
148
147
|
Thor::Shell::Color.any_instance.expects(:ask).never
|
149
148
|
fake_app = mock()
|
data/test/core_test.rb
CHANGED
@@ -39,6 +39,11 @@ class CoreTest < Test::Unit::TestCase
|
|
39
39
|
@core.stubs(:puts)
|
40
40
|
end
|
41
41
|
|
42
|
+
should 'skip packaging gems unless there is a Gemfile' do
|
43
|
+
File.expects(:exists?).returns(false)
|
44
|
+
assert_nil @core.package_gems
|
45
|
+
end
|
46
|
+
|
42
47
|
should 'package all required gems' do
|
43
48
|
File.expects(:exists?).returns(true)
|
44
49
|
@core.expects(:system).with("cd #{Dister::Core::APP_ROOT}").once.returns(true)
|
@@ -48,6 +53,17 @@ class CoreTest < Test::Unit::TestCase
|
|
48
53
|
@core.package_gems
|
49
54
|
end
|
50
55
|
|
56
|
+
should 'exit if gem packaging fails' do
|
57
|
+
File.expects(:exists?).returns(true)
|
58
|
+
@core.expects(:system).with("cd #{Dister::Core::APP_ROOT}").once.returns(true)
|
59
|
+
File.expects(:exists?).returns(true)
|
60
|
+
@core.expects(:system).with("rm -R vendor/cache").once.returns(true)
|
61
|
+
@core.expects(:system).with("bundle package").once.returns(false)
|
62
|
+
assert_raise SystemExit do
|
63
|
+
@core.package_gems
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
51
67
|
should "create a tarball of the application's source files" do
|
52
68
|
File.stubs(:exists?).returns(true)
|
53
69
|
@core.expects(:system).with("rm .dister/dister_application.tar.gz").once.returns(true)
|
data/test/test_helper.rb
CHANGED
data/test/utils_test.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class UtilsTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include Dister::Utils
|
6
|
+
|
7
|
+
should "return readable file sizes" do
|
8
|
+
assert_equal readable_file_size(12233, 0), "12 KB"
|
9
|
+
assert_equal readable_file_size(12233, 2), "11.95 KB"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dister
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 800522045387967993
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 3
|
10
|
+
version: 0.1.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Flavio Castelli
|
@@ -16,8 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date:
|
20
|
-
default_executable:
|
19
|
+
date: 2012-04-12 00:00:00 Z
|
21
20
|
dependencies:
|
22
21
|
- !ruby/object:Gem::Dependency
|
23
22
|
name: curb
|
@@ -27,132 +26,159 @@ dependencies:
|
|
27
26
|
requirements:
|
28
27
|
- - ">="
|
29
28
|
- !ruby/object:Gem::Version
|
30
|
-
hash:
|
29
|
+
hash: 2002549777813010636
|
31
30
|
segments:
|
32
31
|
- 0
|
33
32
|
version: "0"
|
34
33
|
type: :runtime
|
35
34
|
version_requirements: *id001
|
36
35
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
36
|
+
name: highline
|
38
37
|
prerelease: false
|
39
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 914886426701899987
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 6
|
47
|
+
- 1
|
48
|
+
version: 1.6.1
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: progressbar
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
55
|
none: false
|
41
56
|
requirements:
|
42
57
|
- - ">="
|
43
58
|
- !ruby/object:Gem::Version
|
44
|
-
hash:
|
59
|
+
hash: 2002549777813010636
|
45
60
|
segments:
|
46
61
|
- 0
|
47
62
|
version: "0"
|
48
63
|
type: :runtime
|
49
|
-
version_requirements: *
|
64
|
+
version_requirements: *id003
|
50
65
|
- !ruby/object:Gem::Dependency
|
51
66
|
name: studio_api
|
52
67
|
prerelease: false
|
53
|
-
requirement: &
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
54
69
|
none: false
|
55
70
|
requirements:
|
56
71
|
- - ~>
|
57
72
|
- !ruby/object:Gem::Version
|
58
|
-
hash:
|
73
|
+
hash: 1144350831328566222
|
59
74
|
segments:
|
60
75
|
- 3
|
61
|
-
-
|
62
|
-
|
63
|
-
version: 3.1.1
|
76
|
+
- 2
|
77
|
+
version: "3.2"
|
64
78
|
type: :runtime
|
65
|
-
version_requirements: *
|
79
|
+
version_requirements: *id004
|
66
80
|
- !ruby/object:Gem::Dependency
|
67
81
|
name: thor
|
68
82
|
prerelease: false
|
69
|
-
requirement: &
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
70
84
|
none: false
|
71
85
|
requirements:
|
72
86
|
- - ~>
|
73
87
|
- !ruby/object:Gem::Version
|
74
|
-
hash:
|
88
|
+
hash: 4540094552861788330
|
75
89
|
segments:
|
76
90
|
- 0
|
77
91
|
- 14
|
78
92
|
- 0
|
79
93
|
version: 0.14.0
|
80
94
|
type: :runtime
|
81
|
-
version_requirements: *
|
95
|
+
version_requirements: *id005
|
82
96
|
- !ruby/object:Gem::Dependency
|
83
97
|
name: bundler
|
84
98
|
prerelease: false
|
85
|
-
requirement: &
|
99
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
86
100
|
none: false
|
87
101
|
requirements:
|
88
|
-
- -
|
102
|
+
- - ">="
|
89
103
|
- !ruby/object:Gem::Version
|
90
|
-
hash:
|
104
|
+
hash: 2002549777813010636
|
91
105
|
segments:
|
92
|
-
- 1
|
93
106
|
- 0
|
94
|
-
|
95
|
-
version: 1.0.0
|
107
|
+
version: "0"
|
96
108
|
type: :development
|
97
|
-
version_requirements: *
|
109
|
+
version_requirements: *id006
|
98
110
|
- !ruby/object:Gem::Dependency
|
99
111
|
name: fakefs
|
100
112
|
prerelease: false
|
101
|
-
requirement: &
|
113
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
102
114
|
none: false
|
103
115
|
requirements:
|
104
116
|
- - ">="
|
105
117
|
- !ruby/object:Gem::Version
|
106
|
-
hash:
|
118
|
+
hash: 2002549777813010636
|
107
119
|
segments:
|
108
120
|
- 0
|
109
121
|
version: "0"
|
110
122
|
type: :development
|
111
|
-
version_requirements: *
|
123
|
+
version_requirements: *id007
|
112
124
|
- !ruby/object:Gem::Dependency
|
113
125
|
name: mocha
|
114
126
|
prerelease: false
|
115
|
-
requirement: &
|
127
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
116
128
|
none: false
|
117
129
|
requirements:
|
118
130
|
- - ">="
|
119
131
|
- !ruby/object:Gem::Version
|
120
|
-
hash:
|
132
|
+
hash: 2002549777813010636
|
121
133
|
segments:
|
122
134
|
- 0
|
123
135
|
version: "0"
|
124
136
|
type: :development
|
125
|
-
version_requirements: *
|
137
|
+
version_requirements: *id008
|
126
138
|
- !ruby/object:Gem::Dependency
|
127
139
|
name: test-unit
|
128
140
|
prerelease: false
|
129
|
-
requirement: &
|
141
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
130
142
|
none: false
|
131
143
|
requirements:
|
132
144
|
- - "="
|
133
145
|
- !ruby/object:Gem::Version
|
134
|
-
hash:
|
146
|
+
hash: 1882242982824780815
|
135
147
|
segments:
|
136
148
|
- 1
|
137
149
|
- 2
|
138
150
|
- 3
|
139
151
|
version: 1.2.3
|
140
152
|
type: :development
|
141
|
-
version_requirements: *
|
153
|
+
version_requirements: *id009
|
142
154
|
- !ruby/object:Gem::Dependency
|
143
155
|
name: shoulda
|
144
156
|
prerelease: false
|
145
|
-
requirement: &
|
157
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
146
158
|
none: false
|
147
159
|
requirements:
|
148
160
|
- - ">="
|
149
161
|
- !ruby/object:Gem::Version
|
150
|
-
hash:
|
162
|
+
hash: 2002549777813010636
|
151
163
|
segments:
|
152
164
|
- 0
|
153
165
|
version: "0"
|
154
166
|
type: :development
|
155
|
-
version_requirements: *
|
167
|
+
version_requirements: *id010
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: yard
|
170
|
+
prerelease: false
|
171
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
hash: 2002549777813010636
|
177
|
+
segments:
|
178
|
+
- 0
|
179
|
+
version: "0"
|
180
|
+
type: :development
|
181
|
+
version_requirements: *id011
|
156
182
|
description: Turn your rails app into a SUSE Studio appliance in a few steps.
|
157
183
|
email:
|
158
184
|
- flavio@castelli.name
|
@@ -165,6 +191,7 @@ extra_rdoc_files: []
|
|
165
191
|
|
166
192
|
files:
|
167
193
|
- .gitignore
|
194
|
+
- .yardopts
|
168
195
|
- Changelog
|
169
196
|
- Gemfile
|
170
197
|
- LICENSE
|
@@ -197,7 +224,7 @@ files:
|
|
197
224
|
- test/fixtures/unsupported_database.yml
|
198
225
|
- test/options_test.rb
|
199
226
|
- test/test_helper.rb
|
200
|
-
|
227
|
+
- test/utils_test.rb
|
201
228
|
homepage: https://github.com/flavio/dister
|
202
229
|
licenses: []
|
203
230
|
|
@@ -211,7 +238,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
211
238
|
requirements:
|
212
239
|
- - ">="
|
213
240
|
- !ruby/object:Gem::Version
|
214
|
-
hash:
|
241
|
+
hash: 2002549777813010636
|
215
242
|
segments:
|
216
243
|
- 0
|
217
244
|
version: "0"
|
@@ -220,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
247
|
requirements:
|
221
248
|
- - ">="
|
222
249
|
- !ruby/object:Gem::Version
|
223
|
-
hash:
|
250
|
+
hash: 2387630629434237851
|
224
251
|
segments:
|
225
252
|
- 1
|
226
253
|
- 3
|
@@ -229,9 +256,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
229
256
|
requirements: []
|
230
257
|
|
231
258
|
rubyforge_project: dister
|
232
|
-
rubygems_version: 1.
|
259
|
+
rubygems_version: 1.8.12
|
233
260
|
signing_key:
|
234
261
|
specification_version: 3
|
235
262
|
summary: Heroku like solution for SUSE Studio
|
236
263
|
test_files: []
|
237
264
|
|
265
|
+
has_rdoc:
|