Commit 58888667 authored by Jérome Perrin's avatar Jérome Perrin

service list, service info: output json

## before

```console
$ slapos service list
List of services:
erp5 /srv/slapgrid/slappart15/srv/project/slapos/software/erp5/software.cfg
slapos-sr-testing /srv/slapgrid/slappart15/srv/project/slapos/software/slapos-sr-testing/software.cfg
slapos-testing /srv/slapgrid/slappart15/srv/project/slapos/software/slapos-testing/software.cfg
hugo /srv/slapgrid/slappart15/srv/project/slapos/software/hugo/software.cfg
metabase /srv/slapgrid/slappart15/srv/project/slapos/software/metabase/software.cfg
dufs /srv/slapgrid/slappart15/srv/project/slapos/software/webdav/software.cfg
```

```console
$ slapos service info slapos-sr-testing
Software Release URL: /srv/slapgrid/slappart15/srv/project/slapos/software/slapos-sr-testing/software.cfg
Instance state: busy
Instance parameters:
{}
Connection parameters:
("<?xml version='1.0' encoding='utf-8'?>\n"
'<instance>\n'
'  <parameter '
'id="environment-script">/srv/slapgrid/slappart15/srv/runner/instance/slappart7/etc/slapos-local-development-environment.sh</parameter>\n'
```

## after

```console
$ slapos service list
{
  "dufs": "/srv/slapgrid/slappart15/srv/project/slapos/software/webdav/software.cfg",
  "erp5": "/srv/slapgrid/slappart15/srv/project/slapos/software/erp5/software.cfg",
  "hugo": "/srv/slapgrid/slappart15/srv/project/slapos/software/hugo/software.cfg",
  "metabase": "/srv/slapgrid/slappart15/srv/project/slapos/software/metabase/software.cfg",
  "slapos-sr-testing": "/srv/slapgrid/slappart15/srv/project/slapos/software/slapos-sr-testing/software.cfg",
  "slapos-testing": "/srv/slapgrid/slappart15/srv/project/slapos/software/slapos-testing/software.cfg"
}
```
```console
$ slapos service info slapos-sr-testing
{
  "software-url": "/srv/slapgrid/slappart15/srv/project/slapos/software/slapos-sr-testing/software.cfg",
  "instance-state": "busy",
  "instance-parameters": {},
  "connection-parameters": {
    "environment-script": "/srv/slapgrid/slappart15/srv/runner/instance/slappart7/etc/slapos-local-development-environment.sh"
  }
}
```


