construi 0.39.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,3 +3,4 @@
3
3
  require 'construi'
4
4
 
5
5
  Construi.run ARGV
6
+
@@ -8,8 +8,10 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Construi::VERSION
9
9
  spec.authors = ['Levi Stephen']
10
10
  spec.email = ['levi.stephen@gmail.com']
11
+
11
12
  spec.summary = 'Build tool using Docker to specify build environment'
12
13
  spec.description = 'Build tool using Docker to specify build environment'
14
+
13
15
  spec.homepage = 'https://github.com/lstephen/construi'
14
16
  spec.license = 'MIT'
15
17
 
@@ -20,7 +22,7 @@ Gem::Specification.new do |spec|
20
22
 
21
23
  spec.required_ruby_version = '>= 1.9'
22
24
 
23
- spec.add_dependency 'docker-api', '=1.21.4'
25
+ spec.add_dependency 'docker-api', '=1.22.0'
24
26
  spec.add_dependency 'colorize', '=0.7.7'
25
27
 
26
28
  spec.add_development_dependency 'bundler', '~> 1.9'
@@ -1,6 +1,4 @@
1
1
 
2
- require 'yaml'
3
-
4
2
  # Entrypoint for the Construi application
5
3
  module Construi
6
4
  require 'construi/config'
@@ -1,6 +1,25 @@
1
+ require 'yaml'
1
2
 
2
3
  module Construi
3
4
  module Config
5
+ module WrappedYaml
6
+ def parent
7
+ nil
8
+ end
9
+
10
+ def key?(key)
11
+ yaml.is_a?(Hash) && yaml.key?(key.to_s)
12
+ end
13
+
14
+ def get(key, default = nil)
15
+ key?(key) ? yaml[key.to_s] : default
16
+ end
17
+
18
+ def with_parent(or_else = nil)
19
+ parent ? yield(parent) : or_else
20
+ end
21
+ end
22
+
4
23
  module Image
5
24
  def image
6
25
  image_configured :image
@@ -11,7 +30,7 @@ module Construi
11
30
  end
12
31
 
13
32
  def privileged?
14
- key?(:privileged) ? yaml['privileged'] : with_parent(false, &:privileged?)
33
+ key?(:privileged) ? get(:privileged) : with_parent(false, &:privileged?)
15
34
  end
16
35
 
17
36
  def image_configured?
@@ -19,7 +38,7 @@ module Construi
19
38
  end
20
39
 
21
40
  def image_configured(what)
22
- image_configured? ? yaml[what.to_s] : with_parent(&what)
41
+ image_configured? ? get(what) : with_parent(&what)
23
42
  end
24
43
  end
25
44
 
@@ -43,55 +62,84 @@ module Construi
43
62
  end
44
63
  end
45
64
 
46
- def files_configured?
47
- key? :files
48
- end
49
-
50
65
  def files
51
- fs = files_configured? ? yaml['files'].map { |str| File.parse(str) } : []
52
-
53
- with_parent([], &:files).concat fs
66
+ with_parent([], &:files).concat get(:files, []).map { |s| File.parse s }
54
67
  end
55
68
  end
56
69
 
57
- module Environment
58
- include Image
59
- include Files
70
+ module EnvironmentVariables
71
+ def env_configured?
72
+ key? :environment
73
+ end
60
74
 
61
- def parent
62
- nil
75
+ def env_hash
76
+ parent = with_parent({}, &:env_hash)
77
+
78
+ child = get(:environment, {}).each_with_object({}) do |v, h|
79
+ key, value = v.split '='
80
+
81
+ value = ENV[key] if value.nil? || value.empty?
82
+
83
+ h[key] = value
84
+ end
85
+
86
+ parent.merge child
63
87
  end
64
88
 
65
- def key?(key)
66
- yaml.is_a?(Hash) && yaml.key?(key.to_s)
89
+ def env
90
+ env_hash.each_with_object([]) do |(k, v), a|
91
+ a << "#{k}=#{v}" unless v.nil? || v.empty?
92
+ end
67
93
  end
94
+ end
68
95
 
69
- def with_parent(or_else = nil)
70
- parent ? yield(parent) : or_else
96
+ module Options
97
+ def options
98
+ { env: env, privileged: privileged? }
71
99
  end
72
100
  end
73
101
 
