import contextlib import json import os import six from dcos import subcommand import pytest from .common import (assert_command, assert_lines, delete_zk_nodes, exec_command, get_services, service_shutdown, watch_all_deployments) @pytest.fixture(scope="module") def zk_znode(request): request.addfinalizer(delete_zk_nodes) return request def _chronos_description(app_ids): """ :param app_ids: a list of application id :type app_ids: [str] :returns: a binary string representing the chronos description :rtype: str """ result = [ {"apps": app_ids, "description": "A fault tolerant job scheduler for Mesos which " "handles dependencies and ISO8601 based schedules.", "framework": True, "images": { "icon-large": "https://downloads.mesosphere.io/chronos/assets/" "icon-service-chronos-large.png", "icon-medium": "https://downloads.mesosphere.io/chronos/assets/" "icon-service-chronos-medium.png", "icon-small": "https://downloads.mesosphere.io/chronos/assets/" "icon-service-chronos-small.png" }, "licenses": [ { "name": "Apache License Version 2.0", "url": "https://github.com/mesos/chronos/blob/master/LICENSE" } ], "maintainer": "support@mesosphere.io", "name": "chronos", "packageSource": "https://github.com/mesosphere/universe/archive/\ version-1.x.zip", "postInstallNotes": "Chronos DCOS Service has been successfully " "installed!\n\n\tDocumentation: http://mesos." "github.io/chronos\n\tIssues: https://github.com/" "mesos/chronos/issues", "postUninstallNotes": "The Chronos DCOS Service has been uninstalled " "and will no longer run.\nPlease follow the " "instructions at http://beta-docs.mesosphere." "com/services/chronos/#uninstall to clean up " "any persisted state", "preInstallNotes": "We recommend a minimum of one node with at least " "1 CPU and 2GB of RAM available for the Chronos " "Service.", "releaseVersion": "0", "scm": "https://github.com/mesos/chronos.git", "tags": [ "mesosphere", "framework" ], "version": "2.3.4" }] return (json.dumps(result, sort_keys=True, indent=2).replace(' \n', '\n') + '\n').encode('utf-8') def test_package(): stdout = b"""Install and manage DCOS software packages Usage: dcos package --config-schema dcos package --info dcos package describe [--app --options= --cli] dcos package info dcos package install [--cli | [--app --app-id=]] [--options= --yes] dcos package list [--json --endpoints --app-id= ] dcos package search [--json ] dcos package sources dcos package uninstall [--cli | [--app --app-id= --all]] dcos package update [--validate] 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 --cli Apply the operation only to the package's CLI --options= Path to a JSON file containing package installation options --validate Validate package content when updating sources Configuration: [package] # Path to the local package cache. cache_dir = "/var/dcos/cache" # List of package sources, in search order. # # Three protocols are supported: # - Local file # - HTTPS # - Git sources = [ "file:///Users/me/test-registry", "https://my.org/registry", "git://github.com/mesosphere/universe.git" ] """ assert_command(['dcos', 'package', '--help'], stdout=stdout) def test_info(): assert_command(['dcos', 'package', '--info'], stdout=b'Install and manage DCOS software packages\n') def test_version(): assert_command(['dcos', 'package', '--version'], stdout=b'dcos-package version SNAPSHOT\n') def test_sources_list(): stdout = b"""a1a3267044fd65760dbfcdbe221c81e04e512657 \ https://github.com/mesosphere/universe/archive/version-1.x.zip """ assert_command(['dcos', 'package', 'sources'], stdout=stdout) def test_update_without_validation(): returncode, stdout, stderr = exec_command(['dcos', 'package', 'update']) assert returncode == 0 assert b'source' in stdout assert b'Validating package definitions...' not in stdout assert b'OK' not in stdout assert stderr == b'' def test_update_with_validation(): returncode, stdout, stderr = exec_command( ['dcos', 'package', 'update', '--validate']) assert returncode == 0 assert b'source' in stdout assert b'Validating package definitions...' in stdout assert b'OK' in stdout assert stderr == b'' def test_describe_nonexistent(): assert_command(['dcos', 'package', 'describe', 'xyzzy'], stderr=b'Package [xyzzy] not found\n', returncode=1) def test_describe(): stdout = b"""\ { "description": "A cluster-wide init and control system for services in \ cgroups or Docker containers.", "framework": true, "images": { "icon-large": "https://downloads.mesosphere.io/marathon/assets/\ icon-service-marathon-large.png", "icon-medium": "https://downloads.mesosphere.io/marathon/assets/\ icon-service-marathon-medium.png", "icon-small": "https://downloads.mesosphere.io/marathon/assets/\ icon-service-marathon-small.png" }, "licenses": [ { "name": "Apache License Version 2.0", "url": "https://github.com/mesosphere/marathon/blob/master/LICENSE" } ], "maintainer": "support@mesosphere.io", "name": "marathon", "postInstallNotes": "Marathon DCOS Service has been successfully \ installed!\\n\\n\\tDocumentation: https://\ mesosphere.github.io/marathon\\n\\tIssues: https:/github.com/mesosphere/\ marathon/issues\\n", "postUninstallNotes": "The Marathon DCOS Service has been uninstalled and \ will no longer run.\\nPlease follow the instructions at http://beta-docs.\ mesosphere.com/services/marathon/#uninstall to clean up any persisted state", "preInstallNotes": "We recommend a minimum of one node with at least 2 \ CPU's and 1GB of RAM available for the Marathon Service.", "scm": "https://github.com/mesosphere/marathon.git", "tags": [ "mesosphere", "framework" ], "versions": [ "0.8.1" ] } """ assert_command(['dcos', 'package', 'describe', 'marathon'], stdout=stdout) stdout = b"""\ { "command": { "pip": [ "dcos<1.0", "git+https://github.com/mesosphere/\ dcos-helloworld.git#dcos-helloworld=0.1.0" ] }, "description": "Example DCOS application package", "maintainer": "support@mesosphere.io", "name": "helloworld", "postInstallNotes": "A sample post-installation message", "preInstallNotes": "A sample pre-installation message", "tags": [ "mesosphere", "example", "subcommand" ], "versions": [ "0.1.0" ], "website": "https://github.com/mesosphere/dcos-helloworld" } """ assert_command(['dcos', 'package', 'describe', '--cli', 'helloworld'], stdout=stdout) stdout = b"""\ { "app": { "cmd": "LIBPROCESS_PORT=$PORT1 && ./bin/start --master zk://master\ .mesos:2181/mesos --checkpoint --failover_timeout 604800 --framework_\ name marathon-user --ha --zk zk://localhost:2181/mesos/\ marathon-user --http_port $PORT0 ", "constraints": [ [ "hostname", "UNIQUE" ] ], "container": { "docker": { "image": "mesosphere/marathon:v0.8.1", "network": "HOST" }, "type": "DOCKER" }, "cpus": 2.0, "env": { "JVM_OPTS": "-Xms256m -Xmx768m" }, "id": "marathon-user", "instances": 1, "labels": { "DCOS_PACKAGE_FRAMEWORK_NAME": "marathon-user", "DCOS_PACKAGE_IS_FRAMEWORK": "true", "DCOS_PACKAGE_METADATA": "eyJkZXNjcmlwdGlvbiI6ICJBIGNsdXN0ZXItd2lkZSBpbm\ l0IGFuZCBjb250cm9sIHN5c3RlbSBmb3Igc2VydmljZXMgaW4gY2dyb3VwcyBvciBEb2NrZXIgY29u\ dGFpbmVycy4iLCAiZnJhbWV3b3JrIjogdHJ1ZSwgImltYWdlcyI6IHsiaWNvbi1sYXJnZSI6ICJodH\ RwczovL2Rvd25sb2Fkcy5tZXNvc3BoZXJlLmlvL21hcmF0aG9uL2Fzc2V0cy9pY29uLXNlcnZpY2Ut\ bWFyYXRob24tbGFyZ2UucG5nIiwgImljb24tbWVkaXVtIjogImh0dHBzOi8vZG93bmxvYWRzLm1lc2\ 9zcGhlcmUuaW8vbWFyYXRob24vYXNzZXRzL2ljb24tc2VydmljZS1tYXJhdGhvbi1tZWRpdW0ucG5n\ IiwgImljb24tc21hbGwiOiAiaHR0cHM6Ly9kb3dubG9hZHMubWVzb3NwaGVyZS5pby9tYXJhdGhvbi\ 9hc3NldHMvaWNvbi1zZXJ2aWNlLW1hcmF0aG9uLXNtYWxsLnBuZyJ9LCAibGljZW5zZXMiOiBbeyJu\ YW1lIjogIkFwYWNoZSBMaWNlbnNlIFZlcnNpb24gMi4wIiwgInVybCI6ICJodHRwczovL2dpdGh1Yi\ 5jb20vbWVzb3NwaGVyZS9tYXJhdGhvbi9ibG9iL21hc3Rlci9MSUNFTlNFIn1dLCAibWFpbnRhaW5l\ ciI6ICJzdXBwb3J0QG1lc29zcGhlcmUuaW8iLCAibmFtZSI6ICJtYXJhdGhvbiIsICJwb3N0SW5zdG\ FsbE5vdGVzIjogIk1hcmF0aG9uIERDT1MgU2VydmljZSBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgaW5z\ dGFsbGVkIVxuXG5cdERvY3VtZW50YXRpb246IGh0dHBzOi8vbWVzb3NwaGVyZS5naXRodWIuaW8vbW\ FyYXRob25cblx0SXNzdWVzOiBodHRwczovZ2l0aHViLmNvbS9tZXNvc3BoZXJlL21hcmF0aG9uL2lz\ c3Vlc1xuIiwgInBvc3RVbmluc3RhbGxOb3RlcyI6ICJUaGUgTWFyYXRob24gRENPUyBTZXJ2aWNlIG\ hhcyBiZWVuIHVuaW5zdGFsbGVkIGFuZCB3aWxsIG5vIGxvbmdlciBydW4uXG5QbGVhc2UgZm9sbG93\ IHRoZSBpbnN0cnVjdGlvbnMgYXQgaHR0cDovL2JldGEtZG9jcy5tZXNvc3BoZXJlLmNvbS9zZXJ2aW\ Nlcy9tYXJhdGhvbi8jdW5pbnN0YWxsIHRvIGNsZWFuIHVwIGFueSBwZXJzaXN0ZWQgc3RhdGUiLCAi\ cHJlSW5zdGFsbE5vdGVzIjogIldlIHJlY29tbWVuZCBhIG1pbmltdW0gb2Ygb25lIG5vZGUgd2l0aC\ BhdCBsZWFzdCAyIENQVSdzIGFuZCAxR0Igb2YgUkFNIGF2YWlsYWJsZSBmb3IgdGhlIE1hcmF0aG9u\ IFNlcnZpY2UuIiwgInNjbSI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9tYXJhdGhvbi\ 5naXQiLCAidGFncyI6IFsibWVzb3NwaGVyZSIsICJmcmFtZXdvcmsiXSwgInZlcnNpb24iOiAiMC44\ LjEifQ==", "DCOS_PACKAGE_NAME": "marathon", "DCOS_PACKAGE_REGISTRY_VERSION": "1.0.0-rc1", "DCOS_PACKAGE_RELEASE": "0", "DCOS_PACKAGE_SOURCE": "https://github.com/mesosphere/universe/archive/\ version-1.x.zip", "DCOS_PACKAGE_VERSION": "0.8.1" }, "mem": 1024.0, "ports": [ 0, 0 ], "uris": [] }, "description": "A cluster-wide init and control system for services \ in cgroups or Docker containers.", "framework": true, "images": { "icon-large": "https://downloads.mesosphere.io/marathon/assets/\ icon-service-marathon-large.png", "icon-medium": "https://downloads.mesosphere.io/marathon/assets/\ icon-service-marathon-medium.png", "icon-small": "https://downloads.mesosphere.io/marathon/assets/\ icon-service-marathon-small.png" }, "licenses": [ { "name": "Apache License Version 2.0", "url": "https://github.com/mesosphere/marathon/blob/master/LICENSE" } ], "maintainer": "support@mesosphere.io", "name": "marathon", "postInstallNotes": "Marathon DCOS Service has been successfully \ installed!\\n\\n\\tDocumentation: https://\ mesosphere.github.io/marathon\\n\\tIssues: https:/github.com/mesosphere/\ marathon/issues\\n", "postUninstallNotes": "The Marathon DCOS Service has been uninstalled and \ will no longer run.\\nPlease follow the instructions at http://beta-docs.\ mesosphere.com/services/marathon/#uninstall to clean up any persisted state", "preInstallNotes": "We recommend a minimum of one node with at least 2 \ CPU's and 1GB of RAM available for the Marathon Service.", "scm": "https://github.com/mesosphere/marathon.git", "tags": [ "mesosphere", "framework" ], "versions": [ "0.8.1" ] } """ assert_command(['dcos', 'package', 'describe', '--app', '--options', 'tests/data/package/marathon.json', 'marathon'], stdout=stdout) def test_bad_install(): args = ['--options=tests/data/package/chronos-bad.json', '--yes'] stderr = b"""Error: False is not of type 'string' Path: chronos.zk-hosts Value: false Please create a JSON file with the appropriate options, and pass the \ /path/to/file as an --options argument. """ _install_chronos(args=args, returncode=1, stdout=b'', stderr=stderr, postInstallNotes=b'') def test_install(zk_znode): _install_chronos() watch_all_deployments() _uninstall_chronos() get_services(expected_count=1, args=['--inactive']) def test_install_missing_options_file(): """Test that a missing options file results in the expected stderr message.""" assert_command( ['dcos', 'package', 'install', 'chronos', '--yes', '--options=asdf.json'], returncode=1, stdout=b'We recommend a minimum of one node with at least 1 CPU and ' b'2GB of RAM available for the Chronos Service.\n', stderr=b"Error opening file [asdf.json]: No such file or directory\n") def test_package_metadata(): _install_helloworld() # test marathon labels expected_metadata = b"""eyJkZXNjcmlwdGlvbiI6ICJFeGFtcGxlIERDT1MgYXBwbGljYX\ Rpb24gcGFja2FnZSIsICJtYWludGFpbmVyIjogInN1cHBvcnRAbWVzb3NwaGVyZS5pbyIsICJuYW1l\ IjogImhlbGxvd29ybGQiLCAicG9zdEluc3RhbGxOb3RlcyI6ICJBIHNhbXBsZSBwb3N0LWluc3RhbG\ xhdGlvbiBtZXNzYWdlIiwgInByZUluc3RhbGxOb3RlcyI6ICJBIHNhbXBsZSBwcmUtaW5zdGFsbGF0\ aW9uIG1lc3NhZ2UiLCAidGFncyI6IFsibWVzb3NwaGVyZSIsICJleGFtcGxlIiwgInN1YmNvbW1hbm\ QiXSwgInZlcnNpb24iOiAiMC4xLjAiLCAid2Vic2l0ZSI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVz\ b3NwaGVyZS9kY29zLWhlbGxvd29ybGQifQ==""" expected_command = b"""eyJwaXAiOiBbImRjb3M8MS4wIiwgImdpdCtodHRwczovL2dpdGh\ 1Yi5jb20vbWVzb3NwaGVyZS9kY29zLWhlbGxvd29ybGQuZ2l0I2Rjb3MtaGVsbG93b3JsZD0wLjEuM\ CJdfQ==""" expected_source = b'https://github.com/mesosphere/universe/archive/\ version-1.x.zip' expected_labels = { 'DCOS_PACKAGE_METADATA': expected_metadata, 'DCOS_PACKAGE_COMMAND': expected_command, 'DCOS_PACKAGE_REGISTRY_VERSION': b'1.0.0-rc1', 'DCOS_PACKAGE_NAME': b'helloworld', 'DCOS_PACKAGE_VERSION': b'0.1.0', 'DCOS_PACKAGE_SOURCE': expected_source, 'DCOS_PACKAGE_RELEASE': b'0', } app_labels = _get_app_labels('helloworld') for label, value in expected_labels.items(): assert value == six.b(app_labels.get(label)) # test local package.json package = { "description": "Example DCOS application package", "maintainer": "support@mesosphere.io", "name": "helloworld", "postInstallNotes": "A sample post-installation message", "preInstallNotes": "A sample pre-installation message", "tags": ["mesosphere", "example", "subcommand"], "version": "0.1.0", "website": "https://github.com/mesosphere/dcos-helloworld", } package_dir = subcommand.package_dir('helloworld') # test local package.json package_path = os.path.join(package_dir, 'package.json') with open(package_path) as f: assert json.load(f) == package # test local source source_path = os.path.join(package_dir, 'source') with open(source_path) as f: assert six.b(f.read()) == expected_source # test local version version_path = os.path.join(package_dir, 'version') with open(version_path) as f: assert six.b(f.read()) == b'0' # uninstall helloworld _uninstall_helloworld() def test_install_with_id(zk_znode): args = ['--app-id=chronos-1', '--yes'] stdout = (b'Installing package [chronos] version [2.3.4] with app id ' b'[chronos-1]\n') _install_chronos(args=args, stdout=stdout) args = ['--app-id=chronos-2', '--yes'] stdout = (b'Installing package [chronos] version [2.3.4] with app id ' b'[chronos-2]\n') _install_chronos(args=args, stdout=stdout) def test_install_missing_package(): stderr = b"""Package [missing-package] not found You may need to run 'dcos package update' to update your repositories """ assert_command(['dcos', 'package', 'install', 'missing-package'], returncode=1, stderr=stderr) def test_uninstall_with_id(zk_znode): _uninstall_chronos(args=['--app-id=chronos-1']) def test_uninstall_all(zk_znode): _uninstall_chronos(args=['--all']) get_services(expected_count=1, args=['--inactive']) def test_uninstall_missing(): stderr = 'Package [chronos] is not installed.\n' _uninstall_chronos(returncode=1, stderr=stderr) stderr = 'Package [chronos] with id [chronos-1] is not installed.\n' _uninstall_chronos( args=['--app-id=chronos-1'], returncode=1, stderr=stderr) def test_uninstall_subcommand(): _install_helloworld() _uninstall_helloworld() _list() def test_uninstall_cli(): _install_helloworld() _uninstall_helloworld(args=['--cli']) stdout = b"""[ { "apps": [ "/helloworld" ], "description": "Example DCOS application package", "maintainer": "support@mesosphere.io", "name": "helloworld", "packageSource": "https://github.com/mesosphere/universe/archive/\ version-1.x.zip", "postInstallNotes": "A sample post-installation message", "preInstallNotes": "A sample pre-installation message", "releaseVersion": "0", "tags": [ "mesosphere", "example", "subcommand" ], "version": "0.1.0", "website": "https://github.com/mesosphere/dcos-helloworld" } ] """ _list(stdout=stdout) _uninstall_helloworld() def test_list(zk_znode): _list() _list(args=['xyzzy', '--json']) _list(args=['--app-id=/xyzzy', '--json']) _install_chronos() expected_output = _chronos_description(['/chronos']) _list(stdout=expected_output) _list(args=['--json', 'chronos'], stdout=expected_output) _list(args=['--json', '--app-id=/chronos'], stdout=expected_output) _list(args=['--json', 'ceci-nest-pas-une-package']) _list(args=['--json', '--app-id=/ceci-nest-pas-une-package']) _uninstall_chronos() def test_list_table(): with _helloworld(): assert_lines(['dcos', 'package', 'list'], 2) def test_install_yes(): with open('tests/data/package/assume_yes.txt') as yes_file: _install_helloworld( args=[], stdin=yes_file, stdout=b'A sample pre-installation message\n' b'Continue installing? [yes/no] ' b'Installing package [helloworld] version [0.1.0]\n' b'Installing CLI subcommand for package [helloworld]\n' b'A sample post-installation message\n') _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] Exiting installation.\n') def test_list_cli(): _install_helloworld() stdout = b"""\ [ { "apps": [ "/helloworld" ], "command": { "name": "helloworld" }, "description": "Example DCOS application package", "maintainer": "support@mesosphere.io", "name": "helloworld", "packageSource": "https://github.com/mesosphere/universe/archive/\ version-1.x.zip", "postInstallNotes": "A sample post-installation message", "preInstallNotes": "A sample pre-installation message", "releaseVersion": "0", "tags": [ "mesosphere", "example", "subcommand" ], "version": "0.1.0", "website": "https://github.com/mesosphere/dcos-helloworld" } ] """ _list(stdout=stdout) _uninstall_helloworld() 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', '--yes'], stdout=stdout) stdout = b"""\ [ { "command": { "name": "helloworld" }, "description": "Example DCOS application package", "maintainer": "support@mesosphere.io", "name": "helloworld", "packageSource": "https://github.com/mesosphere/universe/archive/\ version-1.x.zip", "postInstallNotes": "A sample post-installation message", "preInstallNotes": "A sample pre-installation message", "releaseVersion": "0", "tags": [ "mesosphere", "example", "subcommand" ], "version": "0.1.0", "website": "https://github.com/mesosphere/dcos-helloworld" } ] """ _list(stdout=stdout) _uninstall_helloworld() def test_uninstall_multiple_frameworknames(zk_znode): _install_chronos( args=['--yes', '--options=tests/data/package/chronos-1.json']) _install_chronos( args=['--yes', '--options=tests/data/package/chronos-2.json']) watch_all_deployments() expected_output = _chronos_description( ['/chronos-user-1', '/chronos-user-2']) _list(stdout=expected_output) _list(args=['--json', 'chronos'], stdout=expected_output) _list(args=['--json', '--app-id=/chronos-user-1'], stdout=_chronos_description(['/chronos-user-1'])) _list(args=['--json', '--app-id=/chronos-user-2'], stdout=_chronos_description(['/chronos-user-2'])) _uninstall_chronos( args=['--app-id=chronos-user-1'], returncode=1, stderr='Unable to shutdown the framework for [chronos-user] because ' 'there are multiple frameworks with the same name: ') _uninstall_chronos( args=['--app-id=chronos-user-2'], returncode=1, stderr='Unable to shutdown the framework for [chronos-user] because ' 'there are multiple frameworks with the same name: ') for framework in get_services(args=['--inactive']): if framework['name'] == 'chronos-user': service_shutdown(framework['id']) def test_search(): returncode, stdout, stderr = exec_command( ['dcos', 'package', 'search', 'framework', '--json']) assert returncode == 0 assert b'chronos' in stdout assert stderr == b'' returncode, stdout, stderr = exec_command( ['dcos', 'package', 'search', 'xyzzy', '--json']) assert returncode == 0 assert b'"packages": []' in stdout assert b'"source": "https://github.com/mesosphere/universe/archive/\ version-1.x.zip"' in stdout assert stderr == b'' returncode, stdout, stderr = exec_command( ['dcos', 'package', 'search', '--json']) registries = json.loads(stdout.decode('utf-8')) for registry in registries: # assert the number of packages is gte the number at the time # this test was written assert len(registry['packages']) >= 5 assert returncode == 0 assert stderr == b'' def test_search_table(): returncode, stdout, stderr = exec_command( ['dcos', 'package', 'search']) assert returncode == 0 assert b'chronos' in stdout assert len(stdout.decode('utf-8').split('\n')) > 5 assert stderr == b'' def _get_app_labels(app_id): returncode, stdout, stderr = exec_command( ['dcos', 'marathon', 'app', 'show', app_id]) assert returncode == 0 assert stderr == b'' app_json = json.loads(stdout.decode('utf-8')) return app_json.get('labels') def _install_helloworld( 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=[]): assert_command(['dcos', 'package', 'uninstall', 'helloworld'] + args) def _uninstall_chronos(args=[], returncode=0, stdout=b'', stderr=''): result_returncode, result_stdout, result_stderr = exec_command( ['dcos', 'package', 'uninstall', 'chronos'] + args) assert result_returncode == returncode assert result_stdout == stdout assert result_stderr.decode('utf-8').startswith(stderr) def _install_chronos( args=['--yes'], returncode=0, stdout=b'Installing package [chronos] version [2.3.4]\n', stderr=b'', preInstallNotes=b'We recommend a minimum of one node with at least 1 ' b'CPU and 2GB of RAM available for the Chronos ' b'Service.\n', postInstallNotes=b'Chronos DCOS Service has been successfully ' b'''installed! \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, preInstallNotes + stdout + postInstallNotes, stderr, stdin=stdin) def _list(args=['--json'], stdout=b'[]\n'): assert_command(['dcos', 'package', 'list'] + args, stdout=stdout) def _helloworld(): stdout = b'''A sample pre-installation message Installing package [helloworld] version [0.1.0] Installing CLI subcommand for package [helloworld] A sample post-installation message ''' return _package('helloworld', stdout=stdout) @contextlib.contextmanager def _package(name, stdout=b''): """Context manager that deploys an app on entrance, and removes it on exit. :param path: path to app's json definition: :type path: str :param app_id: app id :type app_id: str :rtype: None """ assert_command(['dcos', 'package', 'install', name, '--yes'], stdout=stdout) try: yield finally: assert_command(['dcos', 'package', 'uninstall', name])