dister 0.1.0
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 +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
|