store labels locally

This commit is contained in:
Michael Gummelt
2015-04-23 16:40:03 -07:00
parent 6c66c768a4
commit b573ee22c3
8 changed files with 407 additions and 228 deletions

View File

@@ -1,5 +1,4 @@
import json
import logging
import os
import sys
import uuid
@@ -7,7 +6,7 @@ import uuid
import dcoscli
import requests
import rollbar
from dcos.api import config, constants
from dcos.api import config, constants, util
from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY,
SEGMENT_IO_CLI_ERROR_EVENT,
SEGMENT_IO_CLI_EVENT, SEGMENT_IO_WRITE_KEY_DEV,
@@ -15,7 +14,7 @@ from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY,
from futures import ThreadPoolExecutor
from requests.auth import HTTPBasicAuth
logger = logging.getLogger(__name__)
logger = util.get_logger(__name__)
session_id = uuid.uuid4().hex

View File

@@ -50,7 +50,7 @@ import dcoscli
import docopt
import pkg_resources
from dcos.api import (cmds, config, constants, emitting, errors, marathon,
options, package, util)
options, package, subcommand, util)
emitter = emitting.FlatEmitter()
@@ -256,13 +256,13 @@ def _describe(package_name):
return 0
def _install(package_name, options_file, app_id, cli, app):
def _install(package_name, options_path, app_id, cli, app):
"""Install the specified package.
:param package_name: the package to install
:type package_name: str
:param options_file: path to file containing option values
:type options_file: str
:param options_path: path to file containing option values
:type options_path: str
:param app_id: app ID for installation of this package
:type app_id: str
:param cli: indicates if the cli should be installed
@@ -291,23 +291,26 @@ def _install(package_name, options_file, app_id, cli, app):
"repositories"))
return 1
options_json = {}
if options_file is not None:
try:
options_fd = open(options_file)
options_json = json.load(options_fd)
except Exception as e:
emitter.publish(e.message)
return 1
# TODO(CD): Make package version to install configurable
pkg_version, version_error = pkg.latest_version()
if version_error is not None:
emitter.publish(version_error)
return 1
if options_path is None:
options = {}
else:
try:
with open(options_path) as options_file:
user_options = json.load(options_file)
options, err = pkg.options(pkg_version, user_options)
if err is not None:
emitter.publish(err)
return 1
except Exception as e:
emitter.publish(errors.DefaultError(e.message))
return 1
if app:
# Install in Marathon
version_map, version_error = pkg.software_versions()
@@ -331,8 +334,9 @@ def _install(package_name, options_file, app_id, cli, app):
pkg,
pkg_version,
init_client,
options_json,
options,
app_id)
if install_error is not None:
emitter.publish(install_error)
return 1
@@ -342,7 +346,7 @@ def _install(package_name, options_file, app_id, cli, app):
emitter.publish('Installing CLI subcommand for package [{}]'.format(
pkg.name()))
err = package.install_subcommand(pkg, pkg_version, options_json)
err = subcommand.install(pkg, pkg_version, options)
if err is not None:
emitter.publish(err)
return 1

View File

@@ -129,11 +129,9 @@ def _install(package):
dcos_config = config.load_from_path(os.environ[constants.DCOS_CONFIG_ENV])
install_operation = {
'pip': [package]
}
pip_operation = [package]
if 'subcommand.pip_find_links' in dcos_config:
install_operation['pip'].append(
pip_operation.append(
'--find-links {}'.format(dcos_config['subcommand.pip_find_links']))
distribution_name, err = _distribution_name(package)
@@ -143,10 +141,13 @@ def _install(package):
subcommand for a package, run `dcos package install <package> --cli` instead"))
return 1
err = subcommand.install(
env_dir = os.path.join(subcommand.package_dir(distribution_name),
constants.DCOS_SUBCOMMAND_VIRTUALENV_SUBDIR)
err = subcommand.install_with_pip(
distribution_name,
install_operation,
util.dcos_path())
env_dir,
pip_operation)
if err is not None:
emitter.publish(err)
return 1

View File

@@ -1,6 +1,8 @@
import json
import os
import six
from dcos.api import subcommand
from common import exec_command
@@ -149,7 +151,7 @@ def test_bad_install():
'--options=tests/data/package/mesos-dns-config-bad.json'])
assert returncode == 1
assert stdout == b'Installing package [mesos-dns] version [alpha]\n'
assert stdout == b''
assert stderr == b"""\
Error: 'mesos-dns/config-url' is a required property
@@ -174,36 +176,81 @@ def test_install():
assert stderr == b''
def test_package_labels():
app_labels = get_app_labels('mesos-dns')
expected_metadata = b"""\
eyJkZXNjcmlwdGlvbiI6ICJETlMtYmFzZWQgc2VydmljZSBkaXNjb3ZlcnkgZm9yIE1lc29zLiIsI\
CJtYWludGFpbmVyIjogInN1cHBvcnRAbWVzb3NwaGVyZS5pbyIsICJuYW1lIjogIm1lc29zLWRucy\
IsICJwb3N0SW5zdGFsbE5vdGVzIjogIlBsZWFzZSByZWZlciB0byB0aGUgdHV0b3JpYWwgaW5zdHJ\
1Y3Rpb25zIGZvciBmdXJ0aGVyIHNldHVwIHJlcXVpcmVtZW50czogaHR0cDovL21lc29zcGhlcmUu\
Z2l0aHViLmlvL21lc29zLWRucy9kb2NzL3R1dG9yaWFsLWdjZS5odG1sIiwgInNjbSI6ICJodHRwc\
zovL2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9tZXNvcy1kbnMuZ2l0IiwgInRhZ3MiOiBbIm1lc29zcG\
hlcmUiXSwgInZlcnNpb24iOiAiYWxwaGEiLCAid2Vic2l0ZSI6ICJodHRwOi8vbWVzb3NwaGVyZS5\
naXRodWIuaW8vbWVzb3MtZG5zIn0=\
def test_package_metadata():
returncode, stdout, stderr = exec_command(['dcos',
'package',
'install',
'helloworld'])
assert returncode == 0
assert stdout == b"""Installing package [helloworld] version [0.1.0]
Installing CLI subcommand for package [helloworld]
"""
actual_metadata = app_labels.get('DCOS_PACKAGE_METADATA')
assert(six.b(actual_metadata) == expected_metadata)
assert stderr == b''
expected_registry_version = b'0.1.0-alpha'
actual_registry_version = app_labels.get('DCOS_PACKAGE_REGISTRY_VERSION')
assert(six.b(actual_registry_version) == expected_registry_version)
# test marathon labels
expected_metadata = b"""eyJkZXNjcmlwdGlvbiI6ICJFeGFtcGxlIERDT1MgYXBwbGljYX\
Rpb24gcGFja2FnZSIsICJtYWludGFpbmVyIjogInN1cHBvcnRAbWVzb3NwaGVyZS5pbyIsICJuYW1l\
IjogImhlbGxvd29ybGQiLCAidGFncyI6IFsibWVzb3NwaGVyZSIsICJleGFtcGxlIiwgInN1YmNvbW\
1hbmQiXSwgInZlcnNpb24iOiAiMC4xLjAiLCAid2Vic2l0ZSI6ICJodHRwczovL2dpdGh1Yi5jb20v\
bWVzb3NwaGVyZS9kY29zLWhlbGxvd29ybGQifQ=="""
expected_name = b'mesos-dns'
actual_name = app_labels.get('DCOS_PACKAGE_NAME')
assert(six.b(actual_name) == expected_name)
expected_version = b'alpha'
actual_version = app_labels.get('DCOS_PACKAGE_VERSION')
assert(six.b(actual_version) == expected_version)
expected_command = b"""eyJwaXAiOiBbImh0dHA6Ly9kb3dubG9hZHMubWVzb3NwaGVyZS5\
pby9kY29zLWNsaS9kY29zLTAuMS4wLXB5Mi5weTMtbm9uZS1hbnkud2hsIiwgImdpdCtodHRwczovL\
2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9kY29zLWhlbGxvd29ybGQuZ2l0I2Rjb3MtaGVsbG93b3JsZD0\
wLjEuMCJdfQ=="""
expected_source = b'git://github.com/mesosphere/universe.git'
actual_source = app_labels.get('DCOS_PACKAGE_SOURCE')
assert(six.b(actual_source) == expected_source)
expected_labels = {
'DCOS_PACKAGE_METADATA': expected_metadata,
'DCOS_PACKAGE_COMMAND': expected_command,
'DCOS_PACKAGE_REGISTRY_VERSION': b'0.1.0-alpha',
'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 = {
"website": "https://github.com/mesosphere/dcos-helloworld",
"maintainer": "support@mesosphere.io",
"name": "helloworld",
"tags": ["mesosphere", "example", "subcommand"],
"version": "0.1.0",
"description": "Example DCOS application package"
}
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
returncode, stdout, stderr = exec_command(
['dcos', 'package', 'uninstall', 'helloworld'])
assert returncode == 0
assert stdout == b''
assert stderr == b''
def test_install_with_id():

View File

@@ -1,6 +1,9 @@
DCOS_DIR = ".dcos"
"""DCOS data directory. Can store subcommands and the config file."""
DCOS_SUBCOMMAND_VIRTUALENV_SUBDIR = 'env'
"""In a package's directory, this is the virtualenv subdirectory."""
DCOS_SUBCOMMAND_SUBDIR = 'subcommands'
"""Name of the subdirectory that contains all of the subcommands. This is
relative to the location of the executable."""

