outatime 0.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.
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: []