dockerspec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +7 -0
  3. data/CHANGELOG.md +7 -0
  4. data/CONTRIBUTING.md +13 -0
  5. data/LICENSE +190 -0
  6. data/README.md +202 -0
  7. data/Rakefile +57 -0
  8. data/TESTING.md +30 -0
  9. data/TODO.md +6 -0
  10. data/lib/dockerspec.rb +21 -0
  11. data/lib/dockerspec/builder.rb +408 -0
  12. data/lib/dockerspec/builder/config_helpers.rb +425 -0
  13. data/lib/dockerspec/builder/image_gc.rb +71 -0
  14. data/lib/dockerspec/builder/logger.rb +56 -0
  15. data/lib/dockerspec/builder/logger/ci.rb +69 -0
  16. data/lib/dockerspec/builder/logger/debug.rb +47 -0
  17. data/lib/dockerspec/builder/logger/info.rb +111 -0
  18. data/lib/dockerspec/builder/logger/silent.rb +51 -0
  19. data/lib/dockerspec/builder/matchers.rb +134 -0
  20. data/lib/dockerspec/builder/matchers/helpers.rb +88 -0
  21. data/lib/dockerspec/docker_gem.rb +25 -0
  22. data/lib/dockerspec/exceptions.rb +26 -0
  23. data/lib/dockerspec/helper/ci.rb +61 -0
  24. data/lib/dockerspec/helper/docker.rb +44 -0
  25. data/lib/dockerspec/helper/multiple_sources_description.rb +142 -0
  26. data/lib/dockerspec/helper/rspec_example_helpers.rb +48 -0
  27. data/lib/dockerspec/rspec_assertions.rb +54 -0
  28. data/lib/dockerspec/rspec_resources.rb +198 -0
  29. data/lib/dockerspec/rspec_settings.rb +29 -0
  30. data/lib/dockerspec/runner.rb +374 -0
  31. data/lib/dockerspec/serverspec.rb +20 -0
  32. data/lib/dockerspec/serverspec/rspec_resources.rb +174 -0
  33. data/lib/dockerspec/serverspec/rspec_settings.rb +27 -0
  34. data/lib/dockerspec/serverspec/runner.rb +302 -0
  35. data/lib/dockerspec/serverspec/specinfra_backend.rb +128 -0
  36. data/lib/dockerspec/serverspec/specinfra_hack.rb +43 -0
  37. data/lib/dockerspec/version.rb +29 -0
  38. data/spec/spec_helper.rb +44 -0
  39. metadata +293 -0