74
- class Global
75
- include Environment
102
+ module Links
103
+ class Link
104
+ include WrappedYaml
105
+ include Image
106
+ include Files
107
+ include EnvironmentVariables
108
+ include Options
76
109
 
77
- attr_reader :yaml
110
+ attr_reader :yaml
78
111
 
79
- def initialize(yaml)
80
- @yaml = yaml
112
+ def initialize(name, yaml)
113
+ @yaml = yaml
114
+ end
81
115
  end
82
116
 
83
- def env
84
- return [] if yaml['environment'].nil?
117
+ def links
118
+ parent = with_parent({}, &:links)
85
119
 
86
- yaml['environment'].reduce([]) do |acc, e|
87
- key = e.partition('=').first
88
- value = e.partition('=').last
120
+ child = get(:links, {}).each_with_object({}) do |(k, v), ls|
121
+ ls[k] = Link.new k, v
122
+ end
89
123
 
90
- value = ENV[key] if value.empty?
124
+ parent.merge child
125
+ end
126
+ end
91
127
 
92
- acc << "#{key}=#{value}" unless value.nil? or value.empty?
93
- acc
94
- end
128
+ module BuildEnvironment
129
+ include WrappedYaml
130
+ include Image
131
+ include Files
132
+ include EnvironmentVariables
133
+ include Links
134
+ end
135
+
136
+ class Global
137
+ include BuildEnvironment
138
+
139
+ attr_reader :yaml
140
+
141
+ def initialize(yaml)
142
+ @yaml = yaml
95
143
  end
96
144
 
97
145
  def target(target)
@@ -104,7 +152,8 @@ module Construi
104
152
  end
105
153
 
106
154
  class Target
107
- include Environment
155
+ include BuildEnvironment
156
+ include Options
108
157
 
109
158
  attr_reader :yaml, :parent
110
159
 
@@ -116,10 +165,6 @@ module Construi
116
165
  def commands
117
166
  Array(@yaml.is_a?(Hash) ? @yaml['run'] : @yaml)
118
167
  end
119
-
120
- def options
121
- { env: parent.env, privileged: parent.privileged? }
122
- end
123
168
  end
124
169
 
125
170
  def self.load(content)
@@ -0,0 +1,27 @@
1
+
2
+ module Construi
3
+ module Console
4
+ $stdout.sync = true
5
+
6
+ def self.warn(msg)
7
+ puts msg.yellow
8
+ end
9
+
10
+ def self.info(msg)
11
+ puts msg.green
12
+ end
13
+
14
+ def self.progress(msg)
15
+ puts
16
+ info msg
17
+ end
18
+
19
+ def self.output(from, msg)
20
+ msg.each_line do |m|
21
+ puts "#{from.rjust(13)} | ".blue << m
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -1,28 +1,51 @@
1
1
  require 'construi/image'
2
2
 
3
3
  module Construi
4
-
5
4
  class Container
6
5
  private_class_method :new
7
6
 
8
- def initialize(container)
7
+ attr_reader :name
8
+
9
+ def initialize(container, options = {})
9
10
  @container = container
11
+ @name = options[:name] || @container.id
12
+ @log_lifecycle = options[:log_lifecycle] || false
10
13
  end
11
14
 
12
15
  def id
13
16
  @container.id
14
17
  end
15
18
 
19
+ def start
20
+ log_lifecycle "Starting container: '#{name}'..."
21
+ @container.start!
22
+ attach_stdout
23
+ end
24
+
25
+ def stop
26
+ log_lifecycle "Stopping container: '#{name}'..."
27
+ @container.stop
28
+ end
29
+
16
30
  def delete
31
+ stop
32
+ @container.kill
17
33
  @container.delete force: true, v: true
34
+ log_lifecycle "Deleted container: '#{name}'"
18
35
  end
19
36
 
20
37
  def attach_stdout
21
- @container.attach(:stream => true, :logs => true) { |s, c| puts c; $stdout.flush }
22
- true
23
- rescue Docker::Error::TimeoutError
24
- puts 'Failed to attach to stdout'.yellow
25
- false
38
+ Thread.new do
39
+ @container.attach(:stream => true, :logs => true) { |_, c| Console.output name, c }
40
+ end
41
+ end
42
+
43
+ def log_lifecycle?
44
+ @log_lifecycle
45
+ end
46
+
47
+ def log_lifecycle(msg)
48
+ Console.progress msg if log_lifecycle?
26
49
  end
