dockerspec 0.2.0 → 0.3.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE +1 -1
  4. data/README.md +190 -24
  5. data/Rakefile +60 -6
  6. data/TODO.md +3 -2
  7. data/lib/dockerspec.rb +1 -2
  8. data/lib/dockerspec/builder.rb +13 -6
  9. data/lib/dockerspec/builder/config_helpers.rb +8 -3
  10. data/lib/dockerspec/builder/logger/ci.rb +2 -2
  11. data/lib/dockerspec/builder/matchers.rb +32 -51
  12. data/lib/dockerspec/configuration.rb +188 -0
  13. data/lib/dockerspec/docker_exception_parser.rb +2 -2
  14. data/lib/dockerspec/engine/base.rb +156 -0
  15. data/lib/dockerspec/engine/infrataster.rb +87 -0
  16. data/lib/dockerspec/engine/specinfra.rb +130 -0
  17. data/lib/dockerspec/engine/specinfra/backend.rb +163 -0
  18. data/lib/dockerspec/{serverspec/specinfra_hack.rb → engine/specinfra/backend_hack.rb} +17 -3
  19. data/lib/dockerspec/engine_list.rb +150 -0
  20. data/lib/dockerspec/exceptions.rb +13 -0
  21. data/lib/dockerspec/helper/multiple_sources_description.rb +3 -3
  22. data/lib/dockerspec/helper/rspec_example_helpers.rb +86 -6
  23. data/lib/dockerspec/infrataster.rb +26 -0
  24. data/lib/dockerspec/rspec.rb +21 -0
  25. data/lib/dockerspec/{rspec_configuration.rb → rspec/configuration.rb} +1 -1
  26. data/lib/dockerspec/rspec/resources.rb +602 -0
  27. data/lib/dockerspec/rspec/resources/its_container.rb +110 -0
  28. data/lib/dockerspec/{rspec_settings.rb → rspec/settings.rb} +5 -1
  29. data/lib/dockerspec/runner.rb +2 -357
  30. data/lib/dockerspec/runner/base.rb +367 -0
  31. data/lib/dockerspec/runner/compose.rb +322 -0
  32. data/lib/dockerspec/runner/docker.rb +302 -0
  33. data/lib/dockerspec/runner/serverspec.rb +21 -0
  34. data/lib/dockerspec/runner/serverspec/base.rb +185 -0
  35. data/lib/dockerspec/runner/serverspec/compose.rb +106 -0
  36. data/lib/dockerspec/runner/serverspec/docker.rb +116 -0
  37. data/lib/dockerspec/runner/serverspec/rspec.rb +20 -0
  38. data/lib/dockerspec/{serverspec/rspec_settings.rb → runner/serverspec/rspec/settings.rb} +1 -1
  39. data/lib/dockerspec/serverspec.rb +12 -2
  40. data/lib/dockerspec/version.rb +1 -1
  41. data/spec/spec_helper.rb +6 -2
  42. metadata +84 -15
  43. data/lib/dockerspec/rspec_assertions.rb +0 -54
  44. data/lib/dockerspec/rspec_resources.rb +0 -203
  45. data/lib/dockerspec/serverspec/rspec_resources.rb +0 -179
  46. data/lib/dockerspec/serverspec/runner.rb +0 -305
  47. data/lib/dockerspec/serverspec/specinfra_backend.rb +0 -128
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
  #
3
3
  # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
- # Copyright:: Copyright (c) 2015 Xabier de Zuazo
4
+ # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo
5
5
  # License:: Apache License, Version 2.0
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +23,7 @@ require 'specinfra/backend/base'
23
23
  # Add a method to the {Specinfra::Backend::Base} singleton class to set its
24
24
  # internal backend.
25
25
  #
26
- # TODO: This hack makes me want to poke my own eyes out.
26
+ # This hack makes me want to poke my own eyes out :-(
27
27
  #
28
28
  Specinfra::Backend::Base.class_eval do
29
29
  #
@@ -37,7 +37,21 @@ Specinfra::Backend::Base.class_eval do
37
37
  #
38
38
  def self.instance_set(instance)
39
39
  return if @instance == instance
40
- property[:host_inventory] = property[:os] = nil
40
+ host_reset
41
41
  @instance = instance
42
42
  end
43
+
44
+ #
45
+ # Resets the information stored for a host.
46
+ #
47
+ # - Host inventory.
48
+ # - Detected OS.
49
+ #
50
+ # @return void
51
+ #
52
+ # @api public
53
+ #
54
+ def self.host_reset
55
+ property[:host_inventory] = property[:os] = nil
56
+ end
43
57
  end
