dapp 0.12.8 → 0.13.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/dapp +0 -4
  3. data/config/en/common.yml +1 -0
  4. data/config/en/net_status.yml +5 -0
  5. data/lib/dapp.rb +20 -8
  6. data/lib/dapp/cli.rb +2 -5
  7. data/lib/dapp/cli/command/base.rb +1 -5
  8. data/lib/dapp/dapp.rb +0 -22
  9. data/lib/dapp/dapp/shellout/streaming.rb +2 -2
  10. data/lib/dapp/deployment/cli/command/deployment.rb +1 -3
  11. data/lib/dapp/deployment/cli/command/deployment/apply.rb +1 -1
  12. data/lib/dapp/deployment/dapp/dapp.rb +0 -2
  13. data/lib/dapp/dimg/cli/command/base.rb +4 -0
  14. data/lib/dapp/dimg/config/directive/git_artifact_remote.rb +1 -3
  15. data/lib/dapp/dimg/dimg.rb +1 -0
  16. data/lib/dapp/dimg/dimg/path.rb +0 -6
  17. data/lib/dapp/helper/trivia.rb +4 -0
  18. data/lib/dapp/kube.rb +1 -0
  19. data/lib/dapp/kube/cli/cli.rb +1 -0
  20. data/lib/dapp/kube/cli/command/base.rb +14 -0
  21. data/lib/dapp/kube/cli/command/kube.rb +21 -0
  22. data/lib/dapp/kube/cli/command/kube/deploy.rb +30 -0
  23. data/lib/dapp/kube/cli/command/kube/dismiss.rb +21 -0
  24. data/lib/dapp/kube/cli/command/kube/secret_file_encrypt.rb +23 -0
  25. data/lib/dapp/kube/cli/command/kube/secret_generate.rb +13 -0
  26. data/lib/dapp/kube/cli/command/kube/secret_key_generate.rb +13 -0
  27. data/lib/dapp/kube/dapp/command/common.rb +29 -0
  28. data/lib/dapp/kube/dapp/command/deploy.rb +192 -0
  29. data/lib/dapp/kube/dapp/command/dismiss.rb +25 -0
  30. data/lib/dapp/kube/dapp/command/secret_file_encrypt.rb +22 -0
  31. data/lib/dapp/{deployment → kube}/dapp/command/secret_generate.rb +3 -3
  32. data/lib/dapp/{deployment → kube}/dapp/command/secret_key_generate.rb +2 -2
  33. data/lib/dapp/kube/dapp/dapp.rb +16 -0
  34. data/lib/dapp/kube/error/base.rb +7 -0
  35. data/lib/dapp/kube/error/command.rb +7 -0
  36. data/lib/dapp/kube/kubernetes.rb +191 -0
  37. data/lib/dapp/kube/secret.rb +93 -0
  38. data/lib/dapp/version.rb +1 -1
  39. metadata +23 -37
  40. data/lib/dapp/dapp/sentry.rb +0 -112
  41. data/lib/dapp/deployment/cli/command/deployment/secret_generate.rb +0 -13
  42. data/lib/dapp/deployment/cli/command/deployment/secret_key_generate.rb +0 -13
  43. data/lib/dapp/helper/url.rb +0 -23
