uffizzi-cli 2.2.0 → 2.2.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c57cec1f82e6f54de217f0d1e17b88a8ce5bb2dd58eb702df2a10930508f1203
4
- data.tar.gz: b904d36aef21a479dac014429f09eebd80cb1715dfd71cf2457fea34cb5f5175
3
+ metadata.gz: 9f1280afea82284077769e8545880e7420aede5c114a837f3ae2e21482637bac
4
+ data.tar.gz: e9f5235cb793d83b710c291c62f5027f0226d400aa836f9ea56c18f95b474d06
5
5
  SHA512:
6
- metadata.gz: 9d5f9f27fab4efec5b44910d37e53ce60b65ba6995e89a5e65b1a7d60c0925899f7cce271c2670d728aeb66845da3c075cab9492baf1f2c45ac3d80504ae2b4f
7
- data.tar.gz: c3b2a87f53468a55939cde535cf0389eaca7b2666b581312089ee0c51eed195a58366c7c763b16fad8da53ffe8ca9ba92b4cd4bfea88ccffc09397b4d6f39204
6
+ metadata.gz: ca04830842f417cb76799ae64c1106866a4d91a7774a07fa3c63fbbe726eac5ab78381c2059ef23a6157948b457984e723bc0e4273d6dbeda48d406379ebe3e4
7
+ data.tar.gz: 119bb3460dc17116351d4553400867b6225e6431614bdb6934d5f4dc1c532c0cf21b7d61a254210ac2a0eb0643d7183a6ac301b29ea352d81f6dc6feb6a339f2
@@ -16,7 +16,7 @@ module Uffizzi
16
16
  Uffizzi::Token.delete if Uffizzi::Token.exists?
17
17
  end
18
18
 
19
- def check_login(project_option)
19
+ def check_login(project_option = nil)
20
20
  raise Uffizzi::Error.new('You are not logged in. Run `uffizzi login`.') unless signed_in?
21
21
  raise Uffizzi::Error.new('This command needs project to be set in config file') unless project_set?(project_option)
22
22
  end
@@ -76,19 +76,18 @@ module Uffizzi
76
76
  def run(command, command_args = {})
77
77
  Uffizzi.ui.output_format = options[:output]
78
78
  Uffizzi::AuthHelper.check_login(options[:project])
79
- project_slug = options[:project].nil? ? ConfigFile.read_option(:project) : options[:project]
80
79
 
81
80
  case command
82
81
  when 'list'
83
- handle_list_command(project_slug)
82
+ handle_list_command
84
83
  when 'create'
85
- handle_create_command(project_slug, command_args)
84
+ handle_create_command(command_args)
86
85
  when 'describe'
87
- handle_describe_command(project_slug, command_args)
86
+ handle_describe_command(command_args)
88
87
  when 'delete'
89
- handle_delete_command(project_slug, command_args)
88
+ handle_delete_command(command_args)
90
89
  when 'update-kubeconfig'
91
- handle_update_kubeconfig_command(project_slug, command_args)
90
+ handle_update_kubeconfig_command(command_args)
92
91
  when 'disconnect'
93
92
  ClusterDisconnectService.handle(options)
94
93
  when 'sleep'
@@ -98,13 +97,12 @@ module Uffizzi
98
97
  end
99
98
  end
100
99
 
101
- def handle_list_command(project_slug)
100
+ def handle_list_command
102
101
  is_all = options[:all]
103
102
  response = if is_all
104
- get_account_clusters(ConfigFile.read_option(:server), ConfigFile.read_option(:account, :id))
103
+ get_account_clusters(server, ConfigFile.read_option(:account, :id))
105
104
  else
106
- oidc_token = ConfigFile.read_option(:oidc_token)
107
- get_project_clusters(ConfigFile.read_option(:server), project_slug, oidc_token: oidc_token)
105
+ get_project_clusters(server, project_slug, oidc_token: oidc_token)
108
106
  end
109
107
 
110
108
  if ResponseHelper.ok?(response)
@@ -115,7 +113,7 @@ module Uffizzi
115
113
  end
116
114
 
117
115
  # rubocop:disable Metrics/PerceivedComplexity
118
- def handle_create_command(project_slug, command_args)
116
+ def handle_create_command(command_args)
119
117
  Uffizzi.ui.disable_stdout if Uffizzi.ui.output_format
120
118
 
121
119
  if options[:name]
@@ -125,23 +123,20 @@ module Uffizzi
125
123
  end
126
124
 
127
125
  cluster_name = command_args[:name] || options[:name] || ClusterService.generate_name
128
- creation_source = options[:"creation-source"] || ClusterService::MANUAL_CREATION_SOURCE
129
- k8s_version = options[:"k8s-version"]
130
126
  Uffizzi.ui.say_error_and_exit("Cluster name: #{cluster_name} is not valid.") unless ClusterService.valid_name?(cluster_name)
131
127
 
