cide 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ require 'virtus'
2
+
3
+ require 'erb'
4
+ require 'yaml'
5
+
6
+ require 'cide/config_file_loader'
7
+
8
+ module CIDE
9
+ class ConfigFile
10
+ DOCKERFILE_TEMPLATE =
11
+ File.expand_path('../dockerfile_template.erb', __FILE__)
12
+
13
+ module NiceInspect
14
+ def inspect
15
+ out = []
16
+ attributes.each_pair do |key, value|
17
+ out << "#{key}=#{value.inspect}"
18
+ end
19
+ "<#{out.join(' ')}>"
20
+ end
21
+ end
22
+
23
+ class FileAdd
24
+ include Virtus.model(strict: true)
25
+ include NiceInspect
26
+ attribute :src, Array[String], default: []
27
+ attribute :dest, String
28
+ end
29
+
30
+ class Step
31
+ include Virtus.model(strict: true)
32
+ include NiceInspect
33
+ attribute :add, Array[FileAdd], default: []
34
+ attribute :env, Hash[String, String], default: {}
35
+ attribute :run, Array[String], default: []
36
+ end
37
+
38
+ class Link
39
+ include Virtus.model
40
+ include NiceInspect
41
+ attribute :name, String
42
+ attribute :image, String
43
+ attribute :env, Hash[String, String], default: {}
44
+ attribute :run, String
45
+ # Container ID added after the fact
46
+ attr_accessor :id
47
+ end
48
+
49
+ include Virtus.model(strict: true)
50
+ include NiceInspect
51
+ attribute :from, String, default: 'ubuntu'
52
+ attribute :as_root, Step, required: false
53
+ attribute :use_ssh, Boolean, default: false
54
+ attribute :before, Step, required: false
55
+ attribute :env, Hash[String, String], default: {}
56
+ attribute :export_dir, String, required: false
57
+ attribute :links, Array[Link], default: []
58
+ attribute :run, Array[String], default: ['script/ci']
59
+
60
+ attr_reader :warnings, :errors
61
+
62
+ def initialize(*)
63
+ super
64
+ @warnings = []
65
+ @errors = []
66
+ end
67
+
68
+ def to_dockerfile
69
+ ERB.new(File.read(DOCKERFILE_TEMPLATE), nil, '<>-').result(binding)
70
+ end
71
+
72
+ def self.load(dir, output = $stderr)
73
+ file_path = find_config(dir)
74
+ load_file(file_path, output)
75
+ end
76
+
77
+ def self.load_file(file_path, output = $stderr)
78
+ obj = new
79
+ loader = ConfigFileLoader.new(obj)
80
+ loader.load YAML.load_file(file_path)
81
+
82
+ obj.warnings.each do |warn|
83
+ output.puts "WARN: #{warn}"
84
+ end
85
+
86
+ obj.errors.each do |error|
87
+ output.puts "ERROR: #{error}"
88
+ end
89
+
90
+ return obj if obj.errors.empty?
91
+ nil
92
+ end
93
+
94
+ def self.find_config(dir)
95
+ paths = CONFIG_FILES.map { |name| File.expand_path(name, dir) }
96
+ paths
97
+ .find { |path| File.exist?(path) } ||
98
+ fail("Config not found among these paths: #{paths.inspect}")
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,305 @@
1
+ module CIDE
2
+ class ConfigFileLoader
3
+ class Path
4
+ attr_reader :to_s
5
+ def initialize(str)
6
+ @to_s = str.to_s
7
+ end
8
+
9
+ def append(value)
10
+ self.class.new(
11
+ @to_s + (value.is_a?(Integer) ? "[#{value}]" : ".#{value}"),
12
+ )
13
+ end
14
+ end
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
20
+ def load(data)
21
+ data.each_pair do |key, value|
22
+ key = key.to_s
23
+ path = Path.new(key)
24
+ case key
25
+ when 'from', 'image' then
26
+ wanted_key(path, 'from', key)
27
+ @config.from = expect_string(path, value)
28
+ when 'as_root' then
29
+ @config.as_root = maybe_step(path, value)
30
+ when 'use_ssh' then
31
+ @config.use_ssh = expect_boolean(path, value)
32
+ when 'before' then
33
+ @config.before = maybe_step(path, value)
34
+ when 'env', 'forward_env' then
35
+ wanted_key(path, 'env', key)
36
+ @config.env = expect_env_hash(path, value)
37
+ when 'export_dir' then
38
+ @config.export_dir = maybe_string(path, value)
39
+ when 'link', 'links' then
40
+ @config.links = expect_links(path, value)
41
+ when 'run', 'command' then
42
+ wanted_key(path, 'run', key)
43
+ @config.run = expect_run(path, value)
44
+ else
45
+ unknown_key(path)
46
+ end
47
+ end
48
+ @config
49
+ end
50
+
51
+ protected
52
+
53
+ def warn(message)
54
+ @config.warnings << message
55
+ end
56
+
57
+ def error(message)
58
+ @config.errors << message
59
+ end
60
+
61
+ def wanted_key(path, wanted_key, key)
62
+ return if key == wanted_key
63
+ warn "#{path} is deprecated. use '#{wanted_key}' instead."
64
+ end
65
+
66
+ def unknown_key(path)
67
+ warn "Unknown key #{path}"
68
+ end
69
+
70
+ def type_error(path, wanted_type, value)
71
+ error "expected #{path} to be a #{wanted_type} but got a #{value.class}"
72
+ end
73
+
74
+ def expect_string(path, value)
75
+ case value
76
+ when String, Symbol, Integer
77
+ value.to_s
78
+ else
79
+ type_error(path, 'string', value)
80
+ ''
81
+ end
82
+ end
83
+
84
+ def maybe_string(path, value)
85
+ case value
86
+ when String, Symbol, Integer
87
+ value.to_s
88
+ when nil
89
+ nil
90
+ else
91
+ type_error(path, 'string or nil', value)
92
+ nil
93
+ end
94
+ end
95
+
96
+ def maybe_step(path, value)
97
+ case value
98
+ when String, Symbol, Integer, Array then
99
+ load_step(path, run: value)
100
+ when Hash then
101
+ load_step(path, value)
102
+ when nil then
103
+ nil
104
+ else
105
+ type_error(path, 'string, array, hash or nil', value)
106
+ nil
107
+ end
108
+ end
109
+
110
+ def load_step(path, data)
111
+ step = ConfigFile::Step.new
112
+ data.each_pair do |key, value|
113
+ key = key.to_s
114
+ path_ = path.append(key)
115
+ case key
116
+ when 'run' then
117
+ step.run = expect_array(path_, value)
118
+ when 'env', 'forward_env' then
119
+ wanted_key(path_, 'env', key)
120
+ step.env = expect_env_hash(path_, value)
121
+ when 'add' then
122
+ step.add = expect_adds(path_, value)
123
+ else
124
+ unknown_key(path_)
125
+ end
126
+ end
127
+ step
128
+ end
129
+
130
+ def expect_links(path, value)
131
+ array = []
132
+ case value
133
+ when String, Symbol, Integer, Hash then
134
+ array << expect_link(path, value)
135
+ when Array then
136
+ value.compact.each_with_index do |value_, i|
137
+ array << expect_link(path.append(i), value_)
138
+ end
139
+ when nil then
140
+ # nothing to do
141
+ else
142
+ type_error(path, 'string, array of links, hash or nil', value)
143
+ end
144
+ array.compact
145
+ end
146
+
147
+ def expect_link(path, value)
148
+ case value
149
+ when String, Symbol, Integer then
150
+ load_link(path, image: value)
151
+ when Hash
152
+ load_link(path, value)
153
+ else
154
+ type_error(path, 'string or hash expected', value)
155
+ end
156
+ end
157
+
158
+ def load_link(path, data)
159
+ link = ConfigFile::Link.new
160
+ data.each_pair do |key, value|
161
+ key = key.to_s
162
+ path_ = path.append(key)
163
+ case key
164
+ when 'name' then
165
+ link.name = expect_string(path_, value)
166
+ when 'image', 'from' then
167
+ wanted_key(path_, 'image', key)
168
+ link.image = expect_string(path_, value)
169
+ when 'env' then
170
+ link.env = expect_env_hash(path_, value)
171
+ when 'run' then
172
+ link.run = maybe_string(path_, value)
173
+ else
174
+ unknown_key(path_)
175
+ end
176
+ end
177
+ link.name ||= link.image.split(':').first.split('/').last if link.image
178
+ link.image ||= link.name
179
+ if link.name.nil?
180
+ type_error(
181
+ path,
182
+ 'expected hash to either declare the name or image',
183
+ data,
184
+ )
185
+ return nil
186
+ end
187
+ link
188
+ end
189
+
190
+ def expect_boolean(path, value)
191
+ case value
192
+ when true then
193
+ true
194
+ when false then
195
+ false
196
+ else
197
+ type_error(path, 'boolean', value)
198
+ false
199
+ end
200
+ end
201
+
202
+ def expect_array(path, value)
203
+ array = []
204
+ case value
205
+ when Array then
206
+ value.compact.each_with_index do |value_, i|
207
+ array << expect_string(path.append(i), value_)
208
+ end
209
+ when String, Symbol, Integer then
210
+ array << value.to_s
211
+ when nil then
212
+ # nothing to do
213
+ else
214
+ type_error(path, 'array of string, string or nil', value)
215
+ end
216
+ array.compact
217
+ end
218
+
219
+ def expect_adds(path, value)
220
+ array = []
221
+ case value
222
+ when Array then
223
+ value.compact.each_with_index do |value_, i|
224
+ str = expect_string(path.append(i), value_)
225
+ array << load_add_str(str)
226
+ end
227
+ when Hash then
228
+ value.each_pair do |key_, value_|
229
+ src = expect_array(path.append(key_), value_)
230
+ array << ConfigFile::FileAdd.new(src: src, dest: key_.to_s)
231
+ end
232
+ when String, Symbol, Integer then
233
+ array << load_add_str(value.to_s)
234
+ when nil then
235
+ # nothing to do
236
+ else
237
+ type_error(path, 'arrays of string, hash, string or nil', value)
238
+ end
239
+ array.compact
240
+ end
241
+
242
+ def load_add_str(str)
243
+ ConfigFile::FileAdd.new(
244
+ src: [str],
245
+ dest: str,
246
+ )
247
+ end
248
+
249
+ def expect_env(path, key)
250
+ str = expect_string(path, key)
251
+ return nil if str == ''
252
+ value = ENV[str]
253
+ error "Missing environment variable #{key} in #{path}" if value.nil?
254
+ value
255
+ end
256
+
257
+ def expect_env_hash(path, value)
258
+ hash = {}
259
+ case value
260
+ when String, Symbol, Integer
261
+ key1 = value
262
+ value1 = expect_env(path, key1)
263
+ hash[key1] = value1 if value1
264
+ when Array then
265
+ value.compact.each_with_index do |key, i|
266
+ value_ = expect_env(path.append(i), key)
267
+ hash[key.to_s] = value_ if value_
268
+ end
269
+ when Hash then
270
+ value.each_pair do |key, value_|
271
+ key = key.to_s
272
+ path_ = path.append(key)
273
+ if value_.nil?
274
+ value_ = expect_env(path_, key)
275
+ else
276
+ value_ = expect_string(path_, value_)
277
+ end
278
+ hash[key.to_s] = value_ if value_
279
+ end
280
+ else
281
+ type_error(path, 'hash or array of keys or just a string', value)
282
+ end
283
+ hash
284
+ end
285
+
286
+ def expect_run(path, value)
287
+ array = []
288
+ has_error = false
289
+ case value
290
+ when Array
291
+ value.compact.each_with_index do |key, i|
292
+ array << expect_string(path.append(i), key)
293
+ end
294
+ when String, Symbol, Integer
295
+ array.push('sh', '-e', '-c', value.to_s)
296
+ when nil then
297
+ else
298
+ has_error = true
299
+ type_error(path, 'string or array of string', value)
300
+ end
301
+ error("#{path} shouldn't be empty") if array.empty? && !has_error
302
+ array
303
+ end
304
+ end
305
+ end
@@ -1,4 +1,4 @@
1
- require 'shellwords'
1
+ require 'thor'
2
2
 
