From 5e506bff2ea1428a8f9449581a82e65837c17878 Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Thu, 21 May 2015 23:04:27 -0700 Subject: [PATCH] Output pre-install notes on package install. - If there are pre-install notes, prompt the user before proceeding. - Adds --yes and --no flags to bypass installation prompts non-interactively. --- cli/dcoscli/package/main.py | 45 ++++++++++++-- cli/tests/data/dcos.toml | 4 +- cli/tests/data/missing_params_dcos.toml | 4 +- cli/tests/data/package/assume_no.txt | 2 + cli/tests/data/package/assume_yes.txt | 2 + cli/tests/integrations/cli/common.py | 13 ++-- cli/tests/integrations/cli/test_config.py | 10 ++-- cli/tests/integrations/cli/test_package.py | 70 +++++++++++++++------- 8 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 cli/tests/data/package/assume_no.txt create mode 100644 cli/tests/data/package/assume_yes.txt diff --git a/cli/dcoscli/package/main.py b/cli/dcoscli/package/main.py index 29f9b74..f264166 100644 --- a/cli/dcoscli/package/main.py +++ b/cli/dcoscli/package/main.py @@ -6,8 +6,7 @@ Usage: dcos package describe [--app --options= --cli] dcos package info dcos package install [--cli | [--app --app-id=]] - [--options=] - + [--options= --yes] dcos package list-installed [--endpoints --app-id= ] dcos package search [] dcos package sources @@ -19,6 +18,8 @@ Options: -h, --help Show this screen --info Show a short description of this subcommand --version Show version + --yes Assume "yes" is the answer to all prompts and run + non-interactively --all Apply the operation to all matching packages --app Apply the operation only to the package's application --app-id= The application id @@ -52,6 +53,8 @@ import pkg_resources from dcos import cmds, emitting, marathon, options, package, subcommand, util from dcos.errors import DCOSException +from six.moves import input as user_input + logger = util.get_logger(__name__) emitter = emitting.FlatEmitter() @@ -100,7 +103,7 @@ def _cmds(): cmds.Command( hierarchy=['package', 'install'], arg_keys=['', '--options', '--app-id', '--cli', - '--app'], + '--app', '--yes'], function=_install), cmds.Command( @@ -240,7 +243,32 @@ def _user_options(path): return util.load_json(options_file) -def _install(package_name, options_path, app_id, cli, app): +def _confirm(prompt, yes): + """ + :param prompt: message to display to the terminal + :type prompt: str + :param yes: whether to assume that the user responded with yes + :type yes: bool + :returns: True if the user responded with yes; False otherwise + :rtype: bool + """ + + if yes: + return True + else: + while True: + emitter.publish('{} [yes/no]'.format(prompt)) + response = user_input().lower() + if response == 'yes' or response == 'y': + return True + elif response == 'no' or response == 'n': + return False + else: + emitter.publish( + "'{}' is not a valid response.".format(response)) + + +def _install(package_name, options_path, app_id, cli, app, yes): """Install the specified package. :param package_name: the package to install @@ -253,6 +281,8 @@ def _install(package_name, options_path, app_id, cli, app): :type cli: bool :param app: indicate if the application should be installed :type app: bool + :param yes: automatically assume yes to all prompts + :type yes: bool :returns: process status :rtype: int """ @@ -273,6 +303,13 @@ def _install(package_name, options_path, app_id, cli, app): # TODO(CD): Make package version to install configurable pkg_version = pkg.latest_version() + pre_install_notes = pkg.package_json(pkg_version).get('preInstallNotes') + if pre_install_notes: + emitter.publish(pre_install_notes) + if not _confirm('Continue installing?', yes): + emitter.publish('Exiting installation.') + return 0 + user_options = _user_options(options_path) options = pkg.options(pkg_version, user_options) diff --git a/cli/tests/data/dcos.toml b/cli/tests/data/dcos.toml index 83171f8..10b341d 100644 --- a/cli/tests/data/dcos.toml +++ b/cli/tests/data/dcos.toml @@ -1,7 +1,7 @@ [core] -dcos_url = "http://localhost:5080" +dcos_url = "http://172.17.8.101" email = "test@mail.com" reporting = false [package] -cache = "tmp/cache" sources = [ "git://github.com/mesosphere/universe.git", "https://github.com/mesosphere/universe/archive/master.zip",] +cache = "tmp/cache" diff --git a/cli/tests/data/missing_params_dcos.toml b/cli/tests/data/missing_params_dcos.toml index 228ee08..b59e72a 100644 --- a/cli/tests/data/missing_params_dcos.toml +++ b/cli/tests/data/missing_params_dcos.toml @@ -1,6 +1,6 @@ [core] -reporting = false email = "test@mail.com" +reporting = false [package] -cache = "true" sources = [ "git://github.com/mesosphere/universe.git", "https://github.com/mesosphere/universe/archive/master.zip",] +cache = "true" diff --git a/cli/tests/data/package/assume_no.txt b/cli/tests/data/package/assume_no.txt new file mode 100644 index 0000000..8d0d101 --- /dev/null +++ b/cli/tests/data/package/assume_no.txt @@ -0,0 +1,2 @@ +no + diff --git a/cli/tests/data/package/assume_yes.txt b/cli/tests/data/package/assume_yes.txt new file mode 100644 index 0000000..c6991e8 --- /dev/null +++ b/cli/tests/data/package/assume_yes.txt @@ -0,0 +1,2 @@ +yes + diff --git a/cli/tests/integrations/cli/common.py b/cli/tests/integrations/cli/common.py index 6c806c4..32a918b 100644 --- a/cli/tests/integrations/cli/common.py +++ b/cli/tests/integrations/cli/common.py @@ -33,12 +33,13 @@ def exec_command(cmd, env=None, stdin=None): return (process.returncode, stdout, stderr) -def assert_command(cmd, - returncode=0, - stdout=b'', - stderr=b'', - env=None, - stdin=None): +def assert_command( + cmd, + returncode=0, + stdout=b'', + stderr=b'', + env=None, + stdin=None): """Execute CLI command and assert expected behavior. :param cmd: Program and arguments diff --git a/cli/tests/integrations/cli/test_config.py b/cli/tests/integrations/cli/test_config.py index 1521a26..d1c9035 100644 --- a/cli/tests/integrations/cli/test_config.py +++ b/cli/tests/integrations/cli/test_config.py @@ -67,7 +67,7 @@ def test_version(): def test_list_property(env): - stdout = b"""core.dcos_url=http://localhost:5080 + stdout = b"""core.dcos_url=http://172.17.8.101 core.email=test@mail.com core.reporting=False package.cache=tmp/cache @@ -80,7 +80,7 @@ package.sources=['git://github.com/mesosphere/universe.git', \ def test_get_existing_string_property(env): - _get_value('core.dcos_url', 'http://localhost:5080', env) + _get_value('core.dcos_url', 'http://172.17.8.101', env) def test_get_existing_boolean_property(env): @@ -105,9 +105,9 @@ def test_get_top_property(env): def test_set_existing_string_property(env): - _set_value('core.dcos_url', 'http://localhost:5081', env) - _get_value('core.dcos_url', 'http://localhost:5081', env) - _set_value('core.dcos_url', 'http://localhost:5080', env) + _set_value('core.dcos_url', 'http://172.17.8.101:5081', env) + _get_value('core.dcos_url', 'http://172.17.8.101:5081', env) + _set_value('core.dcos_url', 'http://172.17.8.101', env) def test_set_existing_boolean_property(env): diff --git a/cli/tests/integrations/cli/test_package.py b/cli/tests/integrations/cli/test_package.py index 8e64363..b44c137 100644 --- a/cli/tests/integrations/cli/test_package.py +++ b/cli/tests/integrations/cli/test_package.py @@ -16,8 +16,7 @@ Usage: dcos package describe [--app --options= --cli] dcos package info dcos package install [--cli | [--app --app-id=]] - [--options=] - + [--options= --yes] dcos package list-installed [--endpoints --app-id= ] dcos package search [] dcos package sources @@ -29,6 +28,8 @@ Options: -h, --help Show this screen --info Show a short description of this subcommand --version Show version + --yes Assume "yes" is the answer to all prompts and run + non-interactively --all Apply the operation to all matching packages --app Apply the operation only to the package's application --app-id= The application id @@ -241,7 +242,7 @@ icon-service-marathon-small.png" def test_bad_install(): - args = ['--options=tests/data/package/chronos-bad.json'] + args = ['--options=tests/data/package/chronos-bad.json', '--yes'] stderr = b"""Error: False is not of type 'string' Path: chronos.zk-hosts Value: false @@ -328,12 +329,12 @@ CJdfQ==""" def test_install_with_id(): - args = ['--app-id=chronos-1'] + args = ['--app-id=chronos-1', '--yes'] stdout = (b"""Installing package [chronos] version [2.3.4] with app """ b"""id [chronos-1]\n""") _install_chronos(args=args, stdout=stdout) - args = ['--app-id=chronos-2'] + args = ['--app-id=chronos-2', '--yes'] stdout = (b"""Installing package [chronos] version [2.3.4] with app """ b"""id [chronos-2]\n""") _install_chronos(args=args, stdout=stdout) @@ -440,8 +441,9 @@ service-chronos-small.png" "packageSource": "git://github.com/mesosphere/universe.git", "postInstallNotes": "Chronos DCOS Service has been successfully installed!\ \\nWe recommend a minimum of one node with at least 1 CPU and 2GB of RAM \ -available for the Chronos Service.\\n\\n\\tDocumentation: https://github.com/\ -mesos/chronos\\n\\tIssues: https:/github.com/mesos/chronos/issues", +available for the Chronos Service.\\n\\n\\tDocumentation: \ +http://mesos.github.io/chronos\\n\\tIssues: https://github.com/mesos/\ +chronos/issues", "releaseVersion": "0", "scm": "https://github.com/mesos/chronos.git", "tags": [ @@ -474,6 +476,22 @@ mesos/chronos\\n\\tIssues: https:/github.com/mesos/chronos/issues", _uninstall_chronos() +def test_install_yes(): + with open('tests/data/package/assume_yes.txt') as yes_file: + _install_helloworld(stdin=yes_file) + _uninstall_helloworld() + + +def test_install_no(): + with open('tests/data/package/assume_no.txt') as no_file: + _install_helloworld( + args=[], + stdin=no_file, + stdout=b'A sample pre-installation message\n' + b'Continue installing? [yes/no]\n' + b'Exiting installation.\n') + + def test_list_installed_cli(): _install_helloworld() @@ -508,9 +526,10 @@ def test_list_installed_cli(): _uninstall_helloworld() - stdout = (b"Installing CLI subcommand for package [helloworld]\n" + stdout = (b"A sample pre-installation message\n" + b"Installing CLI subcommand for package [helloworld]\n" b"A sample post-installation message\n") - _install_helloworld(args=['--cli'], stdout=stdout) + _install_helloworld(args=['--cli', '--yes'], stdout=stdout) stdout = b"""\ [ @@ -572,7 +591,7 @@ def test_search(): for registry in registries: # assert the number of packages is gte the number at the time # this test was written - assert len(registry['packages']) >= 8 + assert len(registry['packages']) >= 7 assert returncode == 0 assert stderr == b'' @@ -590,13 +609,16 @@ def get_app_labels(app_id): def _install_helloworld( - args=[], - stdout=b"""Installing package [helloworld] version [0.1.0] -Installing CLI subcommand for package [helloworld] -A sample post-installation message -"""): - assert_command(['dcos', 'package', 'install', 'helloworld'] + args, - stdout=stdout) + args=['--yes'], + stdout=b'A sample pre-installation message\n' + b'Installing package [helloworld] version [0.1.0]\n' + b'Installing CLI subcommand for package [helloworld]\n' + b'A sample post-installation message\n', + stdin=None): + assert_command( + ['dcos', 'package', 'install', 'helloworld'] + args, + stdout=stdout, + stdin=stdin) def _uninstall_helloworld(args=[]): @@ -609,7 +631,7 @@ def _uninstall_chronos(args=[], returncode=0, stdout=b'', stderr=b''): def _install_chronos( - args=[], + args=['--yes'], returncode=0, stdout=b'Installing package [chronos] version [2.3.4]\n', stderr=b'', @@ -618,8 +640,14 @@ def _install_chronos( b'with at least 1 CPU and 2GB of RAM available for ' b'''the Chronos Service. -\tDocumentation: https://github.com/mesos/chronos -\tIssues: https:/github.com/mesos/chronos/issues\n'''): +\tDocumentation: http://mesos.github.io/chronos +\tIssues: https://github.com/mesos/chronos/issues\n''', + stdin=None): cmd = ['dcos', 'package', 'install', 'chronos'] + args - assert_command(cmd, returncode, stdout + postInstallNotes, stderr) + assert_command( + cmd, + returncode, + stdout + postInstallNotes, + stderr, + stdin=stdin)