dapp 0.13.8 → 0.13.9

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 (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