From a617c37555eae45b686bb857d58d4037947c8d2c Mon Sep 17 00:00:00 2001 From: Grayson Adkins Date: Fri, 17 Nov 2023 11:33:13 -0600 Subject: [PATCH 01/10] Update README.md --- README.md | 172 +----------------------------------------------------- 1 file changed, 2 insertions(+), 170 deletions(-) diff --git a/README.md b/README.md index c4675242..e337e7c5 100644 --- a/README.md +++ b/README.md @@ -1,178 +1,10 @@ # Uffizzi CLI -A command-line interace (CLI) for [Uffizzi App](https://github.com/UffizziCloud/uffizzi_app) - -## Uffizzi Overview - -Uffizzi is an open-source engine for creating lightweight, ephemeral test environments for APIs and full-stack applications. Uffizzi enables teams to preview new features before merging and to mitigate the risk of introducing regressions into a codebase. Each preview gets a shareable URL that's updated when you push new commits or image tags, so teams can provide continual feedback during the development/QA process. Previews can be configured to expire or be destroyed when a pull request is closed, so environments exist only as long as they are needed. Uffizzi also helps deconflict shared development environments since previews are deployed as isolated namespaces—there is no risk of clobbering another developer's preview. - -While Uffizzi depends on Kubernetes, it does not require end-users to interface with Kubernetes directly. Instead, Uffizzi leverages Docker Compose as its configuration file format, so developers do not need modify Kubernetes manifests or even know about Kubernetes. - -Uffizzi is designed to integrate with any CI/CD system. - -## Uffizzi Architecture - - -Uffizzi consists of the following components: - -- [Uffizzi App](https://github.com/UffizziCloud/uffizzi_app) - The primary REST API for creating and managing Previews -- [Uffizzi Controller](https://github.com/UffizziCloud/uffizzi_controller) - A smart proxy service that handles requests from Uffizzi App to the Kubernetes API -- Uffizzi CLI (this repository) - A command-line interface for Uffizzi App - -To host Uffizzi yourself, you will also need the following external dependencies: - -- Kubernetes (k8s) cluster -- Postgres database -- Redis cache +A command-line interace (CLI) for the [Uffizzi API](https://github.com/UffizziCloud/uffizzi) ## Installation -The Uffizzi CLI can be used interactively or as part of an automated workflow (e.g. GitHub Actions). Both options use the `uffizzi/cli` container image available on Docker Hub. - -### Interactive mode - -Run the CLI as a Docker container in interactive mode: -``` -docker run --interactive --rm --tty --entrypoint=sh uffizzi/cli -``` - -If you specify the following environment variables, the Docker image's -entrypoint script can log you into Uffizzi before executing your command. - -- `UFFIZZI_USER` -- `UFFIZZI_SERVER` -- `UFFIZZI_PASSWORD` -- `UFFIZZI_PROJECT` (optional) - -### Automated mode - -If you want to use Uffizzi as part of an automated workflow, you can pass the Uffizzi commands to the Docker run command. For example: - -``` -docker run -it --rm uffizzi/cli project list -``` - -## Sample commands and examples - -### help - -The `help` subcommand can be used to see more information about a particular command. - -Examples: - -``` -uffizzi help -``` - -``` -uffizzi preview help -``` - -``` -uffizzi project compose help -``` - -### login - -``` -uffizzi login --server=localhost:8080 --username=your@email.com -``` - -Log in to the app with the specified server. - -#### login options - -| Option | Aliase | Description | -| ------------ | ------ | ------------------------- | -| `--username` | `-u` | Your email for logging in | -| `--server` | | The URL of the Uffizzi installation | - -If server uses basic authentication you can specify options for it by setting `basic_auth_user` and `basic_auth_password` via `config set` command. - -### config - -Use this command to configure your cli app. - -``` -$ uffizzi config -``` - -Launching interactive setup guide that sets the values for `server`, `username` and `project` - -### config subcommands - -This command has 4 subcommands `list`, `get`, `set`, and `delete`. - -``` -uffizzi config list -``` - -Shows all options and their values from the config file. - -``` -uffizzi config get-value OPTION -``` - -Shows the value of the specified option. - -``` -uffizzi config set OPTION VALUE -``` - -Sets specified value for specified option. If a specified option already exists and has value it will be overwritten. - -``` -uffizzi config unset OPTION -``` - -Unsets specified option. - -### project - -``` -uffizzi project -``` - -Use this command to configure your projects. This command has 2 subcommands `list` and `compose`. - -``` -uffizzi project list -``` - -Shows all your projects' slugs - -If you have only one project it will be added to your config file automatically, if there's more than one project you need to set up your project manually with the commands `uffizzi config set YOUR_PROJECT_SLUG` or `uffizzi project set-default YOUR_PROJECT_SLUG` - -``` -$ uffizzi project set-default PROJECT_SLUG -``` -Create a preview from a compose file. - -Sets the default project given with the given project slug. When set, all commands use this project as the default context unless overridden by the --project flag. - -### preview - -Create and manage previews - -``` -uffizzi preview create docker-compose.uffizzi.yml -``` -Create a preview from a compose file. - -``` -uffizzi preview delete deployment-21 -``` -Delete a preview with preview ID `deployment-21`. - -### disconnect - -``` -uffizzi disconnect CREDENTIAL_TYPE -``` - -Deletes credential of specified type - -Supported credential types - `docker-hub`, `acr`, `ecr`, `gcr` +See the [Uffizzi Documentation](https://docs.uffizzi.com) for installation instructions. ## Contributing From caf658b3a518cbe2b06034c9ceee9ee41cc96a1f Mon Sep 17 00:00:00 2001 From: Zipofar Date: Tue, 8 Aug 2023 20:00:41 +0300 Subject: [PATCH 02/10] [1110_uffizzi_platform] WIP --- lib/uffizzi/cli.rb | 4 + lib/uffizzi/cli/install.rb | 283 ++++++++++++++++++++++++ lib/uffizzi/services/preview_service.rb | 4 +- test/support/mocks/mock_shell.rb | 18 +- test/uffizzi/cli/install_test.rb | 53 +++++ test/uffizzi/cli/login_test.rb | 2 - 6 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 lib/uffizzi/cli/install.rb create mode 100644 test/uffizzi/cli/install_test.rb diff --git a/lib/uffizzi/cli.rb b/lib/uffizzi/cli.rb index e6a99137..146e7ade 100644 --- a/lib/uffizzi/cli.rb +++ b/lib/uffizzi/cli.rb @@ -75,6 +75,10 @@ def disconnect(credential_type) require_relative 'cli/dev' subcommand 'dev', Cli::Dev + desc 'install', 'install' + require_relative 'cli/install' + subcommand 'install', Cli::Install + map preview: :compose class << self diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb new file mode 100644 index 00000000..76845f87 --- /dev/null +++ b/lib/uffizzi/cli/install.rb @@ -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 diff --git a/lib/uffizzi/services/preview_service.rb b/lib/uffizzi/services/preview_service.rb index 0f7c4e13..98b58db4 100644 --- a/lib/uffizzi/services/preview_service.rb +++ b/lib/uffizzi/services/preview_service.rb @@ -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 diff --git a/test/support/mocks/mock_shell.rb b/test/support/mocks/mock_shell.rb index ce1cf024..dc03dec7 100644 --- a/test/support/mocks/mock_shell.rb +++ b/test/support/mocks/mock_shell.rb @@ -2,7 +2,6 @@ class MockShell class ExitError < StandardError; end - class MockProcessStatus def initialize(success) @success = success @@ -109,6 +108,23 @@ def promise_execute(command, stdout: nil, stderr: nil, waiter: nil) 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 diff --git a/test/uffizzi/cli/install_test.rb b/test/uffizzi/cli/install_test.rb new file mode 100644 index 00000000..46170f9c --- /dev/null +++ b/test/uffizzi/cli/install_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'psych' +require 'base64' +require 'test_helper' + +class InstallTest < Minitest::Test + def setup + @install = Uffizzi::Cli::Install.new + + tmp_dir_name = (Time.now.utc.to_f * 100_000).to_i + helm_values_path = "/tmp/test/#{tmp_dir_name}/helm_values.yaml" + Uffizzi::ConfigFile.stubs(:config_path).returns(helm_values_path) + end + + def test_install_by_wizard + @mock_prompt.promise_question_answer('Uffizzi use a wildcard tls certificate. Do you have it?', 'n') + @mock_prompt.promise_question_answer('Do you want to add wildcard certificate later?', 'y') + @mock_prompt.promise_question_answer('Domain: ', 'my-domain.com') + @mock_prompt.promise_question_answer('User email: ', 'admin@my-domain.com') + @mock_prompt.promise_question_answer('User password: ', 'password') + @mock_prompt.promise_question_answer('Controller password: ', 'password') + @mock_prompt.promise_question_answer('Email address for ACME registration: ', 'admin@my-domain.com') + @mock_prompt.promise_question_answer('Cluster issuer', :first) + + @mock_shell.promise_execute(/kubectl version/, stdout: '1.23.00') + @mock_shell.promise_execute(/helm version/, stdout: '3.00') + @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) + @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') + @mock_shell.promise_execute(/helm list/, stdout: [].to_json) + @mock_shell.promise_execute(/helm install/, stdout: { info: { status: 'deployed' } }.to_json) + + @install.by_wizard('uffizzi') + + last_message = Uffizzi.ui.last_message + assert_match('deployed', last_message) + end + + def test_install_by_options + @mock_shell.promise_execute(/kubectl version/, stdout: '1.23.00') + @mock_shell.promise_execute(/helm version/, stdout: '3.00') + @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) + @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') + @mock_shell.promise_execute(/helm list/, stdout: [].to_json) + @mock_shell.promise_execute(/helm install/, stdout: { info: { status: 'deployed' } }.to_json) + + @install.options = command_options(domain: 'my-domain.com', 'without-wildcard-tls' => true) + @install.by_options('uffizzi') + + last_message = Uffizzi.ui.last_message + assert_match('deployed', last_message) + end +end diff --git a/test/uffizzi/cli/login_test.rb b/test/uffizzi/cli/login_test.rb index a03ba12b..732da2a1 100644 --- a/test/uffizzi/cli/login_test.rb +++ b/test/uffizzi/cli/login_test.rb @@ -5,8 +5,6 @@ class LoginTest < Minitest::Test def setup @cli = Uffizzi::Cli.new - @mock_prompt = MockPrompt.new - Uffizzi.stubs(:prompt).returns(@mock_prompt) @command_params = { username: generate(:email), From 06f8d4e09a74864f85c05122ad904c71c3c9253a Mon Sep 17 00:00:00 2001 From: Zipofar Date: Thu, 31 Aug 2023 19:29:00 +0300 Subject: [PATCH 03/10] [1110_uffizzi_platform] refactor --- lib/uffizzi/cli/install.rb | 70 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb index 76845f87..7e5852ce 100644 --- a/lib/uffizzi/cli/install.rb +++ b/lib/uffizzi/cli/install.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'byebug' require 'uffizzi' require 'uffizzi/config_file' @@ -9,39 +10,40 @@ class Cli::Install < Thor HELM_DEPLOYED_STATUS = 'deployed' CHART_NAME = 'uffizzi-app' VALUES_FILE_NAME = 'helm_values.yaml' + DEFAULT_ISSUER = 'letsencrypt' + DEFAULT_NAMESPACE = 'default' - 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' + desc 'application', 'Install uffizzi to cluster' + method_option :namespace, required: false, type: :string + method_option :domain, required: false, type: :string + method_option :'user-email', required: false, type: :string 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 :issuer, type: :string, enum: ['letsencrypt', 'zerossl'] 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) + def application run_installation do - validate_installation_options(namespace, options) + if options.present? + validate_installation_options + else + ask_installation_params + end 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) + desc 'wildcard-tls', 'Add wildcard tls from files' + method_option :domain, required: true, type: :string + method_option :cert, required: true, type: :string + method_option :key, required: true, type: :string + method_option :namespace, required: false, type: :string + def add_wildcard_tls kubectl_exists? params = { - namespace: namespace, + namespace: options[:namespace], domain: options[:domain], wildcard_cert_path: options[:cert], wildcard_key_path: options[:key], @@ -111,6 +113,8 @@ def helm_release_list(namespace) end def helm_install(namespace) + Uffizzi.ui.say('Start helm release installation') + release_name = namespace cmd = "helm install #{release_name} #{HELM_REPO_NAME}/#{CHART_NAME}" \ " --values #{helm_values_file_path}" \ @@ -145,25 +149,19 @@ def ask_wildcard_cert 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"') + Uffizzi.ui.say('Uffizzi does not work properly without a wildcard certificate.') + Uffizzi.ui.say('You can add wildcard cert later with command:') + Uffizzi.ui.say('uffizzi install wildcard-tls --domain your.domain.com --cert /path/to/cert --key /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 + { wildcard_cert_path: nil, wildcard_key_path: nil } end - def ask_installation_params(namespace) + def ask_installation_params wildcard_cert_paths = ask_wildcard_cert + namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) 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' }, @@ -176,21 +174,21 @@ def ask_installation_params(namespace) domain: domain, user_email: user_email, user_password: user_password, - controller_password: controller_password, + controller_password: generate_password, cert_email: cert_email, cluster_issuer: cluster_issuer, }.merge(wildcard_cert_paths) end - def validate_installation_options(namespace, options) + def validate_installation_options base_params = { - namespace: namespace, + namespace: options[:namespace] || DEFAULT_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], + cluster_issuer: options[:issuer] || DEFAULT_ISSUER, wildcard_cert_path: nil, wildcard_key_path: nil, } @@ -200,7 +198,7 @@ def validate_installation_options(namespace, options) 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") + return Uffizzi.ui.say_error_and_exit("#{empty_key} is required or use the flag --without-wildcard-tls") end wildcard_params = { From 76b889bf08b717fd054d627643fb819d38fe41bd Mon Sep 17 00:00:00 2001 From: Zipofar Date: Fri, 1 Sep 2023 19:21:10 +0300 Subject: [PATCH 04/10] [1110_uffizzi_platform] refactor --- lib/uffizzi/cli/install.rb | 117 +++++++++++++++++++------------ test/uffizzi/cli/install_test.rb | 5 +- 2 files changed, 76 insertions(+), 46 deletions(-) diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb index 7e5852ce..2bd03d6f 100644 --- a/lib/uffizzi/cli/install.rb +++ b/lib/uffizzi/cli/install.rb @@ -12,21 +12,24 @@ class Cli::Install < Thor VALUES_FILE_NAME = 'helm_values.yaml' DEFAULT_ISSUER = 'letsencrypt' DEFAULT_NAMESPACE = 'default' + DEFAULT_APP_PREFIX = 'uffizzi' desc 'application', 'Install uffizzi to cluster' - method_option :namespace, required: false, type: :string - method_option :domain, required: false, type: :string - method_option :'user-email', required: false, type: :string - 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 :namespace, type: :string + method_option :domain, type: :string + method_option :'user-email', type: :string + method_option :'acme-email', type: :string + method_option :'user-password', type: :string + method_option :'controller-password', type: :string method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl'] - 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 + method_option :'wildcard-cert-path', type: :string + method_option :'wildcard-key-path', type: :string + method_option :'without-wildcard-tls', type: :boolean + method_option :repo, type: :string + method_option :'print-values', type: :boolean def application run_installation do - if options.present? + if options.except(:repo, :'print-values').present? validate_installation_options else ask_installation_params @@ -35,34 +38,56 @@ def application end desc 'wildcard-tls', 'Add wildcard tls from files' - method_option :domain, required: true, type: :string - method_option :cert, required: true, type: :string - method_option :key, required: true, type: :string - method_option :namespace, required: false, type: :string - def add_wildcard_tls + method_option :domain, type: :string + method_option :cert, type: :string + method_option :key, type: :string + method_option :namespace, type: :string + def wildcard_tls kubectl_exists? - params = { - namespace: options[:namespace], - domain: options[:domain], - wildcard_cert_path: options[:cert], - wildcard_key_path: options[:key], - } + params = if options.present? && wildcard_tls_options_valid? + { + namespace: options[:namespace] || DEFAULT_NAMESPACE, + domain: options[:domain], + wildcard_cert_path: options[:cert], + wildcard_key_path: options[:key], + } + else + namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) + domain = Uffizzi.prompt.ask('Domain: ', required: true, default: 'example.com') + wildcard_cert_paths = ask_wildcard_cert(has_user_wildcard_cert: true) + + { namespace: namespace, domain: domain }.merge(wildcard_cert_paths) + end kubectl_add_wildcard_tls(params) end private + def wildcard_tls_options_valid? + required_options = [:domain, :cert, :key] + missing_options = required_options - options.symbolize_keys.keys + + return true if missing_options.empty? + + rendered_missing_options = missing_options.map { |o| "'--#{o}'" }.join(', ') + + Uffizzi.ui.say_error_and_exit("No value provided for required options #{rendered_missing_options}") + end + def run_installation kubectl_exists? helm_exists? params = yield helm_values = build_helm_values(params) + return Uffizzi.ui.say(helm_values.to_yaml) if options[:'print-values'] + create_helm_values_file(helm_values) - helm_set_repo + helm_set_repo unless options[:repo] helm_set_release(params.fetch(:namespace)) kubectl_add_wildcard_tls(params) if params[:wildcard_cert_path] && params[:wildcard_key_path] + delete_helm_values_file end def kubectl_exists? @@ -116,7 +141,8 @@ def helm_install(namespace) Uffizzi.ui.say('Start helm release installation') release_name = namespace - cmd = "helm install #{release_name} #{HELM_REPO_NAME}/#{CHART_NAME}" \ + repo = options[:repo] || "#{HELM_REPO_NAME}/#{CHART_NAME}" + cmd = "helm install #{release_name} #{repo}" \ " --values #{helm_values_file_path}" \ " --namespace #{namespace}" \ ' --create-namespace' \ @@ -139,8 +165,8 @@ def kubectl_add_wildcard_tls(params) 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?') + def ask_wildcard_cert(has_user_wildcard_cert: nil) + 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) @@ -153,7 +179,7 @@ def ask_wildcard_cert Uffizzi.ui.say('You can add wildcard cert later with command:') Uffizzi.ui.say('uffizzi install wildcard-tls --domain your.domain.com --cert /path/to/cert --key /path/to/key') - { wildcard_cert_path: nil, wildcard_key_path: nil } + {} end def ask_installation_params @@ -181,38 +207,37 @@ def ask_installation_params end def validate_installation_options - base_params = { - namespace: options[:namespace] || DEFAULT_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] || DEFAULT_ISSUER, - wildcard_cert_path: nil, - wildcard_key_path: nil, - } + installation_options = build_installation_options - return base_params if options[:'without-wildcard-tls'] + if options[:'without-wildcard-tls'] + return installation_options.except(:wildcard_cert_path, :wildcard_key_path) + end 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") + Uffizzi.ui.say_error_and_exit("#{empty_key} is required or use the flag --without-wildcard-tls") end + end - wildcard_params = { + def build_installation_options + { + namespace: options[:namespace] || DEFAULT_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] || DEFAULT_ISSUER, 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_host = [DEFAULT_APP_PREFIX, domain].join('.') { app_url: "https://#{app_host}", @@ -232,7 +257,7 @@ def build_helm_values(params) }, 'uffizzi-controller' => { ingress: { - hostname: "controller.#{domain}", + disabled: true, }, clusterIssuer: params.fetch(:cluster_issuer), certEmail: params.fetch(:cert_email), @@ -264,6 +289,10 @@ def create_helm_values_file(values) File.write(helm_values_file_path, values.to_yaml) end + def delete_helm_values_file + File.delete(helm_values_file_path) if File.exist?(helm_values_file_path) + end + def helm_values_file_path File.join(helm_values_dir_path, VALUES_FILE_NAME) end diff --git a/test/uffizzi/cli/install_test.rb b/test/uffizzi/cli/install_test.rb index 46170f9c..42911fc1 100644 --- a/test/uffizzi/cli/install_test.rb +++ b/test/uffizzi/cli/install_test.rb @@ -16,6 +16,7 @@ def setup def test_install_by_wizard @mock_prompt.promise_question_answer('Uffizzi use a wildcard tls certificate. Do you have it?', 'n') @mock_prompt.promise_question_answer('Do you want to add wildcard certificate later?', 'y') + @mock_prompt.promise_question_answer('Namespace: ', 'uffizzi') @mock_prompt.promise_question_answer('Domain: ', 'my-domain.com') @mock_prompt.promise_question_answer('User email: ', 'admin@my-domain.com') @mock_prompt.promise_question_answer('User password: ', 'password') @@ -30,7 +31,7 @@ def test_install_by_wizard @mock_shell.promise_execute(/helm list/, stdout: [].to_json) @mock_shell.promise_execute(/helm install/, stdout: { info: { status: 'deployed' } }.to_json) - @install.by_wizard('uffizzi') + @install.application last_message = Uffizzi.ui.last_message assert_match('deployed', last_message) @@ -45,7 +46,7 @@ def test_install_by_options @mock_shell.promise_execute(/helm install/, stdout: { info: { status: 'deployed' } }.to_json) @install.options = command_options(domain: 'my-domain.com', 'without-wildcard-tls' => true) - @install.by_options('uffizzi') + @install.application last_message = Uffizzi.ui.last_message assert_match('deployed', last_message) From 28edc02628b9365843f6200c678290a3fccef4a9 Mon Sep 17 00:00:00 2001 From: Zipofar Date: Mon, 4 Sep 2023 20:03:39 +0300 Subject: [PATCH 05/10] [1110_uffizzi_platform] add man --- docker-compose.yml | 2 + lib/uffizzi/cli/install.rb | 66 +++++++++++++-------------- man/uffizzi-install | 76 ++++++++++++++++++++++++++++++++ man/uffizzi-install.ronn | 67 ++++++++++++++++++++++++++++ test/uffizzi/cli/install_test.rb | 9 ++-- 5 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 man/uffizzi-install create mode 100644 man/uffizzi-install.ronn diff --git a/docker-compose.yml b/docker-compose.yml index 17799ffd..ebb40c33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,8 @@ services: - ~/.ssh:/root/.ssh - ~/.bash_history:/root/.bash_history - ~/.config/uffizzi:/root/.config/uffizzi + - ~/test/uffizzi_app/charts/uffizzi-app:/gem/tmp/charts/uffizzi_app + - ~/test/uffizzi_controller_os/charts/uffizzi-controller:/gem/tmp/charts/uffizzi-controller - bundle_cache:/bundle_cache environment: - BUNDLE_PATH=/bundle_cache diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb index 2bd03d6f..9f1bf77f 100644 --- a/lib/uffizzi/cli/install.rb +++ b/lib/uffizzi/cli/install.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'byebug' require 'uffizzi' require 'uffizzi/config_file' @@ -11,7 +10,7 @@ class Cli::Install < Thor CHART_NAME = 'uffizzi-app' VALUES_FILE_NAME = 'helm_values.yaml' DEFAULT_ISSUER = 'letsencrypt' - DEFAULT_NAMESPACE = 'default' + DEFAULT_NAMESPACE = 'uffizzi' DEFAULT_APP_PREFIX = 'uffizzi' desc 'application', 'Install uffizzi to cluster' @@ -20,7 +19,6 @@ class Cli::Install < Thor method_option :'user-email', type: :string method_option :'acme-email', type: :string method_option :'user-password', type: :string - method_option :'controller-password', type: :string method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl'] method_option :'wildcard-cert-path', type: :string method_option :'wildcard-key-path', type: :string @@ -42,10 +40,11 @@ def application method_option :cert, type: :string method_option :key, type: :string method_option :namespace, type: :string + method_option :repo, type: :string def wildcard_tls kubectl_exists? - params = if options.present? && wildcard_tls_options_valid? + params = if options.except(:repo).present? && wildcard_tls_options_valid? { namespace: options[:namespace] || DEFAULT_NAMESPACE, domain: options[:domain], @@ -55,18 +54,25 @@ def wildcard_tls else namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) domain = Uffizzi.prompt.ask('Domain: ', required: true, default: 'example.com') - wildcard_cert_paths = ask_wildcard_cert(has_user_wildcard_cert: true) + wildcard_cert_paths = ask_wildcard_cert(has_user_wildcard_cert: true, domain: domain) { namespace: namespace, domain: domain }.merge(wildcard_cert_paths) end kubectl_add_wildcard_tls(params) + helm_values = helm_get_values(namespace, namespace) + helm_values['uffizzi-controller']['tlsPerDeploymentEnabled'] = false.to_s + create_helm_values_file(helm_values) + helm_set_repo unless options[:repo] + helm_install(release_name: namespace, namespace: namespace, repo: options[:repo]) end + default_task :application + private def wildcard_tls_options_valid? - required_options = [:domain, :cert, :key] + required_options = [:namespace, :domain, :cert, :key] missing_options = required_options - options.symbolize_keys.keys return true if missing_options.empty? @@ -85,9 +91,12 @@ def run_installation create_helm_values_file(helm_values) helm_set_repo unless options[:repo] - helm_set_release(params.fetch(:namespace)) + helm_install(release_name: params[:namespace], namespace: params[:namespace], repo: options[:repo]) kubectl_add_wildcard_tls(params) if params[:wildcard_cert_path] && params[:wildcard_key_path] delete_helm_values_file + + Uffizzi.ui.say('Helm release is deployed') + Uffizzi.ui.say("The uffizzi application url is https://#{DEFAULT_APP_PREFIX}.#{params[:domain]}") end def kubectl_exists? @@ -107,16 +116,6 @@ def helm_set_repo 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) @@ -130,32 +129,31 @@ def helm_repo_search 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) + def helm_install(release_name:, namespace:, repo:) Uffizzi.ui.say('Start helm release installation') - release_name = namespace - repo = options[:repo] || "#{HELM_REPO_NAME}/#{CHART_NAME}" - cmd = "helm install #{release_name} #{repo}" \ + repo = repo || "#{HELM_REPO_NAME}/#{CHART_NAME}" + cmd = "helm upgrade #{release_name} #{repo}" \ " --values #{helm_values_file_path}" \ " --namespace #{namespace}" \ ' --create-namespace' \ + ' --install' \ ' --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 + return if info['status'] == HELM_DEPLOYED_STATUS Uffizzi.ui.say_error_and_exit(info) end + def helm_get_values(release_name, namespace) + cmd = "helm get values #{release_name} -n #{namespace} -o json" + res = execute_command(cmd, say: false) + JSON.parse(res) + end + def kubectl_add_wildcard_tls(params) cmd = "kubectl create secret tls wildcard.#{params.fetch(:domain)}" \ " --cert=#{params.fetch(:wildcard_cert_path)}" \ @@ -165,7 +163,7 @@ def kubectl_add_wildcard_tls(params) execute_command(cmd) end - def ask_wildcard_cert(has_user_wildcard_cert: nil) + def ask_wildcard_cert(has_user_wildcard_cert: nil, domain: nil) has_user_wildcard_cert ||= Uffizzi.prompt.yes?('Uffizzi use a wildcard tls certificate. Do you have it?') if has_user_wildcard_cert @@ -177,13 +175,12 @@ def ask_wildcard_cert(has_user_wildcard_cert: nil) Uffizzi.ui.say('Uffizzi does not work properly without a wildcard certificate.') Uffizzi.ui.say('You can add wildcard cert later with command:') - Uffizzi.ui.say('uffizzi install wildcard-tls --domain your.domain.com --cert /path/to/cert --key /path/to/key') + Uffizzi.ui.say("uffizzi install wildcard-tls --domain #{domain} --cert /path/to/cert --key /path/to/key") {} end def ask_installation_params - wildcard_cert_paths = ask_wildcard_cert namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) domain = Uffizzi.prompt.ask('Domain: ', required: true, default: 'example.com') user_email = Uffizzi.prompt.ask('User email: ', required: true, default: "admin@#{domain}") @@ -194,6 +191,7 @@ def ask_installation_params { name: 'ZeroSSL', value: 'zerossl' }, ] cluster_issuer = Uffizzi.prompt.select('Cluster issuer', cluster_issuers) + wildcard_cert_paths = ask_wildcard_cert(domain: domain) { namespace: namespace, @@ -226,7 +224,7 @@ def build_installation_options 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, + controller_password: generate_password, cert_email: options[:'acme-email'] || options[:'user-email'], cluster_issuer: options[:issuer] || DEFAULT_ISSUER, wildcard_cert_path: options[:'wildcard-cert-path'], @@ -237,6 +235,7 @@ def build_installation_options def build_helm_values(params) domain = params.fetch(:domain) namespace = params.fetch(:namespace) + tls_per_deployment_enabled = params.slice(:wildcard_cert_pathm, :wildcard_key_path).compact.empty? app_host = [DEFAULT_APP_PREFIX, domain].join('.') { @@ -260,6 +259,7 @@ def build_helm_values(params) disabled: true, }, clusterIssuer: params.fetch(:cluster_issuer), + tlsPerDeploymentEnabled: tls_per_deployment_enabled.to_s, certEmail: params.fetch(:cert_email), 'ingress-nginx' => { controller: { diff --git a/man/uffizzi-install b/man/uffizzi-install new file mode 100644 index 00000000..a13338b5 --- /dev/null +++ b/man/uffizzi-install @@ -0,0 +1,76 @@ +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "INSTALL" "" "September 2023" "" +.SH "NAME" +\fBinstall\fR \- install the Uffizzi application to cluster +.SH "SYNOPSIS" +.nf +uffizzi install COMMAND +.fi +.SH "DESCRIPTION" +.nf +The uffizzi install command lets you deploy uffizzi application to your kubecrnetes cluster\. +If COMMAND is not specified, uffizzi install start installation\. +if OPTIONS not specified, uffizzi show installation wizard\. + +For more information on configuration options, see: +https://docs\.uffizzi\.com/references/cli/ +.fi +.SH "COMMANDS" +.nf +COMMAND is one of the following: + + wildcard_tls OPTION + Add the wildcard tls certificate to installed uffizzi application\. +.fi +.SH "OPTIONS" +.nf + OPTION is one of the following: + + namespace + The namespace of the kubecrnetes cluster where application will be deployed\. + Default is uffizzi\. + + domain + The domain that will be used for access the web API\. + + issuer + The cluster issuer that will be used for generate tls certificates\. + Default is letsencrypt\. + + user\-email + The login that will be used for access to web API\. + + user\-password + The password that will be used for access to web API\. + + acme\-email + Email address for ACME registration + + wildcard\-cert\-path + Path to wildcard certificate\. + + wildcard\-key\-path + Path to wildcard certificate key\. + + without\-wildcard\-tls + Set this flag and we can install application without wildcard certificate\. + + print\-values + Show builded vales for helm installation\. + The installation will not be executed\. + + repo + The repository that will be used for helm install +.fi +.SH "EXAMPLES" +.nf +To install the uffizzi command, run: + + $ uffizzi install + +To install the wildcard_tls command, run: + + $ uffizzi install wildcard_tls +.fi + diff --git a/man/uffizzi-install.ronn b/man/uffizzi-install.ronn new file mode 100644 index 00000000..c9f6cf62 --- /dev/null +++ b/man/uffizzi-install.ronn @@ -0,0 +1,67 @@ +uffizzi install - install the Uffizzi application to cluster +================================================================ + +## SYNOPSIS + uffizzi install COMMAND + +## DESCRIPTION + The uffizzi install command lets you deploy uffizzi application to your kubecrnetes cluster. + If COMMAND is not specified, uffizzi install start installation. + if OPTIONS not specified, uffizzi show installation wizard. + + For more information on configuration options, see: + https://docs.uffizzi.com/references/cli/ + +## COMMANDS + COMMAND is one of the following: + + wildcard_tls OPTION + Add the wildcard tls certificate to installed uffizzi application. + +## OPTIONS + OPTION is one of the following: + + namespace + The namespace of the kubecrnetes cluster where application will be deployed. + Default is uffizzi. + + domain + The domain that will be used for access the web API. + + issuer + The cluster issuer that will be used for generate tls certificates. + Default is letsencrypt. + + user-email + The login that will be used for access to web API. + + user-password + The password that will be used for access to web API. + + acme-email + Email address for ACME registration + + wildcard-cert-path + Path to wildcard certificate. + + wildcard-key-path + Path to wildcard certificate key. + + without-wildcard-tls + Set this flag and we can install application without wildcard certificate. + + print-values + Show builded vales for helm installation. + The installation will not be executed. + + repo + The repository that will be used for helm install + +## EXAMPLES + To install the uffizzi command, run: + + $ uffizzi install + + To install the wildcard_tls command, run: + + $ uffizzi install wildcard_tls diff --git a/test/uffizzi/cli/install_test.rb b/test/uffizzi/cli/install_test.rb index 42911fc1..a7cea326 100644 --- a/test/uffizzi/cli/install_test.rb +++ b/test/uffizzi/cli/install_test.rb @@ -29,12 +29,12 @@ def test_install_by_wizard @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') @mock_shell.promise_execute(/helm list/, stdout: [].to_json) - @mock_shell.promise_execute(/helm install/, stdout: { info: { status: 'deployed' } }.to_json) + @mock_shell.promise_execute(/helm upgrade/, stdout: { info: { status: 'deployed' } }.to_json) @install.application last_message = Uffizzi.ui.last_message - assert_match('deployed', last_message) + assert_match('The uffizzi application url is', last_message) end def test_install_by_options @@ -42,13 +42,12 @@ def test_install_by_options @mock_shell.promise_execute(/helm version/, stdout: '3.00') @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') - @mock_shell.promise_execute(/helm list/, stdout: [].to_json) - @mock_shell.promise_execute(/helm install/, stdout: { info: { status: 'deployed' } }.to_json) + @mock_shell.promise_execute(/helm upgrade/, stdout: { info: { status: 'deployed' } }.to_json) @install.options = command_options(domain: 'my-domain.com', 'without-wildcard-tls' => true) @install.application last_message = Uffizzi.ui.last_message - assert_match('deployed', last_message) + assert_match('The uffizzi application url is', last_message) end end From 8bd298185b7096b2c3ae737ee55f927d2b6f066a Mon Sep 17 00:00:00 2001 From: Zipofar Date: Thu, 7 Sep 2023 18:45:54 +0300 Subject: [PATCH 06/10] [1110_uffizzi_platform] update installation --- lib/uffizzi/cli/install.rb | 77 +++++++++++++++++++++----------- test/uffizzi/cli/install_test.rb | 18 ++++---- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb index 9f1bf77f..bc0dd993 100644 --- a/lib/uffizzi/cli/install.rb +++ b/lib/uffizzi/cli/install.rb @@ -9,15 +9,14 @@ class Cli::Install < Thor HELM_DEPLOYED_STATUS = 'deployed' CHART_NAME = 'uffizzi-app' VALUES_FILE_NAME = 'helm_values.yaml' - DEFAULT_ISSUER = 'letsencrypt' DEFAULT_NAMESPACE = 'uffizzi' DEFAULT_APP_PREFIX = 'uffizzi' + DEFAULT_CLUSTER_ISSUER = 'letsencrypt' desc 'application', 'Install uffizzi to cluster' method_option :namespace, type: :string method_option :domain, type: :string method_option :'user-email', type: :string - method_option :'acme-email', type: :string method_option :'user-password', type: :string method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl'] method_option :'wildcard-cert-path', type: :string @@ -53,7 +52,7 @@ def wildcard_tls } else namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) - domain = Uffizzi.prompt.ask('Domain: ', required: true, default: 'example.com') + domain = Uffizzi.prompt.ask('Root Domain: ', required: true, default: 'example.com') wildcard_cert_paths = ask_wildcard_cert(has_user_wildcard_cert: true, domain: domain) { namespace: namespace, domain: domain }.merge(wildcard_cert_paths) @@ -89,14 +88,36 @@ def run_installation helm_values = build_helm_values(params) return Uffizzi.ui.say(helm_values.to_yaml) if options[:'print-values'] + namespace = params[:namespace] + release_name = params[:namespace] + create_helm_values_file(helm_values) helm_set_repo unless options[:repo] - helm_install(release_name: params[:namespace], namespace: params[:namespace], repo: options[:repo]) + helm_install(release_name: release_name, namespace: namespace, repo: options[:repo]) kubectl_add_wildcard_tls(params) if params[:wildcard_cert_path] && params[:wildcard_key_path] delete_helm_values_file + ingress_ip = get_web_ingress_ip_address(release_name, namespace) + Uffizzi.ui.say('Helm release is deployed') - Uffizzi.ui.say("The uffizzi application url is https://#{DEFAULT_APP_PREFIX}.#{params[:domain]}") + Uffizzi.ui.say("The uffizzi application url is 'https://#{DEFAULT_APP_PREFIX}.#{params[:domain]}'") + Uffizzi.ui.say("Create a DNS A record for domain '*.#{params[:domain]}' with value '#{ingress_ip}'") + end + + def get_web_ingress_ip_address(release_name, namespace) + Uffizzi.ui.say('Getting an ingress ip address...') + + 10.times do + web_ingress = kubectl_get_web_ingress(release_name, namespace) + ingresses = web_ingress.dig('status', 'loadBalancer', 'ingress') || [] + ip_address = ingresses.first&.fetch('ip', nil) + + return ip_address if ip_address.present? + + sleep(1) + end + + Uffizzi.ui.say_error_and_exit('We can`t get the uffizzi ingress ip address') end def kubectl_exists? @@ -163,34 +184,38 @@ def kubectl_add_wildcard_tls(params) execute_command(cmd) end + def kubectl_get_web_ingress(release_name, namespace) + cmd = "kubectl get ingress/#{release_name}-web-ingress -n #{namespace} -o json" + + res = execute_command(cmd, say: false) + JSON.parse(res) + end + def ask_wildcard_cert(has_user_wildcard_cert: nil, domain: nil) 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) + if !has_user_wildcard_cert + Uffizzi.ui.say('Uffizzi does not work properly without a wildcard certificate.') + Uffizzi.ui.say('You can add wildcard cert later with command:') + Uffizzi.ui.say("uffizzi install wildcard-tls --domain #{domain} --cert /path/to/cert --key /path/to/key") - return { wildcard_cert_path: cert_path, wildcard_key_path: key_path } + return {} end - Uffizzi.ui.say('Uffizzi does not work properly without a wildcard certificate.') - Uffizzi.ui.say('You can add wildcard cert later with command:') - Uffizzi.ui.say("uffizzi install wildcard-tls --domain #{domain} --cert /path/to/cert --key /path/to/key") + cert_path = Uffizzi.prompt.ask('Path to cert: ', required: true) + Uffizzi.ui.say_error_and_exit("File '#{cert_path}' does not exists") unless File.exist?(cert_path) + + key_path = Uffizzi.prompt.ask('Path to key: ', required: true) + Uffizzi.ui.say_error_and_exit("File '#{key_path}' does not exists") unless File.exist?(key_path) - {} + { wildcard_cert_path: cert_path, wildcard_key_path: key_path } end def ask_installation_params namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) - 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) - 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) + domain = Uffizzi.prompt.ask('Root domain: ', required: true, default: 'example.com') + user_email = Uffizzi.prompt.ask('First user email: ', required: true, default: "admin@#{domain}") + user_password = Uffizzi.prompt.ask('First user password: ', required: true, default: generate_password) wildcard_cert_paths = ask_wildcard_cert(domain: domain) { @@ -199,8 +224,8 @@ def ask_installation_params user_email: user_email, user_password: user_password, controller_password: generate_password, - cert_email: cert_email, - cluster_issuer: cluster_issuer, + cert_email: user_email, + cluster_issuer: DEFAULT_CLUSTER_ISSUER, }.merge(wildcard_cert_paths) end @@ -225,8 +250,8 @@ def build_installation_options user_email: options[:'user-email'] || "admin@#{options[:domain]}", user_password: options[:'user-password'] || generate_password, controller_password: generate_password, - cert_email: options[:'acme-email'] || options[:'user-email'], - cluster_issuer: options[:issuer] || DEFAULT_ISSUER, + cert_email: options[:'user-email'], + cluster_issuer: options[:issuer] || DEFAULT_CLUSTER_ISSUER, wildcard_cert_path: options[:'wildcard-cert-path'], wildcard_key_path: options[:'wildcard-key-path'], } diff --git a/test/uffizzi/cli/install_test.rb b/test/uffizzi/cli/install_test.rb index a7cea326..5971ca6f 100644 --- a/test/uffizzi/cli/install_test.rb +++ b/test/uffizzi/cli/install_test.rb @@ -14,15 +14,11 @@ def setup end def test_install_by_wizard - @mock_prompt.promise_question_answer('Uffizzi use a wildcard tls certificate. Do you have it?', 'n') - @mock_prompt.promise_question_answer('Do you want to add wildcard certificate later?', 'y') @mock_prompt.promise_question_answer('Namespace: ', 'uffizzi') - @mock_prompt.promise_question_answer('Domain: ', 'my-domain.com') - @mock_prompt.promise_question_answer('User email: ', 'admin@my-domain.com') - @mock_prompt.promise_question_answer('User password: ', 'password') - @mock_prompt.promise_question_answer('Controller password: ', 'password') - @mock_prompt.promise_question_answer('Email address for ACME registration: ', 'admin@my-domain.com') - @mock_prompt.promise_question_answer('Cluster issuer', :first) + @mock_prompt.promise_question_answer('Root domain: ', 'my-domain.com') + @mock_prompt.promise_question_answer('First user email: ', 'admin@my-domain.com') + @mock_prompt.promise_question_answer('First user password: ', 'password') + @mock_prompt.promise_question_answer('Uffizzi use a wildcard tls certificate. Do you have it?', 'n') @mock_shell.promise_execute(/kubectl version/, stdout: '1.23.00') @mock_shell.promise_execute(/helm version/, stdout: '3.00') @@ -30,11 +26,12 @@ def test_install_by_wizard @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') @mock_shell.promise_execute(/helm list/, stdout: [].to_json) @mock_shell.promise_execute(/helm upgrade/, stdout: { info: { status: 'deployed' } }.to_json) + @mock_shell.promise_execute(/kubectl get ingress/, stdout: { status: { loadBalancer: { ingress: [{ ip: '34.31.68.232' }] } } }.to_json) @install.application last_message = Uffizzi.ui.last_message - assert_match('The uffizzi application url is', last_message) + assert_match('Create a DNS A record for domain', last_message) end def test_install_by_options @@ -43,11 +40,12 @@ def test_install_by_options @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') @mock_shell.promise_execute(/helm upgrade/, stdout: { info: { status: 'deployed' } }.to_json) + @mock_shell.promise_execute(/kubectl get ingress/, stdout: { status: { loadBalancer: { ingress: [{ ip: '34.31.68.232' }] } } }.to_json) @install.options = command_options(domain: 'my-domain.com', 'without-wildcard-tls' => true) @install.application last_message = Uffizzi.ui.last_message - assert_match('The uffizzi application url is', last_message) + assert_match('Create a DNS A record for domain', last_message) end end From 1449827a3bb282f71d4130f68877bc842d0a0b4d Mon Sep 17 00:00:00 2001 From: Zipofar Date: Wed, 20 Sep 2023 12:31:58 +0300 Subject: [PATCH 07/10] [1110_uffizzi_platform] remove wildcard tls from installation --- lib/uffizzi/cli/install.rb | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb index bc0dd993..4a4c464f 100644 --- a/lib/uffizzi/cli/install.rb +++ b/lib/uffizzi/cli/install.rb @@ -19,15 +19,12 @@ class Cli::Install < Thor method_option :'user-email', type: :string method_option :'user-password', type: :string method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl'] - method_option :'wildcard-cert-path', type: :string - method_option :'wildcard-key-path', type: :string - method_option :'without-wildcard-tls', type: :boolean method_option :repo, type: :string method_option :'print-values', type: :boolean def application run_installation do if options.except(:repo, :'print-values').present? - validate_installation_options + build_installation_options else ask_installation_params end @@ -94,7 +91,6 @@ def run_installation create_helm_values_file(helm_values) helm_set_repo unless options[:repo] helm_install(release_name: release_name, namespace: namespace, repo: options[:repo]) - kubectl_add_wildcard_tls(params) if params[:wildcard_cert_path] && params[:wildcard_key_path] delete_helm_values_file ingress_ip = get_web_ingress_ip_address(release_name, namespace) @@ -229,20 +225,6 @@ def ask_installation_params }.merge(wildcard_cert_paths) end - def validate_installation_options - installation_options = build_installation_options - - if options[:'without-wildcard-tls'] - return installation_options.except(:wildcard_cert_path, :wildcard_key_path) - end - - empty_key = [:'wildcard-cert-path', :'wildcard-key-path'].detect { |k| options[k].nil? } - - if empty_key.present? - Uffizzi.ui.say_error_and_exit("#{empty_key} is required or use the flag --without-wildcard-tls") - end - end - def build_installation_options { namespace: options[:namespace] || DEFAULT_NAMESPACE, @@ -252,15 +234,12 @@ def build_installation_options controller_password: generate_password, cert_email: options[:'user-email'], cluster_issuer: options[:issuer] || DEFAULT_CLUSTER_ISSUER, - wildcard_cert_path: options[:'wildcard-cert-path'], - wildcard_key_path: options[:'wildcard-key-path'], } end def build_helm_values(params) domain = params.fetch(:domain) namespace = params.fetch(:namespace) - tls_per_deployment_enabled = params.slice(:wildcard_cert_pathm, :wildcard_key_path).compact.empty? app_host = [DEFAULT_APP_PREFIX, domain].join('.') { @@ -284,10 +263,13 @@ def build_helm_values(params) disabled: true, }, clusterIssuer: params.fetch(:cluster_issuer), - tlsPerDeploymentEnabled: tls_per_deployment_enabled.to_s, + tlsPerDeploymentEnabled: true.to_s, certEmail: params.fetch(:cert_email), 'ingress-nginx' => { controller: { + ingressClassResource: { + default: true, + }, extraArgs: { 'default-ssl-certificate' => "#{namespace}/wildcard.#{domain}", }, @@ -298,7 +280,7 @@ def build_helm_values(params) end def execute_command(command, say: true) - stdout_str, stderr_str, status = Uffizzi.ui.execute(command) + stdout_str, stderr_str, status = Uffizzi.ui.capture3(command) return yield(stdout_str, stderr_str) if block_given? From bd80420c14b21b11b38184a44039a94a3b709eef Mon Sep 17 00:00:00 2001 From: Zipofar Date: Thu, 16 Nov 2023 19:37:38 +0300 Subject: [PATCH 08/10] [374] WIP --- lib/uffizzi/cli.rb | 4 + lib/uffizzi/cli/install.rb | 409 ++++++++---------- lib/uffizzi/cli/uninstall.rb | 98 +++++ lib/uffizzi/clients/api/api_client.rb | 28 ++ lib/uffizzi/clients/api/api_routes.rb | 8 + lib/uffizzi/services/install_service.rb | 153 +++++++ .../uffizzi_account_controller_settings.json | 9 + ...zzi_account_controller_settings_empty.json | 3 + test/support/mocks/mock_shell.rb | 18 +- test/support/uffizzi_stub_support.rb | 31 +- test/test_helper.rb | 4 + test/uffizzi/cli/install_test.rb | 106 ++++- test/uffizzi/cli/uninstall_test.rb | 38 ++ 13 files changed, 630 insertions(+), 279 deletions(-) create mode 100644 lib/uffizzi/cli/uninstall.rb create mode 100644 lib/uffizzi/services/install_service.rb create mode 100644 test/fixtures/files/uffizzi/uffizzi_account_controller_settings.json create mode 100644 test/fixtures/files/uffizzi/uffizzi_account_controller_settings_empty.json create mode 100644 test/uffizzi/cli/uninstall_test.rb diff --git a/lib/uffizzi/cli.rb b/lib/uffizzi/cli.rb index 146e7ade..79f9575b 100644 --- a/lib/uffizzi/cli.rb +++ b/lib/uffizzi/cli.rb @@ -79,6 +79,10 @@ def disconnect(credential_type) require_relative 'cli/install' subcommand 'install', Cli::Install + desc 'uninstall', 'uninstall' + require_relative 'cli/uninstall' + subcommand 'uninstall', Cli::Uninstall + map preview: :compose class << self diff --git a/lib/uffizzi/cli/install.rb b/lib/uffizzi/cli/install.rb index 4a4c464f..26e52f5c 100644 --- a/lib/uffizzi/cli/install.rb +++ b/lib/uffizzi/cli/install.rb @@ -2,316 +2,251 @@ require 'uffizzi' require 'uffizzi/config_file' +require 'uffizzi/services/kubeconfig_service' module Uffizzi class Cli::Install < Thor - HELM_REPO_NAME = 'uffizzi' - HELM_DEPLOYED_STATUS = 'deployed' - CHART_NAME = 'uffizzi-app' - VALUES_FILE_NAME = 'helm_values.yaml' - DEFAULT_NAMESPACE = 'uffizzi' - DEFAULT_APP_PREFIX = 'uffizzi' - DEFAULT_CLUSTER_ISSUER = 'letsencrypt' - - desc 'application', 'Install uffizzi to cluster' + include ApiClient + + default_task :controller + + desc 'controller [HOSTNMAE]', 'Install uffizzi controller to cluster' method_option :namespace, type: :string - method_option :domain, type: :string - method_option :'user-email', type: :string - method_option :'user-password', type: :string + method_option :email, type: :string, required: true + method_option :context, type: :string method_option :issuer, type: :string, enum: ['letsencrypt', 'zerossl'] - method_option :repo, type: :string - method_option :'print-values', type: :boolean - def application - run_installation do - if options.except(:repo, :'print-values').present? - build_installation_options - else - ask_installation_params - end - end - end + method_option :'repo-url', type: :string + def controller(hostname) + Uffizzi::AuthHelper.check_login - desc 'wildcard-tls', 'Add wildcard tls from files' - method_option :domain, type: :string - method_option :cert, type: :string - method_option :key, type: :string - method_option :namespace, type: :string - method_option :repo, type: :string - def wildcard_tls - kubectl_exists? - - params = if options.except(:repo).present? && wildcard_tls_options_valid? - { - namespace: options[:namespace] || DEFAULT_NAMESPACE, - domain: options[:domain], - wildcard_cert_path: options[:cert], - wildcard_key_path: options[:key], - } - else - namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) - domain = Uffizzi.prompt.ask('Root Domain: ', required: true, default: 'example.com') - wildcard_cert_paths = ask_wildcard_cert(has_user_wildcard_cert: true, domain: domain) + InstallService.kubectl_exists? + InstallService.helm_exists? - { namespace: namespace, domain: domain }.merge(wildcard_cert_paths) + if options[:context].present? && options[:context] != InstallService.kubeconfig_current_context + InstallService.set_current_context(options[:context]) end - kubectl_add_wildcard_tls(params) - helm_values = helm_get_values(namespace, namespace) - helm_values['uffizzi-controller']['tlsPerDeploymentEnabled'] = false.to_s - create_helm_values_file(helm_values) - helm_set_repo unless options[:repo] - helm_install(release_name: namespace, namespace: namespace, repo: options[:repo]) - end + ask_confirmation - default_task :application + uri = parse_hostname(hostname) + installation_options = build_installation_options(uri) + check_existence_controller_settings(uri, installation_options) + helm_values = build_helm_values(installation_options) - private + InstallService.create_helm_values_file(helm_values) + helm_set_repo + InstallService.helm_install!(namespace) + InstallService.delete_helm_values_file + Uffizzi.ui.say('Helm release is deployed') - def wildcard_tls_options_valid? - required_options = [:namespace, :domain, :cert, :key] - missing_options = required_options - options.symbolize_keys.keys + controller_setting_params = build_controller_setting_params(uri, installation_options) + create_controller_settings(controller_setting_params) if existing_controller_setting.blank? + Uffizzi.ui.say('Controller settings are saved') + say_success(uri) + end - return true if missing_options.empty? + private - rendered_missing_options = missing_options.map { |o| "'--#{o}'" }.join(', ') + def helm_set_repo + return if InstallService.helm_repo_search.present? - Uffizzi.ui.say_error_and_exit("No value provided for required options #{rendered_missing_options}") + InstallService.helm_repo_add(options[:'repo-url']) end - def run_installation - kubectl_exists? - helm_exists? - params = yield - helm_values = build_helm_values(params) - return Uffizzi.ui.say(helm_values.to_yaml) if options[:'print-values'] - - namespace = params[:namespace] - release_name = params[:namespace] - - create_helm_values_file(helm_values) - helm_set_repo unless options[:repo] - helm_install(release_name: release_name, namespace: namespace, repo: options[:repo]) - delete_helm_values_file + def build_installation_options(uri) + { + uri: uri, + controller_username: Faker::Lorem.characters(number: 10), + controller_password: generate_password, + cert_email: options[:email], + cluster_issuer: options[:issuer] || InstallService::DEFAULT_CLUSTER_ISSUER, + } + end - ingress_ip = get_web_ingress_ip_address(release_name, namespace) + def wait_ip + spinner = TTY::Spinner.new('[:spinner] Waiting IP addess...', format: :dots) + spinner.auto_spin - Uffizzi.ui.say('Helm release is deployed') - Uffizzi.ui.say("The uffizzi application url is 'https://#{DEFAULT_APP_PREFIX}.#{params[:domain]}'") - Uffizzi.ui.say("Create a DNS A record for domain '*.#{params[:domain]}' with value '#{ingress_ip}'") - end + ip = nil + try = 0 - def get_web_ingress_ip_address(release_name, namespace) - Uffizzi.ui.say('Getting an ingress ip address...') + loop do + ip = InstallService.get_controller_ip(namespace) + break if ip.present? - 10.times do - web_ingress = kubectl_get_web_ingress(release_name, namespace) - ingresses = web_ingress.dig('status', 'loadBalancer', 'ingress') || [] - ip_address = ingresses.first&.fetch('ip', nil) + if try == 30 + spinner.error - return ip_address if ip_address.present? + return 'unknown' + end - sleep(1) + try += 1 + sleep(2) end - Uffizzi.ui.say_error_and_exit('We can`t get the uffizzi ingress ip address') - end + spinner.success - def kubectl_exists? - cmd = 'kubectl version -o json' - execute_command(cmd, say: false).present? + ip end - def helm_exists? - cmd = 'helm version --short' - execute_command(cmd, say: false).present? - end + def wait_certificate_request_ready(uri) + spinner = TTY::Spinner.new('[:spinner] Waiting create certificate for controller host...', format: :dots) + spinner.auto_spin - def helm_set_repo - repo = helm_repo_search - return if repo.present? + try = 0 - helm_repo_add - end + loop do + requests = InstallService.get_certificate_request(namespace, uri) + break if requests.all? { |r| r['status'].downcase == 'true' } - def helm_repo_add - cmd = "helm repo add #{HELM_REPO_NAME} https://uffizzicloud.github.io/uffizzi" - execute_command(cmd) - end + if try == 60 + spinner.error - def helm_repo_search - cmd = "helm search repo #{HELM_REPO_NAME}/#{CHART_NAME} -o json" + return Uffizzi.ui.say('Stop waiting creation certificate') + end - execute_command(cmd) do |result, err| - err.present? ? nil : JSON.parse(result) + try += 1 + sleep(2) end - end - - def helm_install(release_name:, namespace:, repo:) - Uffizzi.ui.say('Start helm release installation') - - repo = repo || "#{HELM_REPO_NAME}/#{CHART_NAME}" - cmd = "helm upgrade #{release_name} #{repo}" \ - " --values #{helm_values_file_path}" \ - " --namespace #{namespace}" \ - ' --create-namespace' \ - ' --install' \ - ' --output json' - res = execute_command(cmd, say: false) - info = JSON.parse(res)['info'] - - return if info['status'] == HELM_DEPLOYED_STATUS - - Uffizzi.ui.say_error_and_exit(info) + spinner.success end - def helm_get_values(release_name, namespace) - cmd = "helm get values #{release_name} -n #{namespace} -o json" - res = execute_command(cmd, say: false) - JSON.parse(res) + def build_helm_values(params) + { + global: { + uffizzi: { + controller: { + username: params[:controller_username], + password: params[:controller_password], + }, + }, + }, + clusterIssuer: params.fetch(:cluster_issuer), + tlsPerDeploymentEnabled: true.to_s, + certEmail: params.fetch(:cert_email), + 'ingress-nginx' => { + controller: { + ingressClassResource: { + default: true, + }, + }, + }, + ingress: { + hostname: InstallService.build_controller_host(params[:uri].host), + }, + }.deep_stringify_keys 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) + def generate_password + hexatridecimal_base = 36 + length = 8 + rand(hexatridecimal_base**length).to_s(hexatridecimal_base) end - def kubectl_get_web_ingress(release_name, namespace) - cmd = "kubectl get ingress/#{release_name}-web-ingress -n #{namespace} -o json" + def check_existence_controller_settings(uri, installation_options) + return if existing_controller_setting.blank? + + Uffizzi.ui.say_error_and_exit('Installation canceled') unless update_and_continue? - res = execute_command(cmd, say: false) - JSON.parse(res) + controller_setting_params = build_controller_setting_params(uri, installation_options) + update_controller_settings(existing_controller_setting[:id], controller_setting_params) end - def ask_wildcard_cert(has_user_wildcard_cert: nil, domain: nil) - has_user_wildcard_cert ||= Uffizzi.prompt.yes?('Uffizzi use a wildcard tls certificate. Do you have it?') + def ask_confirmation + msg = "\r\n"\ + 'This command will install Uffizzi into the default namespace of'\ + " the '#{InstallService.kubeconfig_current_context}' context."\ + "\r\n"\ + "To install in a different place, use options '--namespace' and/or '--context'."\ + "\r\n\r\n"\ + "After installation, new environments created for account '#{account_name}' will be deployed to this host cluster."\ + "\r\n\r\n" - if !has_user_wildcard_cert - Uffizzi.ui.say('Uffizzi does not work properly without a wildcard certificate.') - Uffizzi.ui.say('You can add wildcard cert later with command:') - Uffizzi.ui.say("uffizzi install wildcard-tls --domain #{domain} --cert /path/to/cert --key /path/to/key") + Uffizzi.ui.say(msg) - return {} - end + question = 'Okay to proceed?' + Uffizzi.ui.say_error_and_exit('Installation canceled') unless Uffizzi.prompt.yes?(question) + end - cert_path = Uffizzi.prompt.ask('Path to cert: ', required: true) - Uffizzi.ui.say_error_and_exit("File '#{cert_path}' does not exists") unless File.exist?(cert_path) + def update_and_continue? + msg = "\r\n"\ + 'You already have installation controller params. '\ + "\r\n"\ + 'You can update previous params and continue installation or cancel installation.'\ + "\r\n" - key_path = Uffizzi.prompt.ask('Path to key: ', required: true) - Uffizzi.ui.say_error_and_exit("File '#{key_path}' does not exists") unless File.exist?(key_path) + Uffizzi.ui.say(msg) - { wildcard_cert_path: cert_path, wildcard_key_path: key_path } + question = 'Do you want update the controller settings?' + Uffizzi.prompt.yes?(question) end - def ask_installation_params - namespace = Uffizzi.prompt.ask('Namespace: ', required: true, default: DEFAULT_NAMESPACE) - domain = Uffizzi.prompt.ask('Root domain: ', required: true, default: 'example.com') - user_email = Uffizzi.prompt.ask('First user email: ', required: true, default: "admin@#{domain}") - user_password = Uffizzi.prompt.ask('First user password: ', required: true, default: generate_password) - wildcard_cert_paths = ask_wildcard_cert(domain: domain) + def fetch_controller_settings + response = get_account_controller_settings(server, account_id) + return Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response) - { - namespace: namespace, - domain: domain, - user_email: user_email, - user_password: user_password, - controller_password: generate_password, - cert_email: user_email, - cluster_issuer: DEFAULT_CLUSTER_ISSUER, - }.merge(wildcard_cert_paths) + response.dig(:body, :controller_settings) end - def build_installation_options - { - namespace: options[:namespace] || DEFAULT_NAMESPACE, - domain: options[:domain], - user_email: options[:'user-email'] || "admin@#{options[:domain]}", - user_password: options[:'user-password'] || generate_password, - controller_password: generate_password, - cert_email: options[:'user-email'], - cluster_issuer: options[:issuer] || DEFAULT_CLUSTER_ISSUER, - } + def update_controller_settings(controller_setting_id, params) + response = update_account_controller_settings(server, account_id, controller_setting_id, params) + Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response) end - def build_helm_values(params) - domain = params.fetch(:domain) - namespace = params.fetch(:namespace) - app_host = [DEFAULT_APP_PREFIX, domain].join('.') + def create_controller_settings(params) + response = create_account_controller_settings(server, account_id, params) + Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.created?(response) + end + def build_controller_setting_params(uri, installation_options) { - 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: { - disabled: true, - }, - clusterIssuer: params.fetch(:cluster_issuer), - tlsPerDeploymentEnabled: true.to_s, - certEmail: params.fetch(:cert_email), - 'ingress-nginx' => { - controller: { - ingressClassResource: { - default: true, - }, - extraArgs: { - 'default-ssl-certificate' => "#{namespace}/wildcard.#{domain}", - }, - }, - }, - }, - }.deep_stringify_keys + url: URI::HTTPS.build(host: InstallService.build_controller_host(uri.host)).to_s, + managed_dns_zone: uri.host, + login: installation_options[:controller_username], + password: installation_options[:controller_password], + } end - def execute_command(command, say: true) - stdout_str, stderr_str, status = Uffizzi.ui.capture3(command) + def say_success(uri) + ip_address = wait_ip + wait_certificate_request_ready(uri) - return yield(stdout_str, stderr_str) if block_given? + msg = 'Your Uffizzi controller is ready. To configure DNS,'\ + " create a record for the hostname '*.#{uri.host}' pointing to '#{ip_address}'" + Uffizzi.ui.say(msg) + end - Uffizzi.ui.say_error_and_exit(stderr_str) unless status.success? + def parse_hostname(hostname) + uri = URI.parse(hostname) + host = uri.host || hostname - say ? Uffizzi.ui.say(stdout_str) : stdout_str - rescue Errno::ENOENT => e - Uffizzi.ui.say_error_and_exit(e.message) + case uri + when URI::HTTP, URI::HTTPS + uri + else + URI::HTTPS.build(host: host) + end 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) + def namespace + options[:namespace] || InstallService::DEFAULT_NAMESPACE end - def delete_helm_values_file - File.delete(helm_values_file_path) if File.exist?(helm_values_file_path) + def server + @server ||= ConfigFile.read_option(:server) end - def helm_values_file_path - File.join(helm_values_dir_path, VALUES_FILE_NAME) + def account_id + @account_id ||= ConfigFile.read_option(:account, :id) end - def helm_values_dir_path - File.dirname(Uffizzi::ConfigFile.config_path) + def account_name + @account_name ||= ConfigFile.read_option(:account, :name) end - def generate_password - hexatridecimal_base = 36 - length = 8 - rand(hexatridecimal_base**length).to_s(hexatridecimal_base) + def existing_controller_setting + @existing_controller_setting ||= fetch_controller_settings[0] end end end diff --git a/lib/uffizzi/cli/uninstall.rb b/lib/uffizzi/cli/uninstall.rb new file mode 100644 index 00000000..ce5d21c1 --- /dev/null +++ b/lib/uffizzi/cli/uninstall.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'uffizzi' +require 'uffizzi/config_file' +require 'uffizzi/services/install_service' +require 'uffizzi/services/kubeconfig_service' + +module Uffizzi + class Cli::Uninstall < Thor + include ApiClient + + default_task :controller + + desc 'controller [HOSTNMAE]', 'Install uffizzi controller to cluster' + method_option :namespace, type: :string + method_option :context, type: :string + def controller + Uffizzi::AuthHelper.check_login + + InstallService.kubectl_exists? + InstallService.helm_exists? + + if options[:context].present? && options[:context] != InstallService.kubeconfig_current_context + InstallService.set_current_context(options[:context]) + end + + ask_confirmation + delete_controller_settings + InstallService.helm_uninstall!(namespace) + + helm_unset_repo + end + + private + + def helm_unset_repo + return if InstallService.helm_repo_search.blank? + + InstallService.helm_repo_remove + end + + def ask_confirmation + msg = "This command will uninstall Uffizzi from the '#{namespace}'"\ + " namespace of the '#{InstallService.kubeconfig_current_context}' context."\ + "\r\n"\ + "To uninstall a different installation, use options '--namespace' and/or '--context'."\ + "\r\n\r\n"\ + "After uninstalling, new environments created for account '#{account_name}'"\ + "\r\n"\ + 'will be deployed to Uffizzi Cloud (app.uffizzi.com).'\ + "\r\n\r\n" + + Uffizzi.ui.say(msg) + + question = 'Okay to proceed?' + Uffizzi.ui.say_error_and_exit('Uninstallation canceled') unless Uffizzi.prompt.yes?(question) + end + + def fetch_controller_settings + response = get_account_controller_settings(server, account_id) + return Uffizzi::ResponseHelper.handle_failed_response(response) unless Uffizzi::ResponseHelper.ok?(response) + + response.dig(:body, :controller_settings) + end + + def delete_controller_settings + return if existing_controller_setting.blank? + + response = delete_account_controller_settings(server, account_id, existing_controller_setting[:id]) + + if ResponseHelper.no_content?(response) + Uffizzi.ui.say('Controller settings deleted') + else + ResponseHelper.handle_failed_response(response) + end + end + + def namespace + options[:namespace] || InstallService::DEFAULT_NAMESPACE + end + + def server + @server ||= ConfigFile.read_option(:server) + end + + def account_id + @account_id ||= ConfigFile.read_option(:account, :id) + end + + def account_name + @account_name ||= ConfigFile.read_option(:account, :name) + end + + def existing_controller_setting + @existing_controller_setting ||= fetch_controller_settings[0] + end + end +end diff --git a/lib/uffizzi/clients/api/api_client.rb b/lib/uffizzi/clients/api/api_client.rb index 58ab5a6f..361a513d 100644 --- a/lib/uffizzi/clients/api/api_client.rb +++ b/lib/uffizzi/clients/api/api_client.rb @@ -321,6 +321,34 @@ def get_account_clusters(server, account_id) build_response(response) end + def get_account_controller_settings(server, account_id) + uri = account_controller_settings_uri(server, account_id) + response = http_client.make_get_request(uri) + + build_response(response) + end + + def create_account_controller_settings(server, account_id, params = {}) + uri = account_controller_settings_uri(server, account_id) + response = http_client.make_post_request(uri, params) + + build_response(response) + end + + def update_account_controller_settings(server, account_id, id, params = {}) + uri = account_controller_setting_uri(server, account_id, id) + response = http_client.make_put_request(uri, params) + + build_response(response) + end + + def delete_account_controller_settings(server, account_id, id) + uri = account_controller_setting_uri(server, account_id, id) + response = http_client.make_delete_request(uri) + + build_response(response) + end + private def http_client diff --git a/lib/uffizzi/clients/api/api_routes.rb b/lib/uffizzi/clients/api/api_routes.rb index 2727ba0a..6ff4b82d 100644 --- a/lib/uffizzi/clients/api/api_routes.rb +++ b/lib/uffizzi/clients/api/api_routes.rb @@ -139,4 +139,12 @@ def browser_sign_in_url(server, session_id) def account_clusters_uri(server, account_id) "#{server}/api/cli/v1/accounts/#{account_id}/clusters" end + + def account_controller_settings_uri(server, account_id) + "#{server}/api/cli/v1/accounts/#{account_id}/controller_settings" + end + + def account_controller_setting_uri(server, account_id, id) + "#{server}/api/cli/v1/accounts/#{account_id}/controller_settings/#{id}" + end end diff --git a/lib/uffizzi/services/install_service.rb b/lib/uffizzi/services/install_service.rb new file mode 100644 index 00000000..a0a2c989 --- /dev/null +++ b/lib/uffizzi/services/install_service.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require 'uffizzi/response_helper' +require 'uffizzi/clients/api/api_client' + +class InstallService + DEFAULT_HELM_RELEASE_NAME = 'uffizzi' + INGRESS_NAME = "#{DEFAULT_HELM_RELEASE_NAME}-controller" + DEFAULT_HELM_REPO_NAME = 'uffizzi' + DEFAULT_CONTROLLER_CHART_NAME = 'uffizzi-controller' + HELM_DEPLOYED_STATUS = 'deployed' + VALUES_FILE_NAME = 'helm_values.yaml' + DEFAULT_NAMESPACE = 'default' + DEFAULT_CLUSTER_ISSUER = 'letsencrypt' + DEFAULT_CONTROLLER_REPO_URL = 'https://uffizzicloud.github.io/uffizzi_controller' + DEFAULT_CONTROLLER_DOMAIN_PREFIX = 'controller' + + class << self + include ApiClient + + 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_repo_remove + cmd = "helm repo remove #{DEFAULT_HELM_REPO_NAME}" + execute_command(cmd, skip_error: true) + end + + def helm_repo_search + cmd = "helm search repo #{DEFAULT_HELM_REPO_NAME}/#{DEFAULT_CONTROLLER_CHART_NAME} -o json" + + execute_command(cmd) do |result, err| + err.present? ? nil : JSON.parse(result) + end + end + + def helm_repo_add(repo_url) + repo_url = repo_url || DEFAULT_CONTROLLER_REPO_URL + cmd = "helm repo add #{DEFAULT_HELM_REPO_NAME} #{repo_url}" + execute_command(cmd) + end + + def helm_install!(namespace) + Uffizzi.ui.say('Start helm release installation') + + repo = "#{DEFAULT_HELM_REPO_NAME}/#{DEFAULT_CONTROLLER_CHART_NAME}" + cmd = "helm upgrade #{DEFAULT_HELM_RELEASE_NAME} #{repo}" \ + " --values #{helm_values_file_path}" \ + " --namespace #{namespace}" \ + ' --create-namespace' \ + ' --install' \ + ' --output json' + + res = execute_command(cmd, say: false) + info = JSON.parse(res)['info'] + + return if info['status'] == HELM_DEPLOYED_STATUS + + Uffizzi.ui.say_error_and_exit(info) + end + + def helm_uninstall!(namespace) + Uffizzi.ui.say('Start helm release uninstallation') + + cmd = "helm uninstall #{DEFAULT_HELM_RELEASE_NAME} --namespace #{namespace}" + + execute_command(cmd) + end + + def set_current_context(context) + cmd = "kubectl config use-context #{context}" + execute_command(cmd) + end + + def kubeconfig_current_context + cmd = 'kubectl config current-context' + + execute_command(cmd, say: false) { |stdout| stdout.present? && stdout.chop } + end + + def get_controller_ip(namespace) + cmd = "kubectl get ingress -n #{namespace} -o json" + res = execute_command(cmd, say: false) + ingress = JSON.parse(res)['items'].detect { |i| i['metadata']['name'] = INGRESS_NAME } + + return if ingress.blank? + + load_balancers = ingress.dig('status', 'loadBalancer', 'ingress') + return if load_balancers.blank? + + load_balancers.map { |i| i['ip'] }[0] + end + + def get_certificate_request(namespace, uri) + cmd = "kubectl get certificaterequests -n #{namespace} -o json" + res = execute_command(cmd, say: false) + certificate_request = JSON.parse(res)['items'].detect { |i| i['metadata']['name'].include?(uri.host) } + + return if certificate_request.nil? + + conditions = certificate_request.dig('status', 'conditions') || [] + conditions.map { |c| c.slice('type', 'status') } + end + + def build_controller_host(host) + [DEFAULT_CONTROLLER_DOMAIN_PREFIX, host].join('.') + end + + def delete_helm_values_file + File.delete(helm_values_file_path) if File.exist?(helm_values_file_path) + 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 + + private + + def execute_command(command, say: true, skip_error: false) + stdout_str, stderr_str, status = Uffizzi.ui.capture3(command) + + return yield(stdout_str, stderr_str) if block_given? + + if !status.success? && !skip_error + return Uffizzi.ui.say_error_and_exit(stderr_str) + end + + if !status.success? && skip_error + return Uffizzi.ui.say(stderr_str) + end + + say ? Uffizzi.ui.say(stdout_str) : stdout_str + rescue Errno::ENOENT => e + Uffizzi.ui.say_error_and_exit(e.message) + end + end +end diff --git a/test/fixtures/files/uffizzi/uffizzi_account_controller_settings.json b/test/fixtures/files/uffizzi/uffizzi_account_controller_settings.json new file mode 100644 index 00000000..53798468 --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_account_controller_settings.json @@ -0,0 +1,9 @@ +{ + "controller_settings": [ + { + "id": 1, + "url": "https://controller.my-host.com", + "managed_dns_zone": "my-host.com" + } + ] +} diff --git a/test/fixtures/files/uffizzi/uffizzi_account_controller_settings_empty.json b/test/fixtures/files/uffizzi/uffizzi_account_controller_settings_empty.json new file mode 100644 index 00000000..e7394711 --- /dev/null +++ b/test/fixtures/files/uffizzi/uffizzi_account_controller_settings_empty.json @@ -0,0 +1,3 @@ +{ + "controller_settings": [] +} diff --git a/test/support/mocks/mock_shell.rb b/test/support/mocks/mock_shell.rb index dc03dec7..ce1cf024 100644 --- a/test/support/mocks/mock_shell.rb +++ b/test/support/mocks/mock_shell.rb @@ -2,6 +2,7 @@ class MockShell class ExitError < StandardError; end + class MockProcessStatus def initialize(success) @success = success @@ -108,23 +109,6 @@ def promise_execute(command, stdout: nil, stderr: nil, waiter: nil) 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 diff --git a/test/support/uffizzi_stub_support.rb b/test/support/uffizzi_stub_support.rb index d343ea36..69e45cd2 100644 --- a/test/support/uffizzi_stub_support.rb +++ b/test/support/uffizzi_stub_support.rb @@ -181,13 +181,6 @@ def stub_uffizzi_create_cluster(body, project_slug) stub_request(:post, uri).to_return(status: 201, body: body.to_json) end - def stub_get_cluster_request(body, project_slug) - uri = cluster_uri(Uffizzi.configuration.server, project_slug, cluster_name: nil, oidc_token: nil) - uri = %r{#{uri}([A-Za-z0-9\-_]+)} - - stub_request(:get, uri).to_return(status: 200, body: body.to_json) - end - def stub_get_cluster_ingresses_request(body, project_slug, cluster_name) uri = project_cluster_ingresses_uri(Uffizzi.configuration.server, project_slug, cluster_name: cluster_name, oidc_token: nil) @@ -229,4 +222,28 @@ def stub_create_token_request(body) stub_request(:post, uri).to_return(status: 201, body: body.to_json) end + + def stub_get_account_controller_settings_request(body, account_id) + uri = account_controller_settings_uri(Uffizzi.configuration.server, account_id) + + stub_request(:get, uri).to_return(status: 200, body: body.to_json) + end + + def stub_create_account_controller_settings_request(body, account_id) + uri = account_controller_settings_uri(Uffizzi.configuration.server, account_id) + + stub_request(:post, uri).to_return(status: 201, body: body.to_json) + end + + def stub_update_account_controller_settings_request(body, account_id, controller_settings_id) + uri = account_controller_setting_uri(Uffizzi.configuration.server, account_id, controller_settings_id) + + stub_request(:put, uri).to_return(status: 200, body: body.to_json) + end + + def stub_delete_account_controller_settings_request(account_id, controller_settings_id) + uri = account_controller_setting_uri(Uffizzi.configuration.server, account_id, controller_settings_id) + + stub_request(:delete, uri).to_return(status: 204, body: '') + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 34128679..3764049a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -85,4 +85,8 @@ def command_options(options) def render_server_error(error) "#{Uffizzi::RESPONSE_SERVER_ERROR_HEADER}#{error}" end + + def tmp_dir_name + (Time.now.utc.to_f * 100_000).to_i + end end diff --git a/test/uffizzi/cli/install_test.rb b/test/uffizzi/cli/install_test.rb index 5971ca6f..c96e5f7d 100644 --- a/test/uffizzi/cli/install_test.rb +++ b/test/uffizzi/cli/install_test.rb @@ -8,44 +8,114 @@ class InstallTest < Minitest::Test def setup @install = Uffizzi::Cli::Install.new - tmp_dir_name = (Time.now.utc.to_f * 100_000).to_i - helm_values_path = "/tmp/test/#{tmp_dir_name}/helm_values.yaml" - Uffizzi::ConfigFile.stubs(:config_path).returns(helm_values_path) + sign_in + Uffizzi::ConfigFile.write_option(:project, 'uffizzi') + helm_values_dir_path = "/tmp/test/#{tmp_dir_name}" + InstallService.stubs(:helm_values_dir_path).returns(helm_values_dir_path) end - def test_install_by_wizard - @mock_prompt.promise_question_answer('Namespace: ', 'uffizzi') - @mock_prompt.promise_question_answer('Root domain: ', 'my-domain.com') - @mock_prompt.promise_question_answer('First user email: ', 'admin@my-domain.com') - @mock_prompt.promise_question_answer('First user password: ', 'password') - @mock_prompt.promise_question_answer('Uffizzi use a wildcard tls certificate. Do you have it?', 'n') + def test_install + host = 'my-host.com' + account_id = 1 @mock_shell.promise_execute(/kubectl version/, stdout: '1.23.00') @mock_shell.promise_execute(/helm version/, stdout: '3.00') @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') - @mock_shell.promise_execute(/helm list/, stdout: [].to_json) + @mock_shell.promise_execute(/kubectl config current-context/, stdout: 'my-context') @mock_shell.promise_execute(/helm upgrade/, stdout: { info: { status: 'deployed' } }.to_json) - @mock_shell.promise_execute(/kubectl get ingress/, stdout: { status: { loadBalancer: { ingress: [{ ip: '34.31.68.232' }] } } }.to_json) + ingress_answer = { + items: [ + metadata: { + name: InstallService::INGRESS_NAME, + }, + status: { + loadBalancer: { + ingress: [{ ip: '34.31.68.232' }], + }, + }, + ], + } - @install.application + cert_request_answer = { + items: [ + metadata: { + name: host, + }, + status: { + conditions: [ + { type: 'Approved', status: 'True' }, + { type: 'Ready', status: 'True' }, + ], + }, + ], + } + + @mock_shell.promise_execute(/kubectl get ingress/, stdout: ingress_answer.to_json) + @mock_shell.promise_execute(/kubectl get certificaterequests/, stdout: cert_request_answer.to_json) + @mock_prompt.promise_question_answer('Okay to proceed?', 'y') + + empty_body = json_fixture('files/uffizzi/uffizzi_account_controller_settings_empty.json') + stub_get_account_controller_settings_request(empty_body, account_id) + stub_create_account_controller_settings_request({}, account_id) + + @install.options = command_options(email: 'admin@my-domain.com') + @install.controller(host) last_message = Uffizzi.ui.last_message - assert_match('Create a DNS A record for domain', last_message) + assert_match('Your Uffizzi controller is ready', last_message) end - def test_install_by_options + def test_install_if_settgins_exists + host = 'my-host.com' + account_id = 1 + @mock_shell.promise_execute(/kubectl version/, stdout: '1.23.00') @mock_shell.promise_execute(/helm version/, stdout: '3.00') @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') + @mock_shell.promise_execute(/kubectl config current-context/, stdout: 'my-context') @mock_shell.promise_execute(/helm upgrade/, stdout: { info: { status: 'deployed' } }.to_json) - @mock_shell.promise_execute(/kubectl get ingress/, stdout: { status: { loadBalancer: { ingress: [{ ip: '34.31.68.232' }] } } }.to_json) + ingress_answer = { + items: [ + metadata: { + name: InstallService::INGRESS_NAME, + }, + status: { + loadBalancer: { + ingress: [{ ip: '34.31.68.232' }], + }, + }, + ], + } + + cert_request_answer = { + items: [ + metadata: { + name: host, + }, + status: { + conditions: [ + { type: 'Approved', status: 'True' }, + { type: 'Ready', status: 'True' }, + ], + }, + ], + } + + @mock_shell.promise_execute(/kubectl get ingress/, stdout: ingress_answer.to_json) + @mock_shell.promise_execute(/kubectl get certificaterequests/, stdout: cert_request_answer.to_json) + @mock_prompt.promise_question_answer('Okay to proceed?', 'y') + @mock_prompt.promise_question_answer('Do you want update the controller settings?', 'y') + + body = json_fixture('files/uffizzi/uffizzi_account_controller_settings.json') + stub_get_account_controller_settings_request(body, account_id) + stub_update_account_controller_settings_request(body, account_id, body[:controller_settings][0][:id]) - @install.options = command_options(domain: 'my-domain.com', 'without-wildcard-tls' => true) - @install.application + @install.options = command_options(email: 'admin@my-domain.com') + @install.controller(host) last_message = Uffizzi.ui.last_message - assert_match('Create a DNS A record for domain', last_message) + assert_match('Your Uffizzi controller is ready', last_message) end end diff --git a/test/uffizzi/cli/uninstall_test.rb b/test/uffizzi/cli/uninstall_test.rb new file mode 100644 index 00000000..0dfd3ec6 --- /dev/null +++ b/test/uffizzi/cli/uninstall_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'byebug' +require 'psych' +require 'base64' +require 'test_helper' + +class UninstallTest < Minitest::Test + def setup + @uninstall = Uffizzi::Cli::Uninstall.new + + sign_in + Uffizzi::ConfigFile.write_option(:project, 'uffizzi') + helm_values_dir_path = "/tmp/test/#{tmp_dir_name}" + InstallService.stubs(:helm_values_dir_path).returns(helm_values_dir_path) + end + + def test_uninstall + account_id = 1 + + @mock_shell.promise_execute(/kubectl version/, stdout: '1.23.00') + @mock_shell.promise_execute(/helm version/, stdout: '3.00') + @mock_shell.promise_execute(/helm search repo/, stdout: [].to_json) + @mock_shell.promise_execute(/helm repo add/, stdout: 'ok') + @mock_shell.promise_execute(/kubectl config current-context/, stdout: 'my-context') + @mock_shell.promise_execute(/helm uninstall/, stdout: 'Helm release is uninstalled') + @mock_prompt.promise_question_answer('Okay to proceed?', 'y') + + body = json_fixture('files/uffizzi/uffizzi_account_controller_settings.json') + stub_get_account_controller_settings_request(body, account_id) + stub_delete_account_controller_settings_request(account_id, body[:controller_settings][0][:id]) + + @uninstall.controller + + last_message = Uffizzi.ui.last_message + assert_match('Helm release is uninstalled', last_message) + end +end From 8583a31029236a1abab6b93151ee3057d3c2da41 Mon Sep 17 00:00:00 2001 From: Zipofar Date: Fri, 24 Nov 2023 16:41:38 +0300 Subject: [PATCH 09/10] [374] Add controller installation --- lib/uffizzi/services/install_service.rb | 2 +- man/uffizzi | 2 +- man/uffizzi-cluster | 8 ++- man/uffizzi-cluster-create | 2 +- man/uffizzi-cluster-delete | 2 +- man/uffizzi-cluster-disconnect | 2 +- man/uffizzi-cluster-sleep | 39 +++++++++++ man/uffizzi-cluster-wake | 37 +++++++++++ man/uffizzi-dev | 2 +- man/uffizzi-dev-delete | 33 ++++++++++ man/uffizzi-dev-describe | 10 +-- man/uffizzi-dev-ingress | 2 +- man/uffizzi-dev-ingress-open | 2 +- man/uffizzi-dev-start | 15 +---- man/uffizzi-dev-stop | 24 ++----- man/uffizzi-install | 87 ++++++++++--------------- man/uffizzi-install.ronn | 79 +++++++++------------- man/uffizzi-login | 2 +- test/support/uffizzi_stub_support.rb | 7 ++ 19 files changed, 206 insertions(+), 151 deletions(-) create mode 100644 man/uffizzi-cluster-sleep create mode 100644 man/uffizzi-cluster-wake create mode 100644 man/uffizzi-dev-delete diff --git a/lib/uffizzi/services/install_service.rb b/lib/uffizzi/services/install_service.rb index a0a2c989..032bf613 100644 --- a/lib/uffizzi/services/install_service.rb +++ b/lib/uffizzi/services/install_service.rb @@ -103,7 +103,7 @@ def get_certificate_request(namespace, uri) res = execute_command(cmd, say: false) certificate_request = JSON.parse(res)['items'].detect { |i| i['metadata']['name'].include?(uri.host) } - return if certificate_request.nil? + return [] if certificate_request.nil? conditions = certificate_request.dig('status', 'conditions') || [] conditions.map { |c| c.slice('type', 'status') } diff --git a/man/uffizzi b/man/uffizzi index 283a84c3..2ca8a738 100644 --- a/man/uffizzi +++ b/man/uffizzi @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI" "" "October 2023" "" +.TH "UFFIZZI" "" "November 2023" "" .SH "NAME" \fBuffizzi\fR \- manage Uffizzi resources .SH "SYNOPSIS" diff --git a/man/uffizzi-cluster b/man/uffizzi-cluster index 4491c017..7ec2b05e 100644 --- a/man/uffizzi-cluster +++ b/man/uffizzi-cluster @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-CLUSTER" "" "August 2023" "" +.TH "UFFIZZI\-CLUSTER" "" "November 2023" "" .SH "NAME" \fBuffizzi\-cluster\fR \- manage clusters .SH "SYNOPSIS" @@ -30,6 +30,12 @@ COMMAND is one of the following: list List all clusters + sleep + Put a cluster to sleep (non\-destructive) + + wake + Wake a cluster that is sleeping + update\-kubeconfig Update kubeconfig file .fi diff --git a/man/uffizzi-cluster-create b/man/uffizzi-cluster-create index 0cb70113..6b1b0b45 100644 --- a/man/uffizzi-cluster-create +++ b/man/uffizzi-cluster-create @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-CLUSTER\-CREATE" "" "October 2023" "" +.TH "UFFIZZI\-CLUSTER\-CREATE" "" "November 2023" "" .SH "NAME" \fBuffizzi\-cluster\-create\fR .P diff --git a/man/uffizzi-cluster-delete b/man/uffizzi-cluster-delete index 0326fa5d..e9da28e0 100644 --- a/man/uffizzi-cluster-delete +++ b/man/uffizzi-cluster-delete @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-CLUSTER\-DELETE" "" "September 2023" "" +.TH "UFFIZZI\-CLUSTER\-DELETE" "" "November 2023" "" .SH "NAME" \fBuffizzi\-cluster\-delete\fR \- delete a cluster .SH "SYNOPSIS" diff --git a/man/uffizzi-cluster-disconnect b/man/uffizzi-cluster-disconnect index 73d20a72..b84e8eaa 100644 --- a/man/uffizzi-cluster-disconnect +++ b/man/uffizzi-cluster-disconnect @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-CLUSTER\-DISCONNECT" "" "September 2023" "" +.TH "UFFIZZI\-CLUSTER\-DISCONNECT" "" "November 2023" "" .SH "NAME" \fBuffizzi\-cluster\-disconnect\fR \- disconnect from current cluster context .SH "SYNOPSIS" diff --git a/man/uffizzi-cluster-sleep b/man/uffizzi-cluster-sleep new file mode 100644 index 00000000..dafe1a47 --- /dev/null +++ b/man/uffizzi-cluster-sleep @@ -0,0 +1,39 @@ +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "UFFIZZI\-CLUSTER\-SLEEP" "" "November 2023" "" +.SH "NAME" +\fBuffizzi\-cluster\-sleep\fR +.P +$ uffizzi cluster sleep \-h uffizzi\-cluster\-sleep \- put a cluster to sleep (non\-destructive) ================================================================ +.SH "SYNOPSIS" +.nf +uffizzi cluster sleep [CLUSTER_NAME] +.fi +.SH "DESCRIPTION" +.nf +Scales a Uffizzi cluster down to zero resource utilization +while keeping the namespace and any stateful resources, +like persistent volume claims\. If no CLUSTER_NAME is +specified, the kubeconfig current context is used\. + +For more information on Uffizzi clusters, see: +https://docs\.uffizzi\.com/references/cli/ +.fi +.SH "OPTIONS" +.nf +CLUSTER_NAME +The name of the target Uffizzi cluster +.fi +.SH "EXAMPLES" +.nf +To put the Uffizzi cluster in the current context to +sleep, run: + + $ uffizzi cluster sleep + +To put a Uffizzi cluster outside the current context to +sleep, run: + + $ uffizzi cluster sleep my\-cluster +.fi + diff --git a/man/uffizzi-cluster-wake b/man/uffizzi-cluster-wake new file mode 100644 index 00000000..9c244b82 --- /dev/null +++ b/man/uffizzi-cluster-wake @@ -0,0 +1,37 @@ +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "UFFIZZI\-CLUSTER\-WAKE" "" "November 2023" "" +.SH "NAME" +\fBuffizzi\-cluster\-wake\fR +.P +$ uffizzi cluster wake \-h uffizzi\-cluster\-wake \- wake a cluster that is sleeping ================================================================ +.SH "SYNOPSIS" +.nf +uffizzi cluster wake [CLUSTER_NAME] +.fi +.SH "DESCRIPTION" +.nf +Scales up a Uffizzi cluster to its original resource +utilization from zero (see \'uffizzi cluster sleep \-h\')\. +If no CLUSTER_NAME is specified, the kubeconfig current +context is used\. + +For more information on Uffizzi clusters, see: +https://docs\.uffizzi\.com/references/cli/ +.fi +.SH "OPTIONS" +.nf +CLUSTER_NAME +The name of the target Uffizzi cluster +.fi +.SH "EXAMPLES" +.nf +To wake the Uffizzi cluster in the current context, run: + + $ uffizzi cluster wake + +To wake a Uffizzi cluster outside the current context, run: + + $ uffizzi cluster wake my\-cluster +.fi + diff --git a/man/uffizzi-dev b/man/uffizzi-dev index 3f893ca1..12816dab 100644 --- a/man/uffizzi-dev +++ b/man/uffizzi-dev @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV" "" "October 2023" "" +.TH "UFFIZZI\-DEV" "" "November 2023" "" .SH "NAME" \fBuffizzi\-dev\fR \- manage dev environments .SH "SYNOPSIS" diff --git a/man/uffizzi-dev-delete b/man/uffizzi-dev-delete new file mode 100644 index 00000000..285d44fc --- /dev/null +++ b/man/uffizzi-dev-delete @@ -0,0 +1,33 @@ +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "UFFIZZI\-DEV\-DELETE" "" "November 2023" "" +.SH "NAME" +\fBuffizzi\-dev\-delete\fR +.P +uffizzi dev delete \-h uffizzi\-dev\-delete \- delete a development environment ================================================================ +.SH "SYNOPSIS" +.nf +uffizzi dev delete +.fi +.SH "DESCRIPTION" +.nf +Deletes a dev environment and associated Uffizzi +cluster resources, including any persistent +volumes, and the namespace itself\. The Uffizzi +cluster config is deleted from the kubeconfig file\. + +For more information on Uffizzi clusters, see: +https://docs\.uffizzi\.com/references/cli/ +.fi +.SH "FLAGS" +.nf + \-\-help, \-h + Show this message and exit\. +.fi +.SH "EXAMPLES" +.nf +To delete a dev environment, run: + + $ uffizzi dev delete +.fi + diff --git a/man/uffizzi-dev-describe b/man/uffizzi-dev-describe index 8287b3bf..cfecbb71 100644 --- a/man/uffizzi-dev-describe +++ b/man/uffizzi-dev-describe @@ -1,13 +1,13 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV\-DESCRIBE" "" "October 2023" "" +.TH "UFFIZZI\-DEV\-DESCRIBE" "" "November 2023" "" .SH "NAME" \fBuffizzi\-dev\-describe\fR .P $ uffizzi dev describe \-h uffizzi\-dev\-describe \- show metadata for a dev environment ================================================================ .SH "SYNOPSIS" .nf -uffizzi dev describe +uffizzi dev describe .fi .SH "DESCRIPTION" .nf @@ -20,12 +20,6 @@ This command can fail for the following reasons: For more information on Uffizzi clusters, see: https://docs\.uffizzi\.com/references/cli/ .fi -.SH "POSITIONAL ARGUMENTS" -.nf -[NAME] - NAME for the dev environment you want to describe\. - This is an optional argument\. -.fi .SH "EXAMPLES" .nf The following command prints metadata for the dev diff --git a/man/uffizzi-dev-ingress b/man/uffizzi-dev-ingress index 4c70e01a..489e179b 100644 --- a/man/uffizzi-dev-ingress +++ b/man/uffizzi-dev-ingress @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV\-INGRESS" "" "October 2023" "" +.TH "UFFIZZI\-DEV\-INGRESS" "" "November 2023" "" .SH "NAME" \fBuffizzi\-dev\-ingress\fR .P diff --git a/man/uffizzi-dev-ingress-open b/man/uffizzi-dev-ingress-open index c657e07f..fbdd74d2 100644 --- a/man/uffizzi-dev-ingress-open +++ b/man/uffizzi-dev-ingress-open @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV\-INGRESS\-OPEN" "" "October 2023" "" +.TH "UFFIZZI\-DEV\-INGRESS\-OPEN" "" "November 2023" "" .SH "NAME" \fBuffizzi\-dev\-ingress\-open\fR .P diff --git a/man/uffizzi-dev-start b/man/uffizzi-dev-start index 86bcf5d3..5e5a86cd 100644 --- a/man/uffizzi-dev-start +++ b/man/uffizzi-dev-start @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV\-START" "" "October 2023" "" +.TH "UFFIZZI\-DEV\-START" "" "November 2023" "" .SH "NAME" \fBuffizzi\-dev\-start\fR \- start a development environment .SH "SYNOPSIS" @@ -24,8 +24,6 @@ path CONFIG_FILE\. Skaffold configurations are currently supported\. For help creating a skaffold\.yaml file, see: https://skaffold\.dev/docs/init/ -If a kubeconfig exists - For more information on Uffizzi clusters, see: https://docs\.uffizzi\.com/references/cli/ .fi @@ -80,17 +78,6 @@ run: $ uffizzi dev start \-\-quiet -To push your build artifacts to a private Docker Hub repo -called \'acme/foo\', first add your Docker Hub credentials: - - $ uffizzi connect docker\-hub - (See `uffizzi connect \-h` for other registry options) - -\|\.\|\.\|\.then override the default repo: - - $ uffizzi dev start \e - \-\-default\-repo="hub\.docker\.com/acme/foo" - To start a dev environment using an alternate kubeconfig file, run: diff --git a/man/uffizzi-dev-stop b/man/uffizzi-dev-stop index cf105533..90929993 100644 --- a/man/uffizzi-dev-stop +++ b/man/uffizzi-dev-stop @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV\-STOP" "" "October 2023" "" +.TH "UFFIZZI\-DEV\-STOP" "" "November 2023" "" .SH "NAME" \fBuffizzi\-dev\-stop\fR \- stop a development environment .SH "SYNOPSIS" @@ -9,23 +9,11 @@ uffizzi dev stop .fi .SH "DESCRIPTION" .nf -Stops a dev environment and deletes the backing -Uffizzi cluster resources, including any persistent -volumes, and the namespace itself\. The Uffizzi -cluster config is deleted from the kubeconfig file\. - -This command watches for file changes in a given local -project directory, as specified in your configuration file\. -It then serializes those changes and redeploys them onto -a Uffizzi cluster\. - -The command looks for a configuration at the specified -path CONFIG_FILE\. Skaffold configurations are currently -supported\. For help creating a skaffold\.yaml file, see: -https://skaffold\.dev/docs/init/ - -For more information on Uffizzi clusters, see: -https://docs\.uffizzi\.com/references/cli/ +Stops the skaffold process for the dev environment\. +This command does not delete the dev cluster or any +associated resources\. You can restart the dev environment +with `uffizzi dev start`\. To delete the dev cluster entirely, see +`uffizzi dev delete`\. .fi .SH "FLAGS" .nf diff --git a/man/uffizzi-install b/man/uffizzi-install index a13338b5..d73c07ca 100644 --- a/man/uffizzi-install +++ b/man/uffizzi-install @@ -1,76 +1,57 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "INSTALL" "" "September 2023" "" +.TH "UFFIZZI\-INSTALL" "" "November 2023" "" .SH "NAME" -\fBinstall\fR \- install the Uffizzi application to cluster +\fBuffizzi\-install\fR +.P +$ uffizzi install help uffizzi\-install \- install the uffizzi platform ================================================================ .SH "SYNOPSIS" .nf -uffizzi install COMMAND +uffizzi install [HOSTNAME] .fi .SH "DESCRIPTION" .nf -The uffizzi install command lets you deploy uffizzi application to your kubecrnetes cluster\. -If COMMAND is not specified, uffizzi install start installation\. -if OPTIONS not specified, uffizzi show installation wizard\. +Install the Uffizzi platform on a host cluster\. By default, this command uses your +kubeconfig current context as the host\. -For more information on configuration options, see: -https://docs\.uffizzi\.com/references/cli/ -.fi -.SH "COMMANDS" -.nf -COMMAND is one of the following: - - wildcard_tls OPTION - Add the wildcard tls certificate to installed uffizzi application\. -.fi -.SH "OPTIONS" -.nf - OPTION is one of the following: - - namespace - The namespace of the kubecrnetes cluster where application will be deployed\. - Default is uffizzi\. +The output of this command is an IP address or hostname where your instance of +the Uffizzi controller service is available\. Uffizzi expects this service to be publicly +available at the specified HOSTNAME\. Before you can create Uffizzi environments +on your installation, be sure to configure your DNS to point HOSTNAME to the IP +or hostname output by this command\. - domain - The domain that will be used for access the web API\. +If you\'re looking for an air\-gapped or local installation of Uffizzi, please contact +sales@uffizzi\.com or try the open\-source version (docs\.uffizzi\.com/open\-source)\. - issuer - The cluster issuer that will be used for generate tls certificates\. - Default is letsencrypt\. +EMAIL is a business email, required for letsencrypt cert authority\. - user\-email - The login that will be used for access to web API\. - - user\-password - The password that will be used for access to web API\. - - acme\-email - Email address for ACME registration - - wildcard\-cert\-path - Path to wildcard certificate\. - - wildcard\-key\-path - Path to wildcard certificate key\. +For more information on the Uffizzi installation process, see: +https://docs\.uffizzi\.com/cli/commands/install +.fi +.SH "FLAGS" +.nf +\-\-email + A business email required for letsencrypt - without\-wildcard\-tls - Set this flag and we can install application without wildcard certificate\. + \-\-context + The name of the kubeconfig context to use - print\-values - Show builded vales for helm installation\. - The installation will not be executed\. + \-\-namespace + The namespace where Uffizzi platform will be installed - repo - The repository that will be used for helm install +\-\-help + Display this help page and exit .fi .SH "EXAMPLES" .nf -To install the uffizzi command, run: +To install Uffizzi using the current context at hostname +\'uffizzi\.example\.com\', run: - $ uffizzi install + $ uffizzi install uffizzi\.example\.com \-\-email="jdoe@example\.com" -To install the wildcard_tls command, run: +To install Uffizzi using context \'foo\' and namespace \'bar\', run: - $ uffizzi install wildcard_tls + $ uffizzi install uffizzi\.example\.com \-\-email="jdoe@example\.com" \e + \-\-context=\'foo\' \-\-namespace=\'bar\' .fi diff --git a/man/uffizzi-install.ronn b/man/uffizzi-install.ronn index c9f6cf62..d4022c81 100644 --- a/man/uffizzi-install.ronn +++ b/man/uffizzi-install.ronn @@ -1,67 +1,50 @@ -uffizzi install - install the Uffizzi application to cluster +$ uffizzi install help +uffizzi-install - install the uffizzi platform ================================================================ ## SYNOPSIS - uffizzi install COMMAND + uffizzi install [HOSTNAME] ## DESCRIPTION - The uffizzi install command lets you deploy uffizzi application to your kubecrnetes cluster. - If COMMAND is not specified, uffizzi install start installation. - if OPTIONS not specified, uffizzi show installation wizard. + Install the Uffizzi platform on a host cluster. By default, this command uses your + kubeconfig current context as the host. - For more information on configuration options, see: - https://docs.uffizzi.com/references/cli/ + The output of this command is an IP address or hostname where your instance of + the Uffizzi controller service is available. Uffizzi expects this service to be publicly + available at the specified HOSTNAME. Before you can create Uffizzi environments + on your installation, be sure to configure your DNS to point HOSTNAME to the IP + or hostname output by this command. -## COMMANDS - COMMAND is one of the following: + If you're looking for an air-gapped or local installation of Uffizzi, please contact + sales@uffizzi.com or try the open-source version (docs.uffizzi.com/open-source). - wildcard_tls OPTION - Add the wildcard tls certificate to installed uffizzi application. + EMAIL is a business email, required for letsencrypt cert authority. -## OPTIONS - OPTION is one of the following: + For more information on the Uffizzi installation process, see: + https://docs.uffizzi.com/cli/commands/install - namespace - The namespace of the kubecrnetes cluster where application will be deployed. - Default is uffizzi. +## FLAGS - domain - The domain that will be used for access the web API. + --email + A business email required for letsencrypt - issuer - The cluster issuer that will be used for generate tls certificates. - Default is letsencrypt. + --context + The name of the kubeconfig context to use - user-email - The login that will be used for access to web API. + --namespace + The namespace where Uffizzi platform will be installed - user-password - The password that will be used for access to web API. - - acme-email - Email address for ACME registration - - wildcard-cert-path - Path to wildcard certificate. - - wildcard-key-path - Path to wildcard certificate key. - - without-wildcard-tls - Set this flag and we can install application without wildcard certificate. - - print-values - Show builded vales for helm installation. - The installation will not be executed. - - repo - The repository that will be used for helm install + --help + Display this help page and exit ## EXAMPLES - To install the uffizzi command, run: - $ uffizzi install + To install Uffizzi using the current context at hostname + 'uffizzi.example.com', run: + + $ uffizzi install uffizzi.example.com --email="jdoe@example.com" - To install the wildcard_tls command, run: + To install Uffizzi using context 'foo' and namespace 'bar', run: - $ uffizzi install wildcard_tls + $ uffizzi install uffizzi.example.com --email="jdoe@example.com" \ + --context='foo' --namespace='bar' diff --git a/man/uffizzi-login b/man/uffizzi-login index 37c47034..6a7caf04 100644 --- a/man/uffizzi-login +++ b/man/uffizzi-login @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-LOGIN" "" "September 2023" "" +.TH "UFFIZZI\-LOGIN" "" "November 2023" "" .SH "NAME" \fBuffizzi\-login\fR \- login to Uffizzi to view and manage your previews\. .SH "SYNOPSIS" diff --git a/test/support/uffizzi_stub_support.rb b/test/support/uffizzi_stub_support.rb index 69e45cd2..e003fc8d 100644 --- a/test/support/uffizzi_stub_support.rb +++ b/test/support/uffizzi_stub_support.rb @@ -181,6 +181,13 @@ def stub_uffizzi_create_cluster(body, project_slug) stub_request(:post, uri).to_return(status: 201, body: body.to_json) end + def stub_get_cluster_request(body, project_slug) + uri = cluster_uri(Uffizzi.configuration.server, project_slug, cluster_name: nil, oidc_token: nil) + uri = %r{#{uri}([A-Za-z0-9\-_]+)} + + stub_request(:get, uri).to_return(status: 200, body: body.to_json) + end + def stub_get_cluster_ingresses_request(body, project_slug, cluster_name) uri = project_cluster_ingresses_uri(Uffizzi.configuration.server, project_slug, cluster_name: cluster_name, oidc_token: nil) From 158fa7472a057b838b4cc854fbe19f7ce34c6470 Mon Sep 17 00:00:00 2001 From: Zipofar Date: Tue, 28 Nov 2023 15:12:37 +0300 Subject: [PATCH 10/10] Change version to 2.3.4 --- Gemfile.lock | 8 ++++---- lib/uffizzi/version.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8d2db630..95301ddf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - uffizzi-cli (2.3.3) + uffizzi-cli (2.3.4) activesupport awesome_print faker @@ -36,7 +36,7 @@ GEM factory_bot (6.2.0) activesupport (>= 5.0.0) fakefs (1.8.0) - faker (3.2.1) + faker (3.2.2) i18n (>= 1.8.11, < 2) hashdiff (1.0.1) i18n (1.8.11) @@ -99,8 +99,8 @@ GEM rubocop-rake (0.6.0) rubocop (~> 1.0) ruby-progressbar (1.11.0) - securerandom (0.2.2) - sentry-ruby (5.12.0) + securerandom (0.3.0) + sentry-ruby (5.14.0) concurrent-ruby (~> 1.0, >= 1.0.2) thor (1.3.0) tty-color (0.6.0) diff --git a/lib/uffizzi/version.rb b/lib/uffizzi/version.rb index c1524f71..3ed4ca3b 100644 --- a/lib/uffizzi/version.rb +++ b/lib/uffizzi/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Uffizzi - VERSION = '2.3.3' + VERSION = '2.3.4' end