See merge request nexedi/slapos.core!441
parents 4d34122a b623b78b
Changes Changes
======= =======
1.8.4 (unreleased)
------------------
* service list, service info: output json
1.8.3 (2022-10-17) 1.8.3 (2022-10-17)
------------------ ------------------
* format: use correct IPv4 for tun interfaces * format: use correct IPv4 for tun interfaces
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
# #
############################################################################## ##############################################################################
import json
import pprint import pprint
import sys import sys
...@@ -35,6 +36,14 @@ from slapos.cli.config import ClientConfigCommand ...@@ -35,6 +36,14 @@ from slapos.cli.config import ClientConfigCommand
from slapos.client import init, ClientConfig from slapos.client import init, ClientConfig
from slapos.slap import ResourceNotReady, NotFoundError from slapos.slap import ResourceNotReady, NotFoundError
from slapos.util import (
SoftwareReleaseSchema,
SoftwareReleaseSerialisation,
StrPrettyPrinter,
UndefinedSerializationError,
xml2dict,
)
class InfoCommand(ClientConfigCommand): class InfoCommand(ClientConfigCommand):
"""get status, software_release and parameters of an instance""" """get status, software_release and parameters of an instance"""
...@@ -70,10 +79,26 @@ def do_info(logger, conf, local): ...@@ -70,10 +79,26 @@ def do_info(logger, conf, local):
logger.warning('Instance %s does not exist.', conf.reference) logger.warning('Instance %s does not exist.', conf.reference)
return(2) return(2)
logger.info('Software Release URL: %s', instance._software_release_url) software_schema = SoftwareReleaseSchema(
logger.info('Instance state: %s', instance._requested_state) instance._software_release_url,
logger.info('Instance parameters:') getattr(instance, '_software_type', None))
logger.info(pprint.pformat(instance._parameter_dict)) connection_parameter_dict = xml2dict(instance._connection_dict)
logger.info('Connection parameters:') try:
logger.info(pprint.pformat(instance._connection_dict)) software_serialisation = software_schema.getSerialisation()
except UndefinedSerializationError:
software_serialisation = SoftwareReleaseSerialisation.JsonInXml
if software_serialisation == SoftwareReleaseSerialisation.JsonInXml:
if '_' in connection_parameter_dict:
connection_parameter_dict = json.loads(connection_parameter_dict['_'])
logger.info(
json.dumps(
{
'software-url': instance._software_release_url,
'instance-state': instance._requested_state,
'instance-parameters': instance._parameter_dict,
'connection-parameters': connection_parameter_dict,
},
indent=2,
)
)
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
# #
############################################################################## ##############################################################################
import json
import sys import sys
import six import six
...@@ -54,9 +55,9 @@ def do_list(logger, conf, local): ...@@ -54,9 +55,9 @@ def do_list(logger, conf, local):
resetLogger(logger) resetLogger(logger)
# XXX catch exception # XXX catch exception
instance_dict = local['slap'].getOpenOrderDict() instance_dict = local['slap'].getOpenOrderDict()
if instance_dict == {}: logger.info(
logger.info('No existing service.') json.dumps(
return {title: instance._software_release_url
logger.info('List of services:') for (title, instance) in six.iteritems(instance_dict)},
for title, instance in six.iteritems(instance_dict): sort_keys=True,
logger.info('%s %s', title, instance._software_release_url) indent=2))
...@@ -41,7 +41,7 @@ from slapos.client import (ClientConfig, _getSoftwareReleaseFromSoftwareString, ...@@ -41,7 +41,7 @@ from slapos.client import (ClientConfig, _getSoftwareReleaseFromSoftwareString,
init) init)
from slapos.slap import ResourceNotReady from slapos.slap import ResourceNotReady
from slapos.util import (SoftwareReleaseSchema, SoftwareReleaseSerialisation, from slapos.util import (SoftwareReleaseSchema, SoftwareReleaseSerialisation,
UndefinedSerializationError) UndefinedSerializationError, StrPrettyPrinter)
try: try:
from typing import IO, Dict from typing import IO, Dict
...@@ -138,16 +138,6 @@ class RequestCommand(ClientConfigCommand): ...@@ -138,16 +138,6 @@ class RequestCommand(ClientConfigCommand):
do_request(self.app.log, conf, local) do_request(self.app.log, conf, local)
# BBB on python3 we can use pprint.pformat
class StrPrettyPrinter(pprint.PrettyPrinter):
"""A PrettyPrinter which produces consistent output on python 2 and 3
"""
def format(self, object, context, maxlevels, level):
if six.PY2 and isinstance(object, six.text_type):
object = object.encode('utf-8')
return pprint.PrettyPrinter.format(self, object, context, maxlevels, level)
def do_request(logger, conf, local): def do_request(logger, conf, local):
logger.info('Requesting %s as instance of %s...', logger.info('Requesting %s as instance of %s...',
conf.reference, conf.software_url) conf.reference, conf.software_url)
......
...@@ -62,6 +62,7 @@ import slapos.grid.svcbackend ...@@ -62,6 +62,7 @@ import slapos.grid.svcbackend
import slapos.proxy import slapos.proxy
import slapos.slap import slapos.slap
import six
import supervisor.supervisorctl import supervisor.supervisorctl
signature_certificate_list = """-----BEGIN CERTIFICATE----- signature_certificate_list = """-----BEGIN CERTIFICATE-----
...@@ -602,38 +603,59 @@ class TestCliList(CliMixin): ...@@ -602,38 +603,59 @@ class TestCliList(CliMixin):
'instance2': slapos.slap.SoftwareInstance(_title='instance2', _software_release_url='SR2'), 'instance2': slapos.slap.SoftwareInstance(_title='instance2', _software_release_url='SR2'),
} }
with patch.object(slapos.slap.slap, 'getOpenOrderDict', return_value=return_value) as _: with patch.object(slapos.slap.slap, 'getOpenOrderDict', return_value=return_value) as _:
slapos.cli.list.do_list(self.logger, None, self.local) slapos.cli.list.do_list(self.logger, self.conf, self.local)
if six.PY3:
self.logger.info.assert_any_call('%s %s', 'instance1', 'SR1') self.logger.info.assert_called_once_with('{\n "instance1": "SR1",\n "instance2": "SR2"\n}')
self.logger.info.assert_any_call('%s %s', 'instance2', 'SR2')
def test_emptyList(self): def test_emptyList(self):
with patch.object(slapos.slap.slap, 'getOpenOrderDict', return_value={}) as _: with patch.object(slapos.slap.slap, 'getOpenOrderDict', return_value={}) as _:
slapos.cli.list.do_list(self.logger, None, self.local) slapos.cli.list.do_list(self.logger, self.conf, self.local)
self.logger.info.assert_called_once_with('No existing service.') self.logger.info.assert_called_once_with('{}')
@patch.object(slapos.slap.slap, 'registerOpenOrder', return_value=slapos.slap.OpenOrder()) @patch.object(slapos.slap.slap, 'registerOpenOrder', return_value=slapos.slap.OpenOrder())
class TestCliInfo(CliMixin): class TestCliInfo(CliMixin):
def test_info(self, _):
def test_info_xml_serialisation(self, _):
self._test_info_output(
slapos.slap.SoftwareInstance(
_software_release_url='SR1',
_requested_state='mystate',
_connection_dict=
'<?xml version=\'1.0\' encoding=\'utf-8\'?>\n<instance>\n <parameter id="myconnectionparameter">value1</parameter>\n</instance>\n',
_parameter_dict={'myinstanceparameter': 'value2'}))
def test_info_json_in_serialisation(self, _):
self._test_info_output(
slapos.slap.SoftwareInstance(
_software_release_url='SR1',
_requested_state='mystate',
_connection_dict=
'<?xml version=\'1.0\' encoding=\'utf-8\'?>\n<instance>\n <parameter id="_">{"myconnectionparameter": "value1"}</parameter>\n</instance>\n',
_parameter_dict={'myinstanceparameter': 'value2'}))
def _test_info_output(self, instance):
""" """
Test "slapos service info" command output. Test "slapos service info" command output.
""" """
setattr(self.conf, 'reference', 'instance1') setattr(self.conf, 'reference', 'instance1')
instance = slapos.slap.SoftwareInstance( with patch.object(slapos.slap.OpenOrder, 'getInformation',
_software_release_url='SR1', return_value=instance):
_requested_state = 'mystate',
_connection_dict = {'myconnectionparameter': 'value1'},
_parameter_dict = {'myinstanceparameter': 'value2'}
)
with patch.object(slapos.slap.OpenOrder, 'getInformation', return_value=instance):
slapos.cli.info.do_info(self.logger, self.conf, self.local) slapos.cli.info.do_info(self.logger, self.conf, self.local)
self.logger.info.assert_any_call(pprint.pformat(instance._parameter_dict)) if six.PY3:
self.logger.info.assert_any_call('Software Release URL: %s', instance._software_release_url) self.logger.info.assert_called_once_with(
self.logger.info.assert_any_call('Instance state: %s', instance._requested_state) textwrap.dedent('''\
self.logger.info.assert_any_call(pprint.pformat(instance._parameter_dict)) {
self.logger.info.assert_any_call(pprint.pformat(instance._connection_dict)) "software-url": "SR1",
"instance-state": "mystate",
"instance-parameters": {
"myinstanceparameter": "value2"
},
"connection-parameters": {
"myconnectionparameter": "value1"
}
}'''))
def test_unknownReference(self, _): def test_unknownReference(self, _):
""" """
......
...@@ -1283,13 +1283,10 @@ class TestCliInformation(CliMasterMixin): ...@@ -1283,13 +1283,10 @@ class TestCliInformation(CliMasterMixin):
self.request('http://sr1//', None, 'MyInstance1', None) self.request('http://sr1//', None, 'MyInstance1', None)
self.request('http://sr2//', None, 'MyInstance2', None) self.request('http://sr2//', None, 'MyInstance2', None)
self.request('http://sr3//', None, 'MyInstance3', 'slappart0') self.request('http://sr3//', None, 'MyInstance3', 'slappart0')
output = self.cliDoSlapos(('service', 'list'), stderr=subprocess.DEVNULL).splitlines() output = self.cliDoSlapos(('service', 'list'), stderr=subprocess.DEVNULL)
self.assertEqual(len(output), 4)
self.assertEqual(output[0], 'List of services:')
self.assertEqual( self.assertEqual(
sorted(output[1:]), json.loads(output),
['MyInstance0 http://sr0//', 'MyInstance1 http://sr1//', 'MyInstance2 http://sr2//'], {'MyInstance0': 'http://sr0//', 'MyInstance1': 'http://sr1//', 'MyInstance2': 'http://sr2//'})
)
def test_service_info(self): def test_service_info(self):
self.format_for_number_of_partitions(3) self.format_for_number_of_partitions(3)
...@@ -1298,27 +1295,23 @@ class TestCliInformation(CliMasterMixin): ...@@ -1298,27 +1295,23 @@ class TestCliInformation(CliMasterMixin):
self.request('http://sr2//', None, 'MyInstance2', 'slappart0') self.request('http://sr2//', None, 'MyInstance2', 'slappart0')
output0 = self.cliDoSlapos(('service', 'info', 'MyInstance0'), stderr=subprocess.DEVNULL) output0 = self.cliDoSlapos(('service', 'info', 'MyInstance0'), stderr=subprocess.DEVNULL)
self.assertEqual( self.assertEqual(
output0.splitlines(), json.loads(output0),
[ {
'Software Release URL: http://sr0//', "software-url": "http://sr0//",
'Instance state: busy', "instance-state": "busy",
'Instance parameters:', "instance-parameters": {},
'{}', "connection-parameters": {},
'Connection parameters:', },
'None'
],
) )
output1 = self.cliDoSlapos(('service', 'info', 'MyInstance1'), stderr=subprocess.DEVNULL) output1 = self.cliDoSlapos(('service', 'info', 'MyInstance1'), stderr=subprocess.DEVNULL)
self.assertEqual( self.assertEqual(
output1.splitlines(), json.loads(output1),
[ {
'Software Release URL: http://sr1//', "software-url": "http://sr1//",
'Instance state: busy', "instance-state": "busy",
'Instance parameters:', "instance-parameters": {"couscous": "hello"},
"{'couscous': 'hello'}", "connection-parameters": {},
'Connection parameters:', },
'None'
],
) )
try: try:
self.cliDoSlapos(('service', 'info', 'MyInstance2'), stderr=subprocess.STDOUT) self.cliDoSlapos(('service', 'info', 'MyInstance2'), stderr=subprocess.STDOUT)
......
...@@ -32,6 +32,7 @@ import errno ...@@ -32,6 +32,7 @@ import errno
import hashlib import hashlib
import json import json
import os import os
import pprint
import shutil import shutil
import socket import socket
import sqlite3 import sqlite3
...@@ -474,3 +475,13 @@ class SoftwareReleaseSchema(object): ...@@ -474,3 +475,13 @@ class SoftwareReleaseSchema(object):
instance=instance, instance=instance,
schema=self.getInstanceRequestParameterSchema(), schema=self.getInstanceRequestParameterSchema(),
) )
# BBB on python3 we can use pprint.pformat
class StrPrettyPrinter(pprint.PrettyPrinter):
"""A PrettyPrinter which produces consistent output on python 2 and 3
"""
def format(self, object, context, maxlevels, level):
if six.PY2 and isinstance(object, six.text_type):
object = object.encode('utf-8')
return pprint.PrettyPrinter.format(self, object, context, maxlevels, level)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment