Merge pull request #268 from mesosphere/version
add --package-version option for installing packages
This commit is contained in:
48
cli/dcoscli/data/help/package.txt
Normal file
48
cli/dcoscli/data/help/package.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
Install and manage DCOS packages
|
||||
|
||||
Usage:
|
||||
dcos package --config-schema
|
||||
dcos package --info
|
||||
dcos package describe [--app --options=<file> --cli] <package_name>
|
||||
dcos package install [--cli | [--app --app-id=<app_id>]]
|
||||
[--package-version=<package_version>]
|
||||
[--options=<file>] [--yes] <package_name>
|
||||
dcos package list [--json --endpoints --app-id=<app-id> <package_name>]
|
||||
dcos package search [--json <query>]
|
||||
dcos package sources
|
||||
dcos package uninstall [--cli | [--app --app-id=<app-id> --all]]
|
||||
<package_name>
|
||||
dcos package update [--validate]
|
||||
|
||||
Options:
|
||||
--all Apply the operation to all matching packages
|
||||
--app Apply the operation only to the package's
|
||||
application
|
||||
--app-id=<app-id> The application id
|
||||
--cli Apply the operation only to the package's CLI
|
||||
-h, --help Show this screen
|
||||
--info Show a short description of this subcommand
|
||||
--options=<file> Path to a JSON file containing package
|
||||
installation options
|
||||
--package-version=<package_version> Package version to install
|
||||
--validate Validate package content when updating sources
|
||||
--version Show version
|
||||
--yes Assume "yes" is the answer to all prompts and
|
||||
run non-interactively
|
||||
|
||||
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"
|
||||
]
|
||||
@@ -1,49 +1,3 @@
|
||||
"""Install and manage DCOS software packages
|
||||
|
||||
Usage:
|
||||
dcos package --config-schema
|
||||
dcos package --info
|
||||
dcos package describe [--app --options=<file> --cli] <package_name>
|
||||
dcos package install [--cli | [--app --app-id=<app_id>]]
|
||||
[--options=<file> --yes] <package_name>
|
||||
dcos package list [--json --endpoints --app-id=<app-id> <package_name>]
|
||||
dcos package search [--json <query>]
|
||||
dcos package sources
|
||||
dcos package uninstall [--cli | [--app --app-id=<app-id> --all]]
|
||||
<package_name>
|
||||
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=<app-id> The application id
|
||||
--cli Apply the operation only to the package's CLI
|
||||
--options=<file> 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"
|
||||
]
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@@ -68,11 +22,17 @@ def main():
|
||||
return 1
|
||||
|
||||
|
||||
def _doc():
|
||||
return pkg_resources.resource_string(
|
||||
'dcoscli',
|
||||
'data/help/package.txt').decode('utf-8')
|
||||
|
||||
|
||||
def _main():
|
||||
util.configure_process_from_environ()
|
||||
|
||||
args = docopt.docopt(
|
||||
__doc__,
|
||||
_doc(),
|
||||
version='dcos-package version {}'.format(dcoscli.version))
|
||||
http.silence_requests_warnings()
|
||||
|
||||
@@ -103,8 +63,8 @@ def _cmds():
|
||||
|
||||
cmds.Command(
|
||||
hierarchy=['package', 'install'],
|
||||
arg_keys=['<package_name>', '--options', '--app-id', '--cli',
|
||||
'--app', '--yes'],
|
||||
arg_keys=['<package_name>', '--package-version', '--options',
|
||||
'--app-id', '--cli', '--app', '--yes'],
|
||||
function=_install),
|
||||
|
||||
cmds.Command(
|
||||
@@ -148,7 +108,7 @@ def _package(config_schema, info):
|
||||
elif info:
|
||||
_info()
|
||||
else:
|
||||
emitter.publish(options.make_generic_usage_message(__doc__))
|
||||
emitter.publish(options.make_generic_usage_message(_doc()))
|
||||
return 1
|
||||
|
||||
return 0
|
||||
@@ -161,7 +121,7 @@ def _info():
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
emitter.publish(__doc__.split('\n')[0])
|
||||
emitter.publish(_doc().split('\n')[0])
|
||||
return 0
|
||||
|
||||
|
||||
@@ -215,22 +175,22 @@ def _describe(package_name, cli, app, options_path):
|
||||
raise DCOSException("Package [{}] not found".format(package_name))
|
||||
|
||||
# TODO(CD): Make package version to describe configurable
|
||||
pkg_version = pkg.latest_version()
|
||||
pkg_json = pkg.package_json(pkg_version)
|
||||
version_map = pkg.software_versions()
|
||||
versions = [version_map[pkg_ver] for pkg_ver in version_map]
|
||||
pkg_revision = pkg.latest_package_revision()
|
||||
pkg_json = pkg.package_json(pkg_revision)
|
||||
revision_map = pkg.package_revisions_map()
|
||||
pkg_versions = list(revision_map.values())
|
||||
|
||||
del pkg_json['version']
|
||||
pkg_json['versions'] = versions
|
||||
pkg_json['versions'] = pkg_versions
|
||||
|
||||
if cli or app:
|
||||
user_options = _user_options(options_path)
|
||||
options = pkg.options(pkg_version, user_options)
|
||||
options = pkg.options(pkg_revision, user_options)
|
||||
|
||||
if cli:
|
||||
pkg_json['command'] = pkg.command_json(pkg_version, options)
|
||||
pkg_json['command'] = pkg.command_json(pkg_revision, options)
|
||||
if app:
|
||||
pkg_json['app'] = pkg.marathon_json(pkg_version, options)
|
||||
pkg_json['app'] = pkg.marathon_json(pkg_revision, options)
|
||||
|
||||
emitter.publish(pkg_json)
|
||||
return 0
|
||||
@@ -277,11 +237,14 @@ def _confirm(prompt, yes):
|
||||
"'{}' is not a valid response.".format(response))
|
||||
|
||||
|
||||
def _install(package_name, options_path, app_id, cli, app, yes):
|
||||
def _install(package_name, package_version, options_path, app_id, cli, app,
|
||||
yes):
|
||||
"""Install the specified package.
|
||||
|
||||
:param package_name: the package to install
|
||||
:type package_name: str
|
||||
:param package_version: package version to install
|
||||
:type package_version: str
|
||||
:param options_path: path to file containing option values
|
||||
:type options_path: str
|
||||
:param app_id: app ID for installation of this package
|
||||
@@ -309,10 +272,15 @@ def _install(package_name, options_path, app_id, cli, app, yes):
|
||||
"repositories"
|
||||
raise DCOSException(msg)
|
||||
|
||||
# TODO(CD): Make package version to install configurable
|
||||
pkg_version = pkg.latest_version()
|
||||
pkg_revision = pkg.latest_package_revision(package_version)
|
||||
if pkg_revision is None:
|
||||
msg = "Package [{}] not available".format(package_name)
|
||||
if package_version is not None:
|
||||
msg += " with version {}".format(package_version)
|
||||
raise DCOSException(msg)
|
||||
|
||||
pre_install_notes = pkg.package_json(pkg_version).get('preInstallNotes')
|
||||
pkg_json = pkg.package_json(pkg_revision)
|
||||
pre_install_notes = pkg_json.get('preInstallNotes')
|
||||
if pre_install_notes:
|
||||
emitter.publish(pre_install_notes)
|
||||
if not _confirm('Continue installing?', yes):
|
||||
@@ -321,35 +289,36 @@ def _install(package_name, options_path, app_id, cli, app, yes):
|
||||
|
||||
user_options = _user_options(options_path)
|
||||
|
||||
options = pkg.options(pkg_version, user_options)
|
||||
options = pkg.options(pkg_revision, user_options)
|
||||
|
||||
if app and pkg.has_marathon_definition(pkg_version):
|
||||
revision_map = pkg.package_revisions_map()
|
||||
package_version = revision_map.get(pkg_revision)
|
||||
|
||||
if app and pkg.has_marathon_definition(pkg_revision):
|
||||
# Install in Marathon
|
||||
version_map = pkg.software_versions()
|
||||
sw_version = version_map.get(pkg_version, '?')
|
||||
|
||||
message = 'Installing package [{}] version [{}]'.format(
|
||||
pkg.name(), sw_version)
|
||||
msg = 'Installing Marathon app for package [{}] version [{}]'.format(
|
||||
pkg.name(), package_version)
|
||||
if app_id is not None:
|
||||
message += ' with app id [{}]'.format(app_id)
|
||||
msg += ' with app id [{}]'.format(app_id)
|
||||
|
||||
emitter.publish(message)
|
||||
emitter.publish(msg)
|
||||
|
||||
init_client = marathon.create_client(config)
|
||||
|
||||
package.install_app(
|
||||
pkg,
|
||||
pkg_version,
|
||||
pkg_revision,
|
||||
init_client,
|
||||
options,
|
||||
app_id)
|
||||
|
||||
if cli and pkg.has_command_definition(pkg_version):
|
||||
if cli and pkg.has_command_definition(pkg_revision):
|
||||
# Install subcommand
|
||||
emitter.publish('Installing CLI subcommand for package [{}]'.format(
|
||||
pkg.name()))
|
||||
msg = 'Installing CLI subcommand for package [{}] version [{}]'.format(
|
||||
pkg.name(), package_version)
|
||||
emitter.publish(msg)
|
||||
|
||||
subcommand.install(pkg, pkg_version, options)
|
||||
subcommand.install(pkg, pkg_revision, options)
|
||||
|
||||
subcommand_paths = subcommand.get_package_commands(package_name)
|
||||
new_commands = [os.path.basename(p).replace('-', ' ', 1)
|
||||
@@ -361,7 +330,7 @@ def _install(package_name, options_path, app_id, cli, app, yes):
|
||||
emitter.publish("New command{} available: {}".format(plural,
|
||||
commands))
|
||||
|
||||
post_install_notes = pkg.package_json(pkg_version).get('postInstallNotes')
|
||||
post_install_notes = pkg_json.get('postInstallNotes')
|
||||
if post_install_notes:
|
||||
emitter.publish(post_install_notes)
|
||||
|
||||
|
||||
@@ -265,6 +265,7 @@ def package_table(packages):
|
||||
|
||||
fields = OrderedDict([
|
||||
('NAME', lambda p: p['name']),
|
||||
('VERSION', lambda p: p['version']),
|
||||
('APP',
|
||||
lambda p: '\n'.join(p['apps']) if p.get('apps') else EMPTY_ENTRY),
|
||||
('COMMAND',
|
||||
@@ -274,6 +275,7 @@ def package_table(packages):
|
||||
|
||||
tb = util.table(fields, packages, sortby="NAME")
|
||||
tb.align['NAME'] = 'l'
|
||||
tb.align['VERSION'] = 'l'
|
||||
tb.align['APP'] = 'l'
|
||||
tb.align['COMMAND'] = 'l'
|
||||
tb.align['DESCRIPTION'] = 'l'
|
||||
|
||||
@@ -81,6 +81,7 @@ setup(
|
||||
package_data={
|
||||
'dcoscli': [
|
||||
'data/*.json',
|
||||
'data/help/*.txt',
|
||||
'data/config-schema/*.json',
|
||||
],
|
||||
},
|
||||
|
||||
48
cli/tests/data/package/help.txt
Normal file
48
cli/tests/data/package/help.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
Install and manage DCOS packages
|
||||
|
||||
Usage:
|
||||
dcos package --config-schema
|
||||
dcos package --info
|
||||
dcos package describe [--app --options=<file> --cli] <package_name>
|
||||
dcos package install [--cli | [--app --app-id=<app_id>]]
|
||||
[--package-version=<package_version>]
|
||||
[--options=<file>] [--yes] <package_name>
|
||||
dcos package list [--json --endpoints --app-id=<app-id> <package_name>]
|
||||
dcos package search [--json <query>]
|
||||
dcos package sources
|
||||
dcos package uninstall [--cli | [--app --app-id=<app-id> --all]]
|
||||
<package_name>
|
||||
dcos package update [--validate]
|
||||
|
||||
Options:
|
||||
--all Apply the operation to all matching packages
|
||||
--app Apply the operation only to the package's
|
||||
application
|
||||
--app-id=<app-id> The application id
|
||||
--cli Apply the operation only to the package's CLI
|
||||
-h, --help Show this screen
|
||||
--info Show a short description of this subcommand
|
||||
--options=<file> Path to a JSON file containing package
|
||||
installation options
|
||||
--package-version=<package_version> Package version to install
|
||||
--validate Validate package content when updating sources
|
||||
--version Show version
|
||||
--yes Assume "yes" is the answer to all prompts and
|
||||
run non-interactively
|
||||
|
||||
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"
|
||||
]
|
||||
@@ -61,7 +61,7 @@
|
||||
"framework"
|
||||
],
|
||||
"versions": [
|
||||
"0.8.1",
|
||||
"0.9.0-RC3"
|
||||
"0.9.0-RC3",
|
||||
"0.8.1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"framework"
|
||||
],
|
||||
"versions": [
|
||||
"0.8.1",
|
||||
"0.9.0-RC3"
|
||||
"0.9.0-RC3",
|
||||
"0.8.1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Available DCOS commands:
|
||||
\thelp \tDisplay command line usage information
|
||||
\tmarathon \tDeploy and manage applications on the DCOS
|
||||
\tnode \tManage DCOS nodes
|
||||
\tpackage \tInstall and manage DCOS software packages
|
||||
\tpackage \tInstall and manage DCOS packages
|
||||
\tservice \tManage DCOS services
|
||||
\ttask \tManage DCOS tasks
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Available DCOS commands:
|
||||
\thelp \tDisplay command line usage information
|
||||
\tmarathon \tDeploy and manage applications on the DCOS
|
||||
\tnode \tManage DCOS nodes
|
||||
\tpackage \tInstall and manage DCOS software packages
|
||||
\tpackage \tInstall and manage DCOS packages
|
||||
\tservice \tManage DCOS services
|
||||
\ttask \tManage DCOS tasks
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import contextlib
|
||||
import json
|
||||
import os
|
||||
|
||||
import pkg_resources
|
||||
import six
|
||||
from dcos import subcommand
|
||||
|
||||
@@ -75,59 +76,16 @@ version-1.x.zip",
|
||||
|
||||
|
||||
def test_package():
|
||||
stdout = b"""Install and manage DCOS software packages
|
||||
|
||||
Usage:
|
||||
dcos package --config-schema
|
||||
dcos package --info
|
||||
dcos package describe [--app --options=<file> --cli] <package_name>
|
||||
dcos package install [--cli | [--app --app-id=<app_id>]]
|
||||
[--options=<file> --yes] <package_name>
|
||||
dcos package list [--json --endpoints --app-id=<app-id> <package_name>]
|
||||
dcos package search [--json <query>]
|
||||
dcos package sources
|
||||
dcos package uninstall [--cli | [--app --app-id=<app-id> --all]]
|
||||
<package_name>
|
||||
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=<app-id> The application id
|
||||
--cli Apply the operation only to the package's CLI
|
||||
--options=<file> 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"
|
||||
]
|
||||
"""
|
||||
stdout = pkg_resources.resource_string(
|
||||
'tests',
|
||||
'data/package/help.txt')
|
||||
assert_command(['dcos', 'package', '--help'],
|
||||
stdout=stdout)
|
||||
|
||||
|
||||
def test_info():
|
||||
assert_command(['dcos', 'package', '--info'],
|
||||
stdout=b'Install and manage DCOS software packages\n')
|
||||
stdout=b'Install and manage DCOS packages\n')
|
||||
|
||||
|
||||
def test_version():
|
||||
@@ -224,6 +182,35 @@ def test_install_missing_options_file():
|
||||
stderr=b"Error opening file [asdf.json]: No such file or directory\n")
|
||||
|
||||
|
||||
def test_install_specific_version():
|
||||
stdout = (b'We recommend a minimum of one node with at least 2 '
|
||||
b'CPU\'s and 1GB of RAM available for the Marathon Service.\n'
|
||||
b'Installing Marathon app for package [marathon] '
|
||||
b'version [0.8.1]\n'
|
||||
b'Marathon DCOS Service has been successfully installed!\n\n'
|
||||
b'\tDocumentation: https://mesosphere.github.io/marathon\n'
|
||||
b'\tIssues: https:/github.com/mesosphere/marathon/issues\n\n')
|
||||
|
||||
with _package('marathon',
|
||||
stdout=stdout,
|
||||
args=['--yes', '--package-version=0.8.1']):
|
||||
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'package', 'list', 'marathon', '--json'])
|
||||
assert returncode == 0
|
||||
assert stderr == b''
|
||||
assert json.loads(stdout.decode('utf-8'))[0]['version'] == "0.8.1"
|
||||
|
||||
|
||||
def test_install_bad_package_version():
|
||||
stderr = b'Package [helloworld] not available with version a.b.c\n'
|
||||
assert_command(
|
||||
['dcos', 'package', 'install', 'helloworld',
|
||||
'--package-version=a.b.c'],
|
||||
returncode=1,
|
||||
stderr=stderr)
|
||||
|
||||
|
||||
def test_package_metadata():
|
||||
_install_helloworld()
|
||||
|
||||
@@ -293,13 +280,13 @@ version-1.x.zip'
|
||||
|
||||
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')
|
||||
stdout = (b'Installing Marathon app for package [chronos] version [2.3.4] '
|
||||
b'with app id [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')
|
||||
stdout = (b'Installing Marathon app for package [chronos] version [2.3.4] '
|
||||
b'with app id [chronos-2]\n')
|
||||
_install_chronos(args=args, stdout=stdout)
|
||||
|
||||
|
||||
@@ -371,9 +358,10 @@ version-1.x.zip",
|
||||
|
||||
def test_uninstall_multiple_apps():
|
||||
stdout = (b'A sample pre-installation message\n'
|
||||
b'Installing package [helloworld] version [0.1.0] ' +
|
||||
b'with app id [/helloworld-1]\n'
|
||||
b'Installing CLI subcommand for package [helloworld]\n'
|
||||
b'Installing Marathon app for package [helloworld] version '
|
||||
b'[0.1.0] with app id [/helloworld-1]\n'
|
||||
b'Installing CLI subcommand for package [helloworld] '
|
||||
b'version [0.1.0]\n'
|
||||
b'New command available: dcos helloworld\n'
|
||||
b'A sample post-installation message\n')
|
||||
|
||||
@@ -381,18 +369,19 @@ def test_uninstall_multiple_apps():
|
||||
stdout=stdout)
|
||||
|
||||
stdout = (b'A sample pre-installation message\n'
|
||||
b'Installing package [helloworld] version [0.1.0] ' +
|
||||
b'with app id [/helloworld-2]\n'
|
||||
b'Installing CLI subcommand for package [helloworld]\n'
|
||||
b'Installing Marathon app for package [helloworld] version '
|
||||
b'[0.1.0] with app id [/helloworld-2]\n'
|
||||
b'Installing CLI subcommand for package [helloworld] '
|
||||
b'version [0.1.0]\n'
|
||||
b'New command available: dcos helloworld\n'
|
||||
b'A sample post-installation message\n')
|
||||
|
||||
_install_helloworld(['--yes', '--app-id=/helloworld-2'],
|
||||
stdout=stdout)
|
||||
|
||||
stderr = (b"Multiple apps named [helloworld] are installed: " +
|
||||
b"[/helloworld-1, /helloworld-2].\n" +
|
||||
b"Please use --app-id to specify the ID of the app " +
|
||||
stderr = (b"Multiple apps named [helloworld] are installed: "
|
||||
b"[/helloworld-1, /helloworld-2].\n"
|
||||
b"Please use --app-id to specify the ID of the app "
|
||||
b"to uninstall, or use --all to uninstall all apps.\n")
|
||||
_uninstall_helloworld(stderr=stderr,
|
||||
returncode=1)
|
||||
@@ -433,8 +422,10 @@ def test_install_yes():
|
||||
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'Installing Marathon app for package [helloworld] version '
|
||||
b'[0.1.0]\n'
|
||||
b'Installing CLI subcommand for package [helloworld] '
|
||||
b'version [0.1.0]\n'
|
||||
b'New command available: dcos helloworld\n'
|
||||
b'A sample post-installation message\n')
|
||||
_uninstall_helloworld()
|
||||
@@ -483,8 +474,9 @@ version-1.x.zip",
|
||||
_uninstall_helloworld()
|
||||
|
||||
stdout = (b"A sample pre-installation message\n"
|
||||
b"Installing CLI subcommand for package [helloworld]\n"
|
||||
b'New command available: dcos helloworld\n'
|
||||
b"Installing CLI subcommand for package [helloworld] " +
|
||||
b"version [0.1.0]\n"
|
||||
b"New command available: dcos helloworld\n"
|
||||
b"A sample post-installation message\n")
|
||||
_install_helloworld(args=['--cli', '--yes'], stdout=stdout)
|
||||
|
||||
@@ -610,8 +602,10 @@ def _get_app_labels(app_id):
|
||||
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'Installing Marathon app for package [helloworld] '
|
||||
b'version [0.1.0]\n'
|
||||
b'Installing CLI subcommand for package [helloworld] '
|
||||
b'version [0.1.0]\n'
|
||||
b'New command available: dcos helloworld\n'
|
||||
b'A sample post-installation message\n',
|
||||
returncode=0,
|
||||
@@ -646,7 +640,8 @@ def _uninstall_chronos(args=[], returncode=0, stdout=b'', stderr=''):
|
||||
def _install_chronos(
|
||||
args=['--yes'],
|
||||
returncode=0,
|
||||
stdout=b'Installing package [chronos] version [2.3.4]\n',
|
||||
stdout=b'Installing Marathon app for package [chronos] '
|
||||
b'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 '
|
||||
@@ -675,8 +670,8 @@ def _list(args=['--json'],
|
||||
|
||||
def _helloworld():
|
||||
stdout = b'''A sample pre-installation message
|
||||
Installing package [helloworld] version [0.1.0]
|
||||
Installing CLI subcommand for package [helloworld]
|
||||
Installing Marathon app for package [helloworld] version [0.1.0]
|
||||
Installing CLI subcommand for package [helloworld] version [0.1.0]
|
||||
New command available: dcos helloworld
|
||||
A sample post-installation message
|
||||
'''
|
||||
@@ -686,18 +681,21 @@ A sample post-installation message
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _package(name,
|
||||
stdout=b''):
|
||||
"""Context manager that deploys an app on entrance, and removes it on
|
||||
stdout=b'',
|
||||
args=['--yes']):
|
||||
"""Context manager that installs a package on entrace, and uninstalls it on
|
||||
exit.
|
||||
|
||||
:param path: path to app's json definition:
|
||||
:type path: str
|
||||
:param app_id: app id
|
||||
:type app_id: str
|
||||
:param name: package name
|
||||
:type name: str
|
||||
:param stdout: Expected stdout
|
||||
:type stdout: str
|
||||
:param args: extra CLI args
|
||||
:type args: [str]
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
assert_command(['dcos', 'package', 'install', name, '--yes'],
|
||||
assert_command(['dcos', 'package', 'install', name] + args,
|
||||
stdout=stdout)
|
||||
try:
|
||||
yield
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
NAME APP COMMAND DESCRIPTION
|
||||
helloworld /helloworld helloworld Example DCOS application package
|
||||
NAME VERSION APP COMMAND DESCRIPTION
|
||||
helloworld 0.1.0 /helloworld helloworld Example DCOS application package
|
||||
177
dcos/package.py
177
dcos/package.py
@@ -37,13 +37,13 @@ PACKAGE_REGISTRY_VERSION_KEY = 'DCOS_PACKAGE_REGISTRY_VERSION'
|
||||
PACKAGE_FRAMEWORK_NAME_KEY = 'DCOS_PACKAGE_FRAMEWORK_NAME'
|
||||
|
||||
|
||||
def install_app(pkg, version, init_client, options, app_id):
|
||||
def install_app(pkg, revision, init_client, options, app_id):
|
||||
"""Installs a package's application
|
||||
|
||||
:param pkg: the package to install
|
||||
:type pkg: Package
|
||||
:param version: the package version to install
|
||||
:type version: str
|
||||
:param revision: the package revision to install
|
||||
:type revision: str
|
||||
:param init_client: the program to use to run the package
|
||||
:type init_client: object
|
||||
:param options: package parameters
|
||||
@@ -54,7 +54,7 @@ def install_app(pkg, version, init_client, options, app_id):
|
||||
"""
|
||||
|
||||
# Insert option parameters into the init template
|
||||
init_desc = pkg.marathon_json(version, options)
|
||||
init_desc = pkg.marathon_json(revision, options)
|
||||
|
||||
if app_id is not None:
|
||||
logger.debug('Setting app ID to "%s" (was "%s")',
|
||||
@@ -66,20 +66,20 @@ def install_app(pkg, version, init_client, options, app_id):
|
||||
init_client.add_app(init_desc)
|
||||
|
||||
|
||||
def _make_package_labels(pkg, version, options):
|
||||
def _make_package_labels(pkg, revision, options):
|
||||
"""Returns Marathon app labels for a package.
|
||||
|
||||
:param pkg: The package to install
|
||||
:type pkg: Package
|
||||
:param version: The package version to install
|
||||
:type version: str
|
||||
:param revision: The package revision to install
|
||||
:type revision: str
|
||||
:param options: package parameters
|
||||
:type options: dict
|
||||
:returns: Marathon app labels
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
metadata = pkg.package_json(version)
|
||||
metadata = pkg.package_json(revision)
|
||||
|
||||
encoded_metadata = _base64_encode(metadata)
|
||||
|
||||
@@ -96,11 +96,11 @@ def _make_package_labels(pkg, version, options):
|
||||
PACKAGE_SOURCE_KEY: pkg.registry.source.url,
|
||||
PACKAGE_FRAMEWORK_KEY: json.dumps(is_framework),
|
||||
PACKAGE_REGISTRY_VERSION_KEY: package_registry_version,
|
||||
PACKAGE_RELEASE_KEY: str(version)
|
||||
PACKAGE_RELEASE_KEY: revision
|
||||
}
|
||||
|
||||
if pkg.has_command_definition(version):
|
||||
command = pkg.command_json(version, options)
|
||||
if pkg.has_command_definition(revision):
|
||||
command = pkg.command_json(revision, options)
|
||||
package_labels[PACKAGE_COMMAND_KEY] = _base64_encode(command)
|
||||
|
||||
# Run a heuristic that determines the hint for the framework name
|
||||
@@ -319,7 +319,7 @@ class InstalledPackage(object):
|
||||
ret.update(package_json)
|
||||
|
||||
ret['packageSource'] = self.subcommand.package_source()
|
||||
ret['releaseVersion'] = self.subcommand.package_version()
|
||||
ret['releaseVersion'] = self.subcommand.package_revision()
|
||||
else:
|
||||
ret.update(self.apps[0])
|
||||
ret.pop('appId')
|
||||
@@ -357,9 +357,9 @@ def installed_packages(init_client, endpoints):
|
||||
dicts[key]['apps'].append(app)
|
||||
|
||||
for subcmd in subcommands:
|
||||
package_version = subcmd.package_version()
|
||||
package_revision = subcmd.package_revision()
|
||||
package_source = subcmd.package_source()
|
||||
key = (subcmd.name, package_version, package_source)
|
||||
key = (subcmd.name, package_revision, package_source)
|
||||
dicts[key]['command'] = subcmd
|
||||
|
||||
return [
|
||||
@@ -709,7 +709,7 @@ def update_sources(config, validate=False):
|
||||
errors.append(e.message)
|
||||
continue
|
||||
|
||||
# check the version
|
||||
# check version
|
||||
# TODO(jsancio): move this to the validation when it is forced
|
||||
Registry(source, stage_dir).check_version(
|
||||
LooseVersion('1.0'),
|
||||
@@ -1078,7 +1078,8 @@ class Registry():
|
||||
"""
|
||||
|
||||
version = LooseVersion(self.get_version())
|
||||
if not (version >= min_version and version < max_version):
|
||||
if not (version >= min_version and
|
||||
version < max_version):
|
||||
raise DCOSException((
|
||||
'Unable to update source [{}] because version {} is '
|
||||
'not supported. Supported versions are between {} and '
|
||||
@@ -1194,12 +1195,12 @@ class Package():
|
||||
|
||||
return os.path.basename(self.path)
|
||||
|
||||
def options(self, version, user_options):
|
||||
def options(self, revision, user_options):
|
||||
"""Merges package options with user supplied options, validates, and
|
||||
returns the result.
|
||||
|
||||
:param version: the package version to install
|
||||
:type version: str
|
||||
:param revision: the package revision to install
|
||||
:type revision: str
|
||||
:param user_options: package parameters
|
||||
:type user_options: dict
|
||||
:returns: a dictionary with the user supplied options
|
||||
@@ -1209,7 +1210,7 @@ class Package():
|
||||
if user_options is None:
|
||||
user_options = {}
|
||||
|
||||
config_schema = self.config_json(version)
|
||||
config_schema = self.config_json(revision)
|
||||
default_options = _extract_default_values(config_schema)
|
||||
|
||||
logger.info('Generated default options: %r', default_options)
|
||||
@@ -1239,11 +1240,11 @@ class Package():
|
||||
|
||||
return self._registry
|
||||
|
||||
def has_definition(self, version, filename):
|
||||
def has_definition(self, revision, filename):
|
||||
"""Returns true if the package defines filename; false otherwise.
|
||||
|
||||
:param version: package version
|
||||
:type version: str
|
||||
:param revision: package revision
|
||||
:type revision: str
|
||||
:param filename: file in package definition
|
||||
:type filename: str
|
||||
:returns: whether filename is defined
|
||||
@@ -1253,54 +1254,56 @@ class Package():
|
||||
return os.path.isfile(
|
||||
os.path.join(
|
||||
self.path,
|
||||
os.path.join(version, filename)))
|
||||
os.path.join(revision, filename)))
|
||||
|
||||
def has_command_definition(self, version):
|
||||
def has_command_definition(self, revision):
|
||||
"""Returns true if the package defines a command; false otherwise.
|
||||
|
||||
:param version: package version
|
||||
:type version: str
|
||||
:param revision: package revision
|
||||
:type revision: str
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
return self.has_definition(version, 'command.json')
|
||||
return self.has_definition(revision, 'command.json')
|
||||
|
||||
def has_marathon_definition(self, version):
|
||||
def has_marathon_definition(self, revision):
|
||||
"""Returns true if the package defines a Marathon json. false otherwise.
|
||||
|
||||
:param version: package version
|
||||
:type version: str
|
||||
:param revision: package revision
|
||||
:type revision: str
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
return self.has_definition(version, 'marathon.json')
|
||||
return self.has_definition(revision, 'marathon.json')
|
||||
|
||||
def config_json(self, version):
|
||||
def config_json(self, revision):
|
||||
"""Returns the JSON content of the config.json file.
|
||||
|
||||
:param revision: package revision
|
||||
:type revision: str
|
||||
:returns: Package config schema
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
return self._json(os.path.join(version, 'config.json'))
|
||||
return self._json(os.path.join(revision, 'config.json'))
|
||||
|
||||
def package_json(self, version):
|
||||
def package_json(self, revision):
|
||||
"""Returns the JSON content of the package.json file.
|
||||
|
||||
:param version: the package version
|
||||
:type version: str
|
||||
:param revision: the package revision
|
||||
:type revision: str
|
||||
:returns: Package data
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
return self._json(os.path.join(version, 'package.json'))
|
||||
return self._json(os.path.join(revision, 'package.json'))
|
||||
|
||||
def marathon_json(self, version, options):
|
||||
def marathon_json(self, revision, options):
|
||||
"""Returns the JSON content of the marathon.json template, after
|
||||
rendering it with options.
|
||||
|
||||
:param version: the package version
|
||||
:type version: str
|
||||
:param revision: the package revision
|
||||
:type revision: str
|
||||
:param options: the template options to use in rendering
|
||||
:type options: dict
|
||||
:rtype: dict
|
||||
@@ -1308,11 +1311,11 @@ class Package():
|
||||
|
||||
init_desc = self._render_template(
|
||||
'marathon.json',
|
||||
version,
|
||||
revision,
|
||||
options)
|
||||
|
||||
# Add package metadata
|
||||
package_labels = _make_package_labels(self, version, options)
|
||||
package_labels = _make_package_labels(self, revision, options)
|
||||
|
||||
# Preserve existing labels
|
||||
labels = init_desc.get('labels', {})
|
||||
@@ -1322,35 +1325,35 @@ class Package():
|
||||
|
||||
return init_desc
|
||||
|
||||
def command_json(self, version, options):
|
||||
def command_json(self, revision, options):
|
||||
"""Returns the JSON content of the comand.json template, after
|
||||
rendering it with options.
|
||||
|
||||
:param version: the package version
|
||||
:type version: str
|
||||
:param revision: the package revision
|
||||
:type revision: str
|
||||
:param options: the template options to use in rendering
|
||||
:type options: dict
|
||||
:returns: Package data
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
template = self._data(os.path.join(version, 'command.json'))
|
||||
template = self._data(os.path.join(revision, 'command.json'))
|
||||
rendered = pystache.render(template, options)
|
||||
return json.loads(rendered)
|
||||
|
||||
def _render_template(self, name, version, options):
|
||||
def _render_template(self, name, revision, options):
|
||||
"""Render a template.
|
||||
|
||||
:param name: the file name of the template
|
||||
:type name: str
|
||||
:param version: the package version
|
||||
:type version: str
|
||||
:param revision: the package revision
|
||||
:type revision: str
|
||||
:param options: the template options to use in rendering
|
||||
:type options: dict
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
template = self._data(os.path.join(version, name))
|
||||
template = self._data(os.path.join(revision, name))
|
||||
return util.render_mustache_json(template, options)
|
||||
|
||||
def _json(self, path):
|
||||
@@ -1377,60 +1380,58 @@ class Package():
|
||||
full_path = os.path.join(self.path, path)
|
||||
return util.read_file(full_path)
|
||||
|
||||
def package_versions(self):
|
||||
"""Returns all of the available package versions, most recent first.
|
||||
def package_revisions(self):
|
||||
"""Returns all of the available package revisions, most recent first.
|
||||
|
||||
Note that the result does not describe versions of the package, not
|
||||
the software described by the package.
|
||||
|
||||
:returns: Available versions of this package
|
||||
:returns: Available revisions of this package
|
||||
:rtype: [str]
|
||||
"""
|
||||
|
||||
vs = [f for f in os.listdir(self.path) if not f.startswith('.')]
|
||||
vs.reverse()
|
||||
vs = sorted((f for f in os.listdir(self.path)
|
||||
if not f.startswith('.')), key=int, reverse=True)
|
||||
return vs
|
||||
|
||||
def software_versions(self):
|
||||
"""Returns a mapping from the package version to the version of the
|
||||
software described by the package.
|
||||
def package_revisions_map(self):
|
||||
"""Returns a mapping from the package revision to the package version.
|
||||
|
||||
:returns: Map from package versions to versions of the softwre.
|
||||
:rtype: dict
|
||||
:returns: Map from package revision to package version
|
||||
:rtype: OrderedDict
|
||||
"""
|
||||
|
||||
software_package_map = collections.OrderedDict()
|
||||
for v in self.package_versions():
|
||||
pkg_json = self.package_json(v)
|
||||
software_package_map[v] = pkg_json['version']
|
||||
return software_package_map
|
||||
package_version_map = collections.OrderedDict()
|
||||
for rev in self.package_revisions():
|
||||
pkg_json = self.package_json(rev)
|
||||
package_version_map[rev] = pkg_json['version']
|
||||
return package_version_map
|
||||
|
||||
def latest_version(self):
|
||||
"""Returns the latest package version.
|
||||
def latest_package_revision(self, package_version=None):
|
||||
"""Returns the most recent package revision, for a
|
||||
given package version if specified.
|
||||
|
||||
:returns: The latest version of this package
|
||||
:rtype: str
|
||||
:param package_version: a given package version
|
||||
:type package_version: str
|
||||
:returns: package revision
|
||||
:rtype: str | None
|
||||
"""
|
||||
|
||||
pkg_versions = self.package_versions()
|
||||
if package_version:
|
||||
pkg_rev_map = self.package_revisions_map()
|
||||
# depends on package_revisions() returning an OrderedDict
|
||||
if package_version in pkg_rev_map.values():
|
||||
return next(pkg_rev for pkg_rev in reversed(pkg_rev_map)
|
||||
if pkg_rev_map[pkg_rev] == package_version)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
pkg_revisions = self.package_revisions()
|
||||
revision = pkg_revisions[0]
|
||||
|
||||
if len(pkg_versions) is 0:
|
||||
raise DCOSException(
|
||||
'No versions found for package [{}]'.format(self.name()))
|
||||
|
||||
pkg_versions.sort()
|
||||
return pkg_versions[-1]
|
||||
return revision
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
v, error = self.latest_version()
|
||||
if error is not None:
|
||||
return error.error()
|
||||
|
||||
pkg_json, error = self.package_json(v)
|
||||
|
||||
if error is not None:
|
||||
return error.error()
|
||||
rev = self.latest_package_revision()
|
||||
pkg_json = self.package_json(rev)
|
||||
|
||||
return json.dumps(pkg_json)
|
||||
|
||||
|
||||
@@ -179,13 +179,13 @@ def noun(executable_path):
|
||||
return noun
|
||||
|
||||
|
||||
def _write_package_json(pkg, version):
|
||||
def _write_package_json(pkg, revision):
|
||||
""" Write package.json locally.
|
||||
|
||||
:param pkg: the package being installed
|
||||
:type pkg: Package
|
||||
:param version: the package version to install
|
||||
:type version: str
|
||||
:param revision: the package revision to install
|
||||
:type revision: str
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
@@ -193,28 +193,28 @@ def _write_package_json(pkg, version):
|
||||
|
||||
package_path = os.path.join(pkg_dir, 'package.json')
|
||||
|
||||
package_json = pkg.package_json(version)
|
||||
package_json = pkg.package_json(revision)
|
||||
|
||||
with util.open_file(package_path, 'w') as package_file:
|
||||
json.dump(package_json, package_file)
|
||||
|
||||
|
||||
def _write_package_version(pkg, version):
|
||||
""" Write package version locally.
|
||||
def _write_package_revision(pkg, revision):
|
||||
""" Write package revision locally.
|
||||
|
||||
:param pkg: the package being installed
|
||||
:type pkg: Package
|
||||
:param version: the package version to install
|
||||
:type version: str
|
||||
:param revision: the package revision to install
|
||||
:type revision: str
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
pkg_dir = package_dir(pkg.name())
|
||||
|
||||
version_path = os.path.join(pkg_dir, 'version')
|
||||
revision_path = os.path.join(pkg_dir, 'version')
|
||||
|
||||
with util.open_file(version_path, 'w') as version_file:
|
||||
version_file.write(version)
|
||||
with util.open_file(revision_path, 'w') as revision_file:
|
||||
revision_file.write(revision)
|
||||
|
||||
|
||||
def _write_package_source(pkg):
|
||||
@@ -233,13 +233,13 @@ def _write_package_source(pkg):
|
||||
source_file.write(pkg.registry.source.url)
|
||||
|
||||
|
||||
def _install_env(pkg, version, options):
|
||||
def _install_env(pkg, revision, options):
|
||||
""" Install subcommand virtual env.
|
||||
|
||||
:param pkg: the package to install
|
||||
:type pkg: Package
|
||||
:param version: the package version to install
|
||||
:type version: str
|
||||
:param revision: the package revision to install
|
||||
:type revision: str
|
||||
:param options: package parameters
|
||||
:type options: dict
|
||||
:rtype: None
|
||||
@@ -247,7 +247,7 @@ def _install_env(pkg, version, options):
|
||||
|
||||
pkg_dir = package_dir(pkg.name())
|
||||
|
||||
install_operation = pkg.command_json(version, options)
|
||||
install_operation = pkg.command_json(revision, options)
|
||||
|
||||
env_dir = os.path.join(pkg_dir,
|
||||
constants.DCOS_SUBCOMMAND_VIRTUALENV_SUBDIR)
|
||||
@@ -262,13 +262,13 @@ def _install_env(pkg, version, options):
|
||||
install_operation.keys()))
|
||||
|
||||
|
||||
def install(pkg, version, options):
|
||||
def install(pkg, revision, options):
|
||||
"""Installs the dcos cli subcommand
|
||||
|
||||
:param pkg: the package to install
|
||||
:type pkg: Package
|
||||
:param version: the package version to install
|
||||
:type version: str
|
||||
:param revision: the package revision to install
|
||||
:type revision: str
|
||||
:param options: package parameters
|
||||
:type options: dict
|
||||
:rtype: None
|
||||
@@ -277,11 +277,11 @@ def install(pkg, version, options):
|
||||
pkg_dir = package_dir(pkg.name())
|
||||
util.ensure_dir(pkg_dir)
|
||||
|
||||
_write_package_json(pkg, version)
|
||||
_write_package_version(pkg, version)
|
||||
_write_package_json(pkg, revision)
|
||||
_write_package_revision(pkg, revision)
|
||||
_write_package_source(pkg)
|
||||
|
||||
_install_env(pkg, version, options)
|
||||
_install_env(pkg, revision, options)
|
||||
|
||||
|
||||
def _subcommand_dir():
|
||||
@@ -432,14 +432,14 @@ class InstalledSubcommand(object):
|
||||
|
||||
return package_dir(self.name)
|
||||
|
||||
def package_version(self):
|
||||
def package_revision(self):
|
||||
"""
|
||||
:returns: this subcommand's version.
|
||||
:returns: this subcommand's revision.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
version_path = os.path.join(self._dir(), 'version')
|
||||
return util.read_file(version_path)
|
||||
revision_path = os.path.join(self._dir(), 'version')
|
||||
return util.read_file(revision_path)
|
||||
|
||||
def package_source(self):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user