Skip to content

Commit

Permalink
[1110_uffizzi_platform] WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
zipofar authored and moklidia committed Aug 30, 2023
1 parent e096b1c commit a932923
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 4 deletions.
4 changes: 4 additions & 0 deletions lib/uffizzi/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def disconnect(credential_type)
Disconnect.new.run(credential_type)
end

desc 'install', 'install'
require_relative 'cli/install'
subcommand 'install', Cli::Install

map preview: :compose

class << self
Expand Down
283 changes: 283 additions & 0 deletions lib/uffizzi/cli/install.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
# frozen_string_literal: true

require 'uffizzi'
require 'uffizzi/config_file'

module Uffizzi
class Cli::Install < Thor
HELM_REPO_NAME = 'uffizzi'
HELM_DEPLOYED_STATUS = 'deployed'
CHART_NAME = 'uffizzi-app'
VALUES_FILE_NAME = 'helm_values.yaml'

desc 'by-wizard [NAMESPACE]', 'Install uffizzi to cluster'
def by_wizard(namespace)
run_installation do
ask_installation_params(namespace)
end
end

desc 'by-options [NAMESPACE]', 'Install uffizzi to cluster'
method_option :domain, required: true, type: :string, aliases: '-d'
method_option :'user-email', required: false, type: :string, aliases: '-e'
method_option :'acme-email', required: false, type: :string
method_option :'user-password', required: false, type: :string
method_option :'controller-password', required: false, type: :string
method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl'], default: 'letsencrypt'
method_option :'wildcard-cert-path', required: false, type: :string
method_option :'wildcard-key-path', required: false, type: :string
method_option :'without-wildcard-tls', required: false, type: :boolean
def by_options(namespace)
run_installation do
validate_installation_options(namespace, options)
end
end

desc 'add-wildcard-tls [NAMESPACE]', 'Add wildcard tls from files'
method_option :cert, required: true, type: :string, aliases: '-c'
method_option :key, required: true, type: :string, aliases: '-k'
method_option :domain, required: true, type: :string, aliases: '-d'
def add_wildcard_tls(namespace)
kubectl_exists?

params = {
namespace: namespace,
domain: options[:domain],
wildcard_cert_path: options[:cert],
wildcard_key_path: options[:key],
}

kubectl_add_wildcard_tls(params)
end

private

def run_installation
kubectl_exists?
helm_exists?
params = yield
helm_values = build_helm_values(params)
create_helm_values_file(helm_values)
helm_set_repo
helm_set_release(params.fetch(:namespace))
kubectl_add_wildcard_tls(params) if params[:wildcard_cert_path] && params[:wildcard_key_path]
end

def kubectl_exists?
cmd = 'kubectl version -o json'
execute_command(cmd, say: false).present?
end

def helm_exists?
cmd = 'helm version --short'
execute_command(cmd, say: false).present?
end

def helm_set_repo
repo = helm_repo_search
return if repo.present?

helm_repo_add
end

def helm_set_release(namespace)
releases = helm_release_list(namespace)
release = releases.detect { |r| r['name'] == namespace }
if release.present?
Uffizzi.ui.say_error_and_exit("The release #{release['name']} already exists with status #{release['status']}")
end

helm_install(namespace)
end

def helm_repo_add
cmd = "helm repo add #{HELM_REPO_NAME} https://uffizzicloud.github.io/uffizzi"
execute_command(cmd)
end

def helm_repo_search
cmd = "helm search repo #{HELM_REPO_NAME}/#{CHART_NAME} -o json"

execute_command(cmd) do |result, err|
err.present? ? nil : JSON.parse(result)
end
end

def helm_release_list(namespace)
cmd = "helm list -n #{namespace} -o json"
result = execute_command(cmd, say: false)

JSON.parse(result)
end

def helm_install(namespace)
release_name = namespace
cmd = "helm install #{release_name} #{HELM_REPO_NAME}/#{CHART_NAME}" \
" --values #{helm_values_file_path}" \
" --namespace #{namespace}" \
' --create-namespace' \
' --output json'

res = execute_command(cmd, say: false)
info = JSON.parse(res)['info']

