uffizzi-cli 1.0.4 → 2.0.29
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 +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
|
+
|