file-dependencies 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 +15 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +52 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/bin/file-deps +5 -0
- data/file-dependencies.gemspec +28 -0
- data/lib/file-dependencies.rb +39 -0
- data/lib/file-dependencies/archive.rb +84 -0
- data/lib/file-dependencies/file.rb +81 -0
- data/lib/file-dependencies/gem.rb +13 -0
- data/lib/file-dependencies/rake_tasks.rb +11 -0
- data/lib/file-dependencies/rubygems_plugin.rb +3 -0
- data/lib/file-dependencies/version.rb +3 -0
- data/spec/archive_spec.rb +109 -0
- data/spec/file-dependencies_spec.rb +103 -0
- data/spec/file_spec.rb +146 -0
- data/spec/spec_assist.rb +38 -0
- data/spec/spec_helper.rb +7 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OGQzMzBmNzkxOTg4MDdiYzkxNTRlMWZkNjUxZDVlYjNkMDBkZTg1Nw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTUyZTRmMjIyZGM4ZjM4ZjdjODcyODc4NjQ0NTBjZmFkZDBjNmIyYg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDBjYjlkNDQ3NzI5OGFlOGY1ZjI1NDc0MmNmMDAxNjE0NDU0ZmM5NjQ3ODY1
|
10
|
+
M2Q3ZTE1ZjMwYmY5Y2IzZDhkMzNmNmNlZDYzYTViM2RlNTIwMGRiYTlhOTQ5
|
11
|
+
MzBkN2E0YjlmMTU3YjJkZThmMmM2MzFkZjhhMTZmMTQ3NzYxZjE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NThmZmM1YjA4MmI2ZGZhNDgxNzZlMTFlYTJlMWQ5ZDM1M2FhNzU5ZDNjYTNl
|
14
|
+
YWI5YzY0N2VhNWUxOWU1YjU4ZjU3N2QzZTljNjU1YjY0YjFhMGQzOWJkMjE5
|
15
|
+
ZGY3YjRhYjY3MTNiNDIxZDhhY2FjMzYyOTkyZTI1ZmM0OTRjNWE=
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Let's not argue over this...
|
2
|
+
StringLiterals:
|
3
|
+
Enabled: false
|
4
|
+
|
5
|
+
# I can't find a reason for raise vs fail.
|
6
|
+
SignalException:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
# I can't find a reason to prefer 'map' when 'collect' is what I mean.
|
10
|
+
# I'm collecting things from a list. Maybe someone can help me understand the
|
11
|
+
# semantics here.
|
12
|
+
CollectionMethods:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
# Why do you even *SEE* trailing whitespace? Because your editor was
|
16
|
+
# misconfigured to highlight trailing whitespace, right? Maybe turn that off?
|
17
|
+
# ;)
|
18
|
+
TrailingWhitespace:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
# Line length is another weird problem that somehow in the past 40 years of
|
22
|
+
# computing we don't seem to have solved. It's a display problem :(
|
23
|
+
LineLength:
|
24
|
+
Max: 9000
|
25
|
+
|
26
|
+
# %w() vs [ "x", "y", ... ]
|
27
|
+
# The complaint is on lib/pleaserun/detector.rb's map of OS=>Runner,
|
28
|
+
# i'll ignore it.
|
29
|
+
WordArray:
|
30
|
+
MinSize: 5
|
31
|
+
|
32
|
+
# A 20-line method isn't too bad.
|
33
|
+
MethodLength:
|
34
|
+
Max: 20
|
35
|
+
|
36
|
+
# Hash rockets (=>) forever. Why? Not all of my hash keys are static symbols.
|
37
|
+
HashSyntax:
|
38
|
+
EnforcedStyle: hash_rockets
|
39
|
+
|
40
|
+
# I prefer explicit return. It makes it clear in the code that the
|
41
|
+
# code author intended to return a value from a method.
|
42
|
+
RedundantReturn:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
# My view on a readable case statement seems to disagree with
|
46
|
+
# what rubocop wants and it doesn't let me configure it other than
|
47
|
+
# enable/disable.
|
48
|
+
CaseIndentation:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
Style/FileName:
|
52
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/bin/file-deps
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#-*- mode: ruby -*-
|
2
|
+
require File.expand_path('../lib/file-dependencies/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'file-dependencies'
|
6
|
+
s.version = APP_VERSION
|
7
|
+
s.author = 'Richard Pijnenburg'
|
8
|
+
s.email = ['richard.pijnenburg@elasticsearch.com']
|
9
|
+
s.summary = 'manage file dependencies for gems'
|
10
|
+
s.homepage = 'https://github.com/electrical/file-dependencies'
|
11
|
+
|
12
|
+
s.license = 'APACHE 2.0'
|
13
|
+
|
14
|
+
s.executable = 'file-deps'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($ORS)
|
17
|
+
|
18
|
+
s.description = 'manage file dependencies for gems'
|
19
|
+
|
20
|
+
s.add_runtime_dependency 'minitar'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake', '~> 10.2'
|
23
|
+
s.add_development_dependency 'rspec'
|
24
|
+
s.add_development_dependency 'stud'
|
25
|
+
s.add_development_dependency 'webmock'
|
26
|
+
end
|
27
|
+
|
28
|
+
# vim: syntax=Ruby
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'file-dependencies/file'
|
2
|
+
require 'file-dependencies/archive'
|
3
|
+
require 'json'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'fileutils'
|
6
|
+
# :nodoc:
|
7
|
+
module FileDependencies
|
8
|
+
def process_vendor(dir, target = 'vendor', tmpdir = Dir.tmpdir)
|
9
|
+
vendor_file = ::File.join(dir, 'vendor.json')
|
10
|
+
if ::File.exist?(vendor_file)
|
11
|
+
vendor_file_content = IO.read(vendor_file)
|
12
|
+
file_list = JSON.load(vendor_file_content)
|
13
|
+
FileDependencies.download(file_list, ::File.join(dir, target), tmpdir)
|
14
|
+
else
|
15
|
+
puts "vendor.json not found, looked for the file at #{vendor_file}"
|
16
|
+
end
|
17
|
+
end # def process_vendor
|
18
|
+
module_function :process_vendor
|
19
|
+
|
20
|
+
def download(files, target, tmpdir)
|
21
|
+
FileUtils.mkdir_p(target) unless ::File.directory?(target)
|
22
|
+
files.each do |file|
|
23
|
+
target = ::File.join(target, file['target']) if !file['target'].nil?
|
24
|
+
download = FileDependencies::File.fetch_file(file['url'], file['sha1'], tmpdir)
|
25
|
+
if (res = download.match(/(\S+?)(\.tar\.gz|\.tgz)/))
|
26
|
+
prefix = res.captures.first.gsub("#{tmpdir}/", '')
|
27
|
+
FileDependencies::Archive.untar(download) do |entry|
|
28
|
+
next unless (out = FileDependencies::Archive.eval_file(entry, file['extract'], prefix))
|
29
|
+
::File.join(target, out)
|
30
|
+
end
|
31
|
+
elsif download =~ /.gz/
|
32
|
+
FileDependencies::Archive.ungzip(download, target)
|
33
|
+
else
|
34
|
+
FileUtils.mv(download, ::File.join(target, download.split("/").last))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end # def download
|
38
|
+
module_function :download
|
39
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "archive/tar/minitar"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module FileDependencies
|
5
|
+
# :nodoc:
|
6
|
+
module Archive
|
7
|
+
def ungzip(file, outdir)
|
8
|
+
output = ::File.join(outdir, file.gsub('.gz', '').split("/").last)
|
9
|
+
tgz = Zlib::GzipReader.new(::File.open(file))
|
10
|
+
begin
|
11
|
+
::File.open(output, "w") do |out|
|
12
|
+
IO.copy_stream(tgz, out)
|
13
|
+
end
|
14
|
+
::File.unlink(file)
|
15
|
+
rescue
|
16
|
+
::File.unlink(output) if ::File.file?(output)
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
tgz.close
|
20
|
+
end
|
21
|
+
module_function :ungzip
|
22
|
+
|
23
|
+
def untar(tarball, &block)
|
24
|
+
tgz = Zlib::GzipReader.new(::File.open(tarball))
|
25
|
+
# Pull out typesdb
|
26
|
+
tar = ::Archive::Tar::Minitar::Input.open(tgz)
|
27
|
+
tar.each do |entry|
|
28
|
+
path = block.call(entry)
|
29
|
+
next if path.nil?
|
30
|
+
parent = ::File.dirname(path)
|
31
|
+
|
32
|
+
FileUtils.mkdir_p(parent) unless ::File.directory?(parent)
|
33
|
+
|
34
|
+
# Skip this file if the output file is the same size
|
35
|
+
if entry.directory?
|
36
|
+
FileUtils.mkdir_p(path) unless ::File.directory?(path)
|
37
|
+
else
|
38
|
+
entry_mode = entry.instance_eval { @mode } & 0777
|
39
|
+
if ::File.exist?(path)
|
40
|
+
stat = ::File.stat(path)
|
41
|
+
# TODO(sissel): Submit a patch to archive-tar-minitar upstream to
|
42
|
+
# expose headers in the entry.
|
43
|
+
entry_size = entry.instance_eval { @size }
|
44
|
+
# If file sizes are same, skip writing.
|
45
|
+
next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
|
46
|
+
end
|
47
|
+
puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}" if $DEBUG
|
48
|
+
::File.open(path, "w") do |fd|
|
49
|
+
# eof? check lets us skip empty files. Necessary because the API provided by
|
50
|
+
# Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
|
51
|
+
# IO object. Something about empty files in this EntryStream causes
|
52
|
+
# IO.copy_stream to throw "can't convert nil into String" on JRuby
|
53
|
+
# TODO(sissel): File a bug about this.
|
54
|
+
until entry.eof?
|
55
|
+
chunk = entry.read(16_384)
|
56
|
+
fd.write(chunk)
|
57
|
+
end
|
58
|
+
# IO.copy_stream(entry, fd)
|
59
|
+
end
|
60
|
+
::File.chmod(entry_mode, path)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
tar.close
|
64
|
+
::File.unlink(tarball) if ::File.file?(tarball)
|
65
|
+
end # def untar
|
66
|
+
module_function :untar
|
67
|
+
|
68
|
+
def eval_file(entry, files, prefix)
|
69
|
+
# Avoid tarball headers
|
70
|
+
return false if entry.full_name =~ /PaxHeaders/
|
71
|
+
return entry.full_name.gsub(prefix, '') if files.nil?
|
72
|
+
|
73
|
+
if files.is_a?(Array)
|
74
|
+
# Extract specific files given
|
75
|
+
return false unless files.include?(entry.full_name.gsub(prefix, ''))
|
76
|
+
entry.full_name.split("/").last
|
77
|
+
elsif files.is_a?(String)
|
78
|
+
return false unless entry.full_name =~ Regexp.new(files)
|
79
|
+
entry.full_name.split("/").last
|
80
|
+
end
|
81
|
+
end
|
82
|
+
module_function :eval_file
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
require "net/http"
|
3
|
+
require "uri"
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module FileDependencies
|
8
|
+
# :nodoc:
|
9
|
+
module File
|
10
|
+
SHA1_REGEXP = /(\b[0-9a-f]{40}\b)/
|
11
|
+
|
12
|
+
def fetch_sha1(remote_sha1)
|
13
|
+
unless URI(remote_sha1.to_s).scheme.nil?
|
14
|
+
file = download(remote_sha1, Dir.tmpdir)
|
15
|
+
sha1 = IO.read(file).gsub("\n", '')
|
16
|
+
else
|
17
|
+
sha1 = remote_sha1
|
18
|
+
end
|
19
|
+
raise("invalid SHA1 signature. Got '#{sha1}'") unless sha1.match(SHA1_REGEXP)
|
20
|
+
sha1
|
21
|
+
end
|
22
|
+
module_function :fetch_sha1
|
23
|
+
|
24
|
+
def validate_sha1(local_file, remote_sha1)
|
25
|
+
return true if remote_sha1 == 'none'
|
26
|
+
sha1 = fetch_sha1(remote_sha1)
|
27
|
+
local_sha1 = calc_sha1(local_file)
|
28
|
+
|
29
|
+
raise("SHA1 did not match. Expected #{sha1} but computed #{local_sha1}") if sha1 != local_sha1
|
30
|
+
true
|
31
|
+
end # def validate_sha1
|
32
|
+
module_function :validate_sha1
|
33
|
+
|
34
|
+
def calc_sha1(path)
|
35
|
+
Digest::SHA1.file(path).hexdigest
|
36
|
+
end # def calc__sha1
|
37
|
+
module_function :calc_sha1
|
38
|
+
|
39
|
+
def fetch_file(url, sha1, target)
|
40
|
+
puts "Downloading #{url}" if $DEBUG
|
41
|
+
|
42
|
+
file = download(url, target)
|
43
|
+
return file if validate_sha1(file, sha1)
|
44
|
+
end # def fetch_file
|
45
|
+
module_function :fetch_file
|
46
|
+
|
47
|
+
def download(url, target)
|
48
|
+
uri = URI(url)
|
49
|
+
output = "#{target}/#{::File.basename(uri.path)}"
|
50
|
+
tmp = "#{output}.tmp"
|
51
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
|
52
|
+
request = Net::HTTP::Get.new(uri.path)
|
53
|
+
http.request(request) do |response|
|
54
|
+
raise("HTTP fetch failed for #{url}. #{response}") unless [200, 301].include?(response.code.to_i)
|
55
|
+
size = (response["content-length"].to_i || -1).to_f
|
56
|
+
count = 0
|
57
|
+
::File.open(tmp, "w") do |fd|
|
58
|
+
response.read_body do |chunk|
|
59
|
+
fd.write(chunk)
|
60
|
+
if size > 0 && $stdout.tty?
|
61
|
+
count += chunk.bytesize
|
62
|
+
$stdout.write(sprintf("\r%0.2f%%", count / size * 100))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
$stdout.write("\r \r") if $stdout.tty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
::File.rename(tmp, output)
|
71
|
+
|
72
|
+
return output
|
73
|
+
rescue SocketError => e
|
74
|
+
puts "Failure while downloading #{url}: #{e}"
|
75
|
+
raise
|
76
|
+
ensure
|
77
|
+
::File.unlink(tmp) if ::File.exist?(tmp)
|
78
|
+
end # def download
|
79
|
+
module_function :download
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'json'
|
2
|
+
module FileDependencies
|
3
|
+
# :nodoc:
|
4
|
+
module Gem
|
5
|
+
def hook
|
6
|
+
Gem.post_install do |gem_installer|
|
7
|
+
next if ENV['VENDOR_SKIP'] == 'true'
|
8
|
+
FileDependencies.process_vendor(gem_installer.gem_dir)
|
9
|
+
end
|
10
|
+
end # def hook
|
11
|
+
module_function :hook
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'file-dependencies/archive'
|
4
|
+
|
5
|
+
describe FileDependencies::Archive do
|
6
|
+
|
7
|
+
describe ".ungzip" do
|
8
|
+
|
9
|
+
after do
|
10
|
+
FileUtils.remove_entry_secure(gzipfile) if ::File.exist?(gzipfile)
|
11
|
+
FileUtils.remove_entry_secure(expected_file)
|
12
|
+
FileUtils.remove_entry_secure(tmpdir)
|
13
|
+
end
|
14
|
+
let(:gzipfile) { Assist.generate_gzip('some_content') }
|
15
|
+
let(:expected_file) { gzipfile.gsub('.gz','') }
|
16
|
+
let(:tmpdir) { Stud::Temporary.directory }
|
17
|
+
|
18
|
+
it 'decompresses a gzip file'do
|
19
|
+
expect { FileDependencies::Archive.ungzip(gzipfile, tmpdir) }.to_not(raise_error)
|
20
|
+
expect(File.exist?(expected_file))
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:expected_file) { Assist.generate_file('some_content') }
|
24
|
+
it 'raises error extracting non gzip file' do
|
25
|
+
expect { FileDependencies::Archive.ungzip(expected_file, tmpdir) }.to(raise_error(Zlib::GzipFile::Error))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".untar" do
|
30
|
+
|
31
|
+
after do
|
32
|
+
FileUtils.remove_entry_secure(tmpdir)
|
33
|
+
FileUtils.remove_entry_secure(file)
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:file) { Assist.generate_file('some_content') }
|
37
|
+
let(:tarball) { Assist.generate_tarball({'some/file' => 'content1', 'some/other/file' => 'content2', 'other' => 'content3'}) }
|
38
|
+
let(:tmpdir) { Stud::Temporary.directory }
|
39
|
+
|
40
|
+
it 'extracts a full tarball' do
|
41
|
+
entries = ['some/file', 'some/other/file', 'other' ]
|
42
|
+
|
43
|
+
FileDependencies::Archive.untar(tarball) do |entry|
|
44
|
+
::File.join(tmpdir, entry.full_name)
|
45
|
+
end
|
46
|
+
found_files = Dir.glob(File.join(tmpdir, '**', '*')).reject { |entry| File.directory?(entry) }.sort
|
47
|
+
expected_files = entries.map { |k| ::File.join(tmpdir, k) }.sort
|
48
|
+
expect(expected_files).to(eq(found_files))
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'extracts some files' do
|
52
|
+
entries = ['some/file', 'some/other/file']
|
53
|
+
|
54
|
+
FileDependencies::Archive.untar(tarball) do |entry|
|
55
|
+
if entries.include?(entry.full_name)
|
56
|
+
::File.join(tmpdir, entry.full_name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
found_files = Dir.glob(File.join(tmpdir, '**', '*')).reject { |entry| File.directory?(entry) }.sort
|
60
|
+
expected_files = entries.map { |k| "#{tmpdir}/#{k}" }.sort
|
61
|
+
expect(expected_files).to(eq(found_files))
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'raises error when invalid file is provided' do
|
65
|
+
expect do
|
66
|
+
FileDependencies::Archive.untar(file) do |entry|
|
67
|
+
::File.join(tmpdir, entry.full_name)
|
68
|
+
end
|
69
|
+
end.to(raise_error(Zlib::GzipFile::Error))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe ".eval_file" do
|
74
|
+
|
75
|
+
# Hack to implement the full_name part
|
76
|
+
class ::String
|
77
|
+
def full_name
|
78
|
+
return self
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:entries) { [ 'sometar/PaxHeaders', 'sometar/some/dir/PaxHeaders', 'sometar/some/dir/somefile', 'sometar/somefile', 'sometar/some/other/file', 'sometar/some/jars/file1.jar', 'sometar/some/jars/file2.jar', 'sometar/other/jars/file3.jar' ]}
|
83
|
+
let(:prefix) { 'sometar' }
|
84
|
+
|
85
|
+
let(:extract1) { '.jars' } #wildcard
|
86
|
+
let(:expect1) { [ 'file1.jar', 'file2.jar', 'file3.jar'] }
|
87
|
+
let(:extract2) { ['/some/other/file', '/somefile', '/other/jars/file3.jar' ]}
|
88
|
+
let(:expect2) { ['file', 'somefile', 'file3.jar' ]}
|
89
|
+
let(:extract3) { }
|
90
|
+
let(:expect3) { [ '/some/dir/somefile', '/somefile', '/some/other/file', '/some/jars/file1.jar', '/some/jars/file2.jar', '/other/jars/file3.jar' ] }
|
91
|
+
|
92
|
+
it 'returns all files based on a wildcard' do
|
93
|
+
filelist = entries.map { |entry| FileDependencies::Archive.eval_file(entry, extract1, prefix) }
|
94
|
+
expect(filelist.reject{ |v| v == false}.sort).to(eq(expect1.sort))
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns all files based on an array' do
|
98
|
+
filelist = entries.map { |entry| FileDependencies::Archive.eval_file(entry, extract2, prefix) }
|
99
|
+
expect(filelist.reject{ |v| v == false}.sort).to(eq(expect2.sort))
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns all files when no extracted files are given' do
|
103
|
+
filelist = entries.map { |entry| FileDependencies::Archive.eval_file(entry, extract3, prefix) }
|
104
|
+
expect(filelist.reject{ |v| v == false}.sort).to(eq(expect3.sort))
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'json'
|
4
|
+
require 'file-dependencies'
|
5
|
+
|
6
|
+
include WebMock::API
|
7
|
+
describe FileDependencies do
|
8
|
+
|
9
|
+
describe '.download' do
|
10
|
+
after do
|
11
|
+
[tmpdir, target, file1, file2, file3, file4].each do |entry|
|
12
|
+
FileUtils.remove_entry_secure(entry)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:tmpdir) { Stud::Temporary.directory }
|
17
|
+
let(:target) { Stud::Temporary.directory }
|
18
|
+
|
19
|
+
let(:file1) { Assist.generate_tarball({'some/file' => 'content1', 'some/other/file' => 'content2', 'other' => 'content3'}) }
|
20
|
+
let(:file2) { Assist.generate_file('some_content') }
|
21
|
+
let(:file3) { Assist.generate_gzip('some_content_for_gzip') }
|
22
|
+
let(:file4) { Assist.generate_tarball({'jars/some.jar' => 'content10', 'jars/someother.jar' => 'content11'}) }
|
23
|
+
|
24
|
+
let(:sha1) { FileDependencies::File.calc_sha1(file1) }
|
25
|
+
let(:sha2) { FileDependencies::File.calc_sha1(file2) }
|
26
|
+
let(:sha3) { FileDependencies::File.calc_sha1(file3) }
|
27
|
+
let(:sha4) { FileDependencies::File.calc_sha1(file4) }
|
28
|
+
|
29
|
+
let(:url1) { 'http://www.example.com/somefile1.tar.gz' }
|
30
|
+
let(:url2) { 'http://www.example.com/somefile2.txt' }
|
31
|
+
let(:url3) { 'http://www.example.com/somefile3.gz' }
|
32
|
+
let(:url4) { 'http://www.example.com/somefile4.tar.gz' }
|
33
|
+
|
34
|
+
let(:entries) { ['somefile2.txt', 'somefile3', 'some/file', 'some/other/file', 'other', 'jars/some.jar', 'jars/someother.jar'] }
|
35
|
+
|
36
|
+
let(:files) { [ { 'url' => url1, 'sha1' => sha1 }, { 'url' => url2, 'sha1' => sha2 }, { 'url' => url3, 'sha1' => sha3 }, { 'url' => url4, 'sha1' => sha4, 'extract' => '.jar', 'target' => 'jars' } ] }
|
37
|
+
|
38
|
+
it 'processes file list' do
|
39
|
+
stub_request(:get, url1).to_return(:body => File.new(file1), :status => 200)
|
40
|
+
stub_request(:get, url2).to_return(:body => File.new(file2), :status => 200)
|
41
|
+
stub_request(:get, url3).to_return(:body => File.new(file3), :status => 200)
|
42
|
+
stub_request(:get, url4).to_return(:body => File.new(file4), :status => 200)
|
43
|
+
|
44
|
+
# we should not have any errors
|
45
|
+
expect{ FileDependencies.download(files, target, tmpdir) }.to_not(raise_error)
|
46
|
+
|
47
|
+
# check if we got all the expected files
|
48
|
+
found_files = Dir.glob(File.join(target, '**', '*')).reject { |entry| File.directory?(entry) }.sort
|
49
|
+
expect_files = entries.map { |k| ::File.join(target, k) }.sort
|
50
|
+
expect(found_files).to(eq(expect_files))
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '.process_vendor' do
|
56
|
+
after do
|
57
|
+
[tmpdir, target, file1, file2, file3, file4].each do |entry|
|
58
|
+
FileUtils.remove_entry_secure(entry)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:tmpdir) { Stud::Temporary.directory }
|
63
|
+
let(:target) { Stud::Temporary.directory }
|
64
|
+
|
65
|
+
let(:file1) { Assist.generate_tarball({'some/file' => 'content1', 'some/other/file' => 'content2', 'other' => 'content3'}) }
|
66
|
+
let(:file2) { Assist.generate_file('some_content') }
|
67
|
+
let(:file3) { Assist.generate_gzip('some_content_for_gzip') }
|
68
|
+
let(:file4) { Assist.generate_tarball({'jars/some.jar' => 'content10', 'jars/someother.jar' => 'content11'}) }
|
69
|
+
|
70
|
+
let(:sha1) { FileDependencies::File.calc_sha1(file1) }
|
71
|
+
let(:sha2) { FileDependencies::File.calc_sha1(file2) }
|
72
|
+
let(:sha3) { FileDependencies::File.calc_sha1(file3) }
|
73
|
+
let(:sha4) { FileDependencies::File.calc_sha1(file4) }
|
74
|
+
|
75
|
+
let(:url1) { 'http://www.example.com/somefile1.tar.gz' }
|
76
|
+
let(:url2) { 'http://www.example.com/somefile2.txt' }
|
77
|
+
let(:url3) { 'http://www.example.com/somefile3.gz' }
|
78
|
+
let(:url4) { 'http://www.example.com/somefile4.tar.gz' }
|
79
|
+
|
80
|
+
let(:files) { [ { 'url' => url1, 'sha1' => sha1 }, { 'url' => url2, 'sha1' => sha2 }, { 'url' => url3, 'sha1' => sha3 }, { 'url' => url4, 'sha1' => sha4, 'extract' => '.jar', 'target' => 'jars' } ].to_json }
|
81
|
+
let(:vendorfile) { File.write(File.join(target, 'vendor.json'), files) }
|
82
|
+
let(:entries) { ['somefile2.txt', 'somefile3', 'some/file', 'some/other/file', 'other', 'jars/some.jar', 'jars/someother.jar'] }
|
83
|
+
|
84
|
+
it 'processes the vendor.json file' do
|
85
|
+
stub_request(:get, url1).to_return(:body => File.new(file1), :status => 200)
|
86
|
+
stub_request(:get, url2).to_return(:body => File.new(file2), :status => 200)
|
87
|
+
stub_request(:get, url3).to_return(:body => File.new(file3), :status => 200)
|
88
|
+
stub_request(:get, url4).to_return(:body => File.new(file4), :status => 200)
|
89
|
+
File.write(File.join(target, 'vendor.json'), files)
|
90
|
+
|
91
|
+
# we should not have any errors
|
92
|
+
expect{ FileDependencies.process_vendor(target, 'vendor', tmpdir) }.to_not(raise_error)
|
93
|
+
|
94
|
+
# check if we got all the expected files
|
95
|
+
found_files = Dir.glob(File.join(target, 'vendor', '**', '*')).reject { |entry| File.directory?(entry) }.sort
|
96
|
+
expect_files = entries.map { |k| ::File.join(target, 'vendor', k) }.sort
|
97
|
+
expect(found_files).to(eq(expect_files))
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/spec/file_spec.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'file-dependencies/file'
|
4
|
+
include WebMock::API
|
5
|
+
describe FileDependencies::File do
|
6
|
+
|
7
|
+
|
8
|
+
describe ".calc_sha1" do
|
9
|
+
|
10
|
+
after do
|
11
|
+
::File.unlink(file)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:file) { Assist.generate_file('some_content') }
|
15
|
+
it 'gives back correct sha1 value' do
|
16
|
+
expect(FileDependencies::File.calc_sha1(file)).to(eq('778164c23fae5935176254d2550619cba8abc262'))
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'raises an error when the file doesnt exist' do
|
20
|
+
expect { FileDependencies::File.calc_sha1('dont_exist')}.to(raise_error(Errno::ENOENT))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".validate_sha1" do
|
25
|
+
|
26
|
+
after do
|
27
|
+
::File.unlink(file)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "with a sha1 string" do
|
31
|
+
|
32
|
+
let(:file) { Assist.generate_file('some_content') }
|
33
|
+
it 'returns true when sha1 comparing is valid' do
|
34
|
+
remote_sha1 = '778164c23fae5935176254d2550619cba8abc262'
|
35
|
+
expect(FileDependencies::File.validate_sha1(file, remote_sha1)).to(be_truthy)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'raises error when invalid' do
|
39
|
+
remote_sha1 = '778164c23fae5935176254d2550619cba8abc263'
|
40
|
+
expect { FileDependencies::File.validate_sha1(file, remote_sha1) }.to(raise_error(RuntimeError))
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "with no validation" do
|
46
|
+
|
47
|
+
let(:file) { Assist.generate_file('some_content') }
|
48
|
+
it 'always returns true' do
|
49
|
+
remote_sha1 = 'none'
|
50
|
+
expect(FileDependencies::File.validate_sha1(file, remote_sha1)).to(be_truthy)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "with a remote file" do
|
56
|
+
|
57
|
+
after do
|
58
|
+
::File.unlink(sha1file)
|
59
|
+
::File.unlink(sha1file2)
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:file) { Assist.generate_file('some_content') }
|
63
|
+
let(:sha1file) { Assist.generate_file('778164c23fae5935176254d2550619cba8abc262') }
|
64
|
+
let(:sha1file2) { Assist.generate_file('778164c23fae5935176254d2550619cba8abc263') }
|
65
|
+
let(:remote_sha1) { 'http://example.com/sha1file' }
|
66
|
+
|
67
|
+
it 'returns true when sha1 comparing is valid' do
|
68
|
+
expect(FileDependencies::File).to receive(:download).with(remote_sha1, Dir.tmpdir).and_return(sha1file)
|
69
|
+
expect(FileDependencies::File.validate_sha1(file, remote_sha1)).to(be_truthy)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'raises error when invalid' do
|
73
|
+
expect(FileDependencies::File).to receive(:download).with(remote_sha1, Dir.tmpdir).and_return(sha1file2)
|
74
|
+
expect { FileDependencies::File.validate_sha1(file, remote_sha1) }.to(raise_error)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".fetch_sha1" do
|
81
|
+
|
82
|
+
describe "With a sha1 string" do
|
83
|
+
let (:remote_sha1) { '778164c23fae5935176254d2550619cba8abc262' }
|
84
|
+
it 'returns sha1 string when valid' do
|
85
|
+
expect(FileDependencies::File.fetch_sha1(remote_sha1)).to(eq(remote_sha1))
|
86
|
+
end
|
87
|
+
|
88
|
+
let(:faulty_remote_sha1) { '778164c23fae5935176254d2550619cba8abc2' }
|
89
|
+
it 'raises error when sha1 string is invalid' do
|
90
|
+
expect { FileDependencies::File.fetch_sha1(faulty_remote_sha1) }.to(raise_error)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "with a remote sha1" do
|
95
|
+
|
96
|
+
after do
|
97
|
+
::File.unlink(sha1file)
|
98
|
+
::File.unlink(sha1file1)
|
99
|
+
end
|
100
|
+
|
101
|
+
let(:sha1file) { Assist.generate_file('778164c23fae5935176254d2550619cba8abc262') }
|
102
|
+
let(:sha1file1) { Assist.generate_file('778164c23fae5935176254d2550619cba8abc26') }
|
103
|
+
let(:remote_sha1) { 'http://example.com/sha1file' }
|
104
|
+
|
105
|
+
it 'returns sha1 string when valid' do
|
106
|
+
expect(FileDependencies::File).to receive(:download).with(remote_sha1, Dir.tmpdir).and_return(sha1file)
|
107
|
+
expect(FileDependencies::File.fetch_sha1(remote_sha1)).to(eq('778164c23fae5935176254d2550619cba8abc262'))
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'raises error when sha1 string is invalid' do
|
111
|
+
expect(FileDependencies::File).to receive(:download).with(remote_sha1, Dir.tmpdir).and_return(sha1file1)
|
112
|
+
expect { FileDependencies::File.fetch_sha1(remote_sha1) }.to(raise_error(RuntimeError))
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe ".download" do
|
119
|
+
|
120
|
+
after do
|
121
|
+
FileUtils.remove_entry_secure(tmpdir)
|
122
|
+
FileUtils.remove_entry_secure(file)
|
123
|
+
end
|
124
|
+
|
125
|
+
let(:tmpdir) { Stud::Temporary.directory }
|
126
|
+
url = 'http://www.example.com/somefile'
|
127
|
+
let(:file) { Assist.generate_file('778164c23fae5935176254d2550619cba8abc262') }
|
128
|
+
|
129
|
+
it 'returns the path to the file downloaded' do
|
130
|
+
stub_request(:get, url).to_return(:body => File.new(file), :status => 200)
|
131
|
+
expect(FileDependencies::File.download(url, tmpdir)).to(eq(File.join(tmpdir, 'somefile')))
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raises an error if the file does not exist' do
|
135
|
+
stub_request(:get, url).to_return(:status => 404)
|
136
|
+
expect { FileDependencies::File.download(url, tmpdir) }.to(raise_error(RuntimeError))
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'raises an error on timeout' do
|
140
|
+
stub_request(:get, url).to_timeout
|
141
|
+
expect { FileDependencies::File.download(url, tmpdir) }.to(raise_error(Timeout::Error))
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
data/spec/spec_assist.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'stud/temporary'
|
2
|
+
|
3
|
+
module Assist
|
4
|
+
|
5
|
+
def self.generate_tarball(files)
|
6
|
+
tarpath = "#{Stud::Temporary.pathname}.tar.gz"
|
7
|
+
tarfile = File.new(tarpath, "wb")
|
8
|
+
gz = Zlib::GzipWriter.new(tarfile, Zlib::BEST_COMPRESSION)
|
9
|
+
tar = Archive::Tar::Minitar::Output.new(gz)
|
10
|
+
files.each do |path, value|
|
11
|
+
opts = {
|
12
|
+
:size => value.bytesize,
|
13
|
+
:mode => 0666,
|
14
|
+
:mtime => Time.new
|
15
|
+
}
|
16
|
+
tar.tar.add_file_simple(path, opts) do |io|
|
17
|
+
io.write(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
tar.close
|
21
|
+
tarpath
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.generate_gzip(content)
|
25
|
+
|
26
|
+
file = "#{Stud::Temporary.pathname}.gz"
|
27
|
+
Zlib::GzipWriter.open(file) do |gz|
|
28
|
+
gz.write(content)
|
29
|
+
end
|
30
|
+
file
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.generate_file(content)
|
34
|
+
file = Stud::Temporary.pathname
|
35
|
+
File.write(file, content)
|
36
|
+
file
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: file-dependencies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Pijnenburg
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitar
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: stud
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: manage file dependencies for gems
|
84
|
+
email:
|
85
|
+
- richard.pijnenburg@elasticsearch.com
|
86
|
+
executables:
|
87
|
+
- file-deps
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- .rubocop.yml
|
93
|
+
- .travis.yml
|
94
|
+
- Gemfile
|
95
|
+
- bin/file-deps
|
96
|
+
- file-dependencies.gemspec
|
97
|
+
- lib/file-dependencies.rb
|
98
|
+
- lib/file-dependencies/archive.rb
|
99
|
+
- lib/file-dependencies/file.rb
|
100
|
+
- lib/file-dependencies/gem.rb
|
101
|
+
- lib/file-dependencies/rake_tasks.rb
|
102
|
+
- lib/file-dependencies/rubygems_plugin.rb
|
103
|
+
- lib/file-dependencies/version.rb
|
104
|
+
- spec/archive_spec.rb
|
105
|
+
- spec/file-dependencies_spec.rb
|
106
|
+
- spec/file_spec.rb
|
107
|
+
- spec/spec_assist.rb
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
homepage: https://github.com/electrical/file-dependencies
|
110
|
+
licenses:
|
111
|
+
- APACHE 2.0
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.4.2
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: manage file dependencies for gems
|
133
|
+
test_files: []
|