27
50
 
28
51
  def commit
@@ -30,13 +53,10 @@ module Construi
30
53
  end
31
54
 
32
55
  def run
33
- @container.start
34
- attached = attach_stdout
56
+ start
35
57
  status_code = @container.wait['StatusCode']
36
58
 
37
- puts @container.logs(:stdout => true) unless attached
38
-
39
- raise Error, "Cmd returned status code: #{status_code}" unless status_code == 0
59
+ raise RunError, "Cmd returned status code: #{status_code}" unless status_code == 0
40
60
 
41
61
  commit
42
62
  end
@@ -45,42 +65,49 @@ module Construi
45
65
  other.is_a? Container and id == other.id
46
66
  end
47
67
 
48
- def self.create(image, cmd, options = {})
68
+ def self.create(image, options = {})
49
69
  env = options[:env] || []
50
70
  privileged = options[:privileged] || false
71
+ links = options[:links] || []
51
72
 
52
73
  host_config = {
53
74
  'Binds' => ["#{Dir.pwd}:/var/workspace"],
54
- 'Privileged' => privileged
75
+ 'Privileged' => privileged,
76
+ 'Links' => links
55
77
  }
56
78
 
57
- wrap Docker::Container.create(
58
- 'Cmd' => cmd.split,
79
+ create_options = {
59
80
  'Image' => image.id,
60
81
  'Env' => env,
61
82
  'Tty' => false,
62
83
  'WorkingDir' => '/var/workspace',
63
- 'HostConfig' => host_config)
84
+ 'HostConfig' => host_config
85
+ }
86
+
87
+ create_options['Cmd'] = options[:cmd].split if options.key?(:cmd)
88
+
89
+ wrap Docker::Container.create(create_options), options
64
90
  end
65
91
 
66
- def self.wrap(container)
67
- new container
92
+ def self.wrap(container, options = {})
93
+ new container, options
68
94
  end
69
95
 
70
- def self.use(image, cmd, options = {})
71
- container = create image, cmd, options
96
+ def self.use(image, options = {})
97
+ container = create image, options
72
98
  yield container
73
99
  ensure
74
100
  container.delete unless container.nil?
75
101
  end
76
102
 
77
- def self.run(image, cmd, options = {})
78
- use image, cmd, options, &:run
103
+ def self.run(image, options = {})
104
+ use image, options, &:run
79
105
  end
80
106
 
81
- class Error < StandardError
107
+ class RunError < StandardError
82
108
  end
83
109
 
84
110
  end
85
111
 
112
+
86
113
  end
@@ -59,8 +59,12 @@ module Construi
59
59
  run chmod, options
60
60
  end
61
61
 
62
+ def start(options = {})
63
+ Container.create(self, options).tap(&:start)
64
+ end
65
+
62
66
  def run(cmd, options = {})
63
- Container.run self, cmd, options
67
+ Container.run self, options.merge(cmd: cmd)
64
68
  end
65
69
 
66
70
  def ==(other)
@@ -77,8 +81,8 @@ module Construi
77
81
  end
78
82
 
79
83
  def self.create(image)
80
- puts
81
- puts "Creating image: '#{image}'...".green
84
+ Console.progress "Creating image: '#{image}'..."
85
+
82
86
  wrap Docker::Image.create('fromImage' => image) { |s|
83
87
  status = JSON.parse(s)
84
88
 
@@ -86,17 +90,19 @@ module Construi
86
90
  progress = status['progressDetail']
87
91
 
88
92
  if progress.nil? || progress.empty?
89
- print "#{id}: " unless id.nil?
90
- puts "#{status['status']}"
93
+ msg = ''
94
+ msg << "#{id}: " unless id.nil?
95
+ msg << status['status']
96
+ Console.output image, msg
91
97
  end
92
98
  }
93
99
  end
94
100
 
95
101
  def self.build(build)
96
- puts
97
- puts "Building image: '#{build}'...".green
102
+ Console.progress "Building image: '#{build}'..."
103
+
98
104
  wrap Docker::Image.build_from_dir(build, rm: 0) { |s|
99
- puts JSON.parse(s)['stream']
105
+ Console.output build, JSON.parse(s)['stream']
100
106
  }
101
107
  end
102
108
 
