s3_meta_sync 0.1.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.
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\