Merge pull request #268 from mesosphere/version

add --package-version option for installing packages
This commit is contained in:
tamarrow
2015-07-27 16:07:37 -07:00
13 changed files with 342 additions and 275 deletions

View 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"
]

View File

@@ -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)

View File

@@ -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'

View File

@@ -81,6 +81,7 @@ setup(
package_data={
'dcoscli': [
'data/*.json',
'data/help/*.txt',
'data/config-schema/*.json',
],
},

View 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"
]

View File

@@ -61,7 +61,7 @@
"framework"
],
"versions": [
"0.8.1",
"0.9.0-RC3"
"0.9.0-RC3",
"0.8.1"
]
}

View File

@@ -23,7 +23,7 @@
"framework"
],
"versions": [
"0.8.1",
"0.9.0-RC3"
"0.9.0-RC3",
"0.8.1"
]
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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):
"""