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))
 | 
			
		||||
 | 
			
		||||
        logger.info('Getting %r', url)
 | 
			
		||||
 | 
			
		||||
        response = requests.get(url)
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
            if version is None:
 | 
			
		||||
                return (response.json()['app'], None)
 | 
			
		||||
@@ -136,12 +134,10 @@ class Client(object):
 | 
			
		||||
        url = self._create_url('v2/apps{}/versions'.format(app_id))
 | 
			
		||||
 | 
			
		||||
        logger.info('Getting %r', url)
 | 
			
		||||
 | 
			
		||||
        response = requests.get(url)
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
                return (response.json()['versions'], None)
 | 
			
		||||
            else:
 | 
			
		||||
@@ -159,7 +155,7 @@ class Client(object):
 | 
			
		||||
        url = self._create_url('v2/apps')
 | 
			
		||||
        response = requests.get(url)
 | 
			
		||||
 | 
			
		||||
        if response.status_code == 200:
 | 
			
		||||
        if _success(response.status_code):
 | 
			
		||||
            apps = response.json()['apps']
 | 
			
		||||
            return (apps, None)
 | 
			
		||||
        else:
 | 
			
		||||
@@ -170,8 +166,8 @@ class Client(object):
 | 
			
		||||
 | 
			
		||||
        :param app_resource: Application resource
 | 
			
		||||
        :type app_resource: dict, bytes or file
 | 
			
		||||
        :returns: Status of trying to start the application
 | 
			
		||||
        :rtype: Error
 | 
			
		||||
        :returns: the application description
 | 
			
		||||
        :rtype: (dict, Error)
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        url = self._create_url('v2/apps')
 | 
			
		||||
@@ -182,13 +178,46 @@ class Client(object):
 | 
			
		||||
        else:
 | 
			
		||||
            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)
 | 
			
		||||
        logger.info('Got (%r): %r', response.status_code, response.json())
 | 
			
		||||
 | 
			
		||||
        if response.status_code == 201:
 | 
			
		||||
            return None
 | 
			
		||||
        if _success(response.status_code):
 | 
			
		||||
            return (response.json(), None)
 | 
			
		||||
        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):
 | 
			
		||||
        """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)
 | 
			
		||||
        response = requests.put(url, json={'instances': int(instances)})
 | 
			
		||||
 | 
			
		||||
        if response.status_code == 200:
 | 
			
		||||
        if _success(response.status_code):
 | 
			
		||||
            deployment = response.json()['deploymentId']
 | 
			
		||||
            return (deployment, None)
 | 
			
		||||
        else:
 | 
			
		||||
@@ -257,7 +286,7 @@ class Client(object):
 | 
			
		||||
        url = self._create_url('v2/apps{}'.format(app_id), params)
 | 
			
		||||
        response = requests.delete(url)
 | 
			
		||||
 | 
			
		||||
        if response.status_code == 200:
 | 
			
		||||
        if _success(response.status_code):
 | 
			
		||||
            return None
 | 
			
		||||
        else:
 | 
			
		||||
            return self._response_to_error(response)
 | 
			
		||||
@@ -281,3 +310,15 @@ class Error(errors.Error):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        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 git
 | 
			
		||||
import jsonschema
 | 
			
		||||
import portalocker
 | 
			
		||||
import pystache
 | 
			
		||||
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()))
 | 
			
		||||
 | 
			
		||||
    # Validate options with the config schema
 | 
			
		||||
    try:
 | 
			
		||||
        jsonschema.validate(options, config_schema)
 | 
			
		||||
    except jsonschema.ValidationError as ve:
 | 
			
		||||
        return Error(ve.message)
 | 
			
		||||
    err = util.validate_json(options, config_schema)
 | 
			
		||||
    if err is not None:
 | 
			
		||||
        return err
 | 
			
		||||
 | 
			
		||||
    # Insert option parameters into the init template
 | 
			
		||||
    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?
 | 
			
		||||
 | 
			
		||||
    # 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):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import shutil
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
 | 
			
		||||
import jsonschema
 | 
			
		||||
from dcos.api import constants, errors
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -118,3 +119,20 @@ def load_jsons(value):
 | 
			
		||||
            value,
 | 
			
		||||
            error)
 | 
			
		||||
        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 remove [--force] <app-id>
 | 
			
		||||
    dcos app show [--app-version=<app-version>] <app-id>
 | 
			
		||||
    dcos app start [--force] <app-id> [<instances>]
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
    -h, --help                   Show this screen
 | 
			
		||||
@@ -21,10 +22,12 @@ Options:
 | 
			
		||||
                                 version from the currently deployed
 | 
			
		||||
                                 application definition.
 | 
			
		||||
"""
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import docopt
 | 
			
		||||
import pkg_resources
 | 
			
		||||
from dcos.api import (config, constants, emitting, errors, marathon, options,
 | 
			
		||||
                      util)
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +64,14 @@ def main():
 | 
			
		||||
    if args['show']:
 | 
			
		||||
        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__))
 | 
			
		||||
 | 
			
		||||
    return 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -99,7 +109,7 @@ def _add():
 | 
			
		||||
    client = marathon.create_client(
 | 
			
		||||
        config.load_from_path(
 | 
			
		||||
            os.environ[constants.DCOS_CONFIG_ENV]))
 | 
			
		||||
    err = client.add_app(application_resource)
 | 
			
		||||
    _, err = client.add_app(application_resource)
 | 
			
		||||
    if err is not None:
 | 
			
		||||
        emitter.publish(err)
 | 
			
		||||
        return 1
 | 
			
		||||
@@ -184,6 +194,78 @@ def _show(app_id, version):
 | 
			
		||||
    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):
 | 
			
		||||
    """
 | 
			
		||||
    :param client: Marathon client
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,7 @@ def _start(app_resource_path, config):
 | 
			
		||||
    client = marathon.create_client(config)
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
            print(err.error())
 | 
			
		||||
            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)
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    print('CMD: %r', cmd)
 | 
			
		||||
    print('CMD: {!r}'.format(cmd))
 | 
			
		||||
 | 
			
		||||
    process = subprocess.Popen(
 | 
			
		||||
        cmd,
 | 
			
		||||
@@ -26,7 +26,7 @@ def exec_command(cmd, env=None, stdin=None):
 | 
			
		||||
    stdout, stderr = process.communicate()
 | 
			
		||||
 | 
			
		||||
    # We should always print the stdout and stderr
 | 
			
		||||
    print('STDOUT: %s', stdout.decode('utf-8'))
 | 
			
		||||
    print('STDERR: %s', stderr.decode('utf-8'))
 | 
			
		||||
    print('STDOUT: {!r}'.format(stdout.decode('utf-8')))
 | 
			
		||||
    print('STDERR: {!r}'.format(stderr.decode('utf-8')))
 | 
			
		||||
 | 
			
		||||
    return (process.returncode, stdout, stderr)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ def test_help():
 | 
			
		||||
    dcos app list
 | 
			
		||||
    dcos app remove [--force] <app-id>
 | 
			
		||||
    dcos app show [--app-version=<app-version>] <app-id>
 | 
			
		||||
    dcos app start [--force] <app-id> [<instances>]
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
    -h, --help                   Show this screen
 | 
			
		||||
@@ -160,6 +161,36 @@ def test_show_bad_relative_app_version():
 | 
			
		||||
    _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):
 | 
			
		||||
    returncode, stdout, stderr = exec_command(['dcos', 'app', 'list'])
 | 
			
		||||
 | 
			
		||||
@@ -212,3 +243,12 @@ def _show_app(app_id, version=None):
 | 
			
		||||
    assert stderr == b''
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    version=constants.version,
 | 
			
		||||
 | 
			
		||||
    description='Dcos cli poc project',
 | 
			
		||||
    description='DCOS Command Line Interface',
 | 
			
		||||
    long_description=long_description,
 | 
			
		||||
 | 
			
		||||
    # The project's main homepage.
 | 
			
		||||
@@ -76,7 +76,6 @@ setup(
 | 
			
		||||
        'portalocker',
 | 
			
		||||
        'pystache',
 | 
			
		||||
        'requests',
 | 
			
		||||
        # 'toml',
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    # 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
 | 
			
		||||
    # have to be included in MANIFEST.in as well.
 | 
			
		||||
    package_data={
 | 
			
		||||
        'sample': ['package_data.dat'],
 | 
			
		||||
        'dcos': ['data/*'],
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    # Although 'package_data' is the preferred approach, in some case you may
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user