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.
- checksums.yaml +4 -4
- data/.dockerignore +3 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +10 -2
- data/cide.gemspec +1 -1
- data/cide.yml +2 -4
- data/docs/cide.yml.md +1 -1
- data/lib/cide/builder.rb +54 -0
- data/lib/cide/cli.rb +161 -140
- data/lib/cide/config_file.rb +101 -0
- data/lib/cide/config_file_loader.rb +305 -0
- data/lib/cide/docker.rb +16 -18
- data/lib/cide/runner.rb +110 -0
- data/man/{cide-build.1.md → cide-exec.1.md} +19 -11
- data/man/cide-package.1.md +56 -0
- data/man/cide.1.md +28 -2
- data/man/cide.yml.1.md +1 -1
- data/script/build +29 -0
- data/script/ci +2 -0
- data/spec/build_config_loader_spec.rb +4 -4
- metadata +11 -6
- data/lib/cide/build.rb +0 -2
- data/lib/cide/build/config.rb +0 -103
- data/lib/cide/build/config_loader.rb +0 -307
@@ -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
|
data/lib/cide/docker.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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,
|
23
|
-
|
22
|
+
def docker(*args, verbose: false, capture: false)
|
23
|
+
cmd = (['docker'] + args).map(&:to_s)
|
24
|
+
p cmd if verbose
|
24
25
|
|
25
|
-
|
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`
|