@@ -0,0 +1,150 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
+ # Copyright:: Copyright (c) 2016 Xabier de Zuazo
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'dockerspec/configuration'
21
+ require 'dockerspec/exceptions'
22
+
23
+ module Dockerspec
24
+ #
25
+ # Manages the list of testing engines to use.
26
+ #
27
+ class EngineList
28
+ #
29
+ # A message with description on how to avoid the error when you forget
30
+ # specifying the testing engine you want to use.
31
+ #
32
+ NO_ENGINES_MESSAGE = <<-EOE
33
+
34
+ Remember to include the Test Engine you want to use.
35
+
36
+ For example, to use Serverspec:
37
+
38
+ require 'dockerspec/serverspec'
39
+
40
+ EOE
41
+ .freeze
42
+
43
+ #
44
+ # Constructs the list of engines.
45
+ #
46
+ # Initializes all the selected engines.
47
+ #
48
+ # @param runner [Dockerspec::Runner::Base] The class used to run the
49
+ # docker container.
50
+ #
51
+ # @return [Dockerspec::EngineList] The list of engines.
52
+ #
53
+ # @raise [Dockerspec::EngineError] Raises this exception when the engine
54
+ # list is empty.
55
+ #
56
+ # @api public
57
+ #
58
+ def initialize(runner)
59
+ engine_classes = Configuration.engines
60
+ @engines =
61
+ engine_classes.map { |engine_class| engine_class.new(runner) }
62
+ assert_engines!
63
+ end
64
+
65
+ #
66
+ # Setups all the engines one by one.
67
+ #
68
+ # @param args [Mixed] Arguments to pass to the `#before_running` methods.
69
+ #
70
+ # @return void
71
+ #
72
+ # @api public
73
+ #
74
+ def before_running(*args)
75
+ call_engines_method(:before_running, *args)
76
+ end
77
+
78
+ #
79
+ # Notify the engines that the container to test is selected and ready.
80
+ #
81
+ # @param args [Mixed] Arguments to pass to the `#when_container_ready`
82
+ # methods.
83
+ #
84
+ # @return void
85
+ #
86
+ # @api public
87
+ #
88
+ def when_container_ready(*args)
89
+ call_engines_method(:when_container_ready, *args)
90
+ end
91
+
92
+ #
93
+ # Saves all the engines one by one.
94
+ #
95
+ # @param args [Mixed] Arguments to pass to the `#when_running` methods.
96
+ #
97
+ # @return void
98
+ #
99
+ # @api public
100
+ #
101
+ def when_running(*args)
102
+ call_engines_method(:when_running, *args)
103
+ end
104
+
105
+ #
106
+ # Restores all the engines one by one.
107
+ #
108
+ # @param args [Mixed] Arguments to pass to the `#restore` methods.
109
+ #
110
+ # @return void
111
+ #
112
+ # @api public
113
+ #
114
+ def restore(*args)
115
+ call_engines_method(:restore, *args)
116
+ end
117
+
118
+ protected
119
+
120
+ #
121
+ # Ensures that there has been chosen at least one engine.
122
+ #
123
+ # @return void
124
+ #
125
+ # @raise [Dockerspec::EngineError] Raises this exception when the engine
126
+ # list is empty.
127
+ #
128
+ # @api private
129
+ #
130
+ def assert_engines!
131
+ return unless @engines.empty?
132
+ raise EngineError, NO_ENGINES_MESSAGE
133
+ end
134
+
135
+ #
136
+ # Runs the same method on all the engines.
137
+ #
138
+ # @param method [String, Symbol] The method to run.
139
+ #
140
+ # @param args [Mixed] Arguments to pass to the methods.
141
+ #
142
+ # @return void
143
+ #
144
+ # @api private
145
+ #
146
+ def call_engines_method(method, *args)
147
+ @engines.map { |engine| engine.send(method, *args) }
148
+ end
149
+ end
150
+ end
@@ -24,8 +24,21 @@ module Dockerspec
24
24
  #
25
25
  class DockerRunArgumentError < ArgumentError; end
26
26
  #
27
+ # An exception message raised when there are errors related to Test Engines.
28
+ #
29
+ class EngineError < ArgumentError; end
30
+ #
31
+ # An exception message raised when there are errors related to the Runner.
32
+ #
33
+ class RunnerError < ArgumentError; end
34
+ #
27
35
  # An exception message raised when there are errors running Docker or
28
36
  # building Docker images.
29
37
  #
30
38
  class DockerError < ArgumentError; end
39
+ #
40
+ # An exception message raised when there is an error with the `its_container`
41
+ # resource.
42
+ #
43
+ class ItsContainerError < ArgumentError; end
31
44
  end
@@ -83,14 +83,14 @@ module Dockerspec
83
83
  # @api private
84
84
  #
85
85
  def description_from_docker(str)
86
- return str unless str.match(/^[0-9a-f]+$/)
86
+ return str unless str =~ /^[0-9a-f]+$/
87
87
  str[0..11]
88
88
  end
89
89
 
90
90
  #