return Uffizzi.ui.say('Helm release is deployed') if info['status'] == HELM_DEPLOYED_STATUS

Uffizzi.ui.say_error_and_exit(info)
end

def kubectl_add_wildcard_tls(params)
cmd = "kubectl create secret tls wildcard.#{params.fetch(:domain)}" \
" --cert=#{params.fetch(:wildcard_cert_path)}" \
" --key=#{params.fetch(:wildcard_key_path)}" \
" --namespace #{params.fetch(:namespace)}"

execute_command(cmd)
end

def ask_wildcard_cert
has_user_wildcard_cert = Uffizzi.prompt.yes?('Uffizzi use a wildcard tls certificate. Do you have it?')

if has_user_wildcard_cert
cert_path = Uffizzi.prompt.ask('Path to cert: ', required: true)
key_path = Uffizzi.prompt.ask('Path to key: ', required: true)

return { wildcard_cert_path: cert_path, wildcard_key_path: key_path }
end

add_later = Uffizzi.prompt.yes?('Do you want to add wildcard certificate later?')

if add_later
Uffizzi.ui.say('You can set command "uffizzi install add-wildcard-cert [NAMESPACE]'\
' -d your.domain.com -c /path/to/cert -k /path/to/key"')

{ wildcard_cert_path: nil, wildcard_key_path: nil }
else
Uffizzi.ui.say('Sorry, but uffizzi can not work correctly without wildcard certificate')
exit(0)
end
end

def ask_installation_params(namespace)
wildcard_cert_paths = ask_wildcard_cert
domain = Uffizzi.prompt.ask('Domain: ', required: true, default: 'example.com')
user_email = Uffizzi.prompt.ask('User email: ', required: true, default: "admin@#{domain}")
user_password = Uffizzi.prompt.ask('User password: ', required: true, default: generate_password)
controller_password = Uffizzi.prompt.ask('Controller password: ', required: true, default: generate_password)
cert_email = Uffizzi.prompt.ask('Email address for ACME registration: ', required: true, default: user_email)
cluster_issuers = [
{ name: 'Letsencrypt', value: 'letsencrypt' },
{ name: 'ZeroSSL', value: 'zerossl' },
]
cluster_issuer = Uffizzi.prompt.select('Cluster issuer', cluster_issuers)

{
namespace: namespace,
domain: domain,
user_email: user_email,
user_password: user_password,
controller_password: controller_password,
cert_email: cert_email,
cluster_issuer: cluster_issuer,
}.merge(wildcard_cert_paths)
end

def validate_installation_options(namespace, options)
base_params = {
namespace: namespace,
domain: options[:domain],
user_email: options[:'user-email'] || "admin@#{options[:domain]}",
user_password: options[:'user-password'] || generate_password,
controller_password: options[:'controller-password'] || generate_password,
cert_email: options[:'acme-email'] || options[:'user-email'],
cluster_issuer: options[:issuer],
wildcard_cert_path: nil,
wildcard_key_path: nil,
}

return base_params if options[:'without-wildcard-tls']

empty_key = [:'wildcard-cert-path', :'wildcard-key-path'].detect { |k| options[k].nil? }

if empty_key.present?
return Uffizzi.ui.say_error_and_exit("#{empty_key} is required or use the flag without-wildcard-tls")
end

wildcard_params = {
wildcard_cert_path: options[:'wildcard-cert-path'],
wildcard_key_path: options[:'wildcard-key-path'],
}

base_params.merge(wildcard_params)
end

def build_helm_values(params)
domain = params.fetch(:domain)
namespace = params.fetch(:namespace)
app_host = ['app', domain].join('.')

{
app_url: "https://#{app_host}",
webHostname: app_host,
allowed_hosts: app_host,
managed_dns_zone_dns_name: domain,
global: {
uffizzi: {
firstUser: {
email: params.fetch(:user_email),
password: params.fetch(:user_password),
},
controller: {
password: params.fetch(:controller_password),
},
},
},
'uffizzi-controller' => {
ingress: {
hostname: "controller.#{domain}",
},
clusterIssuer: params.fetch(:cluster_issuer),
certEmail: params.fetch(:cert_email),
'ingress-nginx' => {
controller: {
extraArgs: {
'default-ssl-certificate' => "#{namespace}/wildcard.#{domain}",
},
},
},
},
}.deep_stringify_keys
end

