uffizzi-cli 1.0.4 → 2.0.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/uffizzi.rb +1 -0
- data/lib/uffizzi/auth_helper.rb +13 -5
- data/lib/uffizzi/cli/account.rb +122 -0
- data/lib/uffizzi/cli/cluster.rb +363 -0
- data/lib/uffizzi/cli/common.rb +2 -4
- data/lib/uffizzi/cli/config.rb +5 -4
- data/lib/uffizzi/cli/connect.rb +14 -8
- data/lib/uffizzi/cli/disconnect.rb +1 -1
- data/lib/uffizzi/cli/login.rb +130 -66
- data/lib/uffizzi/cli/login_by_identity_token.rb +11 -7
- data/lib/uffizzi/cli/preview/service.rb +3 -4
- data/lib/uffizzi/cli/preview.rb +33 -32
- data/lib/uffizzi/cli/project.rb +17 -9
- data/lib/uffizzi/cli.rb +46 -7
- data/lib/uffizzi/clients/api/api_client.rb +92 -2
- data/lib/uffizzi/clients/api/api_routes.rb +44 -0
- data/lib/uffizzi/clients/api/http_client.rb +19 -7
- data/lib/uffizzi/config_file.rb +15 -40
- data/lib/uffizzi/error.rb +15 -0
- data/lib/uffizzi/helpers/config_helper.rb +49 -0
- data/lib/uffizzi/helpers/file_helper.rb +25 -0
- data/lib/uffizzi/helpers/login_helper.rb +33 -0
- data/lib/uffizzi/helpers/project_helper.rb +19 -0
- data/lib/uffizzi/promt.rb +4 -0
- data/lib/uffizzi/response_helper.rb +33 -14
- data/lib/uffizzi/services/cluster_service.rb +59 -0
- data/lib/uffizzi/services/compose_file_service.rb +5 -4
- data/lib/uffizzi/services/env_variables_service.rb +4 -2
- data/lib/uffizzi/services/github_service.rb +21 -0
- data/lib/uffizzi/services/kubeconfig_service.rb +132 -0
- data/lib/uffizzi/services/preview_service.rb +52 -18
- data/lib/uffizzi/shell.rb +19 -16
- data/lib/uffizzi/token.rb +37 -0
- data/lib/uffizzi/version.rb +1 -1
- data/lib/uffizzi.rb +10 -0
- data/man/uffizzi +7 -5
- data/man/uffizzi-account +29 -0
- data/man/uffizzi-account-list +26 -0
- data/man/uffizzi-account-list.html +103 -0
- data/man/uffizzi-account-list.ronn +19 -0
- data/man/uffizzi-account-set-default +29 -0
- data/man/uffizzi-account-set-default.html +105 -0
- data/man/uffizzi-account-set-default.ronn +22 -0
- data/man/uffizzi-account.html +106 -0
- data/man/uffizzi-account.ronn +23 -0
- data/man/uffizzi-cluster +37 -0
- data/man/uffizzi-cluster-create +49 -0
- data/man/uffizzi-cluster-create.ronn +41 -0
- data/man/uffizzi-cluster-delete +32 -0
- data/man/uffizzi-cluster-delete.ronn +24 -0
- data/man/uffizzi-cluster-describe +39 -0
- data/man/uffizzi-cluster-describe.ronn +30 -0
- data/man/uffizzi-cluster-list +34 -0
- data/man/uffizzi-cluster-list.ronn +26 -0
- data/man/uffizzi-cluster-update-kubeconfig +37 -0
- data/man/uffizzi-cluster-update-kubeconfig.ronn +29 -0
- data/man/uffizzi-cluster.ronn +32 -0
- data/man/uffizzi-compose +39 -0
- data/man/uffizzi-compose-create +67 -0
- data/man/uffizzi-compose-create.ronn +57 -0
- data/man/uffizzi-compose-delete +38 -0
- data/man/uffizzi-compose-delete.ronn +29 -0
- data/man/uffizzi-compose-describe +38 -0
- data/man/uffizzi-compose-describe.ronn +29 -0
- data/man/uffizzi-compose-events +38 -0
- data/man/uffizzi-compose-events.ronn +29 -0
- data/man/uffizzi-compose-list +58 -0
- data/man/uffizzi-compose-list.ronn +49 -0
- data/man/uffizzi-compose-service-list +42 -0
- data/man/uffizzi-compose-service-list.ronn +32 -0
- data/man/uffizzi-compose-service-logs +59 -0
- data/man/uffizzi-compose-service-logs.ronn +48 -0
- data/man/uffizzi-compose-update +61 -0
- data/man/uffizzi-compose-update.ronn +51 -0
- data/man/uffizzi-compose.ronn +33 -0
- data/man/uffizzi-compose_service_logs +59 -0
- data/man/uffizzi-compose_service_logs.ronn +50 -0
- data/man/uffizzi-config +2 -2
- data/man/uffizzi-config.ronn +1 -1
- data/man/uffizzi-connect +2 -4
- data/man/uffizzi-connect-acr +2 -2
- data/man/uffizzi-connect-acr.ronn +1 -1
- data/man/uffizzi-connect-docker-hub +2 -2
- data/man/uffizzi-connect-docker-hub.ronn +1 -1
- data/man/uffizzi-connect-docker-registry +2 -2
- data/man/uffizzi-connect-docker-registry.ronn +1 -1
- data/man/uffizzi-connect-ecr +2 -2
- data/man/uffizzi-connect-ecr.ronn +1 -1
- data/man/uffizzi-connect-gcr +2 -2
- data/man/uffizzi-connect-gcr.ronn +1 -1
- data/man/uffizzi-connect-ghcr +2 -2
- data/man/uffizzi-connect-ghcr.ronn +1 -1
- data/man/uffizzi-connect.ronn +2 -2
- data/man/uffizzi-disconnect +2 -2
- data/man/uffizzi-disconnect.ronn +1 -1
- data/man/uffizzi-login +7 -3
- data/man/uffizzi-login-by-identity-token +3 -3
- data/man/uffizzi-login-by-identity-token.ronn +2 -2
- data/man/uffizzi-login.html +113 -0
- data/man/uffizzi-login.ronn +6 -2
- data/man/uffizzi-logout +2 -2
- data/man/uffizzi-logout.ronn +1 -1
- data/man/uffizzi-preview +9 -9
- data/man/uffizzi-preview-create +22 -20
- data/man/uffizzi-preview-create.ronn +28 -26
- data/man/uffizzi-preview-delete +11 -10
- data/man/uffizzi-preview-delete.ronn +12 -11
- data/man/uffizzi-preview-describe +10 -10
- data/man/uffizzi-preview-describe.ronn +11 -11
- data/man/uffizzi-preview-events +12 -11
- data/man/uffizzi-preview-events.ronn +13 -12
- data/man/uffizzi-preview-list +19 -18
- data/man/uffizzi-preview-list.ronn +21 -20
- data/man/uffizzi-preview-service-list +16 -12
- data/man/uffizzi-preview-service-list.ronn +16 -13
- data/man/uffizzi-preview-service-logs +14 -12
- data/man/uffizzi-preview-service-logs.ronn +18 -17
- data/man/uffizzi-preview-update +19 -18
- data/man/uffizzi-preview-update.ronn +21 -20
- data/man/uffizzi-preview.ronn +10 -10
- data/man/uffizzi-preview_service_logs +14 -12
- data/man/uffizzi-preview_service_logs.ronn +18 -17
- data/man/uffizzi-project +3 -3
- data/man/uffizzi-project-compose +2 -2
- data/man/uffizzi-project-compose-describe +2 -2
- data/man/uffizzi-project-compose-describe.ronn +1 -1
- data/man/uffizzi-project-compose-set +2 -2
- data/man/uffizzi-project-compose-set.ronn +1 -1
- data/man/uffizzi-project-compose-unset +2 -2
- data/man/uffizzi-project-compose-unset.ronn +1 -1
- data/man/uffizzi-project-compose.ronn +1 -1
- data/man/uffizzi-project-create +2 -2
- data/man/uffizzi-project-create.ronn +1 -1
- data/man/uffizzi-project-delete +2 -2
- data/man/uffizzi-project-delete.ronn +1 -1
- data/man/uffizzi-project-describe +3 -3
- data/man/uffizzi-project-describe.ronn +1 -1
- data/man/uffizzi-project-preview-describe +37 -0
- data/man/uffizzi-project-preview-describe.ronn +29 -0
- data/man/uffizzi-project-preview-set +66 -0
- data/man/uffizzi-project-preview-set.ronn +57 -0
- data/man/uffizzi-project-secret +2 -2
- data/man/uffizzi-project-secret-create +2 -2
- data/man/uffizzi-project-secret-create.ronn +1 -1
- data/man/uffizzi-project-secret-delete +2 -2
- data/man/uffizzi-project-secret-delete.ronn +1 -1
- data/man/uffizzi-project-secret-list +2 -2
- data/man/uffizzi-project-secret-list.ronn +1 -1
- data/man/uffizzi-project-secret.ronn +1 -1
- data/man/uffizzi-project-set-default +2 -2
- data/man/uffizzi-project-set-default.ronn +1 -1
- data/man/uffizzi-project.html +124 -0
- data/man/uffizzi-project.ronn +2 -2
- data/man/uffizzi.ronn +12 -10
- metadata +131 -19
@@ -27,28 +27,47 @@ module Uffizzi
|
|
27
27
|
response[:code] == Net::HTTPOK
|
28
28
|
end
|
29
29
|
|
30
|
-
def handle_failed_response(response)
|
31
|
-
|
32
|
-
|
30
|
+
def handle_failed_response(response, addtional_errors = [])
|
31
|
+
errors = get_response_errors(response)
|
32
|
+
common_msg = errors_to_string(errors)
|
33
|
+
addtional_msg = errors_to_string(addtional_errors)
|
34
|
+
message = [common_msg, addtional_msg]
|
35
|
+
.reject { |m| m == '' }
|
36
|
+
.join("\n")
|
37
|
+
.concat("\n")
|
38
|
+
|
39
|
+
raise Uffizzi::ServerResponseError.new(message)
|
33
40
|
end
|
34
41
|
|
35
42
|
def handle_invalid_compose_response(response)
|
36
|
-
|
37
|
-
raise Uffizzi::
|
43
|
+
message = errors_to_string(response[:body][:compose_file][:payload][:errors])
|
44
|
+
raise Uffizzi::ServerResponseError.new(message)
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_response_errors(response)
|
48
|
+
return response if response.is_a?(Array)
|
49
|
+
return [response.to_s] unless response.is_a?(Hash)
|
50
|
+
|
51
|
+
body = response.fetch(:body, response.to_s)
|
52
|
+
return [body.to_s] unless body.is_a?(Hash)
|
53
|
+
|
54
|
+
errors = body.fetch(:errors, body.to_s)
|
55
|
+
return [errors.to_s] unless [Hash, Array].include?(errors.class)
|
56
|
+
|
57
|
+
errors
|
38
58
|
end
|
39
59
|
|
40
60
|
private
|
41
61
|
|
42
|
-
def
|
43
|
-
errors.
|
44
|
-
|
45
|
-
error_messages.each { |error_message| acc = "#{acc}#{prepare_error_message(error_message)}\n" }
|
46
|
-
else
|
47
|
-
acc = "#{acc}#{prepare_error_message(error_message)}\n"
|
48
|
-
end
|
62
|
+
def errors_to_string(errors)
|
63
|
+
return errors.join("\n") if errors.is_a?(Array)
|
64
|
+
return errors.to_s unless errors.is_a?(Hash)
|
49
65
|
|
50
|
-
|
51
|
-
|
66
|
+
errors
|
67
|
+
.values
|
68
|
+
.flatten
|
69
|
+
.map { |msg| prepare_error_message(msg.to_s) }
|
70
|
+
.join("\n")
|
52
71
|
end
|
53
72
|
|
54
73
|
def prepare_error_message(error_message)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uffizzi/clients/api/api_client'
|
4
|
+
|
5
|
+
class ClusterService
|
6
|
+
CLUSTER_STATE_DEPLOYING_NAMESPACE = 'deploying_namespace'
|
7
|
+
CLUSTER_STATE_DEPLOYING = 'deploying'
|
8
|
+
CLUSTER_STATE_DEPLOYED = 'deployed'
|
9
|
+
CLUSTER_STATE_FAILED_DEPLOY_NAMESPACE = 'failed_deploy_namespace'
|
10
|
+
CLUSTER_STATE_FAILED = 'failed'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
include ApiClient
|
14
|
+
|
15
|
+
def deployed?(cluster_state)
|
16
|
+
cluster_state == CLUSTER_STATE_DEPLOYED
|
17
|
+
end
|
18
|
+
|
19
|
+
def deploying?(cluster_state)
|
20
|
+
[CLUSTER_STATE_DEPLOYING_NAMESPACE, CLUSTER_STATE_DEPLOYING].include?(cluster_state)
|
21
|
+
end
|
22
|
+
|
23
|
+
def failed?(cluster_state)
|
24
|
+
[CLUSTER_STATE_FAILED_DEPLOY_NAMESPACE, CLUSTER_STATE_FAILED].include?(cluster_state)
|
25
|
+
end
|
26
|
+
|
27
|
+
def wait_cluster_deploy(project_slug, cluster_name, oidc_token)
|
28
|
+
loop do
|
29
|
+
params = {
|
30
|
+
cluster_name: cluster_name,
|
31
|
+
oidc_token: oidc_token,
|
32
|
+
}
|
33
|
+
response = get_cluster(Uffizzi::ConfigFile.read_option(:server), project_slug, params)
|
34
|
+
return Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response)
|
35
|
+
|
36
|
+
cluster_data = response.dig(:body, :cluster)
|
37
|
+
|
38
|
+
return cluster_data unless deploying?(cluster_data[:state])
|
39
|
+
|
40
|
+
sleep(5)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_name
|
45
|
+
name = [Faker::Name.first_name, Faker::Name.last_name].map(&:downcase).join('-')
|
46
|
+
|
47
|
+
return name if valid_name?(name)
|
48
|
+
|
49
|
+
generate_name
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_name?(name)
|
53
|
+
return false if name.nil?
|
54
|
+
|
55
|
+
regex = /\A[a-zA-Z0-9-]*\z/
|
56
|
+
regex.match?(name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -84,7 +84,7 @@ class ComposeFileService
|
|
84
84
|
gzipped_file_size = Pathname.new(tmp_tar_path).size
|
85
85
|
|
86
86
|
if gzipped_file_size > MAX_HOST_VOLUME_GZIP_FILE_SIZE
|
87
|
-
Uffizzi.
|
87
|
+
raise Uffizzi::Error.new("File or directory is too large:: #{path}. Gzipped tar archive size is #{gzipped_file_size}")
|
88
88
|
end
|
89
89
|
|
90
90
|
Base64.encode64(File.binread(tmp_tar_path))
|
@@ -139,11 +139,12 @@ class ComposeFileService
|
|
139
139
|
def parse_compose_content_to_object(compose_content)
|
140
140
|
begin
|
141
141
|
compose_data = Psych.safe_load(compose_content, aliases: true)
|
142
|
-
rescue Psych::SyntaxError
|
143
|
-
|
142
|
+
rescue Psych::SyntaxError => e
|
143
|
+
err = [e.problem, e.context].compact.join(' ')
|
144
|
+
raise Uffizzi::Error.new("Syntax error: #{err} at line #{e.line} column #{e.column}")
|
144
145
|
end
|
145
146
|
|
146
|
-
Uffizzi.
|
147
|
+
raise Uffizzi::Error.new('Unsupported compose file') if compose_data.nil?
|
147
148
|
|
148
149
|
compose_data
|
149
150
|
end
|
@@ -7,7 +7,9 @@ require 'base64'
|
|
7
7
|
class EnvVariablesService
|
8
8
|
class << self
|
9
9
|
def substitute_env_variables(compose_file_data)
|
10
|
-
compose_file_data.gsub(
|
10
|
+
compose_file_data.gsub(/\${1,2}\{?([?:\-_A-Za-z0-9]+)\}?/) do |variable|
|
11
|
+
next variable if variable.start_with?('$$')
|
12
|
+
|
11
13
|
variable_content = variable.match(/[?:\-_A-Za-z0-9]+/).to_s
|
12
14
|
fetch_variable_value(variable_content)
|
13
15
|
end
|
@@ -26,7 +28,7 @@ class EnvVariablesService
|
|
26
28
|
else
|
27
29
|
"Environment variable #{variable_name} doesn't exist"
|
28
30
|
end
|
29
|
-
raise
|
31
|
+
raise Uffizzi::Error.new(error_message)
|
30
32
|
end
|
31
33
|
|
32
34
|
def variable_has_default_value?(variable_content)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GithubService
|
4
|
+
class << self
|
5
|
+
GITHUB_OUTPUT = 'GITHUB_OUTPUT'
|
6
|
+
|
7
|
+
def write_to_github_env(data)
|
8
|
+
return unless data.is_a?(Hash)
|
9
|
+
|
10
|
+
github_output = ENV.fetch(GITHUB_OUTPUT) { raise "#{GITHUB_OUTPUT} is not defined" }
|
11
|
+
|
12
|
+
File.open(github_output, 'a') do |f|
|
13
|
+
data.each { |(key, value)| f.puts("#{key}=#{value}") }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def github_actions_exists?
|
18
|
+
ENV['GITHUB_ACTIONS'].present?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uffizzi/clients/api/api_client'
|
4
|
+
require 'psych'
|
5
|
+
|
6
|
+
class KubeconfigService
|
7
|
+
class InvalidKubeconfigError < StandardError
|
8
|
+
def initialize(file_path)
|
9
|
+
msg = "Invalid kubeconfig at path '#{file_path}'"
|
10
|
+
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
KUBECONFIG_GENERAL_KEYS = ['apiVersion', 'clusters', 'contexts', 'current-context', 'kind', 'users'].freeze
|
16
|
+
|
17
|
+
class << self
|
18
|
+
include ApiClient
|
19
|
+
|
20
|
+
def merge(target_kubeconfig, source_kubeconfig)
|
21
|
+
return source_kubeconfig.deep_dup if target_kubeconfig.nil?
|
22
|
+
|
23
|
+
new_cluster_name = get_current_cluster_name(source_kubeconfig)
|
24
|
+
|
25
|
+
if cluster_exists_in_kubeconfig?(target_kubeconfig, new_cluster_name)
|
26
|
+
replace_by_cluster_name(target_kubeconfig, source_kubeconfig, new_cluster_name)
|
27
|
+
else
|
28
|
+
add(target_kubeconfig, source_kubeconfig)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def exclude(target_kubeconfig, source_kubeconfig)
|
33
|
+
return if target_kubeconfig.nil?
|
34
|
+
|
35
|
+
excludable_cluster_name = get_current_cluster_name(source_kubeconfig)
|
36
|
+
exclude_by_cluster_name(target_kubeconfig, excludable_cluster_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_current_context(kubeconfig)
|
40
|
+
kubeconfig['current-context']
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_first_context(kubeconfig)
|
44
|
+
kubeconfig.fetch('contexts', [])[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_current_cluster_name(kubeconfig)
|
48
|
+
kubeconfig['contexts']
|
49
|
+
.detect { |c| c['name'] == get_current_context(kubeconfig) }
|
50
|
+
.dig('context', 'cluster')
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_current_context(kubeconfig, current_context)
|
54
|
+
new_kubeconfig = kubeconfig.deep_dup
|
55
|
+
new_kubeconfig['current-context'] = current_context
|
56
|
+
|
57
|
+
new_kubeconfig
|
58
|
+
end
|
59
|
+
|
60
|
+
def save_to_filepath(filepath, kubeconfig)
|
61
|
+
real_file_path = File.expand_path(filepath)
|
62
|
+
target_kubeconfig = File.exist?(real_file_path) ? Psych.safe_load(File.read(real_file_path)) : nil
|
63
|
+
|
64
|
+
if target_kubeconfig.present? && !valid_kubeconfig?(target_kubeconfig)
|
65
|
+
raise InvalidKubeconfigError.new(filepath)
|
66
|
+
end
|
67
|
+
|
68
|
+
new_kubeconfig = block_given? ? yield(target_kubeconfig) : kubeconfig
|
69
|
+
return if new_kubeconfig.nil?
|
70
|
+
|
71
|
+
dir_path = File.dirname(real_file_path)
|
72
|
+
FileUtils.mkdir_p(dir_path) unless File.directory?(dir_path)
|
73
|
+
File.write(real_file_path, new_kubeconfig.to_yaml)
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_path
|
77
|
+
kubeconfig_env_path || Uffizzi.configuration.default_kubeconfig_path
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def cluster_exists_in_kubeconfig?(kubeconfig, cluster_name)
|
83
|
+
clusters = kubeconfig['clusters'] || []
|
84
|
+
clusters.detect { |c| c['name'] == cluster_name }.present?
|
85
|
+
end
|
86
|
+
|
87
|
+
def add(target_kubeconfig, source_kubeconfig)
|
88
|
+
new_kubeconfig = target_kubeconfig.deep_dup
|
89
|
+
new_kubeconfig['clusters'] << source_kubeconfig['clusters'][0]
|
90
|
+
new_kubeconfig['contexts'] << source_kubeconfig['contexts'][0]
|
91
|
+
new_kubeconfig['users'] << source_kubeconfig['users'][0]
|
92
|
+
|
93
|
+
new_kubeconfig
|
94
|
+
end
|
95
|
+
|
96
|
+
def replace_by_cluster_name(target_kubeconfig, source_kubeconfig, cluster_name)
|
97
|
+
new_kubeconfig = exclude_by_cluster_name(target_kubeconfig, cluster_name)
|
98
|
+
|
99
|
+
add(new_kubeconfig, source_kubeconfig)
|
100
|
+
end
|
101
|
+
|
102
|
+
def exclude_by_cluster_name(kubeconfig, cluster_name)
|
103
|
+
clusters = kubeconfig['clusters']
|
104
|
+
contexts = kubeconfig['contexts']
|
105
|
+
users = kubeconfig['users']
|
106
|
+
|
107
|
+
return kubeconfig if clusters.empty? || contexts.empty? || users.empty?
|
108
|
+
|
109
|
+
target_user = contexts.detect { |c| c.dig('context', 'cluster') == cluster_name }.dig('context', 'user')
|
110
|
+
new_clusters = clusters.reject { |c| c['name'] == cluster_name }
|
111
|
+
new_contexts = contexts.reject { |c| c.dig('context', 'cluster') == cluster_name }
|
112
|
+
new_users = users.reject { |c| c['name'] == target_user }
|
113
|
+
|
114
|
+
kubeconfig.merge({ 'clusters' => new_clusters, 'contexts' => new_contexts, 'users' => new_users })
|
115
|
+
end
|
116
|
+
|
117
|
+
def kubeconfig_env_path
|
118
|
+
file_paths = ENV['KUBECONFIG']
|
119
|
+
return if file_paths.blank?
|
120
|
+
|
121
|
+
file_paths.split(':').first
|
122
|
+
end
|
123
|
+
|
124
|
+
def valid_kubeconfig?(data)
|
125
|
+
return false unless data.is_a?(Hash)
|
126
|
+
|
127
|
+
data_keys = data.keys.map(&:to_s)
|
128
|
+
|
129
|
+
KUBECONFIG_GENERAL_KEYS.all? { |k| data_keys.include?(k) }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -3,6 +3,9 @@
|
|
3
3
|
require 'uffizzi/clients/api/api_client'
|
4
4
|
|
5
5
|
class PreviewService
|
6
|
+
ACTIVITY_ITEM_STATE_FAILED = 'failed'
|
7
|
+
ACTIVITY_ITEM_STATE_DEPLOYED = 'deployed'
|
8
|
+
|
6
9
|
class << self
|
7
10
|
include ApiClient
|
8
11
|
|
@@ -18,7 +21,8 @@ class PreviewService
|
|
18
21
|
|
19
22
|
def run_containers_deploy(project_slug, deployment)
|
20
23
|
deployment_id = deployment[:id]
|
21
|
-
|
24
|
+
oidc_token = Uffizzi::ConfigFile.read_option(:oidc_token)
|
25
|
+
params = { id: deployment_id, token: oidc_token }
|
22
26
|
|
23
27
|
response = deploy_containers(server_url, project_slug, deployment_id, params)
|
24
28
|
|
@@ -42,9 +46,7 @@ class PreviewService
|
|
42
46
|
|
43
47
|
activity_items = []
|
44
48
|
loop do
|
45
|
-
|
46
|
-
handle_activity_items_response(response, spinner)
|
47
|
-
activity_items = response[:body][:activity_items]
|
49
|
+
activity_items = fetch_activity_items(project_slug, deployment[:id])
|
48
50
|
break if activity_items.count == deployment[:containers].count
|
49
51
|
|
50
52
|
sleep(5)
|
@@ -52,9 +54,13 @@ class PreviewService
|
|
52
54
|
|
53
55
|
spinner.success
|
54
56
|
|
55
|
-
Uffizzi.ui.say('
|
57
|
+
Uffizzi.ui.say('Deployed')
|
58
|
+
Uffizzi.ui.say("Deployment url: https://#{deployment[:preview_url]}")
|
59
|
+
Uffizzi.ui.say("Deployment proxy url: https://#{deployment[:proxy_preview_url]}")
|
56
60
|
|
57
61
|
activity_items
|
62
|
+
rescue ApiClient::ResponseError => e
|
63
|
+
Uffizzi::ResponseHelper.handle_failed_response(e.response)
|
58
64
|
end
|
59
65
|
|
60
66
|
def wait_containers_deploy(deployment, project_slug, activity_items)
|
@@ -66,16 +72,25 @@ class PreviewService
|
|
66
72
|
containers_spinners = create_containers_spinners(activity_items, spinner)
|
67
73
|
|
68
74
|
loop do
|
69
|
-
|
70
|
-
handle_activity_items_response(response, spinner)
|
71
|
-
activity_items = response[:body][:activity_items]
|
75
|
+
activity_items = fetch_activity_items(project_slug, deployment[:id])
|
72
76
|
update_containers_spinners!(activity_items, containers_spinners)
|
73
|
-
break if activity_items.all? { |activity_item| activity_item
|
77
|
+
break if activity_items.all? { |activity_item| activity_item_finished?(activity_item) }
|
74
78
|
|
75
79
|
sleep(5)
|
76
80
|
end
|
77
81
|
|
78
82
|
spinner.success?
|
83
|
+
rescue ApiClient::ResponseError => e
|
84
|
+
spinner.error
|
85
|
+
|
86
|
+
descriptions = fetch_k8s_containers_descriptions(deployment, project_slug)
|
87
|
+
containers_last_state_messages = descriptions.map do |description|
|
88
|
+
container_name = "Last State for container '#{description[:container_name]}':"
|
89
|
+
states = description[:last_state].map { |k, v| " #{k}: #{v}" }.join("\n")
|
90
|
+
[container_name, states].join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
Uffizzi::ResponseHelper.handle_failed_response(e.response, containers_last_state_messages)
|
79
94
|
end
|
80
95
|
|
81
96
|
def create_containers_spinners(activity_items, spinner)
|
@@ -90,27 +105,46 @@ class PreviewService
|
|
90
105
|
end
|
91
106
|
|
92
107
|
def update_containers_spinners!(activity_items, containers_spinners)
|
93
|
-
finished_activity_items = activity_items.filter
|
94
|
-
activity_item[:state] == 'deployed' || activity_item[:state] == 'failed'
|
95
|
-
end
|
108
|
+
finished_activity_items = activity_items.filter { |activity_item| activity_item_finished?(activity_item) }
|
96
109
|
|
97
110
|
finished_activity_items.each do |activity_item|
|
98
111
|
container_spinner = containers_spinners.detect { |spinner| spinner[:name] == activity_item[:name] }
|
99
112
|
spinner = container_spinner[:spinner]
|
100
113
|
case activity_item[:state]
|
101
|
-
when
|
114
|
+
when ACTIVITY_ITEM_STATE_DEPLOYED
|
102
115
|
spinner.success
|
103
|
-
when
|
116
|
+
when ACTIVITY_ITEM_STATE_FAILED
|
104
117
|
spinner.error
|
105
118
|
end
|
106
119
|
end
|
107
120
|
end
|
108
121
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
122
|
+
def fetch_activity_items(project_slug, deployment_id)
|
123
|
+
response = get_activity_items(server_url, project_slug, deployment_id)
|
124
|
+
raise ApiClient::ResponseError.new(response) unless Uffizzi::ResponseHelper.ok?(response)
|
125
|
+
|
126
|
+
response[:body][:activity_items]
|
127
|
+
end
|
128
|
+
|
129
|
+
def fetch_k8s_containers_descriptions(deployment, project_slug)
|
130
|
+
descriptions = []
|
131
|
+
|
132
|
+
deployment[:containers].each do |container|
|
133
|
+
container_name = container[:service_name]
|
134
|
+
response = get_k8s_container_description(server_url, project_slug, deployment[:id], container_name)
|
135
|
+
|
136
|
+
Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response)
|
137
|
+
last_state = response[:body][:last_state]
|
138
|
+
next if last_state.nil?
|
139
|
+
|
140
|
+
descriptions << { container_name: container_name, last_state: last_state }
|
113
141
|
end
|
142
|
+
|
143
|
+
descriptions
|
144
|
+
end
|
145
|
+
|
146
|
+
def activity_item_finished?(activity_item)
|
147
|
+
activity_item[:state] == ACTIVITY_ITEM_STATE_DEPLOYED || activity_item[:state] == ACTIVITY_ITEM_STATE_FAILED
|
114
148
|
end
|
115
149
|
end
|
116
150
|
end
|
data/lib/uffizzi/shell.rb
CHANGED
@@ -5,28 +5,22 @@ require 'awesome_print'
|
|
5
5
|
module Uffizzi
|
6
6
|
module UI
|
7
7
|
class Shell
|
8
|
+
class ExitError < Thor::Error; end
|
8
9
|
attr_accessor :output_format
|
9
10
|
|
10
11
|
PRETTY_JSON = 'pretty-json'
|
11
12
|
REGULAR_JSON = 'json'
|
12
|
-
GITHUB_ACTION = 'github-action'
|
13
13
|
|
14
14
|
def initialize
|
15
15
|
@shell = Thor::Shell::Basic.new
|
16
16
|
end
|
17
17
|
|
18
18
|
def say(message)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
when GITHUB_ACTION
|
25
|
-
format_to_github_action(message)
|
26
|
-
else
|
27
|
-
message
|
28
|
-
end
|
29
|
-
@shell.say(formatted_message)
|
19
|
+
@shell.say(format_message(message))
|
20
|
+
end
|
21
|
+
|
22
|
+
def say_error_and_exit(message)
|
23
|
+
raise ExitError.new(format_message(message))
|
30
24
|
end
|
31
25
|
|
32
26
|
def print_in_columns(messages)
|
@@ -56,6 +50,10 @@ module Uffizzi
|
|
56
50
|
$stdout = IO.new(1, 'w')
|
57
51
|
end
|
58
52
|
|
53
|
+
def stdout_pipe?
|
54
|
+
$stdout.stat.pipe?
|
55
|
+
end
|
56
|
+
|
59
57
|
private
|
60
58
|
|
61
59
|
def format_to_json(data)
|
@@ -66,10 +64,15 @@ module Uffizzi
|
|
66
64
|
JSON.pretty_generate(data)
|
67
65
|
end
|
68
66
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
67
|
+
def format_message(message)
|
68
|
+
case output_format
|
69
|
+
when PRETTY_JSON
|
70
|
+
format_to_pretty_json(message)
|
71
|
+
when REGULAR_JSON
|
72
|
+
format_to_json(message)
|
73
|
+
else
|
74
|
+
message
|
75
|
+
end
|
73
76
|
end
|
74
77
|
end
|
75
78
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'uffizzi/helpers/file_helper'
|
5
|
+
|
6
|
+
module Uffizzi
|
7
|
+
class Token
|
8
|
+
TOKEN_PATH = "#{Dir.home}/.config/uffizzi/token"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def token_path
|
12
|
+
TOKEN_PATH
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete
|
16
|
+
File.truncate(token_path, 0) if exists?
|
17
|
+
end
|
18
|
+
|
19
|
+
def exists?
|
20
|
+
File.exist?(token_path) && !read.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def read
|
24
|
+
content = File.read(token_path)
|
25
|
+
content.presence
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(token)
|
31
|
+
return nil unless token.is_a?(String)
|
32
|
+
|
33
|
+
Uffizzi::FileHelper.write_with_lock(token_path, token)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/uffizzi/version.rb
CHANGED
data/lib/uffizzi.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'io/console'
|
4
4
|
require 'tty-spinner'
|
5
|
+
require 'sentry-ruby'
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
8
|
+
require 'active_support/core_ext/object/blank'
|
5
9
|
|
6
10
|
require 'thor'
|
7
11
|
require 'uffizzi/error'
|
@@ -11,8 +15,14 @@ require 'uffizzi/version'
|
|
11
15
|
require 'uffizzi/clients/api/api_client'
|
12
16
|
require 'uffizzi/clients/api/api_routes'
|
13
17
|
require 'uffizzi/config_file'
|
18
|
+
require 'uffizzi/token'
|
14
19
|
require_relative '../config/uffizzi'
|
15
20
|
|
21
|
+
Sentry.init do |config|
|
22
|
+
config.dsn = Base64.decode64(ENV['LOGGER_KEY'].to_s)
|
23
|
+
config.logger = Sentry::Logger.new(nil)
|
24
|
+
end
|
25
|
+
|
16
26
|
module Uffizzi
|
17
27
|
class << self
|
18
28
|
def ui
|
data/man/uffizzi
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
.\" generated with Ronn-NG/v0.9.1
|
2
2
|
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
3
|
-
.TH "UFFIZZI" "" "
|
3
|
+
.TH "UFFIZZI" "" "September 2023" ""
|
4
4
|
.SH "NAME"
|
5
5
|
\fBuffizzi\fR \- manage Uffizzi resources
|
6
6
|
.SH "SYNOPSIS"
|
@@ -13,11 +13,13 @@ The uffizzi CLI manages authentication, configuration, and
|
|
13
13
|
interaction with Uffizzi APIs\.
|
14
14
|
|
15
15
|
For more information on the uffizzi CLI, see:
|
16
|
-
https://
|
16
|
+
https://docs\.uffizzi\.com/references/cli/
|
17
17
|
.fi
|
18
18
|
.SH "GROUP"
|
19
19
|
.nf
|
20
20
|
GROUP is one of the following:
|
21
|
+
cluster
|
22
|
+
Manage virtual clusters
|
21
23
|
|
22
24
|
config
|
23
25
|
Configure the uffizzi CLI
|
@@ -25,12 +27,12 @@ GROUP is one of the following:
|
|
25
27
|
connect
|
26
28
|
Grant a Uffizzi user account access to external services
|
27
29
|
|
28
|
-
|
29
|
-
Manage Uffizzi previews and view logs
|
30
|
+
compose
|
31
|
+
Manage Uffizzi compose environments (previews) and view logs
|
30
32
|
|
31
33
|
project
|
32
34
|
Manage Uffizzi project resources including compose files for
|
33
|
-
specifying preview configurations and secrets
|
35
|
+
specifying compose environment (preview) configurations and secrets
|
34
36
|
.fi
|
35
37
|
.SH "COMMAND"
|
36
38
|
.nf
|
data/man/uffizzi-account
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
.\" generated with Ronn-NG/v0.9.1
|
2
|
+
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
3
|
+
.TH "UFFIZZI\-ACCOUNT" "" "August 2023" ""
|
4
|
+
.SH "NAME"
|
5
|
+
\fBuffizzi\-account\fR \- manage Uffizzi accounts
|
6
|
+
.SH "SYNOPSIS"
|
7
|
+
.nf
|
8
|
+
uffizzi account COMMAND [UFFIZZI_WIDE_FLAG \|\.\|\.\|\.]
|
9
|
+
.fi
|
10
|
+
.SH "DESCRIPTION"
|
11
|
+
.nf
|
12
|
+
Manage Uffizzi accounts
|
13
|
+
|
14
|
+
For more information on Uffizzi accounts, see:
|
15
|
+
https://docs\.uffizzi\.com/references/cli/
|
16
|
+
.fi
|
17
|
+
.SH "COMMANDS"
|
18
|
+
.nf
|
19
|
+
COMMAND is one of the following:
|
20
|
+
|
21
|
+
list
|
22
|
+
List all accounts associated with the user\'s credentials
|
23
|
+
|
24
|
+
set\-default
|
25
|
+
Set the default account in the uffizzi configuration\. Also see
|
26
|
+
$ uffizzi config help for details\.
|
27
|
+
.fi
|
28
|
+
.P
|
29
|
+
Run $ uffizzi account COMMAND \-\-help for more information on a command\.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
.\" generated with Ronn-NG/v0.9.1
|
2
|
+
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
3
|
+
.TH "UFFIZZI\-ACCOUNT\-LIST" "" "August 2023" ""
|
4
|
+
.SH "NAME"
|
5
|
+
\fBuffizzi\-account\-list\fR \- list the accounts of the user
|
6
|
+
.SH "SYNOPSIS"
|
7
|
+
.nf
|
8
|
+
uffizzi account list
|
9
|
+
.fi
|
10
|
+
.SH "DESCRIPTION"
|
11
|
+
.nf
|
12
|
+
This command lists all accounts (not projects) for the associated user
|
13
|
+
|
14
|
+
This command can fail for the following reasons:
|
15
|
+
\- The user is not authenticated
|
16
|
+
|
17
|
+
For more information on preview services, see:
|
18
|
+
https://docs\.uffizzi\.com/references/cli/
|
19
|
+
.fi
|
20
|
+
.SH "EXAMPLES"
|
21
|
+
.nf
|
22
|
+
The following command lists the user\'s accounts:
|
23
|
+
|
24
|
+
$ uffizzi account list
|
25
|
+
.fi
|
26
|
+
|