File Coverage

lib/Rex/Box/VBox.pm
Criterion Covered Total %
statement 33 129 25.5
branch 0 40 0.0
condition 0 40 0.0
subroutine 12 18 66.6
pod 5 6 83.3
total 50 233 21.4


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             =head1 NAME
6              
7             Rex::Box::VBox - Rex/Boxes VirtualBox Module
8              
9             =head1 DESCRIPTION
10              
11             This is a Rex/Boxes module to use VirtualBox VMs.
12              
13             =head1 EXAMPLES
14              
15             To use this module inside your Rexfile you can use the following commands.
16              
17             use Rex::Commands::Box;
18             set box => "VBox";
19              
20             task "prepare_box", sub {
21             box {
22             my ($box) = @_;
23              
24             $box->name("mybox");
25             $box->url("http://box.rexify.org/box/ubuntu-server-12.10-amd64.ova");
26              
27             $box->network(1 => {
28             type => "nat",
29             });
30              
31             $box->network(1 => {
32             type => "bridged",
33             bridge => "eth0",
34             });
35              
36             $box->forward_port(ssh => [2222, 22]);
37              
38             $box->share_folder(myhome => "/home/myuser");
39              
40             $box->auth(
41             user => "root",
42             password => "box",
43             );
44              
45             $box->setup("setup_task");
46             };
47             };
48              
49             If you want to use a YAML file you can use the following template.
50              
51             type: VBox
52             vms:
53             vmone:
54             url: http://box.rexify.org/box/ubuntu-server-12.10-amd64.ova
55             forward_port:
56             ssh:
57             - 2222
58             - 22
59             share_folder:
60             myhome: /home/myhome
61             setup: setup_task
62              
63             And then you can use it the following way in your Rexfile.
64              
65             use Rex::Commands::Box init_file => "file.yml";
66              
67             task "prepare_vms", sub {
68             boxes "init";
69             };
70              
71             =head1 HEADLESS MODE
72              
73             It is also possible to run VirtualBox in headless mode. This only works on Linux and MacOS. If you want to do this you can use the following option at the top of your I.
74              
75             set box_options => { headless => TRUE };
76              
77             =head1 METHODS
78              
79             See also the Methods of Rex::Box::Base. This module inherits all methods of it.
80              
81             =cut
82              
83             package Rex::Box::VBox;
84              
85 1     1   14 use v5.12.5;
  1         6  
86 1     1   14 use warnings;
  1         3  
  1         32  
87 1     1   6 use Data::Dumper;
  1         2  
  1         68  
88 1     1   6 use Rex::Box::Base;
  1         2  
  1         14  
89 1     1   43 use Rex::Commands -no => [qw/auth/];
  1         3  
  1         11  
90 1     1   8 use Rex::Commands::Fs;
  1         4  
  1         9  
91 1     1   7 use Rex::Commands::Virtualization;
  1         2  
  1         13  
92 1     1   9 use Rex::Commands::SimpleCheck;
  1         2  
  1         12  
93              
94             our $VERSION = '1.14.3'; # VERSION
95              
96             BEGIN {
97 1     1   20 LWP::UserAgent->use;
98             }
99              
100 1     1   8 use Time::HiRes qw(tv_interval gettimeofday);
  1         2  
  1         18  
101 1     1   281 use File::Basename qw(basename);
  1         3  
  1         53  
102              
103 1     1   6 use base qw(Rex::Box::Base);
  1         3  
  1         1537  
