cloud_encrypted_sync 0.1.0 → 0.1.1
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/Gemfile.lock +1 -1
- data/README.md +0 -4
- data/lib/cloud_encrypted_sync/adapter_liaison.rb +57 -0
- data/lib/cloud_encrypted_sync/adapter_template.rb +26 -6
- data/lib/cloud_encrypted_sync/configuration.rb +3 -7
- data/lib/cloud_encrypted_sync/cryptographer.rb +0 -3
- data/lib/cloud_encrypted_sync/dummy_adapter.rb +1 -2
- data/lib/cloud_encrypted_sync/errors.rb +8 -0
- data/lib/cloud_encrypted_sync/index.rb +86 -0
- data/lib/cloud_encrypted_sync/master.rb +21 -133
- data/lib/cloud_encrypted_sync/version.rb +1 -1
- data/lib/cloud_encrypted_sync.rb +12 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/adapter_liaison_test.rb +32 -0
- data/test/unit/configuration_test.rb +2 -2
- data/test/unit/cryptographer_test.rb +0 -1
- data/test/unit/master_test.rb +25 -42
- metadata +7 -2
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -23,10 +23,6 @@ preferred cloud.
|
|
23
23
|
CES runs as a command line tool and takes options as CLI arguments and/or from a config file.
|
24
24
|
Arguments passed at the command line take precedence over those in the config file.
|
25
25
|
|
26
|
-
### Creating a valid encryption key and initialization vector.
|
27
|
-
|
28
|
-
TODO
|
29
|
-
|
30
26
|
### Example
|
31
27
|
|
32
28
|
ces --adapter=s3 --bucket=my-backup-bucket \
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module CloudEncryptedSync
|
2
|
+
class AdapterLiaison
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
find_and_require_adapters
|
7
|
+
end
|
8
|
+
|
9
|
+
def push(data,key)
|
10
|
+
adapter.write(Cryptographer.encrypt_data(data),key)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pull(key)
|
14
|
+
Cryptographer.decrypt_data(adapter.read(key))
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key)
|
18
|
+
adapter.delete(key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def key_exists?(key)
|
22
|
+
adapter.key_exists?(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def adapters
|
26
|
+
Adapters::Template.children
|
27
|
+
end
|
28
|
+
|
29
|
+
#######
|
30
|
+
private
|
31
|
+
#######
|
32
|
+
|
33
|
+
def find_and_require_adapters
|
34
|
+
latest_versions_of_installed_adapters.each_pair do |adapter_name,adapter_version|
|
35
|
+
require File.expand_path("../../../../cloud_encrypted_sync_#{adapter_name}_adapter-#{adapter_version}", __FILE__)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def latest_versions_of_installed_adapters
|
40
|
+
glob_path = '../../../../cloud_encrypted_sync_*_adapter-*/lib/*.rb'
|
41
|
+
Dir.glob(File.expand_path(glob_path,__FILE__)).inject({}) do |hash,adapter_path|
|
42
|
+
if adapter_path.match(/cloud_encrypted_sync_(.+)_adapter-(.+)/)
|
43
|
+
adapter_name = $1
|
44
|
+
adapter_version = $2
|
45
|
+
if hash[adapter_name].to_s < adapter_version
|
46
|
+
hash[adapter_name] = adapter_version
|
47
|
+
end
|
48
|
+
end
|
49
|
+
hash
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def adapter
|
54
|
+
adapters[Configuration.settings[:adapter_name].to_sym]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -5,29 +5,49 @@ module CloudEncryptedSync
|
|
5
5
|
class << self
|
6
6
|
|
7
7
|
def inherited(subclass)
|
8
|
-
|
8
|
+
register_subclass_with_parent(subclass)
|
9
|
+
end
|
10
|
+
|
11
|
+
def children
|
12
|
+
@children ||= {}
|
9
13
|
end
|
10
14
|
|
11
15
|
def parse_command_line_options(opts,command_line_options)
|
12
|
-
raise '
|
16
|
+
raise Errors::TemplateMethodCalled.new('parse_command_line_options')
|
13
17
|
end
|
14
18
|
|
15
19
|
def write(data, key)
|
16
|
-
raise '
|
20
|
+
raise Errors::TemplateMethodCalled.new('write')
|
17
21
|
end
|
18
22
|
|
19
23
|
def read(key)
|
20
|
-
raise '
|
24
|
+
raise Errors::TemplateMethodCalled.new('read')
|
21
25
|
end
|
22
26
|
|
23
27
|
def delete(key)
|
24
|
-
raise '
|
28
|
+
raise Errors::TemplateMethodCalled.new('delete')
|
25
29
|
end
|
26
30
|
|
27
31
|
def key_exists?(key)
|
28
|
-
raise '
|
32
|
+
raise Errors::TemplateMethodCalled.new('key_exists?')
|
29
33
|
end
|
30
34
|
|
35
|
+
#######
|
36
|
+
private
|
37
|
+
#######
|
38
|
+
|
39
|
+
def register_subclass_with_parent(subclass)
|
40
|
+
name = formated_name_of(subclass)
|
41
|
+
if children[name]
|
42
|
+
raise Errors::RegistrationError.new("#{name} already registered")
|
43
|
+
else
|
44
|
+
children[name] = subclass
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def formated_name_of(subclass)
|
49
|
+
subclass.name.match(/([^:]+)$/)[0].underscore.to_sym
|
50
|
+
end
|
31
51
|
end
|
32
52
|
end
|
33
53
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
1
|
module CloudEncryptedSync
|
4
2
|
class Configuration
|
5
3
|
|
@@ -32,10 +30,10 @@ module CloudEncryptedSync
|
|
32
30
|
|
33
31
|
if loaded_settings[:sync_path].nil?
|
34
32
|
message = "You must supply a path to a folder to sync.\n\n#{option_parser.help}"
|
35
|
-
raise IncompleteConfigurationError.new(message)
|
33
|
+
raise Errors::IncompleteConfigurationError.new(message)
|
36
34
|
elsif loaded_settings[:encryption_key].nil? or loaded_settings[:encryption_key].empty?
|
37
35
|
message = "You must supply an encryption key.\n\n#{option_parser.help}"
|
38
|
-
raise IncompleteConfigurationError.new(message)
|
36
|
+
raise Errors::IncompleteConfigurationError.new(message)
|
39
37
|
end
|
40
38
|
|
41
39
|
return loaded_settings
|
@@ -64,7 +62,7 @@ module CloudEncryptedSync
|
|
64
62
|
end
|
65
63
|
opts.on('--adapter ADAPTERNAME', 'Name of cloud adapter to use.') do |adapter_name|
|
66
64
|
clo[:adapter_name] = adapter_name
|
67
|
-
clo =
|
65
|
+
clo = AdapterLiaison.instance.adapters[adapter_name.to_sym].parse_command_line_options(opts,clo)
|
68
66
|
end
|
69
67
|
opts.on('--encryption-key KEY') do |key|
|
70
68
|
clo[:encryption_key] = key
|
@@ -77,6 +75,4 @@ module CloudEncryptedSync
|
|
77
75
|
|
78
76
|
end
|
79
77
|
end
|
80
|
-
|
81
|
-
class IncompleteConfigurationError < RuntimeError; end
|
82
78
|
end
|
@@ -16,7 +16,7 @@ module CloudEncryptedSync
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def read(key)
|
19
|
-
raise "key doesn't exist" unless key_exists?(key)
|
19
|
+
raise Errors::NoSuchKey.new("key doesn't exist: #{key}") unless key_exists?(key)
|
20
20
|
stored_data[bucket_name][key]
|
21
21
|
end
|
22
22
|
|
@@ -37,7 +37,6 @@ module CloudEncryptedSync
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def bucket_name
|
40
|
-
raise RuntimeError, Configuration.settings.inspect
|
41
40
|
Configuration.settings[:bucket_name].to_sym
|
42
41
|
end
|
43
42
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module CloudEncryptedSync
|
2
|
+
class Index
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def local
|
7
|
+
@local ||= compile_local_hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def remote
|
11
|
+
@remote ||= begin
|
12
|
+
YAML.parse(liaison.pull(index_key)).to_ruby
|
13
|
+
rescue Errors::NoSuchKey
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def write
|
19
|
+
local_hash = compile_local_hash #recompile
|
20
|
+
liaison.push(local_hash.to_yaml,index_key) #push to remote
|
21
|
+
File.open(snapshot_path, 'w') { |file| YAML.dump(local_hash, file) } #save to local
|
22
|
+
end
|
23
|
+
|
24
|
+
def full_file_path(relative_path)
|
25
|
+
normalized_sync_path+'/'+relative_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def snapshot_path
|
29
|
+
"#{Configuration.data_folder_path}/#{snapshot_filename}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def file_key(full_path)
|
33
|
+
Cryptographer.hash_data(relative_file_path(full_path) + File.open(full_path).read).to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
#######
|
37
|
+
private
|
38
|
+
#######
|
39
|
+
|
40
|
+
def liaison
|
41
|
+
AdapterLiaison.instance
|
42
|
+
end
|
43
|
+
|
44
|
+
def compile_local_hash
|
45
|
+
hash = {}
|
46
|
+
progress_meter = ProgressMeter.new(Dir["#{normalized_sync_path}/**/*"].length,:label => 'Compiling Local Index: ')
|
47
|
+
completed_files = 0
|
48
|
+
Find.find(normalized_sync_path) do |path|
|
49
|
+
print progress_meter.update(completed_files)
|
50
|
+
unless FileTest.directory?(path)
|
51
|
+
hash[file_key(path)] = relative_file_path(path)
|
52
|
+
end
|
53
|
+
completed_files += 1
|
54
|
+
end
|
55
|
+
puts #newline for progress meter
|
56
|
+
return hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def index_key
|
60
|
+
@index_key ||= Cryptographer.hash_data(Configuration.settings[:encryption_key])
|
61
|
+
end
|
62
|
+
|
63
|
+
def snapshot_filename
|
64
|
+
"#{normalized_sync_path.gsub(/[^A-Za-z0-9]/,'_')}.index.yml"
|
65
|
+
end
|
66
|
+
|
67
|
+
def relative_file_path(full_path)
|
68
|
+
full_path.gsub(normalized_sync_path,'')
|
69
|
+
end
|
70
|
+
|
71
|
+
def normalized_sync_path
|
72
|
+
@normalized_sync_path ||= normalize_sync_path
|
73
|
+
end
|
74
|
+
|
75
|
+
def normalize_sync_path
|
76
|
+
path = Configuration.settings[:sync_path]
|
77
|
+
if path.match(/\/$/)
|
78
|
+
return path
|
79
|
+
else
|
80
|
+
return path + '/'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -1,23 +1,10 @@
|
|
1
|
-
require 'find'
|
2
|
-
require 'active_support/core_ext/string'
|
3
|
-
|
4
1
|
module CloudEncryptedSync
|
5
2
|
class Master
|
6
3
|
|
7
4
|
class << self
|
8
5
|
attr_accessor :finalize_required
|
9
|
-
attr_reader :command_line_options, :adapters
|
10
|
-
attr_writer :sync_path
|
11
|
-
|
12
|
-
def register(adapter)
|
13
|
-
@adapters ||= {}
|
14
|
-
name = adapter.name.match(/([^:]+)$/)[0].underscore.to_sym
|
15
|
-
raise RegistrationError, "#{name} already registered" if @adapters[name]
|
16
|
-
@adapters[name] = adapter
|
17
|
-
end
|
18
6
|
|
19
7
|
def activate!
|
20
|
-
find_and_require_adapters
|
21
8
|
sync
|
22
9
|
end
|
23
10
|
|
@@ -28,7 +15,7 @@ module CloudEncryptedSync
|
|
28
15
|
CloudEncryptedSync::Master.pull_files!
|
29
16
|
CloudEncryptedSync::Master.push_files!
|
30
17
|
CloudEncryptedSync::Master.finalize!
|
31
|
-
rescue IncompleteConfigurationError => exception
|
18
|
+
rescue Errors::IncompleteConfigurationError => exception
|
32
19
|
puts exception.message
|
33
20
|
end
|
34
21
|
end
|
@@ -37,12 +24,13 @@ module CloudEncryptedSync
|
|
37
24
|
progress_meter = ProgressMeter.new(files_to_pull.keys.size,:label => 'Pushing Files: ')
|
38
25
|
pushed_files_counter = 0
|
39
26
|
files_to_push.each_pair do |key,relative_path|
|
40
|
-
|
27
|
+
puts #newline for progress meter
|
28
|
+
if liaison.key_exists?(key)
|
41
29
|
#already exists. probably left over from an earlier aborted push
|
42
30
|
puts "Not Pushing (already exists): #{relative_path}"
|
43
31
|
else
|
44
32
|
puts "Pushing: #{relative_path}"
|
45
|
-
|
33
|
+
liaison.push(File.read(Index.full_file_path(relative_path)),key)
|
46
34
|
self.finalize_required = true
|
47
35
|
end
|
48
36
|
pushed_files_counter += 1
|
@@ -54,17 +42,18 @@ module CloudEncryptedSync
|
|
54
42
|
progress_meter = ProgressMeter.new(files_to_pull.keys.size,:label => 'Pulling Files: ')
|
55
43
|
pulled_files_counter = 0
|
56
44
|
files_to_pull.each_pair do |key,relative_path|
|
57
|
-
full_path = full_file_path(relative_path)
|
58
|
-
|
45
|
+
full_path = Index.full_file_path(relative_path)
|
46
|
+
puts #newline for progress meter
|
47
|
+
if File.exist?(full_path) and (Index.file_key(full_path) == key)
|
59
48
|
#already exists. probably left over from an earlier aborted pull
|
60
49
|
puts "Not Pulling (already exists): #{path}"
|
61
50
|
else
|
62
51
|
Dir.mkdir(File.dirname(full_path)) unless File.exist?(File.dirname(full_path))
|
63
52
|
puts "Pulling: #{relative_path}"
|
64
53
|
begin
|
65
|
-
File.open(full_path,'w') { |file| file.write(
|
54
|
+
File.open(full_path,'w') { |file| file.write(liaison.pull(key)) }
|
66
55
|
self.finalize_required = true
|
67
|
-
rescue
|
56
|
+
rescue Errors::NoSuchKey
|
68
57
|
puts "Failed to pull #{relative_path}"
|
69
58
|
end
|
70
59
|
end
|
@@ -76,15 +65,15 @@ module CloudEncryptedSync
|
|
76
65
|
def delete_remote_files!
|
77
66
|
remote_files_to_delete.each_pair do |key,path|
|
78
67
|
puts "Deleting Remote: #{path}"
|
79
|
-
|
68
|
+
liaison.delete(key)
|
80
69
|
self.finalize_required = true
|
81
70
|
end
|
82
71
|
end
|
83
72
|
|
84
73
|
def delete_local_files!
|
85
74
|
local_files_to_delete.each_pair do |key,relative_path|
|
86
|
-
full_path = full_file_path(relative_path)
|
87
|
-
if !File.exist?(full_path) or (file_key(full_path) == key)
|
75
|
+
full_path = Index.full_file_path(relative_path)
|
76
|
+
if !File.exist?(full_path) or (Index.file_key(full_path) == key)
|
88
77
|
puts "Not Deleting Local: #{relative_path}"
|
89
78
|
else
|
90
79
|
puts "Deleting Local: #{relative_path}"
|
@@ -95,119 +84,39 @@ module CloudEncryptedSync
|
|
95
84
|
end
|
96
85
|
|
97
86
|
def finalize!
|
98
|
-
if finalize_required
|
99
|
-
store_directory_hash_file
|
100
|
-
File.open(snapshot_file_path, 'w') { |file| YAML.dump(directory_hash, file) }
|
101
|
-
end
|
87
|
+
Index.write if finalize_required
|
102
88
|
end
|
103
89
|
|
104
90
|
#######
|
105
91
|
private
|
106
92
|
#######
|
107
93
|
|
108
|
-
def
|
109
|
-
|
110
|
-
require File.expand_path("../../../../cloud_encrypted_sync_#{adapter_name}_adapter-#{adapter_version}", __FILE__)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def latest_versions_of_installed_adapters
|
115
|
-
glob_path = '../../../../cloud_encrypted_sync_*_adapter-*/lib/*.rb'
|
116
|
-
Dir.glob(File.expand_path(glob_path,__FILE__)).inject({}) do |hash,adapter_path|
|
117
|
-
if adapter_path.match(/cloud_encrypted_sync_(.+)_adapter-(.+)/)
|
118
|
-
adapter_name = $1
|
119
|
-
adapter_version = $2
|
120
|
-
if hash[adapter_name].to_s < adapter_version
|
121
|
-
hash[adapter_name] = adapter_version
|
122
|
-
end
|
123
|
-
end
|
124
|
-
hash
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def adapter
|
129
|
-
@adapters[Configuration.settings[:adapter_name].to_sym]
|
130
|
-
end
|
131
|
-
|
132
|
-
def encrypt_to_adapter(data,key)
|
133
|
-
adapter.write(Cryptographer.encrypt_data(data),key)
|
134
|
-
end
|
135
|
-
|
136
|
-
def decrypt_from_adapter(key)
|
137
|
-
Cryptographer.decrypt_data(adapter.read(key))
|
138
|
-
end
|
139
|
-
|
140
|
-
def directory_hash
|
141
|
-
return @directory_hash if @directory_hash
|
142
|
-
@directory_hash = {}
|
143
|
-
progress_meter = ProgressMeter.new(Dir["#{normalized_sync_path}/**/*"].length,:label => 'Compiling Directory Analysis: ')
|
144
|
-
completed_files = 0
|
145
|
-
Find.find(normalized_sync_path) do |path|
|
146
|
-
print progress_meter.update(completed_files)
|
147
|
-
if FileTest.directory?(path)
|
148
|
-
completed_files += 1
|
149
|
-
next
|
150
|
-
else
|
151
|
-
@directory_hash[file_key(path)] = relative_file_path(path)
|
152
|
-
completed_files += 1
|
153
|
-
end
|
154
|
-
end
|
155
|
-
puts
|
156
|
-
return @directory_hash
|
157
|
-
end
|
158
|
-
|
159
|
-
def directory_key
|
160
|
-
@directory_key ||= Cryptographer.hash_data(Configuration.settings[:encryption_key])
|
161
|
-
end
|
162
|
-
|
163
|
-
def normalized_sync_path
|
164
|
-
@normalized_sync_path ||= normalize_sync_path
|
165
|
-
end
|
166
|
-
|
167
|
-
def normalize_sync_path
|
168
|
-
path = Configuration.settings[:sync_path]
|
169
|
-
if path.match(/\/$/)
|
170
|
-
return path
|
171
|
-
else
|
172
|
-
return path + '/'
|
173
|
-
end
|
94
|
+
def liaison
|
95
|
+
AdapterLiaison.instance
|
174
96
|
end
|
175
97
|
|
176
98
|
def last_sync_date
|
177
|
-
@last_sync_date ||= File.exist?(
|
99
|
+
@last_sync_date ||= File.exist?(Index.snapshot_path) ? File.stat(Index.snapshot_path).ctime : nil
|
178
100
|
end
|
179
101
|
|
180
102
|
def last_sync_hash
|
181
|
-
@last_sync_hash ||= File.exist?(
|
103
|
+
@last_sync_hash ||= File.exist?(Index.snapshot_path) ? YAML.load(File.read(Index.snapshot_path)) : {}
|
182
104
|
end
|
183
105
|
|
184
106
|
def files_to_push
|
185
|
-
syncable_files_check(
|
107
|
+
syncable_files_check(Index.local,Index.remote)
|
186
108
|
end
|
187
109
|
|
188
110
|
def files_to_pull
|
189
|
-
syncable_files_check(
|
111
|
+
syncable_files_check(Index.remote,Index.local)
|
190
112
|
end
|
191
113
|
|
192
114
|
def remote_files_to_delete
|
193
|
-
deletable_files_check(
|
115
|
+
deletable_files_check(Index.remote,Index.local)
|
194
116
|
end
|
195
117
|
|
196
118
|
def local_files_to_delete
|
197
|
-
deletable_files_check(
|
198
|
-
end
|
199
|
-
|
200
|
-
def remote_directory_hash
|
201
|
-
@remote_directory_hash ||= begin
|
202
|
-
YAML.parse(decrypt_from_adapter(directory_key)).to_ruby
|
203
|
-
rescue #AWS::S3::Errors::NoSuchKey should provide error for adapters to raise
|
204
|
-
{}
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def store_directory_hash_file
|
209
|
-
@directory_hash = nil #force re-compile before pushing to remote
|
210
|
-
encrypt_to_adapter(directory_hash.to_yaml,directory_key)
|
119
|
+
deletable_files_check(Index.local,Index.remote)
|
211
120
|
end
|
212
121
|
|
213
122
|
def deletable_files_check(source_hash,comparison_hash)
|
@@ -222,27 +131,6 @@ module CloudEncryptedSync
|
|
222
131
|
source_hash.select{|k,v| !comparison_hash.has_key?(k) and (last_sync_has_key ? last_sync_hash.has_key?(k) : !last_sync_hash.has_key?(k)) }
|
223
132
|
end
|
224
133
|
|
225
|
-
def snapshot_file_path
|
226
|
-
"#{Configuration.data_folder_path}/#{snapshot_filename}"
|
227
|
-
end
|
228
|
-
|
229
|
-
def snapshot_filename
|
230
|
-
"#{normalized_sync_path.gsub(/[^A-Za-z0-9]/,'_')}.snapshot.yml"
|
231
|
-
end
|
232
|
-
|
233
|
-
def file_key(full_path)
|
234
|
-
Cryptographer.hash_data(relative_file_path(full_path) + File.open(full_path).read).to_s
|
235
|
-
end
|
236
|
-
|
237
|
-
def relative_file_path(full_path)
|
238
|
-
full_path.gsub(normalized_sync_path,'')
|
239
|
-
end
|
240
|
-
|
241
|
-
def full_file_path(relative_path)
|
242
|
-
normalized_sync_path+'/'+relative_path
|
243
|
-
end
|
244
134
|
end
|
245
135
|
end
|
246
|
-
|
247
|
-
class RegistrationError < RuntimeError; end
|
248
136
|
end
|
data/lib/cloud_encrypted_sync.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'yaml'
|
3
|
+
require 'openssl'
|
4
|
+
require 'digest'
|
5
|
+
require 'find'
|
6
|
+
require 'active_support/core_ext/string'
|
7
|
+
|
1
8
|
require File.expand_path('../cloud_encrypted_sync/master', __FILE__)
|
9
|
+
|
10
|
+
require File.expand_path('../cloud_encrypted_sync/adapter_liaison', __FILE__)
|
11
|
+
require File.expand_path('../cloud_encrypted_sync/adapter_template', __FILE__)
|
2
12
|
require File.expand_path('../cloud_encrypted_sync/configuration', __FILE__)
|
3
13
|
require File.expand_path('../cloud_encrypted_sync/cryptographer', __FILE__)
|
4
|
-
require File.expand_path('../cloud_encrypted_sync/adapter_template', __FILE__)
|
5
14
|
require File.expand_path('../cloud_encrypted_sync/dummy_adapter', __FILE__)
|
15
|
+
require File.expand_path('../cloud_encrypted_sync/errors', __FILE__)
|
16
|
+
require File.expand_path('../cloud_encrypted_sync/index', __FILE__)
|
6
17
|
require File.expand_path('../cloud_encrypted_sync/progress_meter', __FILE__)
|
data/test/test_helper.rb
CHANGED
@@ -22,7 +22,7 @@ module CloudEncryptedSync
|
|
22
22
|
def preset_environment
|
23
23
|
Configuration.instance_variable_set(:@settings,nil)
|
24
24
|
Configuration.instance_variable_set(:@command_line_options,nil)
|
25
|
-
|
25
|
+
Index.instance_variable_set(:@local, nil)
|
26
26
|
FileUtils.mkdir_p test_source_folder
|
27
27
|
FileUtils.mkdir_p test_source_folder + '/test_sub_folder'
|
28
28
|
File.open(test_source_folder + '/test_sub_folder/test_file_one.txt', 'w') do |test_file|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CloudEncryptedSync
|
4
|
+
class AdapterLiaisonTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Configuration.stubs(:settings).returns({
|
8
|
+
:encryption_key => 'asdf',
|
9
|
+
:adapter_name => 'dummy',
|
10
|
+
:bucket => "test-bucket",
|
11
|
+
:sync_path => test_source_folder
|
12
|
+
})
|
13
|
+
Configuration.stubs(:data_folder_path).returns("#{Etc.getpwuid.dir}/.cloud_encrypted_sync")
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'should encrypt when writing' do
|
17
|
+
precrypted_data = File.read(test_source_folder + '/test_sub_folder/test_file_one.txt')
|
18
|
+
key = Cryptographer.hash_data('test_file_key')
|
19
|
+
Adapters::Dummy.expects(:write).with(anything,key).returns(true)
|
20
|
+
AdapterLiaison.instance.push(precrypted_data,key)
|
21
|
+
end
|
22
|
+
|
23
|
+
test 'should decrypt_when_reading' do
|
24
|
+
precrypted_data = File.read(test_source_folder + '/test_sub_folder/test_file_one.txt')
|
25
|
+
encrypted_data = Cryptographer.encrypt_data(precrypted_data)
|
26
|
+
key = Cryptographer.hash_data('test_file_key')
|
27
|
+
Adapters::Dummy.expects(:read).with(key).returns(encrypted_data)
|
28
|
+
assert_equal(precrypted_data,AdapterLiaison.instance.pull(key))
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -21,12 +21,12 @@ module CloudEncryptedSync
|
|
21
21
|
|
22
22
|
test 'should gracefully fail without path in ARGV' do
|
23
23
|
::ARGV = '--adapter dummy --bucket foobar'.split(/\s/)
|
24
|
-
assert_raise(IncompleteConfigurationError) { Configuration.settings }
|
24
|
+
assert_raise(Errors::IncompleteConfigurationError) { Configuration.settings }
|
25
25
|
end
|
26
26
|
|
27
27
|
test 'should gracefully fail when not provided encryption_key and vector provided path in ARGV' do
|
28
28
|
::ARGV = '--adapter dummy --bucket foobar /some/path/to/sync'.split(/\s/)
|
29
|
-
assert_raise(IncompleteConfigurationError) { Configuration.settings }
|
29
|
+
assert_raise(Errors::IncompleteConfigurationError) { Configuration.settings }
|
30
30
|
end
|
31
31
|
|
32
32
|
end
|
data/test/unit/master_test.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
require 'yaml'
|
3
2
|
|
4
3
|
module CloudEncryptedSync
|
5
4
|
class MasterTest < ActiveSupport::TestCase
|
@@ -7,7 +6,6 @@ module CloudEncryptedSync
|
|
7
6
|
def setup
|
8
7
|
Configuration.stubs(:settings).returns({
|
9
8
|
:encryption_key => 'asdf',
|
10
|
-
:initialization_vector => 'qwerty',
|
11
9
|
:adapter_name => 'dummy',
|
12
10
|
:bucket => "test-bucket",
|
13
11
|
:sync_path => test_source_folder
|
@@ -17,10 +15,10 @@ module CloudEncryptedSync
|
|
17
15
|
|
18
16
|
test 'should generate directory hash' do
|
19
17
|
assert_equal('',$stdout.string)
|
20
|
-
hash =
|
21
|
-
assert_match(/\% Complete/,$stdout.string)
|
18
|
+
hash = Index.local
|
22
19
|
assert_equal(1,hash.keys.size)
|
23
20
|
assert_equal('test_sub_folder/test_file_one.txt',hash[hash.keys.first])
|
21
|
+
assert_match(/\% Complete/,$stdout.string)
|
24
22
|
end
|
25
23
|
|
26
24
|
test 'should_return_nil_if_never_synced_before' do
|
@@ -29,25 +27,10 @@ module CloudEncryptedSync
|
|
29
27
|
end
|
30
28
|
|
31
29
|
test 'should want to push everything on first run with local files and empty remote' do
|
32
|
-
|
33
|
-
|
30
|
+
Index.stubs(:remote).returns({})
|
31
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
34
32
|
Master.stubs(:last_sync_hash).returns({})
|
35
|
-
assert_equal(
|
36
|
-
end
|
37
|
-
|
38
|
-
test 'should encrypt when writing' do
|
39
|
-
precrypted_data = File.read(test_source_folder + '/test_sub_folder/test_file_one.txt')
|
40
|
-
key = Cryptographer.hash_data('test_file_key')
|
41
|
-
Adapters::Dummy.expects(:write).with(anything,key).returns(true)
|
42
|
-
Master.send(:encrypt_to_adapter,precrypted_data,key)
|
43
|
-
end
|
44
|
-
|
45
|
-
test 'should decrypt_when_reading' do
|
46
|
-
precrypted_data = File.read(test_source_folder + '/test_sub_folder/test_file_one.txt')
|
47
|
-
encrypted_data = Cryptographer.encrypt_data(precrypted_data)
|
48
|
-
key = Cryptographer.hash_data('test_file_key')
|
49
|
-
Adapters::Dummy.expects(:read).with(key).returns(encrypted_data)
|
50
|
-
assert_equal(precrypted_data,Master.send(:decrypt_from_adapter,key))
|
33
|
+
assert_equal(Index.local,Master.send(:files_to_push))
|
51
34
|
end
|
52
35
|
|
53
36
|
test 'should push files' do
|
@@ -61,15 +44,15 @@ module CloudEncryptedSync
|
|
61
44
|
end
|
62
45
|
|
63
46
|
test 'should want to pull everything on first run with remote files and empty local' do
|
64
|
-
|
65
|
-
|
47
|
+
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt'})
|
48
|
+
Index.stubs(:local).returns({})
|
66
49
|
Master.stubs(:last_sync_hash).returns({})
|
67
50
|
assert_equal({'new_file_key' => 'test_sub_folder/new_file.txt'},Master.send(:files_to_pull))
|
68
51
|
end
|
69
52
|
|
70
53
|
test 'should pull files' do
|
71
|
-
|
72
|
-
|
54
|
+
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt'})
|
55
|
+
Index.stubs(:local).returns({})
|
73
56
|
Master.stubs(:last_sync_hash).returns({})
|
74
57
|
Adapters::Dummy.expects(:read).with('new_file_key').returns(Cryptographer.encrypt_data('foobar'))
|
75
58
|
assert_equal('',$stdout.string)
|
@@ -80,44 +63,44 @@ module CloudEncryptedSync
|
|
80
63
|
end
|
81
64
|
|
82
65
|
test 'should only want to push new files on later run' do
|
83
|
-
|
84
|
-
|
66
|
+
Index.stubs(:remote).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
67
|
+
Index.stubs(:local).returns({'new_file_key' => 'test_sub_folder/new_file.txt', 'old_file_key' => 'test_sub_folder/old_file.txt'})
|
85
68
|
Master.stubs(:last_sync_hash).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
86
69
|
assert_equal({'new_file_key' => 'test_sub_folder/new_file.txt'},Master.send(:files_to_push))
|
87
70
|
end
|
88
71
|
|
89
72
|
test 'should want to pull new files from cloud' do
|
90
|
-
|
91
|
-
|
73
|
+
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt', 'old_file_key' => 'test_sub_folder/old_file.txt'})
|
74
|
+
Index.stubs(:local).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
92
75
|
Master.stubs(:last_sync_hash).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
93
76
|
assert_equal({'new_file_key' => 'test_sub_folder/new_file.txt'},Master.send(:files_to_pull))
|
94
77
|
end
|
95
78
|
|
96
79
|
test 'should want to delete locally missing files from cloud' do
|
97
|
-
|
98
|
-
|
80
|
+
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
81
|
+
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
99
82
|
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
100
83
|
assert_equal({'deleted_file_key' => 'test_sub_folder/deleted_file.txt'},Master.send(:remote_files_to_delete))
|
101
84
|
end
|
102
85
|
|
103
86
|
test 'should delete files from cloud' do
|
104
|
-
|
105
|
-
|
87
|
+
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
88
|
+
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
106
89
|
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
107
90
|
Adapters::Dummy.expects(:delete).with('deleted_file_key').returns(true)
|
108
91
|
Master.delete_remote_files!
|
109
92
|
end
|
110
93
|
|
111
94
|
test 'should want to delete appropriate files locally' do
|
112
|
-
|
113
|
-
|
95
|
+
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
96
|
+
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
114
97
|
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
115
98
|
assert_equal({'deleted_file_key' => 'test_sub_folder/deleted_file.txt'},Master.send(:local_files_to_delete))
|
116
99
|
end
|
117
100
|
|
118
101
|
test 'should delete local files' do
|
119
|
-
|
120
|
-
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'}.merge(
|
102
|
+
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
103
|
+
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'}.merge(Index.local))
|
121
104
|
assert_difference('Dir["#{test_source_folder}/**/*"].length',-1) do
|
122
105
|
Master.delete_local_files!
|
123
106
|
end
|
@@ -128,7 +111,7 @@ module CloudEncryptedSync
|
|
128
111
|
sample_directory_hash = {'sample_file_key' => 'test_sub_folder/sample_file.txt'}
|
129
112
|
Master.instance_variable_set(:@finalize_required,true)
|
130
113
|
Master.stubs(:directory_hash).returns(sample_directory_hash)
|
131
|
-
Adapters::Dummy.expects(:write).with(anything,
|
114
|
+
Adapters::Dummy.expects(:write).with(anything,Index.send(:index_key)).returns(true)
|
132
115
|
Master.finalize!
|
133
116
|
end
|
134
117
|
|
@@ -136,15 +119,15 @@ module CloudEncryptedSync
|
|
136
119
|
#setup mock data
|
137
120
|
sample_directory_hash = {'sample_file_key' => 'test_sub_folder/sample_file.txt'}
|
138
121
|
encrypted_directory_hash = Cryptographer.encrypt_data(sample_directory_hash.to_yaml)
|
139
|
-
Adapters::Dummy.
|
122
|
+
Adapters::Dummy.expects(:read).with(Index.send(:index_key)).returns(encrypted_directory_hash)
|
140
123
|
|
141
124
|
#do actual test
|
142
|
-
decrypted_remote_hash =
|
125
|
+
decrypted_remote_hash = Index.remote
|
143
126
|
assert_equal(sample_directory_hash,decrypted_remote_hash)
|
144
127
|
end
|
145
128
|
|
146
129
|
test 'should puts error message to stdout' do
|
147
|
-
Configuration.stubs(:settings).raises(IncompleteConfigurationError,'test message')
|
130
|
+
Configuration.stubs(:settings).raises(Errors::IncompleteConfigurationError,'test message')
|
148
131
|
assert_equal('',$stdout.string)
|
149
132
|
Master.expects(:pull_files).never
|
150
133
|
Master.sync
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloud_encrypted_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mocha
|
@@ -93,14 +93,18 @@ files:
|
|
93
93
|
- bin/ces
|
94
94
|
- cloud_encrypted_sync.gemspec
|
95
95
|
- lib/cloud_encrypted_sync.rb
|
96
|
+
- lib/cloud_encrypted_sync/adapter_liaison.rb
|
96
97
|
- lib/cloud_encrypted_sync/adapter_template.rb
|
97
98
|
- lib/cloud_encrypted_sync/configuration.rb
|
98
99
|
- lib/cloud_encrypted_sync/cryptographer.rb
|
99
100
|
- lib/cloud_encrypted_sync/dummy_adapter.rb
|
101
|
+
- lib/cloud_encrypted_sync/errors.rb
|
102
|
+
- lib/cloud_encrypted_sync/index.rb
|
100
103
|
- lib/cloud_encrypted_sync/master.rb
|
101
104
|
- lib/cloud_encrypted_sync/progress_meter.rb
|
102
105
|
- lib/cloud_encrypted_sync/version.rb
|
103
106
|
- test/test_helper.rb
|
107
|
+
- test/unit/adapter_liaison_test.rb
|
104
108
|
- test/unit/configuration_test.rb
|
105
109
|
- test/unit/cryptographer_test.rb
|
106
110
|
- test/unit/master_test.rb
|
@@ -131,6 +135,7 @@ specification_version: 3
|
|
131
135
|
summary: Encrypted sync of folder contents to/from cloud storage.
|
132
136
|
test_files:
|
133
137
|
- test/test_helper.rb
|
138
|
+
- test/unit/adapter_liaison_test.rb
|
134
139
|
- test/unit/configuration_test.rb
|
135
140
|
- test/unit/cryptographer_test.rb
|
136
141
|
- test/unit/master_test.rb
|