cide 0.7.0 → 0.8.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.
@@ -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`