compote 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,371 @@
1
+ module Compote
2
+
3
+ module Schema
4
+
5
+ resolve_relative_path = {
6
+
7
+ String => proc { | value, config |
8
+
9
+ value = config.get_path value if value.start_with? '.'
10
+
11
+ value
12
+
13
+ },
14
+
15
+ }
16
+
17
+ wrap_string_in_array = {
18
+
19
+ String => proc { | value |
20
+
21
+ [ value ]
22
+
23
+ },
24
+
25
+ }
26
+
27
+ options_array_to_hash = {
28
+
29
+ Array => proc { | value |
30
+
31
+ value.map do | value |
32
+
33
+ [ value, {} ]
34
+
35
+ end.to_h
36
+
37
+ },
38
+
39
+ }
40
+
41
+ variables_array_to_hash = {
42
+
43
+ Array => proc { | value |
44
+
45
+ value.map do | value |
46
+
47
+ result = value.split '=', 2
48
+
49
+ result += [ nil ] if result.size == 1
50
+
51
+ result
52
+
53
+ end.to_h
54
+
55
+ },
56
+
57
+ }
58
+
59
+ labels_array_to_hash = {
60
+
61
+ Array => proc { | value |
62
+
63
+ value.map do | value |
64
+
65
+ result = value.split '=', 2
66
+
67
+ result += [ '' ] if result.size == 1
68
+
69
+ result
70
+
71
+ end.to_h
72
+
73
+ },
74
+
75
+ }
76
+
77
+ compote_extends_to_hash = {
78
+
79
+ String => proc { | value | { value => {} } },
80
+
81
+ Array => options_array_to_hash[ Array ],
82
+
83
+ Hash => proc { | value | value.map { | key, value | [ key, value || {} ] }.to_h },
84
+
85
+ '*' => {
86
+
87
+ 'only' => wrap_string_in_array,
88
+
89
+ 'except' => wrap_string_in_array,
90
+
91
+ },
92
+
93
+ }
94
+
95
+ env_files_mapping = {
96
+
97
+ String => wrap_string_in_array[ String ],
98
+
99
+ '*' => resolve_relative_path,
100
+
101
+ },
102
+
103
+
104
+ MAPPINGS = {
105
+
106
+ 'compote' => {
107
+
108
+ 'extends' => compote_extends_to_hash,
109
+
110
+ 'env_file' => env_files_mapping,
111
+
112
+ 'environment' => variables_array_to_hash,
113
+
114
+ },
115
+
116
+ 'volumes' => {
117
+
118
+ '*' => {
119
+
120
+ 'labels' => labels_array_to_hash,
121
+
122
+ },
123
+
124
+ },
125
+
126
+ 'networks' => {
127
+
128
+ '*' => {
129
+
130
+ 'labels' => labels_array_to_hash,
131
+
132
+ },
133
+
134
+ },
135
+
136
+ 'services' => {
137
+
138
+ '*' => {
139
+
140
+ 'build' => {
141
+
142
+ String => proc { | value | { 'context' => value } },
143
+
144
+ 'args' => variables_array_to_hash,
145
+
146
+ },
147
+
148
+ 'depends_on' => options_array_to_hash,
149
+
150
+ 'dns' => wrap_string_in_array,
151
+
152
+ 'dns_search' => wrap_string_in_array,
153
+
154
+ 'tmpfs' => wrap_string_in_array,
155
+
156
+ 'env_file' => env_files_mapping,
157
+
158
+ 'environment' => variables_array_to_hash,
159
+
160
+ 'labels' => labels_array_to_hash,
161
+
162
+ 'sysctls' => variables_array_to_hash,
163
+
164
+ 'volumes' => {
165
+
166
+ '*' => {
167
+
168
+ String => proc { | value, config |
169
+
170
+ if value.start_with? '.'
171
+
172
+ values = value.split ':'
173
+
174
+ values[ 0 ] = config.get_path values[ 0 ]
175
+
176
+ value = values.join ':'
177
+
178
+ end
179
+
180
+ value
181
+
182
+ },
183
+
184
+ },
185
+
186
+ },
187
+
188
+ 'compote' => {
189
+
190
+ 'extends' => compote_extends_to_hash,
191
+
192
+ 'volumes' => {
193
+
194
+ '*' => {
195
+
196
+ 'labels' => labels_array_to_hash,
197
+
198
+ },
199
+
200
+ },
201
+
202
+ 'networks' => {
203
+
204
+ '*' => {
205
+
206
+ 'labels' => labels_array_to_hash,
207
+
208
+ },
209
+
210
+ },
211
+
212
+ },
213
+
214
+ },
215
+
216
+ },
217
+
218
+ }
219
+
220
+
221
+ def self.validate! ( data )
222
+
223
+ scheme = Pathname.new( __FILE__ ).join( '../schema.json' ).to_path
224
+
225
+ JSON::Validator.validate! scheme, data
226
+
227
+ rescue JSON::Schema::ValidationError => error
228
+
229
+ raise ConfigFormatError.new error: error, data: data
230
+
231
+ end
232
+
233
+ def self.normalize ( config, value, mappings = MAPPINGS )
234
+
235
+ validate! value if mappings == MAPPINGS
236
+
237
+
238
+ value = value.clone
239
+
240
+
241
+ class_mappings = mappings.select { | key, value | key.is_a? Class }
242
+
243
+ unless class_mappings.empty?
244
+
245
+ class_mapping = class_mappings[ value.class ]
246
+
247
+ value = class_mapping.call value, config if class_mapping
248
+
249
+ end
250
+
251
+
252
+ paths_mappings = mappings.select { | key, value | key.is_a? String }
253
+
254
+ unless paths_mappings.empty? || ! value.is_a?( Enumerable ) || value.empty?
255
+
256
+ if paths_mappings.keys == [ '*' ]
257
+
258
+ path_mappings = paths_mappings[ '*' ]
259
+
260
+ if value.is_a? Hash
261
+
262
+ value = value.map do | key, value |
263
+
264
+ [ key, normalize( config, value, path_mappings ) ]
265
+
266
+ end.to_h
267
+
268
+ end
269
+
270
+ if value.is_a? Array
271
+
272
+ value = value.map do | value |
273
+
274
+ normalize config, value, path_mappings
275
+
276
+ end
277
+
278
+ end
279
+
280
+ elsif value.is_a? Hash
281
+
282
+ value = value.map do | key, value |
283
+
284
+ path_mappings = paths_mappings[ key ]
285
+
286
+ if path_mappings
287
+
288
+ [ key, normalize( config, value, path_mappings ) ]
289
+
290
+ else
291
+
292
+ [ key, value ]
293
+
294
+ end
295
+
296
+ end.to_h
297
+
298
+ end
299
+
300
+ end
301
+
302
+
303
+ value
304
+
305
+ end
306
+
307
+ def self.apply_extends ( initial_data, &get_data )
308
+
309
+ extends = initial_data.dig 'compote', 'extends'
310
+
311
+ return initial_data.clone unless extends
312
+
313
+ extending_datas = extends.map do | key, options |
314
+
315
+ data = get_data.call key
316
+
317
+ data = data.select { | key, value | options[ 'only' ].include? key } if options[ 'only' ]
318
+
319
+ data = data.reject { | key, value | options[ 'except' ].include? key } if options[ 'except' ]
320
+
321
+ data
322
+
323
+ end
324
+
325
+ extending_datas += [ initial_data ]
326
+
327
+ resulted_data = merge extending_datas
328
+
329
+ resulted_data
330
+
331
+ end
332
+
333
+ def self.merge ( datas )
334
+
335
+ result = {}
336
+
337
+ datas.each do | data |
338
+
339
+ data.keys.each do | key |
340
+
341
+ values = [ result[ key ], data[ key ] ]
342
+
343
+ result[ key ] = begin
344
+
345
+ if values.all? { | value | value.is_a? Hash }
346
+
347
+ merge values
348
+
349
+ elsif values.all? { | value | value.is_a? Array } && ! %w( command entrypoint ).include?( key )
350
+
351
+ values.first + values.last
352
+
353
+ else
354
+
355
+ values.last.clone
356
+
357
+ end
358
+
359
+ end
360
+
361
+ end
362
+
363
+ end
364
+
365
+ result
366
+
367
+ end
368
+
369
+ end
370
+
371
+ end
@@ -0,0 +1,81 @@
1
+ module Compote
2
+
3
+ class ServiceConfig
4
+
5
+ def initialize ( config, name, data )
6
+
7
+ @config = config
8
+
9
+ @name = name
10
+
11
+ @data = apply_extends data
12
+
13
+
14
+ @compote_settings = @data.fetch 'compote', {}
15
+
16
+ @service_settings = @data.reject { | key, value | key == 'compote' }
17
+
18
+ end
19
+
20
+ def commands
21
+
22
+ @compote_settings.fetch 'commands', {}
23
+
24
+ end
25
+
26
+ def compose_config
27
+
28
+ compose_config = {}
29
+
30
+ compose_config[ 'version' ] = @config.compose_version
31
+
32
+ compose_config[ 'services' ] = @service_settings.empty? ? {} : { @name => @service_settings }
33
+
34
+ compose_config[ 'volumes' ] = @compote_settings.fetch 'volumes', {}
35
+
36
+ compose_config[ 'networks' ] = @compote_settings.fetch 'networks', {}
37
+
38
+ compose_config
39
+
40
+ end
41
+
42
+
43
+ protected
44
+
45
+ def data
46
+
47
+ @data
48
+
49
+ end
50
+
51
+ def apply_extends ( initial_data )
52
+
53
+ Schema.apply_extends initial_data do | key |
54
+
55
+ key_info = key.split ':'
56
+
57
+ key_info = [ '.', key_info[ 0 ] ] if key_info.size == 1
58
+
59
+ config_path, service_name = key_info
60
+
61
+ config = @config.load_config config_path
62
+
63
+ service_config = config.get_service_config service_name
64
+
65
+ data = service_config.data
66
+
67
+ data
68
+
69
+ end
70
+
71
+ rescue ServiceNotFoundError => error
72
+
73
+ error.message += "\n" + "If the service exists, check that its definition is higher than of service \"#{ @name }\"" if error.config == @config
74
+
75
+ raise error
76
+
77
+ end
78
+
79
+ end
80
+
81
+ end