file-dependencies 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|