Module: Veewee::Provider::Parallels::BoxCommand

Included in:
Box
Defined in:
lib/veewee/provider/parallels/box/up.rb,
lib/veewee/provider/parallels/box/halt.rb,
lib/veewee/provider/parallels/box/build.rb,
lib/veewee/provider/parallels/box/export.rb,
lib/veewee/provider/parallels/box/create.rb,
lib/veewee/provider/parallels/box/destroy.rb,
lib/veewee/provider/parallels/box/poweroff.rb,
lib/veewee/provider/parallels/box/helper/ip.rb,
lib/veewee/provider/parallels/box/helper/status.rb,
lib/veewee/provider/parallels/box/helper/buildinfo.rb,
lib/veewee/provider/parallels/box/validate_parallels.rb,
lib/veewee/provider/parallels/box/helper/console_type.rb

Defined Under Namespace

Classes: ErbBinding

Instance Method Summary collapse

Instance Method Details

#build(options) ⇒ Object


6
7
8
# File 'lib/veewee/provider/parallels/box/build.rb', line 6

def build(options)
  super(options)
end

#build_infoObject


6
7
8
9
10
11
12
# File 'lib/veewee/provider/parallels/box/helper/buildinfo.rb', line 6

def build_info
  info=super
  command="prlctl --version"
  output=IO.popen("#{command}").readlines
  info << {:filename => ".parallels_version",:content => output[0].split(/ /)[2]}

end

#console_type(sequence, type_options = {}) ⇒ Object

Type on the console


7
8
9
10
# File 'lib/veewee/provider/parallels/box/helper/console_type.rb', line 7

def console_type(sequence,type_options={})
  #FIXME
  send_sequence(sequence)
end

#create(options) ⇒ Object

When we create a new box We assume the box is not running


8
9
10
11
12
# File 'lib/veewee/provider/parallels/box/create.rb', line 8

def create(options)
  create_vm
  create_disk
  #self.create_floppy("virtualfloppy.img")
end

#create_diskObject


15
16
# File 'lib/veewee/provider/parallels/box/create.rb', line 15

def create_disk
end

#create_vmObject


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/veewee/provider/parallels/box/create.rb', line 25

def create_vm
  parallels_definition=definition.dup
  distribution=parallels_os_type(definition.os_type_id)

  # Create the vm
  command="prlctl create '#{self.name}' --distribution '#{distribution}'"
  shell_exec("#{command}")
  command="prlctl set '#{self.name}' --cpus #{definition.cpu_count} --memsize #{definition.memory_size}"
  shell_exec("#{command}")


  #NOTE: order is important: as this determines the boot order sequence
  #
  # Remove the network to disable pxe boot
  command="prlctl set '#{self.name}' --device-del net0"
  shell_exec("#{command}")

  # Remove default cdrom
  command ="prlctl set '#{self.name}' --device-del cdrom0"
  shell_exec("#{command}")
  #
  # Attach cdrom
  full_iso_file=File.join(env.config.veewee.iso_dir,definition.iso_file)
  ui.info "Mounting cdrom: #{full_iso_file}"
  command ="prlctl set '#{self.name}' --device-add cdrom --enable --image '#{full_iso_file}'"
  shell_exec("#{command}")

  #Enable the network again
  command="prlctl set '#{self.name}' --device-add net --enable --type shared"
  shell_exec("#{command}")



end

#destroy(options = {}) ⇒ Object


6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/veewee/provider/parallels/box/destroy.rb', line 6

def destroy(options={})
  unless self.exists?
    raise Veewee::Error, "Error:: You tried to destroy a non-existing box '#{name}'"
  end

  if self.running?
    self.poweroff
    sleep 2
  end

  command="prlctl delete '#{self.name}'"
  shell_exec("#{command}")

end

#exists?Boolean

Check if box is running

Returns:

  • (Boolean)

16
17
18
19
20
21
22
23
24
# File 'lib/veewee/provider/parallels/box/helper/status.rb', line 16