View File

@@ -28,42 +28,11 @@ PACKAGE_NAME_KEY = 'DCOS_PACKAGE_NAME'
PACKAGE_VERSION_KEY = 'DCOS_PACKAGE_VERSION'
PACKAGE_SOURCE_KEY = 'DCOS_PACKAGE_SOURCE'
PACKAGE_FRAMEWORK_KEY = 'DCOS_PACKAGE_IS_FRAMEWORK'
PACKAGE_RELEASE_KEY = 'DCOS_PACKAGE_RELEASE'
PACKAGE_COMMAND_KEY = 'DCOS_PACKAGE_COMMAND'
def _merge_options(pkg, version, user_options):
"""
:param pkg: the package to install
:type pkg: Package
:param version: the package version to install
:type version: str
:param user_options: package parameters
:type user_options: dict
:returns: a dictionary with the user supplied options
:rtype: (dict, dcos.api.errors.Error)
"""
if user_options is None:
user_options = {}
config_schema, err = pkg.config_json(version)
if err is not None:
return (None, err)
default_options, err = _extract_default_values(config_schema)
if err is not None:
return (None, err)
# Merge option overrides
options = dict(list(default_options.items()) + list(user_options.items()))
# Validate options with the config schema
err = util.validate_json(options, config_schema)
if err is not None:
return (None, err)
return (options, None)
def install_app(pkg, version, init_client, user_options, app_id):
def install_app(pkg, version, init_client, options, app_id):
"""Installs a package's application
:param pkg: the package to install
@@ -72,29 +41,20 @@ def install_app(pkg, version, init_client, user_options, app_id):
:type version: str
:param init_client: the program to use to run the package
:type init_client: object
:param user_options: package parameters
:type user_options: dict
:param options: package parameters
:type options: dict
:param app_id: app ID for installation of this package
:type app_id: str
:rtype: Error
"""
options, err = _merge_options(pkg, version, user_options)
if err is not None:
return err
# Insert option parameters into the init template
template, err = pkg.marathon_template(version)
if err is not None:
return err
# Render the init template with the marshaled options
init_desc, err = util.render_mustache_json(template, options)
init_desc, err = pkg.marathon_json(version, options)
if err is not None:
return err
# Add package metadata
package_labels, err = _make_package_labels(pkg, version)
package_labels, err = _make_package_labels(pkg, version, options)
if err is not None:
return err
@@ -115,23 +75,24 @@ def install_app(pkg, version, init_client, user_options, app_id):
return err
def _make_package_labels(pkg, version):
"""
def _make_package_labels(pkg, version, 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 options: package parameters
:type options: dict
:returns: Marathon app labels
:rtype: (dict, Error)
"""
metadata, meta_error = pkg.package_json(version)
if meta_error is not None:
return (None, meta_error)
metadata_json_string = json.dumps(metadata, sort_keys=True)
metadata_bytes = six.b(metadata_json_string)
encoded_metadata = base64.b64encode(metadata_bytes).decode('utf-8')
encoded_metadata = _base64_encode(metadata)
is_framework = metadata.get('framework')
if not is_framework:
@@ -148,43 +109,32 @@ def _make_package_labels(pkg, version):
PACKAGE_VERSION_KEY: metadata['version'],
PACKAGE_SOURCE_KEY: pkg.registry.source.url,
PACKAGE_FRAMEWORK_KEY: str(is_framework),
PACKAGE_REGISTRY_VERSION_KEY: package_registry_version
PACKAGE_REGISTRY_VERSION_KEY: package_registry_version,
PACKAGE_RELEASE_KEY: str(version)
}
if pkg.is_command_defined(version):
command, cmd_error = pkg.command_json(version, options)
if cmd_error is not None:
return (None, cmd_error)
package_labels[PACKAGE_COMMAND_KEY] = _base64_encode(command)
return (package_labels, None)
def install_subcommand(pkg, version, user_options):
"""Installs a package's command line interface
def _base64_encode(dictionary):
"""Returns base64(json(dictionary)).
:param pkg: the package to install
:type pkg: Package
:param version: the package version to install
:type version: str
:param user_options: package parameters
:type user_options: dict
:rtype: dcos.api.errors.Error
:param dictionary: dict to encode
:type dictionary: dict
:returns: base64 encoding
:rtype: str
"""
options, err = _merge_options(pkg, version, user_options)
if err is not None:
return err
# Insert option parameters into the init template
init_template, err = pkg.command_template(version)
if err is not None:
return err
rendered_template = pystache.render(init_template, options)
install_operation, err = util.load_jsons(rendered_template)
if err is not None:
return err
return subcommand.install(
pkg.name(),
install_operation,
util.dcos_path())
json_str = json.dumps(dictionary, sort_keys=True)
str_bytes = six.b(json_str)
return base64.b64encode(str_bytes).decode('utf-8')
def uninstall(package_name, remove_all, app_id, init_client):
@@ -875,7 +825,7 @@ class Registry():
def source(self):
"""Returns the associated upstream package source for this registry.
:rtype: str
:rtype: Source
"""
return self._source
@@ -972,7 +922,6 @@ class Package():
"""
def __init__(self, registry, path):
assert os.path.isdir(path)
self._registry = registry
self.path = path
@@ -986,6 +935,40 @@ class Package():
return os.path.basename(self.path)
def options(self, version, 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 user_options: package parameters
:type user_options: dict
:returns: a dictionary with the user supplied options
:rtype: (dict, dcos.api.errors.Error)
"""
if user_options is None:
user_options = {}
config_schema, err = self.config_json(version)
if err is not None:
return (None, err)
default_options, err = _extract_default_values(config_schema)
if err is not None:
return (None, err)
# Merge option overrides
options = dict(list(default_options.items()) +
list(user_options.items()))
# Validate options with the config schema
err = util.validate_json(options, config_schema)
if err is not None:
return (None, err)
return (options, None)
@property
def registry(self):
"""Returns the containing registry for this package.
@@ -1008,15 +991,6 @@ class Package():
self.path,
os.path.join(version, 'command.json')))
def command_template(self, version):
"""Returns the JSON content of the command.json file.
:returns: Package command data
:rtype: (str, Error)
"""
return self._data(os.path.join(version, 'command.json'))
def config_json(self, version):
"""Returns the JSON content of the config.json file.
@@ -1029,20 +1003,67 @@ class Package():
def package_json(self, version):
"""Returns the JSON content of the package.json file.
:param version: the package version
:type version: str
:returns: Package data
:rtype: (dict, Error)
"""
return self._json(os.path.join(version, 'package.json'))
def marathon_template(self, version):
"""Returns the JSON content of the marathon.json file.
def marathon_json(self, version, options):
"""Returns the JSON content of the marathon.json template, after
rendering it with options.
:returns: Package marathon data
:rtype: (str, Error)
:param version: the package version
:type version: str
:param options: the template options to use in rendering
:type options: dict
:rtype: (dict, Error)
"""
return self._data(os.path.join(version, 'marathon.json'))
return self._render_template('marathon.json', version, options)
def command_json(self, version, options):
"""Returns the JSON content of the comand.json template, after
rendering it with options.
:param version: the package version
:type version: str
:param options: the template options to use in rendering
:type options: dict
:returns: Package data
:rtype: (dict, Error)
"""
template, err = self._data(os.path.join(version, 'command.json'))
if err is not None:
return (None, err)
rendered = pystache.render(template, options)
return (json.loads(rendered), None)
def _render_template(self, name, version, options):
"""Render a template.
:param name: the file name of the template
:type name: str
:param version: the package version
:type version: str
:param options: the template options to use in rendering
:type options: dict
:rtype: (dict, Error)
"""
template, err = self._data(os.path.join(version, name))
if err is not None:
return (None, err)
json, err = util.render_mustache_json(template, options)
if err is not None:
return (None, err)
return (json, None)
def _json(self, path):
"""Returns the json content of the supplied file, relative to the

View File

@@ -38,21 +38,6 @@ def command_executables(subcommand, dcos_path):
return (executables[0], None)
BIN_DIRECTORY = 'Scripts' if util.is_windows_platform() else 'bin'
def _subcommand_dir():
"""Returns path to the subcommand directory. This directory contains
a virtualenv for each installed subcommand.
:returns: path to the subcommand directory
:rtype: str
"""
return os.path.expanduser(os.path.join("~",
constants.DCOS_DIR,
constants.DCOS_SUBCOMMAND_SUBDIR))
def list_paths(dcos_path):
"""List the real path to executable dcos subcommand programs.
@@ -71,24 +56,19 @@ def list_paths(dcos_path):
_is_executable(os.path.join(binpath, filename)))
]
subcommand_directory = _subcommand_dir()
subcommands = []
for package in distributions(dcos_path):
bin_dir = os.path.join(package_dir(package),
constants.DCOS_SUBCOMMAND_VIRTUALENV_SUBDIR,
BIN_DIRECTORY)
subcommands = [
os.path.join(subcommand_directory, package, BIN_DIRECTORY, filename)
for filename in os.listdir(bin_dir):
path = os.path.join(bin_dir, filename)
for package in distributions(dcos_path)
if (filename.startswith(constants.DCOS_COMMAND_PREFIX) and
_is_executable(path)):
for filename in os.listdir(
os.path.join(subcommand_directory, package, BIN_DIRECTORY))
if (filename.startswith(constants.DCOS_COMMAND_PREFIX) and
_is_executable(
os.path.join(
subcommand_directory,
package,
BIN_DIRECTORY,
filename)))
]
subcommands.append(path)
return commands + subcommands
@@ -114,10 +94,17 @@ def distributions(dcos_path):
:rtype: list of str
"""
subcommand_directory = _subcommand_dir()
subcommand_dir = _subcommand_dir()
if os.path.isdir(subcommand_directory):
return os.listdir(subcommand_directory)
if os.path.isdir(subcommand_dir):
return [
subdir for subdir in os.listdir(subcommand_dir)
if os.path.isdir(
os.path.join(
subcommand_dir,
subdir,
constants.DCOS_SUBCOMMAND_VIRTUALENV_SUBDIR))
]
else:
return []
@@ -182,32 +169,88 @@ def noun(executable_path):
return noun
def install(distribution_name, install_operation, dcos_path):
"""Installs the dcos cli subcommand
def _write_package_json(pkg, version):
""" Write package.json locally.
:param distribution_name: the name of the package
:type distribution_name: str
:param install_operation: operation to use to install subcommand
:type install_operation: dict
:param dcos_path: path to the dcos cli directory
:type dcos_path: str
:param pkg: the package being installed
:type pkg: Package
:param version: the package version to install
:type version: str
:rtype: Error
"""
pkg_dir = package_dir(pkg.name())
package_path = os.path.join(pkg_dir, 'package.json')
package_json, err = pkg.package_json(version)
if err is not None:
return err
with open(package_path, 'w') as package_file:
json.dump(package_json, package_file)
def _write_package_version(pkg, version):
""" Write package version locally.
:param pkg: the package being installed
:type pkg: Package
:param version: the package version to install
:type version: str
:rtype: None
"""
pkg_dir = package_dir(pkg.name())
version_path = os.path.join(pkg_dir, 'version')
with open(version_path, 'w') as version_file:
version_file.write(version)
def _write_package_source(pkg):
""" Write package source locally.
:param pkg: the package being installed
:type pkg: Package
:rtype: None
"""
pkg_dir = package_dir(pkg.name())
source_path = os.path.join(pkg_dir, 'source')
with open(source_path, 'w') as source_file:
source_file.write(pkg.registry.source.url)
def _install_env(pkg, version, 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 options: package parameters
:type options: dict
:returns: an error if the subcommand failed; None otherwise
:rtype: dcos.api.errors.Error
"""
subcommand_directory = _subcommand_dir()
pkg_dir = package_dir(pkg.name())
if not os.path.exists(subcommand_directory):
logger.info('Creating directory: %r', subcommand_directory)
os.makedirs(subcommand_directory, 0o775)
install_operation, err = pkg.command_json(version, options)
if err is not None:
return err
package_directory = os.path.join(subcommand_directory, distribution_name)
env_dir = os.path.join(pkg_dir,
constants.DCOS_SUBCOMMAND_VIRTUALENV_SUBDIR)
if 'pip' in install_operation:
return _install_with_pip(
distribution_name,
os.path.join(dcos_path, BIN_DIRECTORY),
package_directory,
return install_with_pip(
pkg.name(),
env_dir,
install_operation['pip'])
else:
return errors.DefaultError(
@@ -215,53 +258,100 @@ def install(distribution_name, install_operation, dcos_path):
install_operation.keys()))
def uninstall(distribution_name, dcos_path):
def install(pkg, version, 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 options: package parameters
:type options: dict
:returns: an error if the subcommand failed; None otherwise
:rtype: dcos.api.errors.Error
"""
pkg_dir = package_dir(pkg.name())
util.ensure_dir(pkg_dir)
err = _write_package_json(pkg, version)
if err is not None:
return err
_write_package_version(pkg, version)
_write_package_source(pkg)
return _install_env(pkg, version, options)
def _subcommand_dir():
""" Returns ~/.dcos/subcommands """
return os.path.expanduser(os.path.join("~",
constants.DCOS_DIR,
constants.DCOS_SUBCOMMAND_SUBDIR))
# TODO(mgummelt): should be made private after "dcos subcommand" is removed
def package_dir(name):
""" Returns ~/.dcos/subcommands/<name>
:param name: package name
:type name: str
:rtype: str
"""
return os.path.join(_subcommand_dir(),
name)
def uninstall(package_name, dcos_path):
"""Uninstall the dcos cli subcommand
:param distribution_name: the name of the package
:type distribution_name: str
:param package_name: the name of the package
:type package_name: str
:param dcos_path: the path to the dcos cli directory
:type dcos_path: str
:returns: True if the subcommand was uninstalled
:rtype: bool
"""
subcommand_directory = os.path.join(_subcommand_dir(), distribution_name)
pkg_dir = package_dir(package_name)
if os.path.isdir(subcommand_directory):
shutil.rmtree(subcommand_directory)
if os.path.isdir(pkg_dir):
shutil.rmtree(pkg_dir)
return True
return False
BIN_DIRECTORY = 'Scripts' if util.is_windows_platform() else 'bin'
def _install_with_pip(
distribution_name,
bin_directory,
package_directory,
# TODO (mgummelt): should be made private after "dcos subcommand" is removed
def install_with_pip(
package_name,
env_directory,
requirements):
"""
:param distribution_name: the name of the package
:type distribution_name: str
:param bin_directory: the path to the directory containing the
executables (virtualenv, etc).
:type bin_directory: str
:param package_directory: the path to the directory for the package
:type package_directory: str
:param package_name: the name of the package
:type package_name: str
:param env_directory: the path to the directory in which to install the
package's virtual env
:type env_directory: str
:param requirements: the list of pip requirements
:type requirements: list of str
:returns: an Error if it failed to install the package; None otherwise
:rtype: dcos.api.errors.Error
"""
new_package_dir = not os.path.exists(package_directory)
bin_directory = os.path.join(util.dcos_path(), BIN_DIRECTORY)
new_package_dir = not os.path.exists(env_directory)
pip_path = os.path.join(package_directory, BIN_DIRECTORY, 'pip')
pip_path = os.path.join(env_directory, BIN_DIRECTORY, 'pip')
if not os.path.exists(pip_path):
cmd = [os.path.join(bin_directory, 'virtualenv'), package_directory]
cmd = [os.path.join(bin_directory, 'virtualenv'), env_directory]
if _execute_command(cmd) != 0:
return _generic_error(distribution_name)
return _generic_error(package_name)
with util.temptext() as text_file:
fd, requirement_path = text_file
@@ -272,7 +362,7 @@ def _install_with_pip(
print(line, file=requirements_file)
cmd = [
os.path.join(package_directory, BIN_DIRECTORY, 'pip'),
os.path.join(env_directory, BIN_DIRECTORY, 'pip'),
'install',
'--requirement',
requirement_path,
@@ -281,9 +371,9 @@ def _install_with_pip(
if _execute_command(cmd) != 0:
# We should remove the diretory that we just created
if new_package_dir:
shutil.rmtree(package_directory)
shutil.rmtree(env_directory)
return _generic_error(distribution_name)
return _generic_error(package_name)
return None
@@ -315,7 +405,7 @@ def _execute_command(command):
return process.returncode
def _generic_error(distribution_name):
def _generic_error(package_name):
"""
:param package: package name
:type: str
@@ -324,4 +414,4 @@ def _generic_error(distribution_name):
"""
return errors.DefaultError(
'Error installing {!r} package'.format(distribution_name))
'Error installing {!r} package'.format(package_name))

View File

@@ -57,6 +57,19 @@ def temptext():
shutil.rmtree(path, ignore_errors=True)
def ensure_dir(directory):
"""If `directory` does not exist, create it.
:param directory: path to the directory
:type directory: string
:rtype: None
"""
if not os.path.exists(directory):
logger.info('Creating directory: %r', directory)
os.makedirs(directory, 0o775)
def which(program):
"""Returns the path to the named executable program.
@@ -178,7 +191,6 @@ def load_jsons(value):
return (json.loads(value), None)
except:
error = sys.exc_info()[0]
logger = get_logger(__name__)
logger.error(
'Unhandled exception while loading JSON: %r -- %r',
value,
@@ -298,3 +310,5 @@ class CustomJsonRenderer(pystache.Renderer):
:rtype: str
"""
return json.dumps(val)
logger = get_logger(__name__)