@@ -9,6 +9,8 @@ require 'colorize'
9
9
  require 'docker'
10
10
 
11
11
  module Construi
12
+ DOCKER_TIMEOUT = 60
13
+
12
14
  # Runs Construi
13
15
  class Runner
14
16
  def initialize(config)
@@ -25,8 +27,6 @@ module Construi
25
27
 
26
28
  Docker.validate_version!
27
29
 
28
- # Don't time out. We can't differentiate between a long running
29
- # task and a time out.
30
30
  Docker.options[:read_timeout] = nil
31
31
 
32
32
  # Low chunk size as we wish to receive streaming output ASAP
@@ -43,4 +43,5 @@ module Construi
43
43
  targets.map { |t| Target.new t, @config.target(t) } .each(&:run)
44
44
  end
45
45
  end
46
+
46
47
  end
@@ -1,7 +1,6 @@
1
-
1
+ require 'construi/console'
2
2
 
3
3
  module Construi
4
-
5
4
  class Target
6
5
  attr_reader :name, :config
7
6
 
@@ -15,20 +14,38 @@ module Construi
15
14
  end
16
15
 
17
16
  def run
18
- puts "Running #{name}...".green
17
+ Console.progress "Running #{name}..."
18
+
19
+ links = start_linked_images
20
+
21
+ begin
22
+ final_image = IntermediateImage.seed(create_initial_image).reduce(commands) do |image, command|
23
+ Console.progress " > #{command}"
24
+
25
+ link_option = links.each_with_object([]) do |l, o|
26
+ o << "#{l.id}:#{l.name}"
27
+ end
19
28
 
20
- final_image = IntermediateImage.seed(initial_image).reduce(commands) do |image, command|
21
- puts
22
- puts " > #{command}".green
23
- image.run command, @config.options
29
+ image.run command, @config.options.merge(links: link_option, name: name)
30
+ end
31
+
32
+ final_image.delete
33
+ ensure
34
+ links.map(&:delete)
24
35
  end
25
36
 
26
- final_image.delete
37
+ Console.progress "Build Successful."
27
38
  end
28
39
 
29
- def initial_image
40
+ def create_initial_image
30
41
  return Image.from(@config)
31
42
  end
43
+
44
+ def start_linked_images
45
+ @config.links.map do |(name, config)|
46
+ Image.from(config).start(config.options.merge name: name, log_lifecycle: true)
47
+ end
48
+ end
32
49
  end
33
50
 
34
51
  end
@@ -1,3 +1,3 @@
1
1
  module Construi
2
- VERSION = "0.39.0"
2
+ VERSION = "0.40.0"
3
3
  end
@@ -104,6 +104,7 @@ Declares environment variables that will be passed through or set in the build e
104
104
  If no value is provided then the value from the host environment will be used.
105
105
  In this example `NEXUS_SERVER_URL` will be set as provided, while `NEXUS_USERNAME` and
106
106
  `NEXUS_PASSWORD` will be retrieved from the host.
107
+ Can be used on a per target basis.
107
108
 
108
109
  ```
109
110
  image: maven:3-jdk-7
@@ -135,6 +136,32 @@ targets:
135
136
  run: scripts/construi/deploy.sh
136
137
  ```
137
138
 
139
+ ### Links
140
+
141
+ Allows specifying containers that will be linked to the build container during the build
142
+ process.
143
+ This is useful, for example, in having a database running durings tests.
144
+ Linked containers allow setting of environment, files, and privileged as per the build
145
+ container.
146
+ Links can be specified on a global or per target level.
147
+
148
+ ```
149
+ image: ruby:1.9
150
+
151
+ targets:
152
+ integration-tests:
153
+ environment:
154
+ - MYSQL_HOST=mysql
155
+ run:
156
+ - bundle install --path=vendor/bundle
157
+ - rake test:integration
158
+ links:
159
+ mysql:
160
+ image: mysql:5.5
161
+ environment:
162
+ - MYSQL_ALLOW_EMPTY_PASSWORD=yes
163
+ ```
164
+
138
165
  ### Targets
139
166
 
140
167
  Any number of targets can be specified.
@@ -65,9 +65,16 @@ RSpec.describe Construi::Config do
65
65
  end
66
66
 
67
67
  describe '#env' do
68
- subject { config.env }
68
+ subject { config.target('build').env }
69
69
 
70
70
  context 'when no environment section' do
