backup 3.1.3 → 3.2.0

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