data/TESTING.md ADDED
@@ -0,0 +1,30 @@
1
+ # Testing
2
+
3
+ ## Installing the Requirements
4
+
5
+ You can install gem dependencies with bundler:
6
+
7
+ $ gem install bundler
8
+ $ bundler install
9
+
10
+ ## Generate Documentation
11
+
12
+ $ bundle exec rake doc
13
+
14
+ This will generate the HTML documentation in the `doc/` directory.
15
+
16
+ ## All the Tests
17
+
18
+ $ bundle exec rake test
19
+
20
+ ## Running the Syntax Style Tests
21
+
22
+ $ bundle exec rake style
23
+
24
+ ## Running the Unit Tests
25
+
26
+ $ bundle exec rake unit
27
+
28
+ ## Running the Integration Tests
29
+
30
+ $ bundle exec rake integration
data/TODO.md ADDED
@@ -0,0 +1,6 @@
1
+ # TODO for Dockerspec
2
+
3
+ * [ ] Integrate with [Inspec](https://www.chef.io/inspec/).
4
+ * [ ] Integrate with [Infrataster](https://github.com/ryotarai/infrataster).
5
+ * [ ] Support Docker Compose.
6
+ * [ ] Add a Runner logger.
data/lib/dockerspec.rb ADDED
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
+ # Copyright:: Copyright (c) 2015 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/version'
21
+ require 'dockerspec/rspec_resources'
@@ -0,0 +1,408 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@zuazo.org>)
4
+ # Copyright:: Copyright (c) 2015 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 'rspec'
21
+ require 'rspec/its'
22
+ require 'tmpdir'
23
+ require 'erubis'
24
+ require 'dockerspec/docker_gem'
25
+ require 'dockerspec/builder/config_helpers'
26
+ require 'dockerspec/builder/matchers'
27
+ require 'dockerspec/builder/logger'
28
+ require 'dockerspec/builder/image_gc'
29
+ require 'dockerspec/helper/ci'
30
+ require 'dockerspec/helper/multiple_sources_description'
31
+
32
+ module Dockerspec
33
+ #
34
+ # A class to build a container image.
35
+ #
36
+ class Builder
37
+ include Dockerspec::Builder::ConfigHelpers
38
+ include Dockerspec::Helper::CI
39
+ include Dockerspec::Helper::MultipleSourcesDescription
40
+
41
+ #
42
+ # Constructs a Docker image builder class.
43
+ #
44
+ # @example Build an Image From CWD or `DOCKERFILE_PATH`
45
+ # Dockerspec::Builder.new #=> #<Dockerspec::Builder:0x0123>
46
+ #
47
+ # @example Build an Image from a Directory
48
+ # Dockerspec::Builder.new('imagedir') #=> #<Dockerspec::Builder:0x0124>
49
+ #
50
+ # @example Do Not Remove the Image
51
+ # Dockerspec::Builder.new('../', rm: false)
52
+ # #=> #<Dockerspec::Builder:0x0125>
53
+ #
54
+ # @example Passing Multiple Params
55
+ # Dockerspec::Builder.new(path: '../', tag: 'myapp', rm: false)
56
+ # #=> #<Dockerspec::Builder:0x0125>
57
+ #
58
+ # @param opts [String, Hash] The `:path` or a list of options.
59
+ #
60
+ # @option opts [String] :path ('.') The directory or file that contains the
61
+ # *Dockerfile*. By default tries to read it from the `DOCKERFILE_PATH`
62
+ # environment variable and uses `'.'` if it is not set.
63
+ # @option opts [String] :string Use this string as *Dockerfile* instead of
64
+ # `:path`. Not set by default.
65
+ # @option opts [String] :template Use this [Erubis]
66
+ # (http://www.kuwata-lab.com/erubis/users-guide.html) template file as
67
+ # *Dockerfile*.
68
+ # @option opts [String] :id Use this Docker image ID instead of a
69
+ # *Dockerfile*.
70
+ # @option opts [Boolean] :rm Whether to remove the generated docker images
71
+ # after running the tests. By default only removes them if it is running
72
+ # on a CI machine.
73
+ # @option opts [Hash, Erubis::Context] :context ({}) Template *context*
74
+ # used when the `:template` source is used.
75
+ # @option opts [String] :tag Repository tag to be applied to the resulting
76
+ # image.
77
+ # @option opts [Fixnum, Symbol] :log_level Sets the docker library
78
+ # verbosity level. Possible values:
79
+ # `:silent` or `0` (no output),
80
+ # `:ci` or `1` (enables some outputs recommended for CI environments),
81
+ # `:info` or `2` (gives information about main build steps),
82
+ # `:debug` or `3` (outputs all the provided information in its raw
83
+ # original form).
84
+ #
85
+ # @see Dockerspec::RSpecResources#docker_build
86
+ #
87
+ # @api public
88
+ #
89
+ def initialize(*opts)
90
+ @image = nil
91
+ @options = parse_options(opts)
92
+ end
93
+
94
+ #
95
+ # Returns Docker image ID.
96
+ #
97
+ # @example Get the Image ID After Building the Image
98
+ # d = Dockerspec::Builder.new
99
+ # d.build
100
+ # d.id #=> "9f8866b49bfb[...]"
101
+ #
102
+ # @return [String] Docker image ID.
103
+ #
104
+ # @api public
105
+ #
106
+ def id
107
+ @image.id
108
+ end
109
+
110
+ #
111
+ # Builds the docker image.
112
+ #
113
+ # @example Build an Image From a Path
114
+ # d = Dockerspec::Builder.new(path: 'dockerfile_dir')
115
+ # d.build #=> #<Dockerspec::Builder:0x0125>
116
+ #
117
+ # @return [String] Docker image ID.
118
+ #
119
+ # @api public
120
+ #
121
+ def build
122
+ send("build_from_#{source}", @options[source])
123
+ self
124
+ end
125
+
126
+ #
127
+ # Gets a descriptions of the object.
128
+ #
129
+ # @example
130
+ # d = Dockerspec::Builder.new('.')
131
+ # d.to_s #=> "Docker Build from path: ."
132
+ #
133
+ # @return [String] The object description.
134
+ #
135
+ # @api public
136
+ #
137
+ def to_s
138
+ description('Docker Build from')
139
+ end
140
+
141
+ protected
142
+
143
+ #
144
+ # Gets the source to generate the image from.
145
+ #
146
+ # Possible values: `:string`, `:template`, `:id`, `:path`.
147
+ #
148
+ # @example Building an Image from a Path
149
+ # self.source #=> :path
150
+ #
151
+ # @example Building an Image from a Template
152
+ # self.source #=> :template
153
+ #
154
+ # @return [Symbol] The source.
155
+ #
156
+ # @api private
157
+ #
158
+ def source
159
+ return @source unless @source.nil?
160
+ %i(string template id path).any? do |from|
161
+ next false unless @options.key?(from)
162
+ @source = from # Used for description
163
+ end
164
+ @source
165
+ end
166
+
167
+ #
168
+ # Generates a description when build from a template.
169
+ #
170
+ # @example
171
+ # self.description_from_template("file.erb") #=> "file.erb"
172
+ #
173
+ # @return [String] A description.
174
+ #
175
+ # @see Dockerspec::Helper::MultipleSourcesDescription#description_from_file
176
+ #
177
+ # @api private
178
+ #
179
+ alias_method :description_from_template, :description_from_file
180
+
181
+ #
182
+ # Sets or gets the Docker image.
183
+ #
184
+ # @param img [Docker::Image] The Docker image to set.
185
+ #
186
+ # @return [Docker::Image] The Docker image object.
187
+ #
188
+ # @api private
189
+ #
190
+ def image(img = nil)
191
+ return @image if img.nil?
192
+ ImageGC.instance.add(img.id) if @options[:rm]
193
+ @image = img
194
+ end
195
+
196
+ #
197
+ # Gets the default options configured using `RSpec.configuration`.
198
+ #
199
+ # @example
200
+ # self.rspec_options #=> {:path=>".", :rm=>true, :log_level=>:silent}
201
+ #
202
+ # @return [Hash] The configuration options.
203
+ #
204
+ # @api private
205
+ #
206
+ def rspec_options
207
+ config = RSpec.configuration
208
+ {}.tap do |opts|
209
+ opts[:path] = config.dockerfile_path if config.dockerfile_path?
210
+ opts[:rm] = config.rm_build if config.rm_build?
211
+ opts[:log_level] = config.log_level if config.log_level?
212
+ end
213
+ end
214
+
215
+ #
216
+ # Gets the default configuration options after merging them with RSpec
217
+ # configuration options.
218
+ #
219
+ # @example
220
+ # self.default_options #=> {:path=>".", :rm=>true, :log_level=>:silent}
221
+ #
222
+ # @return [Hash] The configuration options.
223
+ #
224
+ # @api private
225
+ #
226
+ def default_options
227
+ {
228
+ path: ENV['DOCKERFILE_PATH'] || '.',
229
+ # Autoremove images in all CIs except Travis (not supported):
230
+ rm: ci? && !travis_ci?,
231
+ # Avoid CI timeout errors:
232
+ log_level: ci? ? :ci : :silent
233
+ }.merge(rspec_options)
234
+ end
235
+
236
+ #
237
+ # Parses the configuration options passed to the constructor.
238
+ #
239
+ # @example
240
+ # self.parse_options #=> {:path=>".", :rm=>true, :log_level=>:silent}
241
+ #
242
+ # @param opts [Array<String, Hash>] The list of optitag. The strings will
243
+ # be interpreted as `:path`, others will be merged.
244
+ #
245
+ # @return [Hash] The configuration options.
246
+ #
247
+ # @see #initialize
248
+ #
249
+ # @api private
250
+ #
251
+ def parse_options(opts)
252
+ opts_hs_ary = opts.map { |x| x.is_a?(Hash) ? x : { path: x } }
253
+ opts_hs_ary.reduce(default_options) { |a, e| a.merge(e) }
254
+ end
255
+
256
+ #
257
+ # Generates the Ruby block used to parse the logs during image construction.
258
+ #
259
+ # @return [Proc] The Ruby block.
260
+ #
261
+ # @api private
262
+ #
263
+ def build_block
264
+ proc { |chunk| logger.print_chunk(chunk) }
265
+ end
266
+
267
+ #
268
+ # Builds the image from a string. Generates the Docker tag if required.
269
+ #
270
+ # It also saves the generated image in the object internally.
271
+ #
272
+ # This creates a temporary directory where it copies all the files and
273
+ # generates the temporary Dockerfile.
274
+ #
275
+ # @param string [String] The Dockerfile content.
276
+ #
277
+ # @return void
278
+ #
279
+ # @api private
280
+ #
281
+ def build_from_string(string, dir = '.')
282
+ Dir.mktmpdir do |tmpdir|
283
+ FileUtils.cp_r("#{dir}/.", tmpdir)
284
+ dockerfile = File.join(tmpdir, 'Dockerfile')
285
+ File.open(dockerfile, 'w') { |f| f.write(string) }
286
+ build_from_dir(tmpdir)
287
+ end
288
+ end
289
+
290
+ #
291
+ # Builds the image from a file that is not called *Dockerfile*.
292
+ #
293
+ # It also saves the generated image in the object internally.
294
+ #
295
+ # This creates a temporary directory where it copies all the files and
296
+ # generates the temporary Dockerfile.
297
+ #
298
+ # @param file [String] The Dockerfile file path.
299
+ #
300
+ # @return void
301
+ #
302
+ # @api private
303
+ #
304
+ def build_from_file(file)
305
+ dir = File.dirname(file)
306
+ string = IO.read(file)
307
+ build_from_string(string, dir)
308
+ end
309
+
310
+ #
311
+ # Builds the image from a directory with a Dockerfile.
312
+ #
313
+ # It also saves the generated image in the object internally.
314
+ #
315
+ # @param dir [String] The directory path.
316
+ #
317
+ # @return void
318
+ #
319
+ # @api private
320
+ #
321
+ def build_from_dir(dir)
322
+ image(::Docker::Image.build_from_dir(dir, &build_block))
323
+ add_respository_tag
324
+ end
325
+
326
+ #
327
+ # Builds the image from a directory or a file.
328
+ #
329
+ # It also saves the generated image in the object internally.
330
+ #
331
+ # @param path [String] The path.
332
+ #
333
+ # @return void
334
+ #
335
+ # @api private
336
+ #
337
+ def build_from_path(path)
338
+ if !File.directory?(path) && File.basename(path) == 'Dockerfile'
339
+ path = File.dirname(path)
340
+ end
341
+ File.directory?(path) ? build_from_dir(path) : build_from_file(path)
342
+ end
343
+
344
+ #
345
+ # Builds the image from a template.
346
+ #
347
+ # It also saves the generated image in the object internally.
348
+ #
349
+ # @param file [String] The Dockerfile [Erubis]
350
+ # (http://www.kuwata-lab.com/erubis/users-guide.html) template path.
351
+ #
352
+ # @return void
353
+ #
354
+ # @api private
355
+ #
356
+ def build_from_template(file)
357
+ context = @options[:context] || {}
358
+
359
+ dir = File.dirname(file)
360
+ template = IO.read(file)
361
+ eruby = Erubis::Eruby.new(template)
362
+ string = eruby.evaluate(context)
363
+ build_from_string(string, dir)
364
+ end
365
+
366
+ #
367
+ # Gets the image from a Image ID.
368
+ #
369
+ # It also saves the image in the object internally.
370
+ #
371
+ # @param id [String] The Docker image ID.
372
+ #
373
+ # @return void
374
+ #
375
+ # @api private
376
+ #
377
+ def build_from_id(id)
378
+ @image = ::Docker::Image.get(id)
379
+ add_respository_tag
380
+ rescue ::Docker::Error::NotFoundError
381
+ @image = ::Docker::Image.create('fromImage' => id)
382
+ end
383
+
384
+ #
385
+ # Adds a repository name and a tag to the Docker image.
386
+ #
387
+ # @return void
388
+ #
389
+ # @api private
390
+ #
391
+ def add_respository_tag
392
+ return unless @options.key?(:tag)
393
+ repo, repo_tag = @options[:tag].split(':', 2)
394
+ @image.tag(repo: repo, tag: repo_tag, force: true)
395
+ end
396
+
397
+ #
398
+ # Gets the Docker Logger to use during the build process.
399
+ #
400
+ # @return void
401
+ #
402
+ # @api private
403
+ #
404
+ def logger
405
+ @logger ||= Logger.instance(@options[:log_level])
406
+ end
407
+ end
408
+ end