91
91
  # Generates a description from Docker ID.
92
92
  #
93
- alias_method :description_from_id, :description_from_docker
93
+ alias description_from_id description_from_docker
94
94
 
95
95
  #
96
96
  # Generates a description from string.
@@ -136,7 +136,7 @@ module Dockerspec
136
136
  #
137
137
  # @api private
138
138
  #
139
- alias_method :description_from_path, :description_from_file
139
+ alias description_from_path description_from_file
140
140
  end
141
141
  end
142
142
  end
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
  #
3
3
  # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
- # Copyright:: Copyright (c) 2015 Xabier de Zuazo
4
+ # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo
5
5
  # License:: Apache License, Version 2.0
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,6 +23,36 @@ module Dockerspec
23
23
  # Some Helper methods to work with RSpec Examples.
24
24
  #
25
25
  module RSpecExampleHelpers
26
+ #
27
+ # Checks if the parent RSpec example information exists in the metadata.
28
+ #
29
+ # @param metadata [Hash] RSpec metadata.
30
+ #
31
+ # @return [Boolean] Returns true if the parent metadata is available.
32
+ #
33
+ # @api private
34
+ #
35
+ def self.metadata_has_parent?(metadata)
36
+ metadata.key?(:parent_example_group) || metadata.key?(:example_group)
37
+ end
38
+
39
+ #
40
+ # Get the parent RSpec example metadata if available.
41
+ #
42
+ # @param metadata [Hash] RSpec metadata.
43
+ #
44
+ # @return [Hash] RSpec metadata from the parent example.
45
+ #
46
+ # @api private
47
+ #
48
+ def self.metadata_parent(metadata)
49
+ if metadata.key?(:parent_example_group)
50
+ metadata[:parent_example_group]
51
+ elsif metadata.key?(:example_group)
52
+ metadata[:example_group]
53
+ end
54
+ end
55
+
26
56
  #
27
57
  # Searches for an object in the description of the parent RSpec examples.
28
58
  #
@@ -34,15 +64,65 @@ module Dockerspec
34
64
  # @api public
35
65
  #
36
66
  def self.search_object(metadata, klass)
37
- return metadata if metadata.nil?
67
+ return nil if metadata.nil?
38
68
  if metadata[:described_class].is_a?(klass)
39
69
  metadata[:described_class]
40
- elsif metadata.key?(:parent_example_group)
41
- search_object(metadata[:parent_example_group], klass)
42
- elsif metadata.key?(:example_group)
43
- search_object(metadata[:example_group], klass)
70
+ elsif metadata_has_parent?(metadata)
71
+ search_object(metadata_parent(metadata), klass)
44
72
  end
45
73
  end
74
+
75
+ #
76
+ # Searches for an object in the description of the parent RSpec examples
77
+ # that implements a specific method.
78
+ #
79
+ # @param metadata [Hash] RSpec metadata.
80
+ # @param meth [Symbol] The method name.
81
+ # @param arity [Integer] The arity of the method.
82
+ #
83
+ # @return [Array<Object>] Returns the objects list.
84
+ #
85
+ # @api public
86
+ #
87
+ def self.search_objects_with(metadata, meth, arity)
88
+ o_ary = []
89
+ return o_ary if metadata.nil?
90
+ if metadata[:described_class].respond_to?(meth) &&
91
+ metadata[:described_class].method(meth).arity == arity
92
+ o_ary << metadata[:described_class]
93
+ end
94
+ return o_ary unless metadata_has_parent?(metadata)
95
+ search_objects_with(metadata_parent(metadata), meth, arity) + o_ary
96
+ end
97
+
98
+ #
99
+ # Restores the Docker running container instance in the Specinfra
100
+ # internal reference.
101
+ #
102
+ # Gets the correct {Runner::Base} reference from the RSpec metadata.
103
+ #
104
+ # @example Restore Specinfra Backend
105
+ # RSpec.configure do |c|
106
+ # c.before(:each) do
107
+ # metadata = RSpec.current_example.metadata
108
+ # Dockerspec::Runner::Base.restore(metadata)
109
+ # end
110
+ # end
111
+ #
112
+ # @param metadata [Hash] RSpec metadata.
113
+ #
114
+ # @return void
115
+ #
116
+ # @api public
117
+ #
118
+ # @see restore
119
+ #
120
+ def self.restore_rspec_context(metadata)
121
+ o_ary =
122
+ Helper::RSpecExampleHelpers
123
+ .search_objects_with(metadata, :restore_rspec_context, 0)
124
+ o_ary.each(&:restore_rspec_context)
125
+ end
46
126
  end
47
127
  end
48
128
  end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
