dister 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.rdoc +86 -0
- data/Rakefile +26 -0
- data/bin/dister +3 -0
- data/dister.gemspec +30 -0
- data/lib/adapters/mysql.yml +13 -0
- data/lib/adapters/mysql2.yml +13 -0
- data/lib/adapters/postgresql.yml +12 -0
- data/lib/adapters/sqlite3.yml +7 -0
- data/lib/dister.rb +21 -0
- data/lib/dister/cli.rb +198 -0
- data/lib/dister/core.rb +488 -0
- data/lib/dister/db_adapter.rb +57 -0
- data/lib/dister/downloader.rb +58 -0
- data/lib/dister/options.rb +113 -0
- data/lib/dister/utils.rb +46 -0
- data/lib/dister/version.rb +5 -0
- data/lib/studio_api/build.rb +7 -0
- data/lib/templates/boot_script.erb +37 -0
- data/lib/templates/build_script.erb +22 -0
- data/lib/templates/passenger.erb +13 -0
- data/test/cli_test.rb +141 -0
- data/test/core_test.rb +79 -0
- data/test/db_adapter_test.rb +82 -0
- data/test/fixtures/supported_database.yml +17 -0
- data/test/fixtures/templates.yml +231 -0
- data/test/fixtures/unsupported_database.yml +17 -0
- data/test/options_test.rb +128 -0
- data/test/test_helper.rb +36 -0
- metadata +235 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2011 Flavio Castelli
|
2
|
+
Copyright (c) 2011 Dominik Mayer
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= Dister: an Heroku like solution for SUSE Studio
|
2
|
+
|
3
|
+
{SUSE Studio}[http://susestudio.com] is an online Linux image creation tool.
|
4
|
+
Creating an appliance to run your rails appliacation 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
|
+
|
8
|
+
You can save some time cloning {this}[http://susegallery.com/a/CZ0T0D/rails-in-a-box]
|
9
|
+
appliance shared on {SUSE Gallery}[http://susegallery.com/], but some efforts
|
10
|
+
are still required.
|
11
|
+
|
12
|
+
Currenlty the easiest solution to deploy a rails application in the cloud is
|
13
|
+
{Heroku}[http://heroku.com/].
|
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 rails application,
|
17
|
+
download it and run into your private or public cloud.
|
18
|
+
|
19
|
+
SUSE Studio currenlty supports the following appliance formats:
|
20
|
+
- oem: it can be run inside KVM or it can be installed to a hard disk/usb pen.
|
21
|
+
- iso and preload iso: you can create a live dvd or an installation dvd.:
|
22
|
+
- vmx and ovf: use these formats if you want to run your appliance inside of
|
23
|
+
VMware, VirtualBox or KVM.
|
24
|
+
- XEN guest: use this format if you want to run your appliance using Xen
|
25
|
+
hypervisor.
|
26
|
+
- ec2: jumping into Amazon's cloud has never been so easy.
|
27
|
+
|
28
|
+
More formats are coming. Checkout {SUSE Studio}[http://susestudio.com] for
|
29
|
+
more details.
|
30
|
+
|
31
|
+
== Common workflow
|
32
|
+
|
33
|
+
This section will show the common workflow required to create a SUSE Studio
|
34
|
+
appliance running a standard Rails application.
|
35
|
+
|
36
|
+
All the following commands must be executed inside of the root directory of
|
37
|
+
your rails application.
|
38
|
+
|
39
|
+
=== Create
|
40
|
+
You can create your SUSE Studio appliance using the following command:
|
41
|
+
dister create APPLIANCE_NAME
|
42
|
+
The following code creates a 32bit appliance based on the latest version of
|
43
|
+
openSUSE supported by SUSE Studio. The appliance will use the
|
44
|
+
{JeOS}[http://en.wikipedia.org/wiki/Just_enough_operating_system] template.
|
45
|
+
|
46
|
+
You can change the default behaviour using the following command line options:
|
47
|
+
- <tt>--basesystem</tt>: to use something different from openSUSE.
|
48
|
+
- <tt>--template</tt>: to use something different from the JeOS template.
|
49
|
+
- <tt>--arch</tt>: to build a 64bit appliance.
|
50
|
+
|
51
|
+
By default all the appliances created by dister have the <em>devel:language:ruby:extensions</em>
|
52
|
+
repository. This repository contains all the ruby-related packages.
|
53
|
+
It's actively maintained by the openSUSE community and by some Novell employee.
|
54
|
+
|
55
|
+
The following packages are automatically added to all appliances:
|
56
|
+
- devel_C_C++ and devel_Ruby: these are needed in order to build native gems.
|
57
|
+
- rubygem-bundler: this package provides latest version of bundler.
|
58
|
+
- rubygem-passenger-apache2: this package is required in order to deploy your
|
59
|
+
rails application using Apache. All the Apache packages will be automatically
|
60
|
+
installed by SUSE Studio because they are dependencies of rubygem-passenger-apache2.
|
61
|
+
|
62
|
+
The create task takes care of uploading some custom build and boot scripts.
|
63
|
+
These scripts take care of initializing your appliance. You can inspect them
|
64
|
+
using SUSE Studio web interface.
|
65
|
+
|
66
|
+
=== Upload your code
|
67
|
+
In order to add your rails application to your SUSE Studio appliance just execute:
|
68
|
+
dister push
|
69
|
+
|
70
|
+
This will automatically create (or update) your local bundle and upload it to
|
71
|
+
SUSE Studio.
|
72
|
+
Your bundle contains:
|
73
|
+
- all the gems needed by your rails appliance (this is done using bundler).
|
74
|
+
- all your code
|
75
|
+
- apache configuration
|
76
|
+
|
77
|
+
=== Build
|
78
|
+
Building can be triggered using the following command:
|
79
|
+
dister build
|
80
|
+
A nice progress bar will be shown.
|
81
|
+
|
82
|
+
TODO: write more documentation.
|
83
|
+
|
84
|
+
== License
|
85
|
+
|
86
|
+
Dister is released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
task :default => "test"
|
8
|
+
|
9
|
+
require 'rake/testtask'
|
10
|
+
Rake::TestTask.new(:test) do |test|
|
11
|
+
test.libs << 'lib' << 'test'
|
12
|
+
test.pattern = 'test/**/*_test.rb'
|
13
|
+
test.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'dister'
|
20
|
+
rdoc.options << '--line-numbers' << "--main" << "README.rdoc"
|
21
|
+
rdoc.rdoc_files.include('README.rdoc')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Clean files generated by rake tasks"
|
26
|
+
task :clobber => [:clobber_rdoc]
|
data/bin/dister
ADDED
data/dister.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/dister/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "dister"
|
6
|
+
s.version = Dister::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ['Flavio Castelli', 'Dominik Mayer']
|
9
|
+
s.email = ['flavio@castelli.name','dmayer@novell.com']
|
10
|
+
s.homepage = "https://features.opensuse.org/311133"
|
11
|
+
s.summary = "Heroku like solution for SUSE Studio"
|
12
|
+
s.description = "Turn your rails app into a SUSE Studio appliance in a few steps."
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.rubyforge_project = "dister"
|
16
|
+
|
17
|
+
s.add_dependency "curb"
|
18
|
+
s.add_dependency "progressbar"
|
19
|
+
s.add_dependency "studio_api", "~>3.1.0"
|
20
|
+
s.add_dependency "thor", "~>0.14.0"
|
21
|
+
|
22
|
+
s.add_development_dependency "bundler", "~>1.0.0"
|
23
|
+
s.add_development_dependency "fakefs"
|
24
|
+
s.add_development_dependency "mocha"
|
25
|
+
s.add_development_dependency "test-unit", "1.2.3"
|
26
|
+
s.add_development_dependency "shoulda"
|
27
|
+
s.files = `git ls-files`.split("\n")
|
28
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
29
|
+
s.require_path = 'lib'
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
packages:
|
2
|
+
- mysql-community-server
|
3
|
+
- ruby-mysql
|
4
|
+
daemon_name: mysql
|
5
|
+
cmdline_tool: mysql
|
6
|
+
create_user_cmd: |
|
7
|
+
CREATE USER '<%= @user %>'@'localhost'
|
8
|
+
IDENTIFIED BY '<%= @password %>';
|
9
|
+
GRANT ALL PRIVILEGES ON *.* TO '<%= @user %>'@'localhost'
|
10
|
+
WITH GRANT OPTION;
|
11
|
+
restore_dump_cmd: |
|
12
|
+
mysql --user=<%= @user %> --password=<%= @password %>
|
13
|
+
<%= @dbname %> < <%= @dump %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
packages:
|
2
|
+
- mysql-community-server
|
3
|
+
- rubygem-mysql2
|
4
|
+
daemon_name: mysql
|
5
|
+
cmdline_tool: mysql
|
6
|
+
create_user_cmd: |
|
7
|
+
CREATE USER '<%= @user %>'@'localhost'
|
8
|
+
IDENTIFIED BY '<%= @password %>';
|
9
|
+
GRANT ALL PRIVILEGES ON *.* TO '<%= @user %>'@'localhost'
|
10
|
+
WITH GRANT OPTION;
|
11
|
+
restore_dump_cmd: |
|
12
|
+
mysql --user=<%= @user %> --password=<%= @password %>
|
13
|
+
<%= @dbname %> < <%= @dump %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
packages:
|
2
|
+
- postgresql-server
|
3
|
+
- rubygem-pg
|
4
|
+
daemon_name: postgresql
|
5
|
+
cmdline_tool: psql
|
6
|
+
create_user_cmd: |
|
7
|
+
CREATE USER '<%= @user %>'
|
8
|
+
WITH PASSWORD '<%= @password %>';
|
9
|
+
CREATEDB;
|
10
|
+
restore_dump_cmd: |
|
11
|
+
psql -U <%= @user %> -h localhost -p <%= @password %>
|
12
|
+
<%= @dbname %> < <%= @dump %>
|
data/lib/dister.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'thor'
|
3
|
+
require 'studio_api'
|
4
|
+
require 'yaml'
|
5
|
+
require 'progressbar'
|
6
|
+
require 'bundler'
|
7
|
+
require 'curb'
|
8
|
+
|
9
|
+
require File.expand_path('../dister/cli', __FILE__)
|
10
|
+
require File.expand_path('../dister/core', __FILE__)
|
11
|
+
require File.expand_path('../dister/options', __FILE__)
|
12
|
+
require File.expand_path('../dister/utils', __FILE__)
|
13
|
+
require File.expand_path('../dister/downloader', __FILE__)
|
14
|
+
require File.expand_path('../dister/db_adapter', __FILE__)
|
15
|
+
require File.expand_path('../studio_api/build', __FILE__)
|
16
|
+
|
17
|
+
module Dister
|
18
|
+
|
19
|
+
autoload :Version, File.expand_path('../dister/version', __FILE__)
|
20
|
+
|
21
|
+
end
|
data/lib/dister/cli.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
module Dister
|
2
|
+
|
3
|
+
class Cli < Thor
|
4
|
+
|
5
|
+
VALID_TEMPLATES = %w(JeOS Server X Gnome KDE)
|
6
|
+
VALID_FOMATS = %w(oem vmx iso xen) #TODO: add other formats
|
7
|
+
VALID_ARCHS = %w(i686 x86_64)
|
8
|
+
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
# Returns Dister's root directory.
|
12
|
+
# NOTE: Some of Thor's actions require this method to be defined.
|
13
|
+
def self.source_root
|
14
|
+
File.expand_path('../../',__FILE__)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "config OPTION VALUE", "set OPTION value to VALUE"
|
18
|
+
method_option :local,
|
19
|
+
:type => :boolean, :default => false, :required => false
|
20
|
+
def config option, value
|
21
|
+
dister_options = Dister::Options.new(!options[:local].nil?)
|
22
|
+
dister_options.send("#{option}=", value)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "create APPLIANCE_NAME", "create a new appliance named APPLIANCE_NAME."
|
26
|
+
method_option :basesystem, :type => :string, :default => nil, :required => false
|
27
|
+
method_option :template, :type => :string, :default => 'JeOS', :required => false
|
28
|
+
method_option :arch, :type => :string, :default => 'i686', :required => false
|
29
|
+
def create(appliance_name)
|
30
|
+
# Check parameters.
|
31
|
+
access_core
|
32
|
+
ensure_valid_option options[:arch], VALID_ARCHS, "arch"
|
33
|
+
ensure_valid_option options[:template], VALID_TEMPLATES, "template"
|
34
|
+
basesystems = @core.basesystems
|
35
|
+
basesystem = options[:basesystem] || basesystems.find_all{|a| a =~ /\d+\.\d+/}.sort.last
|
36
|
+
ensure_valid_option basesystem, basesystems, "base system"
|
37
|
+
# Create appliance and add patterns required to build native gems.
|
38
|
+
@core.create_appliance(appliance_name, options[:template], basesystem, options[:arch])
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "build", "Build the appliance."
|
42
|
+
def build
|
43
|
+
access_core
|
44
|
+
ensure_appliance_exists
|
45
|
+
if @core.build
|
46
|
+
puts "Appliance successfully built."
|
47
|
+
else
|
48
|
+
puts "Build failed."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "download", "Download the appliance."
|
53
|
+
def download
|
54
|
+
access_core
|
55
|
+
ensure_appliance_exists
|
56
|
+
ensure_build_exists
|
57
|
+
@core.download(@builds)
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "testdrive", "Testdrive the appliance."
|
61
|
+
def testdrive
|
62
|
+
access_core
|
63
|
+
ensure_appliance_exists
|
64
|
+
ensure_build_exists
|
65
|
+
@core.testdrive(@builds)
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "format list|add|rm FORMAT", "Enables building of FORMAT"
|
69
|
+
method_option :all, :type => :boolean, :default => false, :required => false
|
70
|
+
def format(operation,format = nil)
|
71
|
+
access_core
|
72
|
+
ensure_valid_option operation, %w(add rm list), "operation"
|
73
|
+
if operation == 'list' and options[:all]
|
74
|
+
puts "Available formats:"
|
75
|
+
puts VALID_FOMATS
|
76
|
+
else
|
77
|
+
existing_types = @core.options.build_types || []
|
78
|
+
chosen_types = case operation
|
79
|
+
when "add"
|
80
|
+
ensure_valid_option format, VALID_FOMATS, "format"
|
81
|
+
@core.options.build_types = (existing_types + [format]).uniq
|
82
|
+
when "rm"
|
83
|
+
@core.options.build_types = (existing_types - [format])
|
84
|
+
else
|
85
|
+
existing_types
|
86
|
+
end
|
87
|
+
puts "Chosen formats:"
|
88
|
+
puts chosen_types
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
desc "templates", "List all the templates available on SUSE Studio."
|
93
|
+
def templates
|
94
|
+
puts VALID_TEMPLATES.sort
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "basesystems", "List all the base systems available on SUSE Studio."
|
98
|
+
def basesystems
|
99
|
+
access_core
|
100
|
+
puts @core.basesystems.sort
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "bundle", "Bundles the application and all required gems."
|
104
|
+
def bundle
|
105
|
+
access_core
|
106
|
+
@core.package_gems
|
107
|
+
@core.package_config_files
|
108
|
+
# Package app last, since it will tarball the application including all gems.
|
109
|
+
@core.package_app
|
110
|
+
end
|
111
|
+
|
112
|
+
desc 'push', 'Pushes all required gems and the application tarball to SUSE Studio.'
|
113
|
+
def push
|
114
|
+
access_core
|
115
|
+
# Always call 'bundle' to ensure we got the latest version bundled.
|
116
|
+
invoke :bundle
|
117
|
+
ensure_appliance_exists
|
118
|
+
@core.upload_bundled_files
|
119
|
+
end
|
120
|
+
|
121
|
+
desc "package add|rm PACKAGE [PACKAGE, ...]", "Add/remove PACKAGE to the appliance"
|
122
|
+
def package operation, *package
|
123
|
+
access_core
|
124
|
+
valid_operations = %w(add rm)
|
125
|
+
ensure_valid_option operation, valid_operations, "operation"
|
126
|
+
case operation
|
127
|
+
when "add"
|
128
|
+
package.each do |p|
|
129
|
+
@core.add_package p
|
130
|
+
end
|
131
|
+
when "rm"
|
132
|
+
package.each do |p|
|
133
|
+
@core.rm_package p
|
134
|
+
end
|
135
|
+
end
|
136
|
+
@core.verify_status
|
137
|
+
puts "Done."
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "info", "Show some useful information about the appliance"
|
141
|
+
def info
|
142
|
+
access_core
|
143
|
+
app = Utils::execute_printing_progress "Contacting SUSE Studio" do
|
144
|
+
@core.appliance
|
145
|
+
end
|
146
|
+
puts "Name: #{app.name}"
|
147
|
+
puts "Based on: #{app.parent.name}"
|
148
|
+
if app.builds.empty?
|
149
|
+
puts "No builds yet."
|
150
|
+
else
|
151
|
+
puts "Builds:"
|
152
|
+
app.builds.each do |b|
|
153
|
+
puts " - #{b.image_type}, version #{b.version}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
puts "Edit url: #{app.edit_url}"
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Convenience method to reduce duplicity and improve readability.
|
162
|
+
# Sets @core
|
163
|
+
def access_core
|
164
|
+
@core ||= Core.new
|
165
|
+
end
|
166
|
+
|
167
|
+
# Checks whether an appliance already exists (invokes :create if not).
|
168
|
+
def ensure_appliance_exists
|
169
|
+
if @core.appliance.nil?
|
170
|
+
appliance_id = @core.shell.ask('Please provide a name for your appliance:')
|
171
|
+
invoke :create, [appliance_id]
|
172
|
+
@core.options.reload
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Checks whether there is at least one existing build (invokes :build if not).
|
177
|
+
def ensure_build_exists
|
178
|
+
@builds = @core.builds
|
179
|
+
if @builds.empty?
|
180
|
+
invoke :build
|
181
|
+
@builds = @core.builds
|
182
|
+
@core.options.reload
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Ensures actual_value is allowed. If not prints an error message to
|
187
|
+
# stderr and exits
|
188
|
+
def ensure_valid_option actual_value, allowed_values, option_name
|
189
|
+
if allowed_values.find{|v| v.downcase == actual_value.downcase}.nil?
|
190
|
+
STDERR.puts "#{actual_value} is not a valid value for #{option_name}"
|
191
|
+
STDERR.puts "Valid values are: #{allowed_values.join(" ")}"
|
192
|
+
exit 1
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|