gepetto 0.0.8

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.
Files changed (36) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +35 -0
  3. data/README.rdoc +212 -0
  4. data/Rakefile +28 -0
  5. data/app_generators/gepetto/USAGE +0 -0
  6. data/app_generators/gepetto/gepetto_generator.rb +35 -0
  7. data/app_generators/gepetto/templates/Rakefile +24 -0
  8. data/app_generators/gepetto/templates/config/fileserver.conf +7 -0
  9. data/app_generators/gepetto/templates/config/puppet.conf +11 -0
  10. data/app_generators/gepetto/templates/manifests/classes/empty.pp +1 -0
  11. data/app_generators/gepetto/templates/manifests/defaults.pp +12 -0
  12. data/app_generators/gepetto/templates/manifests/nodes.pp +5 -0
  13. data/app_generators/gepetto/templates/manifests/site.pp +9 -0
  14. data/app_generators/gepetto/templates/manifests/templates.pp +0 -0
  15. data/app_generators/gepetto/templates/script/module +67 -0
  16. data/app_generators/gepetto/templates/script/puppetca +3 -0
  17. data/app_generators/gepetto/templates/script/puppetmasterd +13 -0
  18. data/app_generators/gepetto/templates/script/puppetrun +2 -0
  19. data/bin/gepetto +18 -0
  20. data/gepetto.gemspec +37 -0
  21. data/lib/gepetto.rb +6 -0
  22. data/lib/gepetto/tasks.rb +1 -0
  23. data/puppet_generators/module/USAGE +18 -0
  24. data/puppet_generators/module/module_generator.rb +24 -0
  25. data/puppet_generators/module/templates/README +0 -0
  26. data/puppet_generators/module/templates/manifests/init.pp +0 -0
  27. data/script/destroy +14 -0
  28. data/script/generate +14 -0
  29. data/tasks/host.pp +75 -0
  30. data/tasks/log.rake +9 -0
  31. data/tasks/puppet.rake +8 -0
  32. data/tasks/sandbox.pp +101 -0
  33. data/tasks/sandbox.rake +437 -0
  34. data/tasks/tmp.rake +24 -0
  35. data/tasks/utils.rake +17 -0
  36. metadata +104 -0
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ /usr/sbin/puppetca --ssldir $PWD/tmp/ssl $*
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+
3
+ # create tmp if needed, other directories are created by puppetmasterd
4
+ [ -d tmp ] || mkdir tmp
5
+
6
+ # change working directories
7
+ OPTIONS="--logdir=$PWD/log --vardir=$PWD/tmp/lib --rundir=$PWD/tmp/run --ssldir=$PWD/tmp/ssl"
8
+ # use local files
9
+ OPTIONS="$OPTIONS --templatedir=$PWD/templates --manifestdir=$PWD/manifests --modulepath=$PWD/modules --confdir=$PWD/config"
10
+
11
+ OPTIONS="$OPTIONS --certname=puppet --logdest=console"
12
+
13
+ /usr/sbin/puppetmasterd --no-daemonize $OPTIONS $*
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ /usr/sbin/puppetrun --ssldir=$PWD/tmp/ssl --confdir=$PWD/config --certname=puppet $*
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rubigen'
5
+
6
+ if %w(-v --version).include? ARGV.first
7
+ require 'gepetto/version'
8
+ puts "#{File.basename($0)} #{Gepetto::VERSION}"
9
+ exit(0)
10
+ end
11
+
12
+ require 'rubigen/scripts/generate'
13
+ RubiGen::Base.use_application_sources!
14
+
15
+ RubiGen::Base.prepend_sources(
16
+ RubiGen::PathSource.new(:app, File.join(File.dirname(__FILE__), "..", "app_generators"))
17
+ )
18
+ RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'gepetto')
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{gepetto}
5
+ s.version = "0.0.7"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Alban Peignier"]
9
+ s.date = %q{2009-08-20}
10
+ s.default_executable = %q{gepetto}
11
+ s.description = %q{A helper suite for Puppet projects to create, manage and help daily development
12
+
13
+ More information about Puppet: http://reductivelabs.com/trac/puppet/}
14
+ s.email = ["alban.peignier@free.fr"]
15
+ s.executables = ["gepetto"]
16
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
17
+ s.files = ["History.txt", "Manifest.txt", "README.rdoc", "Rakefile", "app_generators/gepetto/USAGE", "app_generators/gepetto/gepetto_generator.rb", "app_generators/gepetto/templates/Rakefile", "app_generators/gepetto/templates/config/fileserver.conf", "app_generators/gepetto/templates/config/puppet.conf", "app_generators/gepetto/templates/manifests/classes/empty.pp", "app_generators/gepetto/templates/manifests/defaults.pp", "app_generators/gepetto/templates/manifests/nodes.pp", "app_generators/gepetto/templates/manifests/site.pp", "app_generators/gepetto/templates/manifests/templates.pp", "app_generators/gepetto/templates/script/module", "app_generators/gepetto/templates/script/puppetca", "app_generators/gepetto/templates/script/puppetmasterd", "app_generators/gepetto/templates/script/puppetrun", "bin/gepetto", "gepetto.gemspec", "lib/gepetto.rb", "lib/gepetto/tasks.rb", "puppet_generators/module/USAGE", "puppet_generators/module/module_generator.rb", "puppet_generators/module/templates/README", "puppet_generators/module/templates/manifests/init.pp", "script/destroy", "script/generate", "tasks/host.pp", "tasks/log.rake", "tasks/puppet.rake", "tasks/sandbox.pp", "tasks/sandbox.rake", "tasks/tmp.rake", "tasks/utils.rake"]
18
+ s.homepage = %q{http://github.com/albanpeignier/gepetto/}
19
+ s.rdoc_options = ["--main", "README.rdoc"]
20
+ s.require_paths = ["lib"]
21
+ s.rubyforge_project = %q{gepetto}
22
+ s.rubygems_version = %q{1.3.4}
23
+ s.summary = %q{A helper suite for Puppet projects to create, manage and help daily development More information about Puppet: http://reductivelabs.com/trac/puppet/}
24
+
25
+ if s.respond_to? :specification_version then
26
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
27
+ s.specification_version = 3
28
+
29
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
30
+ s.add_development_dependency(%q<hoe>, [">= 2.3.3"])
31
+ else
32
+ s.add_dependency(%q<hoe>, [">= 2.3.3"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<hoe>, [">= 2.3.3"])
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module Gepetto
5
+ VERSION = '0.0.8'
6
+ end
@@ -0,0 +1 @@
1
+ Dir[File.join(File.dirname(__FILE__), %w[.. .. tasks], '**/*.rake')].each { |rake| load rake }
@@ -0,0 +1,18 @@
1
+ Description:
2
+ Create an empty module organisation as specified by
3
+ http://reductivelabs.com/trac/puppet/wiki/ModuleOrganisation
4
+
5
+ This generates a module directories and files into modules/<modulename>.
6
+
7
+ Example:
8
+ ./script/generate module passenger
9
+
10
+ creates directories :
11
+ modules/passenger/templates
12
+ modules/passenger/files
13
+ modules/passenger/depends
14
+
15
+ creates files :
16
+ modules/passenger/README
17
+ modules/passenger/manifests/init.pp
18
+ modules/passenger/manifests/defaults.pp
@@ -0,0 +1,24 @@
1
+ require 'rbconfig'
2
+
3
+ class ModuleGenerator < RubiGen::Base
4
+
5
+ attr_reader :module_name
6
+
7
+ def initialize(runtime_args, runtime_options = {})
8
+ super
9
+ usage if args.empty?
10
+ @module_name = args.shift
11
+ @destination_root = "modules/#{module_name}"
12
+ end
13
+
14
+ def manifest
15
+ record do |m|
16
+ # Root directory and all subdirectories.
17
+ m.directory ''
18
+ %w{manifests files templates}.each { |path| m.directory path }
19
+ m.template_copy_each %w( README )
20
+ m.template_copy_each %w( init.pp ), 'manifests'
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,75 @@
1
+ Exec { path => "/usr/bin:/usr/sbin/:/bin:/sbin" }
2
+
3
+ # install qemu
4
+
5
+ package { qemu:
6
+ ensure => "latest"
7
+ }
8
+
9
+ # compile kqemu module
10
+
11
+ package { kqemu-source:
12
+ ensure => "latest",
13
+ require => Package[qemu]
14
+ }
15
+
16
+ exec { "modass-kqemu":
17
+ # modass returns 249 with non-inter ...
18
+ command => 'module-assistant --non-inter a-i kqemu || dpkg -l "kqemu-modules-`uname -r`" | grep ^ii',
19
+ unless => 'dpkg -l "kqemu-modules-`uname -r`" | grep ^ii',
20
+ require => Package[kqemu-source]
21
+ }
22
+
23
+ exec { "add kqemu in /etc/modules":
24
+ command => "echo kqemu >> /etc/modules",
25
+ unless => "grep kqemu /etc/modules",
26
+ require => Exec["modass-kqemu"]
27
+ }
28
+
29
+ exec { "modprobe-kqemu":
30
+ command => "modprobe kqemu",
31
+ unless => "lsmod | grep kqemu",
32
+ require => Exec["modass-kqemu"]
33
+ }
34
+
35
+ file { "/dev/kqemu":
36
+ # default permissions on debian, but not on ubuntu
37
+ mode => 666,
38
+ require => Exec["modprobe-kqemu"]
39
+ }
40
+
41
+ # install uml-utilities for tunctl
42
+
43
+ package { uml-utilities: }
44
+
45
+ exec { "add tun in /etc/modules":
46
+ command => "echo tun >> /etc/modules",
47
+ unless => "grep tun /etc/modules"
48
+ }
49
+
50
+ exec { "modprobe tun":
51
+ unless => "lsmod | grep tun"
52
+ }
53
+
54
+ file { "/dev/net/tun":
55
+ mode => 666
56
+ }
57
+
58
+ # provide a basic qemu-ifup
59
+
60
+ file { "/etc/qemu-ifup":
61
+ mode => 755,
62
+ content => '#!/bin/sh -x
63
+
64
+ if [ "$USER" != "root" -o "$1" != "sudo" ]; then
65
+ exec sudo -p "Password for $0:" $0 sudo $1
66
+ fi
67
+
68
+ [ "$1" = "sudo" ] && shift
69
+
70
+ /sbin/ifconfig $1 172.20.0.1
71
+ iptables -t nat -A POSTROUTING -s 172.20.0.1/24 -o eth0 -j MASQUERADE
72
+ sysctl -w net.ipv4.ip_forward=1
73
+ ',
74
+ require => Package[qemu]
75
+ }
@@ -0,0 +1,9 @@
1
+ namespace :log do
2
+ desc "Truncates all *.log files in log/ to zero bytes"
3
+ task :clear do
4
+ FileList["log/*.log"].each do |log_file|
5
+ f = File.open(log_file, "w")
6
+ f.close
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ namespace :puppet do
2
+ desc "Check syntax of puppet manifests"
3
+ task :check_syntax do
4
+ FileList['manifests/**/*.pp'].each do |manifest|
5
+ sh "puppet --color=false --confdir=/tmp --vardir=/tmp --parseonly --ignoreimport #{manifest}"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,101 @@
1
+ # Minimal settings to boot sandbox image with qemu
2
+
3
+ # These variables are defined by rake task
4
+ #$host_ip='172.20.0.1'
5
+ #$sandbox_ip='172.20.0.2'
6
+
7
+ Exec { path => "/usr/bin:/usr/sbin/:/bin:/sbin" }
8
+
9
+ file { "/etc/fstab":
10
+ content => "/dev/hda1 / ext3 errors=remount-ro 0 1
11
+ proc /proc proc defaults 0 0
12
+ "
13
+ }
14
+
15
+ # Console on ttyS0 not tty1
16
+ exec { "inittab-ttyS0-getty":
17
+ command => "sed -i '/getty 38400 tty1/ s/tty1/ttyS0/' /etc/inittab",
18
+ unless => "grep 'getty 38400 ttyS0' /etc/inittab"
19
+ }
20
+
21
+ # and no other gettys
22
+ exec { "inittab-no-tty-gettys":
23
+ command => "sed -i '/getty 38400 tty[23456]/ d' /etc/inittab",
24
+ onlyif => "grep 'getty 38400 tty[23456]' /etc/inittab"
25
+ }
26
+
27
+ file { "/etc/hostname":
28
+ content => "sandbox"
29
+ }
30
+
31
+ # an host object doesn't find a provider
32
+ file { "/etc/hosts":
33
+ content => "127.0.0.1 localhost
34
+ 127.0.1.1 sandbox
35
+ $host_ip puppet
36
+ "
37
+ }
38
+
39
+ # root's password is 'root'
40
+ user { root:
41
+ password => '$1$aybpiIGf$cB7iFDNZvViQtQjEZ5HFQ0'
42
+ }
43
+
44
+ package { [console-common,console-tools,console-data,base-config,man-db,manpages]:
45
+ ensure => absent
46
+ }
47
+
48
+ # if network configuration changes, eth0 is renamed by udev :-/
49
+ file { "/etc/udev/rules.d/70-persistent-net.rules":
50
+ ensure => absent
51
+ }
52
+
53
+ file { "/etc/network/interfaces":
54
+ content => "auto lo
55
+ iface lo inet loopback
56
+
57
+ auto eth0
58
+ iface eth0 inet static
59
+ address $sandbox_ip
60
+ netmask 255.255.255.0
61
+ gateway $host_ip
62
+ dns-nameservers $host_ip
63
+ "
64
+ }
65
+
66
+ # puppet configuration
67
+
68
+ file { "/etc/default/puppet":
69
+ content => 'START=no
70
+ DAEMON_OPTS="-w 5"
71
+ '
72
+ }
73
+
74
+ file { "/etc/puppet/namespaceauth.conf":
75
+ content => "[puppetrunner]
76
+ allow $host_ip
77
+ "
78
+ }
79
+
80
+ file { "/etc/puppet/puppet.conf":
81
+ content => '[main]
82
+ logdir=/var/log/puppet
83
+ vardir=/var/lib/puppet
84
+ ssldir=/var/lib/puppet/ssl
85
+ rundir=/var/run/puppet
86
+ factpath=$vardir/lib/facter
87
+ pluginsync=false
88
+ color=false
89
+
90
+ [puppetd]
91
+ report=true
92
+ # run puppetd .. every day
93
+ runinterval = 86400
94
+ listen=true
95
+ '
96
+ }
97
+
98
+ exec { "syslog-to-ttyS0":
99
+ command => "echo '*.* -/dev/ttyS0' >> /etc/rsyslog.conf",
100
+ unless => 'grep /dev/ttyS0 /etc/rsyslog.conf'
101
+ }
@@ -0,0 +1,437 @@
1
+ require 'rake/tasklib'
2
+ require 'ping'
3
+ require 'tempfile'
4
+
5
+ class Sandbox < Rake::TaskLib
6
+
7
+ def self.default_architecture
8
+ case PLATFORM
9
+ when /x86_64/
10
+ "amd64"
11
+ else
12
+ "i386"
13
+ end
14
+ end
15
+
16
+ @@images_directory = "/var/tmp"
17
+
18
+ def self.images_directory
19
+ @@images_directory
20
+ end
21
+
22
+ def self.images_directory=(directory)
23
+ @@images_directory = directory
24
+ end
25
+
26
+ def puppet_file(name)
27
+ File.dirname(__FILE__) + "/#{name}.pp"
28
+ end
29
+
30
+ attr_reader :name
31
+ attr_accessor :bootstraper, :ip_address, :host_ip_address, :tap_device
32
+ attr_accessor :disk_size, :memory_size, :mount_point
33
+ attr_accessor :architecture
34
+
35
+ def initialize(name = "sandbox")
36
+ @name = name
37
+
38
+ init
39
+ yield self if block_given?
40
+ define
41
+ end
42
+
43
+ def define
44
+ @architecture = Sandbox.default_architecture
45
+ bootstraper = DebianBoostraper.new
46
+
47
+ @ip_address ||= '172.20.0.2'
48
+ @host_ip_address ||= @ip_address.gsub(/\.[0-9]+$/,'.1')
49
+
50
+ @disk_size ||= '512M'
51
+ @memory_size ||= '128M'
52
+
53
+ @mount_point ||= "/mnt/#{name}"
54
+ @tap_device ||= 'tap0'
55
+ end
56
+
57
+ def bootstraper=(bootstraper)
58
+ @bootstraper = bootstraper
59
+ sync_architecture
60
+ end
61
+
62
+ def architecture=(architecture)
63
+ @architecture = architecture
64
+ sync_architecture
65
+ end
66
+
67
+ def sync_architecture
68
+ if self.bootstraper and self.architecture
69
+ self.bootstraper.architecture = self.architecture
70
+ end
71
+ end
72
+
73
+ def kernel_architecture
74
+ case self.architecture
75
+ when 'i386'
76
+ '686'
77
+ else
78
+ self.architecture
79
+ end
80
+ end
81
+
82
+ def init
83
+ namespace @name do
84
+
85
+ desc "Setup local machine to host sandbox"
86
+ task :setup => 'tmp:create' do
87
+ sudo "puppet #{puppet_file(:host)}"
88
+ end
89
+
90
+ # Mix between these ways :
91
+ # - http://www.mail-archive.com/qemu-devel@nongnu.org/msg01208.html
92
+ # - http://www.geocities.com/gtalon51/Articles/Minimal_Linux_system/Minimal_Linux_system.html
93
+ # - qemu-make-debian-root
94
+ namespace :create do
95
+ task :image do
96
+ sh "qemu-img create -f raw #{disk_image} #{disk_size}"
97
+ # create the partition table
98
+ sh "echo '63,' | /sbin/sfdisk --no-reread -uS -H16 -S63 #{disk_image}"
99
+ end
100
+
101
+ task :fs do
102
+ # format the filesystem
103
+ begin
104
+ sudo "losetup -o #{fs_offset} /dev/loop0 #{disk_image}"
105
+
106
+ # because '/sbin/sfdisk -s /dev/loop0' returns a wrong value :
107
+ extract_fs_block_size = "/sbin/sfdisk -l #{disk_image} 2> /dev/null | awk '/img1/ { gsub(\"\\+\", \"\", $5); print $5 }'"
108
+
109
+ sudo "/sbin/mke2fs -jqF /dev/loop0 `#{extract_fs_block_size}`"
110
+ ensure
111
+ sudo "losetup -d /dev/loop0"
112
+ end
113
+ end
114
+
115
+ task :system do
116
+ # install a debian base system
117
+ mount do
118
+ bootstraper.bootstrap mount_point
119
+ end
120
+ end
121
+
122
+ task :kernel do
123
+ mount do
124
+ chroot_sh "echo 'do_initrd = Yes' >> /etc/kernel-img.conf"
125
+
126
+ kernel_package =
127
+ case self.bootstraper.version
128
+ when 'etch', 'lenny'
129
+ "linux-image-2.6-#{kernel_architecture}"
130
+ when 'hardy'
131
+ 'linux-image-2.6.24-16-generic'
132
+ when 'intrepid'
133
+ 'linux-image-generic'
134
+ end
135
+
136
+ chroot_sh "DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes #{kernel_package}"
137
+ end
138
+ end
139
+
140
+ task :grub do
141
+ mount do
142
+ chroot_sh "DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes grub"
143
+
144
+ grub_dir = "#{mount_point}/boot/grub"
145
+ chroot_sh "mkdir /boot/grub" unless File.exists?(grub_dir)
146
+
147
+ stage_files = Dir["#{mount_point}/usr/lib/grub/**/stage?", "#{mount_point}/usr/lib/grub/**/e2fs_stage1_5"]
148
+ sudo "cp #{stage_files.join(' ')} #{grub_dir}"
149
+
150
+ Tempfile.open('menu_lst') do |f|
151
+ f.write(['default 0',
152
+ 'timeout 0',
153
+ 'title Linux',
154
+ 'root (hd0,0)',
155
+ 'kernel /vmlinuz root=/dev/hda1 ro',
156
+ 'initrd /initrd.img'].join("\n"))
157
+ f.close
158
+ sudo "cp #{f.path} #{grub_dir}/menu.lst"
159
+ end
160
+ end
161
+
162
+ Tempfile.open('grub_input') do |f|
163
+ f.write(["device (hd0) #{disk_image}",
164
+ "root (hd0,0)",
165
+ "setup (hd0)",
166
+ "quit"].join("\n"))
167
+ f.close
168
+ sudo "grub --device-map=/dev/null < #{f.path}"
169
+ end
170
+ end
171
+
172
+ task :config do
173
+ Tempfile.open('sandbox_puppet_file') do |sandbox_puppet_file|
174
+ sandbox_puppet_file.puts "$host_ip='#{host_ip_address}'"
175
+ sandbox_puppet_file.puts "$sandbox_ip='#{ip_address}'"
176
+ sandbox_puppet_file.puts IO.read(puppet_file(:sandbox))
177
+
178
+ sandbox_puppet_file.close
179
+
180
+ # finalize configuration with puppet
181
+ mount do
182
+ sudo "cp #{sandbox_puppet_file.path} #{mount_point}/etc/sandbox.pp"
183
+ sudo "chroot #{mount_point} puppet /etc/sandbox.pp"
184
+ end
185
+ end
186
+ end
187
+
188
+ task :tap_device do
189
+ unless tap_device_exists?
190
+ sudo "tunctl -u #{ENV['USER']} -t #{tap_device}"
191
+ end
192
+ end
193
+
194
+ task :snapshot do
195
+ snapshot(:initial)
196
+ end
197
+ end
198
+
199
+ desc "Create a fresh image for sandbox"
200
+ task :create => [ 'clean', 'create:image', 'create:fs', 'create:system', 'create:kernel', 'create:grub', 'create:config', 'create:snapshot' ]
201
+
202
+ desc "Destroy sandbox images"
203
+ task :destroy => 'clean' do
204
+ rm_f disk_image
205
+ rm_f disk_image(:initial)
206
+ end
207
+
208
+ desc "Start sandbox"
209
+ task :start => ['create:tap_device', 'tmp:create'] do
210
+ start
211
+ end
212
+
213
+ desc "Start sandbox from initial image in snapshot"
214
+ task :start_from_initial do
215
+ start :hda => disk_image(:initial), :snapshot => true
216
+ end
217
+
218
+ task :wait do
219
+ wait
220
+ end
221
+
222
+ desc "Stop sandbox"
223
+ task :stop do
224
+ sh "kill -9 `cat tmp/run/#{name}.pid`"
225
+ end
226
+
227
+ task :revert do
228
+ sh "qemu-img convert -O raw #{disk_image(:initial)} #{disk_image}"
229
+ end
230
+
231
+ task :mount do
232
+ mount_image
233
+ end
234
+
235
+ task :umount do
236
+ umount_image
237
+ end
238
+
239
+ task :clean => 'puppet:clean' do
240
+ # clean known_hosts
241
+ known_hosts_file="#{ENV['HOME']}/.ssh/known_hosts"
242
+ sh "sed -i '/#{hostname},#{ip_address}/ d' #{known_hosts_file}" if File.exists?(known_hosts_file)
243
+ end
244
+
245
+ task :status do
246
+ status
247
+ end
248
+
249
+ namespace :puppet do
250
+
251
+ desc "Run puppetd in sandbox"
252
+ task :run do
253
+ sh "./script/puppetrun --host #{hostname}"
254
+ end
255
+
256
+ desc "Sign a request from sandbox"
257
+ task :sign do
258
+ sh "./script/puppetca --sign #{hostname}"
259
+ end
260
+
261
+ task :clean do
262
+ # remove pending request
263
+ sh "rm -f ssl/ca/requests/#{hostname}*.pem"
264
+ # remove signed certificat
265
+ sh "./script/puppetca --clean #{hostname} || true"
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ def start(options = {})
272
+ options = {
273
+ :daemonize => true,
274
+ :snapshot => ENV['SNAPSHOT'],
275
+ :hda => disk_image,
276
+ :nographic => false,
277
+ :m => memory_size,
278
+ :net => ["nic", "tap,ifname=#{tap_device}"]
279
+ }.update(options)
280
+
281
+ if options[:daemonize]
282
+ options = {
283
+ :pidfile => File.expand_path("tmp/run/#{name}.pid"), :serial => "file:" + File.expand_path("log/#{name}.log")
284
+ }.update(options)
285
+ end
286
+
287
+ options_as_string = options.collect do |name,value|
288
+ argument = "-#{name}"
289
+
290
+ case value
291
+ when Array
292
+ value.collect { |v| "#{argument} '#{v}'" }
293
+ when true
294
+ argument
295
+ when false
296
+ when nil
297
+ when ''
298
+ nil
299
+ else
300
+ "#{argument} '#{value}'"
301
+ end
302
+ end.compact.join(' ')
303
+
304
+ qemu_command =
305
+ case PLATFORM
306
+ when /x86_64/
307
+ "qemu-system-x86_64"
308
+ else
309
+ "qemu"
310
+ end
311
+
312
+ sh "#{qemu_command} #{options_as_string}"
313
+ end
314
+
315
+ def snapshot(name)
316
+ sh "qemu-img convert -O qcow2 #{disk_image} #{disk_image(name)}"
317
+ end
318
+
319
+ def wait(timeout = 30, max_try_count = 5)
320
+ try_count = 5
321
+ try_timeout = timeout / try_count
322
+
323
+ 5.times do
324
+ if Ping.pingecho(ip_address, try_timeout)
325
+ return
326
+ else
327
+ sleep try_timeout
328
+ end
329
+ end
330
+
331
+ raise "no response from #{hostname} after #{timeout} seconds"
332
+ end
333
+
334
+ def mount(&block)
335
+ begin
336
+ mount_image
337
+ yield mount_point
338
+ ensure
339
+ umount_image
340
+ end
341
+ end
342
+
343
+ def mount_image
344
+ sudo "mkdir #{mount_point}" unless File.exists? mount_point
345
+ sudo "mount -o loop,offset=#{fs_offset} #{disk_image} #{mount_point}"
346
+
347
+ sudo "mount proc #{mount_point}/proc -t proc" if File.exists? "#{mount_point}/proc"
348
+ end
349
+
350
+ def umount_image
351
+ [ "#{mount_point}/proc", mount_point ].each do |mount|
352
+ sudo "umount #{mount} || true"
353
+ end
354
+ end
355
+
356
+ # TODO to be customizable
357
+
358
+ def disk_image(suffix = nil)
359
+ suffix = "-#{suffix}" if suffix
360
+ File.join Sandbox.images_directory, "#{name}#{suffix}.img"
361
+ end
362
+
363
+ def fs_offset
364
+ 32256
365
+ end
366
+
367
+ def hostname
368
+ if name =~ /^sandbox/
369
+ name
370
+ else
371
+ "sandbox-#{name}"
372
+ end
373
+ end
374
+
375
+ def tap_device_exists?
376
+ IO.readlines('/proc/net/dev').any? { |l| l =~ /\s+#{tap_device}/ }
377
+ end
378
+
379
+ def status
380
+ puts "#{hostname} status:"
381
+ puts self.inspect
382
+ end
383
+
384
+ def chroot_sh(cmd)
385
+ sudo "chroot #{mount_point} sh -c \"#{cmd}\""
386
+ end
387
+
388
+ end
389
+
390
+ class DebianBoostraper
391
+
392
+ attr_accessor :version, :mirror, :include, :exclude, :architecture
393
+
394
+ def initialize(&block)
395
+ default_attributes
396
+
397
+ @include = %w{puppet ssh udev resolvconf}
398
+ @exclude = %w{syslinux at exim mailx libstdc++2.10-glibc2.2 mbr setserial fdutils info ipchains iptables lilo pcmcia-cs ppp pppoe pppoeconf pppconfig telnet exim4 exim4-base exim4-config exim4-daemon-light pciutils modconf tasksel console-common console-tools console-data base-config man-db manpages}
399
+
400
+ yield self if block_given?
401
+ end
402
+
403
+ def default_attributes
404
+ @version = 'lenny'
405
+ @mirror = 'http://ftp.debian.org/debian'
406
+ @architecture = Sandbox.default_architecture
407
+ end
408
+
409
+ def bootstrap(root)
410
+ options_as_string = options.collect{|k,v| "--#{k} #{Array(v).join(',')}"}.join(' ')
411
+ sudo "debootstrap #{options_as_string} #{version} #{root} #{mirror}"
412
+ end
413
+
414
+ def options
415
+ {
416
+ :arch => architecture,
417
+ :exclude => @exclude,
418
+ :include => @include
419
+ }
420
+ end
421
+
422
+ end
423
+
424
+ class UbuntuBoostraper < DebianBoostraper
425
+
426
+ def default_attributes
427
+ super
428
+
429
+ @version = 'intrepid'
430
+ @mirror = 'http://archive.ubuntu.com/ubuntu/'
431
+ end
432
+
433
+ def options
434
+ super.update :components => 'main,universe'
435
+ end
436
+
437
+ end