Initial dcos cli commit

This commit is contained in:
José Armando García Sancio
2015-01-18 10:13:28 +00:00
commit 106f6a45ce
11 changed files with 423 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
dcos.egg-info/
.ropeproject/
env/
*.pyc

0
DESCRIPTION.rst Normal file
View File

68
README.rst Normal file
View File

@@ -0,0 +1,68 @@
CLI-poc
=======
Proof-of-concept for a CLI with modular subcommands.
Setup
-----
#. Make sure you meet requirements for installing packages_
#. Install the "wheel" project::
pip install wheel
#. Clone git repo for the dcos cli::
git clone git@github.com:mesosphere/dcos-cli.git
#. Change directory to the repo directory::
cd dcos-cli
#. Create a virtualenv for the dcos cli project::
virtualenv --prompt='(dcos-cli) ' env
Configure Development Environment
---------------------------------
#. Activate the virtualenv::
source env/bin/activate
#. Install project in develop mode::
pip install -e .
#. Export DCOS_PATH::
export DCOS_PATH=<path-to-project>/env
#. Export DCOS_CONFIG::
mkdir $DCOS_PATH/config
touch $DCOS_PATH/config.Dcos.toml
export DCOS_CONFIG=$DCOS_PATH/env/config/Dcos.toml
Running POC
-----------
#. List command help::
dcos --help
#. Run subcommand::
dcos config --help
Notes
-----
Submodule writing notes gathered so far:
#. Because we are using python's pip to install packages it looks like we can't install packages
that share the same python's package of other installed packages because they will conflict once
deployed to virtualenv directory structure.
#. Currently we require that subcommands implement an info command. For example dcos-subcommand
implements ``subcommand info``.
.. _packages: https://packaging.python.org/en/latest/installing.html#installing-requirements

0
data/data_file Normal file
View File

69
dcos/__init__.py Normal file
View File

@@ -0,0 +1,69 @@
"""
Usage:
dcos [--mesos=<uri>] <command> [<args>...]
dcos -h | --help
dcos --version
Options:
-h, --help Show this screen
--version Show version
--mesos=<uri> URI for the Mesos master
"""
import os
import subprocess
import docopt
def main():
subcommands = _list_external_subcommands(os.environ['DCOS_PATH'])
subcommand_summaries = _external_command_documentation(subcommands)
args = docopt.docopt(
_extend_usage_docopt(__doc__, subcommand_summaries),
version='dcos version 0.1.0', # TODO: grab from setuptool
options_first=True)
argv = [args['<command>']] + args['<args>']
# TODO: We need to figure out a way to make subcommand's discoverable
if args['<command>'] in subcommands:
command = 'dcos-{}'.format(args['<command>'])
exit(subprocess.call([command] + argv))
else:
exit(
'{!r} is not a dcos command. See "dcos --help".'.format(
args['<command>']))
def _list_external_subcommands(dcos_path):
# TODO: check that dir_path is directory?
prefix = 'dcos-'
return [filename[len(prefix):]
for dirpath, _, filenames
in os.walk(os.path.join(dcos_path, "bin"))
for filename in filenames
if filename.startswith(prefix)
and os.access(os.path.join(dirpath, filename), os.X_OK)
]
def _external_command_documentation(commands):
def info(commnand):
return subprocess.check_output(
['dcos-{}'.format(command), command, 'info'])
return [(command, info(command)) for command in commands]
def _extend_usage_docopt(doc, command_summaries):
# TODO: make sure that we deal with long commands
doc += '\nThe dcos commands are:'
for command, summary in command_summaries:
doc += '\n\t{:15}\t{}'.format(command, summary.strip())
return doc

75
dcos/config.py Normal file
View File

@@ -0,0 +1,75 @@
"""
Usage:
dcos config info
dcos config <name> [<value>]
dcos config --unset <name>
dcos config --help
Options:
-h, --help Show this screen
--unset Remove property from the config file
"""
import os
import docopt
import toml
def main():
config_path = os.environ['DCOS_CONFIG']
args = docopt.docopt(__doc__)
if args['config'] and args['info']:
print('Get and set DCOS command line options')
elif args['config'] and args['--unset']:
config = _load_config_file(config_path)
_unset_property(config, args['<name>'])
_save_config_file(config_path, config)
elif args['config'] and args['<value>'] is None:
config = _load_config_file(config_path)
print(_get_property(config, args['<name>']))
elif args['config']:
config = _load_config_file(config_path)
_set_property(config, args['<name>'], args['<value>'])
_save_config_file(config_path, config)
else:
print(args)
def _load_config_file(config_path):
with open(config_path) as config_file:
return toml.loads(config_file.read())
def _save_config_file(config_path, config):
serial = toml.dumps(config)
with open(config_path, 'w') as config_file:
config_file.write(serial)
def _get_property(config, name):
for section in name.split('.'):
config = config[section]
# TODO: Do we want to check that config is not a dictionary?
return config
def _set_property(config, name, value):
sections = name.split('.')
for section in sections[:-1]:
config = config[section]
config[sections[-1]] = value
def _unset_property(config, name):
sections = name.split('.')
for section in sections[:-1]:
config = config[section]
del config[sections[-1]]

