dockerspec 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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