dapp 0.13.8 → 0.13.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/config/en/net_status.yml +4 -0
  3. data/lib/dapp.rb +16 -3
  4. data/lib/dapp/cli/command/update.rb +2 -0
  5. data/lib/dapp/dapp/dapp_config.rb +1 -1
  6. data/lib/dapp/deployment/kubernetes.rb +1 -1
  7. data/lib/dapp/dimg/build/stage/base.rb +1 -1
  8. data/lib/dapp/dimg/config/directive/docker/artifact.rb +5 -0
  9. data/lib/dapp/helper/yaml.rb +23 -0
  10. data/lib/dapp/kube/cli/command/kube.rb +3 -1
  11. data/lib/dapp/kube/cli/command/kube/chart_create.rb +19 -0
  12. data/lib/dapp/kube/cli/command/kube/secret_edit.rb +24 -0
  13. data/lib/dapp/kube/dapp/command/chart_create.rb +104 -0
  14. data/lib/dapp/kube/dapp/command/common.rb +21 -2
  15. data/lib/dapp/kube/dapp/command/deploy.rb +50 -25
  16. data/lib/dapp/kube/dapp/command/secret_edit.rb +45 -0
  17. data/lib/dapp/kube/dapp/command/secret_extract.rb +1 -2
  18. data/lib/dapp/kube/dapp/command/secret_generate.rb +1 -2
  19. data/lib/dapp/kube/dapp/command/secret_regenerate.rb +2 -3
  20. data/lib/dapp/kube/dapp/dapp.rb +4 -0
  21. data/lib/dapp/kube/kubernetes.rb +6 -0
  22. data/lib/dapp/kube/kubernetes/client.rb +255 -0
  23. data/lib/dapp/kube/{client → kubernetes/client}/error.rb +8 -4
  24. data/lib/dapp/kube/kubernetes/client/resource/base.rb +21 -0
  25. data/lib/dapp/kube/kubernetes/client/resource/job.rb +27 -0
  26. data/lib/dapp/kube/kubernetes/client/resource/pod.rb +49 -0
  27. data/lib/dapp/kube/kubernetes/manager/base.rb +15 -0
  28. data/lib/dapp/kube/kubernetes/manager/container.rb +88 -0
  29. data/lib/dapp/kube/kubernetes/manager/job.rb +65 -0
  30. data/lib/dapp/kube/kubernetes/manager/pod.rb +35 -0
  31. data/lib/dapp/version.rb +1 -1
  32. metadata +17 -4
  33. data/lib/dapp/kube/client.rb +0 -241
@@ -0,0 +1,45 @@
1
+ module Dapp
2
+ module Kube
3
+ module Dapp
4
+ module Command
5
+ module SecretEdit
6
+ def kube_secret_edit(file_path)
7
+ secret_key_should_exist!
8
+
9
+ with_kube_tmp_chart_dir do
10
+ decoded_data = begin
11
+ raise Error::Command, code: :file_not_exist, data: { path: File.expand_path(file_path) } unless File.exist?(file_path)
12
+
13
+ if options[:values]
14
+ kube_extract_secret_values(file_path)
15
+ else
16
+ kube_extract_secret_file(file_path)
17
+ end
18
+ end
19
+
20
+ tmp_file_path = kube_tmp_chart_path(file_path)
21
+ tmp_file_path.binwrite(decoded_data)
22
+ system(kube_secret_editor, tmp_file_path.to_s)
23
+
24
+ encoded_data = begin
25
+ if options[:values]
26
+ kube_secret_values(tmp_file_path)
27
+ else
28
+ kube_secret_file(tmp_file_path)
29
+ end
30
+ end
31
+
32
+ IO.binwrite(file_path, "#{encoded_data}\n")
33
+ end
34
+ end
35
+
36
+ def kube_secret_editor
37
+ return ENV['EDITOR'] unless ENV['EDITOR'].nil?
38
+ %w(vim vi nano).each { |editor| return editor unless shellout("which #{editor}").exitstatus.nonzero? }
39
+ raise Error::Command, code: :editor_not_found
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -46,8 +46,7 @@ module Dapp
46
46
  end
47
47
 
48
48
  def kube_extract_secret_values(file_path)
49
- raise Error::Command, code: :incorrect_yaml_structure, data: { path: File.expand_path(file_path) } unless (json = YAML::load(File.read(file_path)))
50
- kube_helm_decode_json(secret, json).to_yaml
49
+ kube_helm_decode_json(secret, yaml_load_file(file_path)).to_yaml
51
50
  end