31
dcos/subcommand.py Normal file
View File

@@ -0,0 +1,31 @@
"""
Usage:
dcos subcommand info
dcos subcommand install python <uri>
dcos subcommand -h | --help
Options:
-h, --help Show this screen
"""
import subprocess
import docopt
def main():
args = docopt.docopt(__doc__)
if args['subcommand'] and args['info']:
print('Manage DCOS external commands')
elif args['subcommand'] and args['install'] and args['python']:
print('Trying to install a python subcommand')
command = ['pip', 'install', args['<uri>']]
print('Running: {!r}'.format(command))
# For now we are just going to call pip and see if it works
exit_status = subprocess.call(command)
print(
'Using pip returned the following exit status: {!r}'.format(
exit_status))
else:
print(args)

5
setup.cfg Normal file
View File

@@ -0,0 +1,5 @@
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python
# 3. If at all possible, it is good practice to do this. If you cannot, you
# will need to generate wheels for each Python version that you support.
universal=1

105
setup.py Normal file
View File

@@ -0,0 +1,105 @@
from setuptools import setup, find_packages
from codecs import open
from os import path
# TODO: what license should we use?
here = path.abspath(path.dirname(__file__))
# Get the long description from the relevant file
with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f:
long_description = f.read()
setup(
name='dcos',
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='0.1.0',
description='Dcos cli poc project',
long_description=long_description,
# The project's main homepage.
url='https://github.com/mesosphere/CLI-poc',
# Author details
author='Mesosphere, Inc.',
author_email='team@mesosphere.io',
# Choose your license
license='TODO',
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
'Development Status :: 3 - Alpha',
# Indicate who your project is intended for
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'Topic :: Software Development :: User Interfaces',
# Pick your license as you wish (should match "license" above)
'License :: OSI Approved :: TODO License',
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
],
# What does your project relate to?
keywords='mesos apache marathon mesospehere command datacenter',
# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
# List run-time dependencies here. These will be installed by pip when your
# project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['docopt', 'toml'],
# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax, for
# example:
# $ pip install -e .[dev,test]
extras_require={
'dev': ['check-manifest'],
'test': ['coverage'],
},
# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
# have to be included in MANIFEST.in as well.
package_data={
'sample': ['package_data.dat'],
},
# Although 'package_data' is the preferred approach, in some case you may
# need to place data files outside of your packages.
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
data_files=[('my_data', ['data/data_file'])],
# To provide executable scripts, use entry points in preference to the
# "scripts" keyword. Entry points provide cross-platform support and allow
# pip to create the appropriate form of executable for the target platform.
entry_points={
'console_scripts': [
'dcos=dcos:main',
'dcos-subcommand=dcos.subcommand:main',
'dcos-config=dcos.config:main',
],
},
)

49
tests/test_config.py Normal file
View File

@@ -0,0 +1,49 @@
from dcos import config
import pytest
@pytest.fixture
def conf():
return {
'dcos': {
'user': 'principal',
'mesos_uri': 'zk://localhost/mesos'
},
'package': {
'repo_uri': 'git://localhost/mesosphere/package-repo.git'
}
}
def test_unset_property(conf):
expect = {
'dcos': {
'user': 'principal',
'mesos_uri': 'zk://localhost/mesos'
},
'package': {}
}
config._unset_property(conf, 'package.repo_uri')
assert conf == expect
def test_set_property(conf):
expect = {
'dcos': {
'user': 'group',
'mesos_uri': 'zk://localhost/mesos'
},
'package': {
'repo_uri': 'git://localhost/mesosphere/package-repo.git'
}
}
config._set_property(conf, 'dcos.user', 'group')
assert conf == expect
def test_get_property(conf):
config._get_property(conf, 'dcos.mesos_uri') == 'zk://localhost/mesos'

17
tests/test_dcos.py Normal file
View File

@@ -0,0 +1,17 @@
import dcos
def test_extend_usage_docopt():
command_summaries = [
('first', 'first summary'),
('second', ' second summary '),
('third', 'third summary\n')
]
expected = """
The dcos commands are:
\tfirst \tfirst summary
\tsecond \tsecond summary
\tthird \tthird summary"""
assert dcos._extend_usage_docopt('', command_summaries) == expected