puppet_docker_tools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5065c334b38bfde31cc9e305063afaab1fb057b3
4
+ data.tar.gz: 39a900482ea78b84b8affcb445148b5be494f884
5
+ SHA512:
6
+ metadata.gz: 217da3ed11d92e6421aa4702f4776a7e807fdae85c6049ab9dd611949c24b988ea060c3ac393edef517eafa61ccc1afc7d245a6f7e3356a8e666fcd6806f23b6
7
+ data.tar.gz: 2e23e6ed966dbbc28ac5b40a7187059b85873c2ad26a1903ab378d39bb5da145af50674625a022873551bddd94eabbef60e014691382fcbbbb84dc21640cb225
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,88 @@
1
+ # puppet_docker_tools
2
+
3
+ Utilities for building and publishing the docker images at hub.docker.com/u/puppet
4
+
5
+ ## Usage
6
+
7
+ ### Getting Started
8
+
9
+ `puppet_docker_tools` is packaged as a gem, so to get started either `gem install puppet_docker_tools` or add `gem 'puppet_docker_tools'` to your Gemfile.
10
+
11
+ You also need to have docker installed and running. See [these docs](https://docs.docker.com/get-started/) for more information on getting docker set up.
12
+
13
+ ```
14
+ $ puppet-docker help
15
+ Usage:
16
+ puppet-docker build [DIRECTORY] [--dockerfile=<dockerfile>] [--repository=<repo>] [--namespace=<namespace>] [--no-cache]
17
+ puppet-docker lint [DIRECTORY] [--dockerfile=<dockerfile>]
18
+ puppet-docker pull [IMAGE] [--repository=<repo>]
19
+ puppet-docker push [DIRECTORY] [--dockerfile=<dockerfile>] [--repository=<repo>] [--namespace=<namespace>]
20
+ puppet-docker rev-labels [DIRECTORY] [--dockerfile=<dockerfile>] [--namespace=<namespace>]
21
+ puppet-docker spec [DIRECTORY]
22
+ puppet-docker test [DIRECTORY] [--dockerfile=<dockerfile>]
23
+ puppet-docker version [DIRECTORY] [--dockerfile=<dockerfile>] [--namespace=<namespace>]
24
+ puppet-docker update-base-images [TAG]...
25
+ puppet-docker help
26
+
27
+ Arguments:
28
+ DIRECTORY Directory containing the Dockerfile for the image you're operating on. Defaults to $PWD
29
+ IMAGE The docker image to operate on, including namespace. Defaults to '<repo>/puppet_docker_tools'
30
+ TAG Pull latest versions of images at TAG. Defaults to ['ubuntu:16.04', 'centos:7', 'alpine:3.4', 'debian:9', 'postgres:9.6.8']
31
+
32
+ Options:
33
+ --repository=<repo> Dockerhub repository containing the image [default: puppet]
34
+ --no-cache Disable use of layer cache when building this image. Defaults to using the cache.
35
+ --namespace=<namespace> Namespace for labels on the container [default: org.label-schema]
36
+ --dockerfile=<dockerfile> File name for your dockerfile [default: Dockerfile]
37
+ ```
38
+
39
+ ### `puppet-docker build`
40
+
41
+ Build a docker image based on the dockerfile in DIRECTORY.
42
+
43
+ ### `puppet-docker lint`
44
+
45
+ Run [hadolint](https://github.com/hadolint/hadolint) on the dockerfile in DIRECTORY. The lint task runs on the `hadolint/hadolint` container with the following rule exclusions:
46
+ * [DL3008](https://github.com/hadolint/hadolint/wiki/DL3008) - Pin versions in apt get install
47
+ * [DL4000](https://github.com/hadolint/hadolint/wiki/DL4000) - MAINTAINER is deprecated
48
+ * [DL4001](https://github.com/hadolint/hadolint/wiki/DL4001) - Don't use both wget and curl
49
+
50
+ ### `puppet-docker pull`
51
+
52
+ Pull the specified image, i.e. 'puppet/puppetserver'. *NOTE*: If you don't include the tag you want to pull, `puppet-docker pull` will pull all tags for that image.
53
+
54
+ ### `puppet-docker push`
55
+
56
+ Push images built from the dockerfile in DIRECTORY to hub.docker.com. This task will fail if you do not have a version specified in your dockerfile. It will push both a versioned and a 'latest' tagged image.
57
+
58
+ ### `puppet-docker rev-labels`
59
+
60
+ Update `vcs-ref` and `build-date` labels in your dockerfile to current git sha and current UTC time.
61
+
62
+ ### `puppet-docker spec`
63
+
64
+ Run the rspec tests under DIRECTORY/spec. Will run tests on files matching `*_spec.rb`.
65
+
66
+ ### `puppet-docker test`
67
+
68
+ Shortcut to run both the `lint` and `spec` tasks.
69
+
70
+ ### `puppet-docker version`
71
+
72
+ Output the `version` label for the dockerfile contained in DIRECTORY.
73
+
74
+ ### `puppet-docker update-base-images`
75
+
76
+ Update the base images. Any number of tags to update can be passed, or by default it will pull the latest version of: ['ubuntu:16.04', 'centos:7', 'alpine:3.4', 'debian:9', 'postgres:9.6.8']
77
+
78
+ ## Issues
79
+
80
+ File issues in the [Community Package Repository (CPR) project](https://tickets.puppet.com/browse/CPR) with the 'Container' component.
81
+
82
+ ## License
83
+
84
+ See [LICENSE](LICENSE) file.
85
+
86
+ ## Maintainers
87
+
88
+ This project is maintained by the release engineering team <release@puppet.com> at Puppet, Inc.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ require 'docopt'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", 'puppet_docker_tools.rb'))
4
+ require 'puppet_docker_tools/runner'
5
+
6
+ doc = <<DOC
7
+ Utilities for building and releasing Puppet docker images.
8
+
9
+ Usage:
10
+ puppet-docker build [DIRECTORY] [--dockerfile=<dockerfile>] [--repository=<repo>] [--namespace=<namespace>] [--no-cache]
11
+ puppet-docker lint [DIRECTORY] [--dockerfile=<dockerfile>]
12
+ puppet-docker pull [IMAGE] [--repository=<repo>]
13
+ puppet-docker push [DIRECTORY] [--dockerfile=<dockerfile>] [--repository=<repo>] [--namespace=<namespace>]
14
+ puppet-docker rev-labels [DIRECTORY] [--dockerfile=<dockerfile>] [--namespace=<namespace>]
15
+ puppet-docker spec [DIRECTORY]
16
+ puppet-docker test [DIRECTORY] [--dockerfile=<dockerfile>]
17
+ puppet-docker version [DIRECTORY] [--dockerfile=<dockerfile>] [--namespace=<namespace>]
18
+ puppet-docker update-base-images [TAG]...
19
+ puppet-docker help
20
+
21
+ Arguments:
22
+ DIRECTORY Directory containing the Dockerfile for the image you're operating on. Defaults to $PWD
23
+ IMAGE The docker image to operate on, including namespace. Defaults to '<repo>/#{File.basename(Dir.pwd)}'
24
+ TAG Pull latest versions of images at TAG. Defaults to ['ubuntu:16.04', 'centos:7', 'alpine:3.4', 'debian:9', 'postgres:9.6.8']
25
+
26
+ Options:
27
+ --repository=<repo> Dockerhub repository containing the image [default: puppet]
28
+ --no-cache Disable use of layer cache when building this image. Defaults to using the cache.
29
+ --namespace=<namespace> Namespace for labels on the container [default: org.label-schema]
30
+ --dockerfile=<dockerfile> File name for your dockerfile [default: Dockerfile]
31
+ DOC
32
+
33
+ begin
34
+ options = Docopt::docopt(doc)
35
+ rescue Docopt::Exit
36
+ puts doc
37
+ exit
38
+ end
39
+
40
+ defaults = {
41
+ 'DIRECTORY' => Dir.pwd,
42
+ 'TAG' => ['ubuntu:16.04', 'centos:7', 'alpine:3.4', 'debian:9', 'postgres:9.6.8'],
43
+ '--namespace' => 'org.label-schema',
44
+ '--repository' => 'puppet',
45
+ '--dockerfile' => 'Dockerfile',
46
+ }
47
+
48
+ options.merge!(defaults) do |key, option, default|
49
+ if option.nil? || Array(option).empty?
50
+ default
51
+ else
52
+ option
53
+ end
54
+ end
55
+
56
+ if options['IMAGE'].nil?
57
+ options['IMAGE'] = "#{options['--repository']}/#{File.basename(options['DIRECTORY'])}"
58
+ end
59
+
60
+ begin
61
+ if options['help']
62
+ puts doc
63
+ elsif options['pull']
64
+ PuppetDockerTools::Utilities.pull(options['IMAGE'])
65
+ elsif options['update-base-images']
66
+ PuppetDockerTools::Utilities.update_base_images(options['TAG'])
67
+ else
68
+ command_runner = PuppetDockerTools::Runner.new(directory: options['DIRECTORY'], repository: options['--repository'], namespace: options['--namespace'], dockerfile: options['--dockerfile'])
69
+
70
+ if options['build']
71
+ command_runner.build(no_cache: options['--no-cache'])
72
+ elsif options['lint']
73
+ command_runner.lint
74
+ elsif options['push']
75
+ command_runner.push
76
+ elsif options['rev-labels']
77
+ command_runner.rev_labels
78
+ elsif options['spec']
79
+ command_runner.spec
80
+ elsif options['test']
81
+ command_runner.lint
82
+ command_runner.spec
83
+ elsif options['version']
84
+ command_runner.version
85
+ end
86
+ end
87
+ rescue => e
88
+ puts "ERROR:\n\t#{e}"
89
+ exit 1
90
+ end
@@ -0,0 +1,8 @@
1
+ LIBDIR = __dir__
2
+
3
+ $:.unshift(LIBDIR) unless
4
+ $:.include?(File.dirname(__FILE__)) || $:.include?(LIBDIR)
5
+
6
+ # The main entry point is {PuppetDockerTools::Run}
7
+ class PuppetDockerTools
8
+ end
@@ -0,0 +1,129 @@
1
+ require 'date'
2
+ require 'docker'
3
+ require 'rspec/core'
4
+ require 'time'
5
+ require 'puppet_docker_tools/utilities'
6
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec", 'spec_helper.rb'))
7
+
8
+ class PuppetDockerTools
9
+ class Runner
10
+ attr_accessor :directory, :repository, :namespace, :dockerfile
11
+
12
+ def initialize(directory: , repository: , namespace: , dockerfile: )
13
+ @directory = directory
14
+ @repository = repository
15
+ @namespace = namespace
16
+ @dockerfile = dockerfile
17
+
18
+ file = "#{directory}/#{dockerfile}"
19
+ fail "File #{file} doesn't exist!" unless File.exist? file
20
+ end
21
+
22
+ # Build a docker image from a directory
23
+ #
24
+ # @param no_cache Whether or not to use existing layer caches when building
25
+ # this image. Defaults to using the cache (no_cache = false).
26
+ def build(no_cache: false)
27
+ image_name = File.basename(directory)
28
+ version = PuppetDockerTools::Utilities.get_value_from_env('version', namespace: namespace, directory: directory, dockerfile: dockerfile)
29
+ path = "#{repository}/#{image_name}"
30
+ puts "Building #{path}:latest"
31
+
32
+ # 't' in the build_options sets the tag for the image we're building
33
+ build_options = { 't' => "#{path}:latest", 'dockerfile' => dockerfile }
34
+ if no_cache
35
+ puts "Ignoring cache for #{path}"
36
+ build_options['nocache'] = true
37
+ end
38
+ Docker::Image.build_from_dir(directory, build_options)
39
+
40
+ if version
41
+ puts "Building #{path}:#{version}"
42
+
43
+ # 't' in the build_options sets the tag for the image we're building
44
+ build_options = { 't' => "#{path}:#{version}", 'dockerfile' => dockerfile }
45
+ Docker::Image.build_from_dir(directory, build_options)
46
+ end
47
+ end
48
+
49
+ # Run hadolint on the Dockerfile in the specified directory. Hadolint is a
50
+ # linter for dockerfiles that also validates inline bash with shellcheck.
51
+ # For more info, see the github repo (https://github.com/hadolint/hadolint)
52
+ #
53
+ def lint
54
+ hadolint_container = 'hadolint/hadolint'
55
+ # make sure we have the container locally
56
+ PuppetDockerTools::Utilities.pull("#{hadolint_container}:latest")
57
+ container = Docker::Container.create('Cmd' => ['/bin/sh', '-c', "hadolint --ignore DL3008 --ignore DL4000 --ignore DL4001 - "], 'Image' => hadolint_container, 'OpenStdin' => true, 'StdinOnce' => true)
58
+ # This container.tap startes the container created above, and passes directory/Dockerfile to the container
59
+ container.tap(&:start).attach(stdin: "#{directory}/#{dockerfile}")
60
+ # Wait for the run to finish
61
+ container.wait
62
+ exit_status = container.json['State']['ExitCode']
63
+ unless exit_status == 0
64
+ fail container.logs(stdout: true, stderr: true)
65
+ end
66
+ end
67
+
68
+ # Push an image to hub.docker.com
69
+ #
70
+ def push
71
+ image_name = File.basename(directory)
72
+ path = "#{repository}/#{image_name}"
73
+ version = PuppetDockerTools::Utilities.get_value_from_label(path, value: 'version', namespace: namespace)
74
+
75
+ # We always want to push a versioned label in addition to the latest label
76
+ unless version
77
+ fail "No version specified in #{dockerfile} for #{path}"
78
+ end
79
+
80
+ puts "Pushing #{path}:#{version} to Docker Hub"
81
+ exitstatus, _ = PuppetDockerTools::Utilities.push_to_dockerhub("#{path}:#{version}")
82
+ unless exitstatus == 0
83
+ fail "Pushing #{path}:#{version} to dockerhub failed!"
84
+ end
85
+
86
+ puts "Pushing #{path}:latest to Docker Hub"
87
+ exitstatus, _ = PuppetDockerTools::Utilities.push_to_dockerhub("#{path}:latest")
88
+ unless exitstatus == 0
89
+ fail "Pushing #{path}:latest to dockerhub failed!"
90
+ end
91
+ end
92
+
93
+ # Update vcs-ref and build-date labels in the Dockerfile
94
+ #
95
+ def rev_labels
96
+ file = File.join(directory, dockerfile)
97
+
98
+ values_to_update = {
99
+ "#{namespace}.vcs-ref" => PuppetDockerTools::Utilities.current_git_sha(directory),
100
+ "#{namespace}.build-date" => Time.now.utc.iso8601
101
+ }
102
+
103
+ text = File.read(file)
104
+ values_to_update.each do |key, value|
105
+ original = text.clone
106
+ text = text.gsub(/#{key}=\"[a-z0-9A-Z\-:]*\"/, "#{key}=\"#{value}\"")
107
+ puts "Updating #{key} in #{file}" unless original == text
108
+ end
109
+
110
+ File.open(file, 'w') { |f| f.puts text }
111
+ end
112
+
113
+ # Run spec tests
114
+ #
115
+ def spec
116
+ tests = Dir.glob("#{directory}/spec/*_spec.rb")
117
+ test_files = tests.map { |test| File.basename(test, '.rb') }
118
+
119
+ puts "Running RSpec tests from #{File.expand_path("#{directory}/spec")} (#{test_files.join ","}), this may take some time"
120
+ RSpec::Core::Runner.run(tests, $stderr, $stdout)
121
+ end
122
+
123
+ # Get the version set in the Dockerfile in the specified directory
124
+ #
125
+ def version
126
+ puts PuppetDockerTools::Utilities.get_value_from_env('version', namespace: namespace, directory: directory, dockerfile: dockerfile)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,176 @@
1
+ require 'docker'
2
+ require 'open3'
3
+
4
+ class PuppetDockerTools
5
+ module Utilities
6
+ module_function
7
+
8
+ # Push an image to hub.docker.com
9
+ #
10
+ # @param image_name The image to push, including the tag e.g., puppet/puppetserver:latest
11
+ # @param stream_output Whether or not to stream output as it comes in, defaults to true
12
+ # @return Returns an array containing the integer exitstatus of the push
13
+ # command and a string containing the combined stdout and stderr
14
+ # from the push
15
+ def push_to_dockerhub(image_name, stream_output=true)
16
+ Open3.popen2e("docker push #{image_name}") do |stdin, output_stream, wait_thread|
17
+ output=''
18
+ while line = output_stream.gets
19
+ if stream_output
20
+ puts line
21
+ end
22
+ output += line
23
+ end
24
+ exit_status = wait_thread.value.exitstatus
25
+ return exit_status, output
26
+ end
27
+ end
28
+
29
+ # Get a value from the labels on a docker image
30
+ #
31
+ # @param image The docker image you want to get a value from, e.g. 'puppet/puppetserver'
32
+ # @param value The value you want to get from the labels, e.g. 'version'
33
+ # @param namespace The namespace for the value, e.g. 'org.label-schema'
34
+ def get_value_from_label(image, value: , namespace: )
35
+ labels = Docker::Image.get(image).json["Config"]["Labels"]
36
+ labels["#{namespace}.#{value.tr('_', '-')}"]
37
+ rescue
38
+ nil
39
+ end
40
+
41
+ # Get a value from a Dockerfile. Extrapolates variables and variables set in
42
+ # the base docker image
43
+ #
44
+ # @param label The label containing the value you want to retrieve, e.g. 'version'
45
+ # @param namespace The namespace for the label, e.g. 'org.label-schema'
46
+ # @param directory The directory containing the Dockerfile, defaults to $PWD
47
+ # @param dockerfile The file name for your dockerfile, defaults to 'Dockerfile'
48
+ def get_value_from_env(label, namespace: '', directory: '.', dockerfile: 'Dockerfile')
49
+ file = "#{directory}/#{dockerfile}"
50
+ fail "File #{file} doesn't exist!" unless File.exist? file
51
+ text = File.read(file)
52
+
53
+ value = text.scan(/#{Regexp.escape(namespace)}\.(.+)=(.+) \\?/).to_h[label]
54
+ # expand out environment variables
55
+ value = get_value_from_variable(value, directory: directory, dockerfile: dockerfile, dockerfile_contents: text) if value.start_with?('$')
56
+ # check in higher-level image if we didn't find it defined in this docker file
57
+ value = get_value_from_base_image(label, namespace: namespace, directory: directory, dockerfile: dockerfile) if value.nil?
58
+ # This gets rid of leading or trailing quotes
59
+ value.gsub(/\A"|"\Z/, '')
60
+ end
61
+
62
+ # Get the current git sha for the specified directory
63
+ #
64
+ # @param directory
65
+ def current_git_sha(directory = '.')
66
+ Dir.chdir directory do
67
+ `git rev-parse HEAD`.strip
68
+ end
69
+ end
70
+
71
+ # Convert timestamps from second since epoch to ISO 8601 timestamps. If the
72
+ # given timestamp is entirely numeric it will be converted to an ISO 8601
73
+ # timestamp, if not the parameter will be returned as passed.
74
+ #
75
+ # @param timestamp The timestamp to convert
76
+ def format_timestamp(timestamp)
77
+ if "#{timestamp}" =~ /^\d+$/
78
+ timestamp = Time.at(Integer(timestamp)).utc.iso8601
79
+ end
80
+ timestamp
81
+ end
82
+
83
+ # Pull a docker image
84
+ #
85
+ # @param image The image to pull. If the image does not include the tag to
86
+ # pull, it will pull all tags for that image
87
+ def pull(image)
88
+ if image.include?(':')
89
+ puts "Pulling #{image}"
90
+ PuppetDockerTools::Utilities.pull_single_tag(image)
91
+ else
92
+ puts "Pulling all tags for #{image}"
93
+ PuppetDockerTools::Utilities.pull_all_tags(image)
94
+ end
95
+ end
96
+
97
+ # Pull all tags for a docker image
98
+ #
99
+ # @param image The image to pull, e.g. puppet/puppetserver
100
+ def pull_all_tags(image)
101
+ Docker::Image.create('fromImage' => image)
102
+
103
+ # Filter through existing tags of that image so we can output what we pulled
104
+ images = Docker::Image.all('filter' => image)
105
+ images.each do |img|
106
+ timestamp = PuppetDockerTools::Utilities.format_timestamp(img.info["Created"])
107
+ puts "Pulled #{img.info["RepoTags"].join(', ')}, last updated #{timestamp}"
108
+ end
109
+ end
110
+
111
+ # Pull a single tag of a docker image
112
+ #
113
+ # @param tag The image/tag to pull, e.g. puppet/puppetserver:latest
114
+ def pull_single_tag(tag)
115
+ image = Docker::Image.create('fromImage' => tag)
116
+ timestamp = PuppetDockerTools::Utilities.format_timestamp(image.info["Created"])
117
+ puts "Pulled #{image.info["RepoTags"].first}, last updated #{timestamp}"
118
+ end
119
+
120
+ # Pull the specified tags
121
+ #
122
+ # @param tags [Array] A list of tags to pull, e.g. ['centos:7', 'ubuntu:16.04']
123
+ def update_base_images(tags)
124
+ tags.each do |tag|
125
+ PuppetDockerTools::Utilities.pull(tag)
126
+ end
127
+ end
128
+
129
+ # Get a value from a Dockerfile
130
+ #
131
+ # @param key The key to read from the Dockerfile, e.g. 'from'
132
+ # @param directory The directory containing the Dockerfile, defaults to $PWD
133
+ # @param dockerfile The file name for your dockerfile, defaults to 'Dockerfile'
134
+ # @param dockerfile_contents A string containing the contents of the Dockerfile [optional]
135
+ def get_value_from_dockerfile(key, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '')
136
+ if dockerfile_contents.empty?
137
+ file = "#{directory}/#{dockerfile}"
138
+ fail "File #{file} doesn't exist!" unless File.exist? file
139
+ dockerfile_contents = File.read("#{file}")
140
+ end
141
+ dockerfile_contents[/^#{key.upcase} (.*$)/, 1]
142
+ end
143
+ private :get_value_from_dockerfile
144
+
145
+ # Get a value from a container's base image
146
+ #
147
+ # @param value The value we want to get from this image's base image, e.g. 'version'
148
+ # @param namespace The namespace for the value, e.g. 'org.label-schema'
149
+ # @param directory The directory containing the Dockerfile, defaults to $PWD
150
+ # @param dockerfile The file name for your dockerfile, defaults to 'Dockerfile'
151
+ # @param dockerfile_contents A string containing the contents of the Dockerfile [optional]
152
+ def get_value_from_base_image(value, namespace:, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '')
153
+ base_image = get_value_from_dockerfile('from', directory: directory, dockerfile: dockerfile, dockerfile_contents: dockerfile_contents).split(':').first.split('/').last
154
+ get_value_from_env(value, namespace: namespace, directory: "#{directory}/../#{base_image}", dockerfile: dockerfile)
155
+ end
156
+ private :get_value_from_base_image
157
+
158
+ # Get a value from a variable in a Dockerfile
159
+ #
160
+ # @param variable The variable we want to look for in the Dockerfile, e.g. $PUPPET_SERVER_VERSION
161
+ # @param directory The directory containing the Dockerfile, defaults to $PWD
162
+ # @param dockerfile The file name for your dockerfile, defaults to 'Dockerfile'
163
+ # @param dockerfile_contents A string containing the contents of the Dockerfile [optional]
164
+ def get_value_from_variable(variable, directory: '.', dockerfile: 'Dockerfile', dockerfile_contents: '')
165
+ if dockerfile_contents.empty?
166
+ file = "#{directory}/#{dockerfile}"
167
+ fail "File #{file} doesn't exist!" unless File.exist? file
168
+ dockerfile_contents = File.read("#{file}")
169
+ end
170
+ # get rid of the leading $ for the variable
171
+ variable[0] = ''
172
+ dockerfile_contents[/#{variable}=(["a-zA-Z0-9\.]+)/, 1]
173
+ end
174
+ private :get_value_from_variable
175
+ end
176
+ end
@@ -0,0 +1,164 @@
1
+ require 'puppet_docker_tools'
2
+ require 'puppet_docker_tools/runner'
3
+ require 'docker'
4
+
5
+ describe PuppetDockerTools::Runner do
6
+ def create_runner(directory:, repository:, namespace:, dockerfile:)
7
+ allow(File).to receive(:exist?).with("#{directory}/#{dockerfile}").and_return(true)
8
+ PuppetDockerTools::Runner.new(directory: directory, repository: repository, namespace: namespace, dockerfile: dockerfile)
9
+ end
10
+
11
+ let(:runner) { create_runner(directory: '/tmp/test-image', repository: 'test', namespace: 'org.label-schema', dockerfile: 'Dockerfile') }
12
+
13
+ describe '#initialize' do
14
+ it "should fail if the dockerfile doesn't exist" do
15
+ allow(File).to receive(:exist?).with('/tmp/test-image/Dockerfile').and_return(false)
16
+ expect { PuppetDockerTools::Runner.new(directory: '/tmp/test-image', repository: 'test', namespace: 'org.label-schema', dockerfile: 'Dockerfile') }.to raise_error(RuntimeError, /doesn't exist/)
17
+ end
18
+ end
19
+
20
+ describe '#build' do
21
+ let(:image) { double(Docker::Image) }
22
+
23
+ it 'builds a latest and version tag if version is found' do
24
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_env).with('version', namespace: runner.namespace, directory: runner.directory, dockerfile: runner.dockerfile).and_return('1.2.3')
25
+ expect(Docker::Image).to receive(:build_from_dir).with(runner.directory, { 't' => 'test/test-image:1.2.3', 'dockerfile' => runner.dockerfile }).and_return(image)
26
+ expect(Docker::Image).to receive(:build_from_dir).with(runner.directory, { 't' => 'test/test-image:latest', 'dockerfile' => runner.dockerfile }).and_return(image)
27
+ runner.build
28
+ end
29
+
30
+ it 'builds just a latest tag if no version is found' do
31
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_env).with('version', namespace: runner.namespace, directory: runner.directory, dockerfile: runner.dockerfile).and_return(nil)
32
+ expect(Docker::Image).to receive(:build_from_dir).with(runner.directory, { 't' => 'test/test-image:latest', 'dockerfile' => runner.dockerfile }).and_return(image)
33
+ runner.build
34
+ end
35
+
36
+ it 'ignores the cache when that parameter is set' do
37
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_env).with('version', namespace: runner.namespace, directory: runner.directory, dockerfile: runner.dockerfile).and_return(nil)
38
+ expect(Docker::Image).to receive(:build_from_dir).with(runner.directory, { 't' => 'test/test-image:latest', 'nocache' => true, 'dockerfile' => runner.dockerfile }).and_return(image)
39
+ runner.build(no_cache: true)
40
+ end
41
+
42
+ it 'uses a custom dockerfile if passed' do
43
+ allow(File).to receive(:exist?).with('/tmp/test-image/Dockerfile.test').and_return(true)
44
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_env).with('version', namespace: 'org.label-schema', directory: '/tmp/test-image', dockerfile: 'Dockerfile.test').and_return(nil)
45
+ expect(Docker::Image).to receive(:build_from_dir).with('/tmp/test-image', { 't' => 'test/test-image:latest', 'dockerfile' => 'Dockerfile.test' }).and_return(image)
46
+ local_runner = create_runner(directory: '/tmp/test-image', repository: 'test', namespace: 'org.label-schema', dockerfile: 'Dockerfile.test')
47
+ local_runner.build
48
+ end
49
+ end
50
+
51
+ describe '#lint' do
52
+ let(:passing_exit) {
53
+ {
54
+ 'State' => {
55
+ 'ExitCode' => 0
56
+ }
57
+ }
58
+ }
59
+ let(:failing_exit) {
60
+ {
61
+ 'State' => {
62
+ 'ExitCode' => 1
63
+ }
64
+ }
65
+ }
66
+
67
+ let(:container) { double(Docker::Container).as_null_object }
68
+
69
+ before do
70
+ allow(PuppetDockerTools::Utilities).to receive(:pull).and_return(double(Docker::Image))
71
+ allow(Docker::Container).to receive(:create).and_return(container)
72
+ allow(container).to receive(:tap).and_return(container)
73
+ allow(container).to receive(:attach)
74
+ allow(container).to receive(:wait)
75
+ allow(container).to receive(:logs).and_return('container logs')
76
+ end
77
+
78
+ it "should lint the container" do
79
+ allow(container).to receive(:json).and_return(passing_exit)
80
+ runner.lint
81
+ end
82
+
83
+ it "should exit with exit status if something went wrong" do
84
+ allow(container).to receive(:json).and_return(failing_exit)
85
+ expect { runner.lint }.to raise_error(RuntimeError, /container logs/)
86
+ end
87
+ end
88
+
89
+ describe '#push' do
90
+ it 'should fail if no version is set' do
91
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_label).and_return(nil)
92
+ expect { runner.push }.to raise_error(RuntimeError, /no version/i)
93
+ end
94
+
95
+ it 'should raise an error if something bad happens pushing the versioned tag' do
96
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_label).with('test/test-image', value: 'version', namespace: runner.namespace).and_return('1.2.3')
97
+ expect(PuppetDockerTools::Utilities).to receive(:push_to_dockerhub).with('test/test-image:1.2.3').and_return([1, nil])
98
+ expect { runner.push }.to raise_error(RuntimeError, /1.2.3 to dockerhub/i)
99
+ end
100
+
101
+ it 'should raise an error if something bad happens pushing the latest tag' do
102
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_label).with('test/test-image', value: 'version', namespace: runner.namespace).and_return('1.2.3')
103
+ expect(PuppetDockerTools::Utilities).to receive(:push_to_dockerhub).with('test/test-image:1.2.3').and_return([0, nil])
104
+ expect(PuppetDockerTools::Utilities).to receive(:push_to_dockerhub).with('test/test-image:latest').and_return([1, nil])
105
+ expect { runner.push }.to raise_error(RuntimeError, /latest to dockerhub/i)
106
+ end
107
+
108
+ it 'should push the versioned and latest tags if nothing goes wrong' do
109
+ expect(PuppetDockerTools::Utilities).to receive(:get_value_from_label).with('test/test-image', value: 'version', namespace: runner.namespace).and_return('1.2.3')
110
+ expect(PuppetDockerTools::Utilities).to receive(:push_to_dockerhub).with('test/test-image:1.2.3').and_return([0, nil])
111
+ expect(PuppetDockerTools::Utilities).to receive(:push_to_dockerhub).with('test/test-image:latest').and_return([0, nil])
112
+ runner.push
113
+ end
114
+ end
115
+
116
+ describe '#rev_labels' do
117
+ let(:original_dockerfile) { <<-HERE
118
+ FROM ubuntu:16.04
119
+
120
+ ENV PUPPET_SERVER_VERSION="5.3.1" DUMB_INIT_VERSION="1.2.1" UBUNTU_CODENAME="xenial" PUPPETSERVER_JAVA_ARGS="-Xms256m -Xmx256m" PATH=/opt/puppetlabs/server/bin:/opt/puppetlabs/puppet/bin:/opt/puppetlabs/bin:$PATH PUPPET_HEALTHCHECK_ENVIRONMENT="production"
121
+
122
+ LABEL maintainer="Puppet Release Team <release@puppet.com>" \\
123
+ org.label-schema.vcs-ref="b75674e1fbf52f7821f7900ab22a19f1a10cafdb" \\
124
+ org.label-schema.build-date="2018-05-09T20:11:01Z"
125
+ HERE
126
+ }
127
+
128
+ let(:updated_dockerfile) { <<-HERE
129
+ FROM ubuntu:16.04
130
+
131
+ ENV PUPPET_SERVER_VERSION="5.3.1" DUMB_INIT_VERSION="1.2.1" UBUNTU_CODENAME="xenial" PUPPETSERVER_JAVA_ARGS="-Xms256m -Xmx256m" PATH=/opt/puppetlabs/server/bin:/opt/puppetlabs/puppet/bin:/opt/puppetlabs/bin:$PATH PUPPET_HEALTHCHECK_ENVIRONMENT="production"
132
+
133
+ LABEL maintainer="Puppet Release Team <release@puppet.com>" \\
134
+ org.label-schema.vcs-ref="8d7b9277c02f5925f5901e5aeb4df9b8573ac70e" \\
135
+ org.label-schema.build-date="2018-05-14T22:35:15Z"
136
+ HERE
137
+ }
138
+
139
+ it "should update vcs-ref and build-date" do
140
+ test_dir = Dir.mktmpdir('spec')
141
+ File.open("#{test_dir}/Dockerfile", 'w') { |file|
142
+ file.puts(original_dockerfile)
143
+ }
144
+ local_runner = create_runner(directory: test_dir, repository: 'test', namespace: 'org.label-schema', dockerfile: 'Dockerfile')
145
+ expect(PuppetDockerTools::Utilities).to receive(:current_git_sha).with(test_dir).and_return('8d7b9277c02f5925f5901e5aeb4df9b8573ac70e')
146
+ expect(Time).to receive(:now).and_return(Time.at(1526337315))
147
+ local_runner.rev_labels
148
+ expect(File.read("#{test_dir}/#{local_runner.dockerfile}")).to eq(updated_dockerfile)
149
+
150
+ # cleanup cleanup
151
+ FileUtils.rm("#{test_dir}/#{local_runner.dockerfile}")
152
+ FileUtils.rmdir(test_dir)
153
+ end
154
+ end
155
+
156
+ describe '#spec' do
157
+ it "runs tests under the 'spec' directory" do
158
+ tests=["/tmp/test-image/spec/test1_spec.rb", "/tmp/test-dir/spec/test2_spec.rb"]
159
+ expect(Dir).to receive(:glob).with("/tmp/test-image/spec/*_spec.rb").and_return(tests)
160
+ expect(RSpec::Core::Runner).to receive(:run).with(tests, $stderr, $stdout).and_return(nil)
161
+ runner.spec
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,198 @@
1
+ require 'puppet_docker_tools'
2
+ require 'puppet_docker_tools/utilities'
3
+
4
+ describe PuppetDockerTools::Utilities do
5
+ let(:dockerfile) { 'Dockerfile' }
6
+ let(:base_dockerfile_contents) { <<-HERE
7
+ FROM ubuntu:16.04
8
+
9
+ ENV PUPPET_SERVER_VERSION="5.3.1" DUMB_INIT_VERSION="1.2.1" UBUNTU_CODENAME="xenial" PUPPETSERVER_JAVA_ARGS="-Xms256m -Xmx256m" PATH=/opt/puppetlabs/server/bin:/opt/puppetlabs/puppet/bin:/opt/puppetlabs/bin:$PATH PUPPET_HEALTHCHECK_ENVIRONMENT="production"
10
+
11
+ LABEL maintainer="Puppet Release Team <release@puppet.com>" \\
12
+ org.label-schema.vendor="Puppet" \\
13
+ org.label-schema.url="https://github.com/puppetlabs/puppet-in-docker" \\
14
+ org.label-schema.name="Puppet Server (No PuppetDB)" \\
15
+ org.label-schema.license="Apache-2.0" \\
16
+ org.label-schema.version=$PUPPET_SERVER_VERSION \\
17
+ org.label-schema.vcs-url="https://github.com/puppetlabs/puppet-in-docker" \\
18
+ org.label-schema.vcs-ref="b75674e1fbf52f7821f7900ab22a19f1a10cafdb" \\
19
+ org.label-schema.build-date="2018-05-09T20:11:01Z" \\
20
+ org.label-schema.schema-version="1.0" \\
21
+ com.puppet.dockerfile="/Dockerfile"
22
+ HERE
23
+ }
24
+ let(:dockerfile_contents) { <<-HERE
25
+ FROM puppet/puppetserver-standalone:5.3.1
26
+
27
+ LABEL maintainer="Puppet Release Team <release@puppet.com>" \\
28
+ org.label-schema.vendor="Puppet" \\
29
+ org.label-schema.url="https://github.com/puppetlabs/puppet-in-docker" \\
30
+ org.label-schema.name="Puppet Server" \\
31
+ org.label-schema.license="Apache-2.0" \\
32
+ org.label-schema.version=$PUPPET_SERVER_VERSION \\
33
+ org.label-schema.vcs-url="https://github.com/puppetlabs/puppet-in-docker" \\
34
+ org.label-schema.vcs-ref="b75674e1fbf52f7821f7900ab22a19f1a10cafdb" \\
35
+ org.label-schema.build-date="2018-05-09T20:11:01Z" \\
36
+ org.label-schema.schema-version="1.0" \\
37
+ com.puppet.dockerfile="/Dockerfile"
38
+ HERE
39
+ }
40
+
41
+ let(:config_labels) { {
42
+ 'Config' => {
43
+ 'Labels' => {
44
+ 'org.label-schema.vendor' => 'Puppet',
45
+ 'org.label-schema.version' => '1.2.3',
46
+ 'org.label-schema.vcs-ref' => 'b75674e1fbf52f7821f7900ab22a19f1a10cafdb'
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ describe "#get_value_from_label" do
53
+ let(:image) { double(Docker::Image).as_null_object }
54
+
55
+ before do
56
+ allow(Docker::Image).to receive(:get).and_return(image)
57
+ allow(image).to receive(:json).and_return(config_labels)
58
+ end
59
+
60
+ it "returns the value of a label" do
61
+ expect(PuppetDockerTools::Utilities.get_value_from_label('puppet/puppetserver-test', value: 'vendor', namespace: 'org.label-schema')).to eq('Puppet')
62
+ end
63
+
64
+ it "replaces '_' with '-' in the label name" do
65
+ expect(PuppetDockerTools::Utilities.get_value_from_label('puppet/puppetserver-test', value: 'vcs_ref', namespace: 'org.label-schema')).to eq('b75674e1fbf52f7821f7900ab22a19f1a10cafdb')
66
+ end
67
+
68
+ it "returns nil if you ask for a value that isn't there" do
69
+ expect(PuppetDockerTools::Utilities.get_value_from_label('puppet/puppetserver-test', value: 'totes-not-a-value', namespace: 'org.label-schema')).to eq(nil)
70
+ end
71
+ end
72
+
73
+ describe "#get_value_from_env" do
74
+ it "should fail if the dockerfile doesn't exist" do
75
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(base_dockerfile_contents)
76
+ expect { PuppetDockerTools::Utilities.get_value_from_env('from', directory: '/tmp/test-dir')}.to raise_error(RuntimeError, /doesn't exist/)
77
+ end
78
+
79
+ it "calls get_value_from_variable if it looks like we have a variable" do
80
+ allow(File).to receive(:exist?).with("/tmp/test-dir/#{dockerfile}").and_return(true)
81
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(base_dockerfile_contents)
82
+ expect(PuppetDockerTools::Utilities.get_value_from_env('version', namespace: 'org.label-schema', directory: '/tmp/test-dir')).to eq('5.3.1')
83
+ end
84
+
85
+ it "calls get value_from_base_image if we didn't find the variable in our dockerfile" do
86
+ allow(File).to receive(:exist?).with("/tmp/test-dir/#{dockerfile}").and_return(true)
87
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(dockerfile_contents)
88
+ allow(File).to receive(:exist?).with("/tmp/test-dir/../puppetserver-standalone/#{dockerfile}").and_return(true)
89
+ allow(File).to receive(:read).with("/tmp/test-dir/../puppetserver-standalone/#{dockerfile}").and_return(base_dockerfile_contents)
90
+ expect(PuppetDockerTools::Utilities.get_value_from_env('version', namespace: 'org.label-schema', directory: '/tmp/test-dir')).to eq('5.3.1')
91
+ end
92
+ end
93
+
94
+ describe "#format_timestamp" do
95
+ it "ISO 8601 formats the timestamp if it's time since epoch" do
96
+ expect(PuppetDockerTools::Utilities.format_timestamp('1526069372')).to eq('2018-05-11T20:09:32Z')
97
+ end
98
+
99
+ it "Returns the passed timestamp if it doesn't look like it's time since epoch" do
100
+ expect(PuppetDockerTools::Utilities.format_timestamp('2018-05-11T20:09:32Z')).to eq('2018-05-11T20:09:32Z')
101
+ end
102
+ end
103
+
104
+ describe "get_value_from_dockerfile" do
105
+ it "reads the key from a passed string if dockerfile is passed" do
106
+ expect(PuppetDockerTools::Utilities.get_value_from_dockerfile('from', dockerfile_contents: base_dockerfile_contents)).to eq('ubuntu:16.04')
107
+ end
108
+
109
+ it "reads the key from a dockerfile if directory is passed" do
110
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(base_dockerfile_contents)
111
+ allow(File).to receive(:exist?).with("/tmp/test-dir/#{dockerfile}").and_return(true)
112
+ expect(PuppetDockerTools::Utilities.get_value_from_dockerfile('from', directory: '/tmp/test-dir')).to eq('ubuntu:16.04')
113
+ end
114
+
115
+ it "fails if the dockerfile doesn't exist" do
116
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(base_dockerfile_contents)
117
+ expect { PuppetDockerTools::Utilities.get_value_from_dockerfile('from', directory: '/tmp/test-dir')}.to raise_error(RuntimeError, /doesn't exist/)
118
+ end
119
+ end
120
+
121
+ describe "#get_value_from_base_image" do
122
+ before do
123
+ allow(File).to receive(:exist?).with("/tmp/test-dir/#{dockerfile}").and_return(true)
124
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(dockerfile_contents)
125
+ allow(File).to receive(:exist?).with("/tmp/test-dir/../puppetserver-standalone/#{dockerfile}").and_return(true)
126
+ allow(File).to receive(:read).with("/tmp/test-dir/../puppetserver-standalone/#{dockerfile}").and_return(base_dockerfile_contents)
127
+ end
128
+
129
+ it "Reads the value from the base image" do
130
+ expect(PuppetDockerTools::Utilities.get_value_from_base_image('version', namespace: 'org.label-schema', directory: '/tmp/test-dir')).to eq('5.3.1')
131
+ end
132
+ end
133
+
134
+ describe "#get_value_from_variable" do
135
+ it "reads the value from a passed string if dockerfile is passed" do
136
+ expect(PuppetDockerTools::Utilities.get_value_from_variable('$PUPPET_SERVER_VERSION', dockerfile_contents: base_dockerfile_contents)).to eq('"5.3.1"')
137
+ end
138
+
139
+ it "reads the value from a dockerfile if directory is passed" do
140
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(base_dockerfile_contents)
141
+ allow(File).to receive(:exist?).with("/tmp/test-dir/#{dockerfile}").and_return(true)
142
+ expect(PuppetDockerTools::Utilities.get_value_from_variable('$PUPPET_SERVER_VERSION', dockerfile_contents: base_dockerfile_contents)).to eq('"5.3.1"')
143
+ end
144
+
145
+ it "fails if the dockerfile doesn't exist" do
146
+ allow(File).to receive(:read).with("/tmp/test-dir/#{dockerfile}").and_return(base_dockerfile_contents)
147
+ expect { PuppetDockerTools::Utilities.get_value_from_variable('$PUPPET_SERVER_VERSION', directory: '/tmp/test-dir')}.to raise_error(RuntimeError, /doesn't exist/)
148
+ end
149
+ end
150
+
151
+ describe '#pull' do
152
+ it 'will pull a single image if the image has a tag' do
153
+ expect(PuppetDockerTools::Utilities).to receive(:pull_single_tag).with('test/test-dir:latest')
154
+ PuppetDockerTools::Utilities.pull('test/test-dir:latest')
155
+ end
156
+
157
+ it 'will pull all the images if no tag is passed' do
158
+ expect(PuppetDockerTools::Utilities).to receive(:pull_all_tags).with('test/test-dir')
159
+ PuppetDockerTools::Utilities.pull('test/test-dir')
160
+ end
161
+ end
162
+
163
+ describe '#pull_all_tags' do
164
+ let(:image_info) {
165
+ {
166
+ 'Created' => '2018-05-11T20:09:32Z',
167
+ 'RepoTags' => ['latest', '1.2.3'],
168
+ }
169
+ }
170
+
171
+ let(:image) { double(Docker::Image) }
172
+ let(:images) { [image] }
173
+
174
+ it 'pulls the tags' do
175
+ expect(Docker::Image).to receive(:create).with('fromImage' => 'test/test-dir')
176
+ expect(Docker::Image).to receive(:all).and_return(images)
177
+ expect(image).to receive(:info).and_return(image_info).twice
178
+ PuppetDockerTools::Utilities.pull_all_tags('test/test-dir')
179
+ end
180
+ end
181
+
182
+ describe '#pull_single_tag' do
183
+ let(:image_info) {
184
+ {
185
+ 'Created' => '2018-05-11T20:09:32Z',
186
+ 'RepoTags' => ['1.2.3'],
187
+ }
188
+ }
189
+ let(:image) { double(Docker::Image) }
190
+
191
+ it 'pulls the single tag' do
192
+ expect(Docker::Image).to receive(:create).with('fromImage' => 'test/test-dir:1.2.3').and_return(image)
193
+ expect(image).to receive(:info).and_return(image_info).twice
194
+ PuppetDockerTools::Utilities.pull_single_tag('test/test-dir:1.2.3')
195
+ end
196
+ end
197
+
198
+ end
@@ -0,0 +1,8 @@
1
+ require 'serverspec'
2
+ require 'docker'
3
+
4
+ # Travis builds can take time
5
+ Docker.options[:read_timeout] = 7200
6
+
7
+ # Load any shared examples or context helpers
8
+ Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].sort.each { |f| require f }
@@ -0,0 +1,5 @@
1
+ shared_context 'using alpine' do
2
+ before(:all) do
3
+ @os = :alpine
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ shared_context 'using centos' do
2
+ before(:all) do
3
+ @os = :redhat
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ shared_context 'with a docker container' do
2
+ before(:all) do
3
+ @container = Docker::Container.create('Image' => @image.id)
4
+ @container.start
5
+ end
6
+
7
+ after(:all) do
8
+ @container.kill
9
+ @container.delete(force: true)
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ shared_context 'with a docker container with a dummy cmd' do
2
+ before(:all) do
3
+ @image = Docker::Image.build_from_dir(CURRENT_DIRECTORY)
4
+ @container = Docker::Container.create(
5
+ 'Image' => @image.id,
6
+ 'Cmd' => ['sh', '-c', 'while true; do sleep 1; done']
7
+ )
8
+ @container.start
9
+
10
+ set :backend, :docker
11
+ set :docker_container, @container.id
12
+ end
13
+
14
+ after(:all) do
15
+ @container.kill
16
+ @container.delete(force: true)
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ shared_context 'with a docker image' do
2
+ before(:all) do
3
+ @image = Docker::Image.build_from_dir(CURRENT_DIRECTORY)
4
+
5
+ set :os, family: @os || :debian
6
+ set :backend, :docker
7
+ set :docker_image, @image.id
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet_docker_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Puppet, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: docker-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.34'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.34'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: serverspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.41'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.41'
55
+ - !ruby/object:Gem::Dependency
56
+ name: docopt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
69
+ description: Utilities for building and publishing the docker images at https://hub.docker.com/u/puppet
70
+ email: release@puppet.com
71
+ executables:
72
+ - puppet-docker
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - bin/puppet-docker
79
+ - lib/puppet_docker_tools.rb
80
+ - lib/puppet_docker_tools/runner.rb
81
+ - lib/puppet_docker_tools/utilities.rb
82
+ - spec/lib/puppet_docker_tools/runner_spec.rb
83
+ - spec/lib/puppet_docker_tools/utilities_spec.rb
84
+ - spec/spec_helper.rb
85
+ - spec/support/context/using_alpine.rb
86
+ - spec/support/context/using_centos.rb
87
+ - spec/support/context/with_docker_container.rb
88
+ - spec/support/context/with_docker_container_dummy_cmd.rb
89
+ - spec/support/context/with_docker_image.rb
90
+ homepage: https://github.com/puppetlabs/puppet_docker_tools
91
+ licenses:
92
+ - Apache-2.0
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.1'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.2.5
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Puppet tools for building docker images
114
+ test_files:
115
+ - spec/lib/puppet_docker_tools/runner_spec.rb
116
+ - spec/lib/puppet_docker_tools/utilities_spec.rb