52
51
 
53
52
  def kube_extract_secret_file(file_path)
@@ -46,8 +46,7 @@ module Dapp
46
46
  end
47
47
 
48
48
  def kube_secret_values(file_path)
49
- raise Error::Command, code: :incorrect_yaml_structure, data: { path: File.expand_path(file_path) } unless (json = YAML::load(File.read(file_path)))
50
- kube_helm_encode_json(secret, json).to_yaml
49
+ kube_helm_encode_json(secret, yaml_load_file(file_path)).to_yaml
51
50
  end
52
51
 
53
52
  def kube_secret_file(file_path)
@@ -12,7 +12,7 @@ module Dapp
12
12
  regenerated_data[file_path] = kube_regenerate_secret_values(file_path)
13
13
  end
14
14
 
15
- Dir.glob(kube_chart_path('secret/**/*'), File::FNM_DOTMATCH).each do |file_path|
15
+ Dir.glob(kube_chart_secret_path('**/*'), File::FNM_DOTMATCH).each do |file_path|
16
16
  next if File.directory?(file_path)
17
17
  regenerated_data[file_path] = kube_regenerate_secret_file(file_path)
18
18
  end
@@ -25,8 +25,7 @@ module Dapp
25
25
  end
26
26
 
27
27
  def kube_regenerate_secret_values(file_path)
28
- raise Error::Command, code: :incorrect_yaml_structure, data: { path: File.expand_path(file_path) } unless (json = YAML::load(File.read(file_path)))
29
- kube_helm_encode_json(secret, kube_helm_decode_json(old_secret, json)).to_yaml
28
+ kube_helm_encode_json(secret, kube_helm_decode_json(old_secret, yaml_load_file(file_path))).to_yaml
30
29
  end
31
30
 
32
31
  def old_secret
@@ -2,13 +2,17 @@ module Dapp
2
2
  module Kube
3
3
  module Dapp
4
4
  module Dapp
5
+ include Helper::YAML
6
+
5
7
  include Command::Deploy
6
8
  include Command::Dismiss
7
9
  include Command::SecretKeyGenerate
8
10
  include Command::SecretGenerate
9
11
  include Command::SecretExtract
10
12
  include Command::SecretRegenerate
13
+ include Command::SecretEdit
11
14
  include Command::MinikubeSetup
15
+ include Command::ChartCreate
12
16
  include Command::Common
13
17
  end
14
18
  end