132
- params = cluster_creation_params(
133
- name: cluster_name,
134
- creation_source: creation_source,
135
- manifest_file_path: options[:manifest],
136
- k8s_version: k8s_version,
137
- )
138
- response = create_cluster(ConfigFile.read_option(:server), project_slug, params)
128
+ unless ClusterService.valid_name?(cluster_name)
129
+ Uffizzi.ui.say_error_and_exit("Cluster name: #{cluster_name} is not valid.")
130
+ end
131
+
132
+ params = cluster_creation_params(cluster_name)
133
+ response = create_cluster(server, project_slug, params)
139
134
 
140
135
  return ResponseHelper.handle_failed_response(response) unless ResponseHelper.created?(response)
141
136
 
142
137
  spinner = TTY::Spinner.new("[:spinner] Creating cluster #{cluster_name}...", format: :dots)
143
138
  spinner.auto_spin
144
- cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, ConfigFile.read_option(:oidc_token))
139
+ cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, oidc_token)
145
140
 
146
141
  if ClusterService.failed?(cluster_data[:state])
147
142
  spinner.error
@@ -151,26 +146,28 @@ module Uffizzi
151
146
  spinner.success
152
147
  handle_succeed_create_response(cluster_data)
153
148
  rescue SystemExit, Interrupt, SocketError
154
- handle_interrupt_creation(cluster_name, ConfigFile.read_option(:server), project_slug)
149
+ handle_interrupt_creation(cluster_name)
155
150
  end
156
151
  # rubocop:enable Metrics/PerceivedComplexity
157
152
 
158
- def handle_describe_command(project_slug, command_args)
159
- cluster_data = fetch_cluster_data(project_slug, command_args[:cluster_name])
153
+ def handle_describe_command(command_args)
154
+ cluster_data = ClusterService.fetch_cluster_data(command_args[:cluster_name], **cluster_api_connection_params)
155
+ render_data = ClusterService.build_render_data(cluster_data)
160
156
 
161
- handle_succeed_describe(cluster_data)
157
+ Uffizzi.ui.output_format = Uffizzi::UI::Shell::PRETTY_LIST
158
+ Uffizzi.ui.say(render_data)
162
159
  end
163
160
 
164
- def handle_delete_command(project_slug, command_args)
161
+ def handle_delete_command(command_args)
165
162
  cluster_name = command_args[:cluster_name]
166
163
  is_delete_kubeconfig = options[:'delete-config']
167
164
 
168
- return handle_delete_cluster(project_slug, cluster_name) unless is_delete_kubeconfig
165
+ return handle_delete_cluster(cluster_name) unless is_delete_kubeconfig
169
166
 
170
- cluster_data = fetch_cluster_data(project_slug, cluster_name)
167
+ cluster_data = ClusterService.fetch_cluster_data(cluster_name, **cluster_api_connection_params)
171
168
  kubeconfig = parse_kubeconfig(cluster_data[:kubeconfig])
172
169
 
173
- handle_delete_cluster(project_slug, cluster_name)
170
+ handle_delete_cluster(cluster_name)
174
171
  exclude_kubeconfig(cluster_data[:id], kubeconfig) if kubeconfig.present?
175
172
  end
176
173
 
@@ -194,12 +191,12 @@ module Uffizzi
194
191
  end
195
192
  end
196
193
 
197
- def handle_delete_cluster(project_slug, cluster_name)
194
+ def handle_delete_cluster(cluster_name)
198
195
  params = {
199
196
  cluster_name: cluster_name,
200
- oidc_token: ConfigFile.read_option(:oidc_token),
197
+ oidc_token: oidc_token,
201
198
  }
202
- response = delete_cluster(ConfigFile.read_option(:server), project_slug, params)
199
+ response = delete_cluster(server, project_slug, params)
203
200
 
204
201
  if ResponseHelper.no_content?(response)
205
202
  Uffizzi.ui.say("Cluster #{cluster_name} deleted")
@@ -208,10 +205,10 @@ module Uffizzi
208
205
  end
209
206
  end
210
207
 
211
- def handle_update_kubeconfig_command(project_slug, command_args)
208
+ def handle_update_kubeconfig_command(command_args)
212
209
  kubeconfig_path = options[:kubeconfig] || KubeconfigService.default_path
213
210
  cluster_name = command_args[:cluster_name]
214
- cluster_data = fetch_cluster_data(project_slug, cluster_name)
211
+ cluster_data = ClusterService.fetch_cluster_data(cluster_name, **cluster_api_connection_params)
215
212
 
216
213
  unless cluster_data[:kubeconfig].present?
217
214
  say_error_update_kubeconfig(cluster_data)
@@ -289,13 +286,15 @@ module Uffizzi
289
286
  end
290
287
  end
291
288
 
292
- def cluster_creation_params(name:, creation_source:, manifest_file_path:, k8s_version:)
289
+ def cluster_creation_params(cluster_name)
290
+ creation_source = options[:"creation-source"] || ClusterService::MANUAL_CREATION_SOURCE
291
+ manifest_file_path = options[:manifest]
292
+ k8s_version = options[:"k8s-version"]
293
293
  manifest_content = load_manifest_file(manifest_file_path)