71
+ let(:config_content) do
72
+ <<-YAML
73
+ targets:
74
+ build: cmd1
75
+ YAML
76
+ end
77
+
71
78
  it { is_expected.to eq([]) }
72
79
  end
73
80
 
@@ -77,6 +84,8 @@ RSpec.describe Construi::Config do
77
84
  environment:
78
85
  - VAR1=VALUE_1
79
86
  - VAR2=VALUE_2
87
+ targets:
88
+ build: cmd1
80
89
  YAML
81
90
  end
82
91
 
@@ -94,10 +103,32 @@ RSpec.describe Construi::Config do
94
103
  environment:
95
104
  - VAR1
96
105
  - VAR2
106
+ targets:
107
+ build: cmd1
97
108
  YAML
98
109
  end
99
110
 
100
- it { is_expected.to contain_exactly('VAR1=VALUE_1', 'VAR2=VALUE_2') }
111
+ it { is_expected.to contain_exactly 'VAR1=VALUE_1', 'VAR2=VALUE_2' }
112
+ end
113
+
114
+ context 'when target specific environment values' do
115
+ let(:config_content) do
116
+ <<-YAML
117
+ environment:
118
+ - VAR1=VALUE_1
119
+ - VAR2=VALUE_2
120
+ targets:
121
+ build:
122
+ environment:
123
+ - VAR1=VALUE_OVERIDEN
124
+ - VAR3=VALUE_3
125
+ YAML
126
+ end
127
+
128
+ it do
129
+ is_expected.to contain_exactly(
130
+ 'VAR1=VALUE_OVERIDEN', 'VAR2=VALUE_2', 'VAR3=VALUE_3')
131
+ end
101
132
  end
102
133
  end
103
134
 
@@ -195,11 +226,13 @@ RSpec.describe Construi::Config do
195
226
  targets:
196
227
  build:
197
228
  image: build:image
229
+ privileged: true
198
230
  run: cmd1
199
231
  YAML
200
232
  end
201
233
 
202
234
  it { is_expected.to have_attributes(:image => 'build:image', :build => nil) }
235
+ it { expect(subject.options).to include(privileged: true) }
203
236
  end
204
237
 
205
238
  context 'when build for target and image for global' do
@@ -325,5 +358,80 @@ RSpec.describe Construi::Config do
325
358
  it { is_expected.to eq(false) }
326
359
  end
327
360
  end
361
+
362
+ describe '#links' do
363
+ subject { config.target('build').links }
364
+
365
+ context 'no links' do
366
+ let(:config_content) do
367
+ <<-YAML
368
+ targets:
369
+ build: cmd1
370
+ YAML
371
+ end
372
+
373
+ it { is_expected.to eq({}) }
374
+ end
375
+
376
+ context 'link on target' do
377
+ let(:config_content) do
378
+ <<-YAML
379
+ targets:
380
+ build:
381
+ run: cmd1
382
+ links:
383
+ db:
384
+ image: mysql
385
+ environment:
386
+ - VAR1=VALUE1
387
+ YAML
388
+ end
389
+
390
+ it { is_expected.to include('db') }
391
+ it { expect(subject['db'].image).to eq('mysql') }
392
+ it { expect(subject['db'].env).to contain_exactly('VAR1=VALUE1') }
393
+ end
394
+
395
+ context 'link on global level' do
396
+ let(:config_content) do
397
+ <<-YAML
398
+ links:
399
+ sshd:
400
+ build: construi/my-sshd
401
+ privileged: true
402
+ targets:
403
+ build:
404
+ run: cmd1
405
+ YAML
406
+ end
407
+
408
+ it { is_expected.to include('sshd') }
409
+ it { expect(subject['sshd'].build).to eq('construi/my-sshd') }
410
+ it { expect(subject['sshd'].privileged?).to be_truthy }
411
+ end
412
+
413
+ context 'multiple images' do
414
+ let(:config_content) do
415
+ <<-YAML
416
+ links:
417
+ sshd:
418
+ build: construi/my-sshd
419
+ targets:
420
+ build:
421
+ run: cmd1
422
+ links:
423
+ db:
424
+ image: mysql
425
+ nginx:
426
+ image: mydockers/nginx
427
+ YAML
428
+ end
429
+
430
+ it { is_expected.to include('sshd', 'db', 'nginx') }
431
+ it { expect(subject['db'].image).to eq('mysql') }
432
+ it { expect(subject['nginx'].image).to eq('mydockers/nginx') }
433
+ it { expect(subject['sshd'].build).to eq('construi/my-sshd') }
434
+ end
435
+ end
328
436
  end
