backup 3.1.3 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,12 +14,20 @@ module Backup
14
14
  # Flag for mirroring the files/directories
15
15
  attr_accessor :mirror
16
16
 
17
- def initialize
17
+ ##
18
+ # Optional user-defined identifier to differentiate multiple syncers
19
+ # defined within a single backup model. Currently this is only used
20
+ # in the log messages.
21
+ attr_reader :syncer_id
22
+
23
+ def initialize(syncer_id = nil)
24
+ @syncer_id = syncer_id
25
+
18
26
  load_defaults!
19
27
 
20
- @path ||= 'backups'
21
- @mirror ||= false
22
- @directories = Array.new
28
+ @path ||= '~/backups'
29
+ @mirror ||= false
30
+ @directories = Array.new
23
31
  end
24
32
 
25
33
  ##
@@ -29,16 +37,23 @@ module Backup
29
37
  instance_eval(&block)
30
38
  end
31
39
 
32
- ##
33
- # Adds a path to the @directories array
34
40
  def add(path)
35
- @directories << path
41
+ directories << path
36
42
  end
37
43
 
38
44
  private
39
45
 
40
46
  def syncer_name
41
- self.class.to_s.sub('Backup::', '')
47
+ @syncer_name ||= self.class.to_s.sub('Backup::', '') +
48
+ (syncer_id ? " (#{ syncer_id })" : '')
49
+ end
50
+
51
+ def log!(action)
52
+ msg = case action
53
+ when :started then 'Started...'
54
+ when :finished then 'Finished!'
55
+ end
56
+ Logger.info "#{ syncer_name } #{ msg }"
42
57
  end
43
58
 
44
59
  end
@@ -39,9 +39,10 @@ module Backup
39
39
  # If not specified in the pre-configured defaults,
40
40
  # the Cloud specific defaults are set here before evaluating
41
41
  # any block provided in the user's configuration file.
42
- def initialize
42
+ def initialize(syncer_id = nil)
43
43
  super
44
44
 
45
+ @path = path.sub(/^~\//, '')
45
46
  @concurrency_type ||= false
46
47
  @concurrency_level ||= 2
47
48
  end
@@ -49,8 +50,8 @@ module Backup
49
50
  ##
50
51
  # Performs the Sync operation
51
52
  def perform!
53
+ log!(:started)
52
54
  Logger.info(
53
- "#{ syncer_name } started the syncing process:\n" +
54
55
  "\s\sConcurrency: #{ @concurrency_type } Level: #{ @concurrency_level }"
55
56
  )
56
57
 
@@ -60,7 +61,7 @@ module Backup
60
61
  ).sync! @mirror, @concurrency_type, @concurrency_level
61
62
  end
62
63
 
63
- Logger.info("#{ syncer_name } Syncing Complete!")
64
+ log!(:finished)
64
65
  end
65
66
 
66
67
  private
@@ -36,7 +36,7 @@ module Backup
36
36
  #
37
37
  # Once pre-configured defaults and Cloud specific defaults are set,
38
38
  # the block from the user's configuration file is evaluated.
39
- def initialize(&block)
39
+ def initialize(syncer_id = nil, &block)
40
40
  super
41
41
 
42
42
  instance_eval(&block) if block_given?
@@ -27,7 +27,7 @@ module Backup
27
27
  #
28
28
  # Once pre-configured defaults and Cloud specific defaults are set,
29
29
  # the block from the user's configuration file is evaluated.
30
- def initialize(&block)
30
+ def initialize(syncer_id = nil, &block)
31
31
  super
32
32
 
33
33
  instance_eval(&block) if block_given?
@@ -4,43 +4,31 @@ module Backup
4
4
  module Syncer
5
5
  module RSync
6
6
  class Base < Syncer::Base
7
- ##
8
- # Additional options for the rsync cli
9
- attr_accessor :additional_options
10
7
 
11
8
  ##
12
- # Instantiates a new RSync Syncer object
13
- # and sets the default configuration
14
- def initialize
15
- super
16
-
17
- @additional_options ||= Array.new
18
- end
9
+ # Additional String or Array of options for the rsync cli
10
+ attr_accessor :additional_rsync_options
19
11
 
20
12
  private
21
13
 
22
14
  ##
