Commit 4630b247 authored by Jérome Perrin's avatar Jérome Perrin

cli/request: support passing instance parameters from a file

Support a syntax like:

      slapos request \
        --node=computer_guid=local \
        --parameters-file=~/request.json \
        ERP5 \
        https://lab.nexedi.com/nexedi/slapos/raw/1.0.145/software/erp5/software.cfg

to request an instance with parameters in ~/request.json file.

This automatically understands the serialisation, when software uses json-in-xml
serialisation, no need to use `_` argument with the encoded json.
parent 78ba275c
...@@ -27,15 +27,45 @@ ...@@ -27,15 +27,45 @@
# #
############################################################################## ##############################################################################
import argparse
import json
import os.path
import pprint import pprint
import lxml.etree
from slapos.cli.config import ClientConfigCommand from slapos.cli.config import ClientConfigCommand
from slapos.client import init, ClientConfig, _getSoftwareReleaseFromSoftwareString from slapos.client import (ClientConfig, _getSoftwareReleaseFromSoftwareString,
init)
from slapos.slap import ResourceNotReady from slapos.slap import ResourceNotReady
from slapos.util import SoftwareReleaseSchema, SoftwareReleaseSerialisation
try:
from typing import IO, Dict
except ImportError:
pass
def getParametersFromFile(file, serialisation):
# type: (IO[str], SoftwareReleaseSerialisation) -> Dict
extension = os.path.splitext(file.name)[1]
if extension == '.xml':
tree = lxml.etree.parse(file)
params = {e.attrib['id']: e.text for e in tree.findall('/parameter')}
# because the use case of xml files is to copy paste existing XML parameters
# as found on slapos web interface, we aren't clever regarding the
# serialisation and assume they are already correct.
serialisation = None
else:
params = json.load(file)
if serialisation == SoftwareReleaseSerialisation.JsonInXml and list(params.keys()) != ['_']:
params = {'_': json.dumps(params)}
return params
def parse_option_dict(options): def parse_option_dict(options):
# type: (str) -> Dict
""" """
Parse a list of option strings like foo=bar baz=qux and return a dictionary. Parse a list of option strings like foo=bar baz=qux and return a dictionary.
Will raise if keys are repeated. Will raise if keys are repeated.
...@@ -78,10 +108,18 @@ class RequestCommand(ClientConfigCommand): ...@@ -78,10 +108,18 @@ class RequestCommand(ClientConfigCommand):
action='store_true', action='store_true',
help='Ask for a slave instance') help='Ask for a slave instance')
ap.add_argument('--parameters', parameter_args = ap.add_mutually_exclusive_group()
parameter_args.add_argument(
'--parameters',
nargs='+', nargs='+',
help="Give your configuration 'option1=value1 option2=value2'") help="Instance parameters, in the form 'option1=value1 option2=value2'.")
parameter_args.add_argument(
'--parameters-file',
type=argparse.FileType('r'),
help="Instance parameters, in a file.\n"
"The file will be interpreted as json, yaml or xml depending on the file extension.")
return ap return ap
def take_action(self, args): def take_action(self, args):
...@@ -104,11 +142,17 @@ def do_request(logger, conf, local): ...@@ -104,11 +142,17 @@ def do_request(logger, conf, local):
if conf.software_url in local: if conf.software_url in local:
conf.software_url = local[conf.software_url] conf.software_url = local[conf.software_url]
software_schema = SoftwareReleaseSchema(conf.software_url, conf.type)
software_schema_serialisation = software_schema.getSerialisation()
parameters = conf.parameters
if conf.parameters_file:
parameters = getParametersFromFile(conf.parameters_file, software_schema_serialisation)
try: try:
partition = local['slap'].registerOpenOrder().request( partition = local['slap'].registerOpenOrder().request(
software_release=conf.software_url, software_release=conf.software_url,
partition_reference=conf.reference, partition_reference=conf.reference,
partition_parameter_kw=conf.parameters, partition_parameter_kw=parameters,
software_type=conf.type, software_type=conf.type,
filter_kw=conf.node, filter_kw=conf.node,
state=conf.state, state=conf.state,
......
...@@ -690,3 +690,113 @@ class TestCliRequest(CliMixin): ...@@ -690,3 +690,113 @@ class TestCliRequest(CliMixin):
'instance reference', 'instance reference',
'software URL', 'software URL',
) )
class TestCliRequestParametersFileJson(CliMixin):
"""Request with --parameter-file, with a .json file.
"""
expected_partition_parameter_kw = {'foo': ['bar']}
def _makeParameterFile(self):
f = tempfile.NamedTemporaryFile(suffix='.json', mode='w', delete=False)
self.addCleanup(os.unlink, f.name)
f.write(textwrap.dedent('''\
{
"foo": ["bar"]
}
'''))
f.flush()
return f.name
def test_request_parameters_file(self):
self.conf.reference = 'instance reference'
self.conf.software_url = 'software URL'
self.conf.parameters = None
f = open(self._makeParameterFile())
self.addCleanup(f.close)
self.conf.parameters_file = f
self.conf.node = {'computer_guid': 'COMP-1234'}
self.conf.type = None
self.conf.state = None
self.conf.slave = False
with patch.object(
slapos.slap.slap,
'registerOpenOrder',
return_value=mock.create_autospec(slapos.slap.OpenOrder)) as registerOpenOrder:
slapos.cli.request.do_request(self.logger, self.conf, self.local)
registerOpenOrder().request.assert_called_once_with(
software_release='software URL',
partition_reference='instance reference',
partition_parameter_kw=self.expected_partition_parameter_kw,
software_type=None,
filter_kw={'computer_guid': 'COMP-1234'},
state=None,
shared=False,
)
self.logger.info.assert_any_call(
'Requesting %s as instance of %s...',
'instance reference',
'software URL',
)
class TestCliRequestParametersFileJsonJsonInXMLSerialisation(
TestCliRequestParametersFileJson):
"""Request with --parameter-file, with a .json file and a software using
json-in-xml for serialisation. In that case, the parameters are automatically
serialised with {'_': json.dumps(params)}
"""
expected_partition_parameter_kw = {"_": "{\"foo\": [\"bar\"]}"}
def test_request_parameters_file(self):
with mock.patch(
'slapos.cli.request.SoftwareReleaseSchema.getSerialisation',
return_value='json-in-xml'):
super(TestCliRequestParametersFileJsonJsonInXMLSerialisation,
self).test_request_parameters_file()
class TestCliRequestParametersFileJsonJsonInXMLSerialisationAlreadySerialised(
TestCliRequestParametersFileJson):
"""Request with --parameter-file, with a .json file and a software using
json-in-xml for serialisation and parameters already serialised with
{'_': json.dumps(params)}. In that case, parameters are not serialized one
more time.
"""
expected_partition_parameter_kw = {"_": "{\"foo\": [\"bar\"]}"}
def _makeParameterFile(self):
f = tempfile.NamedTemporaryFile(suffix='.json', mode='w', delete=False)
self.addCleanup(os.unlink, f.name)
f.write(textwrap.dedent(r'''
{"_": "{\"foo\": [\"bar\"]}"}
'''))
f.flush()
return f.name
def test_request_parameters_file(self):
with mock.patch(
'slapos.cli.request.SoftwareReleaseSchema.getSerialisation',
return_value='json-in-xml'):
super(
TestCliRequestParametersFileJsonJsonInXMLSerialisationAlreadySerialised,
self).test_request_parameters_file()
class TestCliRequestParametersFileXml(TestCliRequestParametersFileJson):
"""Request with --parameter-file, with a .xml file
"""
expected_partition_parameter_kw = {'foo': 'bar'}
def _makeParameterFile(self):
f = tempfile.NamedTemporaryFile(suffix='.xml', mode='w', delete=False)
f.write(textwrap.dedent('''\
<?xml version="1.0" encoding="utf-8"?>
<instance>
<parameter id="foo">bar</parameter>
</instance>
'''))
f.flush()
self.addCleanup(os.unlink, f.name)
return f.name
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