pipely 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ffc556ad3308b03b691185eb21f4ee10f26592f9
4
- data.tar.gz: caf8a2a7b8c63c281979bde1ad75f75bdc0f963f
3
+ metadata.gz: 84180497ce48add0459eec37acfdb95bf66ebe05
4
+ data.tar.gz: d39f9fef3430493934320d8a88b38a2ced74b42f
5
5
  SHA512:
6
- metadata.gz: 3b6a6b168162ec377f29e5f416a956fa90c3a1d7a21c462f5104d0ebf8db11d340e95b90ddbaf6a5de74f21673cb15084e2a99eb156d200b69acc54d6243bd06
7
- data.tar.gz: 7b659bdb5a01ebe0b4306a3a8c8ec7c426df709e3e56df95a50c1f7134fa3c7d5235efd887b187d1ebbf1c545c2d57d50c33bcb6fa4599a167e6b5930271eda6
6
+ metadata.gz: dae88dbeb0ed78e2197b90cba2601ed218e7bfc8787e5ca4a28618e5b167b2e9d010bfe378670b8b4a8d688f7b624f44abc123f75173046fbf25e3afa1d69555
7
+ data.tar.gz: acbb97a002b4f611be91bd2768e25ed75637e9eba962a632d04a4203bfb2db35ca4141d03fe14dfed196313c53e4f49e4945187e3b4b9d8fbc2a9f64d84a0866
@@ -0,0 +1,29 @@
1
+ require 'pipely/bundler/bundle'
2
+ require 'pipely/bundler/gem_packager'
3
+ require 'pipely/bundler/project_gem'
4
+
5
+ module Pipely
6
+
7
+ #
8
+ # Module for packaging up a gem project and its dependencies, as they exist
9
+ # on your machine, for deployment.
10
+ #
11
+ # None of this code is specific to AWS Data Pipelines, and it could be used
12
+ # anywhere else you want to an in-development gem with frozen dependencies.
13
+ #
14
+ module Bundler
15
+
16
+ # List all the gems used in this project in the format:
17
+ #
18
+ # { name => path_to_cache_file }
19
+ #
20
+ # For gems that are git- or path-sourced, it will first build a fresh cache
21
+ # file for the gem.
22
+ #
23
+ def self.gem_files(vendor_dir='vendor/pipeline')
24
+ ProjectGem.load(vendor_dir).gem_files
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,73 @@
1
+ require 'fileutils'
2
+
3
+ module Pipely
4
+ module Bundler
5
+
6
+ #
7
+ # Provides access to a bundle's list of gems
8
+ #
9
+ class Bundle
10
+
11
+ attr_reader :spec_set
12
+
13
+ SOURCE_TYPES = %w[Bundler::Source::Git Bundler::Source::Path]
14
+
15
+ def self.build(vendor_dir,
16
+ groups=[:default],
17
+ definition=::Bundler.definition)
18
+ new(
19
+ vendor_dir,
20
+ definition.specs_for(groups),
21
+ definition.instance_variable_get(:@locked_sources)
22
+ )
23
+ end
24
+
25
+ def initialize(vendor_dir, spec_set, locked_sources)
26
+ @spec_set = spec_set
27
+ @locked_sources = locked_sources
28
+ @vendor_dir = vendor_dir
29
+ unless Dir.exists? @vendor_dir
30
+ FileUtils.mkdir_p(@vendor_dir)
31
+ end
32
+
33
+ end
34
+
35
+ def gem_files(gem_packager=GemPackager.new(@vendor_dir))
36
+ gem_files = {}
37
+
38
+ @spec_set.to_a.each do |spec|
39
+ gem_files.merge!(gem_file(spec, gem_packager))
40
+ end
41
+
42
+ gem_files
43
+ end
44
+
45
+ private
46
+
47
+ def gem_file(spec, gem_packager)
48
+ if source = locked_sources_by_name[spec.name]
49
+ gem_packager.build_from_source(source.name, source.path)
50
+ else
51
+ gem_packager.package(spec)
52
+ end
53
+ end
54
+
55
+ def locked_sources_by_name
56
+ return @locked_sources_by_name if @locked_sources_by_name
57
+
58
+ @locked_sources_by_name = {}
59
+
60
+ @locked_sources.each do |source|
61
+ # Only include git or path sources.
62
+ if SOURCE_TYPES.include?(source.class.name)
63
+ @locked_sources_by_name[source.name] = source
64
+ end
65
+ end
66
+
67
+ locked_sources_by_name
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,119 @@
1
+ require 'fileutils'
2
+ require 'excon'
3
+
4
+ module Pipely
5
+ module Bundler
6
+
7
+ #
8
+ # Builds cache files for git- or path-sourced gems.
9
+ #
10
+ class GemPackager
11
+
12
+ # Alert upon gem-building failures
13
+ class GemBuildError < RuntimeError ; end
14
+
15
+ # Alert upon gem-fetching failures
16
+ class GemFetchError < RuntimeError ; end
17
+
18
+ def initialize(vendor_dir)
19
+ @vendor_dir = vendor_dir
20
+ unless Dir.exists? @vendor_dir
21
+ FileUtils.mkdir_p(@vendor_dir)
22
+ end
23
+ end
24
+
25
+ def package(spec)
26
+ if vendored_gem = vendor_local_gem(spec)
27
+ vendored_gem
28
+
29
+ # Finally, some gems do not exist in the cache or as source. For
30
+ # instance, json is shipped with the ruby dist. Try to fetch directly
31
+ # from rubygems.
32
+ else
33
+ gem_file_name = "#{spec.name}-#{spec.version}.gem"
34
+ { spec.name => download_from_rubygems(gem_file_name)}
35
+ end
36
+ end
37
+
38
+ def vendor_local_gem(spec)
39
+ gem_file = spec.cache_file
40
+ vendored_gem = File.join( @vendor_dir, File.basename(gem_file) )
41
+
42
+ if File.exists?(vendored_gem)
43
+ { spec.name => vendored_gem }
44
+
45
+ # Gem exists in the local ruby gems cache
46
+ elsif File.exists? gem_file
47
+
48
+ # Copy to vendor dir
49
+ FileUtils.cp(gem_file, vendored_gem)
50
+
51
+ { spec.name => vendored_gem }
52
+
53
+ # If source exists, build a gem from it
54
+ elsif File.directory?(spec.gem_dir)
55
+ build_from_source(spec.name, spec.gem_dir)
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ def build_from_source(spec_name, source_path)
62
+ gem_spec_path = "#{spec_name}.gemspec"
63
+
64
+ # Build the gemspec
65
+ gem_spec = Gem::Specification::load(
66
+ File.join(source_path,gem_spec_path))
67
+
68
+ gem_file = build_gem(spec_name, source_path)
69
+
70
+ # Move to vendor dir
71
+ FileUtils.mv(
72
+ File.join(source_path,gem_file),
73
+ File.join(@vendor_dir,gem_file))
74
+
75
+ { gem_spec.name => File.join(@vendor_dir, gem_file) }
76
+ end
77
+
78
+ def build_gem(spec_name, source_path)
79
+ gem_spec_path = "#{spec_name}.gemspec"
80
+
81
+ Dir.chdir(source_path) do
82
+ result = `gem build #{gem_spec_path} 2>&1`
83
+
84
+ if result =~ /ERROR/i
85
+ raise GemBuildError.new(
86
+ "Failed to build #{gem_spec_path} \n" << result)
87
+ else
88
+ result.scan(
89
+ /File:(.+.gem)$/).flatten.first.strip
90
+ end
91
+ end
92
+ end
93
+
94
+ def download_from_rubygems(gem_file_name)
95
+ vendored_gem = File.join( @vendor_dir, gem_file_name )
96
+
97
+ # XXX: add link on wiki details what is going on here
98
+ puts "Fetching gem #{gem_file_name} directly from rubygems, most " +
99
+ "likely this gem was packaged along with your ruby " +
100
+ "distrubtion, for more details see LINK"
101
+
102
+ ruby_gem_url = "https://rubygems.org/downloads/#{gem_file_name}"
103
+ response = Excon.get( ruby_gem_url, {
104
+ middlewares: Excon.defaults[:middlewares] +
105
+ [Excon::Middleware::RedirectFollower]
106
+ })
107
+
108
+ if response.status == 200
109
+ File.open(vendored_gem, 'w') { |file| file.write( response.body ) }
110
+ return vendored_gem
111
+ else
112
+ raise GemFetchError.new(
113
+ "Failed to find #{gem_file_name} at rubygems, recieved
114
+ #{response.status} with #{response.body}" )
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,47 @@
1
+ module Pipely
2
+ module Bundler
3
+
4
+ #
5
+ # Builds the project's gem from gemspec and pulls in its dependencies via
6
+ # the gem's bundle.
7
+ #
8
+ class ProjectGem
9
+
10
+ attr_reader :project_spec
11
+
12
+ def self.load(vendor_dir)
13
+ if gem_spec = Dir.glob("*.gemspec").first
14
+ # project gem spec
15
+ new(Gem::Specification::load(gem_spec), vendor_dir)
16
+ else
17
+ raise "Failed to find gemspec"
18
+ end
19
+ end
20
+
21
+ def initialize(project_spec, vendor_dir)
22
+ @project_spec = project_spec
23
+ @vendor_dir = vendor_dir
24
+ unless Dir.exists? @vendor_dir
25
+ FileUtils.mkdir_p(@vendor_dir)
26
+ end
27
+ end
28
+
29
+ def gem_files
30
+ # Project gem should be at the bottom of the dep list
31
+ @gem_files ||= dependency_gem_files.merge(project_gem_file)
32
+ end
33
+
34
+ def dependency_gem_files(bundle=Bundle.build(@vendor_dir))
35
+ # Always exclude bundler and the project gem
36
+ gems_to_exclude = [ @project_spec.name, 'bundler' ]
37
+
38
+ bundle.gem_files.reject { |name, path| gems_to_exclude.include?(name) }
39
+ end
40
+
41
+ def project_gem_file(gem_packager=GemPackager.new(@vendor_dir))
42
+ gem_packager.build_from_source(@project_spec.name, Dir.pwd)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -1,115 +1,35 @@
1
+ require 'pipely/bundler'
2
+ require 'pipely/deploy/bootstrap_context'
3
+ require 'pipely/deploy/s3_uploader'
4
+
1
5
  module Pipely
2
6
  module Deploy
3
7
 
4
8
  # Helps bootstrap a pipeline
5
9
  class Bootstrap
6
10
 
7
- attr_reader :bucket_name
8
- attr_reader :s3_gems_path
9
11
  attr_reader :project_spec
10
12
  attr_reader :gem_files
11
13
 
12
- def initialize(s3_bucket, s3_gems_path)
13
- @s3_bucket = s3_bucket
14
- @bucket_name = s3_bucket.name
15
- @s3_gems_path = s3_gems_path
14
+ def initialize(s3_uploader)
15
+ @s3_uploader = s3_uploader
16
16
  end
17
17
 
18
18
  # Builds the project's gem from gemspec, uploads the gem to s3, and
19
19
  # uploads all the gem dependences to S3
20
20
  def build_and_upload_gems
21
-
22
- # placeholder, name will be nil if a gemspec is not defined
23
- project_gem_name = nil
24
-
25
- gem_spec = Dir.glob("*.gemspec").first
26
- if gem_spec
27
-
28
- # Build pipeline gem
29
- @project_spec = Gem::Specification::load(gem_spec)
30
- # build the gem
31
- project_gem_file =
32
- `gem build ./#{gem_spec}`.scan(
33
- /File:(.+.gem)$/).flatten.first.strip
34
- project_gem_name = @project_spec.name
35
- upload_gem(project_gem_file)
36
- end
37
-
38
- @gem_files = upload_gems_from_bundler(project_gem_name)
39
-
40
- # project gem has to be loaded last
41
- @gem_files << project_gem_file if @project_spec
21
+ @gem_files = Pipely::Bundler.gem_files
22
+ @s3_uploader.upload(@gem_files.values)
42
23
  end
43
24
 
44
- def context
45
- BootstrapContext.new(
46
- @gem_files.map{ |file|
47
- File.join("s3://", @s3_bucket.name, gem_s3_path(file) )
48
- } )
49
- end
50
-
51
- def gem_s3_path(gem_file)
52
- filename = File.basename(gem_file)
53
- File.join(@s3_gems_path, filename)
54
- end
55
-
56
- private
57
-
58
- def s3_gem_exists?( gem_file )
59
- !@s3_bucket.objects[gem_s3_path(gem_file)].nil?
60
- end
61
-
62
- def upload_gem( gem_file )
63
- puts "uploading #{gem_file} to #{gem_s3_path(gem_file)}"
64
- @s3_bucket.objects[gem_s3_path(gem_file)].write(File.open(gem_file))
65
- end
66
-
67
- def upload_gems_from_bundler(project_gem_name)
68
- gem_files = []
69
- Bundler.definition.specs_for([:default]).each do |spec|
70
- # Exclude project from gem deps
71
- unless spec.name == project_gem_name
72
- gem_file = spec.cache_file
73
-
74
- # XXX: Some gems do not exist in the cache, e.g. json. Looks the
75
- # gem is already packaged with the ruby dist
76
- if File.exists? gem_file
77
- gem_files << gem_file
78
-
79
- # only upload gem if it doesnt exist already
80
- unless s3_gem_exists?( gem_file )
81
- upload_gem(gem_file)
82
- end
83
- end
84
- end
25
+ def context(s3_steps_path)
26
+ BootstrapContext.new.tap do |context|
27
+ context.gem_files = @s3_uploader.s3_urls(gem_files.values)
28
+ context.s3_steps_path = s3_steps_path
85
29
  end
86
-
87
- gem_files
88
- end
89
- end
90
-
91
- # Context passed to the erb templates
92
- class BootstrapContext
93
- attr_reader :gem_files
94
-
95
- def initialize(gem_files)
96
- @gem_files = gem_files
97
30
  end
98
31
 
99
- def install_gems_script
100
- script = ""
101
-
102
- @gem_files.each do |gem_file|
103
- filename = File.basename(gem_file)
104
- script << %Q[
105
- # #{filename}
106
- hadoop fs -copyToLocal #{gem_file} /home/hadoop/#{filename}
107
- gem install --local /home/hadoop/#{filename} --no-ri --no-rdoc
108
- ]
109
- end
110
-
111
- script
112
- end
113
32
  end
33
+
114
34
  end
115
35
  end
@@ -0,0 +1,37 @@
1
+
2
+ module Pipely
3
+ module Deploy
4
+
5
+ # Context passed to the erb templates
6
+ class BootstrapContext
7
+ attr_accessor :gem_files
8
+ attr_accessor :s3_steps_path
9
+
10
+ def install_gems_script(transport = :hadoop_fs, &blk)
11
+ script = ""
12
+
13
+ case transport.to_sym
14
+ when :hadoop_fs
15
+ transport_cmd = 'hadoop fs -copyToLocal'
16
+ when :awscli
17
+ transport_cmd = 'aws s3 cp'
18
+ else
19
+ raise "Unsupported transport: #{transport}" unless blk
20
+ end
21
+
22
+ @gem_files.each do |gem_file|
23
+ filename = File.basename(gem_file)
24
+ command = "#{transport_cmd} #{gem_file} #{filename}" if transport_cmd
25
+ command = yield(gem_file, filename, command) if blk
26
+ script << %Q[
27
+ # #{filename}
28
+ #{command}
29
+ gem install --force --local #{filename} --no-ri --no-rdoc
30
+ ]
31
+ end
32
+
33
+ script
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ require 'digest/md5'
2
+
3
+ module Pipely
4
+ module Deploy
5
+
6
+ #
7
+ # Manage syncing of local files to a particular S3 path
8
+ #
9
+ class S3Uploader
10
+
11
+ attr_reader :bucket_name
12
+ attr_reader :s3_path
13
+
14
+ def initialize(s3_bucket, s3_path)
15
+ @s3_bucket = s3_bucket
16
+ @bucket_name = s3_bucket.name
17
+ @s3_path = s3_path
18
+ end
19
+
20
+ def s3_file_path(file)
21
+ filename = File.basename(file)
22
+ File.join(@s3_path, filename)
23
+ end
24
+
25
+ def s3_urls(files)
26
+ files.map do |file|
27
+ File.join("s3://", @s3_bucket.name, s3_file_path(file) )
28
+ end
29
+ end
30
+
31
+ def upload(files)
32
+ files.each do |file|
33
+ upload_file(file)
34
+ end
35
+ end
36
+
37
+ #
38
+ # Upload file to S3 unless ETAGs already match.
39
+ #
40
+ def upload_file(file)
41
+ target_path = s3_file_path(file)
42
+ s3_object = @s3_bucket.objects[target_path]
43
+
44
+ content = File.read(file)
45
+ digest = Digest::MD5.hexdigest(content)
46
+
47
+ if s3_object.exists? && (digest == s3_object.etag.gsub('"', ''))
48
+ puts "skipping #{file} to #{target_path} (ETAG matches)"
49
+ else
50
+ puts "uploading #{file} to #{target_path}"
51
+ s3_object.write(content)
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -1,5 +1,6 @@
1
1
  require 'rake'
2
2
  require 'aws'
3
+ require 'erubis'
3
4
  require 'pipely/deploy/bootstrap'
4
5
 
5
6
  module Pipely
@@ -59,19 +60,18 @@ module Pipely
59
60
  end
60
61
 
61
62
  def build_bootstrap_context
62
- bootstrap_helper =
63
- Pipely::Deploy::Bootstrap.new(s3_bucket, @s3_gems_path)
63
+ s3_uploader = Pipely::Deploy::S3Uploader.new(s3_bucket, @s3_gems_path)
64
+ bootstrap_helper = Pipely::Deploy::Bootstrap.new(s3_uploader)
64
65
  bootstrap_helper.build_and_upload_gems
65
66
 
66
67
  # erb context
67
68
  {
68
- bootstrap: bootstrap_helper.context,
69
+ bootstrap: bootstrap_helper.context(@s3_steps_path),
69
70
  config: config
70
71
  }
71
72
  end
72
73
 
73
74
  def upload_to_s3( upload_filename, body )
74
-
75
75
  s3_dest = File.join(@s3_steps_path, upload_filename)
76
76
  puts "uploading #{s3_dest}" if verbose
77
77
  s3_bucket.objects[s3_dest].write(body)
@@ -57,11 +57,8 @@ module Pipely
57
57
 
58
58
  def run_task(verbose)
59
59
  with_bucket do |bucket|
60
- step_files.each do |file_name|
61
- dest = file_name.sub(/^#{local_path}/, s3_path)
62
- puts "uploading #{dest}" if verbose
63
- bucket.objects[dest].write(File.read(file_name))
64
- end
60
+ s3_uploader = Pipely::Deploy::S3Uploader.new(bucket, s3_path)
61
+ s3_uploader.upload(step_files)
65
62
  end
66
63
  end
67
64
 
@@ -1,3 +1,3 @@
1
1
  module Pipely
2
- VERSION = "0.7.0" unless defined?(::Pipely::VERSION)
2
+ VERSION = "0.8.0" unless defined?(::Pipely::VERSION)
3
3
  end
@@ -0,0 +1,72 @@
1
+ require 'pipely/bundler/bundle'
2
+
3
+ describe Pipely::Bundler::Bundle do
4
+
5
+ describe ".build" do
6
+ let(:groups) { [ :group1 ] }
7
+ let(:definition) { double "Bundler::Definition" }
8
+ let(:spec_set) { double }
9
+
10
+ before do
11
+ definition.stub(:specs_for).with(groups) { spec_set }
12
+ end
13
+
14
+ it 'builds a Bundle instance with the spec_set' do
15
+ bundle = described_class.build('vendor/test', groups, definition)
16
+ expect(bundle.spec_set).to eq(spec_set)
17
+ end
18
+ end
19
+
20
+ let(:pipely_spec) { double("Gem::Specification", name: "pipely") }
21
+ let(:gem1_spec) { double("Gem::Specification", name: "gem1") }
22
+ let(:gem2_spec) { double("Gem::Specification", name: "gem2") }
23
+
24
+ let(:pipely_source) do
25
+ Bundler::Source::Path.new('name' => "pipely", 'path' => '.')
26
+ end
27
+
28
+ let(:spec_set) { [ pipely_spec, gem1_spec, gem2_spec ] }
29
+ let(:locked_sources) { [ pipely_source ] }
30
+
31
+ subject { described_class.new('vendor/test', spec_set, locked_sources) }
32
+
33
+ describe "#gem_files" do
34
+ let(:gem_packager) { double }
35
+
36
+ before do
37
+ gem_packager.stub(:package).and_return do |spec|
38
+ { spec.name => '/path/to/cache/file.gem' }
39
+ end
40
+
41
+ gem_packager.stub(:build_from_source).and_return do |name, path|
42
+ { name => "#{path}/#{name}-X.Y.Z.gem" }
43
+ end
44
+ end
45
+
46
+ it "returns a cache file for each gem" do
47
+ gem_files = subject.gem_files(gem_packager)
48
+ expect(gem_files.keys).to match_array(%w[ gem1 gem2 pipely ])
49
+ end
50
+
51
+ context "given a packaged/non-locked gem" do
52
+ it "returns the gems and their existing cache files" do
53
+ expect(gem_packager).to receive(:package).with(gem1_spec)
54
+ expect(gem_packager).to receive(:package).with(gem2_spec)
55
+
56
+ subject.gem_files(gem_packager)
57
+ end
58
+ end
59
+
60
+ context "given a locked-source gem" do
61
+ it "should build new cache files from source" do
62
+ expect(gem_packager).to receive(:build_from_source).with(
63
+ pipely_source.name,
64
+ pipely_source.path
65
+ )
66
+
67
+ subject.gem_files(gem_packager)
68
+ end
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,101 @@
1
+ require 'fileutils'
2
+ require 'pipely/bundler/gem_packager'
3
+
4
+ describe Pipely::Bundler::GemPackager do
5
+
6
+ subject { described_class.new(vendor_path) }
7
+ let(:vendor_path) { 'vendor/test' }
8
+
9
+ before(:each) {
10
+ unless Dir.exists? vendor_path
11
+ FileUtils.mkdir_p 'vendor/test'
12
+ end
13
+ }
14
+
15
+ describe "#package" do
16
+ let(:gem_spec) do
17
+ double("spec",
18
+ name: 'test',
19
+ cache_file: 'a/cache/file',
20
+ gem_dir:'a/gem/dir',
21
+ version:'0.0.1'
22
+ )
23
+ end
24
+
25
+ let(:vendored_gem) { "vendor/test/file" }
26
+
27
+ context "with a cache file" do
28
+ before do
29
+ allow(File).to receive(:exists?).with(vendored_gem) { false }
30
+ allow(File).to receive(:exists?).with(gem_spec.cache_file) { true }
31
+ allow(FileUtils).to receive(:cp).with(
32
+ gem_spec.cache_file, vendored_gem)
33
+ end
34
+
35
+ it "returns the cache file" do
36
+ expect(subject.package(gem_spec)).to eq(
37
+ {gem_spec.name => vendored_gem}
38
+ )
39
+ end
40
+ end
41
+
42
+ context "without a cache file" do
43
+ before do
44
+ allow(File).to receive(:exists?).with(gem_spec.cache_file) { false }
45
+ allow(File).to receive(:exists?).with(vendored_gem) { false }
46
+ end
47
+
48
+ context "if source is available" do
49
+ before do
50
+ allow(File).to receive(:directory?).with(gem_spec.gem_dir) { true }
51
+ end
52
+
53
+ it "builds the gem from source" do
54
+ expect(subject).to receive(:build_from_source).and_return(
55
+ {"test"=>"a/packaged/file"})
56
+
57
+ expect(subject.package(gem_spec)).to eq({"test"=>"a/packaged/file"})
58
+ end
59
+ end
60
+
61
+ context "if source not available, e.g. json-1.8.1 built into Ruby 2.1" do
62
+ before do
63
+ allow(File).to receive(:directory?).with(gem_spec.gem_dir) { false }
64
+ end
65
+
66
+ it "downloads from rubygems" do
67
+ response = double(:response, status: 200, body: 'im a gem')
68
+ expect(Excon).to receive(:get).with(
69
+ "https://rubygems.org/downloads/test-0.0.1.gem", anything())
70
+ .and_return(response)
71
+ expect(subject.package(gem_spec)).to eq(
72
+ {"test"=>"vendor/test/test-0.0.1.gem"})
73
+ end
74
+ end
75
+
76
+ context "if source not available and not on rubygems" do
77
+ before do
78
+ allow(File).to receive(:directory?).with(gem_spec.gem_dir) { false }
79
+ end
80
+
81
+ it "raises" do
82
+ response = double(:response, status: 400, body: 'test')
83
+ expect(Excon).to receive(:get).with(
84
+ "https://rubygems.org/downloads/test-0.0.1.gem", anything())
85
+ .and_return(response)
86
+
87
+ expect { subject.package(gem_spec) }.to raise_error
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#build_from_source" do
94
+ context "with bad spec" do
95
+ it "raises" do
96
+ expect { subject.build_from_source("bad-name", ".") }.to raise_error
97
+ end
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,95 @@
1
+ # Copyright Swipely, Inc. All rights reserved.
2
+
3
+ require 'spec_helper'
4
+ require 'pipely/deploy/bootstrap'
5
+
6
+ describe Pipely::Bundler::ProjectGem do
7
+
8
+ let(:project_spec) do
9
+ double "Gem::Specification",
10
+ name: "my-project",
11
+ file_name: "/path/to/cache/my-project.gem"
12
+ end
13
+
14
+ subject { described_class.new(project_spec, 'vendor/test') }
15
+
16
+ describe ".load" do
17
+ let(:filename) { 'foo.gemspec' }
18
+ let(:gemspec) { double }
19
+
20
+ before do
21
+ allow(Dir).to receive(:glob).with("*.gemspec") { [ filename ] }
22
+ allow(Gem::Specification).to receive(:load).with(filename) { gemspec }
23
+ end
24
+
25
+ it "loads the gemspec" do
26
+ loaded = described_class.load('vendor/test')
27
+ expect(loaded.project_spec).to eq(gemspec)
28
+ end
29
+ end
30
+
31
+ describe "#gem_files" do
32
+ let(:dependency_gem_files) do
33
+ {
34
+ 'packaged-gem1' => '/path/to/cache/packaged-gem1.gem',
35
+ 'built-from-source-gem1' => '/path/to/cache/built-from-source-gem1.gem',
36
+ }
37
+ end
38
+
39
+ let(:project_gem_file) do
40
+ {
41
+ project_spec.name => project_spec.file_name
42
+ }
43
+ end
44
+
45
+ before do
46
+ allow(subject).to receive(:dependency_gem_files) { dependency_gem_files }
47
+ allow(subject).to receive(:project_gem_file) { project_gem_file }
48
+ end
49
+
50
+ it "combines the dependency_gem_files and the project_gem_file" do
51
+ expect(subject.gem_files.keys).to match_array(
52
+ dependency_gem_files.keys + project_gem_file.keys
53
+ )
54
+ end
55
+
56
+ it "lists the project_gem_file last" do
57
+ expect(subject.gem_files.keys.last).to eq(project_spec.name)
58
+ end
59
+ end
60
+
61
+ describe "#dependency_gem_files" do
62
+ let(:bundle_gem_files) do
63
+ {
64
+ 'packaged-gem1' => '/path/to/cache/packaged-gem1.gem',
65
+ 'built-from-source-gem1' => '/path/to/cache/built-from-source-gem1.gem',
66
+ 'bundler' => '/path/to/cache/bundler.gem',
67
+ project_spec.name => project_spec.file_name,
68
+ }
69
+ end
70
+
71
+ let(:bundle) do
72
+ double "Pipely::Bundler::Bundle", gem_files: bundle_gem_files
73
+ end
74
+
75
+ it "should filter out the bundler gem and the project gem" do
76
+ result = subject.dependency_gem_files(bundle)
77
+ expect(result.keys).to eq(%w[ packaged-gem1 built-from-source-gem1 ])
78
+ end
79
+ end
80
+
81
+ describe "#project_gem_file" do
82
+ let(:gem_packager) { double "Pipely::Bundler::GemPackager" }
83
+ let(:project_gem_file) { double }
84
+
85
+ before do
86
+ allow(gem_packager).to receive(:build_from_source) { project_gem_file }
87
+ end
88
+
89
+ it "should return the project's own gem file" do
90
+ result = subject.project_gem_file(gem_packager)
91
+ expect(result).to eq(project_gem_file)
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright Swipely, Inc. All rights reserved.
2
+
3
+ require 'spec_helper'
4
+ require 'pipely/deploy/bootstrap_context'
5
+ require 'fileutils'
6
+
7
+ describe Pipely::Deploy::BootstrapContext do
8
+ subject do
9
+ Pipely::Deploy::BootstrapContext.new.tap do |context|
10
+ context.gem_files = ['one.gem', 'two.gem']
11
+ end
12
+ end
13
+
14
+ describe "#install_gems_script" do
15
+ it "defaults to hadoop fs" do
16
+ expect(subject.install_gems_script).to eql "
17
+ # one.gem
18
+ hadoop fs -copyToLocal one.gem one.gem
19
+ gem install --force --local one.gem --no-ri --no-rdoc
20
+
21
+ # two.gem
22
+ hadoop fs -copyToLocal two.gem two.gem
23
+ gem install --force --local two.gem --no-ri --no-rdoc
24
+ "
25
+ end
26
+
27
+ context "with aws cli" do
28
+ it "should build script for aws cli" do
29
+ expect(subject.install_gems_script(:awscli) ).to eql "
30
+ # one.gem
31
+ aws s3 cp one.gem one.gem
32
+ gem install --force --local one.gem --no-ri --no-rdoc
33
+
34
+ # two.gem
35
+ aws s3 cp two.gem two.gem
36
+ gem install --force --local two.gem --no-ri --no-rdoc
37
+ "
38
+ end
39
+ end
40
+
41
+ context "with yield" do
42
+ it "should build script for aws cli" do
43
+ expect(subject.install_gems_script(:awscli) do |file,filename,command|
44
+ "custom command - #{file} #{filename} #{command}"
45
+ end).to eql "
46
+ # one.gem
47
+ custom command - one.gem one.gem aws s3 cp one.gem one.gem
48
+ gem install --force --local one.gem --no-ri --no-rdoc
49
+
50
+ # two.gem
51
+ custom command - two.gem two.gem aws s3 cp two.gem two.gem
52
+ gem install --force --local two.gem --no-ri --no-rdoc
53
+ "
54
+ end
55
+ end
56
+ end
57
+ end
@@ -2,85 +2,52 @@
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'pipely/deploy/bootstrap'
5
- require 'fog'
6
5
  require 'fileutils'
7
6
 
8
- module Pipely
9
- describe Deploy::Bootstrap do
10
- subject { described_class.new(s3_bucket, 'test_path/gems') }
11
- let(:s3_bucket) do
12
- s3 = AWS::S3.new
13
- s3.buckets['a-test-bucket']
14
- end
15
-
16
- it "should have bucket name" do
17
- expect(subject.bucket_name).to eql 'a-test-bucket'
18
- end
19
-
20
- it "should have a s3 gems path" do
21
- expect(subject.s3_gems_path).to eql 'test_path/gems'
22
- end
23
-
24
- describe "#build_and_upload_gems" do
25
- let(:build_and_upload_gems) do
26
- VCR.use_cassette('build_and_upload_gems') do
27
- subject.build_and_upload_gems
28
- end
29
- end
30
-
31
- it "should create project gem" do
32
- build_and_upload_gems
33
-
34
- expect(File).to exist(subject.project_spec.file_name)
35
- end
7
+ describe Pipely::Deploy::Bootstrap do
36
8
 
37
- it "should upload gems" do
38
- specs = Bundler.definition.specs_for([:default])
9
+ subject { described_class.new(s3_uploader) }
39
10
 
40
- gems = specs.map(&:file_name).reject do |g|
41
- # Ignore bundler, since it could be a system installed gem (travis)
42
- # without a cache file
43
- g.match(/^json-\d+\.\d+\.\d+\.gem$/) or
44
- g.match(/^bundler-\d+\.\d+\.\d+\.gem$/)
45
- end
11
+ let(:s3_uploader) { double }
46
12
 
47
- objects = double(:objects)
48
- s3_object = double('s3_object', write: nil)
49
-
50
- allow(objects).to receive(:[]) { s3_object }
13
+ let(:gem_files) do
14
+ {
15
+ 'packaged-gem1' => '/path/to/cache/packaged-gem1.gem',
16
+ 'built-from-source-gem1' => '/path/to/cache/built-from-source-gem1.gem',
17
+ }
18
+ end
51
19
 
52
- gems.each do |gem|
53
- expect(objects).to receive(:[]).with(subject.gem_s3_path(gem))
54
- end
20
+ describe "#build_and_upload_gems" do
21
+ before do
22
+ allow(Pipely::Bundler).to receive(:gem_files) { gem_files }
23
+ end
55
24
 
56
- expect(s3_bucket).to(receive(:objects)).
57
- at_least(gems.size).times.
58
- at_most(gems.size + 1).times. # allow for Bundler
59
- and_return(objects)
25
+ it 'uploads each gem' do
26
+ expect(s3_uploader).to receive(:upload).with(gem_files.values)
60
27
 
61
- build_and_upload_gems
62
- end
28
+ subject.build_and_upload_gems
63
29
  end
30
+ end
64
31
 
65
- describe "#context" do
66
- let(:context) { subject.context}
32
+ describe "#context" do
33
+ let(:context) { subject.context(s3_steps_path) }
34
+ let(:s3_steps_path) { 'a/test/path' }
35
+ let(:s3_gem_paths) { double }
67
36
 
68
- before do
69
- VCR.use_cassette('build_and_upload_gems') do
70
- subject.build_and_upload_gems
71
- end
72
- end
37
+ before do
38
+ allow(subject).to receive(:gem_files) { gem_files }
73
39
 
74
- it "should have gem_files" do
75
- expect(context.gem_files).to_not be_nil
40
+ allow(s3_uploader).to receive(:s3_urls).with(gem_files.values) do
41
+ s3_gem_paths
76
42
  end
43
+ end
77
44
 
78
- it "should be an s3 url" do
79
- expect(context.gem_files.first).to(
80
- match( /^s3:\/\/#{subject.bucket_name}\/#{subject.s3_gems_path}/ )
81
- )
82
- end
45
+ it "should have s3 steps path" do
46
+ expect(context.s3_steps_path).to eq(s3_steps_path)
83
47
  end
84
48
 
49
+ it "builds S3 urls to the uploaded gem files" do
50
+ expect(context.gem_files).to eq(s3_gem_paths)
51
+ end
85
52
  end
86
53
  end
@@ -0,0 +1,54 @@
1
+ # Copyright Swipely, Inc. All rights reserved.
2
+
3
+ require 'spec_helper'
4
+ require 'pipely/deploy/s3_uploader'
5
+ require 'tempfile'
6
+
7
+ describe Pipely::Deploy::S3Uploader do
8
+
9
+ subject { described_class.new(s3_bucket, 'test_path/gems') }
10
+
11
+ let(:s3_bucket) do
12
+ s3 = AWS::S3.new
13
+ s3.buckets['a-test-bucket']
14
+ end
15
+
16
+ it "should have bucket name" do
17
+ expect(subject.bucket_name).to eq('a-test-bucket')
18
+ end
19
+
20
+ it "should have a s3 path" do
21
+ expect(subject.s3_path).to eq('test_path/gems')
22
+ end
23
+
24
+ describe "#upload(files)" do
25
+ let(:objects) { double(:objects) }
26
+
27
+ let(:s3_object) do
28
+ double('s3_object', write: nil, exists?: true, etag: 'mismatch')
29
+ end
30
+
31
+ let(:files) do
32
+ [
33
+ Tempfile.new('packaged-gem1.gem').path,
34
+ Tempfile.new('built-from-source-gem1.gem').path,
35
+ ]
36
+ end
37
+
38
+ before do
39
+ allow(objects).to receive(:[]) { s3_object }
40
+ allow(s3_bucket).to receive(:objects) { objects }
41
+ end
42
+
43
+ it 'uploads each file' do
44
+ files.each do |file|
45
+ expect(objects).to receive(:[]).with(subject.s3_file_path(file))
46
+ end
47
+
48
+ expect(s3_bucket).to receive(:objects).exactly(files.size).times
49
+
50
+ subject.upload(files)
51
+ end
52
+ end
53
+
54
+ end
@@ -3,6 +3,7 @@ require 'aws-sdk'
3
3
  require 'fog'
4
4
  require 'rspec'
5
5
  require 'vcr'
6
+ require 'pry'
6
7
 
7
8
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
8
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pipely
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Gillooly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-11 00:00:00.000000000 Z
11
+ date: 2014-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-graphviz
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - ">="
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: pry
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
209
223
  description:
210
224
  email:
211
225
  - matt@swipely.com
@@ -230,12 +244,18 @@ files:
230
244
  - lib/pipely/build/s3_path_builder.rb
231
245
  - lib/pipely/build/template.rb
232
246
  - lib/pipely/build/template_helpers.rb
247
+ - lib/pipely/bundler.rb
248
+ - lib/pipely/bundler/bundle.rb
249
+ - lib/pipely/bundler/gem_packager.rb
250
+ - lib/pipely/bundler/project_gem.rb
233
251
  - lib/pipely/component.rb
234
252
  - lib/pipely/definition.rb
235
253
  - lib/pipely/dependency.rb
236
254
  - lib/pipely/deploy.rb
237
255
  - lib/pipely/deploy/bootstrap.rb
256
+ - lib/pipely/deploy/bootstrap_context.rb
238
257
  - lib/pipely/deploy/client.rb
258
+ - lib/pipely/deploy/s3_uploader.rb
239
259
  - lib/pipely/fog_client.rb
240
260
  - lib/pipely/graph_builder.rb
241
261
  - lib/pipely/live_pipeline.rb
@@ -257,11 +277,16 @@ files:
257
277
  - spec/lib/pipely/build/s3_path_builder_spec.rb
258
278
  - spec/lib/pipely/build/template_spec.rb
259
279
  - spec/lib/pipely/build_spec.rb
280
+ - spec/lib/pipely/bundler/bundle_spec.rb
281
+ - spec/lib/pipely/bundler/gem_packager_spec.rb
282
+ - spec/lib/pipely/bundler/project_gem_spec.rb
260
283
  - spec/lib/pipely/component_spec.rb
261
284
  - spec/lib/pipely/definition_spec.rb
262
285
  - spec/lib/pipely/dependency_spec.rb
286
+ - spec/lib/pipely/deploy/bootstrap_context_spec.rb
263
287
  - spec/lib/pipely/deploy/bootstrap_spec.rb
264
288
  - spec/lib/pipely/deploy/client_spec.rb
289
+ - spec/lib/pipely/deploy/s3_uploader_spec.rb
265
290
  - spec/lib/pipely/graph_builder_spec.rb
266
291
  - spec/lib/pipely/reference_list_spec.rb
267
292
  - spec/lib/pipely_spec.rb
@@ -297,11 +322,16 @@ test_files:
297
322
  - spec/lib/pipely/build/s3_path_builder_spec.rb
298
323
  - spec/lib/pipely/build/template_spec.rb
299
324
  - spec/lib/pipely/build_spec.rb
325
+ - spec/lib/pipely/bundler/bundle_spec.rb
326
+ - spec/lib/pipely/bundler/gem_packager_spec.rb
327
+ - spec/lib/pipely/bundler/project_gem_spec.rb
300
328
  - spec/lib/pipely/component_spec.rb
301
329
  - spec/lib/pipely/definition_spec.rb
302
330
  - spec/lib/pipely/dependency_spec.rb
331
+ - spec/lib/pipely/deploy/bootstrap_context_spec.rb
303
332
  - spec/lib/pipely/deploy/bootstrap_spec.rb
304
333
  - spec/lib/pipely/deploy/client_spec.rb
334
+ - spec/lib/pipely/deploy/s3_uploader_spec.rb
305
335
  - spec/lib/pipely/graph_builder_spec.rb
306
336
  - spec/lib/pipely/reference_list_spec.rb
307
337
  - spec/lib/pipely_spec.rb