prlbackup 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Guardfile +21 -0
- data/README.md +27 -0
- data/Rakefile +14 -0
- data/bin/prlbackup +5 -0
- data/features/cleanup.feature +29 -0
- data/features/create_backups.feature +53 -0
- data/features/dev_steps.feature +60 -0
- data/features/logging.feature +39 -0
- data/features/steps/dev_steps.rb +49 -0
- data/features/support/env.rb +2 -0
- data/lib/prlbackup/cli.rb +90 -0
- data/lib/prlbackup/version.rb +3 -0
- data/lib/prlbackup/virtual_machine.rb +111 -0
- data/lib/prlbackup.rb +51 -0
- data/man/prlbackup +31 -0
- data/man/prlbackup.1 +168 -0
- data/man/prlbackup.1.html +195 -0
- data/man/prlbackup.1.ronn +107 -0
- data/man/prlbackup.html +253 -0
- data/prlbackup.gemspec +32 -0
- data/spec/prlbackup/cli_spec.rb +84 -0
- data/spec/prlbackup/virtual_machine_spec.rb +252 -0
- data/spec/prlbackup_spec.rb +126 -0
- data/spec/spec_helper.rb +1 -0
- metadata +213 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2, :cli => '--color' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'cucumber' do
|
11
|
+
watch(%r{^bin/.+$}) { 'features' }
|
12
|
+
watch(%r{^lib/.+$}) { 'features' }
|
13
|
+
watch(%r{^features/.+\.feature$})
|
14
|
+
watch(%r{^features/support/.+$}) { 'features' }
|
15
|
+
watch(%r{^features/steps/.+$}) { 'features' }
|
16
|
+
end
|
17
|
+
|
18
|
+
guard 'ronn', :cli => '--manual="PRLBACKUP MANUAL" --style=toc' do
|
19
|
+
watch(%r{^man/.+\.ronn$})
|
20
|
+
watch(%r{^man/.+\.(md|markdown)$})
|
21
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
prlbackup(1) -- an awesome backup tool for Parallels Server Virtual Machines
|
2
|
+
============================================================================
|
3
|
+
|
4
|
+
Installation
|
5
|
+
------------
|
6
|
+
|
7
|
+
gem install prlbackup
|
8
|
+
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
Take a look at `gem man prlbackup`.
|
13
|
+
|
14
|
+
Note on Patches/Pull Requests
|
15
|
+
-----------------------------
|
16
|
+
|
17
|
+
* Fork the project.
|
18
|
+
* Make your feature addition or bug fix.
|
19
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
20
|
+
* Commit, do not mess with Rakefile, gemspec or History.
|
21
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
22
|
+
* Send me a pull request. Bonus points for topic branches.
|
23
|
+
|
24
|
+
Copyright
|
25
|
+
---------
|
26
|
+
|
27
|
+
Copyright (c) 2012 Björn Albers (MIT License)
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'cucumber/rake/task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
6
|
+
t.cucumber_opts = "--format pretty"
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new do |t|
|
10
|
+
t.rspec_opts = %w[--color]
|
11
|
+
t.pattern = "./spec/**/*_spec.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :features
|
data/bin/prlbackup
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Feature: Cleanup
|
2
|
+
|
3
|
+
In order to not waste my time manually cleaning up
|
4
|
+
As a sysadmin using Parallels Server
|
5
|
+
I want to old backups to be automatically deleted
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the following virtual machines:
|
9
|
+
| uuid | status | name |
|
10
|
+
| {97351580-afd7-4aff-9960-814196b28e37} | stopped | Mac OS X Lion |
|
11
|
+
| {423dba54-45e3-46f1-9aa2-87d61ce6b757} | running | Windows XP |
|
12
|
+
| {55aae003-298d-4199-82ed-23658a218605} | stopped | Ubuntu |
|
13
|
+
|
14
|
+
Scenario: Delete old backups
|
15
|
+
Given I double `prlctl backup-list {55aae003-298d-4199-82ed-23658a218605}` with stdout:
|
16
|
+
"""
|
17
|
+
ID Backup_ID Node Date Type Size
|
18
|
+
{55aae003-298d-4199-82ed-23658a218605} {ae6565dd-7f8f-42cb-a088-8b1d98f5160b} psfm.example.com 02/27/2012 13:11:32 f 10537597943
|
19
|
+
{55aae003-298d-4199-82ed-23658a218605} {ae6565dd-7f8f-42cb-a088-8b1d98f5160b}.2 psfm.example.com 02/27/2012 15:26:02 i 2951747588
|
20
|
+
{55aae003-298d-4199-82ed-23658a218605} {5f9dd263-ec56-443e-9917-dab9b40d3027} psfm.example.com 03/13/2012 18:06:00 f 11748325372
|
21
|
+
{55aae003-298d-4199-82ed-23658a218605} {2aeb4ada-6623-4087-9fc5-f09aeaafd81e} psfm.example.com 03/23/2012 21:25:50 f 47315014888
|
22
|
+
{55aae003-298d-4199-82ed-23658a218605} {68f7e154-6755-46f6-ad1f-a79c5f488f35} psfm.example.com 03/28/2012 15:09:05 f 23462808438
|
23
|
+
{55aae003-298d-4199-82ed-23658a218605} {68f7e154-6755-46f6-ad1f-a79c5f488f35}.2 psfm.example.com 04/05/2012 17:21:12 i 12841952117
|
24
|
+
"""
|
25
|
+
When I successfully run `prlbackup --keep-only 2 Ubuntu`
|
26
|
+
Then the double `prlctl backup-delete --tag {ae6565dd-7f8f-42cb-a088-8b1d98f5160b}` should have been run
|
27
|
+
And the double `prlctl backup-delete --tag {5f9dd263-ec56-443e-9917-dab9b40d3027}` should have been run
|
28
|
+
But the double `prlctl backup-delete --tag {2aeb4ada-6623-4087-9fc5-f09aeaafd81e}` should not have been run
|
29
|
+
And the double `prlctl backup-delete --tag {68f7e154-6755-46f6-ad1f-a79c5f488f35}` should not have been run
|
@@ -0,0 +1,53 @@
|
|
1
|
+
Feature: Create Backups
|
2
|
+
|
3
|
+
In order to sleep well
|
4
|
+
As a sysadmin using Parallels Server
|
5
|
+
I want to create my backups with prlbackup
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the following virtual machines:
|
9
|
+
| uuid | status | name |
|
10
|
+
| {97351580-afd7-4aff-9960-814196b28e37} | stopped | Mac OS X Lion |
|
11
|
+
| {423dba54-45e3-46f1-9aa2-87d61ce6b757} | running | Windows XP |
|
12
|
+
| {55aae003-298d-4199-82ed-23658a218605} | stopped | Ubuntu |
|
13
|
+
|
14
|
+
Scenario: Backup single VM (already stopped)
|
15
|
+
When I successfully run `prlbackup Ubuntu`
|
16
|
+
Then the double `prlctl stop {55aae003-298d-4199-82ed-23658a218605}` should not have been run
|
17
|
+
And the double `prlctl backup {55aae003-298d-4199-82ed-23658a218605}` should have been run
|
18
|
+
And the double `prlctl start {55aae003-298d-4199-82ed-23658a218605}` should not have been run
|
19
|
+
|
20
|
+
Scenario: Backup multiple VMs
|
21
|
+
When I successfully run `prlbackup Ubuntu Windows\ XP`
|
22
|
+
Then the double `prlctl backup {55aae003-298d-4199-82ed-23658a218605}` should have been run
|
23
|
+
And the double `prlctl backup {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should have been run
|
24
|
+
But the double `prlctl backup {97351580-afd7-4aff-9960-814196b28e37}` should not have been run
|
25
|
+
|
26
|
+
Scenario: Stop VM during backup
|
27
|
+
When I successfully run `prlbackup "Windows XP"`
|
28
|
+
Then the double `prlctl stop {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should have been run
|
29
|
+
And the double `prlctl backup {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should have been run
|
30
|
+
And the double `prlctl start {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should have been run
|
31
|
+
|
32
|
+
Scenario: Create full backup
|
33
|
+
When I successfully run `prlbackup --full Ubuntu`
|
34
|
+
And the double `prlctl backup {55aae003-298d-4199-82ed-23658a218605} --full` should have been run
|
35
|
+
|
36
|
+
Scenario: Backup all VMs
|
37
|
+
When I successfully run `prlbackup --all`
|
38
|
+
Then the double `prlctl backup {97351580-afd7-4aff-9960-814196b28e37}` should have been run
|
39
|
+
And the double `prlctl backup {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should have been run
|
40
|
+
And the double `prlctl backup {55aae003-298d-4199-82ed-23658a218605}` should have been run
|
41
|
+
|
42
|
+
Scenario: Exclude VMs from Backup
|
43
|
+
When I successfully run `prlbackup --all --exclude "Mac OS X Lion"`
|
44
|
+
Then the double `prlctl backup {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should have been run
|
45
|
+
And the double `prlctl backup {55aae003-298d-4199-82ed-23658a218605}` should have been run
|
46
|
+
But the double `prlctl backup {97351580-afd7-4aff-9960-814196b28e37}` should not have been run
|
47
|
+
|
48
|
+
Scenario: Dry-run
|
49
|
+
When I successfully run `prlbackup --dry-run "Windows XP"`
|
50
|
+
Then the double `prlctl list --info "Windows XP"` should have been run
|
51
|
+
But the double `prlctl stop {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should not have been run
|
52
|
+
And the double `prlctl backup {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should not have been run
|
53
|
+
And the double `prlctl start {423dba54-45e3-46f1-9aa2-87d61ce6b757}` should not have been run
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Feature: Development Steps
|
2
|
+
|
3
|
+
In order to keep my cucumber scenarios nice and tight
|
4
|
+
As a developer of prlbackup
|
5
|
+
I want convenient development steps to fake virtual machines
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the following virtual machines:
|
9
|
+
| uuid | status | name |
|
10
|
+
| {97351580-afd7-4aff-9960-814196b28e37} | stopped | Mac OS X Lion |
|
11
|
+
| {423dba54-45e3-46f1-9aa2-87d61ce6b757} | running | Windows XP |
|
12
|
+
| {55aae003-298d-4199-82ed-23658a218605} | stopped | Ubuntu |
|
13
|
+
|
14
|
+
Scenario: prlctl list --info Ubuntu
|
15
|
+
When I successfully run `prlctl list --info Ubuntu`
|
16
|
+
Then the stdout should contain exactly:
|
17
|
+
"""
|
18
|
+
ID: {55aae003-298d-4199-82ed-23658a218605}
|
19
|
+
Name: Ubuntu
|
20
|
+
State: stopped
|
21
|
+
|
22
|
+
"""
|
23
|
+
|
24
|
+
Scenario: prlctl stop ...
|
25
|
+
When I successfully run `prlctl stop {55aae003-298d-4199-82ed-23658a218605}`
|
26
|
+
Then the stdout should contain exactly:
|
27
|
+
"""
|
28
|
+
Stopping the VM...
|
29
|
+
The VM has been successfully stopped.
|
30
|
+
|
31
|
+
"""
|
32
|
+
|
33
|
+
Scenario: prlctl backup ...
|
34
|
+
When I successfully run `prlctl backup {55aae003-298d-4199-82ed-23658a218605}`
|
35
|
+
Then the stdout should contain exactly:
|
36
|
+
"""
|
37
|
+
Backing up the VM Ubuntu
|
38
|
+
The virtual machine has been successfully backed up with backup id {d51e6df1-83e9-46e2-aef1-3807d721c1be}.
|
39
|
+
|
40
|
+
"""
|
41
|
+
|
42
|
+
Scenario: prlctl start ...
|
43
|
+
When I successfully run `prlctl start {55aae003-298d-4199-82ed-23658a218605}`
|
44
|
+
Then the stdout should contain exactly:
|
45
|
+
"""
|
46
|
+
Starting the VM...
|
47
|
+
The VM has been successfully started.
|
48
|
+
|
49
|
+
"""
|
50
|
+
|
51
|
+
Scenario: prlctl list --all ...
|
52
|
+
When I run `prlctl list --all --output uuid`
|
53
|
+
Then the stdout should contain exactly:
|
54
|
+
"""
|
55
|
+
UUID
|
56
|
+
{97351580-afd7-4aff-9960-814196b28e37}
|
57
|
+
{423dba54-45e3-46f1-9aa2-87d61ce6b757}
|
58
|
+
{55aae003-298d-4199-82ed-23658a218605}
|
59
|
+
|
60
|
+
"""
|
@@ -0,0 +1,39 @@
|
|
1
|
+
Feature: Logging
|
2
|
+
|
3
|
+
In order to be able to review the backup activities
|
4
|
+
As a sysadmin using Parallels Server
|
5
|
+
I want to have a logging feature
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the following virtual machines:
|
9
|
+
| uuid | status | name |
|
10
|
+
| {97351580-afd7-4aff-9960-814196b28e37} | stopped | Mac OS X Lion |
|
11
|
+
| {423dba54-45e3-46f1-9aa2-87d61ce6b757} | running | Windows XP |
|
12
|
+
| {55aae003-298d-4199-82ed-23658a218605} | stopped | Ubuntu |
|
13
|
+
|
14
|
+
Scenario: Log the last stdout line from prlctl
|
15
|
+
When I successfully run `prlbackup "Windows XP"`
|
16
|
+
And the stdout should contain "[Windows XP] Starting backup..."
|
17
|
+
And the stdout should contain "The VM has been successfully stopped"
|
18
|
+
And the stdout should contain "The virtual machine has been successfully backed up with backup id"
|
19
|
+
And the stdout should contain "The VM has been successfully started"
|
20
|
+
|
21
|
+
Scenario: Log the virtual machines name
|
22
|
+
When I successfully run `prlbackup --all`
|
23
|
+
Then the output should match /Mac OS X Lion.+successfully backed up/
|
24
|
+
And the output should match /Windows XP.+successfully backed up/
|
25
|
+
And the output should match /Ubuntu.+successfully backed up/
|
26
|
+
|
27
|
+
Scenario: Log errors
|
28
|
+
Given I double `prlctl backup {55aae003-298d-4199-82ed-23658a218605}` with exit status 42 and stderr:
|
29
|
+
"""
|
30
|
+
BOOOOM!
|
31
|
+
"""
|
32
|
+
When I run `prlbackup Ubuntu`
|
33
|
+
Then the output should match /ERROR.+BOOOOM/
|
34
|
+
|
35
|
+
Scenario: Be verbose
|
36
|
+
When I run `prlbackup --verbose "Windows XP"`
|
37
|
+
Then the stdout should contain "Running `prlctl stop \{423dba54-45e3-46f1-9aa2-87d61ce6b757\}`"
|
38
|
+
And the stdout should contain "Running `prlctl backup \{423dba54-45e3-46f1-9aa2-87d61ce6b757\}`"
|
39
|
+
And the stdout should contain "Running `prlctl start \{423dba54-45e3-46f1-9aa2-87d61ce6b757\}`"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Before do
|
2
|
+
@aruba_timeout_seconds = 10
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^the following virtual machines?:$/ do |vm_table|
|
6
|
+
format = "%-40s%-13s%-16s%s"
|
7
|
+
all_vm = %w[UUID]
|
8
|
+
vm_table.hashes.each do |vm|
|
9
|
+
name, uuid, status = vm["name"], vm["uuid"], vm["status"]
|
10
|
+
all_vm << vm['uuid']
|
11
|
+
steps %Q{
|
12
|
+
Given I double `prlctl list --info "#{name}"` with stdout:
|
13
|
+
"""
|
14
|
+
ID: #{uuid}
|
15
|
+
Name: #{name}
|
16
|
+
State: #{status}
|
17
|
+
"""
|
18
|
+
And I double `prlctl list --info #{uuid}` with stdout:
|
19
|
+
"""
|
20
|
+
ID: #{uuid}
|
21
|
+
Name: #{name}
|
22
|
+
State: running
|
23
|
+
"""
|
24
|
+
And I double `prlctl stop #{uuid}` with stdout:
|
25
|
+
"""
|
26
|
+
Stopping the VM...
|
27
|
+
The VM has been successfully stopped.
|
28
|
+
"""
|
29
|
+
And I double `prlctl backup #{uuid}` with stdout:
|
30
|
+
"""
|
31
|
+
Backing up the VM #{name}
|
32
|
+
The virtual machine has been successfully backed up with backup id {d51e6df1-83e9-46e2-aef1-3807d721c1be}.
|
33
|
+
"""
|
34
|
+
And I double `prlctl start #{uuid}` with stdout:
|
35
|
+
"""
|
36
|
+
Starting the VM...
|
37
|
+
The VM has been successfully started.
|
38
|
+
"""
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
all_vm = all_vm.join("\n")
|
43
|
+
steps %Q{
|
44
|
+
Given I double `prlctl list --all --output uuid` with stdout:
|
45
|
+
"""
|
46
|
+
#{all_vm}
|
47
|
+
"""
|
48
|
+
}
|
49
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module PrlBackup
|
2
|
+
class CLI
|
3
|
+
include Mixlib::CLI
|
4
|
+
|
5
|
+
option :full,
|
6
|
+
:long => '--full',
|
7
|
+
:short => '-f',
|
8
|
+
:description => 'Create full backups.',
|
9
|
+
:boolean => true,
|
10
|
+
:default => false
|
11
|
+
|
12
|
+
option :all,
|
13
|
+
:long => '--all',
|
14
|
+
:short => '-a',
|
15
|
+
:description => 'Backup all virtual machines.',
|
16
|
+
:boolean => true,
|
17
|
+
:default => false
|
18
|
+
|
19
|
+
option :verbose,
|
20
|
+
:long => '--verbose',
|
21
|
+
:short => '-v',
|
22
|
+
:description => 'Be verbose.',
|
23
|
+
:boolean => 'true',
|
24
|
+
:default => false
|
25
|
+
|
26
|
+
option :exclude,
|
27
|
+
:long => '--exclude',
|
28
|
+
:short => '-e',
|
29
|
+
:description => 'Exclude given virtual machines from backup (in combination with --all).',
|
30
|
+
:boolean => true,
|
31
|
+
:default => false
|
32
|
+
|
33
|
+
option :dry_run,
|
34
|
+
:long => '--dry-run',
|
35
|
+
:short => '-n',
|
36
|
+
:description => 'Dont run commands which have an impact on virtual machines.',
|
37
|
+
:boolean => true,
|
38
|
+
:default => false
|
39
|
+
|
40
|
+
option :keep_only,
|
41
|
+
:long => '--keep-only n',
|
42
|
+
:short => '-k n',
|
43
|
+
:description => 'Keep only n full backups (delete the oldest!)',
|
44
|
+
:proc => Proc.new { |k| k.to_i }
|
45
|
+
|
46
|
+
class << self
|
47
|
+
# Run the backups with given options and arguments.
|
48
|
+
def run
|
49
|
+
self.new.run(ARGV)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Parse options and create safe backups for the selected virtual machines.
|
54
|
+
def run(argv)
|
55
|
+
parse_options!(argv)
|
56
|
+
selected_virtual_machines.each do |vm|
|
57
|
+
vm.safe_backup
|
58
|
+
vm.cleanup if config[:keep_only]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Parse options and set global configuration.
|
65
|
+
def parse_options!(argv)
|
66
|
+
@arguments = parse_options(argv)
|
67
|
+
PrlBackup.config = config
|
68
|
+
end
|
69
|
+
|
70
|
+
# The list of selected virtual machines which will be backed up.
|
71
|
+
# Note that this selection is based on the options and arguments.
|
72
|
+
# @return [Array<VirtualMachine>]
|
73
|
+
def selected_virtual_machines
|
74
|
+
case
|
75
|
+
when config[:all] && config[:exclude]
|
76
|
+
VirtualMachine.select { |vm| !given_virtual_machines.include?(vm) }
|
77
|
+
when config[:all]
|
78
|
+
VirtualMachine.all
|
79
|
+
else
|
80
|
+
given_virtual_machines
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# The list of virtual machines given as arguments via ARGV.
|
85
|
+
# @return [Array<VirtualMachine>]
|
86
|
+
def given_virtual_machines
|
87
|
+
@given_virtual_machines ||= @arguments.map { |a| VirtualMachine.new(a) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module PrlBackup
|
2
|
+
class VirtualMachine
|
3
|
+
include PrlBackup
|
4
|
+
|
5
|
+
class << self
|
6
|
+
include Enumerable
|
7
|
+
include PrlBackup
|
8
|
+
|
9
|
+
# Iterate over all virtual machines.
|
10
|
+
# @param [Block]
|
11
|
+
def each
|
12
|
+
all.each { |virtual_machine| yield(virtual_machine) }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a list of all virtual machines.
|
16
|
+
# @return [Array<VirtualMachine>]
|
17
|
+
def all
|
18
|
+
cmd = %w{prlctl list --all --output uuid}
|
19
|
+
command(*cmd).split("\n").grep(/(\{[a-f0-9-]+\})/) { new($1) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Initialize with a valid name or UUID from the virtual machine.
|
24
|
+
def initialize(name_or_uuid)
|
25
|
+
@name_or_uuid = name_or_uuid
|
26
|
+
update_info
|
27
|
+
end
|
28
|
+
|
29
|
+
def config
|
30
|
+
PrlBackup.config
|
31
|
+
end
|
32
|
+
|
33
|
+
# Safely backup the virtual machine.
|
34
|
+
# @note A running virtual machine will be stopped during the backup!
|
35
|
+
def safe_backup(full=false)
|
36
|
+
logger.info('Starting backup...')
|
37
|
+
stopped? ? backup : (stop; backup; start)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Cleanup (delete) old backups.
|
41
|
+
def cleanup
|
42
|
+
backups = full_backups
|
43
|
+
delete_backup(backups.shift) while backups.count > config[:keep_only]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the virtual machine's name.
|
47
|
+
# @return [String]
|
48
|
+
def name
|
49
|
+
info[/^Name:\s+(.+)$/,1] if info
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return the virtual machine's UUID.
|
53
|
+
# @return [String]
|
54
|
+
def uuid
|
55
|
+
info[/^ID:\s+(\{[a-f0-9-]+\})$/,1] if info
|
56
|
+
end
|
57
|
+
|
58
|
+
# Is equal if the virtual machines UUIDs are equal.
|
59
|
+
def ==(other_vm)
|
60
|
+
uuid == other_vm.uuid
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return the name of the virtual machine.
|
64
|
+
def to_s
|
65
|
+
name || 'Unknown VM'
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Info string about the virtual machine.
|
71
|
+
# @Note These infos will only be updated when calling `update_info`.
|
72
|
+
attr_reader :info
|
73
|
+
|
74
|
+
# Start the virtual machine.
|
75
|
+
def start
|
76
|
+
command!('prlctl', 'start', uuid)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Stop the virtual machine.
|
80
|
+
def stop
|
81
|
+
command!('prlctl', 'stop', uuid)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Backup the virtual machine.
|
85
|
+
def backup
|
86
|
+
cmd = ['prlctl', 'backup', uuid]
|
87
|
+
cmd << '--full' if config[:full]
|
88
|
+
command!(*cmd)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Update and return info string for the virtual machine.
|
92
|
+
# @return [String] infos
|
93
|
+
def update_info
|
94
|
+
@info = command('prlctl', 'list', '--info', @name_or_uuid)
|
95
|
+
end
|
96
|
+
|
97
|
+
def stopped?
|
98
|
+
update_info[/^State:\s+stopped$/]
|
99
|
+
end
|
100
|
+
|
101
|
+
# List of full backups for the virtual machine.
|
102
|
+
def full_backups
|
103
|
+
command('prlctl', 'backup-list', uuid).split("\n").map { |l| $1 if l[/^\{[a-f0-9-]+\}\s+(\{[a-f0-9-]+\})[^(\.\d+)]/] }.compact
|
104
|
+
end
|
105
|
+
|
106
|
+
# Delete the backup given by backup UUID.
|
107
|
+
def delete_backup(backup_uuid)
|
108
|
+
command!('prlctl', 'backup-delete', '--tag', backup_uuid)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/prlbackup.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'mixlib/cli'
|
2
|
+
require 'logger'
|
3
|
+
require 'shellwords'
|
4
|
+
require 'prlbackup/version'
|
5
|
+
require 'prlbackup/cli'
|
6
|
+
require 'prlbackup/virtual_machine'
|
7
|
+
|
8
|
+
module PrlBackup
|
9
|
+
class << self
|
10
|
+
# The global configuration based on command line options.
|
11
|
+
attr_accessor :config
|
12
|
+
end
|
13
|
+
|
14
|
+
# Run the command and log the last line from stdout unless --dry-run.
|
15
|
+
# @return [String] stdout of the comand.
|
16
|
+
def command!(*args)
|
17
|
+
logger.info("Running `#{args.shelljoin}`...") if PrlBackup.config[:verbose]
|
18
|
+
unless PrlBackup.config[:dry_run]
|
19
|
+
output = command(*args)
|
20
|
+
logger.info(output.split("\n").last)
|
21
|
+
else
|
22
|
+
output = ''
|
23
|
+
end
|
24
|
+
output
|
25
|
+
end
|
26
|
+
|
27
|
+
# Run the command until it is finished.
|
28
|
+
# @Note This will even run when option --dry-run is selected!
|
29
|
+
# @return [String] stdout of the comand.
|
30
|
+
def command(*args)
|
31
|
+
output = `#{args.shelljoin} 2>&1`
|
32
|
+
status = $?
|
33
|
+
unless status.success?
|
34
|
+
logger.error("Command `#{args.shelljoin}` failed with exit status #{status.exitstatus}:\n#{output}")
|
35
|
+
exit(1)
|
36
|
+
end
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger
|
41
|
+
@logger ||= create_logger
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def create_logger
|
47
|
+
l = Logger.new(STDOUT)
|
48
|
+
l.formatter = proc { |severity, datetime, progname, msg| "prlbackup #{severity}: [#{self}] #{msg}\n" }
|
49
|
+
l
|
50
|
+
end
|
51
|
+
end
|
data/man/prlbackup
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
.\" generated with Ronn/v0.7.3
|
2
|
+
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
3
|
+
.
|
4
|
+
.TH "PRLBACKUP" "" "May 2012" "" "PRLBACKUP MANUAL"
|
5
|
+
.
|
6
|
+
.SH "NAME"
|
7
|
+
\fBprlbackup\fR
|
8
|
+
.
|
9
|
+
.P
|
10
|
+
\&\.\e" generated with Ronn/v0\.7\.3 \.\e" http://github\.com/rtomayko/ronn/tree/0\.7\.3 \. \.TH "PRLBACKUP" "1" "May 2012" "" "PRLBACKUP MANUAL" \. \.SH "NAME" \efBprlbackup\efR \- an awesome backup tool for Parallels Server Virtual Machines \. \.SH "SYNOPSIS" \efBprlbackup\efR \efB\-h\efR|\efB\-\-help\efR \. \.br \efBprlbackup\efR [\efIOPTIONS\efR\.\.\.] \efIvm_id|vm_name\efR\.\.\. \. \.br \efBprlbackup\efR [\efIOPTIONS\efR\.\.\.] \efB\-a\efR|\efB\-\-all\efR \. \.br \efBprlbackup\efR [\efIOPTIONS\efR\.\.\.] \efB\-a\efR|\efB\-\-all\efR \efB\-e\efR|\efB\-\-exclude\efR \efIvm_id|vm_name\efR\.\.\. \. \.SH "DESCRIPTION" \efBprlbackup\efR simplifies the backup of one or multiple Virtual Machines (VM) running on Parallels Server by stoping them during backup and deleting old backups on demand\. A working installation of Parallels Server is required\. \. \.P \efBprlbackup\efR was tested under Parallels Server for Mac 4\.0 (PSfM)\. \. \.SH "OPTIONS" \. \.TP \efB\-h\efR, \efB\-\-help\efR Display a short help\. \. \.TP \efB\-a\efR, \efB\-\-all\efR Backup all virtual machines which are registered in Parallels Server\. \. \.TP \efB\-e\efR, \efB\-\-exclude\efR Backup all but the given virtual machines (only applicable in combination with option \efB\-\-all\efR!)\. \. \.TP \efB\-f\efR, \efB\-\-full\efR Create full backups (by default incremental backups are created)\. Note that the first backup for a VM is always a full backup despide of this option\. \. \.TP \efB\-v\efR, \efB\-\-verbose\efR Show commands with an impact on the VMs prior to their execution\. \. \.TP \efB\-n\efR, \efB\-\-dry\-run\efR Don\e\'t do anything with the VMs, just pretend to\. \. \.TP \efB\-k\efR \efInumber_of_full_backups\efR, \efB\-\-keep\-only\efR \efInumber_of_full_backups\efR Delete the oldest full backup(s) until only \efInumber_of_full_backups\efR exist\. This cleanup action will be executed for each VM right after creating the corresponding backup but only if the backup was successfully created\. Incremental backups are automatically deleted by Parallels Server when their full backup is deleted\. \. \.SH "EXAMPLES" Display short help: \. \.IP "" 4 \. \.nf
|
11
|
+
.
|
12
|
+
.P
|
13
|
+
$ prlbackup \-\-help \. \.fi \. \.IP "" 0 \. \.P Backup VMs by name: \. \.IP "" 4 \. \.nf
|
14
|
+
.
|
15
|
+
.P
|
16
|
+
$ prlbackup Alpha Bravo Charlie \. \.fi \. \.IP "" 0 \. \.P Backup a VM by id: \. \.IP "" 4 \. \.nf
|
17
|
+
.
|
18
|
+
.P
|
19
|
+
$ prlbackup "{97351580\-afd7\-4aff\-9960\-814196b28e37}" \. \.fi \. \.IP "" 0 \. \.P Create full backups of all VMs \. \.IP "" 4 \. \.nf
|
20
|
+
.
|
21
|
+
.P
|
22
|
+
$ prlbackup \-\-full \-\-all \. \.fi \. \.IP "" 0 \. \.P Backup all but not the given VMs: \. \.IP "" 4 \. \.nf
|
23
|
+
.
|
24
|
+
.P
|
25
|
+
$ prlbackup \-\-all \-\-exclude Delta Echo \. \.fi \. \.IP "" 0 \. \.P Show which commands would have been run when backing up all VMs: \. \.IP "" 4 \. \.nf
|
26
|
+
.
|
27
|
+
.P
|
28
|
+
$ prlbackup \-\-dry\-run \-\-verbose \-\-all \. \.fi \. \.IP "" 0 \. \.P Create the weekly full backup for all VMs and delete backups older than four weeks: \. \.IP "" 4 \. \.nf
|
29
|
+
.
|
30
|
+
.P
|
31
|
+
$ prlbackup \-\-all \-\-keep\-only 5 \. \.fi \. \.IP "" 0 \. \.SH "SEE ALSO" \. \.IP "(bu" 4 prlctl(8) \. \.IP "(bu" 4 prlbackup\e\'s homepage \efIhttps://github\.com/bjoernalbers/prlbackup\efR \. \.IP "" 0 \. \.SH "COPYRIGHT" Copyright (c) 2012 Bjoern Albers (\efIbjoernalbers@googlemail\.com\efR) \. \.P Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: \. \.P The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software\. \. \.P THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE\.
|