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 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