diff --git a/cli/dcoscli/marathon/main.py b/cli/dcoscli/marathon/main.py index 532790f..6058bd2 100644 --- a/cli/dcoscli/marathon/main.py +++ b/cli/dcoscli/marathon/main.py @@ -71,9 +71,10 @@ Options: Positional Arguments: The application id - Path to a file containing the app's JSON - definition. If omitted, the definition is read - from stdin. For a detailed description see + Path to a file or HTTP(S) URL containing + the app's JSON definition. If omitted, + the definition is read from stdin. For a + detailed description see (https://mesosphere.github.io/ marathon/docs/rest-api.html#post-/v2/apps). @@ -81,9 +82,10 @@ Positional Arguments: The group id - Path to a file containing the group's JSON - definition. If omitted, the definition is read - from stdin. For a detailed description see + Path to a file or HTTP(S) URL containing + the group's JSON definition. If omitted, + the definition is read from stdin. For a + detailed description see (https://mesosphere.github.io/ marathon/docs/rest-api.html#post-/v2/groups). @@ -96,13 +98,14 @@ Positional Arguments: The task id """ import json +import os import sys import time import dcoscli import docopt import pkg_resources -from dcos import cmds, emitting, jsonitem, marathon, options, util +from dcos import cmds, emitting, http, jsonitem, marathon, options, util from dcos.errors import DCOSException from dcoscli import tables @@ -293,14 +296,31 @@ def _about(): def _get_resource(resource): """ - :param resource: optional filename for the application or group resource + :param resource: optional filename or http(s) url + for the application or group resource :type resource: str :returns: resource :rtype: dict """ if resource is not None: - with util.open_file(resource) as resource_file: - return util.load_json(resource_file) + if os.path.isfile(resource): + with util.open_file(resource) as resource_file: + return util.load_json(resource_file) + else: + try: + http.silence_requests_warnings() + req = http.get(resource) + if req.status_code == 200: + data = b'' + for chunk in req.iter_content(1024): + data += chunk + return util.load_jsons(data.decode('utf-8')) + else: + raise Exception + except Exception: + raise DCOSException( + "Can't read from resource: {0}.\n" + "Please check that it exists.".format(resource)) # Check that stdin is not tty if sys.stdin.isatty(): diff --git a/cli/tests/integrations/test_marathon.py b/cli/tests/integrations/test_marathon.py index 880296b..bf609c4 100644 --- a/cli/tests/integrations/test_marathon.py +++ b/cli/tests/integrations/test_marathon.py @@ -1,10 +1,12 @@ import contextlib import json import os +import threading from dcos import constants import pytest +from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from .common import (app, assert_command, assert_lines, exec_command, list_deployments, show_app, watch_all_deployments, @@ -85,9 +87,10 @@ Options: Positional Arguments: The application id - Path to a file containing the app's JSON - definition. If omitted, the definition is read - from stdin. For a detailed description see + Path to a file or HTTP(S) URL containing + the app's JSON definition. If omitted, + the definition is read from stdin. For a + detailed description see (https://mesosphere.github.io/ marathon/docs/rest-api.html#post-/v2/apps). @@ -95,9 +98,10 @@ Positional Arguments: The group id - Path to a file containing the group's JSON - definition. If omitted, the definition is read - from stdin. For a detailed description see + Path to a file or HTTP(S) URL containing + the group's JSON definition. If omitted, + the definition is read from stdin. For a + detailed description see (https://mesosphere.github.io/ marathon/docs/rest-api.html#post-/v2/groups). @@ -162,6 +166,19 @@ def test_add_app(): _list_apps('zero-instance-app') +def test_add_app_through_http(): + with _zero_instance_app_through_http(): + _list_apps('zero-instance-app') + + +def test_add_app_bad_resource(): + stderr = (b'Can\'t read from resource: bad_resource.\n' + b'Please check that it exists.\n') + assert_command(['dcos', 'marathon', 'app', 'add', 'bad_resource'], + returncode=1, + stderr=stderr) + + def test_add_app_with_filename(): with _zero_instance_app(): _list_apps('zero-instance-app') @@ -697,3 +714,29 @@ def _zero_instance_app(): with app('tests/data/marathon/apps/zero_instance_sleep.json', 'zero-instance-app'): yield + + +@contextlib.contextmanager +def _zero_instance_app_through_http(): + class JSONRequestHandler (BaseHTTPRequestHandler): + + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(open( + 'tests/data/marathon/apps/zero_instance_sleep.json', + 'rb').read()) + + host = 'localhost' + port = 12345 + server = HTTPServer((host, port), JSONRequestHandler) + thread = threading.Thread(target=server.serve_forever) + thread.setDaemon(True) + thread.start() + + with app('http://{}:{}'.format(host, port), 'zero-instance-app'): + try: + yield + finally: + server.shutdown()