294
- oidc_token = Uffizzi::ConfigFile.read_option(:oidc_token)
295
294
 
296
295
  {
297
296
  cluster: {
298
- name: name,
297
+ name: cluster_name,
299
298
  manifest: manifest_content,
300
299
  creation_source: creation_source,
301
300
  k8s_version: k8s_version,
@@ -312,7 +311,7 @@ module Uffizzi
312
311
  raise Uffizzi::Error.new(e.message)
313
312
  end
314
313
 
315
- def handle_interrupt_creation(cluster_name, server, project_slug)
314
+ def handle_interrupt_creation(cluster_name)
316
315
  deletion_response = delete_cluster(server, project_slug, cluster_name: cluster_name)
317
316
  deletion_message = if ResponseHelper.no_content?(deletion_response)
318
317
  "The cluster #{cluster_name} has been disabled."
@@ -348,24 +347,6 @@ module Uffizzi
348
347
  end.join("\n")
349
348
  end
350
349
 
351
- def handle_succeed_describe(cluster_data)
352
- prepared_cluster_data = {
353
- name: cluster_data[:name],
354
- status: cluster_data[:state],
355
- created: Time.strptime(cluster_data[:created_at], '%Y-%m-%dT%H:%M:%S.%N').strftime('%a %b %d %H:%M:%S %Y'),
356
- url: cluster_data[:host],
357
- k8s_version: cluster_data[:k8s_version],
358
- }
359
-
360
- rendered_cluster_data = if Uffizzi.ui.output_format.nil?
361
- prepared_cluster_data.map { |k, v| "- #{k.to_s.upcase}: #{v}" }.join("\n").strip
362
- else
363
- prepared_cluster_data
364
- end
365
-
366
- Uffizzi.ui.say(rendered_cluster_data)
367
- end
368
-
369
350
  def handle_succeed_create_response(cluster_data)
370
351
  kubeconfig_path = options[:kubeconfig] || KubeconfigService.default_path
371
352
  is_update_current_context = options[:'update-current-context']
@@ -432,20 +413,6 @@ module Uffizzi
432
413
  Psych.safe_load(Base64.decode64(kubeconfig))
433
414
  end
434
415
 
435
- def fetch_cluster_data(project_slug, cluster_name)
436
- params = {
437
- cluster_name: cluster_name,
438
- oidc_token: ConfigFile.read_option(:oidc_token),
439
- }
440
- response = get_cluster(ConfigFile.read_option(:server), project_slug, params)
441
-
442
- if ResponseHelper.ok?(response)
443
- response.dig(:body, :cluster)
444
- else
445
- ResponseHelper.handle_failed_response(response)
446
- end
447
- end
448
-
449
416
  def save_previous_current_context(kubeconfig_path, current_context)
450
417
  return if kubeconfig_path.nil? || ConfigHelper.previous_current_context_by_path(kubeconfig_path).present?
451
418
 
@@ -458,5 +425,25 @@ module Uffizzi
458
425
  Uffizzi.ui.say('Please update the current context or provide a cluster name.')
459
426
  Uffizzi.ui.say('$uffizzi cluster sleep my-cluster')
460
427
  end
428
+
429
+ def cluster_api_connection_params
430
+ {
431
+ server: server,
432
+ project_slug: project_slug,
433
+ oidc_token: oidc_token,
434
+ }
435
+ end
436
+
437
+ def oidc_token
438
+ @oidc_token ||= ConfigFile.read_option(:oidc_token)
439
+ end
440
+
441
+ def project_slug
442
+ @project_slug ||= options[:project].nil? ? ConfigFile.read_option(:project) : options[:project]
443
+ end
444
+
445
+ def server
446
+ @server ||= ConfigFile.read_option(:server)
447
+ end
461
448
  end
462
449
  end
@@ -15,67 +15,110 @@ module Uffizzi
15
15
  method_option :kubeconfig, type: :string
16
16
  method_option :'k8s-version', required: false, type: :string
17
17
  def start(config_path = 'skaffold.yaml')
18
- Uffizzi::AuthHelper.check_login(options[:project])
18
+ run('start', config_path: config_path)
19
+ end
20
+
21
+ desc 'stop', 'Stop dev environment'
22
+ def stop
23
+ run('stop')
24
+ end
25
+
26
+ desc 'describe', 'Describe dev environment'
27
+ def describe
28
+ run('describe')
29
+ end
30
+
31
+ desc 'delete', 'Delete dev environment'
32
+ def delete
33
+ run('delete')
34
+ end
35
+
36
+ private
37
+
38
+ def run(command, command_args = {})
39
+ Uffizzi::AuthHelper.check_login
40
+
41
+ case command
42
+ when 'start'
43
+ handle_start_command(command_args)
44
+ when 'stop'
45
+ handle_stop_command
46
+ when 'describe'
47
+ handle_describe_command
48
+ when 'delete'
49
+ handle_delete_command
50
+ end
51
+ end
52
+
53
+ def handle_start_command(command_args)
54
+ config_path = command_args[:config_path]
19
55
  DevService.check_skaffold_existence
20
- DevService.check_running_daemon if options[:quiet]
56
+ DevService.check_no_running_process!
21
57
  DevService.check_skaffold_config_existence(config_path)
22
- cluster_id, cluster_name = start_create_cluster
23
- kubeconfig = wait_cluster_creation(cluster_name)
58
+
59
+ if dev_environment.empty?
60
+ DevService.set_startup_state
61
+ cluster_name = start_create_cluster
62
+ wait_cluster_creation(cluster_name)
63
+ DevService.set_dev_environment_config(cluster_name, config_path, options)
64
+ DevService.set_cluster_deployed_state
65
+ end
24
66
 
25
67
  if options[:quiet]
26
68
  launch_demonise_skaffold(config_path)
27
69
  else
28
- DevService.start_basic_skaffold(config_path, options)
29
- end
30
- ensure
31
- if defined?(cluster_name).present? && defined?(cluster_id).present?
32
- kubeconfig = defined?(kubeconfig).present? ? kubeconfig : nil
33
- handle_delete_cluster(cluster_id, cluster_name, kubeconfig)
70
+ launch_basic_skaffold(config_path)
34
71
  end
35
72
  end
36
73
 
37
- desc 'stop', 'Stop dev environment'
38
- def stop
39
- return Uffizzi.ui.say('Uffizzi dev is not running') unless File.exist?(DevService.pid_path)
74
+ def handle_stop_command
75
+ DevService.check_running_process!
76
+ DevService.stop_process
77
+ Uffizzi.ui.say('Uffizzi dev was stopped')
78
+ end
40
79
 
41
- pid = File.read(DevService.pid_path).to_i
42
- File.delete(DevService.pid_path)
80
+ def handle_describe_command
81
+ DevService.check_environment_exist!
43
82
 
44
- Uffizzi.process.kill('QUIT', pid)
45
- Uffizzi.ui.say('Uffizzi dev was stopped')
46
- rescue Errno::ESRCH
47
- Uffizzi.ui.say('Uffizzi dev is not running')
48
- File.delete(DevService.pid_path)
83
+ cluster_data = fetch_dev_env_cluster!
84
+ cluster_render_data = ClusterService.build_render_data(cluster_data)
85
+ dev_environment_render_data = cluster_render_data.merge(config_path: dev_environment[:config_path])
86
+
87
+ Uffizzi.ui.output_format = Uffizzi::UI::Shell::PRETTY_LIST
88
+ Uffizzi.ui.say(dev_environment_render_data)
49
89
  end
50
90
 
51
- private
91
+ def handle_delete_command
92
+ DevService.check_environment_exist!
93
+
94
+ if DevService.process_running?
95
+ DevService.stop_process
96
+ Uffizzi.ui.say('Uffizzi dev was stopped')
97
+ end
98
+
99
+ cluster_data = fetch_dev_env_cluster!
100
+ handle_delete_cluster(cluster_data)
101
+ DevService.clear_dev_environment_config
102
+ end
52
103
 
53
104
  def start_create_cluster
54
- params = cluster_creation_params(
55
- name: ClusterService.generate_name,
56
- creation_source: ClusterService::MANUAL_CREATION_SOURCE,
57
- k8s_version: options[:"k8s-version"],
58
- )
105
+ params = cluster_creation_params
59
106
  Uffizzi.ui.say('Start creating a cluster')
60
- response = create_cluster(ConfigFile.read_option(:server), project_slug, params)
107
+ response = create_cluster(server, project_slug, params)
61
108
  return ResponseHelper.handle_failed_response(response) unless ResponseHelper.created?(response)
62
109
 
63
- cluster_id = response.dig(:body, :cluster, :id)
64
- cluster_name = response.dig(:body, :cluster, :name)
65
-
66
- [cluster_id, cluster_name]
110
+ response.dig(:body, :cluster, :name)
67
111
  end
68
112
 
69
113
  def wait_cluster_creation(cluster_name)
70
114
  Uffizzi.ui.say('Checking the cluster status...')
71
- cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, ConfigFile.read_option(:oidc_token))
115
+ cluster_data = ClusterService.wait_cluster_deploy(project_slug, cluster_name, oidc_token)
72
116
 
73
117
  if ClusterService.failed?(cluster_data[:state])
74
118
  Uffizzi.ui.say_error_and_exit("Cluster with name: #{cluster_name} failed to be created.")
75
119
  end
76
120
 
77
121
  handle_succeed_cluster_creation(cluster_data)
78
- parse_kubeconfig(cluster_data[:kubeconfig])
79
122
  end
80
123
 
81
124
  def handle_succeed_cluster_creation(cluster_data)
@@ -109,30 +152,30 @@ module Uffizzi
109
152
  ConfigFile.write_option(:clusters, clusters_config)
110
153
  end
111
154
 
112
- def cluster_creation_params(name:, creation_source:, k8s_version:)
113
- oidc_token = Uffizzi::ConfigFile.read_option(:oidc_token)
114
-
155
+ def cluster_creation_params
115
156
  {
116
157
  cluster: {
117
- name: name,
158
+ name: ClusterService.generate_name,
118
159
  manifest: nil,
119
- creation_source: creation_source,
120
- k8s_version: k8s_version,
160
+ creation_source: ClusterService::MANUAL_CREATION_SOURCE,
161
+ k8s_version: options[:"k8s-version"],
121
162
  },
122
163
  token: oidc_token,
123
164
  }
124
165
  end
125
166
 
126
- def handle_delete_cluster(cluster_id, cluster_name, kubeconfig)
127
- return if cluster_id.nil? || cluster_name.nil?
167
+ def handle_delete_cluster(cluster_data)
168
+ cluster_id = cluster_data[:id]
169
+ cluster_name = cluster_data[:name]
170
+ kubeconfig = parse_kubeconfig(cluster_data[:kubeconfig])
128
171
 
129
172
  exclude_kubeconfig(cluster_id, kubeconfig) if kubeconfig.present?
130
173
 
131
174
  params = {
132
175
  cluster_name: cluster_name,
133
- oidc_token: ConfigFile.read_option(:oidc_token),
176
+ oidc_token: oidc_token,
134
177
  }
135
- response = delete_cluster(ConfigFile.read_option(:server), project_slug, params)
178
+ response = delete_cluster(server, project_slug, params)
136
179
 
137
180
  if ResponseHelper.no_content?(response)
138
181
  Uffizzi.ui.say("Cluster #{cluster_name} deleted")
@@ -179,20 +222,59 @@ module Uffizzi
179
222
  def launch_demonise_skaffold(config_path)
180
223
  Uffizzi.process.daemon(true)
181
224
 
182
- at_exit do
183
- File.delete(DevService.pid_path) if File.exist?(DevService.pid_path)
225
+ Uffizzi.at_exit do
226
+ DevService.stop_process
184
227
  end
185
228
 
229
+ DevService.save_pid
186
230
  File.delete(DevService.logs_path) if File.exist?(DevService.logs_path)
187
- File.write(DevService.pid_path, Uffizzi.process.pid)
188
231
  DevService.start_check_pid_file_existence
189
232
  DevService.start_demonised_skaffold(config_path, options)
190
233
  rescue StandardError => e
191
234
  File.open(DevService.logs_path, 'a') { |f| f.puts(e.message) }
192
235
  end
193
236
 
237
+ def launch_basic_skaffold(config_path)
238
+ Uffizzi.at_exit do
239
+ DevService.stop_process
240
+ end
241
+
242
+ DevService.save_pid
243
+ DevService.start_check_pid_file_existence
244
+ DevService.start_basic_skaffold(config_path, options)
245
+ end
246
+
247
+ def fetch_dev_env_cluster!
248
+ if DevService.startup?
249
+ Uffizzi.ui.say_error_and_exit('Dev environment not started yet')
250
+ end
251
+
252
+ cluster_name = dev_environment[:cluster_name]
253
+ ClusterService.fetch_cluster_data(cluster_name, **cluster_api_connection_params)
254
+ end
255
+
256
+ def dev_environment
257
+ @dev_environment ||= DevService.dev_environment
258
+ end
259
+
260
+ def cluster_api_connection_params
261
+ {
262
+ server: server,
263
+ project_slug: project_slug,
264
+ oidc_token: oidc_token,
265
+ }
266
+ end
267
+
194
268
  def project_slug
195
269
  @project_slug ||= ConfigFile.read_option(:project)
196
270
  end
271
+
272
+ def oidc_token
273
+ @oidc_token ||= ConfigFile.read_option(:oidc_token)
274
+ end
275
+
276
+ def server
277
+ @server ||= ConfigFile.read_option(:server)
278
+ end
197
279
  end
198
280
  end
@@ -52,6 +52,14 @@ module Uffizzi
52
52
  cluster_previous_current_contexts.detect { |c| c[:kubeconfig_path] == path }
53
53
  end
54
54
 
55
+ def set_dev_environment(cluster_name, params = {})
56
+ { cluster_name: cluster_name }.merge(params)
57
+ end
58
+
59
+ def dev_environment
60
+ read_option_from_config(:dev_environment) || {}
61
+ end
62
+
55
63
  private
56
64
 
57
65
  def clusters
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'uffizzi/response_helper'
3
4
  require 'uffizzi/clients/api/api_client'
4
5
 
5
6
  class ClusterService
@@ -104,5 +105,28 @@ class ClusterService
104
105
  regex = /\A[a-zA-Z0-9-]*\z/
105
106
  regex.match?(name)
106
107
  end
108
+
109
+ def fetch_cluster_data(cluster_name, server:, project_slug:, oidc_token:)
110
+ params = {
111
+ cluster_name: cluster_name,
112
+ oidc_token: oidc_token,
113
+ }
114
+ response = get_cluster(server, project_slug, params)
115
+
116
+ if Uffizzi::ResponseHelper.ok?(response)
117
+ response.dig(:body, :cluster)
118
+ else
119
+ Uffizzi::ResponseHelper.handle_failed_response(response)
120
+ end
121
+ end
122
+
123
+ def build_render_data(cluster_data)
124
+ {
125
+ name: cluster_data[:name],
126
+ status: cluster_data[:state],
127
+ created: Time.strptime(cluster_data[:created_at], '%Y-%m-%dT%H:%M:%S.%N').strftime('%a %b %d %H:%M:%S %Y'),
128
+ url: cluster_data[:host],
129
+ }
130
+ end
107
131
  end
108
132
  end
@@ -3,27 +3,70 @@
3
3
  require 'uffizzi/clients/api/api_client'
4
4
 
5
5
  class DevService
6
+ DEFAULT_REGISTRY_REPO = 'registry.uffizzi.com'
7
+ STARTUP_STATE = 'startup'
8
+ CLUSTER_DEPLOYED_STATE = 'cluster_deployed'
9
+
6
10
  class << self
7
11
  include ApiClient
8
12
 
9
- DEFAULT_REGISTRY_REPO = 'registry.uffizzi.com'
13
+ def check_no_running_process!
14
+ if process_running?
15
+ Uffizzi.ui.say_error_and_exit("You have already started uffizzi dev. To stop the process do 'uffizzi dev stop'")
16
+ end
17
+ end
10
18
 
11
- def check_running_daemon
12
- return unless File.exist?(pid_path)
19
+ def check_running_process!
20
+ unless process_running?
21
+ Uffizzi.ui.say_error_and_exit('Uffizzi dev is not running')
22
+ end
23
+ end
13
24
 
14
- pid = File.read(pid_path)
15
- File.delete(pid_path) if pid.blank?
16
- Uffizzi.process.kill(0, pid.to_i)
25
+ def check_environment_exist!
26
+ if dev_environment.empty?
27
+ Uffizzi.ui.say_error_and_exit('Uffizzi dev does not exist')
28
+ end
29
+ end
17
30
 
18
- Uffizzi.ui.say_error_and_exit("You have already started uffizzi dev as daemon. To stop the process do 'uffizzi dev stop'")
31
+ def stop_process
32
+ dev_pid = running_pid
33
+ skaffold_pid = running_skaffold_pid
34
+
35
+ begin
36
+ Uffizzi.process.kill('INT', skaffold_pid)
37
+ rescue Errno::ESRCH
38
+ end
39
+
40
+ wait_process_stop(skaffold_pid)
41
+ delete_pid
42
+
43
+ Uffizzi.process.kill('INT', dev_pid)
19
44
  rescue Errno::ESRCH
20
- File.delete(pid_path)
45
+ delete_pid
46
+ end
47
+
48
+ def wait_process_stop(pid)
49
+ loop do
50
+ Uffizzi.process.kill(0, pid)
51
+ sleep(1)
52
+ end
53
+ rescue Errno::ESRCH
54
+ end
55
+
56
+ def process_running?
57
+ pid = running_pid
58
+ return false unless pid.positive?
59
+
60
+ Uffizzi.process.kill(0, pid.to_i)
61
+ true
62
+ rescue Errno::ESRCH
63
+ false
21
64
  end
22
65
 
23
66
  def start_check_pid_file_existence
24
- Thread.new do
67
+ Uffizzi.thread.new do
25
68
  loop do
26
- Uffizzi.process.kill('QUIT', Uffizzi.process.pid) unless File.exist?(pid_path)
69
+ stop_process unless File.exist?(pid_path)
27
70
  sleep(1)
28
71
  end
29
72
  end
@@ -33,7 +76,12 @@ class DevService
33
76
  Uffizzi.ui.say('Start skaffold')
34
77
  cmd = build_skaffold_dev_command(config_path, options)
35
78
 
79
+ Uffizzi.signal.trap('INT') {}
80
+
36
81
  Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr|
82
+ pid = wait_thr.pid
83
+ skaffold_pid = find_skaffold_pid(pid)
84
+ save_skaffold_pid(skaffold_pid)
37
85
  stdout_and_stderr.each { |l| Uffizzi.ui.say(l) }
38
86
  wait_thr.value
39
87
  end
@@ -44,6 +92,10 @@ class DevService
44
92
  cmd = build_skaffold_dev_command(config_path, options)
45
93
 
46
94
  Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr|
95
+ pid = wait_thr.pid
96
+ skaffold_pid = find_skaffold_pid(pid)
97
+ save_skaffold_pid(skaffold_pid)
98
+
47
99
  File.open(logs_path, 'a') do |f|
48
100
  stdout_and_stderr.each do |line|
49
101
  f.puts(line)
@@ -80,6 +132,10 @@ class DevService
80
132
  File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'uffizzi_dev.pid')