23
- # Returns the @directories as a space-delimited string of
24
- # single-quoted values for use in the `rsync` command line.
25
- # Each path is expanded, since these refer to local paths
26
- # for both RSync::Local and RSync::Push.
27
- # RSync::Pull does not use this method.
28
- def directories_option
29
- @directories.map do |directory|
30
- "'#{ File.expand_path(directory) }'"
31
- end.join(' ')
15
+ # Common base command for Local/Push/Pull
16
+ def rsync_command
17
+ utility(:rsync) << ' --archive' << mirror_option <<
18
+ " #{ Array(additional_rsync_options).join(' ') }".rstrip
32
19
  end
33
20
 
34
- ##
35
- # Returns Rsync syntax for enabling mirroring
36
21
  def mirror_option
37
- '--delete' if @mirror
22
+ mirror ? ' --delete' : ''
38
23
  end
39
24
 
40
25
  ##
41
- # Returns Rsync syntax for invoking "archive" mode
42
- def archive_option
43
- '--archive'
26
+ # Each path is expanded, since these refer to local paths and are
27
+ # being shell-quoted. This will also remove any trailing `/` from
28
+ # each path, as we don't want rsync's "trailing / on source directories"
29
+ # behavior. This method is used by RSync::Local and RSync::Push.
30
+ def paths_to_push
31
+ directories.map {|dir| "'#{ File.expand_path(dir) }'" }.join(' ')
44
32
  end
45
33
 
46
34
  end
@@ -5,50 +5,36 @@ module Backup
5
5
  module RSync
6
6
  class Local < Base
7
7
 
8
- ##
9
- # Instantiates a new RSync::Local Syncer.
10
- #
11
- # Pre-configured defaults specified in
12
- # Configuration::Syncer::RSync::Local
13
- # are set via a super() call to RSync::Base,
14
- # which in turn will invoke Syncer::Base.
15
- #
16
- # Once pre-configured defaults and RSync specific defaults are set,
17
- # the block from the user's configuration file is evaluated.
18
- def initialize(&block)
8
+ def initialize(syncer_id = nil, &block)
19
9
  super
20
-
21
10
  instance_eval(&block) if block_given?
22
11
  end
23
12
 
24
- ##
25
- # Performs the RSync::Local operation
26
- # debug options: -vhP
27
13
  def perform!
28
- Logger.info(
29
- "#{ syncer_name } started syncing the following directories:\n\s\s" +
30
- @directories.join("\n\s\s")
31
- )
32
- run("#{ utility(:rsync) } #{ options } " +
33
- "#{ directories_option } '#{ dest_path }'")
14
+ log!(:started)
15
+
16
+ create_dest_path!
17
+ run("#{ rsync_command } #{ paths_to_push } '#{ dest_path }'")
18
+
19
+ log!(:finished)
34
20
  end
35
21
 
36
22
  private
37
23
 
38
- ##
39
- # Return expanded @path
24
+ # Expand path, since this is local and shell-quoted.
40
25
  def dest_path
41
- @dest_path ||= File.expand_path(@path)
26
+ @dest_path ||= File.expand_path(path)
42
27
  end
43
28
 
44
- ##
45
- # Returns all the specified Rsync::Local options,
46
- # concatenated, ready for the CLI
47
- def options
48
- ([archive_option, mirror_option] +
49
- additional_options).compact.join("\s")
29
+ def create_dest_path!
30
+ FileUtils.mkdir_p dest_path
50
31
  end
51
32
 
33
+ attr_deprecate :additional_options, :version => '3.2.0',
34
+ :message => 'Use #additional_rsync_options instead.',
35
+ :action => lambda {|klass, val|
36
+ klass.additional_rsync_options = val
37
+ }
52
38
  end
53
39
  end
54
40
  end
@@ -5,19 +5,15 @@ module Backup
5
5
  module RSync
6
6
  class Pull < Push
7
7
 
8
- ##
9
- # Performs the RSync::Pull operation
10
- # debug options: -vhP
11
8
  def perform!
9
+ log!(:started)
12
10
  write_password_file!
13
11
 
14
- @directories.each do |directory|
15
- Logger.info("#{ syncer_name } started syncing '#{ directory }'.")
16
- run("#{ utility(:rsync) } #{ options } " +
17
- "'#{ username }@#{ ip }:#{ directory.sub(/^\~\//, '') }' " +
18
- "'#{ dest_path }'")
19
- end
12
+ create_dest_path!
13
+ run("#{ rsync_command } #{ host_options }#{ paths_to_pull } " +
14
+ "'#{ dest_path }'")
20
15
 