104              
105             set virtualization => "VBox";
106              
107             $|++;
108              
109             ################################################################################
110             # BEGIN of class methods
111             ################################################################################
112              
113             =head2 new(name => $vmname)
114              
115             Constructor if used in OO mode.
116              
117             my $box = Rex::Box::VBox->new(name => "vmname");
118              
119             =cut
120              
121             sub new {
122 0     0 1   my $class = shift;
123 0   0       my $proto = ref($class) || $class;
124 0           my $self = $proto->SUPER::new(@_);
125              
126 0   0       bless( $self, ref($class) || $class );
127              
128 0 0 0       if ( exists $self->{options}
      0        
129             && exists $self->{options}->{headless}
130             && $self->{options}->{headless} )
131             {
132 0           set virtualization => { type => "VBox", headless => TRUE };
133             }
134              
135 0           $self->{get_ip_count} = 0;
136              
137 0           return $self;
138             }
139              
140             sub import_vm {
141 0     0 1   my ($self) = @_;
142              
143             # check if machine already exists
144 0           my $vms = vm list => "all";
145              
146 0           my $vm_exists = 0;
147 0           for my $vm ( @{$vms} ) {
  0            
148 0 0         if ( $vm->{name} eq $self->{name} ) {
149 0           Rex::Logger::debug("VM already exists. Don't import anything.");
150 0           $vm_exists = 1;
151             }
152             }
153              
154 0 0         if ( !$vm_exists ) {
155              
156             # if not, create it
157 0           $self->_download;
158              
159 0           my $filename = basename( $self->{url} );
160              
161 0           Rex::Logger::info("Importing VM ./tmp/$filename");
162 0           vm import => $self->{name}, file => "./tmp/$filename", %{$self};
  0            
163              
164             #unlink "./tmp/$filename";
165             }
166              
167 0           my $vminfo = vm info => $self->{name};
168              
169             # check if networksettings should be set
170 0 0 0       if ( exists $self->{__network} && $vminfo->{VMState} ne "running" ) {
171 0           my $option = $self->{__network};
172 0           for my $nic_no ( keys %{$option} ) {
  0            
173              
174 0 0         if ( $option->{$nic_no}->{type} ) {
175             Rex::Logger::debug( "Setting network type (dev: $nic_no) to: "
176 0           . $option->{$nic_no}->{type} );
177             vm
178             option => $self->{name},
179 0           "nic$nic_no" => $option->{$nic_no}->{type};
180              
181 0 0         if ( $option->{$nic_no}->{type} eq "bridged" ) {
182              
183             $option->{$nic_no}->{bridge} = select_bridge()
184 0 0         if ( !$option->{$nic_no}->{bridge} );
185              
186             Rex::Logger::debug( "Setting network bridge (dev: $nic_no) to: "
187 0   0       . ( $option->{$nic_no}->{bridge} || "eth0" ) );
188             vm
189             option => $self->{name},
190             "bridgeadapter$nic_no" =>
191 0   0       ( $option->{$nic_no}->{bridge} || "eth0" );
192             }
193             }
194              
195 0 0         if ( $option->{$nic_no}->{driver} ) {
196             Rex::Logger::debug( "Setting network driver (dev: $nic_no) to: "
197 0           . $option->{$nic_no}->{driver} );
198             vm
199             option => $self->{name},
200 0           "nictype$nic_no" => $option->{$nic_no}->{driver};
201             }
202              
203             }
204             }
205 0 0 0       if ( exists $self->{__forward_port} && $vminfo->{VMState} ne "running" ) {
206              
207             # remove all forwards
208 0           vm forward_port => $self->{name}, delete => -all;
209              
210             # add forwards
211 0           vm forward_port => $self->{name}, add => $self->{__forward_port};
212             }
213              
214             # shared folder
215 0 0 0       if ( exists $self->{__shared_folder} && $vminfo->{VMState} ne "running" ) {
216 0           vm share_folder => $self->{name}, add => $self->{__shared_folder};
217             }
218              
219 0 0         if ( $vminfo->{VMState} ne "running" ) {
220 0           $self->start;
221             }
222              
223 0           $self->{info} = vm guestinfo => $self->{name};
224             }
225              
226             sub select_bridge {
227 0     0 0   my $bridges = vm "bridge";
228              
229 0           my $ifname;
230 0 0         if ( @$bridges == 1 ) {
    0          
231 0           Rex::Logger::debug(
232             "Only one bridged interface available. Using it by default.");
233 0           $ifname = $bridges->[0]->{name};
234             }
235             elsif ( @$bridges > 1 ) {
236 0           for ( my $i = 0 ; $i < @$bridges ; $i++ ) {
237 0           my $bridge = $bridges->[$i];
238 0 0         next if ( $bridge->{status} =~ /^down$/i );
239 0           local $Rex::Logger::format = "%s";
240 0           Rex::Logger::info( $i + 1 . " $bridge->{name}" );
241             }
242              
243 0           my $choice;
244 0           do {
245 0           print "What interface should network bridge to? ";
246 0           chomp( $choice = );
247 0           $choice = int($choice);
248             } while ( !$choice );
249              
250 0           $ifname = $bridges->[ $choice - 1 ]->{name};
251             }
252              
253 0           return $ifname;
254             }
255              
256             =head2 share_folder(%option)
257              
258             Creates a shared folder inside the VM with the content from a folder from the Host machine. This only works with VirtualBox.
259              
260             $box->share_folder(
261             name => "/path/on/host",
262             name2 => "/path_2/on/host",
263             );
264              
265             =cut
266              
267             sub share_folder {
268 0     0 1   my ( $self, %option ) = @_;
269 0           $self->{__shared_folder} = \%option;
270             }
271              
272             =head2 info
273              
274             Returns a hashRef of vm information.
275              
276             =cut
277              
278             sub info {
279 0     0 1   my ($self) = @_;
280 0           $self->ip;
281              
282 0           my $vm_info = vm info => $self->{name};
283              
284             # get forwarded ports
285 0           my @forwarded_ports = grep { m/^Forwarding/ } keys %{$vm_info};
  0            
  0            
286              
287 0           my %forward_port;
288 0           for my $fwp (@forwarded_ports) {
289             my ( $name, $proto, $host_ip, $host_port, $vm_ip, $vm_port ) =
290 0           split( /,/, $vm_info->{$fwp} );
291 0           $forward_port{$name} = [ $host_port, $vm_port ];
292             }
293 0           $self->forward_port(%forward_port);
294              
295 0           return $self->{info};
296             }
297              
298             =head2 ip
299              
300             This method return the ip of a vm on which the ssh daemon is listening.
301              
302             =cut
303              
304             sub ip {
305 0     0 1   my ($self) = @_;
306              
307 0   0       $self->{info} ||= vm guestinfo => $self->{name};
308              
309 0 0         if ( scalar keys %{ $self->{info} } == 0 ) { return; }
  0            
  0            
310              
311 0           my $server = $self->{info}->{net}->[0]->{ip};
312 0 0 0       if ( $self->{__forward_port}
    0 0        
      0        
      0        
313             && $self->{__forward_port}->{ssh}
314             && !Rex::is_local() )
315             {
316 0           $server = connection->server . ":" . $self->{__forward_port}->{ssh}->[0];
317             }
318             elsif ( $self->{__forward_port}
319             && $self->{__forward_port}->{ssh}
320             && Rex::is_local() )
321             {
322 0           $server = "127.0.0.1:" . $self->{__forward_port}->{ssh}->[0];
323             }
324              
325 0           $self->{info}->{ip} = $server;
326              
327 0 0         if ( !$server ) {
328 0           sleep 1;
329 0           $self->{get_ip_count}++;
330              
331 0 0         if ( $self->{get_ip_count} >= 30 ) {
332 0           die "Can't get ip of VM.";
333             }
334              
335 0           my $ip = $self->ip;
336 0 0         if ($ip) {
337 0           $self->{get_ip_count} = 0;
338 0           return $ip;
339             }
340             }
341              
342 0           return $server;
343             }
344              
345             1;