dister 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ require 'erb'
2
+
3
+ module Dister
4
+ class DbAdapter
5
+
6
+ def initialize db_config_file, dump=nil
7
+ config = YAML.load_file(db_config_file)
8
+ if !config.has_key?("production")
9
+ STDERR.puts "There's no configuration for the production environment"
10
+ end
11
+
12
+ @adapter = config["production"]["adapter"]
13
+ @user = config["production"]["username"]
14
+ @password = config["production"]["password"]
15
+ @dbname = config["production"]["adapter"]
16
+ @dump = dump
17
+
18
+ filename = File.expand_path("../../adapters/#{@adapter}.yml", __FILE__)
19
+ raise "There's no adapter for #{@adapter}" if !File.exists?(filename)
20
+
21
+ @adapter_config = YAML.load_file(filename)
22
+ end
23
+
24
+ def has_dump?
25
+ return false if @dump.nil?
26
+ return File.exists? @dump
27
+ end
28
+
29
+ def cmdline_tool
30
+ @adapter_config["cmdline_tool"]
31
+ end
32
+
33
+ def packages
34
+ @adapter_config["packages"]
35
+ end
36
+
37
+ def daemon_name
38
+ @adapter_config["daemon_name"]
39
+ end
40
+
41
+ def create_user_cmd
42
+ compile_cmd @adapter_config["create_user_cmd"]
43
+ end
44
+
45
+ def restore_dump_cmd
46
+ compile_cmd @adapter_config["restore_dump_cmd"]
47
+ end
48
+
49
+ private
50
+ def compile_cmd cmd
51
+ return "" if cmd.nil?
52
+
53
+ erb = ERB.new cmd
54
+ erb.result(binding)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ require 'ruby-debug'
2
+ module Dister
3
+ class Downloader
4
+ attr_reader :filename
5
+
6
+
7
+ def initialize url, message
8
+ @filename = File.basename(url)
9
+ @message = message
10
+
11
+ # setup curl
12
+ @curl = Curl::Easy.new
13
+ @curl.url = url
14
+ @curl.follow_location = true
15
+
16
+ @curl.on_body { |data| self.on_body(data); data.size }
17
+ @curl.on_complete { |data| self.on_complete }
18
+ @curl.on_failure { |data| self.on_failure }
19
+ @curl.on_progress do |dl_total, dl_now, ul_total, ul_now|
20
+ self.on_progress(dl_now, dl_total, @curl.download_speed, @curl.total_time)
21
+ true
22
+ end
23
+ end
24
+
25
+ def start
26
+ @file = File.open(@filename, "wb")
27
+ @pbar = ProgressBar.new(@message, 100)
28
+ @curl.perform
29
+ end
30
+
31
+ def on_body(data)
32
+ @file.write(data)
33
+ end
34
+
35
+ def on_progress(downloaded_size, total_size, download_speed, downloading_time)
36
+ if total_size > 0
37
+ @pbar.set(downloaded_size / total_size * 100)
38
+ end
39
+ end
40
+
41
+ def on_complete
42
+ @pbar.finish
43
+ @file.close
44
+ end
45
+
46
+ def on_failure
47
+ begin
48
+ unless code == 'Curl::Err::CurlOKNo error'
49
+ @pbar.finish
50
+ STDOUT.flush
51
+ raise "Download failed with error code: #{code}"
52
+ end
53
+ ensure
54
+ @file.close
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,113 @@
1
+ require 'fileutils'
2
+
3
+ module Dister
4
+ class Options
5
+
6
+ SUSE_STUDIO_DOT_COM_API_PATH = 'https://susestudio.com/api/v2/user'
7
+ GLOBAL_PATH = "#{File.expand_path('~')}/.dister"
8
+ LOCAL_PATH = "#{Dister::Core::APP_ROOT}/.dister/options.yml"
9
+
10
+ attr_reader :use_only_local
11
+
12
+ # Read options from file.
13
+ def initialize use_only_local=false
14
+ @use_only_local = use_only_local
15
+ reload
16
+ end
17
+
18
+ # Provides setter and getter for all options.
19
+ def method_missing(method, *args)
20
+ method_name = method.to_s
21
+ if (method_name =~ /=$/).nil?
22
+ # Getter
23
+ provide[method_name]
24
+ else
25
+ # Setter
26
+ store(method_name[0..-2], args.first)
27
+ end
28
+ end
29
+
30
+ # Read @global and @local option files.
31
+ def reload
32
+ if @use_only_local
33
+ @global = {}
34
+ else
35
+ # Global options hold the user's credentials to access SUSE Studio.
36
+ # They are stored inside the user's home directory.
37
+ @global = read_options_from_file(GLOBAL_PATH)
38
+
39
+ # make sure the default api path is available
40
+ unless @global.has_key? 'api_path'
41
+ @global['api_path'] = SUSE_STUDIO_DOT_COM_API_PATH
42
+ end
43
+ end
44
+ # Local options hold application specific data (e.g. appliance_id)
45
+ # They are stored inside the application's root directory.
46
+ @local = read_options_from_file(LOCAL_PATH)
47
+ end
48
+
49
+ private
50
+
51
+ # Reads from global or local options file and returns an options hash.
52
+ def read_options_from_file(file_path)
53
+ values_hash = YAML.load_file(file_path)
54
+ # In the unlikely case that the options file is empty, return an empty hash.
55
+ values_hash ? values_hash : {}
56
+ rescue Errno::ENOENT
57
+ # File does not exist.
58
+ options_dir = File.dirname(file_path)
59
+ FileUtils.mkdir_p(options_dir) unless File.directory?(options_dir)
60
+ File.new(file_path, 'w')
61
+ retry
62
+ end
63
+
64
+ # Writes an options_hash back to a specified options file.
65
+ def write_options_to_file(options_hash, file_path)
66
+ File.open(file_path, 'w') do |out|
67
+ YAML.dump(options_hash, out)
68
+ end
69
+ end
70
+
71
+ # Determines to which file an option gets written.
72
+ def determine_options_file(option_key)
73
+ return 'local' if @use_only_local
74
+
75
+ # Search in local options first, since they override global options.
76
+ case option_key
77
+ when @local.keys.include?(option_key) then 'local'
78
+ when @global.keys.include?(option_key) then 'global'
79
+ else
80
+ if %w(username api_key api_path).include?(option_key)
81
+ # Credentials are stored globally per default.
82
+ 'global'
83
+ else
84
+ # New options get stored locally.
85
+ 'local'
86
+ end
87
+ end
88
+ end
89
+
90
+ # Stores a specified option_key inside its originating options file.
91
+ def store(option_key, option_value)
92
+ if determine_options_file(option_key) == 'local'
93
+ @local[option_key] = option_value
94
+ options_hash = @local
95
+ file_path = LOCAL_PATH
96
+ else
97
+ @global[option_key] = option_value
98
+ options_hash = @global
99
+ file_path = GLOBAL_PATH
100
+ end
101
+ write_options_to_file(options_hash, file_path)
102
+ end
103
+
104
+ # Returns a hash consisting of both global and local options.
105
+ # All options can be read through this method.
106
+ # NOTE: Local options override global options.
107
+ def provide
108
+ @global.merge(@local)
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,46 @@
1
+ module Dister
2
+ module Utils
3
+ module_function
4
+ # Shows message and prints a dot per second until the block code
5
+ # terminates its execution.
6
+ # Exceptions raised by the block are displayed and program exists with
7
+ # error status 1.
8
+ def execute_printing_progress message
9
+ t = Thread.new do
10
+ print "#{message}"
11
+ while(true) do
12
+ print "."
13
+ STDOUT.flush
14
+ sleep 1
15
+ end
16
+ end
17
+ shell = Thor::Shell::Color.new
18
+ begin
19
+ ret = yield
20
+ t.kill if t.alive?
21
+ shell.say_status "[DONE]", "", :GREEN
22
+ return ret
23
+ rescue
24
+ t.kill if t.alive?
25
+ shell.say_status "[ERROR]", $!, :RED
26
+ exit 1
27
+ end
28
+ end
29
+
30
+ GIGA_SIZE = 1073741824.0
31
+ MEGA_SIZE = 1048576.0
32
+ KILO_SIZE = 1024.0
33
+
34
+ # Return the file size with a readable style.
35
+ def readable_file_size(size, precision)
36
+ case
37
+ when size == 1 then "1 Byte"
38
+ when size < KILO_SIZE then "%d Bytes" % size
39
+ when size < MEGA_SIZE then "%.#{precision}f KB" % (size / KILO_SIZE)
40
+ when size < GIGA_SIZE then "%.#{precision}f MB" % (size / MEGA_SIZE)
41
+ else "%.#{precision}f GB" % (size / GIGA_SIZE)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module Dister
2
+
3
+ VERSION = "0.1.0"
4
+
5
+ end
@@ -0,0 +1,7 @@
1
+ module StudioApi
2
+ class Build
3
+ def to_s
4
+ "version #{self.version}, #{self.image_type} format"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ #
3
+ # This script is executed whenever your appliance boots. Here you can add
4
+ # commands to be executed before the system enters the first runlevel. This
5
+ # could include loading kernel modules, starting daemons that aren't managed
6
+ # by init files, asking questions at the console, etc.
7
+ #
8
+ # The 'kiwi_type' variable will contain the format of the appliance (oem =
9
+ # disk image, vmx = VMware, iso = CD/DVD, xen = Xen).
10
+ #
11
+
12
+ # read in some variables
13
+ . /studio/profile
14
+
15
+ if [ -f /etc/init.d/suse_studio_firstboot ]
16
+ then
17
+ # Put commands to be run on the first boot of your appliance here
18
+ echo "Running SUSE Studio first boot script..."
19
+ cd <%= rails_root %>
20
+ bundle install --local
21
+ <% unless @db_adapter.nil? %>
22
+ # create db user
23
+ if [ -f /root/create_db_user.sql ]
24
+ then
25
+ /etc/init.d/<%= @db_adapter.daemon_name %> start
26
+ <%= @db_adapter.cmdline_tool %> < /root/create_db_user.sql
27
+ fi
28
+
29
+ <% if !@db_adapter.has_dump? %>
30
+ echo "Loading database schema"
31
+ RAILS_ENV=production rake db:create
32
+ RAILS_ENV=production rake db:schema:load
33
+ <% else %>
34
+ <%= @db_adapter.restore_dump_cmd %>
35
+ <% end %>
36
+ <% end %>
37
+ fi
@@ -0,0 +1,22 @@
1
+ #!/bin/bash -e
2
+ #
3
+ # This script is executed at the end of appliance creation. Here you can do
4
+ # one-time actions to modify your appliance before it is ever used, like
5
+ # removing files and directories to make it smaller, creating symlinks,
6
+ # generating indexes, etc.
7
+ #
8
+ # The 'kiwi_type' variable will contain the format of the appliance (oem =
9
+ # disk image, vmx = VMware, iso = CD/DVD, xen = Xen).
10
+ #
11
+
12
+ # read in some variables
13
+ . /studio/profile
14
+
15
+ # add passenger module to apache
16
+ sed -i.bak 's/^\(APACHE_MODULES=.*\)"/\1 passenger"/' /etc/sysconfig/apache2
17
+
18
+ # enable services
19
+ insserv /etc/init.d/apache2
20
+ <% unless @db_adapter.nil? %>
21
+ insserv /etc/init.d/<%= @db_adapter.daemon_name %>
22
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <VirtualHost *:80>
2
+ PassengerEnabled on
3
+ RailsEnv production
4
+ ServerAdmin you@example.com
5
+ DocumentRoot /srv/www/<%= @options.app_name %>/public
6
+ <Directory /srv/www/<%= @options.app_name %>/public>
7
+ Options FollowSymlinks
8
+ Allow from all
9
+ </Directory>
10
+ ErrorLog /var/log/apache2/error.log
11
+ LogLevel warn
12
+ CustomLog /var/log/apache2/access.log combined
13
+ </VirtualHost>
@@ -0,0 +1,141 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ if !defined? FakeTemplates
4
+ Struct.new("FakeTemplate", :name, :basesystem, :appliance_id, :description)
5
+
6
+ FakeTemplates = []
7
+ YAML.load_file(File.expand_path('../fixtures/templates.yml', __FILE__)).each do |item|
8
+ info = item.last
9
+ FakeTemplates << Struct::FakeTemplate.new(info["name"],
10
+ info["basesystem"],
11
+ info["appliance_id"],
12
+ info["description"])
13
+ end
14
+ FakeTemplates.freeze
15
+ end
16
+
17
+ class CliTest < Test::Unit::TestCase
18
+
19
+ context "Run with FakeFS" do
20
+
21
+ setup do
22
+ FakeFS.activate!
23
+ end
24
+
25
+ teardown do
26
+ FakeFS.deactivate!
27
+ end
28
+
29
+ context "no parameter passed" do
30
+
31
+ setup do
32
+ @out = capture(:stdout) { Dister::Cli.start() }
33
+ end
34
+
35
+ should "show help message" do
36
+ assert @out.include?("Tasks:")
37
+ end
38
+
39
+ end
40
+
41
+ context "wrong param" do
42
+
43
+ setup do
44
+ @out = capture(:stdout) do
45
+ @err = capture(:stderr) { Dister::Cli.start(['foo']) }
46
+ end
47
+ end
48
+
49
+ should "show help message" do
50
+ assert_equal 'Could not find task "foo".', @err.chomp
51
+ assert @out.empty?
52
+ end
53
+
54
+ end
55
+
56
+ context "config handling" do
57
+ setup do
58
+ FakeFS.activate!
59
+ end
60
+
61
+ teardown do
62
+ FakeFS.deactivate!
63
+ end
64
+
65
+ should "write to local file" do
66
+ FileUtils.rm_rf Dister::Options::GLOBAL_PATH
67
+ FileUtils.rm_rf Dister::Options::LOCAL_PATH
68
+ @out = capture(:stdout) do
69
+ Dister::Cli.start(['config', 'foo', 'foo_value', '--local'])
70
+ end
71
+ assert !File.exists?(Dister::Options::GLOBAL_PATH)
72
+ assert File.exists?(Dister::Options::LOCAL_PATH)
73
+ options = Dister::Options.new(true)
74
+ assert_equal "foo_value", options.foo
75
+ end
76
+
77
+ end
78
+
79
+ context "creating a new appliance" do
80
+
81
+ setup do
82
+ Dister::Core.any_instance.stubs(:puts)
83
+ Dister::Core.any_instance.stubs(:templates).returns(FakeTemplates)
84
+ basesystems = ["11.1", "SLED10_SP2", "SLES10_SP2", "SLED11", "SLES11",
85
+ "11.2", "SLES11_SP1", "SLED11_SP1", "11.3", "SLED10_SP3",
86
+ "SLES10_SP3", "SLES11_SP1_VMware"]
87
+ Dister::Core.any_instance.stubs(:basesystems).returns(basesystems)
88
+ end
89
+
90
+ should "refuse invalid archs" do
91
+ STDERR.stubs(:puts)
92
+ assert_raise SystemExit do
93
+ Dister::Cli.start(['create', 'foo','--arch', 'ppc'])
94
+ end
95
+ end
96
+
97
+ should "accept valid archs" do
98
+ fake_app = mock()
99
+ fake_app.stubs(:edit_url).returns("http://susestudio.com")
100
+ Dister::Core.any_instance.expects(:create_appliance).returns(fake_app)
101
+ assert_nothing_raised do
102
+ Dister::Cli.start(['create', 'foo','--arch', 'x86_64'])
103
+ end
104
+ end
105
+
106
+ should "guess latest version of openSUSE if no base system is specified" do
107
+ fake_app = mock()
108
+ fake_app.stubs(:edit_url).returns("http://susestudio.com")
109
+ Dister::Core.any_instance.expects(:create_appliance).\
110
+ with("foo", "JeOS", "11.3", "i686").\
111
+ returns(fake_app)
112
+ assert_nothing_raised do
113
+ Dister::Cli.start(['create', 'foo'])
114
+ end
115
+ end
116
+
117
+ should "detect bad combination of template and basesystem" do
118
+ STDERR.stubs(:puts)
119
+ assert_raise(SystemExit) do
120
+ Dister::Cli.start(['create', 'foo', "--template", "jeos",
121
+ "--basesystem", "SLES11_SP1_VMware"])
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ context "When executing 'dister bundle' it" do
128
+
129
+ should 'package all required gems' do
130
+ FileUtils.stubs(:rm).returns(:true)
131
+ Dister::Core.any_instance.expects(:package_gems).once
132
+ Dister::Core.any_instance.expects(:package_config_files).once
133
+ Dister::Core.any_instance.expects(:package_app).once
134
+ Dister::Cli.start ['bundle']
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ end