DCOS-364 Implements dcos app start
This commit is contained in:
@@ -105,12 +105,10 @@ class Client(object):
|
|||||||
'v2/apps{}/versions/{}'.format(app_id, version))
|
'v2/apps{}/versions/{}'.format(app_id, version))
|
||||||
|
|
||||||
logger.info('Getting %r', url)
|
logger.info('Getting %r', url)
|
||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
|
||||||
logger.info('Got (%r): %r', response.status_code, response.json())
|
logger.info('Got (%r): %r', response.status_code, response.json())
|
||||||
|
|
||||||
if response.status_code == 200:
|
if _success(response.status_code):
|
||||||
# Looks like Marathon return different JSON for versions
|
# Looks like Marathon return different JSON for versions
|
||||||
if version is None:
|
if version is None:
|
||||||
return (response.json()['app'], None)
|
return (response.json()['app'], None)
|
||||||
@@ -136,12 +134,10 @@ class Client(object):
|
|||||||
url = self._create_url('v2/apps{}/versions'.format(app_id))
|
url = self._create_url('v2/apps{}/versions'.format(app_id))
|
||||||
|
|
||||||
logger.info('Getting %r', url)
|
logger.info('Getting %r', url)
|
||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
|
||||||
logger.info('Got (%r): %r', response.status_code, response.json())
|
logger.info('Got (%r): %r', response.status_code, response.json())
|
||||||
|
|
||||||
if response.status_code == 200:
|
if _success(response.status_code):
|
||||||
if max_count is None:
|
if max_count is None:
|
||||||
return (response.json()['versions'], None)
|
return (response.json()['versions'], None)
|
||||||
else:
|
else:
|
||||||
@@ -159,7 +155,7 @@ class Client(object):
|
|||||||
url = self._create_url('v2/apps')
|
url = self._create_url('v2/apps')
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if _success(response.status_code):
|
||||||
apps = response.json()['apps']
|
apps = response.json()['apps']
|
||||||
return (apps, None)
|
return (apps, None)
|
||||||
else:
|
else:
|
||||||
@@ -170,8 +166,8 @@ class Client(object):
|
|||||||
|
|
||||||
:param app_resource: Application resource
|
:param app_resource: Application resource
|
||||||
:type app_resource: dict, bytes or file
|
:type app_resource: dict, bytes or file
|
||||||
:returns: Status of trying to start the application
|
:returns: the application description
|
||||||
:rtype: Error
|
:rtype: (dict, Error)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = self._create_url('v2/apps')
|
url = self._create_url('v2/apps')
|
||||||
@@ -182,13 +178,46 @@ class Client(object):
|
|||||||
else:
|
else:
|
||||||
app_json = app_resource
|
app_json = app_resource
|
||||||
|
|
||||||
logger.info("Posting %r to %r", app_json, url)
|
logger.info('Posting %r to %r', app_json, url)
|
||||||
response = requests.post(url, json=app_json)
|
response = requests.post(url, json=app_json)
|
||||||
|
logger.info('Got (%r): %r', response.status_code, response.json())
|
||||||
|
|
||||||
if response.status_code == 201:
|
if _success(response.status_code):
|
||||||
return None
|
return (response.json(), None)
|
||||||
else:
|
else:
|
||||||
return self._response_to_error(response)
|
return (None, self._response_to_error(response))
|
||||||
|
|
||||||
|
def update_app(self, app_id, payload, force=None):
|
||||||
|
"""Update an application.
|
||||||
|
|
||||||
|
:param app_id: the application id
|
||||||
|
:type app_id: str
|
||||||
|
:param payload: the json payload
|
||||||
|
:type payload: dict
|
||||||
|
:param force: whether to override running deployments.
|
||||||
|
:type force: bool
|
||||||
|
:returns: the resulting deployment ID.
|
||||||
|
:rtype: (str, Error)
|
||||||
|
"""
|
||||||
|
|
||||||
|
app_id = self._sanitize_app_id(app_id)
|
||||||
|
|
||||||
|
if force is None or not force:
|
||||||
|
params = None
|
||||||
|
else:
|
||||||
|
params = {'force': True}
|
||||||
|
|
||||||
|
url = self._create_url('v2/apps{}'.format(app_id), params)
|
||||||
|
|
||||||
|
logger.info('Putting %r to %r', payload, url)
|
||||||
|
response = requests.put(url, json=payload)
|
||||||
|
logger.info('Got (%r): %r', response.status_code, response.json())
|
||||||
|
|
||||||
|
if _success(response.status_code):
|
||||||
|
deployment = response.json()['deploymentId']
|
||||||
|
return (deployment, None)
|
||||||
|
else:
|
||||||
|
return (None, self._response_to_error(response))
|
||||||
|
|
||||||
def scale_app(self, app_id, instances, force=None):
|
def scale_app(self, app_id, instances, force=None):
|
||||||
"""Scales an application to the requested number of instances.
|
"""Scales an application to the requested number of instances.
|
||||||
@@ -215,7 +244,7 @@ class Client(object):
|
|||||||
url = self._create_url('v2/apps{}'.format(app_id), params)
|
url = self._create_url('v2/apps{}'.format(app_id), params)
|
||||||
response = requests.put(url, json={'instances': int(instances)})
|
response = requests.put(url, json={'instances': int(instances)})
|
||||||
|
|
||||||
if response.status_code == 200:
|
if _success(response.status_code):
|
||||||
deployment = response.json()['deploymentId']
|
deployment = response.json()['deploymentId']
|
||||||
return (deployment, None)
|
return (deployment, None)
|
||||||
else:
|
else:
|
||||||
@@ -257,7 +286,7 @@ class Client(object):
|
|||||||
url = self._create_url('v2/apps{}'.format(app_id), params)
|
url = self._create_url('v2/apps{}'.format(app_id), params)
|
||||||
response = requests.delete(url)
|
response = requests.delete(url)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if _success(response.status_code):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return self._response_to_error(response)
|
return self._response_to_error(response)
|
||||||
@@ -281,3 +310,15 @@ class Error(errors.Error):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
return self._message
|
return self._message
|
||||||
|
|
||||||
|
|
||||||
|
def _success(status_code):
|
||||||
|
"""Returns true if the success status is between [200, 300).
|
||||||
|
|
||||||
|
:param response_status: the http response status
|
||||||
|
:type response_status: int
|
||||||
|
:returns: True for success status; False otherwise
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
return status_code >= 200 and status_code < 300
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import git
|
import git
|
||||||
import jsonschema
|
|
||||||
import portalocker
|
import portalocker
|
||||||
import pystache
|
import pystache
|
||||||
from dcos.api import errors, util
|
from dcos.api import errors, util
|
||||||
@@ -59,10 +58,9 @@ def install(pkg, version, init_client, user_options, cfg):
|
|||||||
options = dict(list(default_options.items()) + list(user_options.items()))
|
options = dict(list(default_options.items()) + list(user_options.items()))
|
||||||
|
|
||||||
# Validate options with the config schema
|
# Validate options with the config schema
|
||||||
try:
|
err = util.validate_json(options, config_schema)
|
||||||
jsonschema.validate(options, config_schema)
|
if err is not None:
|
||||||
except jsonschema.ValidationError as ve:
|
return err
|
||||||
return Error(ve.message)
|
|
||||||
|
|
||||||
# Insert option parameters into the init template
|
# Insert option parameters into the init template
|
||||||
init_template, tmpl_error = pkg.marathon_template(version)
|
init_template, tmpl_error = pkg.marathon_template(version)
|
||||||
@@ -87,7 +85,8 @@ def install(pkg, version, init_client, user_options, cfg):
|
|||||||
# TODO(CD): Is this necessary / desirable at this point?
|
# TODO(CD): Is this necessary / desirable at this point?
|
||||||
|
|
||||||
# Send the descriptor to init
|
# Send the descriptor to init
|
||||||
return init_client.add_app(init_desc)
|
_, err = init_client.add_app(init_desc)
|
||||||
|
return err
|
||||||
|
|
||||||
|
|
||||||
def list_installed_packages(init_client):
|
def list_installed_packages(init_client):
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
from dcos.api import constants, errors
|
from dcos.api import constants, errors
|
||||||
|
|
||||||
|
|
||||||
@@ -118,3 +119,20 @@ def load_jsons(value):
|
|||||||
value,
|
value,
|
||||||
error)
|
error)
|
||||||
return (None, errors.DefaultError('Error loading JSON.'))
|
return (None, errors.DefaultError('Error loading JSON.'))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_json(instance, schema):
|
||||||
|
"""Validate an instance under the given schema.
|
||||||
|
|
||||||
|
:param instance: the instance to validate
|
||||||
|
:type instance: dict
|
||||||
|
:param schema: the schema to validate with
|
||||||
|
:type schema: dict
|
||||||
|
:returns: an error if the validation failed; None otherwise
|
||||||
|
:rtype: Error
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
jsonschema.validate(instance, schema)
|
||||||
|
return None
|
||||||
|
except jsonschema.ValidationError as ve:
|
||||||
|
return errors.DefaultError(ve.message)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Usage:
|
|||||||
dcos app list
|
dcos app list
|
||||||
dcos app remove [--force] <app-id>
|
dcos app remove [--force] <app-id>
|
||||||
dcos app show [--app-version=<app-version>] <app-id>
|
dcos app show [--app-version=<app-version>] <app-id>
|
||||||
|
dcos app start [--force] <app-id> [<instances>]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this screen
|
-h, --help Show this screen
|
||||||
@@ -21,10 +22,12 @@ Options:
|
|||||||
version from the currently deployed
|
version from the currently deployed
|
||||||
application definition.
|
application definition.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import docopt
|
import docopt
|
||||||
|
import pkg_resources
|
||||||
from dcos.api import (config, constants, emitting, errors, marathon, options,
|
from dcos.api import (config, constants, emitting, errors, marathon, options,
|
||||||
util)
|
util)
|
||||||
|
|
||||||
@@ -61,7 +64,14 @@ def main():
|
|||||||
if args['show']:
|
if args['show']:
|
||||||
return _show(args['<app-id>'], args['--app-version'])
|
return _show(args['<app-id>'], args['--app-version'])
|
||||||
|
|
||||||
|
if args['start']:
|
||||||
|
return _start(
|
||||||
|
args['<app-id>'],
|
||||||
|
args['<instances>'],
|
||||||
|
args['--force'])
|
||||||
|
|
||||||
emitter.publish(options.make_generic_usage_error(__doc__))
|
emitter.publish(options.make_generic_usage_error(__doc__))
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@@ -99,7 +109,7 @@ def _add():
|
|||||||
client = marathon.create_client(
|
client = marathon.create_client(
|
||||||
config.load_from_path(
|
config.load_from_path(
|
||||||
os.environ[constants.DCOS_CONFIG_ENV]))
|
os.environ[constants.DCOS_CONFIG_ENV]))
|
||||||
err = client.add_app(application_resource)
|
_, err = client.add_app(application_resource)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
emitter.publish(err)
|
emitter.publish(err)
|
||||||
return 1
|
return 1
|
||||||
@@ -184,6 +194,78 @@ def _show(app_id, version):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _start(app_id, instances, force):
|
||||||
|
"""Start a Marathon application.
|
||||||
|
|
||||||
|
:param app_id: the id for the application
|
||||||
|
:type app_id: str
|
||||||
|
:param json_items: the list of json item
|
||||||
|
:type json_items: list of str
|
||||||
|
:param force: whether to override running deployments
|
||||||
|
:type force: bool
|
||||||
|
:returns: Process status
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check that the application exists
|
||||||
|
client = marathon.create_client(
|
||||||
|
config.load_from_path(
|
||||||
|
os.environ[constants.DCOS_CONFIG_ENV]))
|
||||||
|
|
||||||
|
desc, err = client.get_app(app_id)
|
||||||
|
if err is not None:
|
||||||
|
emitter.publish(err)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if desc['instances'] > 0:
|
||||||
|
emitter.publish(
|
||||||
|
'Application {!r} already started: {!r} instances.'.format(
|
||||||
|
app_id,
|
||||||
|
desc['instances']))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
schema = json.loads(
|
||||||
|
pkg_resources.resource_string(
|
||||||
|
'dcos',
|
||||||
|
'data/marathon-schema.json').decode('utf-8'))
|
||||||
|
|
||||||
|
app_json = {}
|
||||||
|
|
||||||
|
# Need to add the 'id' because it is required
|
||||||
|
app_json['id'] = app_id
|
||||||
|
|
||||||
|
# Set instances to 1 if not specified
|
||||||
|
if instances is None:
|
||||||
|
instances = 1
|
||||||
|
else:
|
||||||
|
instances, err = _parse_int(instances)
|
||||||
|
if err is not None:
|
||||||
|
emitter.publish(err)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if instances <= 0:
|
||||||
|
emitter.publish(
|
||||||
|
'The number of instances must be positive: {!r}.'.format(
|
||||||
|
instances))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
app_json['instances'] = instances
|
||||||
|
|
||||||
|
err = util.validate_json(app_json, schema)
|
||||||
|
if err is not None:
|
||||||
|
emitter.publish(err)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
deployment, err = client.update_app(app_id, app_json, force)
|
||||||
|
if err is not None:
|
||||||
|
emitter.publish(err)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
emitter.publish('Created deployment {}'.format(deployment))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _calculate_version(client, app_id, version):
|
def _calculate_version(client, app_id, version):
|
||||||
"""
|
"""
|
||||||
:param client: Marathon client
|
:param client: Marathon client
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ def _start(app_resource_path, config):
|
|||||||
client = marathon.create_client(config)
|
client = marathon.create_client(config)
|
||||||
|
|
||||||
with open(app_resource_path) as app_resource_file:
|
with open(app_resource_path) as app_resource_file:
|
||||||
err = client.add_app(app_resource_file)
|
_, err = client.add_app(app_resource_file)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
print(err.error())
|
print(err.error())
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
215
dcos/data/marathon-schema.json
Normal file
215
dcos/data/marathon-schema.json
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^/?(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])$"
|
||||||
|
},
|
||||||
|
"cmd": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"args": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
".*": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"instances": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"cpus": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"mem": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"disk": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"executor": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"constraints": {
|
||||||
|
},
|
||||||
|
"uris": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeUrls": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ports": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 65535
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requirePorts": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"backoffSeconds": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"backoffFactor": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1.0
|
||||||
|
},
|
||||||
|
"container": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"docker": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"image": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"portMappings": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"containerPort": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 65535
|
||||||
|
},
|
||||||
|
"hostPort": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 65535
|
||||||
|
},
|
||||||
|
"servicePort": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 65535
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"image"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"volumes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"containerPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"hostPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"healthChecks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"protocol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"portIndex": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"gracePeriodSeconds": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"intervalSeconds": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"timeoutSeconds": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"maxConsecutiveFailures": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^/?(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upgradeStrategy": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"minimumHealthCapacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0.0,
|
||||||
|
"maximum": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ def exec_command(cmd, env=None, stdin=None):
|
|||||||
:rtype: (int, bytes, bytes)
|
:rtype: (int, bytes, bytes)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print('CMD: %r', cmd)
|
print('CMD: {!r}'.format(cmd))
|
||||||
|
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -26,7 +26,7 @@ def exec_command(cmd, env=None, stdin=None):
|
|||||||
stdout, stderr = process.communicate()
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
# We should always print the stdout and stderr
|
# We should always print the stdout and stderr
|
||||||
print('STDOUT: %s', stdout.decode('utf-8'))
|
print('STDOUT: {!r}'.format(stdout.decode('utf-8')))
|
||||||
print('STDERR: %s', stderr.decode('utf-8'))
|
print('STDERR: {!r}'.format(stderr.decode('utf-8')))
|
||||||
|
|
||||||
return (process.returncode, stdout, stderr)
|
return (process.returncode, stdout, stderr)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ def test_help():
|
|||||||
dcos app list
|
dcos app list
|
||||||
dcos app remove [--force] <app-id>
|
dcos app remove [--force] <app-id>
|
||||||
dcos app show [--app-version=<app-version>] <app-id>
|
dcos app show [--app-version=<app-version>] <app-id>
|
||||||
|
dcos app start [--force] <app-id> [<instances>]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this screen
|
-h, --help Show this screen
|
||||||
@@ -160,6 +161,36 @@ def test_show_bad_relative_app_version():
|
|||||||
_remove_app('zero-instance-app')
|
_remove_app('zero-instance-app')
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_missing_app():
|
||||||
|
returncode, stdout, stderr = exec_command(
|
||||||
|
['dcos', 'app', 'start', 'missing-id'])
|
||||||
|
|
||||||
|
assert returncode == 1
|
||||||
|
assert stdout == b"Error: App '/missing-id' does not exist\n"
|
||||||
|
assert stderr == b''
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_app():
|
||||||
|
_add_app('tests/data/marathon/zero_instance_sleep.json')
|
||||||
|
_start_app('zero-instance-app')
|
||||||
|
_remove_app('zero-instance-app')
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_already_started_app():
|
||||||
|
_add_app('tests/data/marathon/zero_instance_sleep.json')
|
||||||
|
_start_app('zero-instance-app')
|
||||||
|
|
||||||
|
returncode, stdout, stderr = exec_command(
|
||||||
|
['dcos', 'app', 'start', 'zero-instance-app'])
|
||||||
|
|
||||||
|
assert returncode == 1
|
||||||
|
assert (stdout ==
|
||||||
|
b"Application 'zero-instance-app' already started: 1 instances.\n")
|
||||||
|
assert stderr == b''
|
||||||
|
|
||||||
|
_remove_app('zero-instance-app')
|
||||||
|
|
||||||
|
|
||||||
def _list_apps(app_id=None):
|
def _list_apps(app_id=None):
|
||||||
returncode, stdout, stderr = exec_command(['dcos', 'app', 'list'])
|
returncode, stdout, stderr = exec_command(['dcos', 'app', 'list'])
|
||||||
|
|
||||||
@@ -212,3 +243,12 @@ def _show_app(app_id, version=None):
|
|||||||
assert stderr == b''
|
assert stderr == b''
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _start_app(app_id):
|
||||||
|
returncode, stdout, stderr = exec_command(
|
||||||
|
['dcos', 'app', 'start', app_id])
|
||||||
|
|
||||||
|
assert returncode == 0
|
||||||
|
assert stdout.decode().startswith('Created deployment ')
|
||||||
|
assert stderr == b''
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -20,7 +20,7 @@ setup(
|
|||||||
# https://packaging.python.org/en/latest/single_source_version.html
|
# https://packaging.python.org/en/latest/single_source_version.html
|
||||||
version=constants.version,
|
version=constants.version,
|
||||||
|
|
||||||
description='Dcos cli poc project',
|
description='DCOS Command Line Interface',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
|
|
||||||
# The project's main homepage.
|
# The project's main homepage.
|
||||||
@@ -76,7 +76,6 @@ setup(
|
|||||||
'portalocker',
|
'portalocker',
|
||||||
'pystache',
|
'pystache',
|
||||||
'requests',
|
'requests',
|
||||||
# 'toml',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
# List additional groups of dependencies here (e.g. development
|
# List additional groups of dependencies here (e.g. development
|
||||||
@@ -92,7 +91,7 @@ setup(
|
|||||||
# installed, specify them here. If using Python 2.6 or less, then these
|
# installed, specify them here. If using Python 2.6 or less, then these
|
||||||
# have to be included in MANIFEST.in as well.
|
# have to be included in MANIFEST.in as well.
|
||||||
package_data={
|
package_data={
|
||||||
'sample': ['package_data.dat'],
|
'dcos': ['data/*'],
|
||||||
},
|
},
|
||||||
|
|
||||||
# Although 'package_data' is the preferred approach, in some case you may
|
# Although 'package_data' is the preferred approach, in some case you may
|
||||||
|
|||||||
Reference in New Issue
Block a user