File Coverage

lib/Provision/Unix/VirtualOS/Linux.pm
Criterion Covered Total %
statement 30 267 11.2
branch 0 142 0.0
condition 0 26 0.0
subroutine 8 23 34.7
pod 0 16 0.0
total 38 474 8.0


line stmt bran cond sub pod time code
1             package Provision::Unix::VirtualOS::Linux;
2             # ABSTRACT: a framework for building Linux virtual machines
3              
4 7     7   781 use strict;
  7         25  
  7         614  
5 7     7   65 use warnings;
  7         27  
  7         697  
6              
7             our $VERSION = '0.27';
8              
9 7     7   1014 use File::Copy;
  7         2826  
  7         960  
10 7     7   59 use File::Path;
  7         27  
  7         720  
11             #use English qw( -no_match_vars );
12 7     7   66 use Params::Validate qw(:all);
  7         28  
  7         2890  
13              
14 7     7   97 use lib 'lib';
  7         20  
  7         145  
15 7     7   1417 use Provision::Unix;
  7         21  
  7         34090  
16              
17             my ($prov, $vos, $util);
18             my %std_opts = ( debug => 0, fatal => 0 );
19              
20             sub new {
21 7     7 0 28 my $class = shift;
22 7         214 my %p = validate(@_, { vos => { type => OBJECT } } );
23              
24 7         57 $vos = $p{vos};
25 7         28 $prov = $vos->{prov};
26 7         35 $util = $vos->{util};
27              
28 7         34 my $self = {
29             vos => $vos,
30             util => $util,
31             };
32 7         37 bless $self, $class;
33              
34 7         154 $prov->audit( $class . sprintf( " loaded by %s, %s, %s", caller ) );
35              
36 7         50 return $self;
37             }
38              
39             sub get_distro {
40 0     0 0   my $self = shift;
41 0           my $fs_root = shift;
42 0 0         return if ! $fs_root;
43 0           my $etc = "$fs_root/etc";
44 0 0         return if ! -d "$fs_root/etc";
45              
46             # credit to Max Vohra for distro detection logic
47             return
48 0 0         -e "$etc/debian_version" ? 'debian'
    0          
    0          
    0          
    0          
    0          
49             : -e "$etc/redhat-release" ? 'redhat'
50             : -e "$etc/SuSE-release" ? 'suse'
51             : -e "$etc/slackware-version" ? 'slackware'
52             : -e "$etc/gentoo-release" ? 'gentoo'
53             : -e "$etc/arch-release" ? 'arch'
54             : undef;
55             };
56              
57             sub get_package_manager {
58 0 0   0 0   my $distro = shift or return;
59 0 0         return $distro eq 'debian' ? 'apt'
    0          
    0          
    0          
    0          
    0          
60             : $distro eq 'redhat' ? 'yum'
61             : $distro eq 'suse' ? 'zypper'
62             : $distro eq 'slackware' ? undef
63             : $distro eq 'gentoo' ? 'emerge'
64             : $distro eq 'arch' ? 'packman'
65             : return;
66             };
67              
68             sub install_kernel_modules {
69 0     0 0   my $self = shift;
70 0           my %p = validate(@_,
71             { fs_root => { type => SCALAR, },
72             url => { type => SCALAR, optional => 1 },
73             version => { type => SCALAR, optional => 1 },
74             test_mode => { type => BOOLEAN, optional => 1 },
75             },
76             );
77              
78 0           my $fs_root = $p{fs_root};
79 0   0       my $url = $p{url} || 'http://mirror.vpslink.com/xen';
80 0           my $version = $p{version} = `uname -r`; chomp $version;
  0            
81              
82 0 0         return 1 if $p{test_mode};
83              
84 0 0         if ( -d "/boot/domU" ) {
85 0           my ($modules) = glob("/boot/domU/modules*$version*");
86 0 0         $modules or return $prov->error(
87             "unable to find kernel modules in /boot/domU", %std_opts);
88 0           my $module_dir = "$fs_root/lib/modules";
89 0 0         if ( ! -d $module_dir ) {
90 0 0         mkpath $module_dir
91             or return $prov->error("unable to create $module_dir", %std_opts);
92             };
93 0           my $cmd = "tar -zxpf $modules -C $module_dir";
94 0 0         $util->syscmd( $cmd, %std_opts ) or return;
95             }
96             else {
97 0           chdir $fs_root;
98 0           foreach my $mod ( qw/ modules headers / ) {
99             # foreach my $mod ( qw/ modules module-fuse headers / ) {
100 0 0 0       next if $mod eq 'headers' && ! "$fs_root/usr/src";
101 0           my $file = "xen-$mod-$version.tar.gz";
102 0 0         $util->get_url( "$url/$file", %std_opts ) or return;
103 0 0         $util->syscmd( "tar -zxpf $file -C $fs_root", %std_opts ) or return;
104 0           unlink $file;
105             };
106 0           chdir "/home/xen";
107             };
108              
109             # clean up behind template authors
110 0 0         unlink "$fs_root/.bash_history" if -e "$fs_root/.bash_history";
111 0 0         unlink "$fs_root/root/.bash_history" if -e "$fs_root/root/.bash_history";
112 0           return 1;
113             };
114              
115             sub set_rc_local {
116 0     0 0   my $self = shift;
117 0           my %p = validate(@_, { fs_root => { type => SCALAR } } );
118              
119 0           my $fs_root = $p{fs_root};
120              
121 0           my $rc_local = "$fs_root/etc/conf.d/local.start"; # gentoo
122 0 0         if ( ! -f $rc_local ) {
123 0           $rc_local = "$fs_root/etc/rc.local"; # everything else
124             };
125              
126 0           return $util->file_write( $rc_local,
127             lines => [ 'pkill -9 -f nash',
128             'ldconfig > /dev/null',
129             'depmod -a > /dev/null',
130             'exit 0',
131             ],
132             mode => '0755',
133             append => 0,
134             %std_opts,
135             );
136             };
137              
138             sub set_ips {
139 0     0 0   my $self = shift;
140 0           my %p = validate(@_,
141             { ips => { type => ARRAYREF },
142             fs_root => { type => SCALAR },
143             distro => { type => SCALAR, optional => 1 },
144             device => { type => SCALAR, optional => 1 },
145             hostname => { type => SCALAR, optional => 1 },
146             test_mode => { type => BOOLEAN, optional => 1 },
147             }
148             );
149              
150 0           my $distro = delete $p{distro};
151 0   0       $distro ||= $self->get_distro( $p{fs_root} );
152              
153 0 0         return 1 if $p{test_mode};
154              
155 0 0         return $self->set_ips_debian(%p) if $distro =~ /debian|ubuntu/i;
156 0 0         return $self->set_ips_redhat(%p) if $distro =~ /redhat|fedora|centos/i;
157 0 0         return $self->set_ips_gentoo(%p) if $distro =~ /gentoo/i;
158              
159 0           $prov->error( "unable to set up networking on distro $distro", %std_opts );
160 0           return;
161             };
162              
163             sub set_ips_debian {
164 0     0 0   my $self = shift;
165 0           my %p = validate(@_,
166             { ips => { type => ARRAYREF },
167             fs_root => { type => SCALAR },
168             device => { type => SCALAR, optional => 1 },
169             hostname => { type => SCALAR, optional => 1 },
170             test_mode => { type => BOOLEAN, optional => 1 },
171             }
172             );
173              
174 0   0       my $device = $p{device} || 'eth0';
175 0           my @ips = @{ $p{ips} };
  0            
176 0           my $test_mode = $p{test_mode};
177 0           my $hostname = $p{hostname};
178 0           my $fs_root = $p{fs_root};
179              
180 0           my $ip = shift @ips;
181 0           my @octets = split /\./, $ip;
182 0           my $gw = "$octets[0].$octets[1].$octets[2].1";
183 0           my $net = "$octets[0].$octets[1].$octets[2].0";
184              
185 0           my $config = <<EO_FIRST_IP
186             # This configuration file is generated by Provision::Unix.
187             # WARNING: Do not edit this file, else your changes will be lost.
188              
189             # Auto generated interfaces
190             auto $device lo
191             iface lo inet loopback
192             iface $device inet static
193             address $ip
194             netmask 255.255.255.0
195             up route add -net $net netmask 255.255.255.0 dev $device
196             up route add default gw $gw
197             EO_FIRST_IP
198             ;
199              
200 0           my $alias_count = 0;
201 0           foreach ( @ips ) {
202 0           $config .= <<EO_ADDTL_IPS
203              
204             auto $device:$alias_count
205             iface $device:$alias_count inet static
206             address $_
207             netmask 255.255.255.255
208             broadcast 0.0.0.0
209             EO_ADDTL_IPS
210             ;
211 0           $alias_count++;
212             };
213             #return $config;
214              
215 0           my $config_file = "/etc/network/interfaces";
216 0 0         return $config if $test_mode;
217              
218 0 0         if ( $util->file_write( "$fs_root/$config_file",
219             lines => [ $config ],
220             %std_opts,
221             )
222             )
223             {
224 0           $prov->audit( "updated debian $config_file with network settings");
225             }
226             else {
227 0           $prov->error( "failed to update $config_file with network settings", %std_opts);
228             };
229              
230 0 0         if ( $hostname) {
231 0           $self->set_hostname_debian( host => $hostname, fs_root => $fs_root );
232             };
233 0           return $config;
234             };
235              
236             sub set_ips_gentoo {
237 0     0 0   my $self = shift;
238 0           my %p = validate(@_,
239             { ips => { type => ARRAYREF },
240             fs_root => { type => SCALAR },
241             device => { type => SCALAR, optional => 1 },
242             gw_octet => { type => SCALAR, optional => 1 },
243             hostname => { type => SCALAR, optional => 1 },
244             test_mode => { type => BOOLEAN, optional => 1 },
245             }
246             );
247              
248 0   0       my $device = $p{device} || 'eth0';
249 0           my @ips = @{ $p{ips} };
  0            
250 0           my $test_mode= $p{test_mode};
251 0           my $hostname = $p{hostname};
252 0           my $fs_root = $p{fs_root};
253              
254 0           my $ip = shift @ips;
255 0           my @octets = split /\./, $ip;
256 0   0       my $gw_octet = $p{gw_octet} || 1;
257 0           my $gw = "$octets[0].$octets[1].$octets[2].$gw_octet";
258              
259 0           my $conf_dir = "$fs_root/etc/conf.d";
260 0           my $net_conf = "$conf_dir/net";
261              
262 0           my (@lines, @new_lines);
263 0 0         if ( -r $net_conf ) {
264 0 0         @lines = $util->file_read( $net_conf, %std_opts )
265             or $prov->error("error trying to read /etc/conf.d/net", %std_opts);
266             };
267 0           foreach ( @lines ) {
268 0 0         next if $_ =~ /^config_$device/;
269 0 0         next if $_ =~ /^routes_$device/;
270 0           push @new_lines, $_;
271             };
272 0           my $ip_string = "config_$device=( \n\t\"$ip/24\"";
273 0           foreach ( @ips ) { $ip_string .= "\n\t\"$_/32\""; };
  0            
274 0           $ip_string .= ")";
275 0           push @new_lines, $ip_string;
276 0           push @new_lines, "routes_$device=(\n\t\"default via $gw\"\n)";
277 0           $prov->audit("net config: $ip_string");
278 0 0         $util->file_write( $net_conf, lines => \@new_lines, %std_opts )
279             or return $prov->error(
280             "error setting up networking, unable to write to $net_conf", %std_opts);
281              
282 0           return 1;
283             #my $script = "/etc/runlevels/default/net.$device";
284             };
285              
286             sub set_ips_redhat {
287 0     0 0   my $self = shift;
288 0           my %p = validate(@_,
289             { ips => { type => ARRAYREF },
290             fs_root => { type => SCALAR },
291             device => { type => SCALAR, optional => 1 },
292             gw_octet => { type => SCALAR, optional => 1 },
293             hostname => { type => SCALAR, optional => 1 },
294             test_mode => { type => BOOLEAN, optional => 1 },
295             }
296             );
297              
298 0           my $etc = "$p{fs_root}/etc";
299 0   0       my $device = $p{device} || 'eth0';
300 0           my @ips = @{ $p{ips} };
  0            
301 0   0       my $hostname = $p{hostname} || 'localhost';
302 0           my $test_mode = $p{test_mode};
303              
304 0           my $ip = shift @ips;
305 0           my @octets = split /\./, $ip;
306 0   0       my $gw_octet = $p{gw_octet} || 1;
307 0           my $gw = "$octets[0].$octets[1].$octets[2].$gw_octet";
308 0           my $net = "$octets[0].$octets[1].$octets[2].0";
309              
310 0           my $netfile = "sysconfig/network";
311 0           my $if_file = "sysconfig/network-scripts/ifcfg-$device";
312 0           my $route_f = "sysconfig/network-scripts/route-$device";
313 0           my $errors_before = scalar @{ $prov->{errors} };
  0            
314              
315             # cleanup any existing files that may no longer be valid
316 0           unlink <$etc/$if_file*>;
317              
318 0           my $contents = <<EO_NETFILE
319             NETWORKING="yes"
320             GATEWAY="$gw"
321             HOSTNAME="$hostname"
322             EO_NETFILE
323             ;
324 0 0         return $contents if $test_mode;
325 0           my $r = $util->file_write( "$etc/$netfile", lines => [ $contents ], %std_opts );
326 0 0         $r ? $prov->audit("updated /etc/$netfile with hostname $hostname and gateway $gw")
327             : $prov->error("failed to update $netfile", fatal => 0);
328              
329 0           $contents = <<EO_IF_FILE
330             DEVICE=$device
331             BOOTPROTO=static
332             ONBOOT=yes
333             IPADDR=$ip
334             NETMASK=255.255.255.0
335             EO_IF_FILE
336             ;
337 0           $r = $util->file_write( "$etc/$if_file", lines => [ $contents ], %std_opts );
338 0 0         $r ? $prov->audit("updated /etc/$if_file with ip $ip")
339             : $prov->error("failed to update $if_file", %std_opts);
340              
341 0           $contents = <<EO_ROUTE_FILE
342             $net/24 dev $device scope host
343             default via $gw
344             EO_ROUTE_FILE
345             ;
346 0           $r = $util->file_write( "$etc/$route_f", lines => [ $contents ], %std_opts );
347 0 0         $r ? $prov->audit("updated /etc/$route_f with net $net and gw $gw")
348             : $prov->error("failed to update $route_f", %std_opts);
349              
350 0           my $alias_count = 0;
351 0           foreach ( @ips ) {
352 0           $if_file = "sysconfig/network-scripts/ifcfg-$device:$alias_count";
353 0           $contents = <<EO_IF_FILE
354             DEVICE=$device:$alias_count
355             BOOTPROTO=static
356             ONBOOT=yes
357             IPADDR=$_
358             NETMASK=255.255.255.0
359             EO_IF_FILE
360             ;
361 0           $alias_count++;
362 0           $r = $util->file_write( "$etc/$if_file", lines => [ $contents ], %std_opts );
363 0 0         $r ? $prov->audit("updated /etc/$if_file with device $device and ip $_")
364             : $prov->error("failed to update $if_file", %std_opts);
365             };
366 0 0         return if scalar @{ $prov->{errors}} > $errors_before;
  0            
367 0           return 1;
368             };
369              
370             sub set_hostname {
371 0     0 0   my $self = shift;
372 0           my %p = validate(@_,
373             { host => { type => SCALAR },
374             fs_root => { type => SCALAR },
375             distro => { type => SCALAR, optional => 1 },
376             }
377             );
378              
379 0   0       my $distro = delete $p{distro} || $self->get_distro( $p{fs_root} );
380 0 0         return $self->set_hostname_debian(%p) if $distro =~ /debian|ubuntu/i;
381 0 0         return $self->set_hostname_redhat(%p) if $distro =~ /redhat|fedora|centos/i;
382 0 0         return $self->set_hostname_gentoo(%p) if $distro =~ /gentoo/i;
383              
384 0           $prov->error( "unable to set hostname on distro $distro", %std_opts );
385 0           return;
386             };
387              
388             sub set_hostname_debian {
389 0     0 0   my $self = shift;
390 0           my %p = validate(@_,
391             { host => { type => SCALAR },
392             fs_root => { type => SCALAR },
393             }
394             );
395              
396 0           my $host = $p{host};
397 0           my $fs_root = $p{fs_root};
398              
399 0 0         $util->file_write( "$fs_root/etc/hostname" ,
400             lines => [ $host ],
401             %std_opts,
402             )
403             or return $prov->error("unable to set hostname", %std_opts );
404              
405 0           $prov->audit("wrote hostname to /etc/hostname");
406 0           return 1;
407             };
408              
409             sub set_hostname_gentoo {
410 0     0 0   my $self = shift;
411 0           my %p = validate(@_,
412             { host => { type => SCALAR },
413             fs_root => { type => SCALAR },
414             }
415             );
416              
417 0           my $host = $p{host};
418 0           my $fs_root = $p{fs_root};
419              
420 0 0         mkpath "$fs_root/etc/conf.d" if ! "$fs_root/etc/conf.d";
421              
422 0 0         $util->file_write( "$fs_root/etc/conf.d/hostname" ,
423             lines => [ "HOSTNAME=$host" ],
424             %std_opts,
425             )
426             or return $prov->error("error setting hostname", %std_opts);
427              
428 0           $prov->audit("updated /etc/conf.d/hostname with $host");
429 0           return 1;
430             };
431              
432             sub set_hostname_redhat {
433 0     0 0   my $self = shift;
434 0           my %p = validate(@_,
435             { host => { type => SCALAR },
436             fs_root => { type => SCALAR },
437             }
438             );
439              
440 0           my $fs_root = $p{fs_root};
441 0           my $host = $p{host};
442              
443 0           my $config = "$fs_root/etc/sysconfig/network";
444 0           my @new;
445 0 0         if ( -r $config ) {
446 0           my @lines = $util->file_read( $config, %std_opts );
447 0           foreach ( @lines ) {
448 0 0         next if $_ =~ /^HOSTNAME/;
449 0           push @new, $_;
450             };
451             };
452 0           push @new, "HOSTNAME=$host";
453              
454 0 0         $util->file_write( $config, lines => \@new, %std_opts )
455             or return $prov->error("failed to update $config with hostname $host", %std_opts);
456              
457 0           $prov->audit("updated $config with hostname $host");
458 0           return 1;
459             };
460              
461             sub set_upstart_console {
462 0     0 0   my $self = shift;
463 0           my ($fs_root, $getty_cmd) = @_;
464              
465 0           my $contents = <<EO_INITTAB
466             #
467             # This service maintains a getty on xvc0 from the point the system is
468             # started until it is shut down again.
469              
470             start on runlevel 2
471             start on runlevel 3
472              
473             stop on runlevel 0
474             stop on runlevel 1
475             stop on runlevel 4
476             stop on runlevel 5
477             stop on runlevel 6
478              
479             respawn
480             exec $getty_cmd
481              
482             EO_INITTAB
483             ;
484              
485 0 0         $util->file_write( "$fs_root/etc/event.d/xvc0",
486             lines => [ $contents ],
487             %std_opts,
488             ) or return;
489 0           $prov->audit( "installed /etc/event.d/xvc0" );
490              
491 0           my $serial = "$fs_root/etc/event.d/serial";
492 0 0         return if ! -e $serial;
493              
494 0           my @lines = $util->file_read( $serial, %std_opts );
495 0           my @new;
496 0           foreach my $line ( @lines ) {
497 0 0         if ( $line =~ /^[start|stop]/ ) {
498 0           push @new, "#$line";
499 0           next;
500             };
501 0           push @new, $line;
502             }
503 0 0         $util->file_write( "$fs_root/etc/event.d/serial",
504             lines => \@new,
505             %std_opts,
506             ) or return;
507 0           $prov->audit("updated /etc/event.d/serial");
508 0           return;
509             }
510              
511             sub setup_inittab {
512 0     0 0   my $self = shift;
513 0           my %p = validate(@_,
514             { fs_root => { type => SCALAR },
515             template => { type => SCALAR },
516             }
517             );
518              
519 0           my $fs_root = $p{fs_root};
520 0           my $template = $p{template};
521 0           my $login;
522 0           my $tty_dev = 'xvc0';
523             # $tty_dev = 'console'
524             # if ( -e "$fs_root/dev/console" && ! -e "$fs_root/dev/xvc0" );
525              
526 0 0         if ( $template !~ /debian|ubuntu/i ) {
527 0           $login = $self->setup_autologin( fs_root => $fs_root );
528             };
529 0 0         if ( $template =~ /redhat|fedora|centos/ ) {
530 0           $tty_dev = 'console';
531             };
532 0 0 0       $login ||= -e "$fs_root/bin/bash" ? '/bin/bash' : '/bin/sh';
533              
534 0 0         my $getty_cmd = -e "$fs_root/sbin/getty" ?
    0          
535             "/sbin/getty -n -l $login 38400 $tty_dev"
536             : -e "$fs_root/sbin/agetty" ?
537             "/sbin/agetty -n -i -l $login $tty_dev 38400"
538             : '/bin/sh';
539              
540             # check for upstart
541 0 0         if ( -e "$fs_root/etc/event.d" ) {
542 0           $self->set_upstart_console( $fs_root, $getty_cmd );
543             };
544              
545 0           my $inittab = "$fs_root/etc/inittab";
546 0           my @lines = $util->file_read( $inittab, %std_opts );
547 0           my @new;
548 0           foreach ( @lines ) {
549 0 0         next if $_ =~ /^1:/;
550 0           push @new, $_;
551             }
552 0           push @new, "1:2345:respawn:$getty_cmd";
553 0           copy $inittab, "$inittab.dist";
554 0 0         $util->file_write( $inittab, lines => \@new, %std_opts )
555             or return $prov->error( "unable to write $inittab", %std_opts);
556              
557 0           $prov->audit("updated /etc/inittab ");
558 0           return 1;
559             };
560              
561             sub setup_autologin {
562 0     0 0   my $self = shift;
563 0           my %p = validate(@_, { fs_root => { type => SCALAR, } } );
564              
565 0           my $fs_root = $p{fs_root};
566              
567 0           my $auto = <<'EO_C_CODE'
568             #include <unistd.h>
569             /*
570             http://wiki.archlinux.org/index.php/Automatically_login_some_user_to_a_virtual_console_on_startup
571             */
572             int main() {
573             printf( "%s \n", "Logging on to VPS console. Press Ctrl-] to Quit.");
574             printf( "%s \n", "Press Enter to start." );
575             execlp( "login", "login", "-f", "root", 0);
576             }
577             EO_C_CODE
578             ;
579              
580 0 0         $util->file_write( "$fs_root/tmp/autologin.c",
581             lines => [ $auto ],
582             %std_opts
583             ) or return;
584              
585 0 0         my $chroot = $util->find_bin( 'chroot', %std_opts ) or return;
586 0 0         my $gcc = $util->find_bin( 'gcc', %std_opts ) or return;
587 0           my $cmd = "$chroot $fs_root $gcc -m32 -o /bin/autologin /tmp/autologin.c";
588 0 0         $util->syscmd( $cmd, %std_opts ) or return;
589 0           unlink "$fs_root/tmp/autologin.c";
590 0 0         return if ! -x "$fs_root/bin/autologin";
591 0           return '/bin/autologin';
592             }
593              
594              
595             1;
596              
597              
598             __END__
599             =pod
600              
601             =head1 NAME
602              
603             Provision::Unix::VirtualOS::Linux - a framework for building Linux virtual machines
604              
605             =head1 VERSION
606              
607             version 1.06
608              
609             =head1 AUTHOR
610              
611             Matt Simerson <msimerson@cpan.org>
612              
613             =head1 COPYRIGHT AND LICENSE
614              
615             This software is copyright (c) 2013 by The Network People, Inc..
616              
617             This is free software; you can redistribute it and/or modify it under
618             the same terms as the Perl 5 programming language system itself.
619              
620             =cut
621