nutkins 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/nutkins +13 -13
- data/lib/hash_dig.rb +14 -0
- data/lib/nutkins/docker_builder.rb +16 -14
- data/lib/nutkins/version.rb +1 -1
- data/lib/nutkins.rb +74 -41
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e73d665ef1da7e2713d6d7ab469d963f50969c39
|
4
|
+
data.tar.gz: 02399c0c0dca4636432ec49625978433b9e3f994
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d0cc75a3a1d4b93d7ffc50ebed8233e69d3c17577cabffe1f96e94a4aee7ac94432b683e479b5fdd83f11f390bd6c2003ce699d5dac4ed259429b54c4e6b140
|
7
|
+
data.tar.gz: eca9bb3a1db88257cf7813e0eb62fe2cab4c5ec7b41067ac4d691f010b9de3ac9a778f505ec1370c441a3ec4559a9e5faff6e9d0c92a7ac7eaba3630498f1256
|
data/bin/nutkins
CHANGED
@@ -24,24 +24,24 @@ module Nutkins::Command
|
|
24
24
|
|
25
25
|
op.on '-p', '--project dir', 'override path to project', 'project_dir'
|
26
26
|
|
27
|
-
op.subcommand 'build,b *
|
27
|
+
op.subcommand 'build,b *paths', 'build docker image[s] from nutkin.yamls/dockerfiles'
|
28
28
|
|
29
|
-
op.subcommand 'create,c *
|
29
|
+
op.subcommand 'create,c *paths', 'create container from image' do |subop|
|
30
30
|
subop.on '-r', '--reuse', 'reuse previously built image', 'reuse'
|
31
31
|
subop.on '-p', '--preserve', 'preserve existing container', 'preserve'
|
32
32
|
end
|
33
33
|
|
34
|
-
op.subcommand 'delete,d *
|
34
|
+
op.subcommand 'delete,d *paths', 'delete container corresponding to image'
|
35
35
|
op.subcommand 'delete-all', 'delete containers corresponding to all images'
|
36
36
|
|
37
|
-
op.subcommand 'run,r
|
37
|
+
op.subcommand 'run,r path', 'run created container' do |subop|
|
38
38
|
subop.on '-r', '--reuse', 'reuse previously created container', 'reuse'
|
39
39
|
subop.on '-s', '--shell', 'add bash shell to console', 'shell'
|
40
40
|
end
|
41
41
|
|
42
|
-
op.subcommand 'exec,e
|
42
|
+
op.subcommand 'exec,e path *cmd', 'execute a command in a running container'
|
43
43
|
op.subcommand 'build-secret,B path', 'build secret files/volumes'
|
44
|
-
op.subcommand 'extract-secrets,X [*
|
44
|
+
op.subcommand 'extract-secrets,X [*paths]', 'extract secret files/volumes'
|
45
45
|
|
46
46
|
op.subcommand 'start-etcd', 'start container running etcd'
|
47
47
|
op.subcommand 'stop-etcd', 'stop container running etcd'
|
@@ -62,23 +62,23 @@ module Nutkins::Command
|
|
62
62
|
|
63
63
|
case command
|
64
64
|
when 'build'
|
65
|
-
config.
|
65
|
+
config.paths.each &nutkins.method(:build)
|
66
66
|
when 'create'
|
67
|
-
config.
|
68
|
-
nutkins.create
|
67
|
+
config.paths.each do |path|
|
68
|
+
nutkins.create path, preserve: config.preserve, reuse: config.reuse
|
69
69
|
end
|
70
70
|
when 'delete'
|
71
|
-
config.
|
71
|
+
config.paths.each &nutkins.method(:delete)
|
72
72
|
when 'delete-all'
|
73
73
|
nutkins.delete_all
|
74
74
|
when 'run'
|
75
|
-
nutkins.run config.
|
75
|
+
nutkins.run config.path, reuse: config.reuse, shell: config.shell
|
76
76
|
when 'exec'
|
77
|
-
nutkins.exec config.
|
77
|
+
nutkins.exec config.path, config.cmd
|
78
78
|
when 'build-secret'
|
79
79
|
nutkins.build_secret config.path
|
80
80
|
when 'extract-secrets'
|
81
|
-
nutkins.extract_secrets config.
|
81
|
+
nutkins.extract_secrets config.paths
|
82
82
|
when 'start-etcd'
|
83
83
|
nutkins.start_etcd_container
|
84
84
|
when 'stop-etcd'
|
data/lib/hash_dig.rb
ADDED
@@ -2,21 +2,23 @@ require_relative "docker"
|
|
2
2
|
require "json"
|
3
3
|
require "digest"
|
4
4
|
|
5
|
-
module Nutkins::
|
5
|
+
module Nutkins::Docker::Builder
|
6
|
+
Docker = Nutkins::Docker
|
7
|
+
|
6
8
|
def self.build cfg
|
7
9
|
base = cfg["base"]
|
8
10
|
raise "to use build commands you must specify the base image" unless base
|
9
11
|
|
10
12
|
# TODO: build cache from this and use to determine restore point
|
11
|
-
#
|
13
|
+
# Docker.run 'inspect', tag, stderr: false
|
12
14
|
|
13
|
-
unless
|
15
|
+
unless Docker.run 'inspect', base, stderr: false
|
14
16
|
puts "getting base image"
|
15
17
|
Docker.run 'pull', base, stdout: true
|
16
18
|
end
|
17
19
|
|
18
20
|
# the base image to start rebuilding from
|
19
|
-
parent_img_id =
|
21
|
+
parent_img_id = Docker.get_short_commit Docker.container_id_for_name(base)
|
20
22
|
pwd = Dir.pwd
|
21
23
|
begin
|
22
24
|
Dir.chdir cfg["directory"]
|
@@ -80,7 +82,7 @@ module Nutkins::DockerBuilder
|
|
80
82
|
|
81
83
|
if run_args
|
82
84
|
puts "run #{run_args}"
|
83
|
-
unless
|
85
|
+
unless Docker.run 'run', parent_img_id, *run_shell_cmd, stdout: true
|
84
86
|
raise "run failed: #{run_args}"
|
85
87
|
end
|
86
88
|
|
@@ -88,18 +90,18 @@ module Nutkins::DockerBuilder
|
|
88
90
|
begin
|
89
91
|
if add_files
|
90
92
|
add_files.each do |src|
|
91
|
-
if not
|
93
|
+
if not Docker.run 'cp', src, "#{cont_id}:#{add_files_dest}"
|
92
94
|
raise "could not copy #{src} to #{cont_id}:#{add_files_dest}"
|
93
95
|
end
|
94
96
|
end
|
95
97
|
end
|
96
98
|
|
97
99
|
commit_args = env_args ? ['-c', env_args] : []
|
98
|
-
parent_img_id =
|
100
|
+
parent_img_id = Docker.run_get_stdout 'commit', *commit_args, cont_id
|
99
101
|
raise "could not commit docker image" if parent_img_id.nil?
|
100
|
-
parent_img_id =
|
102
|
+
parent_img_id = Docker.get_short_commit parent_img_id
|
101
103
|
ensure
|
102
|
-
if not
|
104
|
+
if not Docker.run 'rm', cont_id
|
103
105
|
puts "could not remove build container #{cont_id}"
|
104
106
|
end
|
105
107
|
end
|
@@ -112,16 +114,16 @@ module Nutkins::DockerBuilder
|
|
112
114
|
Dir.chdir pwd
|
113
115
|
end
|
114
116
|
|
115
|
-
|
117
|
+
Docker.run 'tag', parent_img_id, cfg['tag']
|
116
118
|
end
|
117
119
|
|
118
120
|
def self.find_cached_img_id parent_img_id, command
|
119
|
-
all_images =
|
120
|
-
images_meta = JSON.parse(
|
121
|
+
all_images = Docker.run_get_stdout('images', '-aq').split("\n")
|
122
|
+
images_meta = JSON.parse(Docker.run_get_stdout('inspect', *all_images))
|
121
123
|
images_meta.each do |image_meta|
|
122
124
|
if image_meta.dig('ContainerConfig', 'Cmd') == command and
|
123
|
-
|
124
|
-
return
|
125
|
+
Docker.get_short_commit(image_meta['Parent']) == parent_img_id
|
126
|
+
return Docker.get_short_commit(image_meta['Id'])
|
125
127
|
end
|
126
128
|
end
|
127
129
|
nil
|
data/lib/nutkins/version.rb
CHANGED
data/lib/nutkins.rb
CHANGED
@@ -13,6 +13,8 @@ require "nutkins/docker_builder"
|
|
13
13
|
require "nutkins/download"
|
14
14
|
require "nutkins/version"
|
15
15
|
|
16
|
+
require_relative "hash_dig"
|
17
|
+
|
16
18
|
# Must be somedomain.net instead of somedomain.net/, otherwise, it will throw exception.
|
17
19
|
module Nutkins
|
18
20
|
CONFIG_FILE_NAME = 'nutkins.yaml'
|
@@ -22,6 +24,9 @@ module Nutkins
|
|
22
24
|
|
23
25
|
class CloudManager
|
24
26
|
def initialize(project_dir: nil)
|
27
|
+
@img_configs = {}
|
28
|
+
# when an image is built true is stored against it's name to avoid building it again
|
29
|
+
@built = {}
|
25
30
|
@project_root = project_dir || Dir.pwd
|
26
31
|
cfg_path = File.join(@project_root, CONFIG_FILE_NAME)
|
27
32
|
if File.exists? cfg_path
|
@@ -31,12 +36,23 @@ module Nutkins
|
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
34
|
-
def build
|
35
|
-
cfg = get_image_config
|
36
|
-
img_dir =
|
39
|
+
def build path
|
40
|
+
cfg = get_image_config path
|
41
|
+
img_dir = cfg['directory']
|
42
|
+
img_name = cfg['image']
|
43
|
+
return if @built[img_name]
|
44
|
+
|
37
45
|
raise "directory `#{img_dir}' does not exist" unless Dir.exists? img_dir
|
38
46
|
tag = cfg['tag']
|
39
47
|
|
48
|
+
# TODO: flag to suppress building base image?
|
49
|
+
base = cfg['base']
|
50
|
+
unless @built[base]
|
51
|
+
if image_in_project? base
|
52
|
+
puts "building parent of #{img_name}: #{base}"
|
53
|
+
build base
|
54
|
+
end
|
55
|
+
end
|
40
56
|
prev_image_id = Docker.image_id_for_tag tag
|
41
57
|
|
42
58
|
build_cfg = cfg["build"]
|
@@ -48,11 +64,11 @@ module Nutkins
|
|
48
64
|
|
49
65
|
if cfg.dig "build", "commands"
|
50
66
|
# if build commands are available use nutkins built-in builder
|
51
|
-
|
67
|
+
Docker::Builder::build cfg
|
52
68
|
else
|
53
69
|
# fallback to `docker build` which is less good
|
54
70
|
if not Docker.run 'build', '-t', cfg['latest_tag'], '-t', tag, img_dir, stdout: true
|
55
|
-
raise "issue building docker image for #{
|
71
|
+
raise "issue building docker image for #{path}"
|
56
72
|
end
|
57
73
|
end
|
58
74
|
|
@@ -69,18 +85,19 @@ module Nutkins
|
|
69
85
|
else
|
70
86
|
puts "no image exists for image... what went wrong?"
|
71
87
|
end
|
88
|
+
@built[img_name] = true
|
72
89
|
end
|
73
90
|
|
74
|
-
def create
|
91
|
+
def create path, preserve: false, docker_args: [], reuse: false
|
75
92
|
flags = []
|
76
|
-
cfg = get_image_config
|
93
|
+
cfg = get_image_config path
|
77
94
|
create_cfg = cfg["create"]
|
78
95
|
if create_cfg
|
79
96
|
(create_cfg["ports"] or []).each do |port|
|
80
97
|
flags.push '-p', "#{port}:#{port}"
|
81
98
|
end
|
82
99
|
|
83
|
-
img_dir =
|
100
|
+
img_dir = cfg['directory']
|
84
101
|
(create_cfg["volumes"] or []).each do |volume|
|
85
102
|
src, dest = volume.split ' -> '
|
86
103
|
src_dir = File.absolute_path File.join(img_dir, VOLUMES_PATH, src)
|
@@ -108,12 +125,12 @@ module Nutkins
|
|
108
125
|
Docker.run "rm", prev_container_id
|
109
126
|
prev_container_id = nil
|
110
127
|
end
|
111
|
-
build
|
128
|
+
build path
|
112
129
|
end
|
113
130
|
|
114
131
|
puts "creating new docker image"
|
115
132
|
unless Docker.run "create", "-it", *flags, tag, *docker_args
|
116
|
-
raise "failed to create `#{
|
133
|
+
raise "failed to create `#{path}' container"
|
117
134
|
end
|
118
135
|
|
119
136
|
unless preserve
|
@@ -124,16 +141,18 @@ module Nutkins
|
|
124
141
|
end
|
125
142
|
end
|
126
143
|
|
127
|
-
puts "created `#{
|
144
|
+
puts "created `#{path}' container"
|
128
145
|
end
|
129
146
|
|
130
|
-
def run
|
131
|
-
cfg = get_image_config
|
147
|
+
def run path, reuse: false, shell: false
|
148
|
+
cfg = get_image_config path
|
132
149
|
tag = cfg['tag']
|
133
150
|
create_args = []
|
134
151
|
if shell
|
135
152
|
raise '--shell and --reuse arguments are incompatible' if reuse
|
136
153
|
|
154
|
+
# TODO: fix crash when image doesn't exist yet... the tag isn't
|
155
|
+
# there to be inspected yet
|
137
156
|
# TODO: test for smell-baron
|
138
157
|
create_args = JSON.parse(`docker inspect #{tag}`)[0]["Config"]["Cmd"]
|
139
158
|
|
@@ -148,21 +167,20 @@ module Nutkins
|
|
148
167
|
|
149
168
|
id = reuse && Docker.container_id_for_tag(tag)
|
150
169
|
unless id
|
151
|
-
create
|
170
|
+
create path, docker_args: create_args
|
152
171
|
id = Docker.container_id_for_tag tag
|
153
|
-
raise "couldn't create container to run `#{
|
172
|
+
raise "couldn't create container to run `#{path}'" unless id
|
154
173
|
end
|
155
174
|
|
156
175
|
Kernel.exec "docker", "start", "-ai", id
|
157
176
|
end
|
158
177
|
|
159
|
-
def delete
|
160
|
-
cfg = get_image_config
|
178
|
+
def delete path
|
179
|
+
cfg = get_image_config path
|
161
180
|
tag = cfg['tag']
|
162
181
|
container_id = Docker.container_id_for_tag tag
|
163
182
|
raise "no container to delete" if container_id.nil?
|
164
183
|
puts "deleting container #{container_id}"
|
165
|
-
# TODO: also delete :latest
|
166
184
|
Docker.run "rm", container_id
|
167
185
|
end
|
168
186
|
|
@@ -186,13 +204,15 @@ module Nutkins
|
|
186
204
|
File.unlink secret if path_is_dir
|
187
205
|
end
|
188
206
|
|
189
|
-
def extract_secrets
|
190
|
-
if
|
191
|
-
|
207
|
+
def extract_secrets img_dirs
|
208
|
+
if img_dirs.empty?
|
209
|
+
img_dirs = get_all_img_dirs
|
210
|
+
# there may be secrets in the root even if there is no image build there
|
211
|
+
img_dirs.push '.' unless img_dirs.include? '.'
|
192
212
|
end
|
193
213
|
|
194
|
-
|
195
|
-
get_secrets(
|
214
|
+
img_dirs.each do |img_dir|
|
215
|
+
get_secrets(img_dir).each do |secret|
|
196
216
|
loop do
|
197
217
|
puts "enter passphrase for #{secret}"
|
198
218
|
break if system 'gpg', secret
|
@@ -207,10 +227,11 @@ module Nutkins
|
|
207
227
|
end
|
208
228
|
end
|
209
229
|
|
210
|
-
def exec
|
211
|
-
puts "TODO: exec #{
|
230
|
+
def exec path, *cmd
|
231
|
+
puts "TODO: exec #{path}: #{cmd.join ' '}"
|
212
232
|
end
|
213
233
|
|
234
|
+
# TODO: move this stuff into another file
|
214
235
|
def start_etcd_container
|
215
236
|
name = get_etcd_container_name
|
216
237
|
return unless name
|
@@ -232,14 +253,15 @@ module Nutkins
|
|
232
253
|
'-advertise-client-urls', "http://#{gateway}:#{ETCD_PORT}",
|
233
254
|
'-listen-client-urls', "http://0.0.0.0:#{ETCD_PORT}"
|
234
255
|
|
235
|
-
|
236
|
-
configs =
|
256
|
+
img_dirs = get_all_img_dirs
|
257
|
+
configs = img_dirs.map &method(:get_image_config)
|
237
258
|
etcd_store = {}
|
238
259
|
configs.each do |config|
|
239
260
|
etcd_store.merge! config['etcd']['data'] if config.dig('etcd', 'data')
|
240
261
|
|
241
|
-
|
242
|
-
|
262
|
+
etcd_files = config.dig('etcd', 'files')
|
263
|
+
if etcd_files
|
264
|
+
etcd_files.each do |file|
|
243
265
|
etcd_data_path = File.join config['directory'], file
|
244
266
|
begin
|
245
267
|
etcd_store.merge! YAML.load_file(etcd_data_path)
|
@@ -296,19 +318,30 @@ module Nutkins
|
|
296
318
|
repository && "nutkins-etcd-#{repository}"
|
297
319
|
end
|
298
320
|
|
321
|
+
# path should be "." or a single element path referencing the project root
|
299
322
|
def get_image_config path
|
323
|
+
cached = @img_configs[path]
|
324
|
+
return cached if cached
|
325
|
+
|
300
326
|
directory = get_project_dir(path)
|
301
327
|
img_cfg_path = File.join directory, IMG_CONFIG_FILE_NAME
|
302
|
-
|
328
|
+
raise "missing #{img_cfg_path}" unless File.exists?(img_cfg_path)
|
329
|
+
img_cfg = YAML.load_file(img_cfg_path)
|
303
330
|
img_cfg['image'] ||= path if path != '.'
|
331
|
+
raise "#{img_cfg_path} must contain 'image' entry" unless img_cfg['image']
|
332
|
+
|
304
333
|
img_cfg['shell'] ||= '/bin/sh'
|
334
|
+
img_cfg['path'] ||= img_cfg_path
|
305
335
|
img_cfg['directory'] = directory
|
306
336
|
img_cfg["version"] ||= @config.version if @config.version
|
307
337
|
img_cfg['version'] = img_cfg['version'].to_s
|
308
|
-
raise
|
338
|
+
raise "#{img_cfg_path} must contain 'version' entry" unless img_cfg.has_key? 'version'
|
309
339
|
img_cfg['latest_tag'] = get_tag img_cfg
|
310
340
|
img_cfg['tag'] = img_cfg['latest_tag'] + ':' + img_cfg['version']
|
311
|
-
|
341
|
+
|
342
|
+
base = img_cfg['base']
|
343
|
+
raise "#{img_cfg_path} must include `base` field" unless base
|
344
|
+
@img_configs[path] = img_cfg
|
312
345
|
end
|
313
346
|
|
314
347
|
def get_project_dir path
|
@@ -316,10 +349,6 @@ module Nutkins
|
|
316
349
|
end
|
317
350
|
|
318
351
|
def get_tag img_cfg
|
319
|
-
unless img_cfg.has_key? "image"
|
320
|
-
raise "nutkins.yaml should contain `image' entry for this command"
|
321
|
-
end
|
322
|
-
|
323
352
|
repository = img_cfg['repository'] || @config.repository
|
324
353
|
if repository.nil?
|
325
354
|
raise "nutkins.yaml or nutkin.yaml should contain `repository' entry for this command"
|
@@ -327,15 +356,19 @@ module Nutkins
|
|
327
356
|
repository + '/' + img_cfg['image']
|
328
357
|
end
|
329
358
|
|
330
|
-
def
|
331
|
-
Dir.glob("#{@project_root}
|
359
|
+
def get_all_img_dirs
|
360
|
+
Dir.glob("#{@project_root}{,/*}/nutkin.yaml").map do |path|
|
332
361
|
File.basename File.dirname(path)
|
333
362
|
end
|
334
363
|
end
|
335
364
|
|
336
|
-
|
337
|
-
|
338
|
-
|
365
|
+
def image_in_project? image_name
|
366
|
+
get_all_img_dirs.map(&method(:get_image_config)).find do |cfg|
|
367
|
+
cfg['image'] == image_name
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def get_secrets img_dir
|
339
372
|
Dir.glob("#{img_dir}/{volumes,secrets}/*.gpg")
|
340
373
|
end
|
341
374
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nutkins
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Pike
|
@@ -84,6 +84,7 @@ files:
|
|
84
84
|
- bin/nutkins
|
85
85
|
- bin/setup
|
86
86
|
- circle.yml
|
87
|
+
- lib/hash_dig.rb
|
87
88
|
- lib/nutkins.rb
|
88
89
|
- lib/nutkins/docker.rb
|
89
90
|
- lib/nutkins/docker_builder.rb
|