def execute_command(command, say: true)
stdout_str, stderr_str, status = Uffizzi.ui.execute(command)

return yield(stdout_str, stderr_str) if block_given?

Uffizzi.ui.say_error_and_exit(stderr_str) unless status.success?

say ? Uffizzi.ui.say(stdout_str) : stdout_str
rescue Errno::ENOENT => e
Uffizzi.ui.say_error_and_exit(e.message)
end

def create_helm_values_file(values)
FileUtils.mkdir_p(helm_values_dir_path) unless File.directory?(helm_values_dir_path)
File.write(helm_values_file_path, values.to_yaml)
end

def helm_values_file_path
File.join(helm_values_dir_path, VALUES_FILE_NAME)
end

def helm_values_dir_path
File.dirname(Uffizzi::ConfigFile.config_path)
end

def generate_password
hexatridecimal_base = 36
length = 8
rand(hexatridecimal_base**length).to_s(hexatridecimal_base)
end
end
end
4 changes: 3 additions & 1 deletion lib/uffizzi/services/preview_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def wait_containers_creation(deployment, project_slug)

Uffizzi.ui.say('Deployed')
Uffizzi.ui.say("Deployment url: https://#{deployment[:preview_url]}")
Uffizzi.ui.say("Deployment proxy url: https://#{deployment[:proxy_preview_url]}")
if deployment[:proxy_preview_url].present?
Uffizzi.ui.say("Deployment proxy url: https://#{deployment[:proxy_preview_url]}")
end

activity_items
rescue ApiClient::ResponseError => e
Expand Down
5 changes: 5 additions & 0 deletions lib/uffizzi/shell.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'awesome_print'
require 'open3'

module Uffizzi
module UI
Expand Down Expand Up @@ -54,6 +55,10 @@ def stdout_pipe?
$stdout.stat.pipe?
end

def execute(command)
Open3.capture3(command)
end

private

def format_to_json(data)
Expand Down
40 changes: 40 additions & 0 deletions test/support/mocks/mock_shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

class MockShell
class ExitError < StandardError; end

class MockProcessStatus
def initialize(success)
@success = success
end

def success?
@success
end
end

attr_accessor :messages, :output_format, :stdout_pipe

PRETTY_JSON = 'pretty-json'
Expand All @@ -10,6 +21,7 @@ class ExitError < StandardError; end

def initialize
@messages = []
@command_responses = []
@output_enabled = true
@stdout_pipe = false
end
Expand Down Expand Up @@ -56,8 +68,36 @@ def enable_stdout
@output_enabled = true
end

def execute(command, *_params)
stdout, stderr = get_command_response(command)
status = MockProcessStatus.new(stderr.nil?)

[stdout, stderr, status]
end

def promise_execute(command, stdout: nil, stderr: nil)
@command_responses << { command: command, stdout: stdout, stderr: stderr }
end

private

def get_command_response(command)
response_index = @command_responses.index do |command_response|
case command_response[:command]
when Regexp
command_response[:command].match?(command)
else
command_response[:command] == command
end
end

stdout = @command_responses[response_index].fetch(:stdout)
stderr = @command_responses[response_index].fetch(:stderr)
@command_responses.delete_at(response_index)

[stdout, stderr]
end

def format_to_json(data)
data.to_json
end
Expand Down
4 changes: 3 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ class Minitest::Test
def before_setup
super

@mock_prompt = MockPrompt.new
@mock_shell = MockShell.new
Uffizzi::UI::Shell.stubs(:new).returns(@mock_shell)
Uffizzi.stubs(:ui).returns(@mock_shell)
Uffizzi.stubs(:prompt).returns(@mock_prompt)
Uffizzi::ConfigFile.stubs(:config_path).returns(TEST_CONFIG_PATH)
Uffizzi::Token.stubs(:token_path).returns(TEST_TOKEN_PATH)
end
Expand Down
Loading

0 comments on commit a932923

Please sign in to comment.