81
133
  end
82
134
 
135
+ def skaffold_pid_path
136
+ File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'skaffold_dev.pid')
137
+ end
138
+
83
139
  def logs_path
84
140
  File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'uffizzi_dev.log')
85
141
  end
@@ -104,5 +160,83 @@ class DevService
104
160
 
105
161
  File.expand_path(path)
106
162
  end
163
+
164
+ def running_pid
165
+ return nil.to_i unless File.exist?(pid_path)
166
+
167
+ File.read(pid_path).to_i
168
+ end
169
+
170
+ def save_pid
171
+ File.write(pid_path, Uffizzi.process.pid)
172
+ end
173
+
174
+ def delete_pid
175
+ File.delete(pid_path) if File.exist?(pid_path)
176
+ File.delete(skaffold_pid_path) if File.exist?(skaffold_pid_path)
177
+ end
178
+
179
+ def running_skaffold_pid
180
+ return nil.to_i unless File.exist?(skaffold_pid_path)
181
+
182
+ File.read(skaffold_pid_path).to_i
183
+ end
184
+
185
+ def save_skaffold_pid(pid)
186
+ File.write(skaffold_pid_path, pid)
187
+ end
188
+
189
+ def set_dev_environment_config(cluster_name, config_path, options)
190
+ params = options.merge(config_path: File.expand_path(config_path))
191
+ new_dev_environment = Uffizzi::ConfigHelper.set_dev_environment(cluster_name, params)
192
+ Uffizzi::ConfigFile.write_option(:dev_environment, new_dev_environment)
193
+ end
194
+
195
+ def set_startup_state
196
+ new_dev_environment = dev_environment.merge(state: STARTUP_STATE)
197
+ Uffizzi::ConfigFile.write_option(:dev_environment, new_dev_environment)
198
+ end
199
+
200
+ def set_cluster_deployed_state
201
+ new_dev_environment = dev_environment.merge(state: CLUSTER_DEPLOYED_STATE)
202
+ Uffizzi::ConfigFile.write_option(:dev_environment, new_dev_environment)
203
+ end
204
+
205
+ def startup?
206
+ dev_environment[:state] == STARTUP_STATE
207
+ end
208
+
209
+ def clear_dev_environment_config
210
+ Uffizzi::ConfigFile.write_option(:dev_environment, {})
211
+ end
212
+
213
+ def dev_environment
214
+ Uffizzi::ConfigHelper.dev_environment
215
+ end
216
+
217
+ def find_skaffold_pid(pid)
218
+ pid_regex = /\w*#{pid}.*skaffold dev/
219
+ io = Uffizzi.ui.popen('ps -ef')
220
+ processes = io.readlines.select { |l| l.match?(pid_regex) }
221
+
222
+ if processes.count.zero?
223
+ raise StandardError.new('Can\'t find skaffold process pid')
224
+ end
225
+
226
+ # HACK: For MacOS
227
+ if processes.count == 1
228
+ current_pid = processes[0].gsub(/\s+/, ' ').lstrip.split[1]
229
+ return pid if current_pid.to_s == pid.to_s
230
+
231
+ raise StandardError.new('Can\'t find skaffold process pid')
232
+ end
233
+
234
+ # HACK: For Linux
235
+ parent_process = processes
236
+ .map { |ps| ps.gsub(/\s+/, ' ').lstrip.split }
237
+ .detect { |ps| ps[2].to_s == pid.to_s }
238
+
239
+ parent_process[1]
240
+ end
107
241
  end
