outatime 0.2.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: ad66a0137663ddd2acc6eb4d723360557dd39c9b
4
+ data.tar.gz: dbf910aa8229373c9d7d305ab83abcb82c67fcad
5
+ SHA512:
6
+ metadata.gz: d85e34909a6fd7b71555565e207519fe537c3b416ca625ea5b93b96fa38428c9ecb5eede76812525fb62a326149de5652dfebb592cfc388bedbb10f0d893cc57
7
+ data.tar.gz: 34f8bc3d299908d13c035475c1a8e21eaf8d53bd0dadc049bc9776723cf62782864b39993da1c8dce37f1e768616cff2fa690bd2d7e91b1f7d231d34e7652a86
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in outatime.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Pressed.net
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Outatime
2
+
3
+ For users of versioned AWS S3 buckets. This command-line tool will allow you to download a snapshot of the files in that bucket from a given point in time.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'outatime'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install outatime
20
+
21
+ ## Usage
22
+
23
+ ```
24
+ outatime -r {region} -b {bucket-name} -f {date} -p {subdirectory} --profile {aws profile name}
25
+ ```
26
+
27
+ Example:
28
+ ```
29
+ outatime -r us-east-1 -b my-bucket -f '21 Oct 2015' -p schematics/hoverboard --profile aws-profile
30
+ ```
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bundle` to install dependencies. Then, run `rake spec` to run the tests.
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pressednet/outatime.
39
+
40
+ ## LICENSE
41
+
42
+ MIT License, see [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/outatime ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/outatime'
4
+
5
+ begin
6
+ options = Trollop::options do
7
+ opt :region, "AWS region", type: :string, default: "us-west-2"
8
+ opt :bucket, "Bucket name", type: :string, required: true
9
+ opt :from, "Time description", type: :string, required: true
10
+ opt :prefix, "Restore files from this prefix", type: :string, default: ""
11
+ opt :destination, "Destination for restored files", type: :string, required: true
12
+ opt :profile, "AWS Profile Name", type: :string, required: false, default: nil
13
+ opt :threads, "Number of download threads", type: :integer, default: 20
14
+ opt :verbose, "Verbose Mode", type: :boolean, default: false
15
+ end
16
+
17
+ if options[:profile]
18
+ Outatime.update_aws_profile(
19
+ region: options[:region],
20
+ credentials: Aws::SharedCredentials.new(profile_name: options[:profile])
21
+ )
22
+ end
23
+
24
+ Outatime::CLI.new(options).run
25
+
26
+ rescue => e
27
+ raise e if $DEBUG
28
+ STDERR.puts e.message
29
+ STDERR.puts e.backtrace.join("\n")
30
+ exit 1
31
+ end
data/lib/outatime.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "aws-sdk"
2
+ require "chronic"
3
+ require "pathname"
4
+ require "ruby-progressbar"
5
+ require "thread"
6
+ require "trollop"
7
+ require_relative "outatime/version"
8
+ require_relative "outatime/fetcher"
9
+ require_relative "outatime/cli"
10
+
11
+ module Outatime
12
+ # Public: Update AWS profile
13
+ #
14
+ # params - Hash of profile settings.
15
+ #
16
+ # Returns nothing.
17
+ def self.update_aws_profile(params)
18
+ Aws.config.update(params)
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ module Outatime
2
+ class CLI
3
+ attr_accessor :options
4
+
5
+ # Public: It will fetch the correct version of a file from S3 and shows a
6
+ # progress bar to indicate its progress.
7
+ #
8
+ # options - The Hash options used to configure how fetcher works:
9
+ # :region - The AWS region.
10
+ # :bucket - The versioned bucket name.
11
+ # :from - Time description.
12
+ # :prefix - Restore files from this prefix.
13
+ # :destination - Destination for restored files
14
+ # :threads - Number of download threads
15
+ # :verbose - Verbose Mode
16
+ #
17
+ def initialize(options)
18
+ @options = options
19
+ end
20
+
21
+ # Public: Runs the fetcher and download the correct files version.
22
+ def run
23
+ fetcher = Outatime::Fetcher.new(options)
24
+
25
+ pb = ProgressBar.create(total: fetcher.total_size,
26
+ format: "%t: |%B| %f %c/%C %R MB/sec",
27
+ rate_scale: lambda { |rate| rate / 1024 / 1024 },
28
+ throttle_rate: 0.5)
29
+
30
+ fetcher.fetch! do |file|
31
+ pb.progress += file.size
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,142 @@
1
+ module Outatime
2
+ class Fetcher
3
+ attr_accessor :options
4
+
5
+ # Public: Fetcher will fetch the correct version of a file from S3.
6
+ #
7
+ # options - The Hash options used to configure how fetcher works:
8
+ # :bucket - The versioned bucket name (required).
9
+ # :destination - Destination for restored files (required).
10
+ # :from - Time description (required).
11
+ # :prefix - Restore files from this prefix.
12
+ # :s3_client - An existing Aws::S3::Client.
13
+ # :threads - Number of download threads
14
+ # :verbose - Verbose Mode
15
+ #
16
+ def initialize(options = {})
17
+ @options = options
18
+ @files_mutex = Mutex.new
19
+ @fetch_block_mutex = Mutex.new
20
+ @s3_client = options[:s3_client] if options[:s3_client]
21
+ @from = ::Chronic.parse(@options[:from]) if @options[:from]
22
+
23
+ # raise if the date/time was not parsed
24
+ raise ArgumentError, "The from time was not parseable." if @from.nil?
25
+ end
26
+
27
+ # Public: Fetches the file versions from S3 bucket.
28
+ #
29
+ # block - an optional block that receives the file description after it is
30
+ # downloaded to the local.
31
+ #
32
+ # Returns nothing.
33
+ def fetch!(&block)
34
+ fetch_objects(object_versions, &block)
35
+ end
36
+
37
+ # Public: Returns the objects total size.
38
+ #
39
+ # Returns an integer.
40
+ def total_size
41
+ object_versions.inject(0) { |sum, obj| sum += obj.size }
42
+ end
43
+
44
+ # Public: Fetch the S3 object versions.
45
+ #
46
+ # Returns an Array of Aws::S3::Types::ObjectVersion.
47
+ def object_versions
48
+ puts "fetching object versions from #{@from}" if verbose?
49
+ @files ||= begin
50
+ versions = []
51
+ delete_markers = []
52
+
53
+ s3_client.list_object_versions(bucket: @options[:bucket],
54
+ prefix: @options[:prefix]).each do |response|
55
+
56
+ versions += filter_future_items(response.versions, @from)
57
+ delete_markers += filter_future_items(response.delete_markers, @from)
58
+ end
59
+
60
+ # keep only the latest versions
61
+ # AWS lists the latest versions first, so it should be OK to use uniq here.
62
+ versions.uniq! { |obj| obj.key }
63
+ delete_markers.uniq! { |obj| obj.key }
64
+
65
+ delete_marker_keys = delete_markers.map { |dm| dm.key }
66
+
67
+ # check versions to see if we have newer delete_markers
68
+ # if so, delete those versions
69
+ versions.delete_if do |version|
70
+ if dm_index = delete_marker_keys.index(version.key)
71
+ if version.last_modified <= delete_markers[dm_index].last_modified
72
+ true
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Private: Checks if it is in verbose mode.
82
+ #
83
+ # Returns a boolean.
84
+ def verbose?
85
+ @options[:verbose]
86
+ end
87
+
88
+ # Private: Fetches the objects from S3 bucket.
89
+ #
90
+ # files - an Array of Aws::S3::Types::ObjectVersion.
91
+ #
92
+ # Returns nothing.
93
+ def fetch_objects(files)
94
+ threads = []
95
+
96
+ @options[:threads].times do
97
+ threads << Thread.new do
98
+ while !(file = @files_mutex.synchronize { files.pop }).nil? do
99
+ dest = Pathname.new("#{@options[:destination]}/#{file.key}")
100
+
101
+ if file.key.end_with?("/")
102
+ puts "Creating s3 subdirectory #{file.key} - #{Time.now}" if verbose?
103
+ dest.mkpath
104
+ else
105
+ dest.dirname.mkpath
106
+
107
+ puts "Copying from s3 #{file.key} - #{Time.now}" if verbose?
108
+ s3_client.get_object(response_target: "#{dest}",
109
+ bucket: @options[:bucket],
110
+ key: file.key,
111
+ version_id: file.version_id)
112
+ end
113
+
114
+ @fetch_block_mutex.synchronize { yield file } if block_given?
115
+ end
116
+ end
117
+ end
118
+
119
+ threads.map(&:join)
120
+ end
121
+
122
+ # Private: Creates the S3 client instance.
123
+ #
124
+ # Returns an Aws::S3::Client.
125
+ def s3_client
126
+ region = @options[:region] || ENV["AWS_REGION"]
127
+ @s3_client ||= Aws::S3::Client.new(region: region)
128
+ end
129
+
130
+ # Private: Returns an Array of items modified on or before the given date/time.
131
+ #
132
+ # items - An Array of objects. Object must respond to #last_modified.
133
+ # date_time - Comparison date/time.
134
+ #
135
+ # Returns Array.
136
+ def filter_future_items(items, date_time)
137
+ items.find_all do |obj|
138
+ obj.last_modified <= @from
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module Outatime
2
+ VERSION = "0.2.0"
3
+ end
data/outatime.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'outatime/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "outatime"
8
+ spec.version = Outatime::VERSION
9
+ spec.authors = ["Justin Mazzi", "Rubem Nakamura", "Mike Boone"]
10
+ spec.email = ["justin@pressed.net", "rubem.nakamura@pressed.net", "mike.boone@pressed.net"]
11
+
12
+ spec.summary = %q{Choose versioned S3 files from a point in time.}
13
+ spec.description = %q{Choose file versions from a versioned S3 bucket based on a given time.}
14
+ spec.homepage = "https://github.com/pressednet/outatime/"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "bin"
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency "aws-sdk", "~> 2.6", ">= 2.6.14"
25
+ spec.add_runtime_dependency "chronic", "~> 0.10.2"
26
+ spec.add_runtime_dependency "ruby-progressbar", "~> 1.8.1"
27
+ spec.add_runtime_dependency "thread", "~> 0.2.2"
28
+ spec.add_runtime_dependency "trollop", "~> 2.1.2"
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.13"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ spec.add_development_dependency "activesupport", "~> 5.0.0"
34
+ spec.add_development_dependency "byebug"
35
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: outatime
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Mazzi
8
+ - Rubem Nakamura
9
+ - Mike Boone
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2016-12-22 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws-sdk
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.6'
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 2.6.14
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - "~>"
30
+ - !ruby/object:Gem::Version
31
+ version: '2.6'
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.6.14
35
+ - !ruby/object:Gem::Dependency
36
+ name: chronic
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.10.2
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.10.2
49
+ - !ruby/object:Gem::Dependency
50
+ name: ruby-progressbar
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 1.8.1
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 1.8.1
63
+ - !ruby/object:Gem::Dependency
64
+ name: thread
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.2.2
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 0.2.2
77
+ - !ruby/object:Gem::Dependency
78
+ name: trollop
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 2.1.2
84
+ type: :runtime
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 2.1.2
91
+ - !ruby/object:Gem::Dependency
92
+ name: bundler
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.13'
98
+ type: :development
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '1.13'
105
+ - !ruby/object:Gem::Dependency
106
+ name: rake
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '10.0'
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '10.0'
119
+ - !ruby/object:Gem::Dependency
120
+ name: rspec
121
+ requirement: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.0'
126
+ type: :development
127
+ prerelease: false
128
+ version_requirements: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '3.0'
133
+ - !ruby/object:Gem::Dependency
134
+ name: activesupport
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 5.0.0
140
+ type: :development
141
+ prerelease: false
142
+ version_requirements: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: 5.0.0
147
+ - !ruby/object:Gem::Dependency
148
+ name: byebug
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ description: Choose file versions from a versioned S3 bucket based on a given time.
162
+ email:
163
+ - justin@pressed.net
164
+ - rubem.nakamura@pressed.net
165
+ - mike.boone@pressed.net
166
+ executables:
167
+ - outatime
168
+ extensions: []
169
+ extra_rdoc_files: []
170
+ files:
171
+ - ".gitignore"
172
+ - ".rspec"
173
+ - ".travis.yml"
174
+ - Gemfile
175
+ - LICENSE
176
+ - README.md
177
+ - Rakefile
178
+ - bin/outatime
179
+ - lib/outatime.rb
180
+ - lib/outatime/cli.rb
181
+ - lib/outatime/fetcher.rb
182
+ - lib/outatime/version.rb
183
+ - outatime.gemspec
184
+ homepage: https://github.com/pressednet/outatime/
185
+ licenses:
186
+ - MIT
187
+ metadata: {}
188
+ post_install_message:
189
+ rdoc_options: []
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ required_rubygems_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ requirements: []
203
+ rubyforge_project:
204
+ rubygems_version: 2.5.2
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: Choose versioned S3 files from a point in time.
208
+ test_files: []