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 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: []