108
242
  end
data/lib/uffizzi/shell.rb CHANGED
@@ -11,6 +11,7 @@ module Uffizzi
11
11
 
12
12
  PRETTY_JSON = 'pretty-json'
13
13
  REGULAR_JSON = 'json'
14
+ PRETTY_LIST = 'pretty-list'
14
15
 
15
16
  def initialize
16
17
  @shell = Thor::Shell::Basic.new
@@ -55,6 +56,10 @@ module Uffizzi
55
56
  $stdout.stat.pipe?
56
57
  end
57
58
 
59
+ def popen(command)
60
+ IO.popen(command)
61
+ end
62
+
58
63
  def popen2e(command, &block)
59
64
  Open3.popen2e(command, &block)
60
65
  end
@@ -73,12 +78,25 @@ module Uffizzi
73
78
  JSON.pretty_generate(data)
74
79
  end
75
80
 
81
+ def format_to_pretty_list(data)
82
+ case data
83
+ when Array
84
+ data.map { |v| format_to_pretty_list(v) }.join("\n\n")
85
+ when Hash
86
+ data.map { |k, v| "- #{k.to_s.upcase}: #{v}" }.join("\n").strip
87
+ else
88
+ data
89
+ end
90
+ end
91
+
76
92
  def format_message(message)