16
+ log!(:finished)
21
17
  ensure
22
18
  remove_password_file!
23
19
  end
@@ -25,11 +21,51 @@ module Backup
25
21
  private
26
22
 
27
23
  ##
28
- # Return expanded @path, since this path is local
24
+ # Returns the syntax for pulling multiple paths from the remote host.
25
+ # e.g.
26
+ # rsync -a -e "ssh -p 22" host:'path1' :'path2' '/dest'
27
+ # rsync -a rsync_user@host::'modname/path1' ::'modname/path2' '/dest'
28
+ #
29
+ # Remove any preceeding '~/', since these paths are on the remote.
30
+ # Also remove any trailing `/`, since we don't want rsync's
31
+ # "trailing / on source directories" behavior.
32
+ def paths_to_pull
33
+ sep = mode == :ssh ? ':' : '::'
34
+ directories.map {|dir|
35
+ "#{ sep }'#{ dir.sub(/^~\//, '').sub(/\/$/, '') }'"
36
+ }.join(' ').sub(/^#{ sep }/, '')
37
+ end
38
+
39
+ # Expand path, since this is local and shell-quoted.
29
40
  def dest_path
30
- @dest_path ||= File.expand_path(@path)
41
+ @dest_path ||= File.expand_path(path)
31
42
  end
32
43
 
44
+ def create_dest_path!
45
+ FileUtils.mkdir_p dest_path
46
+ end
47
+
48
+ attr_deprecate :additional_options, :version => '3.2.0',
49
+ :message => 'Use #additional_rsync_options instead.',
50
+ :action => lambda {|klass, val|
51
+ klass.additional_rsync_options = val
52
+ }
53
+
54
+ attr_deprecate :username, :version => '3.2.0',
55
+ :message => 'Use #ssh_user instead.',
56
+ :action => lambda {|klass, val|
57
+ klass.ssh_user = val
58
+ }
59
+ attr_deprecate :password, :version => '3.2.0',
60
+ :message => 'Use #rsync_password instead.',
61
+ :action => lambda {|klass, val|
62
+ klass.rsync_password = val
63
+ }
64
+ attr_deprecate :ip, :version => '3.2.0',
65
+ :message => 'Use #host instead.',
66
+ :action => lambda {|klass, val|
67
+ klass.host = val
68
+ }
33
69
  end
34
70
  end
35
71
  end
@@ -6,54 +6,119 @@ module Backup
6
6
  class Push < Base
7
7
 
8
8
  ##
9
- # Server credentials
10
- attr_accessor :username, :password
9
+ # Mode of operation
10
+ #
11
+ # [:ssh (default)]
12
+ # Connects to the remote via SSH.
13
+ # Does not use an rsync daemon on the remote.
14
+ #
15
+ # [:ssh_daemon]
16
+ # Connects to the remote via SSH.
17
+ # Spawns a single-use daemon on the remote, which allows certain
18
+ # daemon features (like modules) to be used.
19
+ #
20
+ # [:rsync_daemon]
21
+ # Connects directly to an rsync daemon via TCP.
22
+ # Data transferred is not encrypted.
23
+ #
24
+ attr_accessor :mode
11
25
 
12
26
  ##
13
- # Server IP Address and SSH port
14
- attr_accessor :ip
27
+ # Server Address
28
+ attr_accessor :host
15
29
 
16
30
  ##
17
- # The SSH port to connect to
31
+ # SSH or RSync port
32
+ #
33
+ # For `:ssh` or `:ssh_daemon` mode, this specifies the SSH port to use
34
+ # and defaults to 22.
35
+ #
36
+ # For `:rsync_daemon` mode, this specifies the TCP port to use
37
+ # and defaults to 873.
18
38
  attr_accessor :port
19
39
 
20
40
  ##
21
- # Flag for compressing (only compresses for the transfer)
22
- attr_accessor :compress
41
+ # SSH User
42
+ #
43
+ # If the user running the backup is not the same user that needs to
44
+ # authenticate with the remote server, specify the user here.
45
+ #
46
+ # The user must have SSH keys setup for passphrase-less access to the
47
+ # remote. If the SSH User does not have passphrase-less keys, or no
48
+ # default keys in their `~/.ssh` directory, you will need to use the
49
+ # `-i` option in `:additional_ssh_options` to specify the
50
+ # passphrase-less key to use.
51
+ #
52
+ # Used only for `:ssh` and `:ssh_daemon` modes.
53
+ attr_accessor :ssh_user
23
54
 
24
55
  ##
25
- # Instantiates a new RSync::Push or RSync::Pull Syncer.
56
+ # Additional SSH Options
26
57
  #
27
- # Pre-configured defaults specified in
28
- # Configuration::Syncer::RSync::Push or
29
- # Configuration::Syncer::RSync::Pull
30
- # are set via a super() call to RSync::Base,
31
- # which in turn will invoke Syncer::Base.
58
+ # Used to supply a String or Array of options to be passed to the SSH
59
+ # command in `:ssh` and `:ssh_daemon` modes.
32
60
  #
33
- # Once pre-configured defaults and RSync specific defaults are set,
34
- # the block from the user's configuration file is evaluated.
35
- def initialize(&block)
36
- super
61
+ # For example, if you need to supply a specific SSH key for the `ssh_user`,
62
+ # you would set this to: "-i '/path/to/id_rsa'". Which would produce:
63
+ #
64
+ # rsync -e "ssh -p 22 -i '/path/to/id_rsa'"
65
+ #
66
+ # Arguments may be single-quoted, but should not contain any double-quotes.
67
+ #
68
+ # Used only for `:ssh` and `:ssh_daemon` modes.
69
+ attr_accessor :additional_ssh_options
37
70
 
38
- @port ||= 22
39
- @compress ||= false
71
+ ##
72
+ # RSync User
73
+ #
74
+ # If the user running the backup is not the same user that needs to
75
+ # authenticate with the rsync daemon, specify the user here.
76
+ #
77
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
78
+ attr_accessor :rsync_user
40
79
 
80
+ ##
81
+ # RSync Password
82
+ #
83
+ # If specified, Backup will write the password to a temporary file and
84
+ # use it with rsync's `--password-file` option for daemon authentication.
85
+ #
86
+ # Note that setting this will override `rsync_password_file`.
87
+ #
88
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
89
+ attr_accessor :rsync_password
90
+
91
+ ##
92
+ # RSync Password File
93
+ #
94
+ # If specified, this path will be passed to rsync's `--password-file`
95
+ # option for daemon authentication.
96
+ #
97
+ # Used only for `:ssh_daemon` and `:rsync_daemon` modes.
98
+ attr_accessor :rsync_password_file
99
+
100
+ ##
101
+ # Flag for compressing (only compresses for the transfer)
102
+ attr_accessor :compress
103
+
104
+ def initialize(syncer_id = nil, &block)
105
+ super
41
106
  instance_eval(&block) if block_given?
107
+
108
+ @mode ||= :ssh
109
+ @port ||= mode == :rsync_daemon ? 873 : 22
110
+ @compress ||= false
42
111
  end
43
112
 
44
- ##
45
- # Performs the RSync:Push operation
46
- # debug options: -vhP
47
113
  def perform!
114
+ log!(:started)
48
115
  write_password_file!
49
116
 
50
- Logger.info(
51
- "#{ syncer_name } started syncing the following directories:\n\s\s" +
52
- @directories.join("\n\s\s")
53
- )
54
- run("#{ utility(:rsync) } #{ options } #{ directories_option } " +
55
- "'#{ username }@#{ ip }:#{ dest_path }'")
117
+ create_dest_path!
118
+ run("#{ rsync_command } #{ paths_to_push } " +
119
+ "#{ host_options }'#{ dest_path }'")
56
120
 
121
+ log!(:finished)
57
122
  ensure
58
123
  remove_password_file!
59
124
  end
@@ -61,55 +126,101 @@ module Backup
61
126
  private
62
127
 
63
128
  ##
64
- # Return @path with any preceeding "~/" removed
129
+ # Remove any preceeding '~/', since this is on the remote,
130
+ # and remove any trailing `/`.
65
131
  def dest_path
66
- @dest_path ||= @path.sub(/^\~\//, '')
132
+ @dest_path ||= path.sub(/^~\//, '').sub(/\/$/, '')
67
133
  end
68
134
 
69
135
  ##
70
- # Returns all the specified Rsync::[Push/Pull] options,
71
- # concatenated, ready for the CLI
72
- def options
73
- ([archive_option, mirror_option, compress_option, port_option,
74
- password_option] + additional_options).compact.join("\s")
136
+ # Runs a 'mkdir -p' command on the remote to ensure the dest_path exists.
137
+ # This used because rsync will attempt to create the path, but will only
138
+ # call 'mkdir' without the '-p' option. This is only applicable in :ssh
139
+ # mode, and only used if the path would require this.
140
+ def create_dest_path!
141
+ return unless mode == :ssh && dest_path.index('/').to_i > 0
142
+
143
+ run "#{ utility(:ssh) } #{ ssh_transport_args } #{ host } " +
144
+ %Q["mkdir -p '#{ dest_path }'"]
75
145
  end
76
146
 
77
147
  ##
78
- # Returns Rsync syntax for compressing the file transfers
79
- def compress_option
80
- '--compress' if @compress
148
+ # For Push, this will prepend the #dest_path.
149
+ # For Pull, this will prepend the first path in #paths_to_pull.
150
+ def host_options
151
+ if mode == :ssh
152
+ "#{ host }:"
153
+ else
154
+ user = "#{ rsync_user }@" if rsync_user
155
+ "#{ user }#{ host }::"
156
+ end
81
157
  end
82
158
 
83
159
  ##
84
- # Returns Rsync syntax for defining a port to connect to
85
- def port_option
86
- "-e 'ssh -p #{@port}'"
160
+ # Common base command, plus options for Push/Pull
161
+ def rsync_command
162
+ super << compress_option << password_option << transport_options
163
+ end
164
+
165
+ def compress_option
166
+ compress ? ' --compress' : ''
87
167
  end
88
168
 
89
- ##
90
- # Returns Rsync syntax for setting a password (via a file)
91
169
  def password_option
92
- "--password-file='#{@password_file.path}'" if @password_file
170
+ return '' if mode == :ssh
171
+
172
+ path = @password_file ? @password_file.path : rsync_password_file
173
+ path ? " --password-file='#{ File.expand_path(path) }'" : ''
93
174
  end
94
175
 
95
- ##
96
- # Writes the provided password to a temporary file so that
97
- # the rsync utility can read the password from this file
98
- def write_password_file!
99
- unless @password.nil?
100
- @password_file = Tempfile.new('backup-rsync-password')
101
- @password_file.write(@password)
102
- @password_file.close
176
+ def transport_options
177
+ if mode == :rsync_daemon
178
+ " --port #{ port }"
179
+ else
180
+ %Q[ -e "#{ utility(:ssh) } #{ ssh_transport_args }"]
103
181
  end
104
182
  end
105
183
 
106
- ##
107
- # Removes the previously created @password_file
108
- # (temporary file containing the password)
184
+ def ssh_transport_args
185
+ args = "-p #{ port } "
186
+ args << "-l #{ ssh_user } " if ssh_user
187
+ args << Array(additional_ssh_options).join(' ')
188
+ args.rstrip
189
+ end
190
+
191
+ def write_password_file!
192
+ return unless rsync_password && mode != :ssh
193
+
194
+ @password_file = Tempfile.new('backup-rsync-password')
195
+ @password_file.write(rsync_password)
196
+ @password_file.close
197
+ end
198
+
109
199
  def remove_password_file!
110
200
  @password_file.delete if @password_file
111
- @password_file = nil
112
201
  end
202
+
203
+ attr_deprecate :additional_options, :version => '3.2.0',
204
+ :message => 'Use #additional_rsync_options instead.',
205
+ :action => lambda {|klass, val|
206
+ klass.additional_rsync_options = val
207
+ }
208
+
209
+ attr_deprecate :username, :version => '3.2.0',
210
+ :message => 'Use #ssh_user instead.',
211
+ :action => lambda {|klass, val|
212
+ klass.ssh_user = val
213
+ }
214
+ attr_deprecate :password, :version => '3.2.0',
215
+ :message => 'Use #rsync_password instead.',
216
+ :action => lambda {|klass, val|
217
+ klass.rsync_password = val
218
+ }
219
+ attr_deprecate :ip, :version => '3.2.0',
220
+ :message => 'Use #host instead.',
221
+ :action => lambda {|klass, val|
222
+ klass.host = val
223
+ }
113
224
  end
114
225
  end
115
226
  end