blockbuster 0.4.1

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: 0bdda5e73f8cf27127f1e0e256f9049ea96d0fd3
4
+ data.tar.gz: a5e0150870a52e9e4e559e3f7a151ab29d163cff
5
+ SHA512:
6
+ metadata.gz: a7c390e1556f8bb3a9f0c905c543b01fab2c938e6c6638b4d5a5ec33c307058c1ab07ad8d7ea3183875e62cd276dd8c26756efee4843dd1c1e431c1492b01918
7
+ data.tar.gz: 6d7a7349a31718c0a0680d762ba2a5844a34f3bdcd15523bdca57d91f504ac81a5972781a651753c72a0b79a1753bce50087a8cf35bb6941d4e23fc88bff16db
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ !/spec/fixtures/*gz
11
+ /test/
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ LineLength:
2
+ Enabled: false
3
+ Style/FrozenStringLiteralComment:
4
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
5
+ script:
6
+ - bundle exec bundle-audit check --update
7
+ - bundle exec rake rubocop
8
+ - bundle exec rake test
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ 0.4.1
2
+ ===
3
+
4
+ - Use target_path instead of file_name for output
5
+
6
+ 0.4.0
7
+ ===
8
+
9
+ - introduce Delta features (opt-in feature)
10
+ - manager accepts a configuration object
11
+ - packager broken up into extractor and packager
12
+ - master file represented by Master object
13
+ - delta files represented by Delta object
14
+ - delegate files to extract to ExtractionList object
15
+ - add a Comparator object as blockbuster's in-memory datastore
16
+
17
+ 0.3.0
18
+ ====
19
+
20
+ - configuration class introduced
21
+ - manager delegates to configuration
22
+ - Blockbuster allow a `configure` setup block
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in blockbuster.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Lukas Eklund
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # Blockbuster
2
+
3
+ [![Build Status](https://travis-ci.org/fastly/blockbuster.svg?branch=master)](https://travis-ci.org/fastly/blockbuster)
4
+
5
+ Managing your VCR cassettes since 2016.
6
+
7
+ The task of this gem is to take all your VCR cassettes and package them into one `.tar.gz` file
8
+ for adding to git or other distributed version control system.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'blockbuster'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install blockbuster
25
+
26
+ Optionally, ignore your cassettes in git and make sure to include the tar.gz file:
27
+
28
+ ```
29
+ # .gitignore
30
+
31
+ test/cassettes
32
+ !test/vcr_cassettes.tar.gz
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ #### Minitest example
38
+
39
+ Given a directory layout of:
40
+
41
+ ```
42
+ -- test
43
+ |-- blockbuster_spec.rb
44
+ |-- cassettes
45
+ | |-- foo.yml
46
+ | `-- bar.yml
47
+ `-- test_helper.rb
48
+ ```
49
+
50
+ In your `test_helper.rb` add
51
+
52
+
53
+
54
+ ```
55
+ require 'blockbuster'
56
+
57
+ manager = Blockbuster::Manager.new do |c|
58
+ c.test_directory = File.dirname(__FILE__)
59
+ c.silent = false
60
+ end
61
+
62
+ # Alternatively you can pass Blockbuster::Manager.new a Blockbuster::Configuration object. But do not do both. The block will win if you attempt to do both. To be clear, passing a configuration as an argument AND additionally providing a block isn't destructive, it just has no purpose. Pick one or the other.
63
+
64
+ manager.rent
65
+ ```
66
+
67
+ And then in an after run block
68
+
69
+ ```
70
+ Minitest.after_run do
71
+ manager.drop_off
72
+ end
73
+ ```
74
+
75
+ If there were changes/additions/deletions to your cassette files a new tar.gz cassette file will be created.
76
+
77
+ #### Blockbuster::Configuration
78
+
79
+ The configuration constructor takes the following options:
80
+
81
+ ```
82
+ cassette_directory: String
83
+ Name of directory where cassette files are stored.
84
+ Will be stored under the test directory.
85
+ default: 'casssettes'
86
+ master_tar_file: String
87
+ name of gz cassettes file.
88
+ default: 'vcr_cassettes.tar.gz'
89
+ test_directory: String
90
+ path to test directory where cassete file and cassetes will be stored.
91
+ default: 'test'
92
+ silent: Boolean
93
+ Silence all output.
94
+ default: false
95
+ wipe_cassette_dir: Boolean
96
+ If true, will wipe the existing cassette directory when `rent` is called.
97
+ default: false
98
+ enable_deltas: Boolean (more on this below)
99
+ Toggle the Delta feature
100
+ default: false
101
+ delta_directory: String
102
+ Name of the directory to store deltas (relative to test_directory)
103
+ default: 'deltas'
104
+ current_delta_name: String
105
+ Field that names the current delta
106
+ default: 'current_delta.tar.gz'
107
+ ```
108
+
109
+ These are all read-only attributes with the exception of `silent`. This is writeable so that one can suppress output
110
+ on setup but see output about new/changed cassettes upon `drop_off`.
111
+
112
+ There are 3 public methods
113
+
114
+ ```
115
+ manager.rent
116
+ manager.setup
117
+ ```
118
+
119
+ Extracts all cassettes from `test/vcr_cassettes.tar.gz` into `test/cassetes`
120
+ directory. To wipe the existing directory before extracting cassettes
121
+ initialize the manager with `wipe_cassette_dir: true`.
122
+
123
+ ```
124
+ manager.rewind?
125
+ ```
126
+
127
+ Compares the the files in `test/cassettes` to the files created during setup. Returns `true`
128
+ if there are any changes or additions. Returns `false` if they are identical.
129
+
130
+ ```
131
+ manager.drop_off
132
+ manager.teardown
133
+ ```
134
+
135
+ Packages cassete files into `test/vcr_cassettes.tar.gz` if `rewind?` returns true.
136
+ Can be called with `force: true` to force it to create the cassete file.
137
+
138
+ #### Recreating a cassette file
139
+
140
+ If you are using automatic re-recording of cassettes Blockbuster will see the changes and create a new package.
141
+ To skip the cassete extraction and use the existing local cassettes you can run your tests with `VCR_MODE=local`
142
+
143
+ ```
144
+ VCR_MODE=local rake test
145
+ ```
146
+
147
+ You can remove a single existing cassette and run in local mode and VCR will re-record that cassette and Blockbuster will
148
+ package a new cassettes file.
149
+
150
+ #### Removing or renaming a cassette file
151
+
152
+ If you rename a cassette or need to delete one from the archive you need to do the following:
153
+
154
+ * Run your test suite so that you have an up-to-date cassette directory
155
+ * Do the work to rename test/cassette etc
156
+ * Run tests (even that single test) with `VCR_MODE=local`
157
+
158
+ #### Re-record all cassettes
159
+
160
+ ```
161
+ > rm -r test/cassettes
162
+ > rm test/vcr_cassettes.tar.gz
163
+ > rake test
164
+ ```
165
+
166
+ ### Delta feature (*Experimental*)
167
+
168
+ If you are working on a project that requires a lot of re-recording, or is in active development with HTTP interactions to different systems and multiple developers working on the project, the benefits of Blockbuster degrade quite quickly. Merge conflicts happen very frequently since all cassettes are stored in one file, and the only resolution is to re-record everything.
169
+
170
+ This is why deltas were built. The idea is inspired by Sphinx's delta index system. The idea is to add changes or creations to delta files, and not a master file. In the typical git branching workflow, this would work as follows:
171
+
172
+ - If no master file exists, one is generated the first time someone utilizes blockbuster.
173
+ - current_delta_name is set by dynamically retrieving the git branch name (This git-branch retrieval is the responsibility of the application to configure)
174
+ - as long as a master file exists, Blockbuster will only add changes to a new tarball.
175
+ - once you've switched to a new branch (presumably you've gotten your branch merged into master), Blockbuster stops writing to that delta, and only applies changes to a new delta based off the new branch name.
176
+ - The delta file names include a timestamp, based on when the file was packaged. This allows Blockbuster to use best-effort sorting to load in all delta files.
177
+ - Blockbuster maintains an in-memory datastore of files and their last checksums. To build this, Blockbuster extracts files from all available tarballs in a sorted order. The order is always Master first, and then delta files sorted by filename, which for all intents and purposes is based on time of creation (since the name includes a timestamp). This means that if more than one tarball contains the exact same file, the checksum in the datastore will come from the last file in the sort that contains it.
178
+ - This allows conflicts to become far less possible. Additionally, even if a conflict does occur, resolving the conflict becomes much easier, as the conflict will be isolated to the changes you are actively working on.
179
+ - Deletions aren't managed by deltas. This is because we want to maintain the principle of never touching any other tarball other than master or the the current delta. To actually delete a file, we'd have to remove it from any tarball it exists in. This isn't worth the advantage of the guarantee of leaving existing deltas alone. In the end, regenerating a Master file will resolve deleted files.
180
+ - Regenerating a new Master file is actually relatively simple. The only mechanism required to have Blockbuster do this automatically is to simply delete the existing Master file. It is additionally currently the responsibility of the application to remove deltas when regenerating a new master file.
181
+
182
+ ## Development
183
+
184
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
185
+
186
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
187
+
188
+ ## Contributing
189
+
190
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/blockbuster. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
191
+
192
+
193
+ ## License
194
+
195
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
196
+
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'rubocop/rake_task'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'spec'
7
+ t.libs << 'lib'
8
+ t.test_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ desc 'Run rubocop'
12
+ RuboCop::RakeTask.new do |task|
13
+ task.options = %w(--display-cop-names)
14
+ task.formatters = %w(fuubar)
15
+ task.fail_on_error = true
16
+ end
17
+
18
+ task spec: :test
19
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'blockbuster'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'pry'
14
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'blockbuster/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'blockbuster'
8
+ spec.version = Blockbuster::VERSION
9
+ spec.authors = ['Lukas Eklund', 'Alexander Bergman', 'Hassan Shahid']
10
+ spec.email = ['leklund@fastly.com', 'alexander@fastly.com', 'hassan@fastly.com']
11
+
12
+ spec.summary = 'Packaging VCR cassettes for git since 2016'
13
+ spec.homepage = 'https://github.com/fastly/blockbuster'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.10'
22
+ spec.add_development_dependency 'bundler-audit'
23
+ spec.add_development_dependency 'minitest'
24
+ spec.add_development_dependency 'mocha'
25
+ spec.add_development_dependency 'pry'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rubocop', '~> 0.37'
28
+ end
@@ -0,0 +1,20 @@
1
+ require 'fileutils'
2
+ require 'rubygems/package'
3
+ require 'zlib'
4
+
5
+ require 'blockbuster/configuration'
6
+ require 'blockbuster/concerns/file_helpers'
7
+ require 'blockbuster/concerns/output_helpers'
8
+ require 'blockbuster/concerns/extractor'
9
+ require 'blockbuster/concerns/packager'
10
+ require 'blockbuster/master'
11
+ require 'blockbuster/delta'
12
+ require 'blockbuster/comparator'
13
+ require 'blockbuster/extraction_list'
14
+
15
+ require 'blockbuster/manager'
16
+ require 'blockbuster/version'
17
+
18
+ # nodoc
19
+ module Blockbuster
20
+ end
@@ -0,0 +1,90 @@
1
+ module Blockbuster
2
+ # Data store for files, sources, states, and checksums
3
+ class Comparator
4
+ include Blockbuster::FileHelpers
5
+ include Blockbuster::OutputHelpers
6
+
7
+ CONTENT = 'content'.freeze
8
+ SOURCE = 'source'.freeze
9
+
10
+ attr_reader :configuration, :inventory, :edited, :current_delta_files, :deleted
11
+
12
+ def initialize(configuration)
13
+ @configuration = configuration
14
+ @inventory = {}
15
+ @edited = []
16
+ @current_delta_files = []
17
+ @deleted = []
18
+ end
19
+
20
+ def add(key, value, source)
21
+ inventory[key] = { CONTENT => value, SOURCE => source }
22
+ end
23
+
24
+ def delete(key)
25
+ inventory.delete(key)
26
+ end
27
+
28
+ def keys
29
+ inventory.keys
30
+ end
31
+
32
+ def present?
33
+ !keys.empty?
34
+ end
35
+
36
+ def edited?(file)
37
+ (edited + current_delta_files).include?(file)
38
+ end
39
+
40
+ def store_current_delta_files
41
+ inventory.each do |k, v|
42
+ scrubbed = Blockbuster::Delta.file_name_without_timestamp(v[SOURCE])
43
+ current_delta_files << k if scrubbed == configuration.current_delta_name
44
+ end
45
+ end
46
+
47
+ def compare(key, new_digest)
48
+ digest = inventory[key]
49
+
50
+ if digest.nil?
51
+ silent_puts "New cassette: #{key}"
52
+ return true
53
+ elsif digest[CONTENT] != new_digest
54
+ silent_puts "Cassette changed: #{key}"
55
+ return true
56
+ end
57
+ end
58
+
59
+ def rewind?(files) # rubocop:disable Metrics/AbcSize
60
+ base_files = []
61
+
62
+ files.each do |file|
63
+ next unless File.file?(file)
64
+
65
+ key = configuration.key_from_path(file)
66
+ base_files << key
67
+
68
+ edited << key if compare(key, file_digest(file))
69
+ end
70
+
71
+ @deleted = keys - base_files
72
+
73
+ return true if any_deleted?
74
+
75
+ !edited.empty?
76
+ end
77
+
78
+ def any_deleted?
79
+ if configuration.deltas_disabled? && !@deleted.empty?
80
+ silent_puts "Cassettes deleted: #{@deleted}"
81
+ return true
82
+ elsif configuration.deltas_enabled? && !(current_delta_files & @deleted).empty?
83
+ silent_puts "Cassettes deleted: #{@deleted}"
84
+ return true
85
+ end
86
+
87
+ false
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,34 @@
1
+ module Blockbuster
2
+ # extracts files from gzipped tarballs
3
+ module Extractor
4
+ def extract_cassettes
5
+ return unless File.exist?(file_path)
6
+ File.open(file_path, 'rb') do |file|
7
+ Zlib::GzipReader.wrap(file) do |gz|
8
+ Gem::Package::TarReader.new(gz) do |tar|
9
+ tar.each do |entry|
10
+ untar_file(entry) if entry.file?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def untar_file(entry)
18
+ contents = entry.read
19
+ @comparator.add(entry.full_name, Digest::MD5.hexdigest(contents), file_name)
20
+
21
+ save_to_disk(entry, contents) unless configuration.local_mode
22
+ end
23
+
24
+ def save_to_disk(entry, contents)
25
+ destination = File.join configuration.test_directory, entry.full_name
26
+
27
+ FileUtils.mkdir_p(File.dirname(destination))
28
+ File.open(destination, 'wb') do |cass|
29
+ cass.write(contents)
30
+ end
31
+ File.chmod(entry.header.mode, destination)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ module Blockbuster
2
+ # file helper methods
3
+ module FileHelpers
4
+ def file_digest(file)
5
+ Digest::MD5.file(file).hexdigest
6
+ end
7
+
8
+ def tar_digest(content)
9
+ Digest::MD5.hexdigest(content)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Blockbuster
2
+ # helpers for outputting to STDOUT
3
+ module OutputHelpers
4
+ def silent_puts(msg)
5
+ puts "[Blockbuster] #{msg}" unless configuration.silent?
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ module Blockbuster
2
+ # pure ruby implmentation of tar gzip and diff
3
+ module Packager
4
+ def create_cassette_file
5
+ FileUtils.rm(file_path) if File.exist?(file_path)
6
+ File.open(target_path, 'wb') do |file|
7
+ Zlib::GzipWriter.wrap(file) do |gz|
8
+ Gem::Package::TarWriter.new(gz) do |tar|
9
+ configuration.cassette_files.each do |cass|
10
+ tar_file(tar, cass)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def tar_file(tar, file)
18
+ rel_path = configuration.key_from_path(file)
19
+
20
+ if configuration.deltas_enabled?
21
+ return unless @comparator.edited?(rel_path)
22
+ end
23
+
24
+ write_to_disk(tar, file)
25
+ end
26
+
27
+ def write_to_disk(tar, file)
28
+ mode = File.stat(file).mode
29
+ rel_path = configuration.key_from_path(file)
30
+
31
+ if File.directory?(file)
32
+ tar.mkdir rel_path, mode
33
+ else
34
+ tar.add_file_simple rel_path, mode, File.size(file) do |io|
35
+ File.open(file, 'rb') { |f| io.write f.read }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,98 @@
1
+ module Blockbuster
2
+ # Manages blockbuster configuration
3
+ class Configuration
4
+ MASTER_TAR_FILE = 'vcr_cassettes'.freeze
5
+ CASSETTE_DIRECTORY = 'cassettes'.freeze
6
+ TEST_DIRECTORY = 'test'.freeze
7
+ WIPE_CASSETTE_DIR = false
8
+ LOCAL_MODE = 'local'.freeze
9
+ SILENT = false
10
+ ENABLE_DELTAS = false
11
+ DELTA_DIRECTORY = 'deltas'.freeze
12
+ CURRENT_DELTA_NAME = 'current_delta'.freeze
13
+ EXTENSION = '.tar.gz'.freeze
14
+
15
+ # @param cassette_directory [String] Name of directory cassette files are stored.
16
+ # Will be stored under the test directory. default: 'casssettes'
17
+ # @param master_tar_file [String] name of gz cassettes file. default: 'vcr_cassettes.tar.gz'
18
+ # @param test_directory [String] path to test directory where cassete file and cassetes will be stored.
19
+ # default: 'test'
20
+ # @param silent [Boolean] Silence all output. default: false
21
+ # @param enable_deltas [Boolean] Enables delta functionality. default: false
22
+ # @param delta_directory [String] Specifies directory for deltas. default: 'deltas'
23
+ # @param current_delta_name [String] Name of the current delta. default: 'current_delta.tar.gz'
24
+ attr_writer :cassette_directory, :master_tar_file, :local_mode, :test_directory, :wipe_cassette_dir, :silent, :enable_deltas, :delta_directory, :current_delta_name
25
+
26
+ def cassette_directory
27
+ @cassette_directory ||= CASSETTE_DIRECTORY
28
+ end
29
+
30
+ def master_tar_file
31
+ @master_tar_file ||= MASTER_TAR_FILE
32
+ end
33
+
34
+ def test_directory
35
+ @test_directory ||= TEST_DIRECTORY
36
+ end
37
+
38
+ def silent
39
+ @silent ||= SILENT
40
+ end
41
+
42
+ alias silent? silent
43
+
44
+ def wipe_cassette_dir
45
+ @wipe_cassette_dir ||= WIPE_CASSETTE_DIR
46
+ end
47
+
48
+ def local_mode
49
+ @local_mode ||= ENV['VCR_MODE'] == LOCAL_MODE
50
+ end
51
+
52
+ def enable_deltas
53
+ @enable_deltas ||= ENABLE_DELTAS
54
+ end
55
+
56
+ alias deltas_enabled? enable_deltas
57
+
58
+ def deltas_disabled?
59
+ !deltas_enabled?
60
+ end
61
+
62
+ def delta_directory
63
+ @delta_directory ||= DELTA_DIRECTORY
64
+ end
65
+
66
+ def full_delta_directory
67
+ File.join(test_directory, delta_directory)
68
+ end
69
+
70
+ def current_delta_name
71
+ @current_delta_name ||= CURRENT_DELTA_NAME
72
+ @current_delta_name += EXTENSION unless @current_delta_name.include?(EXTENSION)
73
+
74
+ @current_delta_name
75
+ end
76
+
77
+ def key_from_path(file)
78
+ path_array = File.dirname(file).split('/')
79
+ idx = path_array.index(cassette_directory)
80
+ path_array[idx..-1].push(File.basename(file)).join('/')
81
+ end
82
+
83
+ def cassette_dir
84
+ File.join(test_directory, cassette_directory)
85
+ end
86
+
87
+ def cassette_files
88
+ Dir.glob("#{cassette_dir}/**/*")
89
+ end
90
+
91
+ def master_tar_file_path
92
+ name = File.join(test_directory, master_tar_file)
93
+ name += EXTENSION unless name.include?(EXTENSION)
94
+
95
+ name
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,76 @@
1
+ module Blockbuster
2
+ # Delta file objects
3
+ class Delta
4
+ include Blockbuster::Extractor
5
+ include Blockbuster::Packager
6
+
7
+ attr_reader :current, :file_name, :configuration
8
+
9
+ # nodoc
10
+ class NotEnabledError < StandardError
11
+ def message
12
+ 'Deltas are not enabled. Please enable them via configuration to use them'
13
+ end
14
+ end
15
+
16
+ INITIALIZING_NUMBER = 101_010_101
17
+
18
+ def self.files(directory)
19
+ Dir.glob("#{directory}/*.tar.gz").sort.map { |file| File.basename(file) }
20
+ end
21
+
22
+ def self.initialize_for_each(comparator, configuration)
23
+ setup_directory(configuration.full_delta_directory)
24
+
25
+ delta_files = files(configuration.full_delta_directory)
26
+
27
+ # If the current delta doesn't exist we want to add it
28
+ current_delta = configuration.current_delta_name
29
+ delta_files << "#{INITIALIZING_NUMBER}_#{current_delta}" unless delta_files.any? { |file| file_name_without_timestamp(file) == current_delta }
30
+
31
+ delta_files.map do |file|
32
+ new(file, comparator, configuration)
33
+ end
34
+ end
35
+
36
+ def self.setup_directory(directory)
37
+ return if Dir.exist?(directory)
38
+
39
+ FileUtils.mkdir_p(directory)
40
+ FileUtils.touch("#{directory}/.keep")
41
+ end
42
+
43
+ def self.file_name_without_timestamp(file_name)
44
+ file_name.sub(/^\d+_/, '')
45
+ end
46
+
47
+ def initialize(file_name, comparator, configuration)
48
+ raise NotEnabledError if configuration.deltas_disabled?
49
+
50
+ @configuration = configuration
51
+ @comparator = comparator
52
+ @file_name = file_name
53
+ @current = true if file_name_without_timestamp == configuration.current_delta_name
54
+ end
55
+
56
+ def file_name_without_timestamp
57
+ self.class.file_name_without_timestamp(file_name)
58
+ end
59
+
60
+ def current
61
+ @current || false
62
+ end
63
+
64
+ alias current? current
65
+
66
+ def file_path
67
+ File.join(configuration.full_delta_directory, file_name)
68
+ end
69
+
70
+ def target_path
71
+ target = [Time.now.to_i, configuration.current_delta_name].join('_')
72
+
73
+ File.join(configuration.full_delta_directory, target)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,50 @@
1
+ module Blockbuster
2
+ # generates an ordered collection of files to extract
3
+ class ExtractionList
4
+ attr_reader :files, :configuration
5
+
6
+ def initialize(comparator, configuration)
7
+ @configuration = configuration
8
+ @comparator = comparator
9
+
10
+ list = [master]
11
+ list << deltas if configuration.deltas_enabled?
12
+
13
+ @files = list.flatten
14
+ end
15
+
16
+ def current_delta
17
+ deltas.find(&:current?)
18
+ end
19
+
20
+ def deltas
21
+ @deltas ||= Delta.initialize_for_each(@comparator, configuration)
22
+ end
23
+
24
+ def extract_cassettes
25
+ files.map(&:extract_cassettes)
26
+ end
27
+
28
+ def master
29
+ @master ||= Master.new(@comparator, configuration)
30
+ end
31
+
32
+ # determines what file representation to return for writing to
33
+ #
34
+ # 1. master when deltas are disabled
35
+ #
36
+ # 2. master when master does not exist. This handles two scenarios:
37
+ # - master does not exist (if this happens, we should always regenerate master)
38
+ # - we want to regenerate master (this assumes some other mechanism is responsible
39
+ # for deleting master to make this work)
40
+ #
41
+ # 3. current_delta
42
+ def primary
43
+ return master if configuration.deltas_disabled?
44
+
45
+ return master unless File.exist?(master.file_path)
46
+
47
+ current_delta
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,63 @@
1
+ module Blockbuster
2
+ # Manages cassette packaging and unpackaging
3
+ class Manager
4
+ include Blockbuster::OutputHelpers
5
+
6
+ attr_accessor :comparator
7
+
8
+ def initialize(instance_configuration = Blockbuster::Configuration.new)
9
+ yield configuration if block_given?
10
+
11
+ @configuration ||= instance_configuration
12
+
13
+ @comparator = Comparator.new(@configuration)
14
+ @extraction_list = ExtractionList.new(@comparator, @configuration)
15
+ end
16
+
17
+ def configuration
18
+ @configuration ||= Blockbuster::Configuration.new
19
+ end
20
+
21
+ # extracts cassettes from a tar.gz file
22
+ #
23
+ # tracks a md5 hash of each file in the tarball
24
+ def rent
25
+ master_file_path = @extraction_list.master.file_path
26
+
27
+ unless File.exist?(master_file_path)
28
+ silent_puts "File does not exist: #{master_file_path}."
29
+ return false
30
+ end
31
+
32
+ remove_existing_cassette_directory if configuration.wipe_cassette_dir
33
+
34
+ silent_puts "Extracting VCR cassettes to #{configuration.cassette_dir}"
35
+
36
+ @extraction_list.extract_cassettes
37
+
38
+ @comparator.store_current_delta_files if configuration.deltas_enabled?
39
+ end
40
+
41
+ # repackages cassettes into a compressed tarball
42
+ def drop_off(force: false)
43
+ if comparator.rewind?(configuration.cassette_files) || force
44
+ silent_puts "Recreating cassette file #{@extraction_list.primary.target_path}"
45
+ @extraction_list.primary.create_cassette_file
46
+ end
47
+ end
48
+
49
+ alias setup rent
50
+ alias teardown drop_off
51
+
52
+ private
53
+
54
+ def remove_existing_cassette_directory
55
+ return if configuration.local_mode
56
+
57
+ dir = configuration.cassette_dir
58
+
59
+ silent_puts "Wiping cassettes directory: #{dir}"
60
+ FileUtils.rm_r(dir) if Dir.exist?(dir)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ module Blockbuster
2
+ # Master file object
3
+ class Master
4
+ include Blockbuster::Extractor
5
+ include Blockbuster::Packager
6
+
7
+ attr_reader :file_name, :configuration
8
+
9
+ def initialize(comparator, configuration)
10
+ @configuration = configuration
11
+ @comparator = comparator
12
+ @file_name = configuration.master_tar_file
13
+ end
14
+
15
+ # read path
16
+ def file_path
17
+ configuration.master_tar_file_path
18
+ end
19
+
20
+ # write path (master will always write to the same file name)
21
+ def target_path
22
+ file_path
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Blockbuster
2
+ VERSION = '0.4.1'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blockbuster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Lukas Eklund
8
+ - Alexander Bergman
9
+ - Hassan Shahid
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2016-08-18 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.10'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.10'
29
+ - !ruby/object:Gem::Dependency
30
+ name: bundler-audit
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: minitest
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: mocha
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: pry
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: rake
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '10.0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: '10.0'
99
+ - !ruby/object:Gem::Dependency
100
+ name: rubocop
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: '0.37'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '0.37'
113
+ description:
114
+ email:
115
+ - leklund@fastly.com
116
+ - alexander@fastly.com
117
+ - hassan@fastly.com
118
+ executables: []
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - ".gitignore"
123
+ - ".rubocop.yml"
124
+ - ".ruby-version"
125
+ - ".travis.yml"
126
+ - CHANGELOG.md
127
+ - CODE_OF_CONDUCT.md
128
+ - Gemfile
129
+ - LICENSE.txt
130
+ - README.md
131
+ - Rakefile
132
+ - bin/console
133
+ - bin/setup
134
+ - blockbuster.gemspec
135
+ - lib/blockbuster.rb
136
+ - lib/blockbuster/comparator.rb
137
+ - lib/blockbuster/concerns/extractor.rb
138
+ - lib/blockbuster/concerns/file_helpers.rb
139
+ - lib/blockbuster/concerns/output_helpers.rb
140
+ - lib/blockbuster/concerns/packager.rb
141
+ - lib/blockbuster/configuration.rb
142
+ - lib/blockbuster/delta.rb
143
+ - lib/blockbuster/extraction_list.rb
144
+ - lib/blockbuster/manager.rb
145
+ - lib/blockbuster/master.rb
146
+ - lib/blockbuster/version.rb
147
+ homepage: https://github.com/fastly/blockbuster
148
+ licenses:
149
+ - MIT
150
+ metadata: {}
151
+ post_install_message:
152
+ rdoc_options: []
153
+ require_paths:
154
+ - lib
155
+ required_ruby_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ requirements: []
166
+ rubyforge_project:
167
+ rubygems_version: 2.5.1
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: Packaging VCR cassettes for git since 2016
171
+ test_files: []