def exists?

  command="prlctl list --all "
  shell_results=shell_exec("#{command}",{:mute => true})
  exists=shell_results.stdout.split(/\n/).grep(/ #{name}$/).size!=0

  env.logger.info("Vm exists? #{exists}")
  return exists
end

#export_vagrant(options) ⇒ Object


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/veewee/provider/parallels/box/export.rb', line 14

def export_vagrant(options)
  # For now, we just assume prlctl is in the path. If not...it'll fail.
  @prlcmd = "prlctl"
  @prldisktool = "prl_disk_tool"

  # Check if box already exists
  unless self.exists?
    ui.info "#{name} is not found, maybe you need to build it first?"
    exit
  end

  if File.exists?("#{name}.box")
    if options["force"]
      env.logger.debug("#{name}.box exists, but --force was provided")
      env.logger.debug("removing #{name}.box first")
      FileUtils.rm("#{name}.box")
      env.logger.debug("#{name}.box removed")
    else
      raise Veewee::Error, "export file #{name}.box already exists. Use --force option to overwrite."
    end
  end


  # We need to shutdown first
  if self.running?
    ui.info "Vagrant requires the box to be shutdown, before it can export"
    ui.info "Sudo also needs to work for user #{definition.ssh_user}"
    ui.info "Performing a clean shutdown now."

    self.halt

    #Wait for state poweroff
    while (self.running?) do
      ui.info ".",{:new_line => false}
      sleep 1
    end
    ui.info ""
    ui.info "Machine #{name} is powered off cleanly"
  end

  #Vagrant requires a relative path for output of boxes

  #4.0.x. not using boxes as a subdir
  boxdir=Pathname.new(Dir.pwd)

  full_path=File.join(boxdir,name+".box")
  path1=Pathname.new(full_path)
  path2=Pathname.new(Dir.pwd)
  box_path=File.expand_path(path1.relative_path_from(path2).to_s)

  if File.exists?("#{box_path}")
    raise Veewee::Error, "box #{name}.box already exists"
  end

  # VMWare Fusion does this to the real machine, so we will too.
  optimize_disk

  # Create temp directory
  current_dir = FileUtils.pwd
  ui.info "Creating a temporary directory for export"
  tmp_dir = Dir.mktmpdir
  env.logger.debug("Create temporary directory for export #{tmp_dir}")

  begin

    ui.info "Adding additional files"

    # Handling the Vagrantfile
    if options["vagrantfile"].to_s == ""

      # Fetching mac address

      data = {
        :macaddress => get_mac_address
      }

      # Prepare the vagrant erb
      vars = ErbBinding.new(data)
      template_path = File.join(File.dirname(__FILE__),'..','..','..','templates',"Vagrantfile.erb")
      template = File.open(template_path).readlines.join
      erb = ERB.new(template)
      vars_binding = vars.send(:get_binding)
      result = erb.result(vars_binding)
      ui.info("Creating Vagrantfile")
      vagrant_path = File.join(tmp_dir,'Vagrantfile')
      env.logger.debug("Path: #{vagrant_path}")
      env.logger.debug(result)
      File.open(vagrant_path,'w') {|f| f.write(result) }
    else
      f = options["vagrantfile"]
      env.logger.debug("Including vagrantfile: #{f}")
      FileUtils.cp(f,File.join(tmp_dir,"Vagrantfile"))
    end

    #Inject a metadata.json file
    ui.info("Adding metadata.json file for Parallels Desktop provider")
    File.open(File.join(tmp_dir, 'metadata.json'), 'w') {|f| f.write(template_metadatafile()) }

    ui.info "Exporting the box"
    tmp_dest = File.join(tmp_dir, "box.pvm")

    clone_command = "#{@prlcmd} clone #{name} --name #{name}-veewee --template --location #{tmp_dir}"
    shell_exec clone_command
    env.logger.debug("Clone #{name} to #{name}-veewee, location #{tmp_dir}")

    # Previous command causes the VM to get registered, so unregister it to keep user's VM list clean
    unregister_command = "#{@prlcmd} unregister #{name}-veewee"
    shell_exec unregister_command
    env.logger.debug "Unregister #{name}-veewee after clone"

    FileUtils.move File.join(tmp_dir, "#{name}-veewee.pvm"), tmp_dest
    env.logger.debug("Rename Parallels Desktop-created file to what we expect")

    ui.info "Packaging the box"
    FileUtils.cd(tmp_dir)
    command_box_path = box_path
    # Gzip, for extra smallness
    command = "tar -cvzf '#{command_box_path}' ."
    env.logger.debug(command)
    shell_exec (command)

  rescue Errno::ENOENT => ex
    raise Veewee::Error, "#{ex}"
  rescue Error => ex
    raise Veewee::Error, "Packaging of the box failed:\n+#{ex}"
  ensure
    # Remove temporary directory
    ui.info "Cleaning up temporary directory"
    env.logger.debug("Removing temporary dir #{tmp_dir}")
    FileUtils.rm_rf(tmp_dir)

    FileUtils.cd(current_dir)
  end
  ui.info ""

  ui.info "To import it into vagrant type:"
  ui.info "vagrant box add '#{name}' '#{box_path}'"
  ui.info ""
  ui.info "To use it:"
  ui.info "vagrant init '#{name}'"
  ui.info "vagrant up --provider=parallels"
  ui.info "vagrant ssh"
end

#get_mac_addressObject


165
166
167
168
169
# File 'lib/veewee/provider/parallels/box/export.rb', line 165

def get_mac_address
  mac = read_settings.fetch("Hardware").fetch("net0").fetch("mac")
  env.logger.debug("mac address: #{mac}")
  return mac
end

#guest_iso_pathObject

Determine the iso of the guest additions


15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/veewee/provider/parallels/box/helper/buildinfo.rb', line 15

def guest_iso_path
  # So we begin by transferring the ISO file of the vmware tools

  parallels_base_path = "/Applications/Parallels Desktop.app/Contents/Resources/Tools"

  # Set default
  iso_image="#{parallels_base_path}/prl-tools-lin.iso"
  iso_image="#{parallels_base_path}/prl-tools-mac.iso" if definition.os_type_id=~/^Darwin/
  iso_image="#{parallels_base_path}/prl-tools-win.iso" if definition.os_type_id=~/^Win/
  iso_image="#{parallels_base_path}/prl-tools-other.iso" if definition.os_type_id=~/^Free/
  iso_image="#{parallels_base_path}/prl-tools-other.iso" if definition.os_type_id=~/^Solaris/
  return iso_image

end

#halt(options = {}) ⇒ Object

FIXME


7
8
9
# File 'lib/veewee/provider/parallels/box/halt.rb', line 7

def halt(options={})
   super(options)
end

#ip_addressObject

Get the IP address of the box


7
8
9
10
11
12
# File 'lib/veewee/provider/parallels/box/helper/ip.rb', line 7

def ip_address
  mac=mac_address
  command="grep #{mac} /Library/Preferences/Parallels/parallels_dhcp_leases|cut -d '=' -f 1"
  ip=shell_exec("#{command}").stdout.strip.downcase
  return ip
end

#mac_addressObject


14
15
16
17
18
# File 'lib/veewee/provider/parallels/box/helper/ip.rb', line 14

def mac_address
  command="prlctl list -i '#{self.name}'|grep 'net0 (' | cut -d '=' -f 3 | cut -d ' ' -f 1 "
  mac=shell_exec("#{command}").stdout.strip.downcase
  return mac
end

#optimize_diskObject


175
176
177
178
179
180
# File 'lib/veewee/provider/parallels/box/export.rb', line 175

def optimize_disk
  env.ui.info "Optimizing Disk"
  path_to_hdd = File.join read_settings.fetch("Home"), "harddisk.hdd"
  optimize_command = "#{@prldisktool} compact --hdd #{path_to_hdd}"
  shell_exec optimize_command
end

#parallels_os_type(type_id) ⇒ Object


18
19
20
21
22
23
# File 'lib/veewee/provider/parallels/box/create.rb', line 18

def parallels_os_type(type_id)
  env.logger.info "Translating #{type_id} into parallels type"
  parallelstype=env.ostypes[type_id][:parallels]
  env.logger.info "Found Parallels type #{parallelstype}"
  return parallelstype
end

#poweroff(options = {}) ⇒ Object


6
7
8
9
# File 'lib/veewee/provider/parallels/box/poweroff.rb', line 6

def poweroff(options={})
  command="prlctl stop '#{self.name}' --kill"
  shell_exec("#{command}")
end

#press_key(key) ⇒ Object

Returns array


42
43
44
# File 'lib/veewee/provider/parallels/box/helper/console_type.rb', line 42

def press_key(key)
	  return key
end

#read_settingsObject

Inspired by vagrant-parallels


159
160
161
162
163
# File 'lib/veewee/provider/parallels/box/export.rb', line 159

def read_settings
  command = "#{@prlcmd} list --info --json \"#{self.name}\""
  r = shell_exec(command).stdout
  JSON.parse (r.gsub("/\s+/", "").gsub(/^(INFO)?\[/, '').gsub(/\]$/, ''))
end

#running?Boolean

Returns:

  • (Boolean)

6
7
8
9
10
11
12
13
# File 'lib/veewee/provider/parallels/box/helper/status.rb', line 6

def running?
  command="prlctl list -i '#{self.name}'"
  shell_results=shell_exec("#{command}",{:mute => true})
  running=shell_results.stdout.split(/\n/).grep(/^State: running/).size!=0

  env.logger.info("Vm running? #{running}")
  return running
end

#send_sequence(sequence) ⇒ Object


12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/veewee/provider/parallels/box/helper/console_type.rb', line 12

def send_sequence(sequence)

  ui.info ""

  counter=0
  sequence.each { |s|
    counter=counter+1

    ui.info "Typing:[#{counter}]: "+s

	    # No need to send the state, we always PRESS then RELEASE.
	    # So we send the whole string at once and press/release every string item
    keystring=self.string_to_parallels_keycode_string(s)

	    send_string(keystring);
  }

  ui.info "Done typing."
  ui.info ""


end

#send_string(keystring) ⇒ Object


35
36
37
38
39
# File 'lib/veewee/provider/parallels/box/helper/console_type.rb', line 35

def send_string(keystring)
  python_script=File.join(File.dirname(__FILE__),'..','..','..','..','..','python','parallels_send_string.py')
  command="python #{python_script} '#{self.name}' '#{keystring}'"
  shell_results=shell_exec("#{command}")
end

#shift(sequence) ⇒ Object

Returns array


47
48
49
50
51
52
53
54
55
# File 'lib/veewee/provider/parallels/box/helper/console_type.rb', line 47

def shift(sequence)
  seq=Array.new
  seq << press_key('SHIFT_LEFT')

  # We never apply SHIFT_LEFT to more than one logical character at a
  # time
  seq << sequence
  return seq
end

#string_to_parallels_keycode_string(thestring) ⇒ Object


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/veewee/provider/parallels/box/helper/console_type.rb', line 57

def string_to_parallels_keycode_string(thestring)


  # Setup one key presses
  k=Hash.new
  for key in 'A'..'Z'
    k[key] = shift(press_key(key))
  end

  for key in 'a'..'z'
    k[key] = press_key(key.upcase)
  end

  for key in '0'..'9'
    k[key] = press_key(key.upcase)
  end

  k['>'] = shift(press_key('GREATER'))
  k['.'] = press_key('GREATER')
  k['<'] = shift(press_key('LESS'))
  k[':'] = shift(press_key('COLON'))
  k[';'] = press_key('COLON')
  k['/'] = press_key('SLASH')
  k[' '] = press_key('SPACE')
  k['-'] = press_key('MINUS')
  k['\''] = press_key('QUOTE')
  k['{'] = press_key('CBRACE_LEFT')
  k['}'] = press_key('CBRACE_RIGHT')
  k['`'] = press_key('TILDA')
  k['~'] = shift(press_key('TILDA'))

  k['_'] = shift(press_key('MINUS'))
  k['?'] = shift(press_key('SLASH'))
  k['"'] = shift(press_key('QUOTE'))
  k[')'] = shift(press_key('0'))
  k['!'] = shift(press_key('1'))
  k['@'] = shift(press_key('2'))
  k['#'] = shift(press_key('3'))
  k['$'] = shift(press_key('4'))
  k['%'] = shift(press_key('5'))
  k['^'] = shift(press_key('6'))
  k['&'] = shift(press_key('7'))
  k['*'] = shift(press_key('8'))
  k['('] = shift(press_key('9'))
  k['|'] = shift(press_key('BACKSLASH'))
  k[','] = press_key('LESS')
  k['\\'] = press_key('BACKSLASH')
  k['+'] = shift(press_key('PLUS'))
  k['='] = press_key('PLUS')

  # Setup special keys
  special=Hash.new;
  special['<Enter>'] = [{ 'code' => 'ENTER', 'state' => 'press' }]
  special['<Backspace>'] = [{ 'code' => 'BACKSPACE', 'state' => 'press' }]
  special['<Spacebar>'] = [{ 'code' => 'SPACE', 'state' => 'press' }]
  special['<Return>'] = [{ 'code' => 'RETURN', 'state' => 'press' }]
  special['<Esc>'] = [{ 'code' => 'ESC', 'state' => 'press' }]
  special['<Tab>'] = [{ 'code' => 'TAB', 'state' => 'press' }]
  #FIXME
  special['<KillX>'] = '1d 38 0e';
  special['<Wait>'] = [{'code' => 'wait', 'state' => ''}];

  special['<Up>'] = [{ 'code' => 'UP', 'state' => 'press' }]
  special['<Down>'] = [{ 'code' => 'DOWN', 'state' => 'press' }]
  special['<PageUp'] = [{ 'code' => 'PAGE_UP', 'state' => 'press' }]
  special['<PageDown'] = [{ 'code' => 'PAGE_DOWN', 'state' => 'press' }]
  special['<End>'] = [{ 'code' => 'END', 'state' => 'press' }]
  special['<Insert>'] = [{ 'code' => 'INSERT', 'state' => 'press' }]
  special['<Delete>'] = [{ 'code' => 'DELETE', 'state' => 'press' }]
  special['<Left>'] = [{ 'code' => 'LEFT', 'state' => 'press' }]
  special['<Right>'] = [{ 'code' => 'RIGHT', 'state' => 'press' }]
  special['<Home>'] = [{ 'code' => 'HOME', 'state' => 'press' }]

  for i in 1..12 do
    special["<F#{i}>"] = press_key("F#{i}")
  end

  keycodes= ""

  until thestring.length == 0
    nospecial=true;
    special.keys.each { |key|
      if thestring.start_with?(key)
        #take thestring
        #check if it starts with a special key + pop special string
        if key=="<Wait>"
          sleep 1
        else
          special[key].each do |c|
            keycodes.concat("#{c['code']} ")
          end
        end
        thestring=thestring.slice(key.length,thestring.length-key.length)
        nospecial=false;
        break;
      end
    }
    if nospecial
      code=k[thestring.slice(0,1)]
      if !code.nil?
        code=[code] if code.class==String
        code.each do |c|
		  if(c == 'SHIFT_LEFT')
          	keycodes.concat("#{c}\#")
		  else
          	keycodes.concat("#{c} ")
		  end
        end
      else
        ui.info "no scan code for #{thestring.slice(0,1)}"
      end
      #pop one
      thestring=thestring.slice(1,thestring.length-1)
    end
  end

  return keycodes
end

#template_metadatafileObject


171
172
173
# File 'lib/veewee/provider/parallels/box/export.rb', line 171

def template_metadatafile
  %Q({"provider": "parallels"}\n)
end

#transfer_buildinfo(options) ⇒ Object

Transfer information provide by the provider to the box


33
34
35
36
37
38
39
40
41
42
# File 'lib/veewee/provider/parallels/box/helper/buildinfo.rb', line 33

def transfer_buildinfo(options)
  super(options)

  # When we get here, ssh is available and no postinstall scripts have been executed yet
  # So we begin by transferring the ISO file of the vmware tools

  ui.info "About to transfer parallels tools iso buildinfo to the box #{name} - #{ip_address} - #{ssh_options}"
  iso_image=guest_iso_path
  self.copy_to_box(iso_image,File.basename(iso_image))
end

#up(options = {}) ⇒ Object


6
7
8
9
10
# File 'lib/veewee/provider/parallels/box/up.rb', line 6

def up(options={})
  gui_enabled=options[:nogui]==true ? false : true
  command="prlctl start '#{self.name}'"
  shell_exec("#{command}")
end

#validate_parallels(options) ⇒ Object


6
7
8
# File 'lib/veewee/provider/parallels/box/validate_parallels.rb', line 6

def validate_parallels(options)
  validate_tags([ 'parallels','puppet','chef'],options)
end