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
@@ -0,0 +1,249 @@
1
+ require_relative '../../../../spec_helper'
2
+ require 'kontena/cli/apps/yaml/reader'
3
+
4
+ describe Kontena::Cli::Apps::YAML::Reader do
5
+ include FixturesHelpers
6
+
7
+ def absolute_yaml_path(file = 'kontena.yml')
8
+ "#{Dir.pwd}/#{file}"
9
+ end
10
+
11
+ let(:subject) do
12
+ described_class.new('kontena.yml')
13
+ end
14
+
15
+ let(:service_extender) do
16
+ spy
17
+ end
18
+
19
+ let(:valid_result) do
20
+ {
21
+ 'wordpress' => {
22
+ 'extends' => {
23
+ 'file' => 'docker-compose.yml',
24
+ 'service' => 'wordpress'
25
+ },
26
+ 'image' => 'wordpress:4.1',
27
+ 'ports' => ['80:80'],
28
+ 'links' => ['mysql:mysql'],
29
+ 'stateful' => true,
30
+ 'environment' => ['WORDPRESS_DB_PASSWORD=test_secret'],
31
+ 'instances' => 2,
32
+ 'deploy' => { 'strategy' => 'ha' },
33
+ 'secrets' => []
34
+ },
35
+ 'mysql' => {
36
+ 'extends' => {
37
+ 'file' => 'docker-compose.yml',
38
+ 'service' => 'mysql'
39
+ },
40
+ 'image' => 'mysql:5.6',
41
+ 'stateful' => true,
42
+ 'environment' => ['MYSQL_ROOT_PASSWORD=test_secret'],
43
+ 'secrets' => []
44
+ }
45
+ }
46
+ end
47
+
48
+ let(:valid_v2_result) do
49
+ {
50
+ 'version' => '2',
51
+ 'services' => {
52
+ 'wordpress' => {
53
+ 'image' => 'wordpress:4.1',
54
+ 'ports' => ['80:80'],
55
+ 'depends_on' => ['mysql'],
56
+ 'stateful' => true,
57
+ 'environment' => ['WORDPRESS_DB_PASSWORD=test_secret'],
58
+ 'instances' => 2,
59
+ 'deploy' => { 'strategy' => 'ha' },
60
+ 'networks' => ['front-tier', 'back-tier'],
61
+ 'secrets' => []
62
+ },
63
+ 'mysql' => {
64
+ 'image' => 'mysql:5.6',
65
+ 'stateful' => true,
66
+ 'environment' => ['MYSQL_ROOT_PASSWORD=test_secret'],
67
+ 'volumes' => ['mysql-data:/var/lib/mysql'],
68
+ 'networks' => ['back-tier'],
69
+ 'secrets' => []
70
+ }
71
+ },
72
+ 'volumes' => {
73
+ 'mysql-data' => { 'driver' => 'local' }
74
+ },
75
+ 'networks' => {
76
+ 'front-tier' => { 'driver' => 'bridge' },
77
+ 'back-tier' => { 'driver' => 'bridge' }
78
+ }
79
+ }
80
+ end
81
+
82
+ describe '#initialize' do
83
+ before(:each) do
84
+ allow(File).to receive(:read)
85
+ .with(absolute_yaml_path('kontena.yml'))
86
+ .and_return(fixture('kontena.yml'))
87
+ allow(File).to receive(:read)
88
+ .with(absolute_yaml_path('docker-compose.yml'))
89
+ .and_return(fixture('docker-compose.yml'))
90
+ end
91
+
92
+ it 'reads given file' do
93
+ expect(File).to receive(:read)
94
+ .with(absolute_yaml_path)
95
+ .and_return(fixture('kontena.yml'))
96
+ subject
97
+ end
98
+
99
+ context 'variable interpolation' do
100
+ before(:each) do
101
+ allow(ENV).to receive(:key?).and_return(true)
102
+ allow(ENV).to receive(:[]).with('TAG').and_return('4.1')
103
+ allow(ENV).to receive(:[]).with('project').and_return('test')
104
+ allow(ENV).to receive(:[]).with('grid').and_return('test-grid')
105
+ allow(ENV).to receive(:[]).with('MYSQL_IMAGE').and_return('mariadb:latest')
106
+ end
107
+
108
+ it 'interpolates $VAR variables' do
109
+ allow(File).to receive(:read)
110
+ .with(absolute_yaml_path)
111
+ .and_return(fixture('kontena-with-variables.yml'))
112
+ services = subject.yaml
113
+ expect(services['wordpress']['image']).to eq('wordpress:4.1')
114
+ end
115
+
116
+ it 'interpolates ${VAR} variables' do
117
+ allow(File).to receive(:read)
118
+ .with(absolute_yaml_path)
119
+ .and_return(fixture('kontena-with-variables.yml'))
120
+ services = subject.yaml
121
+ expect(services['mysql']['image']).to eq('mariadb:latest')
122
+ end
123
+
124
+ it 'warns about empty variables' do
125
+ allow(File).to receive(:read)
126
+ .with(absolute_yaml_path)
127
+ .and_return(fixture('kontena-with-variables.yml'))
128
+ allow(ENV).to receive(:key?)
129
+ .with('MYSQL_IMAGE')
130
+ .and_return(false)
131
+
132
+ expect {
133
+ subject
134
+ }.to output('The MYSQL_IMAGE is not set. Substituting an empty string.
135
+ ').to_stdout
136
+ end
137
+ end
138
+
139
+ it 'replaces $$VAR variables to $VAR format' do
140
+ allow(ENV).to receive(:key?).and_return(true)
141
+ allow(ENV).to receive(:[]).with('TAG').and_return('4.1')
142
+ allow(ENV).to receive(:[]).with('MYSQL_IMAGE').and_return('mariadb:latest')
143
+ allow(ENV).to receive(:[]).with('project').and_return('test')
144
+ allow(ENV).to receive(:[]).with('grid').and_return('test-grid')
145
+ allow(File).to receive(:read)
146
+ .with(absolute_yaml_path)
147
+ .and_return(fixture('kontena-with-variables.yml'))
148
+ services = subject.execute[:services]
149
+ expect(services['mysql']['environment'].first).to eq('INTERNAL_VAR=$INTERNAL_VAR')
150
+ end
151
+ end
152
+
153
+ describe '#v2?' do
154
+ context 'version 1' do
155
+ it 'returns false' do
156
+ allow(File).to receive(:read)
157
+ .with(absolute_yaml_path('docker-compose.yml'))
158
+ .and_return(fixture('docker-compose.yml'))
159
+ allow(File).to receive(:read)
160
+ .with(absolute_yaml_path('kontena.yml'))
161
+ .and_return(fixture('kontena.yml'))
162
+ expect(subject.v2?).to be_falsey
163
+ end
164
+ end
165
+ context 'version 2' do
166
+ it 'returns true' do
167
+ allow(File).to receive(:read)
168
+ .with(absolute_yaml_path('docker-compose_v2.yml'))
169
+ .and_return(fixture('docker-compose_v2.yml'))
170
+ allow(File).to receive(:read)
171
+ .with(absolute_yaml_path('kontena.yml'))
172
+ .and_return(fixture('kontena_v2.yml'))
173
+ expect(subject.v2?).to be_truthy
174
+ end
175
+ end
176
+ end
177
+
178
+ describe '#execute' do
179
+ before(:each) do
180
+ allow(ENV).to receive(:[]).with('project').and_return('test')
181
+ allow(ENV).to receive(:[]).with('grid').and_return('test-grid')
182
+
183
+ allow(File).to receive(:read)
184
+ .with(absolute_yaml_path('kontena.yml'))
185
+ .and_return(fixture('kontena.yml'))
186
+ allow(File).to receive(:read)
187
+ .with(absolute_yaml_path('docker-compose.yml'))
188
+ .and_return(fixture('docker-compose.yml'))
189
+ end
190
+
191
+ it 'extends services' do
192
+ docker_compose_yml = YAML.load(fixture('docker-compose.yml') % { project: 'test' })
193
+ wordpress_options = {
194
+ 'extends' => {
195
+ 'file' => 'docker-compose.yml',
196
+ 'service' => 'wordpress'
197
+ },
198
+ 'stateful' => true,
199
+ 'environment' => ['WORDPRESS_DB_PASSWORD=test_secret'],
200
+ 'instances' => 2,
201
+ 'deploy' => { 'strategy' => 'ha' }
202
+ }
203
+ mysql_options = {
204
+ 'extends' => {
205
+ 'file' => 'docker-compose.yml',
206
+ 'service' => 'mysql'
207
+ },
208
+ 'stateful' => true,
209
+ 'environment' => ['MYSQL_ROOT_PASSWORD=test_secret']
210
+ }
211
+ expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
212
+ .with(wordpress_options)
213
+ .once
214
+ .and_return(service_extender)
215
+ expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
216
+ .with(mysql_options)
217
+ .once
218
+ .and_return(service_extender)
219
+ expect(service_extender).to receive(:extend).with(docker_compose_yml['wordpress'])
220
+ expect(service_extender).to receive(:extend).with(docker_compose_yml['mysql'])
221
+
222
+ subject.execute
223
+ end
224
+
225
+ context 'environment variables' do
226
+ it 'converts env hash to array' do
227
+ result = subject.execute[:services]
228
+ expect(result['wordpress']['environment']).to eq(['WORDPRESS_DB_PASSWORD=test_secret'])
229
+ end
230
+
231
+ it 'does nothing to env array' do
232
+ result = subject.execute[:services]
233
+ expect(result['mysql']['environment']).to eq(['MYSQL_ROOT_PASSWORD=test_secret'])
234
+ end
235
+ end
236
+ it 'returns result hash' do
237
+ outcome = subject.execute
238
+ expect(outcome[:services]).to eq(valid_result)
239
+ end
240
+
241
+ it 'returns validation errors' do
242
+ allow(File).to receive(:read)
243
+ .with(absolute_yaml_path('kontena.yml'))
244
+ .and_return(fixture('kontena-invalid.yml'))
245
+ outcome = subject.execute
246
+ expect(outcome[:errors].size).to eq(1)
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,104 @@
1
+ require_relative '../../../../spec_helper'
2
+ require 'kontena/cli/apps/yaml/service_extender'
3
+
4
+ describe Kontena::Cli::Apps::YAML::ServiceExtender do
5
+ let(:options) do
6
+ {
7
+ 'image' => 'alpine:latest',
8
+ 'ports' => '80:80'
9
+ }
10
+ end
11
+
12
+ let(:parent_options) do
13
+ {
14
+ 'build' => '.'
15
+ }
16
+ end
17
+
18
+ describe '#extend' do
19
+ it 'merges options' do
20
+ result = described_class.new(options).extend(parent_options)
21
+ expected_result = {
22
+ 'image' => 'alpine:latest',
23
+ 'build' => '.',
24
+ 'ports' => '80:80',
25
+ 'environment' => [],
26
+ 'secrets' => []
27
+ }
28
+ expect(result).to eq(expected_result)
29
+ end
30
+
31
+ context 'environment variables' do
32
+ it 'inherites env vars from upper level' do
33
+ from = { 'environment' => ['FOO=bar'] }
34
+ to = {}
35
+ result = described_class.new(to).extend(from)
36
+ expect(result['environment']).to eq(['FOO=bar'])
37
+ end
38
+
39
+ it 'overrides values' do
40
+ from = { 'environment' => ['FOO=bar'] }
41
+ to = { 'environment' => ['FOO=baz'] }
42
+ result = described_class.new(to).extend(from)
43
+ expect(result['environment']).to eq(['FOO=baz'])
44
+ end
45
+
46
+ it 'combines variables' do
47
+ from = { 'environment' => ['FOO=bar'] }
48
+ to = { 'environment' => ['BAR=baz'] }
49
+ result = described_class.new(to).extend(from)
50
+ expect(result['environment']).to eq(['BAR=baz', 'FOO=bar'])
51
+ end
52
+ end
53
+
54
+ context 'secrets' do
55
+ it 'inherites secrets from upper level' do
56
+ secret = {
57
+ 'secret' => 'CUSTOMER_DB_PASSWORD',
58
+ 'name' => 'MYSQL_PASSWORD',
59
+ 'type' => 'env'
60
+ }
61
+ from = { 'secrets' => [secret] }
62
+ to = {}
63
+ result = described_class.new(to).extend(from)
64
+ expect(result['secrets']).to eq([secret])
65
+ end
66
+
67
+ it 'overrides secrets' do
68
+ from_secret = {
69
+ 'secret' => 'CUSTOMER_DB_PASSWORD',
70
+ 'name' => 'MYSQL_PASSWORD',
71
+ 'type' => 'env'
72
+ }
73
+
74
+ to_secret = {
75
+ 'secret' => 'CUSTOMER_DB_PASSWORD',
76
+ 'name' => 'MYSQL_ROOT_PASSWORD',
77
+ 'type' => 'env'
78
+ }
79
+ from = { 'secrets' => [from_secret] }
80
+ to = { 'secrets' => [to_secret] }
81
+ result = described_class.new(to).extend(from)
82
+ expect(result['secrets']).to eq([to_secret])
83
+ end
84
+
85
+ it 'combines secrets' do
86
+ from_secret = {
87
+ 'secret' => 'CUSTOMER_DB_PASSWORD',
88
+ 'name' => 'MYSQL_PASSWORD',
89
+ 'type' => 'env'
90
+ }
91
+
92
+ to_secret = {
93
+ 'secret' => 'CUSTOMER_API_TOKEN',
94
+ 'name' => 'API_TOKEN',
95
+ 'type' => 'env'
96
+ }
97
+ from = { 'secrets' => [from_secret] }
98
+ to = { 'secrets' => [to_secret] }
99
+ result = described_class.new(to).extend(from)
100
+ expect(result['secrets']).to eq([to_secret, from_secret])
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,263 @@
1
+ require_relative '../../../../spec_helper'
2
+ require 'kontena/cli/apps/yaml/validator'
3
+
4
+ describe Kontena::Cli::Apps::YAML::Validator do
5
+ describe '#validate_keys' do
6
+ it 'returns error on invalid key' do
7
+ result = subject.validate_keys('name' => 'wordpress')
8
+ expect(result['name'].size).to eq(1)
9
+ end
10
+ end
11
+
12
+ describe '#validate_options' do
13
+ context 'image' do
14
+ it 'image is optional' do
15
+ result = subject.validate_options('build' => '.')
16
+ expect(result.success?).to be_truthy
17
+ expect(result.messages.key?('image')).to be_falsey
18
+ end
19
+
20
+ it 'must be string' do
21
+ result = subject.validate_options('image' => 10)
22
+ expect(result.success?).to be_falsey
23
+ expect(result.messages.key?('image')).to be_truthy
24
+ end
25
+ end
26
+
27
+ it 'validates stateful is boolean' do
28
+ result = subject.validate_options('stateful' => 'bool')
29
+ expect(result.messages.key?('stateful')).to be_truthy
30
+ end
31
+
32
+ it 'validates net is host or bridge' do
33
+ result = subject.validate_options('net' => 'invalid')
34
+ expect(result.messages.key?('net')).to be_truthy
35
+
36
+ result = subject.validate_options('net' => 'bridge')
37
+ expect(result.messages.key?('net')).to be_falsey
38
+
39
+ result = subject.validate_options('net' => 'host')
40
+ expect(result.messages.key?('net')).to be_falsey
41
+ end
42
+
43
+ context 'affinity' do
44
+ it 'is optional' do
45
+ result = subject.validate_options({})
46
+ expect(result.messages.key?('affinity')).to be_falsey
47
+ end
48
+
49
+ it 'must be array' do
50
+ result = subject.validate_options('affinity' => 'node==node1')
51
+ expect(result.messages.key?('affinity')).to be_truthy
52
+ result = subject.validate_options('affinity' => ['node==node1'])
53
+ expect(result.messages.key?('affinity')).to be_falsey
54
+ end
55
+
56
+ it 'validates format' do
57
+ result = subject.validate_options('affinity' => ['node=node1'])
58
+ expect(result.messages.key?('affinity')).to be_truthy
59
+
60
+ result = subject.validate_options('affinity' => ['node==node1', 'service!=mariadb'])
61
+ expect(result.messages.key?('affinity')).to be_falsey
62
+ end
63
+ end
64
+
65
+ context 'command' do
66
+ it 'is optional' do
67
+ result = subject.validate_options({})
68
+ expect(result.messages.key?('command')).to be_falsey
69
+ end
70
+
71
+ it 'must be string or empty' do
72
+ result = subject.validate_options('command' => 1234)
73
+ expect(result.messages.key?('command')).to be_truthy
74
+
75
+ result = subject.validate_options('command' => nil)
76
+ expect(result.messages.key?('command')).to be_falsey
77
+
78
+ result = subject.validate_options('command' => 'bundle exec rails s')
79
+ expect(result.messages.key?('command')).to be_falsey
80
+ end
81
+ end
82
+
83
+ it 'validates cpu_shares is integer' do
84
+ result = subject.validate_options('cpu_shares' => '1m')
85
+ expect(result.messages.key?('cpu_shares')).to be_truthy
86
+ result = subject.validate_options('cpu_shares' => 1024)
87
+ expect(result.messages.key?('cpu_shares')).to be_falsey
88
+ result = subject.validate_options({})
89
+ expect(result.messages.key?('cpu_shares')).to be_falsey
90
+ end
91
+
92
+ it 'validates environment is array or hash' do
93
+ result = subject.validate_options('environment' => 'KEY=VALUE')
94
+ expect(result.messages.key?('environment')).to be_truthy
95
+ result = subject.validate_options('environment' => ['KEY=VALUE'])
96
+ expect(result.messages.key?('environment')).to be_falsey
97
+ result = subject.validate_options('environment' => { 'KEY' => 'VALUE' })
98
+ expect(result.messages.key?('environment')).to be_falsey
99
+ end
100
+
101
+ context 'validates secrets' do
102
+ it 'must be array' do
103
+ result = subject.validate_options('secrets' => {})
104
+ expect(result.messages.key?('secrets')).to be_truthy
105
+ end
106
+
107
+ context 'item' do
108
+ it 'must contain secret' do
109
+ result = subject.validate_options('secrets' => [{ 'name' => 'test', 'type' => 'env' }])
110
+ expect(result.messages.key?('secrets')).to be_truthy
111
+ end
112
+
113
+ it 'must contain name' do
114
+ result = subject.validate_options('secrets' => [{ 'secret' => 'test', 'type' => 'env' }])
115
+ expect(result.messages.key?('secrets')).to be_truthy
116
+ end
117
+
118
+ it 'must contain type' do
119
+ result = subject.validate_options('secrets' => [{ 'secret' => 'test', 'name' => 'test' }])
120
+ expect(result.messages.key?('secrets')).to be_truthy
121
+ end
122
+
123
+ it 'accepts valid input' do
124
+ result = subject.validate_options('secrets' =>
125
+ [
126
+ {
127
+ 'secret' => 'test',
128
+ 'name' => 'test',
129
+ 'type' => 'env'
130
+ }
131
+ ])
132
+ expect(result.messages.key?('secrets')).to be_falsey
133
+ end
134
+ end
135
+ end
136
+
137
+ context 'validates hooks' do
138
+ context 'validates pre_build' do
139
+ it 'must be array' do
140
+ result = subject.validate_options('hooks' => { 'pre_build' => {} })
141
+ expect(result.messages.key?('hooks')).to be_truthy
142
+ data = {
143
+ 'hooks' => {
144
+ 'pre_build' => [
145
+ {
146
+ 'cmd' => 'rake db:migrate'
147
+ }
148
+ ]
149
+ }
150
+ }
151
+ result = subject.validate_options(data)
152
+ expect(result.messages.key?('hooks')).to be_falsey
153
+ end
154
+ end
155
+ context 'validates post_start' do
156
+ it 'must be array' do
157
+ result = subject.validate_options('hooks' => { 'post_start' => {} })
158
+ expect(result.messages.key?('hooks')).to be_truthy
159
+ data = {
160
+ 'hooks' => {
161
+ 'post_start' => [
162
+ {
163
+ 'name' => 'migrate',
164
+ 'cmd' => 'rake db:migrate',
165
+ 'instances' => '*'
166
+ }
167
+ ]
168
+ }
169
+ }
170
+ result = subject.validate_options(data)
171
+ expect(result.messages.key?('hooks')).to be_falsey
172
+ end
173
+
174
+ context 'item' do
175
+ it 'must contain name' do
176
+ result = subject.validate_options('hooks' =>
177
+ {
178
+ 'post_start' => [
179
+ {
180
+ 'cmd' => 'rake db:migrate',
181
+ 'instances' => '1'
182
+ }
183
+ ]
184
+ })
185
+ expect(result.messages.key?('hooks')).to be_truthy
186
+ end
187
+
188
+ it 'must contain cmd' do
189
+ result = subject.validate_options('hooks' =>
190
+ {
191
+ 'post_start' => [
192
+ {
193
+ 'name' => 'migrate',
194
+ 'instances' => '1'
195
+ }
196
+ ]
197
+ })
198
+ expect(result.messages.key?('hooks')).to be_truthy
199
+ end
200
+
201
+ it 'must contain instance number or *' do
202
+ result = subject.validate_options('hooks' =>
203
+ {
204
+ 'post_start' => [
205
+ { 'name' => 'migrate',
206
+ 'cmd' => 'rake db:migrate'
207
+ }
208
+ ]
209
+ })
210
+ expect(result.messages.key?('hooks')).to be_truthy
211
+ data = {
212
+ 'hooks' => {
213
+ 'post_start' => [
214
+ {
215
+ 'name' => 'migrate',
216
+ 'cmd' => 'rake db:migrate',
217
+ 'instances' => 'all',
218
+ 'oneshot' => true
219
+ }
220
+ ]
221
+ }
222
+ }
223
+ result = subject.validate_options(data)
224
+ expect(result.messages.key?('hooks')).to be_truthy
225
+ end
226
+
227
+ it 'may contain boolean oneshot' do
228
+ data = {
229
+ 'hooks' => {
230
+ 'post_start' => [
231
+ {
232
+ 'name' => 'migrate',
233
+ 'cmd' => 'rake db:migrate',
234
+ 'instances' => '*',
235
+ 'oneshot' => 'true'
236
+ }
237
+ ]
238
+ }
239
+ }
240
+ result = subject.validate_options(data)
241
+ expect(result.messages.key?('hooks')).to be_truthy
242
+ end
243
+ end
244
+
245
+ it 'validates volumes is array' do
246
+ result = subject.validate_options('volumes' => '/app')
247
+ expect(result.messages.key?('volumes')).to be_truthy
248
+
249
+ result = subject.validate_options('volumes' => ['/app'])
250
+ expect(result.messages.key?('volumes')).to be_falsey
251
+ end
252
+
253
+ it 'validates volumes_from is array' do
254
+ result = subject.validate_options('volumes_from' => 'mysql_data')
255
+ expect(result.messages.key?('volumes_from')).to be_truthy
256
+
257
+ result = subject.validate_options('volumes_from' => ['mysql_data'])
258
+ expect(result.messages.key?('volumes_from')).to be_falsey
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end