77
93
  case output_format
78
94
  when PRETTY_JSON
79
95
  format_to_pretty_json(message)
80
96
  when REGULAR_JSON
81
97
  format_to_json(message)
98
+ when PRETTY_LIST
99
+ format_to_pretty_list(message)
82
100
  else
83
101
  message
84
102
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uffizzi
4
- VERSION = '2.2.0'
4
+ VERSION = '2.2.2'
5
5
  end
data/lib/uffizzi.rb CHANGED
@@ -38,7 +38,19 @@ module Uffizzi
38
38
  end
39
39
 
40
40
  def process
41
- @process ||= Process
41
+ Process
42
+ end
43
+
44
+ def signal
45
+ Signal
46
+ end
47
+
48
+ def thread
49
+ Thread
50
+ end
51
+
52
+ def at_exit(&block)
53
+ Kernel.at_exit(&block)
42
54
  end
43
55
  end
44
56
  end
data/man/uffizzi CHANGED
@@ -33,6 +33,9 @@ GROUP is one of the following:
33
33
  project
34
34
  Manage Uffizzi project resources including compose files for
35
35
  specifying compose environment (preview) configurations and secrets
36
+
37
+ dev
38
+ Creates a Uffizzi cluster preconfigured for development workflows
36
39
  .fi