3
3
  module CIDE
4
4
  # Simple docker client helper
@@ -19,32 +19,30 @@ module CIDE
19
19
 
20
20
  class VersionError < StandardError; end
21
21
 
22
- def docker(*args, **opts)
23
- setup_docker
22
+ def docker(*args, verbose: false, capture: false)
23
+ cmd = (['docker'] + args).map(&:to_s)
24
+ p cmd if verbose
24
25
 
25
- ret = run Shellwords.join(['docker'] + args), opts
26
+ if capture
27
+ r, w = IO.pipe
28
+ pid = Process.spawn(*cmd, out: w)
29
+ w.close
30
+ return r.read
31
+ else
32
+ pid = Process.spawn(*cmd)
33
+
34
+ return 0
35
+ end
36
+ ensure
37
+ Process.wait(pid)
26
38
  exitstatus = $?.exitstatus
27
39
  fail Error, exitstatus if exitstatus > 0
28
- ret
29
40
  end
30
41
 
31
42
  protected
32
43
 
33
44
  def setup_docker
34
45
  @setup_docker ||= (
35
- if `uname`.strip == 'Darwin' && !ENV['DOCKER_HOST']
36
- unless system('which boot2docker >/dev/null 2>&1')
37
- puts 'make sure boot2docker is installed and running'
38
- puts
39
- puts '> brew install boot2docker'
40
- exit 1
41
- end
42
-
43
- `boot2docker shellinit 2>/dev/null`
44
- .lines
45
- .grep(/export (\w+)=(.*)/) { ENV[$1] = $2.strip }
46
- end
47
-
48
46
  # Check docker version
49
47
  docker_version = nil
50
48
  case `docker version 2>/dev/null`