@@ -1,6 +1,8 @@
|
||||
import requests
|
||||
import json
|
||||
import urllib
|
||||
|
||||
from . import errors
|
||||
import requests
|
||||
from dcos.api import errors
|
||||
|
||||
|
||||
class Client(object):
|
||||
@@ -19,22 +21,87 @@ class Client(object):
|
||||
self._host = host
|
||||
self._port = port
|
||||
|
||||
def _create_url(self, path):
|
||||
def _create_url(self, path, query_params=None):
|
||||
"""Creates the url from the provided path
|
||||
|
||||
:param path: Url path
|
||||
:type path: str
|
||||
:return: Constructed url
|
||||
:param query_params: Query string parameters
|
||||
:type query_params: dict
|
||||
:returns: Constructed url
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self._url_pattern.format(
|
||||
url = self._url_pattern.format(
|
||||
host=self._host,
|
||||
port=self._port,
|
||||
path=path)
|
||||
|
||||
if query_params is not None:
|
||||
query_string = urllib.urlencode(query_params)
|
||||
url = (url + '?{}').format(query_string)
|
||||
|
||||
return url
|
||||
|
||||
def _sanitize_app_id(self, app_id):
|
||||
"""
|
||||
:param app_id: Raw application ID
|
||||
:type app_id: str
|
||||
:returns: Sanitized application ID
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
# Add a leading '/' if necessary.
|
||||
if not app_id.startswith('/'):
|
||||
app_id = '/' + app_id
|
||||
return app_id
|
||||
|
||||
def _response_to_error(self, response):
|
||||
"""
|
||||
:param response: HTTP resonse object
|
||||
:type response: requests.Response
|
||||
:returns: The error embedded in the response JSON
|
||||
:rtype: Error
|
||||
"""
|
||||
|
||||
return Error('Error: {}'.format(response.json()['message']))
|
||||
|
||||
def get_app(self, app_id):
|
||||
"""Returns a representation of the requested application.
|
||||
:param app_id: The ID of the application.
|
||||
:type app_id: str
|
||||
:returns: The requested Marathon application
|
||||
:rtype: (dict, Error)
|
||||
"""
|
||||
|
||||
app_id = self._sanitize_app_id(app_id)
|
||||
|
||||
url = self._create_url('v2/apps' + app_id)
|
||||
response = requests.get(url)
|
||||
|
||||
if response.status_code == 200:
|
||||
app = response.json()['app']
|
||||
return (app, None)
|
||||
else:
|
||||
return (None, self._response_to_error(response))
|
||||
|
||||
def get_apps(self):
|
||||
"""Get a list of known applications.
|
||||
:returns: List of known applications.
|
||||
:rtype: (list of dict, Error)
|
||||
"""
|
||||
|
||||
url = self._create_url('v2/apps')
|
||||
response = requests.get(url)
|
||||
|
||||
if response.status_code == 200:
|
||||
apps = response.json()['apps']
|
||||
return (apps, None)
|
||||
else:
|
||||
return (None, self._response_to_error(response))
|
||||
|
||||
def start_app(self, app_resource):
|
||||
"""Create and start a new application
|
||||
"""Create and start a new application.
|
||||
|
||||
:param app_resource: Application resource
|
||||
:type app_resource: dict, bytes, or file
|
||||
@@ -48,11 +115,77 @@ class Client(object):
|
||||
if response.status_code == 201:
|
||||
return (True, None)
|
||||
else:
|
||||
return (
|
||||
None,
|
||||
Error(
|
||||
'Error talking to Marathon: {}'.format(
|
||||
response.json()['message'])))
|
||||
return (None, self._response_to_error(response))
|
||||
|
||||
def scale_app(self, app_id, instances, force=None):
|
||||
"""Scales an application to the requested number of instances.
|
||||
:param app_id: The ID of the application to scale.
|
||||
:type app_id: str
|
||||
:param instances: The requested number of instances.
|
||||
:type instances: int
|
||||
:param force: Whether to override running deployments.
|
||||
:type force: bool
|
||||
:returns: The resulting deployment ID.
|
||||
:rtype: (bool, Error)
|
||||
"""
|
||||
|
||||
if force is None:
|
||||
force = False
|
||||
|
||||
app_id = self._sanitize_app_id(app_id)
|
||||
|
||||
params = None
|
||||
if force:
|
||||
params = {'force': True}
|
||||
|
||||
url = self._create_url('v2/apps{}'.format(app_id), params)
|
||||
scale_json = json.loads('{{ "instances": {} }}'.format(int(instances)))
|
||||
response = requests.put(url, json=scale_json)
|
||||
|
||||
if response.status_code == 200:
|
||||
deployment = response.json()['deploymentId']
|
||||
return (deployment, None)
|
||||
else:
|
||||
return (None, self._response_to_error(response))
|
||||
|
||||
def suspend_app(self, app_id, force=None):
|
||||
"""Scales an application to zero instances.
|
||||
:param app_id: The ID of the application to suspend.
|
||||
:type app_id: str
|
||||
:param force: Whether to override running deployments.
|
||||
:type force: bool
|
||||
:returns: The resulting deployment ID.
|
||||
:rtype: (bool, Error)
|
||||
"""
|
||||
|
||||
return self.scale_app(app_id, 0, force)
|
||||
|
||||
def remove_app(self, app_id, force=None):
|
||||
"""Completely removes the requested application.
|
||||
:param app_id: The ID of the application to suspend.
|
||||
:type app_id: str
|
||||
:param force: Whether to override running deployments.
|
||||
:type force: bool
|
||||
:returns: Status of trying to remove the application.
|
||||
:rtype: (bool, Error)
|
||||
"""
|
||||
|
||||
if force is None:
|
||||
force = False
|
||||
|
||||
app_id = self._sanitize_app_id(app_id)
|
||||
|
||||
params = None
|
||||
if force:
|
||||
params = {'force': True}
|
||||
|
||||
url = self._create_url('v2/apps{}'.format(app_id), params)
|
||||
response = requests.delete(url)
|
||||
|
||||
if response.status_code == 200:
|
||||
return (True, None)
|
||||
else:
|
||||
return (None, self._response_to_error(response))
|
||||
|
||||
|
||||
class Error(errors.Error):
|
||||
|
||||
@@ -14,8 +14,7 @@ import os
|
||||
|
||||
import docopt
|
||||
import toml
|
||||
|
||||
from ...api import config, constants
|
||||
from dcos.api import config, constants
|
||||
|
||||
|
||||
def main():
|
||||
@@ -34,7 +33,7 @@ def main():
|
||||
|
||||
elif args['config'] and args['<value>'] is None:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
print(config[args['<name>']])
|
||||
print(toml_config[args['<name>']])
|
||||
|
||||
elif args['config']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
|
||||
@@ -15,8 +15,7 @@ import os
|
||||
import subprocess
|
||||
|
||||
import docopt
|
||||
|
||||
from ...api import constants, options
|
||||
from dcos.api import constants, options
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -18,8 +18,7 @@ import os
|
||||
import subprocess
|
||||
|
||||
import docopt
|
||||
|
||||
from ..api import constants
|
||||
from dcos.api import constants
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
"""
|
||||
Usage:
|
||||
dcos marathon info
|
||||
dcos marathon list
|
||||
dcos marathon describe <app_id>
|
||||
dcos marathon start <app_resource>
|
||||
dcos marathon scale <app_id> <instances> [--force]
|
||||
dcos marathon suspend <app_id> [--force]
|
||||
dcos marathon remove <app_id> [--force]
|
||||
dcos marathon --help
|
||||
dcos marathon --version
|
||||
|
||||
@@ -10,11 +15,11 @@ Options:
|
||||
--version Show version
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import docopt
|
||||
|
||||
from ...api import config, constants, marathon, options
|
||||
from dcos.api import config, constants, marathon, options
|
||||
|
||||
|
||||
def main():
|
||||
@@ -25,22 +30,99 @@ def main():
|
||||
|
||||
if args['marathon'] and args['info']:
|
||||
return _info()
|
||||
elif args['marathon'] and args['list']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
return _list(toml_config)
|
||||
elif args['marathon'] and args['describe']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
return _describe(args['<app_id>'], toml_config)
|
||||
elif args['marathon'] and args['start']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
return _start(args['<app_resource>'], toml_config)
|
||||
elif args['marathon'] and args['scale']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
return _scale(args['<app_id>'],
|
||||
args['<instances>'],
|
||||
args['--force'],
|
||||
toml_config)
|
||||
elif args['marathon'] and args['suspend']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
return _suspend(args['<app_id>'], args['--force'], toml_config)
|
||||
elif args['marathon'] and args['remove']:
|
||||
toml_config = config.Toml.load_from_path(config_path)
|
||||
return _remove(args['<app_id>'], args['--force'], toml_config)
|
||||
else:
|
||||
print(options.make_generic_usage_error(__doc__))
|
||||
return 1
|
||||
|
||||
|
||||
def _info():
|
||||
"""Print marathon cli information
|
||||
"""Print marathon cli information.
|
||||
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
print('Deploy and manage containers for Mesos')
|
||||
print('Deploy and manage applications on Apache Mesos')
|
||||
return 0
|
||||
|
||||
|
||||
def _create_client(config):
|
||||
"""Creates a Marathon client with the supplied configuration.
|
||||
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Marathon client
|
||||
:rtype: dcos.api.marathon.Client
|
||||
"""
|
||||
return marathon.Client(config['marathon.host'], config['marathon.port'])
|
||||
|
||||
|
||||
def _list(config):
|
||||
"""Lists known Marathon applications.
|
||||
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
client = _create_client(config)
|
||||
|
||||
apps, err = client.get_apps()
|
||||
if err is not None:
|
||||
print(err.error())
|
||||
return 1
|
||||
|
||||
if not apps:
|
||||
print("No apps to list.")
|
||||
|
||||
for app in apps:
|
||||
print(app['id'])
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _describe(app_id, config):
|
||||
"""Show details of a Marathon applications.
|
||||
|
||||
:param app_id: ID of the app to suspend
|
||||
:type app_id: str
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
client = _create_client(config)
|
||||
|
||||
app, err = client.get_app(app_id)
|
||||
if err is not None:
|
||||
print(err.error())
|
||||
return 1
|
||||
|
||||
print(json.dumps(app,
|
||||
sort_keys=True,
|
||||
indent=2))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -54,7 +136,7 @@ def _start(app_resource_path, config):
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
client = marathon.Client(config['marathon.host'], config['marathon.port'])
|
||||
client = _create_client(config)
|
||||
|
||||
with open(app_resource_path) as app_resource_file:
|
||||
success, err = client.start_app(app_resource_file)
|
||||
@@ -63,3 +145,75 @@ def _start(app_resource_path, config):
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _scale(app_id, instances, force, config):
|
||||
"""Suspends a running Marathon application.
|
||||
|
||||
:param app_id: ID of the app to suspend
|
||||
:type app_id: str
|
||||
:param instances: The requested number of instances.
|
||||
:type instances: int
|
||||
:param force: Whether to override running deployments.
|
||||
:type force: bool
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
client = _create_client(config)
|
||||
|
||||
deployment, err = client.scale_app(app_id, instances, force)
|
||||
if err is not None:
|
||||
print(err.error())
|
||||
return 1
|
||||
|
||||
print('Created deployment {}'.format(deployment))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _suspend(app_id, force, config):
|
||||
"""Suspends a running Marathon application.
|
||||
|
||||
:param app_id: ID of the app to suspend
|
||||
:type app_id: str
|
||||
:param force: Whether to override running deployments.
|
||||
:type force: bool
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
client = _create_client(config)
|
||||
|
||||
deployment, err = client.suspend_app(app_id, force)
|
||||
if err is not None:
|
||||
print(err.error())
|
||||
return 1
|
||||
|
||||
print('Created deployment {}'.format(deployment))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _remove(app_id, force, config):
|
||||
"""Remove a Marathon application.
|
||||
|
||||
:param app_id: ID of the app to remove
|
||||
:type app_id: str
|
||||
:param force: Whether to override running deployments.
|
||||
:type force: bool
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Process status
|
||||
:rtype: int
|
||||
"""
|
||||
client = _create_client(config)
|
||||
|
||||
success, err = client.remove_app(app_id, force)
|
||||
if err is not None:
|
||||
print(err.error())
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
@@ -11,8 +11,7 @@ Options:
|
||||
import subprocess
|
||||
|
||||
import docopt
|
||||
|
||||
from ...api import constants
|
||||
from dcos.api import constants
|
||||
|
||||
|
||||
def main():
|
||||
@@ -21,7 +20,7 @@ def main():
|
||||
version='dcos-subcommand version {}'.format(constants.version))
|
||||
|
||||
if args['subcommand'] and args['info']:
|
||||
print('Manage DCOS external commands')
|
||||
print('Manage external DCOS commands')
|
||||
elif args['subcommand'] and args['install'] and args['python']:
|
||||
print('Trying to install a python subcommand')
|
||||
command = ['pip', 'install', args['<uri>']]
|
||||
|
||||
8
setup.py
8
setup.py
@@ -71,7 +71,13 @@ setup(
|
||||
# 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', 'requests'],
|
||||
install_requires=[
|
||||
'docopt',
|
||||
'jsonschema',
|
||||
'pystache',
|
||||
'requests',
|
||||
'toml',
|
||||
],
|
||||
|
||||
# List additional groups of dependencies here (e.g. development
|
||||
# dependencies). You can install these using the following syntax, for
|
||||
|
||||
11
tests/data/marathon/sleep.json
Normal file
11
tests/data/marathon/sleep.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "test-app",
|
||||
"cmd": "sleep 1000",
|
||||
"cpus": 0.1,
|
||||
"mem": 16,
|
||||
"instances": 1,
|
||||
"labels": {
|
||||
"PACKAGE_ID": "test-app",
|
||||
"PACKAGE_VERSION": "1.2.3"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"cmd": "sleep 1000",
|
||||
"cpus": 0.25,
|
||||
"id": "sleeping-app",
|
||||
"instances": 1,
|
||||
"mem": 50
|
||||
}
|
||||
Reference in New Issue
Block a user