kontena-cli 0.13.4 → 0.14.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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/kontena-cli.gemspec +2 -0
  4. data/lib/kontena/cli/app_command.rb +2 -0
  5. data/lib/kontena/cli/apps/common.rb +80 -74
  6. data/lib/kontena/cli/apps/config_command.rb +29 -0
  7. data/lib/kontena/cli/apps/deploy_command.rb +12 -81
  8. data/lib/kontena/cli/apps/docker_helper.rb +3 -3
  9. data/lib/kontena/cli/apps/init_command.rb +0 -3
  10. data/lib/kontena/cli/apps/list_command.rb +2 -3
  11. data/lib/kontena/cli/apps/logs_command.rb +2 -3
  12. data/lib/kontena/cli/apps/monitor_command.rb +3 -4
  13. data/lib/kontena/cli/apps/remove_command.rb +4 -4
  14. data/lib/kontena/cli/apps/restart_command.rb +2 -3
  15. data/lib/kontena/cli/apps/scale_command.rb +3 -5
  16. data/lib/kontena/cli/apps/service_generator.rb +123 -0
  17. data/lib/kontena/cli/apps/service_generator_v2.rb +26 -0
  18. data/lib/kontena/cli/apps/show_command.rb +1 -2
  19. data/lib/kontena/cli/apps/start_command.rb +2 -3
  20. data/lib/kontena/cli/apps/stop_command.rb +2 -3
  21. data/lib/kontena/cli/apps/yaml/reader.rb +150 -0
  22. data/lib/kontena/cli/apps/yaml/service_extender.rb +60 -0
  23. data/lib/kontena/cli/apps/yaml/validations.rb +79 -0
  24. data/lib/kontena/cli/apps/yaml/validator.rb +55 -0
  25. data/lib/kontena/cli/apps/yaml/validator_v2.rb +74 -0
  26. data/lib/kontena/cli/common.rb +23 -0
  27. data/lib/kontena/cli/etcd/remove_command.rb +2 -0
  28. data/lib/kontena/cli/grids/remove_command.rb +2 -0
  29. data/lib/kontena/cli/grids/users/remove_command.rb +3 -0
  30. data/lib/kontena/cli/master/azure/create_command.rb +0 -2
  31. data/lib/kontena/cli/master/packet/create_command.rb +42 -0
  32. data/lib/kontena/cli/master/packet_command.rb +14 -0
  33. data/lib/kontena/cli/master/upcloud/create_command.rb +39 -0
  34. data/lib/kontena/cli/master/upcloud_command.rb +13 -0
  35. data/lib/kontena/cli/master/users/remove_command.rb +3 -0
  36. data/lib/kontena/cli/master/users/roles/remove_command.rb +2 -0
  37. data/lib/kontena/cli/master_command.rb +4 -0
  38. data/lib/kontena/cli/node_command.rb +4 -0
  39. data/lib/kontena/cli/nodes/azure/create_command.rb +0 -2
  40. data/lib/kontena/cli/nodes/list_command.rb +4 -8
  41. data/lib/kontena/cli/nodes/packet/create_command.rb +35 -0
  42. data/lib/kontena/cli/nodes/packet/restart_command.rb +17 -0
  43. data/lib/kontena/cli/nodes/packet/terminate_command.rb +20 -0
  44. data/lib/kontena/cli/nodes/packet_command.rb +15 -0
  45. data/lib/kontena/cli/nodes/remove_command.rb +2 -0
  46. data/lib/kontena/cli/nodes/show_command.rb +3 -1
  47. data/lib/kontena/cli/nodes/upcloud/create_command.rb +33 -0
  48. data/lib/kontena/cli/nodes/upcloud/restart_command.rb +20 -0
  49. data/lib/kontena/cli/nodes/upcloud/terminate_command.rb +20 -0
  50. data/lib/kontena/cli/nodes/upcloud_command.rb +15 -0
  51. data/lib/kontena/cli/registry/remove_command.rb +3 -0
  52. data/lib/kontena/cli/services/remove_command.rb +2 -0
  53. data/lib/kontena/cli/services/services_helper.rb +1 -0
  54. data/lib/kontena/cli/vault/list_command.rb +2 -0
  55. data/lib/kontena/cli/vault/read_command.rb +2 -0
  56. data/lib/kontena/cli/vault/remove_command.rb +4 -0
  57. data/lib/kontena/cli/vault/update_command.rb +8 -1
  58. data/lib/kontena/cli/vault/write_command.rb +2 -0
  59. data/lib/kontena/cli/vpn/remove_command.rb +3 -0
  60. data/lib/kontena/machine/azure/master_provisioner.rb +2 -2
  61. data/lib/kontena/machine/azure/node_provisioner.rb +7 -4
  62. data/lib/kontena/machine/digital_ocean/node_provisioner.rb +1 -1
  63. data/lib/kontena/machine/packet.rb +17 -0
  64. data/lib/kontena/machine/packet/cloudinit.yml +66 -0
  65. data/lib/kontena/machine/packet/cloudinit_master.yml +118 -0
  66. data/lib/kontena/machine/packet/master_provisioner.rb +93 -0
  67. data/lib/kontena/machine/packet/node_destroyer.rb +42 -0
  68. data/lib/kontena/machine/packet/node_provisioner.rb +77 -0
  69. data/lib/kontena/machine/packet/node_restarter.rb +41 -0
  70. data/lib/kontena/machine/packet/packet_common.rb +89 -0
  71. data/lib/kontena/machine/upcloud.rb +9 -0
  72. data/lib/kontena/machine/upcloud/cloudinit.yml +64 -0
  73. data/lib/kontena/machine/upcloud/cloudinit_master.yml +118 -0
  74. data/lib/kontena/machine/upcloud/master_provisioner.rb +136 -0
  75. data/lib/kontena/machine/upcloud/node_destroyer.rb +82 -0
  76. data/lib/kontena/machine/upcloud/node_provisioner.rb +119 -0
  77. data/lib/kontena/machine/upcloud/node_restarter.rb +47 -0
  78. data/lib/kontena/machine/upcloud/upcloud_common.rb +70 -0
  79. data/lib/kontena/scripts/completer +8 -3
  80. data/spec/fixtures/docker-compose_v2.yml +10 -0
  81. data/spec/fixtures/kontena-invalid.yml +4 -0
  82. data/spec/fixtures/kontena-with-variables.yml +19 -0
  83. data/spec/fixtures/kontena.yml +2 -2
  84. data/spec/fixtures/kontena_v2.yml +35 -0
  85. data/spec/kontena/cli/app/common_spec.rb +39 -101
  86. data/spec/kontena/cli/app/deploy_command_spec.rb +37 -388
  87. data/spec/kontena/cli/app/docker_helper_spec.rb +4 -4
  88. data/spec/kontena/cli/app/service_generator_spec.rb +374 -0
  89. data/spec/kontena/cli/app/service_generator_v2_spec.rb +74 -0
  90. data/spec/kontena/cli/app/yaml/reader_spec.rb +249 -0
  91. data/spec/kontena/cli/app/yaml/service_extender_spec.rb +104 -0
  92. data/spec/kontena/cli/app/yaml/validator_spec.rb +263 -0
  93. data/spec/kontena/cli/app/yaml/validator_v2_spec.rb +309 -0
  94. data/spec/kontena/cli/common_spec.rb +39 -1
  95. data/spec/kontena/cli/master/users/remove_command_spec.rb +9 -0
  96. data/spec/kontena/cli/master/users/roles/remove_command_spec.rb +2 -0
  97. metadata +86 -2