+ # Copyright:: Copyright (c) 2016 Xabier de Zuazo
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'dockerspec'
21
+ require 'dockerspec/engine/infrataster'
22
+
23
+ #
24
+ # Use Infrataster to run the tests (engine).
25
+ #
26
+ Dockerspec::Configuration.add_engine Dockerspec::Engine::Infrataster
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
+ # Copyright:: Copyright (c) 2016 Xabier de Zuazo
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'dockerspec/rspec/resources'
21
+ require 'dockerspec/rspec/configuration'
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
  #
3
3
  # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
- # Copyright:: Copyright (c) 2015 Xabier de Zuazo
4
+ # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo
5
5
  # License:: Apache License, Version 2.0
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -0,0 +1,602 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
+ # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'dockerspec/configuration'
21
+ require 'dockerspec/rspec/settings'
22
+ require 'dockerspec/builder'
23
+ require 'dockerspec/rspec/resources/its_container'
24
+ require 'dockerspec/exceptions'
25
+
26
+ module Dockerspec
27
+ #
28
+ # Some classes related to RSpec testing framework.
29
+ #
30
+ module RSpec
31
+ #
32
+ # Some resources included inside {RSpec::Core::ExampleGroup} to build and
33
+ # run Docker containers.
34
+ #
35
+ # ## Load the Test Engine You Want to Use
36
+ #
37
+ # If you want to run [Serverspec](http://serverspec.org/) tests, you need
38
+ # to require the `dockerspec/serverspec` path:
39
+ #
40
+ # ```ruby
41
+ # require 'dockerspec/serverspec'
42
+ # ```
43
+ #
44
+ # If you want to run [Infrataster](https://github.com/ryotarai/infrataster)
45
+ # tests, you need to require the `dockerspec/infrataster` path:
46
+ #
47
+ # ```ruby
48
+ # require 'dockerspec/infrataster'
49
+ # ```
50
+ #
51
+ # Of course, you can load both engines:
52
+ #
53
+ # ```ruby
54
+ # require 'dockerspec/serverspec'
55
+ # require 'dockerspec/infrataster'
56
+ # ```
57
+ #
58
+ # ## RSpec Settings
59
+ #
60
+ # * `dockerfile_path`: The dockerfile path.
61
+ # * `rm_build`: Whether to remove the build after the run.
62
+ # * `log_level`: Log level to use by default.
63
+ # * `docker_wait`: Seconds to wait before running the tests.
64
+ # * `container_name`: Docker container to test with Docker Compose.
65
+ #
66
+ # All the RSpec settings are optional.
67
+ #
68
+ # @example RSpec Settings
69
+ # RSpec.configure do |config|
70
+ # config.log_level = :silent
71
+ # end
72
+ #
73
+ module Resources
74
+ #
75
+ # Builds a Docker image.
76
+ #
77
+ # The image can be build from a path or from a string.
78
+ #
79
+ # See the {Dockerspec::Builder::ConfigHelpers} documentation for more
80
+ # information about the available RSpec resource helpers.
81
+ #
82
+ # @example A Simple Example
83
+ # describe 'My Dockerfile' do
84
+ # describe docker_build('.') do
85
+ # it { should have_maintainer /John Doe/ }
86
+ # it { should have_cmd ['/bin/dash'] }
87
+ # it { should have_expose '80' }
88
+ # end
89
+ # end
90
+ #
91
+ # @example A Complete Example
92
+ # describe docker_build(path: '.') do
93
+ # it { should have_maintainer 'John Doe "john.doe@example.com"' }
94
+ # it { should have_maintainer(/John Doe/) }
95
+ # it { should have_cmd %w(2 2000) }
96
+ # it { should have_label 'description' }
97
+ # it { should have_label 'description' => 'My Container' }
98
+ # it { should have_expose '80' }
99
+ # it { should have_expose(/80$/) }
100
+ # it { should have_env 'container' }
101
+ # it { should have_env 'container' => 'docker' }
102
+ # it { should have_env 'PATH' => '/tmp/bin:/sbin:/bin' }
103
+ # it { should have_entrypoint ['sleep'] }
104
+ # it { should have_volume '/volume1' }
105
+ # it { should have_volume %r{/vol.*2} }
106
+ # it { should have_user 'nobody' }
107
+ # it { should have_workdir '/opt' }
108
+ # it { should have_workdir %r{^/op} }
109
+ # it { should have_onbuild 'RUN echo onbuild' }
110
+ # it { should have_stopsignal 'SIGTERM' }
111
+ # end
112
+ #
113
+ # @example Checking the Attribute Values Using the `its` Method
114
+ # describe docker_build(path: '.') do
115
+ # its(:maintainer) { should eq 'John Doe "john.doe@example.com"' }
116
+ # its(:cmd) { should eq %w(2 2000) }
117
+ # its(:labels) { should include 'description' }
118
+ # its(:labels) { should include 'description' => 'My Container' }
119
+ # its(:exposes) { should include '80' }
120
+ # its(:env) { should include 'container' }
121
+ # its(:env) { should include 'container' => 'docker' }
122
+ # its(:entrypoint) { should eq ['sleep'] }
123
+ # its(:volumes) { should include '/volume1' }
124
+ # its(:user) { should eq 'nobody' }
125
+ # its(:workdir) { should eq '/opt' }
126
+ # its(:onbuilds) { should include 'RUN echo onbuild' }
127
+ # its(:stopsignal) { should eq 'SIGTERM' }
128
+ # end
129
+ #
130
+ # @example Checking Its Size and OS
131
+ # describe docker_build(path: '.') do
132
+ # its(:size) { should be < 20 * 2**20 } # 20M
133
+ # its(:arch) { should eq 'amd64' }
134
+ # its(:os) { should eq 'linux' }
135
+ # end
136
+ #
137
+ # @example Building from a File
138
+ # describe docker_build(path: '../dockerfiles/Dockerfile-nginx') do
139
+ # # [...]
140
+ # end
141
+ #
142
+ # @example Building from a Template
143
+ # describe docker_build(template: 'Dockerfile1.erb') do
144
+ # # [...]
145
+ # end
146
+ #
147
+ # @example Building from a Template with a Context
148
+ # describe docker_build(
149
+ # template: 'Dockerfile1.erb', context: {version: '8'}
150
+ # ) do
151
+ # it { should have_maintainer(/John Doe/) }
152
+ # it { should have_cmd %w(/bin/sh) }
153
+ # # [...]
154
+ # end
155
+ #
156
+ # @example Building from a String
157
+ # describe docker_build(string: "FROM nginx:1.9\n [...]") do
158
+ # # [...]
159
+ # end
160
+ #
161
+ # @example Building from a Docker Image ID
162
+ # describe docker_build(id: '07d362aea98d') do
163
+ # # [...]
164
+ # end
165
+ #
166
+ # @example Building from a Docker Image Name
167
+ # describe docker_build(id: 'nginx:1.9') do
168
+ # # [...]
169
+ # end
170
+ #
171
+ # @param opts [String, Hash] The `:path` or a list of options.
172
+ #
173
+ # @option opts [String] :path ('.') The directory or file that contains
174
+ # the *Dockerfile*. By default tries to read it from the
175
+ # `DOCKERFILE_PATH` environment variable and uses `'.'` if it is not
176
+ # set.
177
+ # @option opts [String] :string Use this string as *Dockerfile* instead of
178
+ # `:path`. Not set by default.
179
+ # @option opts [String] :template Use this [Erubis]
180
+ # (http://www.kuwata-lab.com/erubis/users-guide.html) template file as
181
+ # *Dockerfile*.
182
+ # @option opts [String] :id Use this Docker image ID instead of a
183
+ # *Dockerfile*.
184
+ # @option opts [Boolean] :rm Whether to remove the generated docker images
185
+ # after running the tests. By default only removes them if it is running
186
+ # on a CI machine.
187
+ # @option opts [Hash, Erubis::Context] :context ({}) Template *context*
188
+ # used when the `:template` source is used.
189
+ # @option opts [String] :tag Repository tag to be applied to the resulting
190
+ # image.
191
+ # @option opts [Fixnum, Symbol] :log_level Sets the docker library
192
+ # verbosity level. Possible values:
193
+ # `:silent` or `0` (no output),
194
+ # `:ci` or `1` (enables some outputs recommended for CI environments),
195
+ # `:info` or `2` (gives information about main build steps),
196
+ # `:debug` or `3` (outputs all the provided information in its raw
197
+ # original form).
198
+ #
199
+ # @return [Dockerspec::Builder] Builder object.
200
+ #
201
+ # @raise [Dockerspec::DockerError] For underlaying docker errors.
202
+ #
203
+ # @see Dockerspec::Builder::ConfigHelpers
204
+ #
205
+ # @api public
206
+ #
207
+ def docker_build(*opts)
208
+ builder = Dockerspec::Builder.new(*opts)
209
+ builder.build
210
+ described_image(builder.id)
211
+ builder
212
+ end
213
+
214
+ #
215
+ # Runs a docker image.
216
+ #
217
+ # See the [Serverspec Resource Types documentation]
218
+ # (http://serverspec.org/resource_types.html) to see the available
219
+ # resources.
220
+ #
221
+ # By default tries to detect the most appropriate Docker backend: native
222
+ # or LXC.
223
+ #
224
+ # @example A Basic Example to Test the HTTPd Service
225
+ # describe docker_build('.', tag: 'myapp') do
226
+ # describe docker_run('myapp') do
227
+ # describe service('httpd') do
228
+ # it { should be_enabled }
229
+ # it { should be_running }
230
+ # end
231
+ # # [...]
232
+ # end
233
+ # end
234
+ #
235
+ # @example Avoid Automatic OS Detection to Speed Up the Tests
236
+ # describe docker_build('.', tag: 'myapp') do
237
+ # describe docker_run('myapp', family: 'debian') do
238
+ # # [...]
239
+ # end
240
+ # end
241
+ #
242
+ # @example Using Hash Format
243
+ # describe docker_build('.', tag: 'myapp') do
244
+ # describe docker_run(tag: 'myapp', family: 'debian') do
245
+ # # [...]
246
+ # end
247
+ # end
248
+ #
249
+ # @example Force a Specific Docker Backend
250
+ # describe docker_build('.', tag: 'myapp') do
251
+ # describe docker_run('myapp', backend: :lxc) do
252
+ # # [...]
253
+ # end
254
+ # end
255
+ #
256
+ # @example Use a Backend Not Included by Default
257
+ # # specinfra-backend-docker_nsenter gem must be installed by hand
258
+ # require 'specinfra/backend/docker_nsenter'
259
+ # describe docker_build('.', tag: 'myapp') do
260
+ # describe docker_run('myapp', backend: :nsenter) do
261
+ # # [...]
262
+ # end
263
+ # end
264
+ #
265
+ # @example Running a Container Image Tag
266
+ # describe docker_run('debian:8') do
267
+ # # [...]
268
+ # end
269
+ #
270
+ # @example Testing `FROM` Dockerfile Instruction
271
+ # # FROM debian:8
272
+ # describe docker_run('myapp') do
273
+ # describe file('/etc/debian_version') do
274
+ # it { should be_file }
275
+ # its(:content) { should match /^8\./ }
276
+ # end
277
+ # # Another way to check it:
278
+ # describe command('lsb_release -ri') do
279
+ # its(:stdout) { should match /^Distributor ID:\s+Debian/ }
280
+ # its(:stdout) { should match /^Release:\s+8\./ }
281
+ # end
282
+ # end
283
+ #
284
+ # @example Testing `COPY` and `ADD` Dockerfile Instructions
285
+ # # COPY docker-entrypoint.sh /entrypoint.sh
286
+ # describe docker_run('myapp') do
287
+ # describe file('/entrypoint.sh') do
288
+ # it { should be_file }
289
+ # its(:content) { should match /^exec java -jar myapp\.jar/ }
290
+ # end
291
+ # end
292
+ #
293
+ # @example Testing `RUN` Dockerfile Instructions
294
+ # describe docker_run('myapp') do
295
+ # # RUN apt-get install -y wget
296
+ # describe package('wget') do
297
+ # it { should be_installed }
298
+ # end
299
+ # # RUN useradd -g myapp -d /opt/myapp myapp
300
+ # describe user('myapp') do
301
+ # it { should exist }
302
+ # it { should belong_to_group 'myapp' }
303
+ # it { should have_home_directory '/opt/myapp' }
304
+ # end
305
+ # end
306
+ #
307
+ # @example Testing with Infrataster
308
+ # require 'dockerspec/infrataster'
309
+ # describe docker_run('nginx') do
310
+ # describe server(described_container) do
311
+ # describe http('/') do
312
+ # it 'responds content including "Welcome to nginx!"' do
313
+ # expect(response.body).to include 'Welcome to nginx!'
314
+ # end
315
+ # it 'responds as "nginx" server' do
316
+ # expect(response.headers['server']).to match(/nginx/i)
317
+ # end
318
+ # end
319
+ # end
320
+ # end
321
+ #
322
+ # @param opts [String, Hash] The `:tag` or a list of options. This
323
+ # configuration options will be passed to the Testing Engines like
324
+ # Infrataster. So you can include your Infrataster server configuration
325
+ # here.
326
+ #
327
+ # @option opts [String] :tag The Docker image tag name to run.
328
+ # @option opts [String] :id The Docker container ID to use instead of
329
+ # starting a new container.
330
+ # @option opts [Boolean] :rm (calculated) Whether to remove the Docker
331
+ # container afterwards.
332
+ # @option opts [String] :path The environment `PATH` value of the
333
+ # container.
334
+ # @option opts [Hash, Array] :env Some `ENV` instructions to add to the
335
+ # container.
336
+ # @option opts [Symbol, String] :family (calculated) The OS family.
337
+ # It's automatically detected by default, but can be used to
338
+ # **speed up the tests**. Some possible values:
339
+ # `:alpine`, `:arch`, `:coreos`, `:debian`, `:gentoo`, `:nixos`,
340
+ # `:plamo`, `:poky`, `:redhat`, `:suse`.
341
+ # @option opts [Symbol] :backend (calculated) Docker backend to use:
342
+ # `:docker`, `:lxc`.
343
+ #
344
+ # @return [Dockerspec::Runner::Docker] Runner object.
345
+ #
346
+ # @raise [Dockerspec::DockerRunArgumentError] Raises this exception when
347
+ # some required fields are missing.
348
+ #
349
+ # @raise [Dockerspec::EngineError] Raises this exception when the engine
350
+ # list is empty.
351
+ #
352
+ # @raise [Dockerspec::DockerError] For underlaying docker errors.
353
+ #
354
+ # @api public
355
+ #
356
+ def docker_run(*opts)
357
+ runner = Dockerspec::Configuration.docker_runner.new(*opts)
358
+ runner.run
359
+ described_container(runner.container_name)
360
+ runner
361
+ end
362
+
363
+ #
364
+ # Runs Docker Compose.
365
+ #
366
+ # By default tries to detect the most appropriate Docker backend: native
367
+ # or LXC.
368
+ #
369
+ # @example Testing Containers from a YAML File
370
+ # describe docker_compose('docker-compose.yml', wait: 15) do
371
+ # its_container(:myapp) do
372
+ # describe process('apache2') do
373
+ # it { should be_running }
374
+ # end
375
+ # # [...]
376
+ # end
377
+ # its_container(:db) do
378
+ # describe process('mysqld') do
379
+ # it { should be_running }
380
+ # end
381
+ # # [...]
382
+ # end
383
+ # end
384
+ #
385
+ # @example Testing Only One Container from a Directory
386
+ # describe docker_compose('data/', container: :myapp, wait: 15) do
387
+ # describe process('apache2') do
388
+ # it { should be_running }
389
+ # end
390
+ # # [...]
391
+ # end
392
+ #
393
+ # @example Avoid Automatic OS Detection to Speed Up the Tests
394
+ # describe docker_compose(
395
+ # 'data/', container: :myapp, family: 'debian', wait: 15
396
+ # ) do
397
+ # describe process('apache2') do
398
+ # it { should be_running }
399
+ # end
400
+ # # [...]
401
+ # end
402
+ #
403
+ # @param opts [String, Hash] The `:file` or a list of options.
404
+ #
405
+ # @option opts [String] :file The compose YAML file or a directory
406
+ # containing the `'docker-compose.yml'` file.
407
+ # @option opts [Symbol, String] :container The name of the container to
408
+ # test. It is better to use
409
+ # {Dockerspec::RSpec::Resources#its_container} if you want to test
410
+ # multiple containers.
411
+ # @option opts [Boolean] :rm (calculated) Whether to remove the Docker
412
+ # containers afterwards.
413
+ # @option opts [Symbol, String] :family (calculated) The OS family.
414
+ # It's automatically detected by default, but can be used to
415
+ # **speed up the tests**. Some possible values:
416
+ # `:alpine`, `:arch`, `:coreos`, `:debian`, `:gentoo`, `:nixos`,
417
+ # `:plamo`, `:poky`, `:redhat`, `:suse`.
418
+ # @option opts [Symbol] :backend (calculated) Docker backend to use:
419
+ # `:docker_compose`, `:docker_compose_lxc`.
420
+ #
421
+ # @return [Dockerspec::Runner::Compose] Runner object.
422
+ #
423
+ # @raise [Dockerspec::DockerRunArgumentError] Raises this exception when
424
+ # some required fields are missing.
425
+ #
426
+ # @raise [Dockerspec::EngineError] Raises this exception when the engine
427
+ # list is empty.
428
+ #
429
+ # @raise [Dockerspec::DockerError] For underlaying docker errors.
430
+ #
431
+ # @api public
432
+ #
433
+ def docker_compose(*opts)
434
+ runner = Dockerspec::Configuration.compose_runner.new(*opts)
435
+ runner.run
436
+ end
437
+
438
+ #
439
+ # Selects the container to test inside {#docker_compose}.
440
+ #
441
+ # @example Testing Multiple Containers
442
+ # describe docker_compose('docker-compose.yml', wait: 15) do
443
+ # its_container(:myapp) do
444
+ # describe process('apache2') do
445
+ # it { should be_running }
446
+ # its(:args) { should match(/-DFOREGROUND/) }
447
+ # end
448
+ # # [...]
449
+ # end
450
+ # its_container(:db) do
451
+ # describe process('mysqld') do
452
+ # it { should be_running }
453
+ # end
454
+ # # [...]
455
+ # end
456
+ # end
457
+ #
458
+ # @example Avoid Automatic OS Detection to Speed Up the Tests
459
+ # describe docker_compose('docker-compose.yml', wait: 15) do
460
+ # its_container('myapp', family: 'centos') do
461
+ # describe process('httpd') do
462
+ # it { should be_running }
463
+ # end
464
+ # # [...]
465
+ # end
466
+ # its_container('db', family: 'debian') do
467
+ # describe process('mysqld') do
468
+ # it { should be_running }
469
+ # end
470
+ # # [...]
471
+ # end
472
+ # end
473
+ #
474
+ # @example Testing a Database with Infrataster using Docker Compose
475
+ # require 'dockerspec/infrataster'
476
+ # # After including `gem 'infrataster-plugin-mysql'` in your Gemfile:
477
+ # require 'infrataster-plugin-mysql'
478
+ # describe docker_compose('docker-compose.yml', wait: 60) do
479
+ # its_container(:db, mysql: { user: 'root', password: 'example' }) do
480
+ # describe server(described_container) do
481
+ # describe mysql_query('SHOW STATUS') do
482
+ # it 'returns positive uptime' do
483
+ # row = results.find { |r| r['Variable_name'] == 'Uptime' }
484
+ # expect(row['Value'].to_i).to be > 0
485
+ # end
486
+ # end
487
+ # end
488
+ # end
489
+ # end
490
+ #
491
+ # @param container [Symbol, String] The name of the container to test.
492
+ #
493
+ # @param opts [Hash] A list of options. This configuration options will
494
+ # be passed to the Testing Engines like Infrataster. So you can include
495
+ # your Infrataster server configuration here.
496
+ #
497
+ # @option opts [Symbol, String] :family (calculated) The OS family.
498
+ # It's automatically detected by default, but can be used to
499
+ # **speed up the tests**. Some possible values:
500
+ # `:alpine`, `:arch`, `:coreos`, `:debian`, `:gentoo`, `:nixos`,
501
+ # `:plamo`, `:poky`, `:redhat`, `:suse`.
502
+ #
503
+ # @return [Dockerspec::Runner::Compose] Runner object.
504
+ #
505
+ # @raise [Dockerspec::DockerComposeError] Raises this exception when
506
+ # you call `its_container` without calling `docker_compose`.
507
+ #
508
+ # @api public
509
+ #
510
+ def its_container(container, *opts, &block)
511
+ compose = Runner::Compose.current_instance
512
+ if compose.nil?
513
+ raise ItsContainerError, ItsContainer::NO_DOCKER_COMPOSE_MESSAGE
514
+ end
515
+ container_opts = opts[0].is_a?(Hash) ? opts[0] : {}
516
+ compose.select_container(container, container_opts)
517
+ described_container(compose.container_name)
518
+ describe(ItsContainer.new(container), *opts, &block)
519
+ end
520
+
521
+ #
522
+ # Sets or gets the latest run container name.
523
+ #
524
+ # This can be used to avoid adding a tag to the build image.
525
+ #
526
+ # @example
527
+ # describe docker_build('.') do
528
+ # describe docker_run(described_image, family: 'debian') do
529
+ # # [...]
530
+ # end
531
+ # end
532
+ #
533
+ # @param value [String] The docker image id.
534
+ #
535
+ # @return [String] The docker image id.
536
+ #
537
+ # @api public
538
+ #
539
+ def described_image(value = nil)
540
+ # rubocop:disable Style/ClassVars
541
+ @@described_image = value unless value.nil?
542
+ # rubocop:enable Style/ClassVars
543
+ @@described_image
544
+ end
545
+
546
+ #
547
+ # Sets or gets the latest run container name.
548
+ #
549
+ # Used to call the Infrataster {#server} method.
550
+ #
551
+ # @example Testing a Docker Container
552
+ # describe docker_run('myapp') do
553
+ # describe server(described_container) do
554
+ # describe http('/') do
555
+ # it 'responds content including "My App Homepage"' do
556
+ # expect(response.body).to match(/My App Homepage/i)
557
+ # end
558
+ # end
559
+ # end
560
+ # end
561
+ #
562
+ # @example Testing with Docker Compose
563
+ # describe docker_compose('docker-compose.yml', wait: 60) do
564
+ # its_container(:wordpress) do
565
+ # describe server(described_container) do
566
+ # describe http('/wp-admin/install.php') do
567
+ # it 'responds content including "Wordpress Installation"' do
568
+ # expect(response.body).to match(/WordPress .* Installation/i)
569
+ # end
570
+ # end
571
+ # end
572
+ # end
573
+ # end
574
+ #
575
+ # @param value [Symbol, String] The container name.
576
+ #
577
+ # @return [Symbol] The container name.
578
+ #
579
+ # @api public
580
+ #
581
+ def described_container(value = nil)
582
+ # rubocop:disable Style/ClassVars
583
+ @@described_container = value unless value.nil?
584
+ # rubocop:enable Style/ClassVars
585
+ @@described_container.to_sym
586
+ end
587
+ end
588
+ end
589
+ end
590
+
591
+ #
592
+ # Add the Dockerspec resources to RSpec core.
593
+ #
594
+ RSpec::Core::ExampleGroup.class_eval do
595
+ extend Dockerspec::RSpec::Resources
596
+ include Dockerspec::RSpec::Resources
597
+ end
598
+
599
+ #
600
+ # Allow using #docker_build in the outermost example
601
+ #
602
+ extend Dockerspec::RSpec::Resources