BackupMan 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,5 +19,5 @@ rdoc
19
19
  pkg
20
20
  doc
21
21
  .yardoc
22
- *.gemspec
22
+ tmp/
23
23
  ## PROJECT::SPECIFIC
data/Rakefile CHANGED
@@ -12,7 +12,8 @@ begin
12
12
  gem.authors = ["Markus Strauss"]
13
13
  gem.rubyforge_project = "backupman"
14
14
  gem.add_development_dependency "rspec", ">= 1.2.9"
15
- gem.add_development_dependency "yard", ">= 0"
15
+ gem.add_development_dependency "yard", ">= 0.4.0"
16
+ gem.add_development_dependency "cucumber", ">= 0.4.4"
16
17
  gem.add_dependency "log4r", ">= 1.1.2"
17
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
19
  end
@@ -39,7 +40,18 @@ end
39
40
 
40
41
  task :spec => :check_dependencies
41
42
 
42
- task :default => :spec
43
+ begin
44
+ require 'cucumber/rake/task'
45
+ Cucumber::Rake::Task.new(:features)
46
+
47
+ task :features => :check_dependencies
48
+ rescue LoadError
49
+ task :features do
50
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
51
+ end
52
+ end
53
+
54
+ task :default => :features
43
55
 
44
56
  require 'rake/rdoctask'
45
57
  Rake::RDocTask.new do |rdoc|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -0,0 +1,18 @@
