s3_meta_sync 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc3bcfa388ab131fa0a8cd4117dac7c2e21460e7
4
+ data.tar.gz: 311333996d21e3ee022dd1beb8727b5298b2b5d6
5
+ SHA512:
6
+ metadata.gz: 1d6d33a5fbfac5e447a0aa76470d35ab31e691bca5a5464af9d767d2a2aac569d61669a75d14e32d177f18cb2bf8ec68dabd1b0adae3c755e910c1f5d02fd5f1
7
+ data.tar.gz: 3b2adea548763266b7c439148d629c6ac521567aea458158c15be01bf7c81cf481de867790998dd2c7695b4a603d98e9344e4e79a3a6cd09c3e603c0ba2ca65b
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ ��gm����
2
+ �b��ې�m0�?�|�jC�$�"���]���{ɤ'ٍර#sJE�F����8`F�,jel5()Z�G�
data/bin/s3-meta-sync ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+ require "rubygems" if RUBY_VERSION < "1.9"
4
+ require "s3_meta_sync"
5
+ exit S3MetaSync.run(ARGV)
@@ -0,0 +1,3 @@
1
+ module S3MetaSync
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,162 @@
1
+ require "s3_meta_sync/version"
2
+ require "open-uri"
3
+ require "yaml"
4
+ require "digest/md5"
5
+ require "aws/s3"
6
+ require "optparse"
7
+
8
+ module S3MetaSync
9
+ RemoteWithoutMeta = Class.new(Exception)
10
+ META_FILE = ".s3-meta-sync"
11
+
12
+ class Syncer
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ def sync(source, destination)
18
+ raise if source.end_with?("/") or destination.end_with?("/")
19
+
20
+ if destination.include?(":")
21
+ @bucket, destination = destination.split(":")
22
+ upload(source, destination)
23
+ else
24
+ @bucket, source = source.split(":")
25
+ download(source, destination)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def upload(source, destination)
32
+ remote_info = begin
33
+ download_meta(destination)
34
+ rescue RemoteWithoutMeta
35
+ {}
36
+ end
37
+ generate_meta(source)
38
+ local_info = read_meta(source)
39
+
40
+ local_info.each do |path, md5|
41
+ next if remote_info[path] == md5
42
+ upload_file(source, path, destination)
43
+ end
44
+
45
+ (remote_info.keys - local_info.keys).each do |path|
46
+ delete_remote_file(destination, path)
47
+ end
48
+
49
+ upload_file(source, META_FILE, destination)
50
+ end
51
+
52
+ def upload_file(source, path, destination)
53
+ s3.objects["#{destination}/#{path}"].write File.read("#{source}/#{path}"), :acl => :public_read
54
+ end
55
+
56
+ def delete_remote_file(remote, path)
57
+ s3.objects["#{remote}/#{path}"].delete
58
+ end
59
+
60
+ def delete_local_file(local, path)
61
+ File.delete("#{local}/#{path}")
62
+ end
63
+
64
+ def s3
65
+ @s3 ||= ::AWS::S3.new(:access_key_id => @config[:key], :secret_access_key => @config[:secret]).buckets[@bucket]
66
+ end
67
+
68
+ def generate_meta(source)
69
+ meta = Hash[Dir["#{source}/**/*"].select { |f| File.file?(f) }.map do |file|
70
+ [file.sub("#{source}/", ""), Digest::MD5.file(file).to_s]
71
+ end]
72
+ file = "#{source}/#{META_FILE}"
73
+ FileUtils.mkdir_p(File.dirname(file))
74
+ File.write(file, meta.to_yaml)
75
+ end
76
+
77
+ def read_meta(source)
78
+ file = "#{source}/#{META_FILE}"
79
+ File.exist?(file) ? YAML.load(File.read(file)) : {}
80
+ end
81
+
82
+ def download_meta(destination)
83
+ content = download_content("#{destination}/#{META_FILE}")
84
+ YAML.load(content)
85
+ rescue
86
+ raise RemoteWithoutMeta
87
+ end
88
+
89
+ def download_file(source, path, destination)
90
+ content = download_content("#{source}/#{path}")
91
+ file = "#{destination}/#{path}"
92
+ FileUtils.mkdir_p(File.dirname(file))
93
+ File.write(file, content)
94
+ end
95
+
96
+ def download_content(path)
97
+ url = "https://s3#{"-#{region}" if region}.amazonaws.com/#{@bucket}/#{path}"
98
+ open(url).read
99
+ rescue OpenURI::HTTPError
100
+ raise "Unable to download #{url} -- #{$!}"
101
+ end
102
+
103
+ def download(source, destination)
104
+ remote_info = download_meta(source)
105
+ generate_meta(destination)
106
+ local_info = read_meta(destination) # TODO maybe generate !?
107
+
108
+ remote_info.each do |path, md5|
109
+ next if local_info[path] == md5
110
+ download_file(source, path, destination)
111
+ end
112
+
113
+ (local_info.keys - remote_info.keys).each do |path|
114
+ delete_local_file(destination, path)
115
+ end
116
+
117
+ download_file(source, META_FILE, destination)
118
+
119
+ `find #{destination} -depth -empty -delete`
120
+ end
121
+
122
+ def region
123
+ @config[:region] unless @config[:region].to_s.empty?
124
+ end
125
+ end
126
+
127
+ class << self
128
+ def run(argv)
129
+ source, dest, options = parse_options(argv)
130
+ Syncer.new(options).sync(source, dest)
131
+ 0
132
+ end
133
+
134
+ def parse_options(argv)
135
+ options = {}
136
+ OptionParser.new do |opts|
137
+ opts.banner = <<-BANNER.gsub(/^ {10}/, "")
138
+ Sync folders with s3 using a metadata file with md5 sums.
139
+
140
+ # upload local files and remove everything that is not local
141
+ s3-meta-sync <local> <bucket:folder> --key <aws-access-key> --secret <aws-secret-key>
142
+
143
+ # download files and remove everything that is not remote
144
+ s3-meta-sync <bucket:folder> <local> # no credentials required
145
+
146
+
147
+ Options:
148
+ BANNER
149
+ opts.on("-k", "--key KEY", "AWS access key") { |c| options[:key] = c }
150
+ opts.on("-s", "--secret SECRET", "AWS secret key") { |c| options[:secret] = c }
151
+ opts.on("-r", "--region REGION", "AWS region if not us-standard") { |c| options[:region] = c }
152
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
153
+ opts.on("-v", "--version", "Show Version"){ puts VERSION; exit}
154
+ end.parse!(argv)
155
+
156
+ raise "need source and destination" unless argv.size == 2
157
+ raise "need credentials --key + --secret" if argv.last.include?(":") and not options[:key] or not options[:secret]
158
+
159
+ [*argv, options]
160
+ end
161
+ end
162
+ end
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3_meta_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDMjCCAhqgAwIBAgIBADANBgkqhkiG9w0BAQUFADA/MRAwDgYDVQQDDAdtaWNo
14
+ YWVsMRcwFQYKCZImiZPyLGQBGRYHZ3Jvc3NlcjESMBAGCgmSJomT8ixkARkWAml0
15
+ MB4XDTEzMDIwMzE4MTMxMVoXDTE0MDIwMzE4MTMxMVowPzEQMA4GA1UEAwwHbWlj
16
+ aGFlbDEXMBUGCgmSJomT8ixkARkWB2dyb3NzZXIxEjAQBgoJkiaJk/IsZAEZFgJp
17
+ dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMorXo/hgbUq97+kII9H
18
+ MsQcLdC/7wQ1ZP2OshVHPkeP0qH8MBHGg6eYisOX2ubNagF9YTCZWnhrdKrwpLOO
19
+ cPLaZbjUjljJ3cQR3B8Yn1veV5IhG86QseTBjymzJWsLpqJ1UZGpfB9tXcsFtuxO
20
+ 6vHvcIHdzvc/OUkICttLbH+1qb6rsHUceqh+JrH4GrsJ5H4hAfIdyS2XMK7YRKbh
21
+ h+IBu6dFWJJByzFsYmV1PDXln3UBmgAt65cmCu4qPfThioCGDzbSJrGDGLmw/pFX
22
+ FPpVCm1zgYSb1v6Qnf3cgXa2f2wYGm17+zAVyIDpwryFru9yF/jJxE38z/DRsd9R
23
+ /88CAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQUsiNnXHtKeMYYcr4yJVmQ
24
+ WONL+IwwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQAlyN7kKo/NQCQ0
25
+ AOzZLZ3WAePvStkCFIJ53tsv5Kyo4pMAllv+BgPzzBt7qi605mFSL6zBd9uLou+W
26
+ Co3s48p1dy7CjjAfVQdmVNHF3MwXtfC2OEyvSQPi4xKR8iba8wa3xp9LVo1PuLpw
27
+ /6DsrChWw74HfsJN6qJOK684hJeT8lBYAUfiC3wD0owoPSg+XtyAAddisR+KV5Y1
28
+ NmVHuLtQcNTZy+gRht3ahJRMuC6QyLmkTsf+6MaenwAMkAgHdswGsJztOnNnBa3F
29
+ y0kCSWmK6D+x/SbfS6r7Ke07MRqziJdB9GuE1+0cIRuFh8EQ+LN6HXCKM5pon/GU
30
+ ycwMXfl0
31
+ -----END CERTIFICATE-----
32
+ date: 2013-09-25 00:00:00.000000000 Z
33
+ dependencies:
34
+ - !ruby/object:Gem::Dependency
35
+ name: aws-sdk
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ description:
49
+ email: michael@grosser.it
50
+ executables:
51
+ - s3-meta-sync
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - bin/s3-meta-sync
56
+ - lib/s3_meta_sync.rb
57
+ - lib/s3_meta_sync/version.rb
58
+ homepage: http://github.com/grosser/s3_meta_sync
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.0.6
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Sync folders with s3 using a metadata file and md5 diffs
82
+ test_files: []
metadata.gz.sig ADDED
@@ -0,0 +1 @@
1
+ <]k1��le-*�Cs;�?㣻K�c\