@@ -0,0 +1,13 @@
1
+ module Dapp::Kube::CLI::Command
2
+ class Kube < ::Dapp::CLI
3
+ class SecretKeyGenerate < Base
4
+ banner <<BANNER.freeze
5
+ Usage:
6
+
7
+ dapp kube secret key generate
8
+
9
+ Options:
10
+ BANNER
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module Dapp
2
+ module Kube
3
+ module Dapp
4
+ module Command
5
+ module Common
6
+ def kube_check_helm!
7
+ raise Error::Command, code: :helm_not_found if shellout('which helm').exitstatus == 1
8
+ end
9
+
10
+ def kube_release_name
11
+ "#{name}-#{kube_namespace}"
12
+ end
13
+
14
+ def kube_namespace
15
+ options[:namespace].tr('_', '-')
16
+ end
17
+
18
+ def secret
19
+ @secret ||= Secret.new(ENV['DAPP_SECRET_KEY']) if ENV.key?('DAPP_SECRET_KEY')
20
+ end
21
+
22
+ def kubernetes
23
+ @kubernetes ||= Kubernetes.new(namespace: kube_namespace)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,192 @@
1
+ module Dapp
2
+ module Kube
3
+ module Dapp
4
+ module Command
5
+ module Deploy
6
+ def kube_deploy
7
+ raise Error::Command, code: :helm_directory_not_exist unless kube_chart_path.exist?
8
+ kube_check_helm!
9
+ kube_check_helm_chart!
10
+
11
+ repo = option_repo
12
+ image_version = options[:image_version]
13
+ validate_repo_name!(repo)
14
+ validate_tag_name!(image_version)
15
+
16
+ begin
17
+ kube_copy_chart
18
+ kube_helm_decode_secrets
19
+ kube_generate_helm_chart_tpl
20
+
21
+ additional_values = [].tap do |options|
22
+ options << "--values #{kube_chart_path("values/#{kube_namespace}.yaml")}" if kube_chart_path("values/#{kube_namespace}.yaml").exist?
23
+ options << "--values #{kube_tmp_chart_secret_values_path.exist? ? kube_tmp_chart_secret_values_path : kube_chart_secret_values_path}" if kube_chart_secret_values_path.exist?
24
+ end
25
+
26
+ set_options = [].tap do |options|
27
+ options << "--set global.dapp.repo=#{repo}"
28
+ options << "--set global.dapp.image_version=#{image_version}"
29
+ options << "--set global.namespace=#{kube_namespace}"
30
+ options << "--set global.env=#{kube_namespace}"
31
+ end
32
+
33
+ kube_flush_hooks_jobs(additional_values, set_options)
34
+ kube_run_deploy(additional_values, set_options)
35
+ ensure
36
+ if dev_mode?
37
+ log_info "Temporary chart directory: #{kube_tmp_chart_path}"
38
+ else
39
+ FileUtils.rm_rf(kube_tmp_chart_path)
40
+ end
41
+ end
42
+ end
43
+
44
+ def kube_copy_chart
45
+ FileUtils.cp_r("#{kube_chart_path}/.", kube_tmp_chart_path)
46
+ end
47
+
48
+ def kube_helm_decode_secrets
49
+ if secret.nil?
50
+ log_warning(desc: { code: :dapp_secret_key_not_found }) if kube_chart_secret_values_path.file? || kube_chart_secret_path.directory?
51
+ else
52
+ kube_helm_decode_secret_values
53
+ kube_helm_decode_secret_files
54
+ end
55
+ end
56
+
57
+ def kube_helm_decode_secret_values
58
+ return unless kube_chart_secret_values_path.file?
59
+ decoded_data = kube_helm_decode_json(YAML::load(kube_chart_secret_values_path.read))
60
+ kube_tmp_chart_secret_values_path.write(decoded_data.to_yaml)
61
+ end
62
+
63
+ def kube_helm_decode_json(json)
64
+ decode_value = proc do |value|
65
+ case value
66
+ when Array then value.map { |v| decode_value.call(v) }
67
+ when Hash then kube_helm_decode_json(value)
68
+ else
69
+ secret.extract(value)
70
+ end
71
+ end
72
+ json.each { |k, v| json[k] = decode_value.call(v) }
73
+ end
74
+
75
+ def kube_helm_decode_secret_files
76
+ return unless kube_chart_secret_path.directory?
77
+ Dir.glob(kube_chart_secret_path.join('**/*')).each do |entry|
78
+ next unless File.file?(entry)
79
+ secret_relative_path = Pathname(entry).subpath_of(kube_chart_secret_path)
80
+ IO.binwrite(kube_tmp_chart_secret_path(secret_relative_path), secret.extract(IO.binread(entry)))
81
+ end
82
+ end
83
+
84
+ def kube_generate_helm_chart_tpl
85
+ secret_directory = secret.nil? ? 'secret' : kube_tmp_chart_secret_path.subpath_of(kube_tmp_chart_path)
86
+ cont = <<-EOF
87
+ {{/* vim: set filetype=mustache: */}}
88
+
89
+ {{- define "dapp_secret_file" -}}
90
+ {{- $relative_file_path := index . 0 -}}
91
+ {{- $context := index . 1 -}}
92
+ {{- $context.Files.Get (print "#{secret_directory}/" $relative_file_path) -}}
93
+ {{- end -}}
94
+ EOF
95
+ kube_tmp_chart_path('templates/dapp_secret_file.tpl').write(cont)
96
+ end
97
+
98
+ def kube_flush_hooks_jobs(additional_values, set_options)
99
+ return if (config_jobs_names = kube_helm_hooks_jobs_to_delete(additional_values, set_options).keys).empty?
100
+ kube_job_list.reject { |name| config_jobs_names.include? name }.each do
101
+ log_process("Delete hooks job `#{job_name}` for release #{kube_release_name} ", short: true) { kube_delete_job!(name) }
102
+ end
103
+ end
104
+
105
+ def kube_helm_hooks_jobs_to_delete(additional_values, set_options)
106
+ generator = proc do |text|
107
+ text.split(/# Source.*|---/).reject {|c| c.strip.empty? }.map {|c| YAML::load(c) }.reduce({}) do |objects, c|
108
+ objects[c['kind']] ||= {}
109
+ objects[c['kind']][(c['metadata'] || {})['name']] = c
110
+ objects
111
+ end
112
+ end
113
+
114
+ args = [kube_release_name, kube_tmp_chart_path, additional_values, set_options, kube_helm_extra_options(dry_run: true)].flatten
115
+ output = shellout!("helm upgrade #{args.join(' ')}", log_verbose: false).stdout
116
+
117
+ manifest_start_index = output.lines.index("MANIFEST:\n") + 1
118
+ hook_start_index = output.lines.index("HOOKS:\n") + 1
119
+ configs = generator.call(output.lines[hook_start_index..manifest_start_index-1].join)
120
+
121
+ (configs['Job'] || []).reject do |_, c|
122
+ c['metadata'] ||= {}
123
+ c['metadata']['annotations'] ||= {}
124
+ c['metadata']['annotations']['helm.sh/resource-policy'] == 'keep'
125
+ end
126
+ end
127
+
128
+ def kube_job_list
129
+ kubernetes.job_list['items'].map { |i| i['metadata']['name'] }
130
+ end
131
+
132
+ def kube_delete_job!(name)
133
+ kubernetes.delete_job!(name)
134
+ loop do
135
+ break unless kubernetes.job?(name)
136
+ sleep 1
137
+ end
138
+ end
139
+
140
+ def kube_run_deploy(additional_values, set_options)
141
+ log_process("Deploy release #{kube_release_name} ", verbose: true) do
142
+ args = [kube_release_name, kube_tmp_chart_path, additional_values, set_options, kube_helm_extra_options].flatten
143
+ kubernetes.create_namespace!(kube_namespace) unless kubernetes.namespace?(kube_namespace)
144
+ shellout! "helm upgrade #{args.join(' ')}", force_log: true
145
+ end
146
+ end
147
+
148
+ def kube_check_helm_chart!
149
+ raise Error::Command, code: :project_helm_chart_not_found, data: { path: kube_chart_path } unless kube_chart_path.exist?
150
+ end
151
+
152
+ def kube_helm_extra_options(dry_run: dry_run?)
153
+ [].tap do |options|
154
+ options << "--namespace #{kube_namespace}"
155
+ options << '--install'
156
+ options << '--wait'
157
+ options << '--timeout 1800'
158
+
159
+ options << '--dry-run' if dry_run
160
+ options << '--debug' if dry_run || log_verbose?
161
+ end
162
+ end
163
+
164
+ def kube_tmp_chart_secret_values_path
165
+ kube_tmp_chart_path('decoded-secret-values.yaml')
166
+ end
167
+
168
+ def kube_tmp_chart_secret_path(*path)
169
+ kube_tmp_chart_path('decoded-secret', *path).tap { |p| p.parent.mkpath }
170
+ end
171
+
172
+ def kube_tmp_chart_path(*path)
173
+ @kube_tmp_path ||= Dir.mktmpdir('dapp-secret-', options[:tmp_dir_prefix] || '/tmp')
174
+ make_path(@kube_tmp_path, *path).expand_path.tap { |p| p.parent.mkpath }
175
+ end
176
+
177
+ def kube_chart_secret_values_path
178
+ kube_chart_path('secret-values.yaml').expand_path
179
+ end
180
+
181
+ def kube_chart_secret_path(*path)
182
+ kube_chart_path('secret', *path).expand_path
183
+ end
184
+
185
+ def kube_chart_path(*path)
186
+ make_path(self.path, '.helm', *path).expand_path
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,25 @@
1
+ module Dapp
2
+ module Kube
3
+ module Dapp
4
+ module Command
5
+ module Dismiss
6
+ def kube_dismiss
7
+ kube_check_helm!
8
+ kube_check_helm_release!
9
+ log_process("Delete release #{kube_release_name} ", verbose: true) do
10
+ with_log_indent do
11
+ shellout! "helm delete #{kube_release_name} --purge"
12
+ kubernetes.delete_namespace!(kube_namespace) if options[:with_namespace]
13
+ end
14
+ end
15
+ end
16
+
17
+ def kube_check_helm_release!
18
+ pr = shellout("helm list | grep #{kube_release_name}")
19
+ raise Error::Command, code: :helm_release_not_exist, data: { name: kube_release_name } if pr.status == 1 || pr.stdout.empty?
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ module Dapp
2
+ module Kube
3
+ module Dapp
4
+ module Command
5
+ module SecretFileEncrypt
6
+ def kube_secret_file_encrypt(file_path)
7
+ raise Error::Command, code: :secret_key_not_found if secret.nil?
8
+ raise Error::Command, code: :file_not_exist, data: { path: File.expand_path(file_path) } unless File.exist?(file_path)
9
+
10
+ encrypted_data = secret.generate(IO.binread(file_path))
11
+ if (output_file_path = options[:output_file_path]).nil?
12
+ puts encrypted_data
13
+ else
14
+ FileUtils.mkpath File.dirname(output_file_path)
15
+ IO.binwrite(output_file_path, encrypted_data)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,11 +1,11 @@
1
1
  module Dapp
2
- module Deployment
2
+ module Kube
3
3
  module Dapp
4
4
  module Command
5
5
  module SecretGenerate
6
- def deployment_secret_generate
6
+ def kube_secret_generate
7
7
  raise Error::Command, code: :secret_key_not_found if secret.nil?
8
- unless (data = $stdin.gets(nil)).nil?
8
+ unless (data = $stdin.noecho { |s| s.gets(nil) }).nil?
9
9
  puts unless data.end_with?("\n")
10
10
  puts secret.generate(data)
11
11
  end
@@ -1,9 +1,9 @@
1
1
  module Dapp
2
- module Deployment
2
+ module Kube
3
3
  module Dapp
4
4
  module Command
5
5
  module SecretKeyGenerate
6
- def deployment_secret_key_generate
6
+ def kube_secret_key_generate
7
7
  puts Secret.generate_key
8
8
  end
9
9
  end
@@ -0,0 +1,16 @@
1
+ module Dapp
2
+ module Kube
3
+ module Dapp
4
+ module Dapp
5
+ include Command::Deploy
6
+ include Command::Dismiss
7
+ include Command::SecretGenerate
8
+ include Command::SecretKeyGenerate
9
+ include Command::SecretFileEncrypt
10
+ include Command::Common
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ ::Dapp::Dapp.send(:include, ::Dapp::Kube::Dapp::Dapp)
@@ -0,0 +1,7 @@
1
+ module Dapp
2
+ module Kube
3
+ module Error
4
+ class Base < ::Dapp::Error::Base; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Dapp
2
+ module Kube
3
+ module Error
4
+ class Command < Base; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,191 @@
1
+ module Dapp
2
+ module Kube
3
+ class Kubernetes
4
+ def initialize(namespace: nil)
5
+ @namespace = namespace
6
+ @query_parameters = {}
7
+ end
8
+
9
+ def namespace
10
+ @namespace || 'default'
11
+ end
12
+
13
+ # Чтобы не перегружать методы явной передачей namespace.
14
+ # Данный метод может пригодиться только в ситуации, когда надо указать другой namespace,
15
+ # в большинстве случаев используется namespace из конструктора.
16
+ def with_namespace(namespace, &blk)
17
+ old_namespace = @namespace
18
+ begin
19
+ @namespace = namespace
20
+ return yield
21
+ ensure
22
+ @namespace = old_namespace
23
+ end
24
+ end
25
+
26
+ def with_query(query, &blk)
27
+ old_query = @query_parameters
28
+ begin
29
+ @query_parameters = query
30
+ return yield
31
+ ensure
32
+ @query_parameters = old_query
33
+ end
34
+ end
35
+
36
+ # NOTICE: Название метода аналогично kind'у выдаваемого результата.
37
+ # NOTICE: В данном случае в результате kind=DeploymentList.
38
+ # NOTICE: Методы создания/обновления/удаления сущностей kubernetes заканчиваются на '!'. Например, create_deployment!.
39
+
40
+ {
41
+ '/api/v1' => [:service, :replicationcontroller, :pod],
42
+ '/apis/extensions/v1beta1' => [:deployment, :replicaset],
43
+ '/apis/batch/v1' => [:job]
44
+ }.each do |api, objects|
45
+ objects.each do |object|
46
+ define_method :"#{object}_list" do |**query_parameters|
47
+ request!(:get, "#{api}/namespaces/#{namespace}/#{object}s", **query_parameters)
48
+ end
49
+
50
+ define_method object do |name, **query_parameters|
51
+ request!(:get, "#{api}/namespaces/#{namespace}/#{object}s/#{name}", **query_parameters)
52
+ end
53
+
54
+ define_method "#{object}_status" do |name, **query_parameters|
55
+ request!(:get, "#{api}/namespaces/#{namespace}/#{object}s/#{name}/status", **query_parameters)
56
+ end
57
+
58
+ define_method :"create_#{object}!" do |spec, **query_parameters|
59
+ request!(:post, "#{api}/namespaces/#{namespace}/#{object}s", body: spec, **query_parameters)
60
+ end
61
+
62
+ define_method :"replace_#{object}!" do |name, spec, **query_parameters|
63
+ request!(:put, "#{api}/namespaces/#{namespace}/#{object}s/#{name}", body: spec, **query_parameters)
64
+ end
65
+
66
+ define_method :"delete_#{object}!" do |name, **query_parameters|
67
+ request!(:delete, "#{api}/namespaces/#{namespace}/#{object}s/#{name}", **query_parameters)
68
+ end
69
+
70
+ define_method :"delete_#{object}s!" do |**query_parameters|
71
+ request!(:delete, "#{api}/namespaces/#{namespace}/#{object}s", **query_parameters)
72
+ end
73
+
74
+ define_method :"#{object}?" do |name, **query_parameters|
75
+ public_send(:"#{object}_list", **query_parameters)['items'].map { |item| item['metadata']['name'] }.include?(name)
76
+ end
77
+ end
78
+ end
79
+
80
+ def namespace_list(**query_parameters)
81
+ request!(:get, '/api/v1/namespaces', **query_parameters)
82
+ end
83
+
84
+ def namespace?(name, **query_parameters)
85
+ namespace_list(**query_parameters)['items'].map { |item| item['metadata']['name'] }.include?(name)
86
+ end
87
+
88
+ def create_namespace!(name, **query_parameters)
89
+ request!(:post, '/api/v1/namespaces', body: { metadata: { name: name } }, **query_parameters)
90
+ end
91
+
92
+ def delete_namespace!(name, **query_parameters)
93
+ request!(:delete, "/api/v1/namespaces/#{name}", **query_parameters)
94
+ end
95
+
96
+ def pod_log(name, follow: false, **query_parameters, &blk)
97
+ excon_parameters = follow ? { response_block: blk } : {}
98
+ request!(:get,
99
+ "/api/v1/namespaces/#{namespace}/pods/#{name}/log",
100
+ excon_parameters: excon_parameters,
101
+ **{ follow: follow }.merge(query_parameters))
102
+ rescue Excon::Error::Timeout
103
+ raise Error::Timeout
104
+ end
105
+
106
+ def event_list(**query_parameters)
107
+ request!(:get, "/api/v1/namespaces/#{namespace}/events", **query_parameters)
108
+ end
109
+
110
+ protected
111
+
112
+ # query_parameters — соответствует 'Query Parameters' в документации kubernetes
113
+ # excon_parameters — соответствует опциям Excon
114
+ # body — hash для http-body, соответствует 'Body Parameters' в документации kubernetes, опционален
115
+ def request!(method, path, body: nil, excon_parameters: {}, **query_parameters)
116
+ with_connection(excon_parameters: excon_parameters) do |conn|
117
+ request_parameters = {method: method, path: path, query: @query_parameters.merge(query_parameters)}
118
+ request_parameters[:body] = JSON.dump(body) if body
119
+ load_body! conn.request(request_parameters), request_parameters
120
+ end
121
+ end
122
+
123
+ def load_body!(response, request_parameters)
124
+ response_ok = response.status.to_s.start_with? '2'
125
+
126
+ if response_ok
127
+ JSON.parse(response.body)
128
+ else
129
+ err_data = {}
130
+ err_data[:response_http_status] = response.status
131
+ if response_body = (JSON.parse(response.body) rescue nil)
132
+ err_data[:response_body] = response_body
133
+ else
134
+ err_data[:response_raw_body] = response.body
135
+ end
136
+ err_data[:request_parameters] = request_parameters
137
+
138
+ if response.status.to_s.start_with? '5'
139
+ raise Error::Base, code: :server_error, data: err_data
140
+ elsif response.status.to_s == '404'
141
+ raise Error::NotFound, data: err_data
142
+ else not response.status.to_s.start_with? '2'
143
+ raise Error::Base, code: :bad_request, data: err_data
144
+ end
145
+ end
146
+ end
147
+
148
+ def with_connection(excon_parameters: {}, &blk)
149
+ old_ssl_ca_file = Excon.defaults[:ssl_ca_file]
150
+ old_middlewares = Excon.defaults[:middlewares].dup
151
+
152
+ begin
153
+ Excon.defaults[:ssl_ca_file] = kube_config.fetch('clusters', [{}]).first.fetch('cluster', {}).fetch('certificate-authority', nil)
154
+ Excon.defaults[:middlewares] << Excon::Middleware::RedirectFollower
155
+
156
+ return yield Excon.new(kube_server_url, **kube_server_options(excon_parameters))
157
+ ensure
158
+ Excon.defaults[:ssl_ca_file] = old_ssl_ca_file
159
+ Excon.defaults[:middlewares] = old_middlewares
160
+ end
161
+ end
162
+
163
+ def kube_server_url
164
+ @kube_server_url ||= begin
165
+ kube_config.fetch('clusters', [{}]).first.fetch('cluster', {}).fetch('server', nil).tap do |url|
166
+ begin
167
+ Excon.new(url, **kube_server_options).get
168
+ rescue Excon::Error::Socket
169
+ raise Error::ConnectionRefused, code: :kube_server_connection_refused, data: { url: url }
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ def kube_server_options(excon_parameters = {})
176
+ { client_cert: kube_config.fetch('users', [{}]).first.fetch('user', {}).fetch('client-certificate', nil),
177
+ client_key: kube_config.fetch('users', [{}]).first.fetch('user', {}).fetch('client-key', nil) }.merge(excon_parameters)
178
+ end
179
+
180
+ def kube_config
181
+ @kube_config ||= begin
182
+ if File.exist?((kube_config_path = File.join(ENV['HOME'], '.kube/config')))
183
+ YAML.load_file(kube_config_path)
184
+ else
185
+ raise Error::Base, code: :kube_config_not_found, data: { path: kube_config_path }
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end