BackupMan 0.1.1 → 0.1.2

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/.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.