file-dependencies 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ pkg
2
+ # no Gemfile.lock
3
+ *.lock
4
+ .ruby-version
5
+ *.gem
6
+ target
7
+ build.log
8
+ *.swp
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
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - 2.0.0
7
+ script: rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ #-*- mode: ruby -*-
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec
data/bin/file-deps ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'file-dependencies'
4
+ current_dir = Dir.pwd
5
+ FileDependencies.process_vendor(current_dir)
@@ -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,11 @@
1
+ require 'rake'
2
+ require 'file-dependencies'
3
+
4
+ namespace "vendor" do
5
+
6
+ desc "Process vendor files"
7
+ task "files" do
8
+ FileDependencies.process_vendor(Dir.pwd)
9
+ end
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'file-dependencies/gem'
2
+
3
+ FileDependencies::Gem.hook
@@ -0,0 +1,3 @@
1
+ # Note to authors: this should not include dashes because 'gem' barfs if
2
+ # you include a dash in the version string.
3
+ APP_VERSION = '0.1.0'
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'spec_assist'
2
+ require 'webmock/rspec'
3
+
4
+ RSpec.configure do |config|
5
+ config.extend Assist
6
+ config.order = :random
7
+ end
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: []