From e7dafc5ef19ab1fe65909723b03de41a1061778f Mon Sep 17 00:00:00 2001 From: Zipofar Date: Thu, 28 Sep 2023 16:28:12 +0300 Subject: [PATCH 1/6] [340] Add detach mode --- lib/uffizzi.rb | 4 ++ lib/uffizzi/cli/dev.rb | 43 ++++++++++++++- lib/uffizzi/config_file.rb | 1 + lib/uffizzi/services/dev_service.rb | 55 +++++++++++++++++++ man/uffizzi-dev-start.ronn | 84 ++++++++++++++++++++++++++--- man/uffizzi-dev-stop.ronn | 33 ++++++++++++ test/support/mocks/mock_process.rb | 26 +++++++++ test/test_helper.rb | 10 ++++ test/uffizzi/cli/dev_test.rb | 35 ++++++++++++ 9 files changed, 281 insertions(+), 10 deletions(-) create mode 100644 lib/uffizzi/services/dev_service.rb create mode 100644 man/uffizzi-dev-stop.ronn create mode 100644 test/support/mocks/mock_process.rb diff --git a/lib/uffizzi.rb b/lib/uffizzi.rb index e557536c..0326dad6 100644 --- a/lib/uffizzi.rb +++ b/lib/uffizzi.rb @@ -36,5 +36,9 @@ def prompt def root @root ||= Pathname.new(File.expand_path('..', __dir__)) end + + def process + @process ||= Process + end end end diff --git a/lib/uffizzi/cli/dev.rb b/lib/uffizzi/cli/dev.rb index f6fe6eed..3937a1bf 100644 --- a/lib/uffizzi/cli/dev.rb +++ b/lib/uffizzi/cli/dev.rb @@ -2,6 +2,7 @@ require 'uffizzi/services/command_service' require 'uffizzi/services/cluster_service' +require 'uffizzi/services/dev_service' require 'uffizzi/services/kubeconfig_service' module Uffizzi @@ -9,12 +10,19 @@ class Cli::Dev < Thor include ApiClient desc 'start [CONFIG]', 'Start dev environment' + method_option :detach, type: :boolean, aliases: :d def start(config_path = 'skaffold.yaml') check_skaffold_existence check_login + DevService.check_running_daemon if options[:detach] cluster_id, cluster_name = start_create_cluster kubeconfig = wait_cluster_creation(cluster_name) - launch_scaffold(config_path) + + if options[:detach] + launch_demonise_skaffold(config_path) + else + launch_scaffold(config_path) + end ensure if defined?(cluster_name).present? && defined?(cluster_id).present? kubeconfig = defined?(kubeconfig).present? ? kubeconfig : nil @@ -22,6 +30,20 @@ def start(config_path = 'skaffold.yaml') end end + desc 'stop', 'Stop dev environment' + def stop + return Uffizzi.ui.say('Uffizzi dev is not running') unless File.exist?(DevService.pid_path) + + pid = File.read(DevService.pid_path).to_i + File.delete(DevService.pid_path) + + Uffizzi.process.kill('QUIT', pid) + Uffizzi.ui.say('Uffizzi dev was stopped') + rescue Errno::ESRCH + Uffizzi.ui.say('Uffizzi dev is not running') + File.delete(DevService.pid_path) + end + private def check_login @@ -99,6 +121,8 @@ def cluster_creation_params(name, creation_source) end def handle_delete_cluster(cluster_id, cluster_name, kubeconfig) + return if cluster_id.nil? || cluster_name.nil? + exclude_kubeconfig(cluster_id, kubeconfig) if kubeconfig.present? params = { @@ -149,12 +173,27 @@ def parse_kubeconfig(kubeconfig) Psych.safe_load(Base64.decode64(kubeconfig)) end + def launch_demonise_skaffold(config_path) + DevService.check_running_daemon + File.delete(DevService.logs_path) if File.exist?(DevService.logs_path) + + Uffizzi.process.daemon + File.write(DevService.pid_path, Uffizzi.process.pid) + DevService.start_check_pid_file_existence + + at_exit do + File.delete(DevService.pid_path) if File.exist?(DevService.pid_path) + end + + DevService.start_demonised_skaffold(config_path) + end + def launch_scaffold(config_path) Uffizzi.ui.say('Start skaffold') cmd = "skaffold dev --filename='#{config_path}'" Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr| - stdout_and_stderr.each { |l| puts l } + stdout_and_stderr.each { |l| Uffizzi.ui.say(l) } wait_thr.value end end diff --git a/lib/uffizzi/config_file.rb b/lib/uffizzi/config_file.rb index 4b84694e..c5c3663c 100644 --- a/lib/uffizzi/config_file.rb +++ b/lib/uffizzi/config_file.rb @@ -5,6 +5,7 @@ module Uffizzi class ConfigFile + CONFIG_DIR = "#{Dir.home}/.config/uffizzi" CONFIG_PATH = "#{Dir.home}/.config/uffizzi/config_default.json" class << self diff --git a/lib/uffizzi/services/dev_service.rb b/lib/uffizzi/services/dev_service.rb new file mode 100644 index 00000000..304d913c --- /dev/null +++ b/lib/uffizzi/services/dev_service.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'uffizzi/clients/api/api_client' + +class DevService + class << self + include ApiClient + + def check_running_daemon + return unless File.exist?(pid_path) + + pid = File.read(pid_path) + File.delete(pid_path) if pid.blank? + Uffizzi.process.kill(0, pid.to_i) + + Uffizzi.ui.say_error_and_exit("You already start uffizzi dev as daemon. To stop process do 'uffizzi dev stop'") + rescue Errno::ESRCH + File.delete(pid_path) + end + + def start_check_pid_file_existence + Thread.new do + loop do + Uffizzi.process.kill('QUIT', Uffizzi.process.pid) unless File.exist?(pid_path) + sleep(1) + end + end + end + + def start_demonised_skaffold(config_path) + File.write(logs_path, "Start skaffold\n") + + cmd = "skaffold dev --filename='#{config_path}'" + + Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr| + File.open(logs_path, 'a') do |f| + stdout_and_stderr.each do |line| + f.puts(line) + f.flush + end + end + + wait_thr.value + end + end + + def pid_path + File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'uffizzi_dev.pid') + end + + def logs_path + File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'uffizzi_dev.log') + end + end +end diff --git a/man/uffizzi-dev-start.ronn b/man/uffizzi-dev-start.ronn index 5a48719e..2a65b56c 100644 --- a/man/uffizzi-dev-start.ronn +++ b/man/uffizzi-dev-start.ronn @@ -1,22 +1,90 @@ -uffizzi-dev-start - start development environment +uffizzi-dev-start - start a development environment ================================================================ ## SYNOPSIS - uffizzi dev start [SKAFFOLD_CONFIG_FILE_PATH] + uffizzi dev start [CONFIG_FILE] [FLAGS] ## DESCRIPTION - Creates a new cluster and start skaffold. If no SKAFFOLD_CONFIG_FILE_PATH is specified, - the default path will be used. - Default SKAFFOLD_CONFIG_FILE_PATH is 'skaffold.yaml' + Creates a Uffizzi cluster preconfigured for development + workflows, including building, pushing, and deploying + your changes every time project files are saved. + current-context is updated in 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/ + + If a kubeconfig exists For more information on Uffizzi clusters, see: https://docs.uffizzi.com/references/cli/ +## POSITIONAL ARGUMENTS + [CONFIG_FILE] + Path to the development environment configuration file. + Currently supports skaffold.yaml files. + +## FLAGS + --build="" + This option specifies whether to build images on the + local environment or on the remote Uffizzi cluster. + Possible values are "local" or "remote". + + --default-repo="" + A public or private repo used to push/pull build + artifacts. Overrides the global default image registry: + "registry.uffizzi.com". See `uffizzi connect -h` for + adding private registry credentials. + + --detach, -d + Run the development process in detached mode (i.e., in + the background). Without this option, logs are streamed + to the terminal in the foreground. Run 'uffizzi dev stop` + to stop the detached process. + + --help, -h + Show this message and exit. + + --kubeconfig="/path/to/your/kubeconfig" + Path to kubeconfig file. If this option is not specified, + this command looks for the file at ~/.kube/config. + ## EXAMPLES - To start development environment, run: + If your configuration file is in the current working + directory and you want to use an auto-generated name, + run: $ uffizzi dev start - To start development environment with custom skaffold.yaml file, run: + To start a dev environment using a skaffold.yaml config + file in directory '~/foo', run: + + $ uffizzi dev start ~/foo/skaffold.yaml + + To start a dev environment in detached mode, + run: + + $ uffizzi dev start --detach + + 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 \ + --default-repo="hub.docker.com/acme/foo" + + To start a dev environment using an alternate kubeconfig file, + run: - $ uffizzi cluster create /path/to/skaffold.yaml + $ uffizzi dev start \ + --kubeconfig="/path/to/alternate/kubeconfig" diff --git a/man/uffizzi-dev-stop.ronn b/man/uffizzi-dev-stop.ronn new file mode 100644 index 00000000..1c612cc3 --- /dev/null +++ b/man/uffizzi-dev-stop.ronn @@ -0,0 +1,33 @@ +uffizzi-dev-stop - stop a development environment +================================================================ + +## SYNOPSIS + uffizzi dev stop + +## DESCRIPTION + 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/ + +## FLAGS + --help, -h + Show this message and exit. + +## EXAMPLES + To stop a dev environment, run: + + $ uffizzi dev stop diff --git a/test/support/mocks/mock_process.rb b/test/support/mocks/mock_process.rb new file mode 100644 index 00000000..87161260 --- /dev/null +++ b/test/support/mocks/mock_process.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class MockProcess + attr_accessor :pid + + def initialize + @pid = generate_pid + end + + def kill(sig, pid) + return @pid if sig.zero? && pid == @pid + raise Errno::ESRCH if pid != @pid + + @pid = nil + end + + def daemon + @pid = generate_pid + end + + private + + def generate_pid + (Time.now.utc.to_f * 100_000).to_i + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 635d0765..c7e85845 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -20,6 +20,7 @@ require 'uffizzi/config_file' require 'uffizzi/shell' require 'uffizzi/error' +require 'uffizzi/services/dev_service' include AuthSupport include FixtureSupport include UffizziStubSupport @@ -34,16 +35,23 @@ class Minitest::Test TEST_CONFIG_PATH = 'tmp/config_default.json' TEST_TOKEN_PATH = 'tmp/token_default.json' + TEST_PID_PATH = 'tmp/dev.pid' + TEST_DEV_LOGS_PATH = 'tmp/dev-logs.txt' def before_setup super @mock_prompt = MockPrompt.new @mock_shell = MockShell.new + @mock_process = MockProcess.new Uffizzi.stubs(:ui).returns(@mock_shell) Uffizzi.stubs(:prompt).returns(@mock_prompt) + Uffizzi.stubs(:process).returns(@mock_process) Uffizzi::ConfigFile.stubs(:config_path).returns(TEST_CONFIG_PATH) Uffizzi::Token.stubs(:token_path).returns(TEST_TOKEN_PATH) + Uffizzi::ConfigFile.stubs(:config_path).returns(TEST_CONFIG_PATH) + DevService.stubs(:pid_path).returns(TEST_PID_PATH) + DevService.stubs(:logs_path).returns(TEST_DEV_LOGS_PATH) end def before_teardown @@ -58,6 +66,8 @@ def before_teardown File.delete(TEST_CONFIG_PATH) if File.exist?(TEST_CONFIG_PATH) File.delete(TEST_TOKEN_PATH) if File.exist?(TEST_TOKEN_PATH) + File.delete(TEST_PID_PATH) if File.exist?(TEST_PID_PATH) + File.delete(TEST_DEV_LOGS_PATH) if File.exist?(TEST_DEV_LOGS_PATH) end def command_options(options) diff --git a/test/uffizzi/cli/dev_test.rb b/test/uffizzi/cli/dev_test.rb index 081a6b5e..42e014cd 100644 --- a/test/uffizzi/cli/dev_test.rb +++ b/test/uffizzi/cli/dev_test.rb @@ -70,4 +70,39 @@ def test_start_dev_with_existed_current_context assert_requested(stubbed_uffizzi_cluster_get_request) assert_requested(stubbed_uffizzi_cluster_delete_request) end + + def test_start_dev_as_daemon + cluster_create_body = json_fixture('files/uffizzi/uffizzi_cluster_deploying.json') + cluster_get_body = json_fixture('files/uffizzi/uffizzi_cluster_deployed.json') + stubbed_uffizzi_cluster_create_request = stub_uffizzi_create_cluster(cluster_create_body, @project_slug) + stubbed_uffizzi_cluster_get_request = stub_get_cluster_request(cluster_get_body, @project_slug) + stubbed_uffizzi_cluster_delete_request = stub_uffizzi_delete_cluster(@project_slug) + @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') + @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') + @dev.options = command_options(detach: true) + + @dev.start + + cluster_from_config = Uffizzi::ConfigFile.read_option(:clusters) + + assert_match('deleted', Uffizzi.ui.last_message) + assert_nil(cluster_from_config) + assert_requested(stubbed_uffizzi_cluster_create_request) + assert_requested(stubbed_uffizzi_cluster_get_request) + assert_requested(stubbed_uffizzi_cluster_delete_request) + end + + def test_start_dev_as_daemon_when_deamon_already_run + @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') + @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') + @dev.options = command_options(detach: true) + File.write(DevService.pid_path, '1000') + @mock_process.pid = 1000 + + error = assert_raises(MockShell::ExitError) do + @dev.start + end + + assert_match('You already start uffizzi', error.message) + end end From 9ebea3c7e4c69a62f3093f2a2afdf54ddc6d3422 Mon Sep 17 00:00:00 2001 From: Zipofar Date: Tue, 3 Oct 2023 18:03:54 +0300 Subject: [PATCH 2/6] [340] Add flags kubeconfig and default-repo --- lib/uffizzi/cli/cluster.rb | 4 +- lib/uffizzi/cli/dev.rb | 36 ++++--------- lib/uffizzi/services/cluster_service.rb | 1 + lib/uffizzi/services/dev_service.rb | 59 ++++++++++++++++++++-- lib/uffizzi/services/kubeconfig_service.rb | 4 +- test/uffizzi/cli/dev_test.rb | 22 ++++++-- 6 files changed, 88 insertions(+), 38 deletions(-) diff --git a/lib/uffizzi/cli/cluster.rb b/lib/uffizzi/cli/cluster.rb index cd3e67f5..02bbd31f 100644 --- a/lib/uffizzi/cli/cluster.rb +++ b/lib/uffizzi/cli/cluster.rb @@ -11,8 +11,6 @@ require 'uffizzi/services/kubeconfig_service' require 'uffizzi/services/cluster/disconnect_service' -MANUAL = 'manual' - module Uffizzi class Cli::Cluster < Thor class Error < StandardError; end @@ -115,7 +113,7 @@ def handle_create_command(project_slug, command_args) end cluster_name = command_args[:name] || options[:name] || ClusterService.generate_name - creation_source = options[:"creation-source"] || MANUAL + creation_source = options[:"creation-source"] || ClusterService::MANUAL_CREATION_SOURCE unless ClusterService.valid_name?(cluster_name) Uffizzi.ui.say_error_and_exit("Cluster name: #{cluster_name} is not valid.") diff --git a/lib/uffizzi/cli/dev.rb b/lib/uffizzi/cli/dev.rb index 3937a1bf..4bbd957c 100644 --- a/lib/uffizzi/cli/dev.rb +++ b/lib/uffizzi/cli/dev.rb @@ -11,17 +11,20 @@ class Cli::Dev < Thor desc 'start [CONFIG]', 'Start dev environment' method_option :detach, type: :boolean, aliases: :d + method_option :'default-repo', type: :string + method_option :kubeconfig, type: :string def start(config_path = 'skaffold.yaml') - check_skaffold_existence - check_login + DevService.check_skaffold_existence DevService.check_running_daemon if options[:detach] + DevService.check_skaffold_config_existence(config_path) + check_login cluster_id, cluster_name = start_create_cluster kubeconfig = wait_cluster_creation(cluster_name) if options[:detach] launch_demonise_skaffold(config_path) else - launch_scaffold(config_path) + DevService.start_basic_skaffold(config_path, options) end ensure if defined?(cluster_name).present? && defined?(cluster_id).present? @@ -53,7 +56,7 @@ def check_login def start_create_cluster cluster_name = ClusterService.generate_name - creation_source = MANUAL + creation_source = ClusterService::MANUAL_CREATION_SOURCE params = cluster_creation_params(cluster_name, creation_source) Uffizzi.ui.say('Start creating a cluster') response = create_cluster(ConfigFile.read_option(:server), project_slug, params) @@ -78,7 +81,7 @@ def wait_cluster_creation(cluster_name) end def handle_succeed_cluster_creation(cluster_data) - kubeconfig_path = KubeconfigService.default_path + kubeconfig_path = options[:kubeconfig] || KubeconfigService.default_path parsed_kubeconfig = parse_kubeconfig(cluster_data[:kubeconfig]) Uffizzi.ui.say("Cluster with name: #{cluster_data[:name]} was created.") @@ -185,28 +188,7 @@ def launch_demonise_skaffold(config_path) File.delete(DevService.pid_path) if File.exist?(DevService.pid_path) end - DevService.start_demonised_skaffold(config_path) - end - - def launch_scaffold(config_path) - Uffizzi.ui.say('Start skaffold') - cmd = "skaffold dev --filename='#{config_path}'" - - Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr| - stdout_and_stderr.each { |l| Uffizzi.ui.say(l) } - wait_thr.value - end - end - - def check_skaffold_existence - cmd = 'skaffold version' - stdout_str, stderr_str = Uffizzi.ui.capture3(cmd) - - return if stdout_str.present? && stderr_str.blank? - - Uffizzi.ui.say_error_and_exit(stderr_str) - rescue StandardError => e - Uffizzi.ui.say_error_and_exit(e.message) + DevService.start_demonised_skaffold(config_path, options) end def project_slug diff --git a/lib/uffizzi/services/cluster_service.rb b/lib/uffizzi/services/cluster_service.rb index 576f77fd..283d2d57 100644 --- a/lib/uffizzi/services/cluster_service.rb +++ b/lib/uffizzi/services/cluster_service.rb @@ -9,6 +9,7 @@ class ClusterService CLUSTER_STATE_FAILED_DEPLOY_NAMESPACE = 'failed_deploy_namespace' CLUSTER_STATE_FAILED = 'failed' CLUSTER_NAME_MAX_LENGTH = 15 + MANUAL_CREATION_SOURCE = 'manual' class << self include ApiClient diff --git a/lib/uffizzi/services/dev_service.rb b/lib/uffizzi/services/dev_service.rb index 304d913c..38dda9e9 100644 --- a/lib/uffizzi/services/dev_service.rb +++ b/lib/uffizzi/services/dev_service.rb @@ -6,6 +6,8 @@ class DevService class << self include ApiClient + DEFAULT_REGISTRY_REPO = 'registry.uffizzi.com' + def check_running_daemon return unless File.exist?(pid_path) @@ -27,10 +29,19 @@ def start_check_pid_file_existence end end - def start_demonised_skaffold(config_path) - File.write(logs_path, "Start skaffold\n") + def start_basic_skaffold(config_path, options) + Uffizzi.ui.say('Start skaffold') + cmd = build_skaffold_dev_command(config_path, options) + + Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr| + stdout_and_stderr.each { |l| Uffizzi.ui.say(l) } + wait_thr.value + end + end - cmd = "skaffold dev --filename='#{config_path}'" + def start_demonised_skaffold(config_path, options) + File.write(logs_path, "Start skaffold\n") + cmd = build_skaffold_dev_command(config_path, options) Uffizzi.ui.popen2e(cmd) do |_stdin, stdout_and_stderr, wait_thr| File.open(logs_path, 'a') do |f| @@ -44,6 +55,27 @@ def start_demonised_skaffold(config_path) end end + def check_skaffold_existence + cmd = 'skaffold version' + stdout_str, stderr_str = Uffizzi.ui.capture3(cmd) + + return if stdout_str.present? && stderr_str.blank? + + Uffizzi.ui.say_error_and_exit(stderr_str) + rescue StandardError => e + Uffizzi.ui.say_error_and_exit(e.message) + end + + def check_skaffold_config_existence(config_path) + msg = 'A valid dev environment configuration is required. Please provide a valid config,'\ + "\r\n"\ + 'or run `skaffold init` to generate a skaffold.yaml configuration.'\ + "\r\n"\ + 'See the `uffizzi dev start --help` page for supported configs and usage details.' + + Uffizzi.ui.say_error_and_exit(msg) unless File.exist?(config_path) + end + def pid_path File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'uffizzi_dev.pid') end @@ -51,5 +83,26 @@ def pid_path def logs_path File.join(Uffizzi::ConfigFile::CONFIG_DIR, 'uffizzi_dev.log') end + + def build_skaffold_dev_command(config_path, options) + cmd = [ + 'skaffold dev', + "--filename='#{config_path}'", + "--default-repo='#{default_registry_repo(options[:'default-repo'])}'", + "--kubeconfig='#{default_kubeconfig_path(options[:kubeconfig])}'", + ] + + cmd.join(' ') + end + + def default_registry_repo(repo) + repo || DEFAULT_REGISTRY_REPO + end + + def default_kubeconfig_path(kubeconfig_path) + path = kubeconfig_path || KubeconfigService.default_path + + File.expand_path(path) + end end end diff --git a/lib/uffizzi/services/kubeconfig_service.rb b/lib/uffizzi/services/kubeconfig_service.rb index 216dbd47..e39f336f 100644 --- a/lib/uffizzi/services/kubeconfig_service.rb +++ b/lib/uffizzi/services/kubeconfig_service.rb @@ -81,7 +81,9 @@ def save_to_filepath(filepath, kubeconfig = nil) end def default_path - kubeconfig_env_path || Uffizzi.configuration.default_kubeconfig_path + path = kubeconfig_env_path || Uffizzi.configuration.default_kubeconfig_path + + File.expand_path(path) end def read_kubeconfig(filepath) diff --git a/test/uffizzi/cli/dev_test.rb b/test/uffizzi/cli/dev_test.rb index 42e014cd..00924fc2 100644 --- a/test/uffizzi/cli/dev_test.rb +++ b/test/uffizzi/cli/dev_test.rb @@ -15,6 +15,9 @@ def setup @project_slug = Uffizzi::ConfigFile.read_option(:project) tmp_dir_name = (Time.now.utc.to_f * 100_000).to_i @kubeconfig_path = "/tmp/test/#{tmp_dir_name}/test-kubeconfig.yaml" + @skaffold_file_path = "/tmp/test/#{tmp_dir_name}/skaffold.yaml" + FileUtils.mkdir_p(File.dirname(@skaffold_file_path)) + File.write(@skaffold_file_path, '') end def test_start_dev @@ -26,7 +29,7 @@ def test_start_dev @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') - @dev.start + @dev.start(@skaffold_file_path) cluster_from_config = Uffizzi::ConfigFile.read_option(:clusters) @@ -58,7 +61,7 @@ def test_start_dev_with_existed_current_context @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') - @dev.start + @dev.start(@skaffold_file_path) cluster_from_config = Uffizzi::ConfigFile.read_option(:clusters) current_kubeconfig = Psych.safe_load(File.read(@kubeconfig_path)) @@ -81,7 +84,7 @@ def test_start_dev_as_daemon @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') @dev.options = command_options(detach: true) - @dev.start + @dev.start(@skaffold_file_path) cluster_from_config = Uffizzi::ConfigFile.read_option(:clusters) @@ -100,9 +103,20 @@ def test_start_dev_as_daemon_when_deamon_already_run @mock_process.pid = 1000 error = assert_raises(MockShell::ExitError) do - @dev.start + @dev.start(@skaffold_file_path) end assert_match('You already start uffizzi', error.message) end + + def test_start_dev_without_skaffold_config + File.delete(@skaffold_file_path) + @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') + + error = assert_raises(MockShell::ExitError) do + @dev.start(@skaffold_file_path) + end + + assert_match('Please provide a valid config', error.message) + end end From e5cd05b4834c78b2b0e1f9f0d9011dfca806d101 Mon Sep 17 00:00:00 2001 From: Zipofar Date: Wed, 4 Oct 2023 13:43:37 +0300 Subject: [PATCH 3/6] [340] Add test --- test/uffizzi/cli/dev_test.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/uffizzi/cli/dev_test.rb b/test/uffizzi/cli/dev_test.rb index 00924fc2..7102446b 100644 --- a/test/uffizzi/cli/dev_test.rb +++ b/test/uffizzi/cli/dev_test.rb @@ -119,4 +119,28 @@ def test_start_dev_without_skaffold_config assert_match('Please provide a valid config', error.message) end + + def test_start_dev_with_kubeconfig_and_default_repo_flags + default_repo = 'ttl.sh' + kubeconfig_path = '/tmp/some_path' + cluster_create_body = json_fixture('files/uffizzi/uffizzi_cluster_deploying.json') + cluster_get_body = json_fixture('files/uffizzi/uffizzi_cluster_deployed.json') + stubbed_uffizzi_cluster_create_request = stub_uffizzi_create_cluster(cluster_create_body, @project_slug) + stubbed_uffizzi_cluster_get_request = stub_get_cluster_request(cluster_get_body, @project_slug) + stubbed_uffizzi_cluster_delete_request = stub_uffizzi_delete_cluster(@project_slug) + @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') + skaffold_dev_regex = /skaffold dev --filename='.*' --default-repo='#{default_repo}' --kubeconfig='#{kubeconfig_path}'/ + @mock_shell.promise_execute(skaffold_dev_regex, stdout: 'Good') + + @dev.options = command_options('default-repo': default_repo, kubeconfig: kubeconfig_path) + @dev.start(@skaffold_file_path) + + cluster_from_config = Uffizzi::ConfigFile.read_option(:clusters) + + assert_match('deleted', Uffizzi.ui.last_message) + assert_nil(cluster_from_config) + assert_requested(stubbed_uffizzi_cluster_create_request) + assert_requested(stubbed_uffizzi_cluster_get_request) + assert_requested(stubbed_uffizzi_cluster_delete_request) + end end From 23de18801a521d430d153a8337e33e7ae8535fab Mon Sep 17 00:00:00 2001 From: Zipofar Date: Thu, 5 Oct 2023 15:00:32 +0300 Subject: [PATCH 4/6] [340] Fix filepath for daemon mode --- Makefile | 14 ++++++++++++++ lib/uffizzi/cli/dev.rb | 12 ++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2ea2998c..68583f56 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ NEXT_PATCH=$(shell docker-compose run --rm gem bash -c "bundle exec bump show-ne NEXT_MINOR=$(shell docker-compose run --rm gem bash -c "bundle exec bump show-next minor") NEXT_MAJOR=$(shell docker-compose run --rm gem bash -c "bundle exec bump show-next major") TAG_FULL_VERSION=v${VERSION} +CURRENT_LOCAL_GEM_NAME := $(shell ls | sort -r | grep 'uffizzi-cli-.*\.gem' | head -1) release_gem: mkdir -p ${HOME}/.gem @@ -53,4 +54,17 @@ lint: run_single_test: docker-compose run --rm gem bash -c 'bundle exec rake test TEST=$(TEST_PATH) TESTOPTS="--name=${TEST_NAME}"' +gem_build: + gem build uffizzi.gemspec + +gem_install: + gem install $(CURRENT_LOCAL_GEM_NAME) + +gem_build_install: + make gem_build + make gem_install + +gem_uninstall: + gem uninstall uffizzi-cli + .PHONY: test diff --git a/lib/uffizzi/cli/dev.rb b/lib/uffizzi/cli/dev.rb index 4bbd957c..c2fd2b82 100644 --- a/lib/uffizzi/cli/dev.rb +++ b/lib/uffizzi/cli/dev.rb @@ -177,18 +177,18 @@ def parse_kubeconfig(kubeconfig) end def launch_demonise_skaffold(config_path) - DevService.check_running_daemon - File.delete(DevService.logs_path) if File.exist?(DevService.logs_path) - - Uffizzi.process.daemon - File.write(DevService.pid_path, Uffizzi.process.pid) - DevService.start_check_pid_file_existence + Uffizzi.process.daemon(true) at_exit do File.delete(DevService.pid_path) if File.exist?(DevService.pid_path) end + File.delete(DevService.logs_path) if File.exist?(DevService.logs_path) + File.write(DevService.pid_path, Uffizzi.process.pid) + DevService.start_check_pid_file_existence DevService.start_demonised_skaffold(config_path, options) + rescue StandardError => e + File.open(DevService.logs_path, 'a') { |f| f.puts(e.message) } end def project_slug From ebb6bb76a1392de1b31300314bcdddb90cd4e9e4 Mon Sep 17 00:00:00 2001 From: Zipofar Date: Fri, 6 Oct 2023 13:32:17 +0300 Subject: [PATCH 5/6] [340] Rename flag detach to quiet --- lib/uffizzi/cli/dev.rb | 6 +++--- test/uffizzi/cli/dev_test.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/uffizzi/cli/dev.rb b/lib/uffizzi/cli/dev.rb index c2fd2b82..a41bbdb0 100644 --- a/lib/uffizzi/cli/dev.rb +++ b/lib/uffizzi/cli/dev.rb @@ -10,18 +10,18 @@ class Cli::Dev < Thor include ApiClient desc 'start [CONFIG]', 'Start dev environment' - method_option :detach, type: :boolean, aliases: :d + method_option :quiet, type: :boolean, aliases: :q method_option :'default-repo', type: :string method_option :kubeconfig, type: :string def start(config_path = 'skaffold.yaml') DevService.check_skaffold_existence - DevService.check_running_daemon if options[:detach] + DevService.check_running_daemon if options[:quiet] DevService.check_skaffold_config_existence(config_path) check_login cluster_id, cluster_name = start_create_cluster kubeconfig = wait_cluster_creation(cluster_name) - if options[:detach] + if options[:quiet] launch_demonise_skaffold(config_path) else DevService.start_basic_skaffold(config_path, options) diff --git a/test/uffizzi/cli/dev_test.rb b/test/uffizzi/cli/dev_test.rb index 7102446b..b437f449 100644 --- a/test/uffizzi/cli/dev_test.rb +++ b/test/uffizzi/cli/dev_test.rb @@ -82,7 +82,7 @@ def test_start_dev_as_daemon stubbed_uffizzi_cluster_delete_request = stub_uffizzi_delete_cluster(@project_slug) @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') - @dev.options = command_options(detach: true) + @dev.options = command_options(quiet: true) @dev.start(@skaffold_file_path) @@ -98,7 +98,7 @@ def test_start_dev_as_daemon def test_start_dev_as_daemon_when_deamon_already_run @mock_shell.promise_execute(/skaffold version/, stdout: 'v.2.7.1') @mock_shell.promise_execute(/skaffold dev --filename/, stdout: 'Good') - @dev.options = command_options(detach: true) + @dev.options = command_options(quiet: true) File.write(DevService.pid_path, '1000') @mock_process.pid = 1000 From d79ef03e689d7063df32ac941479b0df0c67ceee Mon Sep 17 00:00:00 2001 From: Zipofar Date: Fri, 6 Oct 2023 15:25:14 +0300 Subject: [PATCH 6/6] [340] Update man --- lib/uffizzi/services/dev_service.rb | 2 +- man/uffizzi-dev-start | 88 ++++++++++++++++++++++++++--- man/uffizzi-dev-start.ronn | 6 +- man/uffizzi-dev-stop | 41 ++++++++++++++ 4 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 man/uffizzi-dev-stop diff --git a/lib/uffizzi/services/dev_service.rb b/lib/uffizzi/services/dev_service.rb index 38dda9e9..57999660 100644 --- a/lib/uffizzi/services/dev_service.rb +++ b/lib/uffizzi/services/dev_service.rb @@ -15,7 +15,7 @@ def check_running_daemon File.delete(pid_path) if pid.blank? Uffizzi.process.kill(0, pid.to_i) - Uffizzi.ui.say_error_and_exit("You already start uffizzi dev as daemon. To stop process do 'uffizzi dev stop'") + Uffizzi.ui.say_error_and_exit("You have already started uffizzi dev as daemon. To stop the process do 'uffizzi dev stop'") rescue Errno::ESRCH File.delete(pid_path) end diff --git a/man/uffizzi-dev-start b/man/uffizzi-dev-start index af8585c0..19a27c2f 100644 --- a/man/uffizzi-dev-start +++ b/man/uffizzi-dev-start @@ -1,29 +1,99 @@ .\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 -.TH "UFFIZZI\-DEV\-START" "" "September 2023" "" +.TH "UFFIZZI\-DEV\-START" "" "October 2023" "" .SH "NAME" -\fBuffizzi\-dev\-start\fR \- start development environment +\fBuffizzi\-dev\-start\fR \- start a development environment .SH "SYNOPSIS" .nf -uffizzi dev start [SKAFFOLD_CONFIG_FILE_PATH] +uffizzi dev start [CONFIG_FILE] [FLAGS] .fi .SH "DESCRIPTION" .nf -Creates a new cluster and start skaffold\. If no SKAFFOLD_CONFIG_FILE_PATH is specified, -the default path will be used\. -Default SKAFFOLD_CONFIG_FILE_PATH is \'skaffold\.yaml\' +Creates a Uffizzi cluster preconfigured for development +workflows, including building, pushing, and deploying +your changes every time project files are saved\. +current\-context is updated in 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/ + +If a kubeconfig exists For more information on Uffizzi clusters, see: https://docs\.uffizzi\.com/references/cli/ .fi +.SH "POSITIONAL ARGUMENTS" +.nf +[CONFIG_FILE] + Path to the development environment configuration file\. + Currently supports skaffold\.yaml files\. +.fi +.SH "FLAGS" +.nf + \-\-build="" + This option specifies whether to build images on the + local environment or on the remote Uffizzi cluster\. + Possible values are "local" or "remote"\. + + \-\-default\-repo="" + A public or private repo used to push/pull build + artifacts\. Overrides the global default image registry: + "registry\.uffizzi\.com"\. See `uffizzi connect \-h` for + adding private registry credentials\. + + \-\-quiet, \-q + Run the development process in detached mode (i\.e\., in + the background)\. Without this option, logs are streamed + to the terminal in the foreground\. Run \'uffizzi dev stop` + to stop the detached process\. + + \-\-help, \-h + Show this message and exit\. + + \-\-kubeconfig="/path/to/your/kubeconfig" + Path to kubeconfig file\. If this option is not specified, + this command looks for the file at ~/\.kube/config\. +.fi .SH "EXAMPLES" .nf -To start development environment, run: +If your configuration file is in the current working +directory and you want to use an auto\-generated name, +run: $ uffizzi dev start -To start development environment with custom skaffold\.yaml file, run: +To start a dev environment using a skaffold\.yaml config +file in directory \'~/foo\', run: + + $ uffizzi dev start ~/foo/skaffold\.yaml + +To start a dev environment in quiet mode, +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: - $ uffizzi cluster create /path/to/skaffold\.yaml + $ uffizzi dev start \e + \-\-kubeconfig="/path/to/alternate/kubeconfig" .fi diff --git a/man/uffizzi-dev-start.ronn b/man/uffizzi-dev-start.ronn index 2a65b56c..59e510c4 100644 --- a/man/uffizzi-dev-start.ronn +++ b/man/uffizzi-dev-start.ronn @@ -42,7 +42,7 @@ uffizzi-dev-start - start a development environment "registry.uffizzi.com". See `uffizzi connect -h` for adding private registry credentials. - --detach, -d + --quiet, -q Run the development process in detached mode (i.e., in the background). Without this option, logs are streamed to the terminal in the foreground. Run 'uffizzi dev stop` @@ -67,10 +67,10 @@ uffizzi-dev-start - start a development environment $ uffizzi dev start ~/foo/skaffold.yaml - To start a dev environment in detached mode, + To start a dev environment in quiet mode, run: - $ uffizzi dev start --detach + $ uffizzi dev start --quiet To push your build artifacts to a private Docker Hub repo called 'acme/foo', first add your Docker Hub credentials: diff --git a/man/uffizzi-dev-stop b/man/uffizzi-dev-stop new file mode 100644 index 00000000..cf105533 --- /dev/null +++ b/man/uffizzi-dev-stop @@ -0,0 +1,41 @@ +.\" generated with Ronn-NG/v0.9.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.9.1 +.TH "UFFIZZI\-DEV\-STOP" "" "October 2023" "" +.SH "NAME" +\fBuffizzi\-dev\-stop\fR \- stop a development environment +.SH "SYNOPSIS" +.nf +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/ +.fi +.SH "FLAGS" +.nf + \-\-help, \-h + Show this message and exit\. +.fi +.SH "EXAMPLES" +.nf +To stop a dev environment, run: + + $ uffizzi dev stop +.fi +