@@ -0,0 +1,6 @@
1
+ module Dapp
2
+ module Kube
3
+ module Kubernetes
4
+ end # Kubernetes
5
+ end # Kube
6
+ end # Dapp
@@ -0,0 +1,255 @@
1
+ module Dapp
2
+ module Kube
3
+ module Kubernetes
4
+ class Client
5
+ include Helper::YAML
6
+
7
+ def initialize(namespace: nil)
8
+ @namespace = namespace
9
+ @query_parameters = {}
10
+ end
11
+
12
+ def namespace
13
+ @namespace || kube_context_config['context']['namespace'] || 'default'
14
+ end
15
+
16
+ # Чтобы не перегружать методы явной передачей namespace.
17
+ # Данный метод может пригодиться только в ситуации, когда надо указать другой namespace,
18
+ # в большинстве случаев используется namespace из конструктора.
19
+ def with_namespace(namespace, &blk)
20
+ old_namespace = @namespace
21
+ begin
22
+ @namespace = namespace
23
+ return yield
24
+ ensure
25
+ @namespace = old_namespace
26
+ end
27
+ end
28
+
29
+ def with_query(query, &blk)
30
+ old_query = @query_parameters
31
+ begin
32
+ @query_parameters = query
33
+ return yield
34
+ ensure
35
+ @query_parameters = old_query
36
+ end
37
+ end
38
+
39
+ # NOTICE: Название метода аналогично kind'у выдаваемого результата.
40
+ # NOTICE: В данном случае в результате kind=DeploymentList.
41
+ # NOTICE: Методы создания/обновления/удаления сущностей kubernetes заканчиваются на '!'. Например, create_deployment!.
42
+
43
+ {
44
+ '/api/v1' => [:service, :replicationcontroller, :pod],
45
+ '/apis/extensions/v1beta1' => [:deployment, :replicaset],
46
+ '/apis/batch/v1' => [:job]
47
+ }.each do |api, objects|
48
+ objects.each do |object|
49
+ define_method :"#{object}_list" do |**query_parameters|
50
+ request!(:get, "#{api}/namespaces/#{namespace}/#{object}s", **query_parameters)
51
+ end
52
+
53
+ define_method object do |name, **query_parameters|
54
+ request!(:get, "#{api}/namespaces/#{namespace}/#{object}s/#{name}", **query_parameters)
55
+ end
56
+
57
+ define_method "#{object}_status" do |name, **query_parameters|
58
+ request!(:get, "#{api}/namespaces/#{namespace}/#{object}s/#{name}/status", **query_parameters)
59
+ end
60
+
61
+ define_method :"create_#{object}!" do |spec, **query_parameters|
62
+ request!(:post, "#{api}/namespaces/#{namespace}/#{object}s", body: spec, **query_parameters)
63
+ end
64
+
65
+ define_method :"replace_#{object}!" do |name, spec, **query_parameters|
66
+ request!(:put, "#{api}/namespaces/#{namespace}/#{object}s/#{name}", body: spec, **query_parameters)
67
+ end
68
+
69
+ define_method :"delete_#{object}!" do |name, **query_parameters|
70
+ request!(:delete, "#{api}/namespaces/#{namespace}/#{object}s/#{name}", **query_parameters)
71
+ end
72
+
73
+ define_method :"delete_#{object}s!" do |**query_parameters|
74
+ request!(:delete, "#{api}/namespaces/#{namespace}/#{object}s", **query_parameters)
75
+ end
76
+
77
+ define_method :"#{object}?" do |name, **query_parameters|
78
+ public_send(:"#{object}_list", **query_parameters)['items'].map { |item| item['metadata']['name'] }.include?(name)
79
+ end
80
+ end
81
+ end
82
+
83
+ def namespace_list(**query_parameters)
84
+ request!(:get, '/api/v1/namespaces', **query_parameters)
85
+ end
86
+
87
+ def namespace?(name, **query_parameters)
88
+ namespace_list(**query_parameters)['items'].map { |item| item['metadata']['name'] }.include?(name)
89
+ end
90
+
91
+ def create_namespace!(name, **query_parameters)
92
+ request!(:post, '/api/v1/namespaces', body: { metadata: { name: name } }, **query_parameters)
93
+ end
94
+
95
+ def delete_namespace!(name, **query_parameters)
96
+ request!(:delete, "/api/v1/namespaces/#{name}", **query_parameters)
97
+ end
98
+
99
+ def pod_log(name, follow: false, **query_parameters, &blk)
100
+ excon_parameters = follow ? { response_block: blk } : {}
101
+ request!(:get,
102
+ "/api/v1/namespaces/#{namespace}/pods/#{name}/log",
103
+ excon_parameters: excon_parameters,
104
+ response_body_parameters: {json: false},
105
+ **{ follow: follow }.merge(query_parameters))
106
+ rescue Excon::Error::Timeout
107
+ raise Error::Timeout
108
+ end
109
+
110
+ def event_list(**query_parameters)
111
+ request!(:get, "/api/v1/namespaces/#{namespace}/events", **query_parameters)
112
+ end
113
+
114
+ protected
115
+
116
+ # query_parameters — соответствует 'Query Parameters' в документации kubernetes
117
+ # excon_parameters — соответствует опциям Excon
118
+ # body — hash для http-body, соответствует 'Body Parameters' в документации kubernetes, опционален
119
+ def request!(method, path, body: nil, excon_parameters: {}, response_body_parameters: {}, **query_parameters)
120
+ with_connection(excon_parameters: excon_parameters) do |conn|
121
+ request_parameters = {method: method, path: path, query: @query_parameters.merge(query_parameters)}
122
+ request_parameters[:body] = JSON.dump(body) if body
123
+ load_body! conn.request(request_parameters), request_parameters, **response_body_parameters
124
+ end
125
+ end
126
+
127
+ def load_body!(response, request_parameters, json: true)
128
+ response_ok = response.status.to_s.start_with? '2'
129
+
130
+ if response_ok
131
+ if json
132
+ JSON.parse(response.body)
133
+ else
134
+ response.body
135
+ end
136
+ else
137
+ err_data = {}
138
+ err_data[:response_http_status] = response.status
139
+ if response_body = (JSON.parse(response.body) rescue nil)
140
+ err_data[:response_body] = response_body
141
+ else
142
+ err_data[:response_raw_body] = response.body
143
+ end
144
+ err_data[:request_parameters] = request_parameters
145
+
146
+ if response.status.to_s.start_with? '5'
147
+ raise Error::Base, code: :server_error, data: err_data
148
+ elsif response.status.to_s == '404'
149
+ case err_data.fetch(:response_body, {}).fetch('details', {})['kind']
150
+ when 'pods'
151
+ raise Error::Pod::NotFound, data: err_data
152
+ else
153
+ raise Error::NotFound, data: err_data
154
+ end
155
+ else not response.status.to_s.start_with? '2'
156
+ raise Error::Base, code: :bad_request, data: err_data
157
+ end
158
+ end
159
+ end
160
+
161
+ def with_connection(excon_parameters: {}, &blk)
162
+ old_ssl_ca_file = Excon.defaults[:ssl_ca_file]
163
+ old_ssl_cert_store = Excon.defaults[:ssl_cert_store]
164
+ old_middlewares = Excon.defaults[:middlewares].dup
165
+
166
+ begin
167
+ ssl_cert_store = OpenSSL::X509::Store.new
168
+ if ssl_ca_file = kube_config.fetch('clusters', [{}]).first.fetch('cluster', {}).fetch('certificate-authority', nil)
169
+ ssl_cert_store.add_file ssl_ca_file
170
+ elsif ssl_ca_data = kube_config.fetch('clusters', [{}]).first.fetch('cluster', {}).fetch('certificate-authority-data', nil)
171
+ ssl_cert_store.add_cert OpenSSL::X509::Certificate.new(Base64.decode64(ssl_ca_data))
172
+ end
173
+
174
+ Excon.defaults[:ssl_ca_file] = nil
175
+ Excon.defaults[:ssl_cert_store] = ssl_cert_store
176
+ Excon.defaults[:middlewares] << Excon::Middleware::RedirectFollower
177
+
178
+ connection = begin
179
+ Excon.new(kube_cluster_config['cluster']['server'], **kube_server_options(excon_parameters)).tap(&:get)
180
+ rescue Excon::Error::Socket => err
181
+ raise Error::ConnectionRefused,
182
+ code: :kube_server_connection_refused,
183
+ data: { kube_cluster_config: kube_cluster_config, kube_user_config: kube_user_config, error: err.message }
184
+ end
185
+
186
+ return yield connection
187
+ ensure
188
+ Excon.defaults[:ssl_ca_file] = old_ssl_ca_file
189
+ Excon.defaults[:ssl_cert_store] = old_ssl_cert_store
190
+ Excon.defaults[:middlewares] = old_middlewares
191
+ end
192
+ end
193
+
194
+ def kube_server_options(excon_parameters = {})
195
+ {}.tap do |opts|
196
+ client_cert = kube_config.fetch('users', [{}]).first.fetch('user', {}).fetch('client-certificate', nil)
197
+ opts[:client_cert] = client_cert if client_cert
198
+
199
+ client_cert_data = kube_config.fetch('users', [{}]).first.fetch('user', {}).fetch('client-certificate-data', nil)
200
+ opts[:client_cert_data] = Base64.decode64(client_cert_data) if client_cert_data
201
+
202
+ client_key = kube_config.fetch('users', [{}]).first.fetch('user', {}).fetch('client-key', nil)
203
+ opts[:client_key] = client_key if client_key
204
+
205
+ client_key_data = kube_config.fetch('users', [{}]).first.fetch('user', {}).fetch('client-key-data', nil)
206
+ opts[:client_key_data] = Base64.decode64(client_key_data) if client_key_data
207
+ end
208
+ end
209
+
210
+ def kube_user_config
211
+ @kube_user_config ||= begin
212
+ kube_config.fetch('users', []).find {|user| user['name'] == kube_context_config['context']['user']} || begin
213
+ raise Error::BadConfig, code: :kube_user_not_found, data: {context: kube_context_config}
214
+ end
215
+ end
216
+ end
217
+
218
+ def kube_cluster_config
219
+ @kube_cluster_config ||= begin
220
+ kube_config.fetch('clusters', []).find {|cluster| cluster['name'] == kube_context_config['context']['cluster']} || begin
221
+ raise Error::BadConfig, code: :kube_cluster_not_found, data: {context: kube_context_config}
222
+ end
223
+ end
224
+ end
225
+
226
+ def kube_context_config
227
+ @kube_context_config ||= begin
228
+ kube_config.fetch('contexts', []).find {|context| context['name'] == kube_context_name} || begin
229
+ raise Error::BadConfig, code: :kube_context_not_found, data: {context_name: kube_context_name}
230
+ end
231
+ end
232
+ end
233
+
234
+ def kube_context_name
235
+ @kube_context_name ||= kube_config['current-context'] || begin
236
+ if context = kube_config.fetch('contexts', []).first
237
+ warn "[WARN] .kube/config current-context is not set, using context '#{context['name']}'"
238
+ context['name']
239
+ end
240
+ end
241
+ end
242
+
243
+ def kube_config
244
+ @kube_config ||= begin
245
+ if File.exist?((kube_config_path = File.join(ENV['HOME'], '.kube/config')))
246
+ yaml_load_file(kube_config_path)
247
+ else
248
+ raise Error::Base, code: :kube_config_not_found, data: { path: kube_config_path }
249
+ end
250
+ end
251
+ end
252
+ end # Client
253
+ end # Kubernetes
254
+ end # Kube
255
+ end # Dapp
@@ -1,6 +1,6 @@
1
1
  module Dapp
