puppet_docker_tools 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +88 -0
- data/bin/puppet-docker +90 -0
- data/lib/puppet_docker_tools.rb +8 -0
- data/lib/puppet_docker_tools/runner.rb +129 -0
- data/lib/puppet_docker_tools/utilities.rb +176 -0
- data/spec/lib/puppet_docker_tools/runner_spec.rb +164 -0
- data/spec/lib/puppet_docker_tools/utilities_spec.rb +198 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/context/using_alpine.rb +5 -0
- data/spec/support/context/using_centos.rb +5 -0
- data/spec/support/context/with_docker_container.rb +11 -0
- data/spec/support/context/with_docker_container_dummy_cmd.rb +18 -0
- data/spec/support/context/with_docker_image.rb +9 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/bin/puppet-docker
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
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
|