1
+ Feature: Development processes of BackupMan itself (rake tasks)
2
+
3
+ As a BackupMan maintainer or contributor
4
+ I want rake tasks to maintain and release the gem
5
+ So that I can spend time on the tests and code, and not excessive time on maintenance processes
6
+
7
+ Scenario: Generate RubyGem
8
+ Given this project is active project folder
9
+ And "pkg" folder is deleted
10
+ When I invoke task "rake build"
11
+ Then folder "pkg" is created
12
+ And file with name matching "pkg/*.gem" is created
13
+ # And gem spec key "rdoc_options" contains /--mainREADME.rdoc/
14
+
15
+ Scenario: Test specs
16
+ Given this project is active project folder
17
+ When I invoke task "rake spec"
18
+ Then I should see all 2 examples pass
@@ -0,0 +1,33 @@
1
+ Feature: The DSL supports various parameters
2
+
3
+ Scenario Outline: Presence of DSL parameters
4
+ Given the task is "<task>"
5
+ And the parameters are "<parameters>"
6
+ And that goes into file "tmp/configuration_file"
7
+ And this project is active project folder
8
+ When I run project executable "./bin/backup_man" with arguments "-t tmp/configuration_file"
9
+ Then the result should be <result>
10
+
11
+ Scenarios: full set, all parameters are provided and existent (valid)
12
+ | task | parameters | result |
13
+ | Tar | onlyif, backup, to, user, host, filename, options | ok |
14
+ | Mysql | onlyif, backup, to, user, host, filename, options | ok |
15
+ | Rsync | onlyif, backup, to, user, host, options | ok |
16
+
17
+ Scenarios: full set, but invalid parameters are present
18
+ | task | parameters | result |
19
+ | Tar | onlyif, backup, to, user, host, filename, options, invalid_parameter | fatal "undefined method `invalid_parameter'" |
20
+ | Mysql | onlyif, backup, to, user, host, filename, options, invalid_parameter | fatal "undefined method `invalid_parameter'" |
21
+ | Rsync | onlyif, backup, to, user, host, options, invalid_parameter | fatal "undefined method `invalid_parameter'" |
22
+
23
+ Scenarios: minimal set, all required parameters are provided
24
+ | task | parameters | result |
25
+ | Tar | backup | ok |
26
+ | Mysql | | ok |
27
+ | Rsync | backup | ok |
28
+
29
+ Scenarios: one required parameter is missing
30
+ | task | parameters | result |
31
+ | Tar | onlyif, to, user, host, filename, options | error "A required parameter is missing: backup" |
32
+ | Rsync | onlyif, to, user, host, options | error "A required parameter is missing: backup" |
33
+
@@ -0,0 +1,30 @@
1
+ Feature: logfile
2
+ In order to have a log of the BackupMan's doings
3
+ BackupMan should write to a logfile
4
+
5
+ Scenario: the logfile is writable
6
+ Given the logfile is writeable
7
+ And the user has not used option '-h'
8
+ When I start BackupMan
9
+ Then the logfile should become updated
10
+
11
+ Scenario: the logfile is not writeable
12
+ Given the logfile is not writeable
13
+ And the user has not used option '-h'
14
+ When I start BackupMan
15
+ Then error "logfile not writable" should be printed
16
+
17
+ Scenario: logfile parameter given, and writeable
18
+ When I start BackupMan with parameter "-l /path/to/logfile"
19
+ And the file "path/to/logfile" is writeable
20
+ Then the logfile should become updated
21
+
22
+ Scenario: logfile parameter given, and not writeable
23
+ When I start BackupMan with parameter "-l /path/to/logfile"
24
+ And the file "path/to/logfile" is not writeable
25
+ And the directory "path/to" is not writeable
26
+ Then error "logfile not writable" should be printed
27
+
28
+ Scenario: no logfile parameter given
29
+ When I start BackupMan
30
+ Then "/var/log/backup_man.log" should be used as the default logfile
@@ -0,0 +1,16 @@
1
+ ### Primary features
2
+ Feature: BackupMan can do tar backups from remote hosts
3
+ Feature: BackupMan can do rsync backups from remote hosts
4
+ Feature: BackupMan can do mysql dumps from remote hosts
5
+ # Feature: BackupMan can cleanup backups it has created
6
+ # Feature: BackupMan keeps a database of all backups it has created in order to make safe cleanups
7
+ Feature: BackupMan per default is completely silent when everything runs smoothly
8
+ # Feature: BackupMan can do "dry runs" in order for the user to know that the program will do
9
+
10
+ ### Secondary features
11
+ Feature: BackupMan can print its usage
12
+ Feature: BackupMan uses a log file
13
+ Feature: BackupMan can be configured to use a custom logfile
14
+ Feature: BackupMan can print debug messages on screen and in the logfile when requested
15
+ Feature: BackupMan uses convention over configuration, has reasonable defaults whenever possible
16
+ # Feature: To provide reasonable defaults, BackupMan evaluates what kind of OS the remote is
@@ -0,0 +1,163 @@
1
+ Given /^this project is active project folder/ do
2
+ @active_project_folder = File.expand_path(File.dirname(__FILE__) + "/../..")
3
+ end
4
+
5
+ Given /^env variable \$([\w_]+) set to "(.*)"/ do |env_var, value|
6
+ ENV[env_var] = value
7
+ end
8
+
9
+ Given /"(.*)" folder is deleted/ do |folder|
10
+ in_project_folder { FileUtils.rm_rf folder }
11
+ end
12
+
13
+ When /^I invoke "(.*)" generator with arguments "(.*)"$/ do |generator, arguments|
14
+ @stdout = StringIO.new
15
+ in_project_folder do
16
+ if Object.const_defined?("APP_ROOT")
17
+ APP_ROOT.replace(FileUtils.pwd)
18
+ else
19
+ APP_ROOT = FileUtils.pwd
20
+ end
21
+ run_generator(generator, arguments.split(' '), SOURCES, :stdout => @stdout)
22
+ end
23
+ File.open(File.join(@tmp_root, "generator.out"), "w") do |f|
24
+ @stdout.rewind
25
+ f << @stdout.read
26
+ end
27
+ end
28
+
29
+ When /^I run executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
30
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
31
+ in_project_folder do
32
+ system "#{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
33
+ end
34
+ end
35
+
36
+ When /^I run project executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
37
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
38
+ in_project_folder do
39
+ system "ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
40
+ end
41
+ end
42
+
43
+ When /^I run local executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
44
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
45
+ executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable))
46
+ in_project_folder do
47
+ system "ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
48
+ end
49
+ end
50
+
51
+ When /^I invoke task "rake (.*)"/ do |task|
52
+ @stdout = File.expand_path(File.join(@tmp_root, "tests.out"))
53
+ in_project_folder do
54
+ system "rake #{task} --trace > #{@stdout} 2> #{@stdout}"
55
+ end
56
+ end
57
+
58
+ Then /^folder "(.*)" (is|is not) created/ do |folder, is|
59
+ in_project_folder do
60
+ File.exists?(folder).should(is == 'is' ? be_true : be_false)
61
+ end
62
+ end
63
+
64
+ Then /^file "(.*)" (is|is not) created/ do |file, is|
65
+ in_project_folder do
66
+ File.exists?(file).should(is == 'is' ? be_true : be_false)
67
+ end
68
+ end
69
+
70
+ Then /^file with name matching "(.*)" is created/ do |pattern|
71
+ in_project_folder do
72
+ Dir[pattern].should_not be_empty
73
+ end
74
+ end
75
+
76
+ Then /^file "(.*)" contents (does|does not) match \/(.*)\// do |file, does, regex|
77
+ in_project_folder do
78
+ actual_output = File.read(file)
79
+ (does == 'does') ?
80
+ actual_output.should(match(/#{regex}/)) :
81
+ actual_output.should_not(match(/#{regex}/))
82
+ end
83
+ end
84
+
85
+ Then /gem file "(.*)" and generated file "(.*)" should be the same/ do |gem_file, project_file|
86
+ File.exists?(gem_file).should be_true
87
+ File.exists?(project_file).should be_true
88
+ gem_file_contents = File.read(File.dirname(__FILE__) + "/../../#{gem_file}")
89
+ project_file_contents = File.read(File.join(@active_project_folder, project_file))
90
+ project_file_contents.should == gem_file_contents
91
+ end
92
+
93
+ Then /^(does|does not) invoke generator "(.*)"$/ do |does_invoke, generator|
94
+ actual_output = File.read(@stdout)
95
+ does_invoke == "does" ?
96
+ actual_output.should(match(/dependency\s+#{generator}/)) :
97
+ actual_output.should_not(match(/dependency\s+#{generator}/))
98
+ end
99
+
100
+ Then /help options "(.*)" and "(.*)" are displayed/ do |opt1, opt2|
101
+ actual_output = File.read(@stdout)
102
+ actual_output.should match(/#{opt1}/)
103
+ actual_output.should match(/#{opt2}/)
104
+ end
105
+
106
+ Then /^I should see$/ do |text|
107
+ actual_output = File.read(@stdout)
108
+ actual_output.should contain(text)
109
+ end
110
+
111
+ Then /^I should not see$/ do |text|
112
+ actual_output = File.read(@stdout)
113
+ actual_output.should_not contain(text)
114
+ end
115
+
116
+ Then /^I should see exactly$/ do |text|
117
+ actual_output = File.read(@stdout)
118
+ actual_output.should == text
119
+ end
120
+
121
+ Then /^I should see all (\d+) tests pass/ do |expected_test_count|
122
+ expected = %r{^#{expected_test_count} tests, \d+ assertions, 0 failures, 0 errors}
123
+ actual_output = File.read(@stdout)
124
+ actual_output.should match(expected)
125
+ end
126
+
127
+ Then /^I should see all (\d+) examples pass/ do |expected_test_count|
128
+ expected = %r{^#{expected_test_count} examples?, 0 failures}
129
+ actual_output = File.read(@stdout)
130
+ actual_output.should match(expected)
131
+ end
132
+
133
+ Then /^yaml file "(.*)" contains (\{.*\})/ do |file, yaml|
134
+ in_project_folder do
135
+ yaml = eval yaml
136
+ YAML.load(File.read(file)).should == yaml
137
+ end
138
+ end
139
+
140
+ Then /^Rakefile can display tasks successfully/ do
141
+ @stdout = File.expand_path(File.join(@tmp_root, "rakefile.out"))
142
+ in_project_folder do
143
+ system "rake -T > #{@stdout} 2> #{@stdout}"
144
+ end
145
+ actual_output = File.read(@stdout)
146
+ actual_output.should match(/^rake\s+\w+\s+#\s.*/)
147
+ end
148
+
149
+ Then /^task "rake (.*)" is executed successfully/ do |task|
150
+ @stdout.should_not be_nil
151
+ actual_output = File.read(@stdout)
152
+ actual_output.should_not match(/^Don't know how to build task '#{task}'/)
153
+ actual_output.should_not match(/Error/i)
154
+ end
155
+
156
+ Then /^gem spec key "(.*)" contains \/(.*)\// do |key, regex|
157
+ in_project_folder do
158
+ gem_file = Dir["pkg/*.gem"].first
159
+ gem_spec = Gem::Specification.from_yaml(`gem spec #{gem_file}`)
160
+ spec_value = gem_spec.send(key.to_sym)
161
+ spec_value.to_s.should match(/#{regex}/)
162
+ end
163
+ end
@@ -0,0 +1,81 @@
1
+
2
+ def defaults
3
+ {
4
+ "onlyif" => "'true'",
5
+ "backup" => "['/']",
6
+ "to" => "'to'",
7
+ "user" => "'user'",
8
+ "host" => "'host'",
9
+ "filename" => "'filename'",
10
+ "options" => "'options'"
11
+ }
12
+ end
13
+
14
+ Given /^the task is "([^\"]*)"$/ do |task|
15
+ @subject = task
16
+ end
17
+
18
+ Given /^the parameters are "([^\"]*)"$/ do |parameters|
19
+ @parameters = parameters.split(",").map{ |p| p.strip }
20
+ end
21
+
22
+ Given /^that goes into file "([^\"]*)"$/ do |configfile|
23
+ f = File.open(configfile, "w") do |f|
24
+ f.puts "#{@subject}.new('test') do |b|"
25
+ @parameters.each { |par| f.puts " b.#{par} #{defaults[par]}"}
26
+ f.puts "end"
27
+ end
28
+ end
29
+
30
+ Then /^the result should be ok$/ do
31
+ steps %Q{
32
+ Then I should not see any output
33
+ And the exit code should be 0
34
+ }
35
+ end
36
+
37
+ Then /^the result should be error$/ do
38
+ steps %Q{
39
+ Then I should see some output
40
+ }
41
+ end
42
+
43
+ Then /^the result should be fatal$/ do
44
+ steps %Q{
45
+ Then I should see some output
46
+ And the exit code should not be 0
47
+ }
48
+ end
49
+
50
+ Then /^the result should be error "([^\"]*)"$/ do |error|
51
+ steps %Q{
52
+ Then I should see
53
+ """
54
+ #{error}
55
+ """
56
+ }
57
+ end
58
+
59
+ Then /^the result should be fatal "([^\"]*)"$/ do |error|
60
+ steps %Q{
61
+ Then I should see
62
+ """
63
+ #{error}
64
+ """
65
+ And the exit code should not be 0
66
+ }
67
+ end
68
+
69
+ Then /^the exit code (should|should not) be (.+)$/ do |should, code|
70
+ $?.method(:should) == code
71
+ end
72
+
73
+ Then /^Then I should see some output$/ do
74
+ actual_output = File.read(@stdout)
75
+ actual_output.should_not == ""
76
+ end
77
+
78
+ Then /^I should not see any output$/ do
79
+ actual_output = File.read(@stdout)
80
+ actual_output.should == ""
81
+ end
File without changes
@@ -0,0 +1,29 @@
1
+ module CommonHelpers
2
+ def in_tmp_folder(&block)
3
+ FileUtils.chdir(@tmp_root, &block)
4
+ end
5
+
6
+ def in_project_folder(&block)
7
+ project_folder = @active_project_folder || @tmp_root
8
+ FileUtils.chdir(project_folder, &block)
9
+ end
10
+
11
+ def in_home_folder(&block)
12
+ FileUtils.chdir(@home_path, &block)
13
+ end
14
+
15
+ def force_local_lib_override(project_name = @project_name)
16
+ rakefile = File.read(File.join(project_name, 'Rakefile'))
17
+ File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
18
+ f << "$:.unshift('#{@lib_path}')\n"
19
+ f << rakefile
20
+ end
21
+ end
22
+
23
+ def setup_active_project_folder project_name
24
+ @active_project_folder = File.join(@tmp_root, project_name)
25
+ @project_name = project_name
26
+ end
27
+ end
28
+
29
+ World(CommonHelpers)
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + "/../../lib/backup_man"
2
+
3
+ gem 'cucumber'
4
+ require 'cucumber'
5
+ gem 'rspec'
6
+ require 'spec'
7
+
8
+ Before do
9
+ @tmp_root = File.dirname(__FILE__) + "/../../tmp"
10
+ @home_path = File.expand_path(File.join(@tmp_root, "home"))
11
+ FileUtils.rm_rf @tmp_root
12
+ FileUtils.mkdir_p @home_path
13
+ ENV['HOME'] = @home_path
14
+ end
@@ -0,0 +1,11 @@
1
+ module Matchers
2
+ def contain(expected)
3
+ simple_matcher("contain #{expected.inspect}") do |given, matcher|
4
+ matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
5
+ matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
6
+ given.index expected
7
+ end
8
+ end
9
+ end
10
+
11
+ World(Matchers)
@@ -1,10 +1,11 @@
1
1
  require 'backup_man/backup_man'
2
2
  require 'backup_man/dsl'
3
+ require 'fileutils'
3
4
 
4
5
  module BackupMan
5
6
 
6
7
  def self.log_end_of_operation
7
- Log.instance.info( "Finished #{self}." )
8
+ Log.info( "Finished #{self}." )
8
9
  end
9
10
 
10
11
  # we wanna log when the program ends
@@ -13,30 +14,32 @@ module BackupMan
13
14
 
14
15
  # References: _Design Patterns in Ruby_ by Russ Olsen
15
16
  class Backup
16
-
17
+
17
18
  # the name of our backup set; this is used for generating a default
18
19
  # backup_directory; e.g. use the hostname of the machine to backup
19
20
  attr_reader :name
20
-
21
+
21
22
  # where shall our backup data go (directory path)
22
23
  attr_reader :backup_directory
23
-
24
+
24
25
  # user name of the remote machine (defaults to 'root')
25
26
  attr_accessor :user
26
-
27
+
27
28
  # hostname of the remote machine (defaults to job definition name)
28
29
  attr_accessor :host
29
-
30
+
30
31
  # DSL for conditional execution
31
32
  include DSL
32
-
33
+
33
34
  def_dsl :onlyif
35
+ def_dsl_required :onlyif
36
+
34
37
  def_dsl :backup, :data_sources
35
- def_dsl :to, :backup_directory
36
- def_dsl :user
37
- def_dsl :host
38
-
39
-
38
+ def_dsl :to, :backup_directory, true
39
+ def_dsl :user, :user, true
40
+ def_dsl :host, :user, true
41
+
42
+
40
43
  # this method sets all the default values but does not overwrite existing
41
44
  # settings; this method cannot be called in the initializer of Backup
42
45
  # because the default values are not available at that time;
@@ -55,50 +58,63 @@ module BackupMan
55
58
  yield(self) if block_given?
56
59
  BackupMan.instance.register_backup( self )
57
60
  end
58
-
61
+
59
62
  # calling this actually runs the backup; DO NOT override this; override
60
63
  # _run instead
61
64
  def run
62
65
  log_begin_of_run
63
66
  set_defaults
64
67
  debug_log_dsl_info
68
+ unless missing_required_parameters.empty?
69
+ Log.error( "#{self}: A required parameter is missing: #{missing_required_parameters.join ' '}")
70
+ return
71
+ end
65
72
  onlyif = eval( @onlyif )
66
- Log.instance.debug( "onlyif = { #{@onlyif} } evaluates #{onlyif}" )
73
+ Log.debug( "onlyif = { #{@onlyif} } evaluates #{onlyif}" )
67
74
  if onlyif
68
75
  unless @backup_directory
69
- Log.instance.error( "#{self}: No backup directory. Don't know where to store all this stuff.")
76
+ Log.error( "#{self}: No backup directory. Don't know where to store all this stuff.")
70
77
  else
71
78
  FileUtils.mkdir_p @backup_directory
72
79
  _run
73
80
  end
74
81
  else
75
- Log.instance.info( "#{self}: Preconditions for backup run not fulfilled.")
82
+ Log.info( "#{self}: Preconditions for backup run not fulfilled.")
76
83
  end
77
84
  log_end_of_run
78
85
  end
79
-
86
+
80
87
  # @return [String]
81
88
  def to_s
82
89
  "#{self.class} #{self.name}"
83
90
  end
84
91
 
85
92
 
86
-
93
+
87
94
  private
88
-
95
+
89
96
  # @abstract override this to implement the actual backup commands
90
97
  def _run
91
98
  throw "Hey. Cannot run just 'Backup'."
92
99
  end
93
-
100
+
101
+ # @return [Array of Strings] of missing parameters
102
+ def missing_required_parameters
103
+ missing = []
104
+ self.class.dsl_methods.each do |name, var, mandatory|
105
+ missing << name if mandatory && self.instance_variable_get("@#{var}").empty?
106
+ end
107
+ missing
108
+ end
109
+
94
110
  # not used acutally
95
111
  def log_begin_of_run
96
- Log.instance.info( "Starting #{self.class} run for #{@name}." )
112
+ Log.info( "Starting #{self.class} run for #{@name}." )
97
113
  end
98
-
114
+
99
115
  # simply logs that the program terminates
100
116
  def log_end_of_run
101
- Log.instance.info( "Finished #{self.class} run for #{@name}." )
117
+ Log.info( "Finished #{self.class} run for #{@name}." )
102
118
  end
103
119
 
104
120
  # @return [String] the ssh command string including user@host
@@ -7,7 +7,7 @@ module BackupMan
7
7
 
8
8
  include Singleton
9
9
 
10
- attr_accessor :destdir, :ssh_app, :logfile, :lockdir
10
+ attr_accessor :destdir, :ssh_app, :logfile, :lockdir, :testing
11
11
 
12
12
  def initialize
13
13
  @backups = []
@@ -22,7 +22,7 @@ module BackupMan
22
22
  begin
23
23
  backup.run
24
24
  rescue Interrupt
25
- Log.instance.warn( "Interrupt: Cancelling remaining operations.")
25
+ Log.warn( "Interrupt: Cancelling remaining operations.")
26
26
  return
27
27
  end
28
28
  end
@@ -16,6 +16,7 @@ module BackupMan
16
16
 
17
17
  options = {
18
18
  :debug => false,
19
+ :testing => false,
19
20
  :logpath => '/var/log/backup_man.log'
20
21
  }
21
22
  mandatory_options = %w( )
@@ -37,6 +38,9 @@ module BackupMan
37
38
  }
38
39
  opts.on("-h", "--help",
39
40
  "Show this help message.") { stdout.puts opts; exit }
41
+ opts.on("-t", "--test", "Testing mode.", "No actions will be performed. Just to test if the config parses fine." ) {
42
+ options[:testing] = true
43
+ }
40
44
  opts.parse!(arguments)
41
45
 
42
46
  if mandatory_options && mandatory_options.find { |option| options[option.to_sym].nil? }
@@ -45,27 +49,28 @@ module BackupMan
45
49
  end
46
50
 
47
51
  # doing our stuff here
52
+ BackupMan.instance.testing = options[:testing]
48
53
 
49
54
  # first we check if our logfile is writeable; if not, we give a warning
50
55
  logfile = Pathname.new( options[:logpath] )
51
56
  if (logfile.exist? && logfile.writable?) || logfile.parent.writable?
52
57
  BackupMan.instance.logfile = logfile.to_s
53
58
  else
54
- Log.instance.warn( "Log file is not writeable: #{logfile}.")
59
+ Log.warn( "Log file is not writeable: #{logfile}.")
55
60
  end
56
61
 
57
62
  # root-warning
58
- Log.instance.warn( "Please do not run this program as root.") if `id -u`.strip == '0'
63
+ Log.warn( "Please do not run this program as root.") if `id -u`.strip == '0'
59
64
 
60
65
  # reconfigure our Logger for debugging if necessary
61
66
  if options[:debug]
62
- Log.instance.enable_debugmode
63
- Log.instance.debug( "Debugging mode enabled.")
67
+ Log.enable_debugmode
68
+ Log.debug( "Debugging mode enabled.")
64
69
  end
65
70
 
66
71
 
67
72
  unless ARGV[0]
68
- Log.instance.fatal( "No config file given." )
73
+ Log.fatal( "No config file given." )
69
74
  stdout.puts parser
70
75
  exit 1
71
76
  else
@@ -73,24 +78,30 @@ module BackupMan
73
78
  config_filepath = Pathname.new "/etc/backup_man/#{config_filepath}" unless config_filepath.file?
74
79
 
75
80
  unless config_filepath.file?
76
- Log.instance.fatal( "Config file not found [#{config_filepath}].")
81
+ Log.fatal( "Config file not found [#{config_filepath}].")
77
82
  exit 2
78
83
  end
79
84
 
80
85
  # get and evaluate the config file; errors in the config file may be
81
86
  # difficult to debug, so be careful
82
- Log.instance.debug( "Reading file '#{config_filepath}'.")
83
- eval( File.read( config_filepath ) )
87
+ Log.debug( "Reading file '#{config_filepath}'.")
88
+
89
+ begin
90
+ eval( File.read( config_filepath ) )
91
+ rescue NoMethodError
92
+ Log.fatal( $! )
93
+ exit 3
94
+ end
84
95
 
85
96
  # configure global defaults
86
97
  BackupMan.instance.set_default( :destdir, '/var/backups/backup_man')
87
98
  BackupMan.instance.set_default( :lockdir, '/var/lock/backup_man')
88
99
  BackupMan.instance.set_default( :ssh_app, 'ssh')
89
100
 
90
- Log.instance.debug( "Global settings:")
91
- Log.instance.debug( " DESTDIR = #{BackupMan.instance.destdir}")
92
- Log.instance.debug( " LOCKDIR = #{BackupMan.instance.lockdir}")
93
- Log.instance.debug( " SSH_APP = #{BackupMan.instance.ssh_app}")
101
+ Log.debug( "Global settings:")
102
+ Log.debug( " DESTDIR = #{BackupMan.instance.destdir}")
103
+ Log.debug( " LOCKDIR = #{BackupMan.instance.lockdir}")
104
+ Log.debug( " SSH_APP = #{BackupMan.instance.ssh_app}")
94
105
 
95
106
  # run the whole thing
96
107
  BackupMan.instance.run
@@ -37,16 +37,16 @@ module BackupMan
37
37
  unless locked?
38
38
  lock
39
39
  begin
40
- Log.instance.info( "#{self}: Running.")
41
- print `#{cmd}` unless TESTING
40
+ Log.info( "#{self}: Running.")
41
+ print `#{cmd}` unless BackupMan.instance.testing
42
42
  rescue Interrupt
43
- Log.instance.warn( "#{self}: Operation interrupted.")
43
+ Log.warn( "#{self}: Operation interrupted.")
44
44
  raise
45
45
  ensure
46
46
  unlock
47
47
  end
48
48
  else
49
- Log.instance.warn( "Command already running: #{self}." )
49
+ Log.warn( "Command already running: #{self}." )
50
50
  end
51
51
  end
52
52
 
@@ -54,19 +54,19 @@ module BackupMan
54
54
  private
55
55
 
56
56
  def lock
57
- Log.instance.debug( "Locking command " + self.cmd )
57
+ Log.debug( "Locking command " + self.cmd )
58
58
  unless locked?
59
59
  f = File.new( self.lockfile, "w" )
60
60
  # FIXME: command output shall go into the correct logging lvl (eg ERROR)
61
61
  f.write(self.cmd)
62
62
  f.close
63
63
  else
64
- Log.instance.info( "Lockfile exists: " + lockfile )
64
+ Log.info( "Lockfile exists: " + lockfile )
65
65
  end
66
66
  end
67
67
 
68
68
  def unlock
69
- Log.instance.debug( "Unlocking command " + self.cmd )
69
+ Log.debug( "Unlocking command " + self.cmd )
70
70
  FileUtils.remove_file( self.lockfile )
71
71
  end
72
72
 
@@ -1,43 +1,62 @@
1
+ class Array
2
+ # @return [Array] a deep copy of self
3
+ def dclone
4
+ Marshal.load( Marshal.dump( self ) )
5
+ end
6
+ end
7
+
8
+
1
9
  module BackupMan
2
10
 
3
11
  # use "include DSL" to use this module
4
12
  module DSL
5
-
13
+
6
14
  # extend host class with class methods when we're included
7
15
  def self.included(host_class)
8
16
  host_class.extend(ClassMethods)
9
17
  end
10
18
 
11
19
  def debug_log_dsl_info
12
- Log.instance.debug( "Job settings:")
20
+ Log.debug( "Job settings:")
13
21
  self.class.dsl_methods.each do |method, var|
14
- Log.instance.debug( " #{method} = #{self.instance_variable_get("@#{var}")}" )
22
+ Log.debug( " #{method} = #{self.instance_variable_get("@#{var}")}" )
15
23
  end
16
24
  end
17
25
 
18
26
  module ClassMethods
19
-
20
- def def_dsl( name, var = name )
27
+
28
+ # @param [String] name
29
+ # @param [String] variable name, used for internal storage
30
+ # @param [Boolean] mandatory, true if this var is a required parameter
31
+ def def_dsl( name, var = name, mandatory = false )
21
32
  class_eval( %Q{
22
33
  def #{name}( #{var} )
23
34
  @#{var} = #{var}
24
35
  end
25
36
  })
26
- register_dsl( name, var )
37
+ register_dsl( name, var, mandatory )
38
+ end
39
+
40
+ # @param [Symbol] name of required parameter
41
+ def def_dsl_required( required_name )
42
+ self.dsl_methods.each_index do |i|
43
+ if self.dsl_methods[i][0] == required_name
44
+ self.dsl_methods[i][2] = true
45
+ end
46
+ end
27
47
  end
28
48
 
29
- def register_dsl( name, var )
49
+ def register_dsl( name, var, mandatory )
30
50
  @dsl_methods = [] if @dsl_methods.nil?
31
- @dsl_methods << [name, var]
51
+ @dsl_methods << [name, var, mandatory]
32
52
  end
33
53
 
34
54
  # returns an array of all dsl methods of this and all superclasses
35
55
  def dsl_methods
36
- dsl_methods = []
37
- if self.superclass != Object
38
- dsl_methods = dsl_methods | self.superclass.dsl_methods
39
- end
40
- dsl_methods | @dsl_methods
56
+ # if we dont have anything yet, we copy from superclass (copying is
57
+ # necessary beacause we might change the :required setting)
58
+ @dsl_methods ||= self.superclass.dsl_methods.dclone
59
+ @dsl_methods
41
60
  end
42
61
 
43
62
 
@@ -39,10 +39,19 @@ module BackupMan
39
39
  end
40
40
  end
41
41
 
42
+ # globally enable debugmode for all outputters
42
43
  def enable_debugmode
43
44
  Log4r::Outputter.each_outputter { |outputter| outputter.level = DEBUG }
44
45
  end
45
-
46
+
47
+ # we send all missing methods to our single logger instance
48
+ def self.method_missing(name, *args)
49
+ unless args.empty?
50
+ self.instance.send( name, args )
51
+ else
52
+ self.instance.send( name )
53
+ end
54
+ end
46
55
  end
47
56
 
48
57
  end
@@ -6,9 +6,11 @@ module BackupMan
6
6
 
7
7
  # we need a name for our backup-file (DSL)
8
8
  def_dsl :filename
9
+ def_dsl_required :filename
9
10
 
10
11
  # options for the mysqldump run (DSL)
11
12
  def_dsl :options
13
+ def_dsl_required :options
12
14
 
13
15
  def set_defaults
14
16
  super
@@ -5,7 +5,10 @@ module BackupMan
5
5
  class Rsync < Backup
6
6
 
7
7
  # options for the rsync run (DSL)
8
+ def_dsl_required :backup
9
+
8
10
  def_dsl :options
11
+ def_dsl_required :options
9
12
 
10
13
  def set_defaults
11
14
  @backup_directory = "#{BackupMan.instance.destdir}/#{@name}/rsync" unless @backup_directory
@@ -5,8 +5,13 @@ require 'backup_man/dsl'
5
5
  module BackupMan
6
6
  class Tar < Backup
7
7
 
8
+ def_dsl_required :backup
9
+
8
10
  def_dsl :filename
11
+ def_dsl_required :filename
12
+
9
13
  def_dsl :options
14
+ def_dsl_required :options
10
15
 
11
16
  def set_defaults
12
17
  super
data/lib/backup_man.rb CHANGED
@@ -14,5 +14,5 @@ def require_relative(relative_feature)
14
14
  end
15
15
 
16
16
  module BackupMan
17
- TESTING = false
17
+ ::Version = File.read('VERSION') # used by optparse
18
18
  end
@@ -16,10 +16,10 @@ module BackupMan
16
16
  argv = ["-l", "/tmp/does/not/exist.log"]
17
17
  out = StringIO.new
18
18
  $stderr = StringIO.new
19
- lambda { ::BackupMan::CLI.execute( out, ARGV) }.should_not raise_error SystemExit
19
+ lambda { ::BackupMan::CLI.execute( out, argv) }.should_not raise_error SystemExit
20
20
  $stderr.string.should include("Log file is not writeable")
21
21
  end
22
-
22
+
23
23
  end
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: BackupMan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 2
9
+ version: 0.1.2
5
10
  platform: ruby
6
11
  authors:
7
12
  - Markus Strauss
@@ -9,39 +14,65 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-11-20 00:00:00 +01:00
17
+ date: 2010-02-26 00:00:00 +01:00
13
18
  default_executable: backup_man
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: rspec
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 9
23
31
  version: 1.2.9
24
- version:
32
+ type: :development
33
+ version_requirements: *id001
25
34
  - !ruby/object:Gem::Dependency
26
35
  name: yard
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ - 4
44
+ - 0
45
+ version: 0.4.0
27
46
  type: :development
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: cucumber
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
30
52
  requirements:
31
53
  - - ">="
32
54
  - !ruby/object:Gem::Version
33
- version: "0"
34
- version:
55
+ segments:
56
+ - 0
57
+ - 4
58
+ - 4
59
+ version: 0.4.4
60
+ type: :development
61
+ version_requirements: *id003
35
62
  - !ruby/object:Gem::Dependency
36
63
  name: log4r
37
- type: :runtime
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
40
66
  requirements:
41
67
  - - ">="
42
68
  - !ruby/object:Gem::Version
69
+ segments:
70
+ - 1
71
+ - 1
72
+ - 2
43
73
  version: 1.1.2
44
- version:
74
+ type: :runtime
75
+ version_requirements: *id004
45
76
  description: A tool for system administrators to easily configure pull-over-SSH backups. Install this gem on your backup server. Configure your backups definitions in /etc/backup_man and run backup_man from cron to securely pull your data over SSH.
46
77
  email: Markus@ITstrauss.eu
47
78
  executables:
@@ -61,6 +92,16 @@ files:
61
92
  - VERSION
62
93
  - bin/backup_man
63
94
  - examples/example-host-config
95
+ - features/development.feature
96
+ - features/dsl_parameters.feature
97
+ - features/logfile.feature
98
+ - features/overview.feature
99
+ - features/step_definitions/common_steps.rb
100
+ - features/step_definitions/dsl_parameters_steps.rb
101
+ - features/step_definitions/logfile_steps.rb
102
+ - features/support/common.rb
103
+ - features/support/env.rb
104
+ - features/support/matchers.rb
64
105
  - lib/backup_man.rb
65
106
  - lib/backup_man/backup.rb
66
107
  - lib/backup_man/backup_man.rb
@@ -87,18 +128,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
128
  requirements:
88
129
  - - ">="
89
130
  - !ruby/object:Gem::Version
131
+ segments:
132
+ - 0
90
133
  version: "0"
91
- version:
92
134
  required_rubygems_version: !ruby/object:Gem::Requirement
93
135
  requirements:
94
136
  - - ">="
95
137
  - !ruby/object:Gem::Version
138
+ segments:
139
+ - 0
96
140
  version: "0"
97
- version:
98
141
  requirements: []
99
142
 
100
143
  rubyforge_project: backupman
101
- rubygems_version: 1.3.5
144
+ rubygems_version: 1.3.6
102
145
  signing_key:
103
146
  specification_version: 3
104
147
  summary: A tool for system administrators to easily configure pull-over-SSH backups.