dockerspec 0.1.0

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