329
437
 
@@ -30,22 +30,11 @@ RSpec.describe Construi::Container do
30
30
  .and_yield('stream', 'msg2')
31
31
  end
32
32
 
33
- subject! { attach_stdout.call }
33
+ subject! { attach_stdout.call.join }
34
34
 
35
35
  it { expect(docker_container).to have_received(:attach).with(:stream => true, :logs => true) }
36
- it { expect($stdout.string).to include("msg1\nmsg2") }
37
- end
38
-
39
- context 'when attach times out' do
40
- before do
41
- allow(docker_container)
42
- .to receive(:attach)
43
- .and_raise(Docker::Error::TimeoutError.new)
44
- end
45
-
46
- subject! { attach_stdout.call }
47
-
48
- it { expect($stdout.string).to include('Failed to attach to stdout'.yellow) }
36
+ it { expect($stdout.string).to include("msg1\n") }
37
+ it { expect($stdout.string).to include("msg2\n") }
49
38
  end
50
39
  end
51
40
 
@@ -69,8 +58,7 @@ RSpec.describe Construi::Container do
69
58
 
70
59
  subject! { run.call }
71
60
 
72
- it { expect(docker_container).to have_received(:start) }
73
- it { expect(docker_container).to have_received(:attach).with(:stream => true, :logs => true) }
61
+ it { expect(docker_container).to have_received(:start!) }
74
62
  it { expect(docker_container).to have_received(:wait) }
75
63
  it { expect(docker_container).to have_received(:commit) }
76
64
  it { is_expected.to eq(Construi::Image.wrap(image)) }
@@ -79,7 +67,7 @@ RSpec.describe Construi::Container do
79
67
  context 'when command fails' do
80
68
  let(:status_code) { 1 }
81
69
 
82
- it { expect { run.call }.to raise_error Construi::Container::Error, /status code: 1/}
70
+ it { expect { run.call }.to raise_error Construi::Container::RunError, /status code: 1/}
83
71
  end
84
72
  end
85
73
 
@@ -92,7 +80,7 @@ RSpec.describe Construi::Container do
92
80
  before { allow(docker_container_class).to receive(:create).and_return docker_container }
93
81
  before { allow(Dir).to receive(:pwd).and_return(pwd) }
94
82
 
95
- subject! { Construi::Container::create(image, cmd, env: env, privileged: privileged) }
83
+ subject! { Construi::Container::create image, cmd: cmd, env: env, privileged: privileged }
96
84
 
97
85
  it do
98
86
  expect(docker_container_class).to have_received(:create).with( {
@@ -103,7 +91,8 @@ RSpec.describe Construi::Container do
103
91
  'WorkingDir' => '/var/workspace',
104
92
  'HostConfig' => {
105
93
  'Binds' => ["#{pwd}:/var/workspace"],
106
- 'Privileged' => true
94
+ 'Privileged' => true,
95
+ 'Links' => []
107
96
  }
108
97
  } )
109
98
  end
@@ -117,7 +106,7 @@ RSpec.describe Construi::Container do
117
106
  before { allow(docker_container).to receive(:wait).and_return({'StatusCode' => 0}) }
118
107
  before { allow(docker_container).to receive(:commit).and_return image }
119
108
 
120
- subject! { Construi::Container.run(image, cmd) }
109
+ subject! { Construi::Container.run image, cmd: cmd }
121
110
 
122
111
  it do
123
112
  expect(docker_container_class).to have_received(:create).with(
@@ -127,9 +116,11 @@ RSpec.describe Construi::Container do
127
116
  }))
128
117
  end
129
118
  it { is_expected.to eq(Construi::Image.wrap image) }
130
- it { expect(docker_container).to have_received(:start) }
119
+ it { expect(docker_container).to have_received(:start!) }
131
120
  it { expect(docker_container).to have_received(:commit) }
132
- it { expect(docker_container).to have_received(:delete) }
121
+ it { expect(docker_container).to have_received(:stop) }
122
+ it { expect(docker_container).to have_received(:kill) }
123
+ it { expect(docker_container).to have_received(:delete).with(hash_including(v: true)) }
133
124
  end