37
40
  .SH "COMMAND"
38
41
  .nf
@@ -0,0 +1,36 @@
1
+ .\" generated with Ronn-NG/v0.9.1
2
+ .\" http://github.com/apjanke/ronn-ng/tree/0.9.1
3
+ .TH "UFFIZZI\-DEV\-DESCRIBE" "" "October 2023" ""
4
+ .SH "NAME"
5
+ \fBuffizzi\-dev\-describe\fR
6
+ .P
7
+ $ uffizzi dev describe \-h uffizzi\-dev\-describe \- show metadata for a dev environment ================================================================
8
+ .SH "SYNOPSIS"
9
+ .nf
10
+ uffizzi dev describe <NAME>
11
+ .fi
12
+ .SH "DESCRIPTION"
13
+ .nf
14
+ Shows metadata for a dev environment
15
+
16
+ This command can fail for the following reasons:
17
+ \- The dev environment specified does not exist\.
18
+ \- The dev environment specified belongs to a different project\.
19
+
20
+ For more information on Uffizzi clusters, see:
21
+ https://docs\.uffizzi\.com/references/cli/
22
+ .fi
23
+ .SH "POSITIONAL ARGUMENTS"
24
+ .nf
25
+ [NAME]
26
+ NAME for the dev environment you want to describe\.
27
+ This is an optional argument\.
28
+ .fi
29
+ .SH "EXAMPLES"
30
+ .nf
31
+ The following command prints metadata for the dev
32
+ environment:
33
+
34
+ $ uffizzi dev describe
35
+ .fi
36
+
@@ -0,0 +1,27 @@
1
+ $ uffizzi dev describe -h
2
+ uffizzi-dev-describe - show metadata for a dev environment
3
+ ================================================================
4
+
5
+ ## SYNOPSIS
6
+ uffizzi dev describe <NAME>
7
+
8
+ ## DESCRIPTION
9
+ Shows metadata for a dev environment
10
+
11
+ This command can fail for the following reasons:
12
+ - The dev environment specified does not exist.
13
+ - The dev environment specified belongs to a different project.
14
+
15
+ For more information on Uffizzi clusters, see:
16
+ https://docs.uffizzi.com/references/cli/
17
+
18
+ ## POSITIONAL ARGUMENTS
19
+ [NAME]
20
+ NAME for the dev environment you want to describe.
21
+ This is an optional argument.
22
+
23
+ ## EXAMPLES
24
+ The following command prints metadata for the dev
25
+ environment:
26
+
27
+ $ uffizzi dev describe
data/man/uffizzi.ronn CHANGED
@@ -29,6 +29,9 @@ uffizzi - manage Uffizzi resources
29
29
  Manage Uffizzi project resources including compose files for
30
30
  specifying compose environment (preview) configurations and secrets
31
31
 
32
+ dev
33
+ Creates a Uffizzi cluster preconfigured for development workflows
34
+
32
35
  ## COMMAND
33
36
  COMMAND is one of the following:
34
37
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uffizzi-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Thurman
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-10-19 00:00:00.000000000 Z
12
+ date: 2023-10-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -522,6 +522,8 @@ files:
522
522
  - man/uffizzi-connect-ghcr.ronn
523
523
  - man/uffizzi-connect.ronn
524
524
  - man/uffizzi-dev
525
+ - man/uffizzi-dev-describe
526
+ - man/uffizzi-dev-describe.ronn
525
527
  - man/uffizzi-dev-start
526
528
  - man/uffizzi-dev-start.ronn
527
529
  - man/uffizzi-dev-stop