2
2
  module Kube
3
- module Client::Error
3
+ module Kubernetes::Client::Error
4
4
  class Base < ::Dapp::Kube::Error::Kubernetes
5
5
  def initialize(**net_status)
6
6
  super(**net_status, context: :kubernetes)
@@ -16,6 +16,10 @@ module Dapp
16
16
  class Timeout < Base; end
17
17
  class ConnectionRefused < Base; end
18
18
  class BadConfig < Base; end
19
- end
20
- end
21
- end
19
+
20
+ module Pod
21
+ class NotFound < Kubernetes::Client::Error::NotFound ; end
22
+ end # Pod
23
+ end # Kubernetes::Client::Error
24
+ end # Kube
25
+ end # Dapp
@@ -0,0 +1,21 @@
1
+ module Dapp
2
+ module Kube
3
+ module Kubernetes::Client::Resource
4
+ class Base
5
+ attr_reader :spec
6
+
7
+ def initialize(spec)
8
+ @spec = spec
9
+ end
10
+
11
+ def name
12
+ spec.fetch('metadata', {})['name']
13
+ end
14
+
15
+ def annotations
16
+ spec.fetch('metadata', {}).fetch('annotations', {})
17
+ end
18
+ end # Base
19
+ end # Kubernetes::Client::Resource
20
+ end # Kube
21
+ end # Dapp
@@ -0,0 +1,27 @@
1
+ module Dapp
2
+ module Kube
3
+ module Kubernetes::Client::Resource
4
+ class Job < Base
5
+ def uid
6
+ spec.fetch('metadata', {})['uid']
7
+ end
8
+
9
+ def terminated?
10
+ failed? || succeeded?
11
+ end
12
+
13
+ def failed?
14
+ !!spec.fetch('status', {}).fetch('conditions', []).find do |cond|
15
+ cond['type'] == 'Failed'
16
+ end
17
+ end
18
+
19
+ def succeeded?
20
+ !!spec.fetch('status', {}).fetch('conditions', []).find do |cond|
21
+ cond['type'] == 'Complete'
22
+ end
23
+ end
24
+ end # Job
25
+ end # Kubernetes::Client::Resource
26
+ end # Kube
27
+ end # Dapp
@@ -0,0 +1,49 @@
1
+ module Dapp
2
+ module Kube
3
+ module Kubernetes::Client::Resource
4
+ class Pod < Base
5
+ def container_id(container_name)
6
+ container_status = spec.fetch('status', {})
7
+ .fetch('containerStatuses')
8
+ .find {|cs| cs['name'] == container_name}
9
+
10
+ if container_status
11
+ container_status['containerID']
12
+ else
13
+ nil
14
+ end
15
+ end
16
+
17
+ def container_state(container_name)
18
+ container_status = spec
19
+ .fetch('status', {})
20
+ .fetch('containerStatuses', [])
21
+ .find {|cs| cs['name'] == container_name}
22
+
23
+ if container_status
24
+ container_state, container_state_data = container_status.fetch('state', {}).first
25
+ [container_state, container_state_data]
26
+ else
27
+ [nil, {}]
28
+ end
29
+ end
30
+
31
+ def phase
32
+ spec.fetch('status', {}).fetch('phase', nil)
33
+ end
34
+
35
+ def containers_names
36
+ spec.fetch('spec', {})
37
+ .fetch('containers', [])
38
+ .map {|container_spec| container_spec['name']}
39
+ end
40
+
41
+ def restart_policy
42
+ spec
43
+ .fetch('spec', {})
44
+ .fetch('restartPolicy', nil)
45
+ end
46
+ end # Pod
47
+ end # Kubernetes::Client::Resource
48
+ end # Kube
49
+ end # Dapp