134
125
 
135
126
  end
@@ -52,7 +52,7 @@ RSpec.describe Construi::Image do
52
52
 
53
53
  subject! { image.run(cmd, env: env) }
54
54
 
55
- it { expect(container).to have_received(:run).with(image, cmd, env: env) }
55
+ it { expect(container).to have_received(:run).with(image, cmd: cmd, env: env) }
56
56
  end
57
57
 
58
58
  describe '#insert_local' do
@@ -80,7 +80,7 @@ RSpec.describe Construi::Image do
80
80
  .to have_received(:insert_local)
81
81
  .with 'localPath' => host, 'outputPath' => container
82
82
  end
83
- it { expect(container_class).to have_received(:run).with(image, "ls -l #{container}", default_options) }
83
+ it { expect(container_class).to have_received(:run).with(image, default_options.merge(cmd: "ls -l #{container}")) }
84
84
  end
85
85
 
86
86
  context 'with permissions' do
@@ -95,16 +95,16 @@ RSpec.describe Construi::Image do
95
95
  it do
96
96
  expect(container_class)
97
97
  .to have_received(:run)
98
- .with(image, "chmod -R #{permissions} #{container}", {})
98
+ .with(image, cmd: "chmod -R #{permissions} #{container}")
99
99
  end
100
100
  it { expect($stdout.string).to include(" > chmod -R #{permissions} #{container}") }
101
- it { expect(container_class).to have_received(:run).with(image, "ls -l #{container}", default_options) }
101
+ it { expect(container_class).to have_received(:run).with(image, default_options.merge(cmd: "ls -l #{container}")) }
102
102
  end
103
103
 
104
104
  end
105
105
 
106
106
  describe '.from' do
107
- let(:config) { instance_double(Construi::Config::Environment).as_null_object }
107
+ let(:config) { instance_double(Construi::Config::BuildEnvironment).as_null_object }
108
108
 
109
109
  let(:image) { nil }
110
110
  let(:build) { nil }
@@ -191,7 +191,7 @@ RSpec.describe Construi::Image do
191
191
  end
192
192
 
193
193
  it 'outputs build status messages' do
194
- expect($stdout.string).to include("msg1\nmsg2\n")
194
+ expect($stdout.string).to match(/msg1\n.*msg2\n/)
195
195
  end
196
196
  end
197
197
 
@@ -220,7 +220,7 @@ RSpec.describe Construi::Image do
220
220
  end
221
221
 
222
222
  it 'outputs create status messages' do
223
- expect($stdout.string).to include("id: msg1\nid: msg2\nmsg4\n")
223
+ expect($stdout.string).to match(/id: msg1\n.*id: msg2\n.*msg4\n/)
224
224
  end
225
225
  end
226
226
  end
@@ -32,7 +32,7 @@ RSpec.describe Construi::Runner do
32
32
  subject! { runner.run(targets) }
33
33
 
34
34
  it { expect(docker).to have_received(:validate_version!) }
35
- it { expect(image).to have_received(:run).with('cmd1', env: [], privileged: false) }
35
+ it { expect(image).to have_received(:run).with('cmd1', env: [], privileged: false, links: [], name: 'target1') }
36
36
  it { expect(image).to have_received(:delete) }
37
37
 
38
38
  it { expect($stdout.string).to include('Running target1...'.green) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: construi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.39.0
4
+ version: 0.40.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-07-17 00:00:00.000000000 Z
12
+ date: 2015-07-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: docker-api
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 1.21.4
21
+ version: 1.22.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - '='
28
28
  - !ruby/object:Gem::Version
29
- version: 1.21.4
29
+ version: 1.22.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: colorize
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -243,6 +243,7 @@ files:
243
243
  - construi/site/run.sh
244
244
  - lib/construi.rb
245
245
  - lib/construi/config.rb
246
+ - lib/construi/console.rb
246
247
  - lib/construi/container.rb
247
248
  - lib/construi/image.rb
248
249
  - lib/construi/runner.rb
@@ -281,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
281
282
  version: '0'
282
283
  segments:
283
284
  - 0
284
- hash: 472212049094284394
285
+ hash: 2884457678396287104
285
286
  requirements: []
286
287
  rubyforge_project:
287
288
  rubygems_version: 1.8.23.2