virtualman 1.1.9 → 2.0.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/bin/virtualman +7 -3
- data/lib/virtualman.rb +8 -5
- data/lib/virtualman/interactive/REPL.rb +124 -0
- data/lib/virtualman/interactive/configuration.rb +55 -0
- data/lib/virtualman/interactive/helper.rb +209 -0
- data/lib/virtualman/{menu.rb → interactive/menu.rb} +34 -27
- data/lib/virtualman/vm.rb +51 -48
- data/lib/virtualman/vmlister.rb +49 -47
- metadata +7 -6
- data/lib/virtualman/configuration.rb +0 -51
- data/lib/virtualman/interractive.rb +0 -226
data/bin/virtualman
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
####Required
|
4
4
|
require 'virtualman'
|
5
|
-
|
5
|
+
|
6
|
+
repl = Virtualman::Interactive::REPL.new
|
6
7
|
|
7
8
|
menu = ["clone","cook","ssh","add","delete","bootify","exit"]
|
8
9
|
|
@@ -11,8 +12,11 @@ choice = "0"
|
|
11
12
|
while choice != "exit"
|
12
13
|
puts "----VirtualMan----"
|
13
14
|
puts "Please select an action to do with your VM."
|
14
|
-
choice = Menu.unic_run(menu)
|
15
|
-
|
15
|
+
choice = Virtualman::Interactive::Menu.unic_run(menu)
|
16
|
+
|
17
|
+
if choice != "exit"
|
18
|
+
repl.send(choice)
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
puts "Thank you come again!"
|
data/lib/virtualman.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
|
2
|
-
require 'virtualman/
|
3
|
-
require 'virtualman/
|
4
|
-
require 'virtualman/
|
5
|
-
require 'virtualman/
|
1
|
+
module Virtualman
|
2
|
+
require 'virtualman/interactive/helper'
|
3
|
+
require 'virtualman/interactive/menu'
|
4
|
+
require 'virtualman/vm'
|
5
|
+
require 'virtualman/vmlister'
|
6
|
+
require 'virtualman/interactive/configuration'
|
7
|
+
require 'virtualman/interactive/REPL'
|
8
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Virtualman
|
2
|
+
module Interactive
|
3
|
+
class REPL
|
4
|
+
attr_reader :configuration
|
5
|
+
|
6
|
+
include Virtualman::Interactive::Helper
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@configuration = Virtualman::Interactive::Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def clone
|
13
|
+
cloned_vm = self.clone_vm
|
14
|
+
self.start_vm(cloned_vm)
|
15
|
+
self.configuration.add_cloned_vm(cloned_vm)
|
16
|
+
end
|
17
|
+
|
18
|
+
def cook
|
19
|
+
puts "----Cook a clone!----"
|
20
|
+
puts "Which VM do you want to cook?"
|
21
|
+
role_path = self.configuration.options["role_path"]
|
22
|
+
cookbook_path = self.configuration.options["cookbook_path"]
|
23
|
+
|
24
|
+
selected_vm = self.choose_vm "cloned_vms"
|
25
|
+
|
26
|
+
puts "Which role do you want to give?"
|
27
|
+
|
28
|
+
role = self.choose "roles"
|
29
|
+
|
30
|
+
user="root"
|
31
|
+
|
32
|
+
if self.start_vm(selected_vm)
|
33
|
+
cmd = "ssh #{user}@#{selected_vm.ip} '/usr/local/bin/chef-solo -j #{role_path}#{role["name"]}.json -r #{cookbook_path}'"
|
34
|
+
puts cmd
|
35
|
+
Kernel.system(cmd)
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "Thanks!"
|
39
|
+
end
|
40
|
+
|
41
|
+
def ssh
|
42
|
+
puts "----SSH to cloned VM----"
|
43
|
+
puts "Which VM do you want to ssh?"
|
44
|
+
|
45
|
+
selected_vm = self.choose_vm "cloned_vms"
|
46
|
+
|
47
|
+
puts "With which user? [root]"
|
48
|
+
user = Menu.ask
|
49
|
+
|
50
|
+
if user.empty?
|
51
|
+
user = "root"
|
52
|
+
end
|
53
|
+
|
54
|
+
if self.start_vm(selected_vm)
|
55
|
+
cmd = "ssh #{user}@#{selected_vm.ip}"
|
56
|
+
Kernel.exec(cmd)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete
|
61
|
+
puts "----Delete a cloned VM----"
|
62
|
+
puts "Which VM delete?"
|
63
|
+
|
64
|
+
selected_vm = self.choose_vm "cloned_vms"
|
65
|
+
|
66
|
+
puts "!!YOU ARE ABOUT TO DELETE PEMANENTLY DELETE #{selected_vm.name}!!"
|
67
|
+
puts "Are you sure? (yes/[no])"
|
68
|
+
|
69
|
+
choice = Menu.ask
|
70
|
+
|
71
|
+
if choice == "yes"
|
72
|
+
selected_vm.stop!
|
73
|
+
|
74
|
+
selected_vm.manage("unregistervm","--delete")
|
75
|
+
|
76
|
+
self.configuration.delete_cloned_vm(selected_vm)
|
77
|
+
else
|
78
|
+
puts "VM not deleted!"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def add
|
83
|
+
puts "----Add a VM----"
|
84
|
+
puts "Give the path of the OVA file you want to add"
|
85
|
+
puts "(An OVA file represents a Virtual Machine)"
|
86
|
+
|
87
|
+
path = Menu.ask
|
88
|
+
|
89
|
+
cmd = "VBoxManage import #{path}"
|
90
|
+
puts cmd
|
91
|
+
`#{cmd}`
|
92
|
+
|
93
|
+
if $?.exitstatus == 0
|
94
|
+
added_vm = Vm.new(File.basename("#{path}",".*"))
|
95
|
+
self.configuration.add_cloned_vm(added_vm)
|
96
|
+
else
|
97
|
+
puts "There was an error during the import"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def bootify
|
102
|
+
puts "----Start a VM at boot----"
|
103
|
+
puts "Initializing bootify...."
|
104
|
+
self.init_bootify
|
105
|
+
puts "Whcih VM do you want to add to your boot sequence?"
|
106
|
+
|
107
|
+
selected_vm = self.choose_vm "cloned_vms"
|
108
|
+
|
109
|
+
puts "We need to shutdown the VM to add to the boot list."
|
110
|
+
puts "Do you want to procedd now? (yes/[no])"
|
111
|
+
|
112
|
+
choice = Menu.ask
|
113
|
+
|
114
|
+
if choice == "yes"
|
115
|
+
selected_vm.stop!
|
116
|
+
selected_vm.manage("modifyvm", "--autostart-enabled on", "--autostart-delay 10")
|
117
|
+
puts "#{selected_vm.name} added to your boot sequence."
|
118
|
+
start_vm selected_vm
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Virtualman
|
4
|
+
module Interactive
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
attr_reader :options, :filepath
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@filepath = File.join(ENV['HOME'],'.virtualman.rc.yaml')
|
11
|
+
|
12
|
+
if File.exists? @filepath
|
13
|
+
@options = YAML.load_file(@filepath)
|
14
|
+
else
|
15
|
+
STDERR.puts("No configuration file found here ~/.vm_to_clone.rc.yaml")
|
16
|
+
STDERR.puts("Thanks to create this file before continuing!")
|
17
|
+
STDERR.puts "Config file example:"
|
18
|
+
STDERR.puts "---"
|
19
|
+
STDERR.puts "source_vm:"
|
20
|
+
STDERR.puts " - name: Debian"
|
21
|
+
STDERR.puts " snapshot: ready to clone!"
|
22
|
+
STDERR.puts " - name: Gentoo"
|
23
|
+
STDERR.puts " snapshot: almost ready"
|
24
|
+
STDERR.puts "role_path: http://path/to/your/role/fodler"
|
25
|
+
STDERR.puts "cookbook_path: http://path/to/your/cookbooks.tar.gz"
|
26
|
+
STDERR.puts "roles:"
|
27
|
+
STDERR.puts "- devtest"
|
28
|
+
STDERR.puts "- devtest_jenkins"
|
29
|
+
STDERR.puts "- build_pkg_devtest_jenkins"
|
30
|
+
Kernel.abort("No configuration file found here ~/.vm_to_clone.rc.yaml")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def record_conf(msg)
|
35
|
+
File.open(@filepath, "w") {|f| f.write(@options.to_yaml) }
|
36
|
+
puts "#{msg} to your configuration file"
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_cloned_vm(vm_cloned)
|
40
|
+
if @options["cloned_vms"] != nil
|
41
|
+
@options["cloned_vms"] << {"name" => vm_cloned.name}
|
42
|
+
else
|
43
|
+
@options.merge!({"cloned_vms" => [{"name" => vm_cloned.name}]})
|
44
|
+
end
|
45
|
+
record_conf("VM #{vm_cloned.name} has been saved")
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_cloned_vm(vm_cloned)
|
49
|
+
@options["cloned_vms"].reject!{|vm| vm == {"name" => vm_cloned.name.gsub(/\"/,'')} }
|
50
|
+
record_conf("VM #{vm_cloned.name} has been deleted")
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module Virtualman
|
2
|
+
module Interactive
|
3
|
+
module Helper
|
4
|
+
require 'fileutils'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def choose source
|
10
|
+
|
11
|
+
list = Array.new
|
12
|
+
self.configuration.options[source].each do |option|
|
13
|
+
list << option["name"]
|
14
|
+
end
|
15
|
+
list << "exit"
|
16
|
+
|
17
|
+
choice = Menu.unic_run(list)
|
18
|
+
|
19
|
+
exit 0 if choice == "exit"
|
20
|
+
|
21
|
+
self.configuration.options[source].each_with_index do |option,index|
|
22
|
+
if option["name"] == choice
|
23
|
+
return self.configuration.options[source][index]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
#Source can be either source_vms or clonned_vms
|
30
|
+
def choose_vm source
|
31
|
+
vm_choice = choose source
|
32
|
+
return Vm.new("#{vm_choice["name"]}")
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def clone_vm
|
37
|
+
puts "----Clone_VM----"
|
38
|
+
puts "Which VM do you want to clone?"
|
39
|
+
|
40
|
+
vm_choice = choose "source_vms"
|
41
|
+
vm_to_clone = Vm.new("\"#{vm_choice["name"]}\"")
|
42
|
+
|
43
|
+
puts "How do you want to name your freshly spawned VM?"
|
44
|
+
vm_name = Menu.ask
|
45
|
+
|
46
|
+
options = "--snapshot \"#{vm_choice["snapshot"]}\" --name \"#{vm_name}\" --register"
|
47
|
+
vm_to_clone.manage("clonevm", options)
|
48
|
+
vm_cloned = Vm.new(vm_name)
|
49
|
+
return vm_cloned
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_vm(vm)
|
53
|
+
if !vm.running?
|
54
|
+
puts "Do you want to start the VM? headless?"
|
55
|
+
puts "(default is no)"
|
56
|
+
puts "yes/headless/[no]"
|
57
|
+
|
58
|
+
answer = Menu.ask
|
59
|
+
|
60
|
+
if answer == "headless"
|
61
|
+
option = "--type headless"
|
62
|
+
puts "The VM is booting headless"
|
63
|
+
elsif answer == "yes"
|
64
|
+
option = ""
|
65
|
+
puts "The VM is booting"
|
66
|
+
else
|
67
|
+
puts "The vm will not be started"
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
|
71
|
+
vm.manage("startvm", option)
|
72
|
+
|
73
|
+
Kernel.print "Waiting for the VM to get an IP"
|
74
|
+
while !vm.ip
|
75
|
+
Kernel.print "."
|
76
|
+
Kernel.sleep (1)
|
77
|
+
end
|
78
|
+
Kernel.print "\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
return vm.running?
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
def check_chmod_script
|
87
|
+
script = "/Applications/VirtualBox.app/Contents/MacOS/VBoxAutostartDarwin.sh"
|
88
|
+
permission = Kernel.sprintf("%o", File.world_readable?(script) )
|
89
|
+
if permission != "744"
|
90
|
+
puts "Changing file permission of #{script}, to allow it to be executable"
|
91
|
+
puts "Thanks to provide your admin password."
|
92
|
+
File.chmod(0744, script)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def integrate_template filename, path, template_string, *variables
|
97
|
+
template = ERB.new(template_string, 0, "%<>")
|
98
|
+
|
99
|
+
templated = template.result(binding)
|
100
|
+
|
101
|
+
File.open("/tmp/#{filename}", 'w') { |file| file.write(templated) }
|
102
|
+
|
103
|
+
if File.exist?(path+filename)
|
104
|
+
diff = `diff #{path}#{filename} /tmp/#{filename}`
|
105
|
+
result = $?
|
106
|
+
if result != 0
|
107
|
+
puts "We are trying to install the previous file but there are differences"
|
108
|
+
puts "here are the differences"
|
109
|
+
puts "#{diff}"
|
110
|
+
puts "Do you want to override the original?"
|
111
|
+
puts "yes/[no]"
|
112
|
+
|
113
|
+
answer = Menu.ask
|
114
|
+
|
115
|
+
if answer != "yes"
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
else
|
119
|
+
return true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
puts "Installing the file #{path}#{filename}..."
|
124
|
+
puts "Please povide your password :"
|
125
|
+
|
126
|
+
output = `sudo cp /tmp/#{filename} #{path}#{filename}`
|
127
|
+
result = $?
|
128
|
+
if result == 0
|
129
|
+
return true
|
130
|
+
else
|
131
|
+
Kernel.abort(output)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def check_plist_file
|
136
|
+
puts "Checking plist file..."
|
137
|
+
plist_file = "org.virtualbox.vboxautostart.plist"
|
138
|
+
plist_path = "/Library/LaunchDaemons/"
|
139
|
+
|
140
|
+
plist_template = %q{
|
141
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
142
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
143
|
+
<plist version="1.0">
|
144
|
+
<dict>
|
145
|
+
<key>Disabled</key>
|
146
|
+
<false/>
|
147
|
+
<key>KeepAlive</key>
|
148
|
+
<false/>
|
149
|
+
<key>Label</key>
|
150
|
+
<string>org.virtualbox.vboxautostart</string>
|
151
|
+
<key>ProgramArguments</key>
|
152
|
+
<array>
|
153
|
+
<string>/Applications/VirtualBox.app/Contents/MacOS/VBoxAutostartDarwin.sh</string>
|
154
|
+
<string>--start</string>
|
155
|
+
<string>/etc/vbox/autostart.cfg</string>
|
156
|
+
</array>
|
157
|
+
<key>RunAtLoad</key>
|
158
|
+
<true/>
|
159
|
+
<key>debug</key>
|
160
|
+
<true/>
|
161
|
+
</dict>
|
162
|
+
</plist>
|
163
|
+
}
|
164
|
+
|
165
|
+
if self.integrate_template(plist_file, plist_path, plist_template)
|
166
|
+
puts "loading the plist file"
|
167
|
+
puts "Thanks to provide your admin password."
|
168
|
+
`sudo launchctl load #{plist_path}#{plist_file}`
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def check_conf_file
|
174
|
+
puts "Checking conf file..."
|
175
|
+
conf_file = "autostart.cfg"
|
176
|
+
conf_path = "/etc/vbox/"
|
177
|
+
|
178
|
+
conf_template = %q{
|
179
|
+
# Default policy is to deny starting a VM, the other option is "allow".
|
180
|
+
default_policy = deny
|
181
|
+
|
182
|
+
# <%= variables[0] %> is allowed to start virtual machines but starting them
|
183
|
+
# will be delayed for 10 seconds
|
184
|
+
<%= variables[0] %> = {
|
185
|
+
allow = true
|
186
|
+
startup_delay = 10
|
187
|
+
}
|
188
|
+
}.gsub(/^ /, '')
|
189
|
+
|
190
|
+
username = `whoami`.strip
|
191
|
+
|
192
|
+
integrate_template(conf_file, conf_path, conf_template, username)
|
193
|
+
end
|
194
|
+
|
195
|
+
def init_bootify
|
196
|
+
|
197
|
+
#check plist file
|
198
|
+
check_plist_file
|
199
|
+
|
200
|
+
#Check chmod of the bash script
|
201
|
+
check_chmod_script
|
202
|
+
|
203
|
+
#Check the conf file
|
204
|
+
check_conf_file
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -23,34 +23,41 @@
|
|
23
23
|
|
24
24
|
#Forked from https://github.com/cldwalker/menu
|
25
25
|
|
26
|
-
module
|
27
|
-
|
26
|
+
module Virtualman
|
27
|
+
module Interactive
|
28
|
+
module Menu
|
29
|
+
extend self
|
30
|
+
|
31
|
+
def ask
|
32
|
+
$stdin.reopen '/dev/tty'
|
33
|
+
$stdin.gets.chomp
|
34
|
+
end
|
35
|
+
|
36
|
+
def unic_prompt(lines)
|
37
|
+
ljust_size = lines.size.to_s.size + 1
|
38
|
+
lines.each_with_index {|obj,i|
|
39
|
+
puts "#{i+1}.".ljust(ljust_size) + " " +obj
|
40
|
+
}
|
41
|
+
print "\nSpecify your choice\nChoose: "
|
42
|
+
end
|
43
|
+
|
44
|
+
def unic_answer(array, input)
|
45
|
+
if input[/(\d+)/]
|
46
|
+
index = input.to_i - 1
|
47
|
+
if array[index]
|
48
|
+
return array[index]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Kernel.abort "#{input} is an invalid choice."
|
53
|
+
end
|
54
|
+
|
55
|
+
def unic_run(array)
|
56
|
+
unic_prompt(array)
|
57
|
+
answer = ask
|
58
|
+
return unic_answer(array, answer)
|
59
|
+
end
|
28
60
|
|
29
|
-
def ask
|
30
|
-
$stdin.reopen '/dev/tty'
|
31
|
-
$stdin.gets.chomp
|
32
|
-
end
|
33
|
-
|
34
|
-
def unic_run(array)
|
35
|
-
unic_prompt(array)
|
36
|
-
answer = ask
|
37
|
-
return unic_answer(array, answer)
|
38
|
-
end
|
39
|
-
|
40
|
-
def unic_answer(array, input)
|
41
|
-
if input[/(\d+)/]
|
42
|
-
index = $1.to_i - 1
|
43
|
-
return array[index] if array[index]
|
44
|
-
else
|
45
|
-
abort("`#{input}' is an invalid choice.")
|
46
61
|
end
|
47
62
|
end
|
48
|
-
|
49
|
-
def unic_prompt(lines)
|
50
|
-
ljust_size = lines.size.to_s.size + 1
|
51
|
-
lines.each_with_index {|obj,i|
|
52
|
-
puts "#{i+1}.".ljust(ljust_size) + " " +obj
|
53
|
-
}
|
54
|
-
print "\nSpecify your choice\nChoose: "
|
55
|
-
end
|
56
63
|
end
|
data/lib/virtualman/vm.rb
CHANGED
@@ -1,62 +1,65 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
1
|
+
module Virtualman
|
2
|
+
# Implement a way to interact with the VirtualBox command line tool.
|
3
|
+
# Each #Vm is a Class that contains the #name of the VM. With that
|
4
|
+
# name you can then interact with it through VBoxManage for example
|
4
5
|
|
5
|
-
class Vm
|
6
|
+
class Vm
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
# This attribute contains the name of the VM in VirtualBox.
|
9
|
+
attr_reader :name
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# Returns a boolean whether the VM is running or not
|
17
|
-
def running?
|
18
|
-
!`VBoxManage showvminfo #{name} | grep State | grep running`.empty?
|
19
|
-
end
|
20
|
-
|
21
|
-
# A general method to interact with the VM.
|
22
|
-
# action is the kind of action to request to VBoxManage
|
23
|
-
# *param is a list of options
|
24
|
-
def manage(action, *param)
|
25
|
-
puts "VBoxManage #{action} #{@name} #{param.join(" ")}"
|
26
|
-
puts `VBoxManage #{action} #{@name} #{param.join(" ")}`
|
27
|
-
end
|
11
|
+
# A #Vm is just described by it's name.
|
12
|
+
# *type is for further needs
|
13
|
+
def initialize(vm_name, *type)
|
14
|
+
@name = vm_name
|
15
|
+
end
|
28
16
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
sleep (1)
|
17
|
+
# A general method to interact with the VM.
|
18
|
+
# action is the kind of action to request to VBoxManage
|
19
|
+
# *param is a list of options
|
20
|
+
def manage(action, *param)
|
21
|
+
#puts "VBoxManage #{action} #{@name} #{param.join(" ")}"
|
22
|
+
output = `VBoxManage #{action} \"#{@name}\" #{param.join(" ")} 2>&1`
|
23
|
+
if $?.exitstatus != 0
|
24
|
+
Kernel.abort(output)
|
25
|
+
else
|
26
|
+
return output
|
40
27
|
end
|
41
|
-
sleep (5)
|
42
28
|
end
|
43
29
|
|
44
|
-
|
30
|
+
# Returns a boolean whether the VM is running or not
|
31
|
+
def running?
|
32
|
+
return !self.manage("showvminfo").split("\n").grep(/running/).empty?
|
33
|
+
end
|
45
34
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
35
|
+
# return the ip of the VM if it is running. O if it is not or if the ip is not correct.
|
36
|
+
def ip
|
37
|
+
if self.running?
|
38
|
+
ip = self.manage("guestproperty enumerate").split("\n").grep(/IP/)[0].split(",")[1].sub(/^ value: /, '')
|
39
|
+
if ip.match /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/
|
40
|
+
return ip
|
41
|
+
else
|
42
|
+
return false
|
43
|
+
end
|
54
44
|
else
|
45
|
+
puts "The VM is not running, cannot determine IP"
|
55
46
|
return false
|
56
47
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
# A method to stop properly a vm
|
51
|
+
# It assumes that you can access to the VM with root and ssh_keys
|
52
|
+
def stop!
|
53
|
+
if self.running?
|
54
|
+
`ssh root@#{self.ip} "shutdown -h now"`
|
55
|
+
print "Waiting for complete shutdown of #{self.name}"
|
56
|
+
while self.running?
|
57
|
+
print "."
|
58
|
+
sleep (1)
|
59
|
+
end
|
60
|
+
sleep (5)
|
61
|
+
print("\n")
|
62
|
+
end
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|
data/lib/virtualman/vmlister.rb
CHANGED
@@ -1,58 +1,60 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
module Virtualman
|
2
|
+
# Create an array that contains all the #Vm from VirtualBox
|
3
|
+
# And implement methods to play with it as a whole.
|
4
|
+
class VmLister < Array
|
5
|
+
|
6
|
+
# Return an array with the list returned by the command "VBoxManage list vms"
|
7
|
+
# The argument *type will be used later (to specify a remote host for instance)
|
8
|
+
def perform_list(*type)
|
9
|
+
vm_list = `VBoxManage list vms`.split(/\n/).collect {|e| e[/".*"/]}
|
10
|
+
return vm_list
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Populate the array with the method perform_list
|
14
|
+
# The argument *type will be used later (to specify a remote host for instance)
|
15
|
+
def populate!(*type)
|
16
|
+
self.perform_list.each {|vm| self << Vm.new(vm)}
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
# A simple lister
|
20
|
+
def list
|
21
|
+
self.each {|vm| puts "#{vm.name}"}
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# Get the IP of each running vm.
|
25
|
+
def ip
|
26
|
+
self.each {|vm| puts vm.ip}
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
# This methods returns a #VmLister object but just with the running VMs of the list.
|
30
|
+
def running?
|
31
|
+
running_vms = VmLister.new
|
32
|
+
self.collect {|vm| running_vms << vm if vm.running?}
|
33
|
+
return running_vms
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
# A general method to interact with the VBoxManage tool
|
37
|
+
# the block param could be useful for advanced links between your script and this class
|
38
|
+
def manage(action, *param, &block)
|
39
|
+
self.each do |vm|
|
40
|
+
block_param = block.call(vm.name) if block_given?
|
40
41
|
|
41
|
-
|
42
|
+
vm.manage(action, param, block_param)
|
43
|
+
end
|
42
44
|
end
|
43
|
-
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
# A method to automatically export the list of VMs
|
47
|
+
def backup!(folder)
|
48
|
+
self.each {|vm| vm.stop!}
|
49
|
+
sleep (5)
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
self.each do |vm|
|
52
|
+
filename = Time.now().strftime("#{vm.name.delete "\""}_%Y%m%dT%H%M")
|
53
|
+
vm.manage("export","-o #{folder}/#{filename}.ova")
|
54
|
+
sleep(5)
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
+
self.each {|vm| vm.manage("startvm", "--type headless")}
|
58
|
+
end
|
57
59
|
end
|
58
|
-
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtualman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,9 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-15 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: An interactive tool to manage quickly your VMs
|
14
|
+
description: An interactive tool, and library to manage quickly your VMs
|
15
15
|
email: pierre.ozoux@gmail.com
|
16
16
|
executables:
|
17
17
|
- virtualman
|
@@ -19,11 +19,12 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- lib/virtualman.rb
|
22
|
-
- lib/virtualman/configuration.rb
|
23
|
-
- lib/virtualman/
|
24
|
-
- lib/virtualman/menu.rb
|
22
|
+
- lib/virtualman/interactive/configuration.rb
|
23
|
+
- lib/virtualman/interactive/REPL.rb
|
24
|
+
- lib/virtualman/interactive/menu.rb
|
25
25
|
- lib/virtualman/vm.rb
|
26
26
|
- lib/virtualman/vmlister.rb
|
27
|
+
- lib/virtualman/interactive/helper.rb
|
27
28
|
- bin/virtualman
|
28
29
|
homepage: http://rubygems.org/gems/virtualman
|
29
30
|
licenses: []
|
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
module Configuration
|
4
|
-
|
5
|
-
def config_file
|
6
|
-
File.join(ENV['HOME'],'.virtualman.rc.yaml')
|
7
|
-
end
|
8
|
-
|
9
|
-
def load_conf
|
10
|
-
if File.exists? config_file
|
11
|
-
config_options = YAML.load_file(config_file)
|
12
|
-
else
|
13
|
-
STDERR.puts "No configuration file found here ~/.vm_to_clone.rc.yaml"
|
14
|
-
STDERR.puts "Config file example:"
|
15
|
-
STDERR.puts "---"
|
16
|
-
STDERR.puts "source_vm:"
|
17
|
-
STDERR.puts " - name: Debian"
|
18
|
-
STDERR.puts " snapshot: ready to clone!"
|
19
|
-
STDERR.puts " - name: Gentoo"
|
20
|
-
STDERR.puts " snapshot: almost ready"
|
21
|
-
STDERR.puts "role_path: http://path/to/your/role/fodler"
|
22
|
-
STDERR.puts "cookbook_path: http://path/to/your/cookbooks.tar.gz"
|
23
|
-
STDERR.puts "roles:"
|
24
|
-
STDERR.puts "- devtest"
|
25
|
-
STDERR.puts "- devtest_jenkins"
|
26
|
-
STDERR.puts "- build_pkg_devtest_jenkins"
|
27
|
-
end
|
28
|
-
return config_options
|
29
|
-
end
|
30
|
-
|
31
|
-
def record_conf(vm_cloned, *param)
|
32
|
-
conf = load_conf()
|
33
|
-
|
34
|
-
if param[0] == "delete"
|
35
|
-
verb = "deleted"
|
36
|
-
conf["cloned_vms"].reject!{|vm| vm == {"name" => vm_cloned.name.gsub(/\"/,'')} }
|
37
|
-
else
|
38
|
-
verb = "saved"
|
39
|
-
|
40
|
-
if conf["cloned_vms"]
|
41
|
-
conf["cloned_vms"] << {"name" => vm_cloned.name}
|
42
|
-
else
|
43
|
-
conf.merge!({"cloned_vms" => {"name" => vm_cloned.name}})
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
File.open(config_file, "w") {|f| f.write(conf.to_yaml) }
|
48
|
-
puts "VM #{vm_cloned.name} has been #{verb} in your configuration file."
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
@@ -1,226 +0,0 @@
|
|
1
|
-
require 'open3'
|
2
|
-
require 'erb'
|
3
|
-
|
4
|
-
module Interractive
|
5
|
-
include Configuration
|
6
|
-
|
7
|
-
def choosed_option(array,choice,attribute_to_check)
|
8
|
-
array.each_with_index do |option,index|
|
9
|
-
if option[attribute_to_check] == choice
|
10
|
-
return array[index]
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def choose options, *attribute_to_check
|
16
|
-
options_array = Configuration.load_conf[options]
|
17
|
-
if attribute_to_check.empty?
|
18
|
-
return Menu.unic_run(options_array)
|
19
|
-
else
|
20
|
-
list = Array.new
|
21
|
-
options_array.each do |option|
|
22
|
-
list << option[attribute_to_check[0]]
|
23
|
-
end
|
24
|
-
list << "exit"
|
25
|
-
|
26
|
-
choice = Menu.unic_run(list)
|
27
|
-
|
28
|
-
exit 0 if choice == "exit"
|
29
|
-
|
30
|
-
return choosed_option(options_array,choice,attribute_to_check[0])
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def choose_vm source
|
35
|
-
vm_choice = choose source, "name"
|
36
|
-
vm_choosed = Vm.new("\"#{vm_choice["name"]}\"")
|
37
|
-
return vm_choosed
|
38
|
-
end
|
39
|
-
|
40
|
-
def clone_vm
|
41
|
-
puts "----Clone_VM----"
|
42
|
-
puts "Which VM do you want to clone?"
|
43
|
-
|
44
|
-
vm_choice = choose "source_vms", "name"
|
45
|
-
vm_to_clone = Vm.new("\"#{vm_choice["name"]}\"")
|
46
|
-
|
47
|
-
puts "How do you want to name your freshly spawned VM?"
|
48
|
-
vm_name = Menu.ask
|
49
|
-
|
50
|
-
options = "--snapshot \"#{vm_choice["snapshot"]}\" --name \"#{vm_name}\" --register"
|
51
|
-
vm_to_clone.manage("clonevm", options)
|
52
|
-
|
53
|
-
vm_cloned = Vm.new(vm_name)
|
54
|
-
return vm_cloned
|
55
|
-
end
|
56
|
-
|
57
|
-
def start_vm(vm)
|
58
|
-
if !vm.running?
|
59
|
-
puts "Do you want to start the VM? headless?"
|
60
|
-
puts "(default is no)"
|
61
|
-
puts "yes/headless/[no]"
|
62
|
-
|
63
|
-
answer = Menu.ask
|
64
|
-
|
65
|
-
if answer == "headless"
|
66
|
-
option = "--type headless"
|
67
|
-
puts "The VM is booting headless"
|
68
|
-
elsif answer == "yes"
|
69
|
-
option = ""
|
70
|
-
puts "The VM is booting"
|
71
|
-
else
|
72
|
-
puts "The vm will not be started"
|
73
|
-
return vm
|
74
|
-
end
|
75
|
-
|
76
|
-
vm.manage("startvm", option)
|
77
|
-
|
78
|
-
print "Waiting for the VM to get an IP"
|
79
|
-
while !vm.ip
|
80
|
-
print "."
|
81
|
-
sleep (1)
|
82
|
-
end
|
83
|
-
print "\n"
|
84
|
-
end
|
85
|
-
|
86
|
-
return vm.running?
|
87
|
-
end
|
88
|
-
|
89
|
-
def clone
|
90
|
-
cloned_vm = clone_vm
|
91
|
-
start_vm(cloned_vm)
|
92
|
-
record_conf(cloned_vm)
|
93
|
-
end
|
94
|
-
|
95
|
-
def cook
|
96
|
-
puts "----Cook a clone!----"
|
97
|
-
puts "Which VM do you want to cook?"
|
98
|
-
role_path = load_conf["role_path"]
|
99
|
-
cookbook_path = load_conf["cookbook_path"]
|
100
|
-
|
101
|
-
selected_vm = choose_vm "cloned_vms"
|
102
|
-
|
103
|
-
puts "Which role do you want to give?"
|
104
|
-
|
105
|
-
role = choose "roles"
|
106
|
-
|
107
|
-
user="root"
|
108
|
-
|
109
|
-
if start_vm(selected_vm)
|
110
|
-
cmd = "ssh #{user}@#{selected_vm.ip} '/usr/local/bin/chef-solo -j #{role_path}#{role}.json -r #{cookbook_path}'"
|
111
|
-
p cmd
|
112
|
-
system(cmd)
|
113
|
-
end
|
114
|
-
|
115
|
-
puts "Thanks!"
|
116
|
-
end
|
117
|
-
|
118
|
-
def ssh
|
119
|
-
puts "----SSH to cloned VM----"
|
120
|
-
puts "Which VM do you want to ssh?"
|
121
|
-
|
122
|
-
selected_vm = choose_vm "cloned_vms"
|
123
|
-
|
124
|
-
puts "With which user? [root]"
|
125
|
-
user = Menu.ask
|
126
|
-
|
127
|
-
if user.empty?
|
128
|
-
user = "root"
|
129
|
-
end
|
130
|
-
|
131
|
-
if start_vm(selected_vm)
|
132
|
-
cmd = "ssh #{user}@#{selected_vm.ip}"
|
133
|
-
exec (cmd)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def delete
|
138
|
-
puts "----Delete a cloned VM----"
|
139
|
-
puts "Which VM delete?"
|
140
|
-
|
141
|
-
selected_vm = choose_vm "cloned_vms"
|
142
|
-
|
143
|
-
puts "!!YOU ARE ABOUT TO DELETE PEMANENTLY DELETE #{selected_vm.name}!!"
|
144
|
-
puts "Are you sure? (yes/[no])"
|
145
|
-
|
146
|
-
choice = Menu.ask
|
147
|
-
|
148
|
-
if choice == "yes"
|
149
|
-
selected_vm.stop!
|
150
|
-
|
151
|
-
selected_vm.manage("unregistervm","--delete")
|
152
|
-
|
153
|
-
record_conf(selected_vm, "delete")
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def add
|
158
|
-
puts "----Add a VM----"
|
159
|
-
puts "Give the path of the OVA file you want to add"
|
160
|
-
puts "(An OVA file represents a Virtual Machine)"
|
161
|
-
|
162
|
-
path = Menu.ask
|
163
|
-
|
164
|
-
cmd = "VBoxManage import #{path}"
|
165
|
-
p cmd
|
166
|
-
system(cmd)
|
167
|
-
|
168
|
-
added_vm = Vm.new(File.basename("#{path}",".*"))
|
169
|
-
record_conf(added_vm)
|
170
|
-
end
|
171
|
-
|
172
|
-
def bootify
|
173
|
-
puts "----Start a VM at boot----"
|
174
|
-
puts "Whcih VM do you want to add to your boot sequence?"
|
175
|
-
|
176
|
-
selected_vm = choose_vm "cloned_vms"
|
177
|
-
|
178
|
-
# Create template.
|
179
|
-
template_plist = %q{
|
180
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
181
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
182
|
-
<plist version="1.0">
|
183
|
-
<dict>
|
184
|
-
<key>Label</key>
|
185
|
-
<string>org.virtualman.<%= vmname %></string>
|
186
|
-
<key>ProgramArguments</key>
|
187
|
-
<array>
|
188
|
-
<string>VBoxManage</string>
|
189
|
-
<string>startvm</string>
|
190
|
-
<string><%= vmname %></string>
|
191
|
-
<string>--type</string>
|
192
|
-
<string>headless</string>
|
193
|
-
</array>
|
194
|
-
<key>RunAtLoad</key>
|
195
|
-
<true/>
|
196
|
-
<key>UserName</key>
|
197
|
-
<string><%= username %></string>
|
198
|
-
<key>debug</key>
|
199
|
-
<true/>
|
200
|
-
</dict>
|
201
|
-
</plist>
|
202
|
-
}.gsub(/^ /, '')
|
203
|
-
|
204
|
-
plist = ERB.new(template_plist, 0, "%<>")
|
205
|
-
|
206
|
-
# Set up template data.
|
207
|
-
|
208
|
-
vmname = selected_vm.name.gsub(/\"/,'')
|
209
|
-
username = `whoami`.strip
|
210
|
-
|
211
|
-
# Produce result.
|
212
|
-
|
213
|
-
vm_plist = plist.result(binding)
|
214
|
-
|
215
|
-
plist_filename = "org.virtualman.#{vmname}.plist"
|
216
|
-
tmp_file = "/tmp/#{plist_filename}"
|
217
|
-
path = "/Library/LaunchDaemons/"
|
218
|
-
|
219
|
-
File.open(tmp_file, 'w') { |file| file.write(vm_plist) }
|
220
|
-
|
221
|
-
`sudo cp #{tmp_file} #{path};sudo launchctl load #{path}#{plist_filename}`
|
222
|
-
|
223
|
-
puts "#{vmname} added to your boot sequence."
|
224
|
-
end
|
225
|
-
|
226
|
-
end
|