@@ -10,7 +10,7 @@ describe Kontena::Cli::Apps::DockerHelper do
10
10
  let(:services_with_valid_hooks) do
11
11
  {
12
12
  'test_service' => {
13
- 'build' => '.',
13
+ 'build' => { 'context' => '.' },
14
14
  'image' => 'test_service',
15
15
  'hooks' => {
16
16
  'pre_build' => [
@@ -25,7 +25,7 @@ describe Kontena::Cli::Apps::DockerHelper do
25
25
  let(:services_with_invalid_hook) do
26
26
  {
27
27
  'test_service' => {
28
- 'build' => '.',
28
+ 'build' => { 'context' => '.' },
29
29
  'image' => 'test_service',
30
30
  'hooks' => {
31
31
  'pre_build' => [
@@ -40,7 +40,7 @@ describe Kontena::Cli::Apps::DockerHelper do
40
40
  let(:services_with_no_hook) do
41
41
  {
42
42
  'test_service' => {
43
- 'build' => '.',
43
+ 'build' => { 'context' => '.' },
44
44
  'image' => 'test_service',
45
45
  }
46
46
  }
@@ -70,7 +70,7 @@ describe Kontena::Cli::Apps::DockerHelper do
70
70
  end
71
71
 
72
72
  describe '#run_pre_build_hook' do
73
-
73
+
74
74
  context 'when hook defined' do
75
75
  it 'runs the hook' do
76
76
  allow(subject).to receive(:build_docker_image)
@@ -0,0 +1,374 @@
1
+ require_relative "../../../spec_helper"
2
+ require "kontena/cli/apps/service_generator"
3
+
4
+ describe Kontena::Cli::Apps::ServiceGenerator do
5
+ let(:subject) do
6
+ described_class.new({})
7
+ end
8
+
9
+ describe '#parse_data' do
10
+ context 'volumes' do
11
+ it 'returns volumes if set' do
12
+ data = {
13
+ 'image' => 'foo/bar:latest',
14
+ 'volumes' => [
15
+ 'mongodb-1'
16
+ ]
17
+ }
18
+ result = subject.send(:parse_data, data)
19
+ expect(result['volumes']).to eq(data['volumes'])
20
+ end
21
+
22
+ it 'returns empty volumes if not set' do
23
+ data = {
24
+ 'image' => 'foo/bar:latest'
25
+ }
26
+ result = subject.send(:parse_data, data)
27
+ expect(result['volumes']).to eq([])
28
+ end
29
+ end
30
+
31
+ context 'volumes_from' do
32
+ it 'returns volumes_from if set' do
33
+ data = {
34
+ 'image' => 'foo/bar:latest',
35
+ 'volumes_from' => [
36
+ 'mongodb-1'
37
+ ]
38
+ }
39
+ result = subject.send(:parse_data, data)
40
+ expect(result['volumes_from']).to eq(data['volumes_from'])
41
+ end
42
+
43
+ it 'returns empty volumes_from if not set' do
44
+ data = {
45
+ 'image' => 'foo/bar:latest'
46
+ }
47
+ result = subject.send(:parse_data, data)
48
+ expect(result['volumes_from']).to eq([])
49
+ end
50
+ end
51
+
52
+ context 'command' do
53
+ it 'returns cmd array if set' do
54
+ data = {
55
+ 'image' => 'foo/bar:latest',
56
+ 'command' => 'ls -la'
57
+ }
58
+ result = subject.send(:parse_data, data)
59
+ expect(result['cmd']).to eq(data['command'].split(' '))
60
+ end
61
+
62
+ it 'does not return cmd if not set' do
63
+ data = {
64
+ 'image' => 'foo/bar:latest'
65
+ }
66
+ result = subject.send(:parse_data, data)
67
+ expect(result.has_key?('cmd')).to be_falsey
68
+ end
69
+ end
70
+
71
+ context 'affinity' do
72
+ it 'returns affinity if set' do
73
+ data = {
74
+ 'image' => 'foo/bar:latest',
75
+ 'affinity' => [
76
+ 'label==az=b'
77
+ ]
78
+ }
79
+ result = subject.send(:parse_data, data)
80
+ expect(result['affinity']).to eq(data['affinity'])
81
+ end
82
+
83
+ it 'returns affinity as empty array if not set' do
84
+ data = {
85
+ 'image' => 'foo/bar:latest'
86
+ }
87
+ result = subject.send(:parse_data, data)
88
+ expect(result.has_key?('affinity')).to be_truthy
89
+ expect(result['affinity']).to eq([])
90
+ end
91
+ end
92
+
93
+ context 'user' do
94
+ it 'returns user if set' do
95
+ data = {
96
+ 'image' => 'foo/bar:latest',
97
+ 'user' => 'user'
98
+ }
99
+ result = subject.send(:parse_data, data)
100
+ expect(result['user']).to eq('user')
101
+ end
102
+
103
+ it 'does not return user if not set' do
104
+ data = {
105
+ 'image' => 'foo/bar:latest'
106
+ }
107
+ result = subject.send(:parse_data, data)
108
+ expect(result.has_key?('user')).to be_falsey
109
+ end
110
+ end
111
+
112
+ context 'stateful' do
113
+ it 'returns stateful if set' do
114
+ data = {
115
+ 'image' => 'foo/bar:latest',
116
+ 'stateful' => true
117
+ }
118
+ result = subject.send(:parse_data, data)
119
+ expect(result['stateful']).to eq(true)
120
+ end
121
+
122
+ it 'returns stateful as false if not set' do
123
+ data = {
124
+ 'image' => 'foo/bar:latest'
125
+ }
126
+ result = subject.send(:parse_data, data)
127
+ expect(result['stateful']).to eq(false)
128
+ end
129
+ end
130
+
131
+ context 'links' do
132
+ it 'returns empty array if links not set' do
133
+ data = {
134
+ 'image' => 'wordpress:latest'
135
+ }
136
+ result = subject.send(:parse_data, data)
137
+ expect(result['links']).to eq([])
138
+ end
139
+
140
+ it 'returns parsed links array' do
141
+ data = {
142
+ 'image' => 'wordpress:latest',
143
+ 'links' => ['mysql:db']
144
+ }
145
+ result = subject.send(:parse_data, data)
146
+ expect(result['links']).to eq([{
147
+ 'name' => 'mysql',
148
+ 'alias' => 'db'
149
+ }])
150
+ end
151
+ end
152
+ context 'privileged' do
153
+ it 'returns privileged if set' do
154
+ data = {
155
+ 'image' => 'foo/bar:latest',
156
+ 'privileged' => false
157
+ }
158
+ result = subject.send(:parse_data, data)
159
+ expect(result['privileged']).to eq(false)
160
+ end
161
+
162
+ it 'does not return privileged if not set' do
163
+ data = {
164
+ 'image' => 'foo/bar:latest'
165
+ }
166
+ result = subject.send(:parse_data, data)
167
+ expect(result['privileged']).to be_nil
168
+ end
169
+ end
170
+
171
+ context 'cap_add' do
172
+ it 'returns cap_drop if set' do
173
+ data = {
174
+ 'image' => 'foo/bar:latest',
175
+ 'cap_add' => [
176
+ 'NET_ADMIN'
177
+ ]
178
+ }
179
+ result = subject.send(:parse_data, data)
180
+ expect(result['cap_add']).to eq(data['cap_add'])
181
+ end
182
+
183
+ it 'does not return cap_add if not set' do
184
+ data = {
185
+ 'image' => 'foo/bar:latest'
186
+ }
187
+ result = subject.send(:parse_data, data)
188
+ expect(result['cap_add']).to be_nil
189
+ end
190
+ end
191
+
192
+ context 'cap_drop' do
193
+ it 'returns cap_drop if set' do
194
+ data = {
195
+ 'image' => 'foo/bar:latest',
196
+ 'cap_drop' => [
197
+ 'NET_ADMIN'
198
+ ]
199
+ }
200
+ result = subject.send(:parse_data, data)
201
+ expect(result['cap_drop']).to eq(data['cap_drop'])
202
+ end
203
+
204
+ it 'does not return cap_drop if not set' do
205
+ data = {
206
+ 'image' => 'foo/bar:latest'
207
+ }
208
+ result = subject.send(:parse_data, data)
209
+ expect(result['cap_drop']).to be_nil
210
+ end
211
+ end
212
+
213
+ context 'net' do
214
+ it 'returns net if set' do
215
+ data = {
216
+ 'image' => 'foo/bar:latest',
217
+ 'net' => 'host'
218
+ }
219
+ result = subject.send(:parse_data, data)
220
+ expect(result['net']).to eq('host')
221
+ end
222
+
223
+ it 'does not return pid if not set' do
224
+ data = {
225
+ 'image' => 'foo/bar:latest'
226
+ }
227
+ result = subject.send(:parse_data, data)
228
+ expect(result['net']).to be_nil
229
+ end
230
+ end
231
+
232
+ context 'pid' do
233
+ it 'returns pid if set' do
234
+ data = {
235
+ 'image' => 'foo/bar:latest',
236
+ 'pid' => 'host'
237
+ }
238
+ result = subject.send(:parse_data, data)
239
+ expect(result['pid']).to eq('host')
240
+ end
241
+
242
+ it 'does not return pid if not set' do
243
+ data = {
244
+ 'image' => 'foo/bar:latest'
245
+ }
246
+ result = subject.send(:parse_data, data)
247
+ expect(result['pid']).to be_nil
248
+ end
249
+ end
250
+
251
+ context 'log_driver' do
252
+ it 'returns log_driver if set' do
253
+ data = {
254
+ 'image' => 'foo/bar:latest',
255
+ 'log_driver' => 'syslog'
256
+ }
257
+ result = subject.send(:parse_data, data)
258
+ expect(result['log_driver']).to eq('syslog')
259
+ end
260
+
261
+ it 'does not return log_driver if not set' do
262
+ data = {
263
+ 'image' => 'foo/bar:latest'
264
+ }
265
+ result = subject.send(:parse_data, data)
266
+ expect(result['log_driver']).to be_nil
267
+ end
268
+ end
269
+
270
+ context 'log_opt' do
271
+ it 'returns log_opts hash if log_opt is set' do
272
+ data = {
273
+ 'image' => 'foo/bar:latest',
274
+ 'log_driver' => 'fluentd',
275
+ 'log_opt' => {
276
+ 'fluentd-address' => '192.168.99.1:24224',
277
+ 'fluentd-tag' => 'docker.{{.Name}}'
278
+ }
279
+ }
280
+ result = subject.send(:parse_data, data)
281
+ expect(result['log_opts']).to eq(data['log_opt'])
282
+ end
283
+
284
+ it 'does not return log_opts if log_opt is not set' do
285
+ data = {
286
+ 'image' => 'foo/bar:latest'
287
+ }
288
+ result = subject.send(:parse_data, data)
289
+ expect(result['log_opts']).to be_nil
290
+ end
291
+ end
292
+
293
+ context 'deploy_opts' do
294
+ it 'returns deploy_opts if deploy.wait_for_port is defined' do
295
+ data = {
296
+ 'image' => 'foo/bar:latest',
297
+ 'deploy' => {
298
+ 'wait_for_port' => '8080'
299
+ }
300
+ }
301
+ result = subject.send(:parse_data, data)
302
+ expect(result['deploy_opts']['wait_for_port']).to eq('8080')
303
+ end
304
+
305
+ it 'returns deploy_opts if deploy.min_health is defined' do
306
+ data = {
307
+ 'image' => 'foo/bar:latest',
308
+ 'deploy' => {
309
+ 'min_health' => '0.5'
310
+ }
311
+ }
312
+ result = subject.send(:parse_data, data)
313
+ expect(result['deploy_opts']['min_health']).to eq('0.5')
314
+ end
315
+
316
+ it 'sets strategy if deploy.strategy is defined' do
317
+ data = {
318
+ 'image' => 'foo/bar:latest',
319
+ 'deploy' => {
320
+ 'strategy' => 'daemon'
321
+ }
322
+ }
323
+ result = subject.send(:parse_data, data)
324
+ expect(result['strategy']).to eq('daemon')
325
+ end
326
+
327
+ it 'does not return deploy_opts if no deploy options are defined' do
328
+ data = {
329
+ 'image' => 'foo/bar:latest'
330
+ }
331
+ result = subject.send(:parse_data, data)
332
+ expect(result['deploy_opts']).to be_nil
333
+ end
334
+ end
335
+
336
+ context 'hooks' do
337
+ it 'returns hooks hash if defined' do
338
+ data = {
339
+ 'image' => 'foo/bar:latest',
340
+ 'hooks' => {
341
+ 'post_start' => []
342
+ }
343
+ }
344
+ result = subject.send(:parse_data, data)
345
+ expect(result['hooks']).to eq(data['hooks'])
346
+ end
347
+
348
+ it 'does returns empty hook hash if not defined' do
349
+ data = {'image' => 'foo/bar:latest'}
350
+ result = subject.send(:parse_data, data)
351
+ expect(result['hooks']).to eq({})
352
+ end
353
+ end
354
+
355
+ context 'secrets' do
356
+ it 'returns secrets array if defined' do
357
+ data = {
358
+ 'image' => 'foo/bar:latest',
359
+ 'secrets' => [
360
+ {'secret' => 'MYSQL_ADMIN_PASSWORD', 'name' => 'WORDPRESS_DB_PASSWORD', 'type' => 'env'}
361
+ ]
362
+ }
363
+ result = subject.send(:parse_data, data)
364
+ expect(result['secrets']).to eq(data['secrets'])
365
+ end
366
+
367
+ it 'does not return secrets if not defined' do
368
+ data = {'image' => 'foo/bar:latest'}
369
+ result = subject.send(:parse_data, data)
370
+ expect(result['secrets']).to be_nil
371
+ end
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,74 @@
1
+ require_relative "../../../spec_helper"
2
+ require "kontena/cli/apps/service_generator_v2"
3
+ require 'ruby_dig'
4
+
5
+ describe Kontena::Cli::Apps::ServiceGeneratorV2 do
6
+ let(:subject) do
7
+ described_class.new({})
8
+ end
9
+
10
+ describe '#parse_data' do
11
+ it 'parses network_mode' do
12
+ data = {
13
+ 'image' => 'wordpress:latest',
14
+ 'network_mode' => 'bridge'
15
+ }
16
+ result = subject.send(:parse_data, data)
17
+ expect(result['net']).to eq('bridge')
18
+ end
19
+
20
+ it 'parses logging' do
21
+ data = {
22
+ 'image' => 'wordpress:latest',
23
+ 'logging' => {
24
+ 'driver' => 'influxdb',
25
+ 'options' => {
26
+ 'syslog-address' => 'tcp://192.168.0.42:123'
27
+ }
28
+ }
29
+ }
30
+ result = subject.send(:parse_data, data)
31
+ expect(result['log_driver']).to eq('influxdb')
32
+ expect(result['log_opts']).to eq({
33
+ 'syslog-address' => 'tcp://192.168.0.42:123'
34
+ })
35
+ end
36
+
37
+ it 'adds depends_on to links' do
38
+ data = {
39
+ 'image' => 'wordpress:latest',
40
+ 'depends_on' => ['mysql']
41
+ }
42
+ result = subject.send(:parse_data, data)
43
+ expect(result['links']).to eq([{
44
+ 'name' => 'mysql',
45
+ 'alias' => 'mysql'
46
+ }])
47
+ end
48
+ end
49
+
50
+ describe '#parse_build_options' do
51
+ context 'when build option is a string' do
52
+ it 'converts build option to hash' do
53
+ data = {
54
+ 'build' => '.',
55
+ 'image' => 'myapp'
56
+ }
57
+ result = subject.send(:parse_build_options, data)
58
+ expect(result).to eq({ 'context' => '.' })
59
+ end
60
+ end
61
+ context 'when build options is a hash' do
62
+ it 'uses it as build options' do
63
+ data = {
64
+ 'build' => {
65
+ 'context' => '.',
66
+ 'dockerfile' => 'alternate-dockerfile'
67
+ }
68
+ }
69
+ result = subject.send(:parse_build_options, data)
70
+ expect(result).to eq(data['build'])
71
+ end
72
+ end
73
+ end
74
+ end