nutkins 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1bd8ba58c448c0926badec45b1f9b008e64f3b24
4
- data.tar.gz: 224e661371deed2e36281e9371370b7d3df757fb
3
+ metadata.gz: e73d665ef1da7e2713d6d7ab469d963f50969c39
4
+ data.tar.gz: 02399c0c0dca4636432ec49625978433b9e3f994
5
5
  SHA512:
6
- metadata.gz: c3a1d0b249945812bd1e62d16400f99f2d89ec7e1acac4c68f87c3f7096f861c9dad186e9a7e74e805ca1c7d7123149e28eb4e75dc68c09578bbc92bc5f4ed5c
7
- data.tar.gz: 667e711943593dd8f1d6b10b883225402dc6b5d8b6ded9a95abf90fdd1c517080dde6811ea58c012f2a1df29a8460268988e1c315b319db982e0b3c71d809d0c
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 *names', 'build docker image from dockerfile'
27
+ op.subcommand 'build,b *paths', 'build docker image[s] from nutkin.yamls/dockerfiles'
28
28
 
29
- op.subcommand 'create,c *names', 'create container from image' do |subop|
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 *names', 'delete container corresponding to image'
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 name', 'run created container' do |subop|
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 name *cmd', 'execute a command in a running container'
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 [*names]', 'extract secret files/volumes'
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.names.each &nutkins.method(:build)
65
+ config.paths.each &nutkins.method(:build)
66
66
  when 'create'
67
- config.names.each do |name|
68
- nutkins.create name, preserve: config.preserve, reuse: config.reuse
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.names.each &nutkins.method(:delete)
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.name, reuse: config.reuse, shell: config.shell
75
+ nutkins.run config.path, reuse: config.reuse, shell: config.shell
76
76
  when 'exec'
77
- nutkins.exec config.name, config.cmd
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.names
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
@@ -0,0 +1,14 @@
1
+ unless Hash.new.respond_to? :dig
2
+ class Hash
3
+ def dig first_key, *other_keys
4
+ val = self[first_key]
5
+ if other_keys.empty?
6
+ val
7
+ elsif val and val.kind_of? Hash
8
+ val.dig *other_keys
9
+ else
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end
@@ -2,21 +2,23 @@ require_relative "docker"
2
2
  require "json"
3
3
  require "digest"
4
4
 
5
- module Nutkins::DockerBuilder
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
- # Nutkins::Docker.run 'inspect', tag, stderr: false
13
+ # Docker.run 'inspect', tag, stderr: false
12
14
 
13
- unless Nutkins::Docker.run 'inspect', base, stderr: false
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 = Nutkins::Docker.get_short_commit Nutkins::Docker.container_id_for_name(base)
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 Nutkins::Docker.run 'run', parent_img_id, *run_shell_cmd, stdout: true
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 Nutkins::Docker.run 'cp', src, "#{cont_id}:#{add_files_dest}"
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 = Nutkins::Docker.run_get_stdout 'commit', *commit_args, cont_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 = Nutkins::Docker.get_short_commit parent_img_id
102
+ parent_img_id = Docker.get_short_commit parent_img_id
101
103
  ensure
102
- if not Nutkins::Docker.run 'rm', cont_id
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
- Nutkins::Docker.run 'tag', parent_img_id, cfg['tag']
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 = Nutkins::Docker.run_get_stdout('images', '-aq').split("\n")
120
- images_meta = JSON.parse(Nutkins::Docker.run_get_stdout('inspect', *all_images))
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
- Nutkins::Docker.get_short_commit(image_meta['Parent']) == parent_img_id
124
- return Nutkins::Docker.get_short_commit(image_meta['Id'])
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
@@ -1,3 +1,3 @@
1
1
  module Nutkins
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
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 img_name
35
- cfg = get_image_config img_name
36
- img_dir = get_project_dir img_name
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
- DockerBuilder::build cfg
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 #{img_name}"
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 img_name, preserve: false, docker_args: [], reuse: false
91
+ def create path, preserve: false, docker_args: [], reuse: false
75
92
  flags = []
76
- cfg = get_image_config img_name
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 = get_project_dir img_name
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 img_name
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 `#{img_name}' container"
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 `#{img_name}' container"
144
+ puts "created `#{path}' container"
128
145
  end
129
146
 
130
- def run img_name, reuse: false, shell: false
131
- cfg = get_image_config img_name
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 img_name, docker_args: create_args
170
+ create path, docker_args: create_args
152
171
  id = Docker.container_id_for_tag tag
153
- raise "couldn't create container to run `#{img_name}'" unless id
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 img_name
160
- cfg = get_image_config img_name
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 img_names
190
- if img_names.empty?
191
- img_names = get_all_img_names(img_names).push '.'
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
- img_names.each do |img_name|
195
- get_secrets(img_name).each do |secret|
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 img_name, *cmd
211
- puts "TODO: exec #{img_name}: #{cmd.join ' '}"
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
- img_names = get_all_img_names(img_names)
236
- configs = img_names.map &method(:get_image_config)
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
- if config.dig('etcd', 'files')
242
- config['etcd']['files'].each do |file|
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
- img_cfg = File.exists?(img_cfg_path) ? YAML.load_file(img_cfg_path) : {}
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 'missing mandatory version field' unless img_cfg.has_key? 'version'
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
- img_cfg
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 get_all_img_names img_names
331
- Dir.glob("#{@project_root}/*/Dockerfile").map do |path|
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
- # can supply img_name or . for project root
337
- def get_secrets img_name
338
- img_dir = get_project_dir img_name
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.9.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