Add integration tests for the dcos-marathon cli
This commit is contained in:
@@ -13,4 +13,5 @@
|
||||
sleep 2
|
||||
|
||||
# Run the tox integration tests
|
||||
tox -c /dcos-cli/tox.ini
|
||||
cd /dcos-cli
|
||||
make clean all
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import json
|
||||
import urllib
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from dcos.api import errors
|
||||
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Class for talking to the Marathon server. """
|
||||
@@ -38,7 +43,7 @@ class Client(object):
|
||||
path=path)
|
||||
|
||||
if query_params is not None:
|
||||
query_string = urllib.urlencode(query_params)
|
||||
query_string = urlencode(query_params)
|
||||
url = (url + '?{}').format(query_string)
|
||||
|
||||
return url
|
||||
@@ -64,6 +69,11 @@ class Client(object):
|
||||
:rtype: Error
|
||||
"""
|
||||
|
||||
message = response.json().get('message')
|
||||
if message is None:
|
||||
logging.error('Missing message from json: %s', response.json())
|
||||
return Error('Unknown error from Marathon')
|
||||
|
||||
return Error('Error: {}'.format(response.json()['message']))
|
||||
|
||||
def get_app(self, app_id):
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
"""
|
||||
Usage:
|
||||
dcos marathon describe [--json] <app_id>
|
||||
dcos marathon info
|
||||
dcos marathon list
|
||||
dcos marathon describe <app_id>
|
||||
dcos marathon remove [--force] <app_id>
|
||||
dcos marathon scale [--force] <app_id> <instances>
|
||||
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
|
||||
dcos marathon suspend [--force] <app_id>
|
||||
|
||||
Options:
|
||||
-h, --help Show this screen
|
||||
--version Show version
|
||||
--force This flag disable checks in Marathon during update
|
||||
operations.
|
||||
--json Outputs JSON format instead of default (TOML) format
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import docopt
|
||||
import toml
|
||||
from dcos.api import config, constants, marathon, options
|
||||
|
||||
|
||||
@@ -35,7 +37,7 @@ def main():
|
||||
return _list(toml_config)
|
||||
elif args['marathon'] and args['describe']:
|
||||
toml_config = config.load_from_path(config_path)
|
||||
return _describe(args['<app_id>'], toml_config)
|
||||
return _describe(args['<app_id>'], args['--json'], toml_config)
|
||||
elif args['marathon'] and args['start']:
|
||||
toml_config = config.load_from_path(config_path)
|
||||
return _start(args['<app_resource>'], toml_config)
|
||||
@@ -94,7 +96,7 @@ def _list(config):
|
||||
return 1
|
||||
|
||||
if not apps:
|
||||
print("No apps to list.")
|
||||
print("No applications to list.")
|
||||
|
||||
for app in apps:
|
||||
print(app['id'])
|
||||
@@ -102,11 +104,13 @@ def _list(config):
|
||||
return 0
|
||||
|
||||
|
||||
def _describe(app_id, config):
|
||||
"""Show details of a Marathon applications.
|
||||
def _describe(app_id, is_json, config):
|
||||
"""Show details of a Marathon application.
|
||||
|
||||
:param app_id: ID of the app to suspend
|
||||
:type app_id: str
|
||||
:param is_json: Whether to print in JSON format or TOML
|
||||
:type is_json: bool
|
||||
:param config: Configuration dictionary
|
||||
:type config: config.Toml
|
||||
:returns: Process status
|
||||
@@ -119,9 +123,10 @@ def _describe(app_id, config):
|
||||
print(err.error())
|
||||
return 1
|
||||
|
||||
print(json.dumps(app,
|
||||
sort_keys=True,
|
||||
indent=2))
|
||||
if is_json:
|
||||
print(json.dumps(app, sort_keys=True, indent=2))
|
||||
else:
|
||||
print(toml.dumps(app))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
|
||||
@@ -8,12 +9,20 @@ def exec_command(cmd, env=None):
|
||||
:type cmd: list of str
|
||||
:param env: Environment variables
|
||||
:type env: dict of str to str
|
||||
:returns: Object to the running process
|
||||
:rtype: subprocess.Popen
|
||||
:returns: A tuple with the returncode, stdout and stderr
|
||||
:rtype: (int, bytes, bytes)
|
||||
"""
|
||||
|
||||
return subprocess.Popen(
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=env)
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
# We should always print the stdout and stderr
|
||||
logging.error('STDOUT: %s', stdout.decode('utf-8'))
|
||||
logging.error('STDERR: %s', stderr.decode('utf-8'))
|
||||
|
||||
return (process.returncode, stdout, stderr)
|
||||
|
||||
@@ -2,10 +2,9 @@ from common import exec_command
|
||||
|
||||
|
||||
def test_help():
|
||||
process = exec_command(['dcos', 'config', '--help'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', '--help'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b"""Usage:
|
||||
dcos config info
|
||||
dcos config <name> [<value>]
|
||||
@@ -21,29 +20,25 @@ Options:
|
||||
|
||||
|
||||
def test_info():
|
||||
process = exec_command(['dcos', 'config', 'info'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', 'info'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'Get and set DCOS command line options\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_version():
|
||||
process = exec_command(['dcos', 'config', '--version'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', '--version'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'dcos-config version 0.1.0\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_list_property():
|
||||
process = exec_command(['dcos', 'config', '--list'])
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', '--list'])
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b"""marathon.host=localhost
|
||||
marathon.port=8080
|
||||
package.cache=tmp/cache
|
||||
@@ -81,40 +76,33 @@ def test_set_missing_property():
|
||||
|
||||
|
||||
def _set_value(key, value):
|
||||
process = exec_command(['dcos', 'config', key, value])
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', key, value])
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b''
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _get_value(key, value):
|
||||
process = exec_command(['dcos', 'config', key])
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', key])
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == '{}\n'.format(value).encode('utf-8')
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _unset_value(key):
|
||||
process = exec_command(['dcos', 'config', '--unset', key])
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'config', '--unset', key])
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b''
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _get_missing_value(key):
|
||||
process = exec_command(['dcos', 'config', key])
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'config', key])
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
assert process.returncode == 1
|
||||
assert returncode == 1
|
||||
assert stdout == b''
|
||||
assert stderr == b''
|
||||
|
||||
@@ -5,10 +5,9 @@ from dcos.cli.main import _which
|
||||
|
||||
|
||||
def test_help():
|
||||
process = exec_command(['dcos', '--help'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', '--help'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b"""Usage:
|
||||
dcos <command> [<args>...]
|
||||
|
||||
@@ -23,10 +22,9 @@ read about a specific subcommand.
|
||||
|
||||
|
||||
def test_version():
|
||||
process = exec_command(['dcos', '--version'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', '--version'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'dcos version 0.1.0\n'
|
||||
assert stderr == b''
|
||||
|
||||
@@ -37,10 +35,9 @@ def test_missing_dcos_config():
|
||||
'DCOS_PATH': os.environ['DCOS_PATH'],
|
||||
}
|
||||
|
||||
process = exec_command(['dcos'], env=env)
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos'], env=env)
|
||||
|
||||
assert process.returncode == 1
|
||||
assert returncode == 1
|
||||
assert stdout == (b"Environment variable 'DCOS_CONFIG' must be set "
|
||||
b"to the DCOS config file.\n")
|
||||
assert stderr == b''
|
||||
@@ -53,10 +50,9 @@ def test_dcos_config_not_a_file():
|
||||
'DCOS_CONFIG': 'missing/file',
|
||||
}
|
||||
|
||||
process = exec_command(['dcos'], env=env)
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos'], env=env)
|
||||
|
||||
assert process.returncode == 1
|
||||
assert returncode == 1
|
||||
assert stdout == (b"Environment variable 'DCOS_CONFIG' maps to "
|
||||
b"'missing/file' and it is not a file.\n")
|
||||
assert stderr == b''
|
||||
@@ -68,10 +64,9 @@ def test_missing_dcos_path():
|
||||
'DCOS_CONFIG': os.environ['DCOS_CONFIG'],
|
||||
}
|
||||
|
||||
process = exec_command(['dcos'], env=env)
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos'], env=env)
|
||||
|
||||
assert process.returncode == 1
|
||||
assert returncode == 1
|
||||
assert stdout == (b"Environment variable 'DCOS_PATH' not set to the DCOS "
|
||||
b"CLI path.\n")
|
||||
assert stderr == b''
|
||||
@@ -84,10 +79,9 @@ def test_dcos_path_not_a_dir():
|
||||
'DCOS_PATH': 'missing/dir',
|
||||
}
|
||||
|
||||
process = exec_command(['dcos'], env=env)
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos'], env=env)
|
||||
|
||||
assert process.returncode == 1
|
||||
assert returncode == 1
|
||||
assert stdout == (b"Environment variable 'DCOS_PATH' maps to "
|
||||
b"'missing/dir' which is not a directory.\n")
|
||||
assert stderr == b''
|
||||
@@ -99,9 +93,8 @@ def test_missing_path():
|
||||
'DCOS_PATH': os.environ['DCOS_PATH'],
|
||||
}
|
||||
|
||||
process = exec_command([_which('dcos')], env=env)
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command([_which('dcos')], env=env)
|
||||
|
||||
assert process.returncode == 1
|
||||
assert returncode == 1
|
||||
assert stdout == b"Environment variable 'PATH' not set.\n"
|
||||
assert stderr == b''
|
||||
|
||||
@@ -3,10 +3,9 @@ from common import exec_command
|
||||
|
||||
|
||||
def test_help():
|
||||
process = exec_command(['dcos', 'help', '--help'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'help', '--help'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b"""Usage:
|
||||
dcos help info
|
||||
dcos help --all
|
||||
@@ -20,28 +19,25 @@ Options:
|
||||
|
||||
|
||||
def test_info():
|
||||
process = exec_command(['dcos', 'help', 'info'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'help', 'info'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'Display help information about DCOS\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_version():
|
||||
process = exec_command(['dcos', 'help', '--version'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'help', '--version'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'dcos-help version 0.1.0\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_list_all():
|
||||
process = exec_command(['dcos', 'help', '--all'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'help', '--all'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == """Available DCOS command in '{}':
|
||||
|
||||
\tconfig \tGet and set DCOS command line options
|
||||
|
||||
168
integrations/cli/test_marathon.py
Normal file
168
integrations/cli/test_marathon.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import json
|
||||
from common import exec_command
|
||||
|
||||
|
||||
def test_help():
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'marathon', '--help'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout == b"""Usage:
|
||||
dcos marathon describe [--json] <app_id>
|
||||
dcos marathon info
|
||||
dcos marathon list
|
||||
dcos marathon remove [--force] <app_id>
|
||||
dcos marathon scale [--force] <app_id> <instances>
|
||||
dcos marathon start <app_resource>
|
||||
dcos marathon suspend [--force] <app_id>
|
||||
|
||||
Options:
|
||||
-h, --help Show this screen
|
||||
--version Show version
|
||||
--force This flag disable checks in Marathon during update
|
||||
operations.
|
||||
--json Outputs JSON format instead of default (TOML) format
|
||||
"""
|
||||
|
||||
|
||||
def test_version():
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', '--version'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout == b'dcos-marathon version 0.1.0\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_info():
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'marathon', 'info'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout == b'Deploy and manage applications on Apache Mesos\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_empty_list():
|
||||
_list_apps()
|
||||
|
||||
|
||||
def test_start_app():
|
||||
_start_app('tests/data/marathon/sleep.json')
|
||||
_list_apps('test-app')
|
||||
_remove_app('test-app')
|
||||
|
||||
|
||||
def test_remove_app():
|
||||
_start_app('tests/data/marathon/sleep.json')
|
||||
_remove_app('test-app')
|
||||
_list_apps()
|
||||
|
||||
|
||||
# TODO: Let's improve this once we have a fixed version of toml
|
||||
def test_describe_app():
|
||||
_start_app('tests/data/marathon/sleep.json')
|
||||
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'describe', 'test-app'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout != b''
|
||||
assert stderr == b''
|
||||
|
||||
_remove_app('test-app')
|
||||
|
||||
|
||||
def test_describe_app_in_json():
|
||||
_start_app('tests/data/marathon/sleep.json')
|
||||
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'describe', '--json', 'test-app'])
|
||||
|
||||
result = json.loads(stdout.decode('utf-8'))
|
||||
|
||||
assert returncode == 0
|
||||
assert isinstance(result, dict)
|
||||
assert result['id'] == '/test-app'
|
||||
assert stderr == b''
|
||||
|
||||
_remove_app('test-app')
|
||||
|
||||
|
||||
def test_scale_app():
|
||||
_start_app('tests/data/marathon/zero_instance_sleep.json')
|
||||
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'scale', 'zero-instance-app', '2'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout.decode('utf-8').startswith('Created deployment ')
|
||||
assert stderr == b''
|
||||
|
||||
_remove_app('zero-instance-app')
|
||||
|
||||
|
||||
def test_force_scale_appp():
|
||||
_start_app('tests/data/marathon/sleep.json')
|
||||
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'scale', '--force', 'test-app', '2'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout.decode('utf-8').startswith('Created deployment ')
|
||||
assert stderr == b''
|
||||
|
||||
_remove_app('test-app')
|
||||
|
||||
|
||||
def test_suspend_app():
|
||||
_start_app('tests/data/marathon/zero_instance_sleep.json')
|
||||
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'suspend', 'zero-instance-app'])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout.decode('utf-8').startswith('Created deployment ')
|
||||
assert stderr == b''
|
||||
|
||||
_remove_app('zero-instance-app')
|
||||
|
||||
|
||||
def test_remove_missing_app():
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'remove', 'missing-id'])
|
||||
|
||||
assert returncode == 1
|
||||
assert stdout == b"Error: App '/missing-id' does not exist\n"
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _start_app(file_path):
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'start', file_path])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout == b''
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _list_apps(app_id=None):
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'marathon', 'list'])
|
||||
|
||||
if app_id is None:
|
||||
result = b'No applications to list.\n'
|
||||
elif isinstance(app_id, str):
|
||||
result = '/{}\n'.format(app_id).encode('utf-8')
|
||||
else:
|
||||
assert False
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout == result
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _remove_app(app_id):
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'remove', app_id])
|
||||
|
||||
assert returncode == 0
|
||||
assert stdout == b''
|
||||
assert stderr == b''
|
||||
@@ -2,10 +2,9 @@ from common import exec_command
|
||||
|
||||
|
||||
def test_package():
|
||||
process = exec_command(['dcos', 'package', '--help'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'package', '--help'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b"""Usage:
|
||||
dcos package configure <package_name>
|
||||
dcos package info
|
||||
@@ -41,28 +40,25 @@ Configuration:
|
||||
|
||||
|
||||
def test_info():
|
||||
process = exec_command(['dcos', 'package', 'info'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'package', 'info'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'Install and manage DCOS software packages.\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_version():
|
||||
process = exec_command(['dcos', 'package', '--version'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'package', '--version'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b'dcos-package version 0.1.0\n'
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_sources_list():
|
||||
process = exec_command(['dcos', 'package', 'sources'])
|
||||
stdout, stderr = process.communicate()
|
||||
returncode, stdout, stderr = exec_command(['dcos', 'package', 'sources'])
|
||||
|
||||
assert process.returncode == 0
|
||||
assert returncode == 0
|
||||
assert stdout == b"""cc5af1bcaec7323400a95e1c38caf61378f6f081 \
|
||||
file:///Users/me/test-registry
|
||||
0c854fa7f2ede3dcc3122bf2b7db160491cf9f33 \
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
[package]
|
||||
cache = "tmp/cache"
|
||||
sources = [
|
||||
"file:///Users/me/test-registry",
|
||||
"https://my.org/registry",
|
||||
"git://github.com/mesosphere/universe.git"
|
||||
]
|
||||
|
||||
cache = "tmp\/cache"
|
||||
sources = [ "file:\/\/\/Users\/me\/test-registry", "https:\/\/my.org\/registry", "git:\/\/github.com\/mesosphere\/universe.git",]
|
||||
[marathon]
|
||||
port = 8080
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
|
||||
11
tests/data/marathon/zero_instance_sleep.json
Normal file
11
tests/data/marathon/zero_instance_sleep.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "zero-instance-app",
|
||||
"cmd": "sleep 1000",
|
||||
"cpus": 0.1,
|
||||
"mem": 16,
|
||||
"instances": 0,
|
||||
"labels": {
|
||||
"PACKAGE_ID": "zero-instance-app",
|
||||
"PACKAGE_VERSION": "1.2.3"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user