Add support for python type defined argument parser
This commit is contained in:
parent
42cfbc4e15
commit
96949f4f69
6 changed files with 96 additions and 65 deletions
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
from cdist.core.pytypes import *
|
||||
import argparse
|
||||
|
||||
|
||||
class FileType(PythonType):
|
||||
|
@ -101,3 +101,15 @@ class FileType(PythonType):
|
|||
self.die('Unknown state {}'.format(state_should))
|
||||
|
||||
return "\n".join(code)
|
||||
|
||||
def get_args_parser(self):
|
||||
parser = argparse.ArgumentParser(add_help=False,
|
||||
argument_default=argparse.SUPPRESS)
|
||||
parser.add_argument('--state', dest='state', action='store',
|
||||
required=False, default='present')
|
||||
for param in ('group', 'mode', 'owner', 'source'):
|
||||
parser.add_argument('--' + param, dest=param, action='store',
|
||||
required=False, default=None)
|
||||
|
||||
parser.add_argument("object_id", nargs=1)
|
||||
return parser
|
||||
|
|
|
@ -22,10 +22,8 @@
|
|||
#
|
||||
|
||||
import os
|
||||
import importlib.util
|
||||
import inspect
|
||||
import cdist
|
||||
from cdist.core.pytypes import PythonType
|
||||
from cdist.core.pytypes import get_pytype_class
|
||||
from . import util
|
||||
|
||||
|
||||
|
@ -122,25 +120,8 @@ class Code(object):
|
|||
|
||||
def run_py(self, cdist_object):
|
||||
cdist_type = cdist_object.cdist_type
|
||||
module_name = cdist_type.name
|
||||
file_path = os.path.join(cdist_type.absolute_path, '__init__.py')
|
||||
|
||||
if os.path.isfile(file_path):
|
||||
spec = importlib.util.spec_from_file_location(module_name,
|
||||
file_path)
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
classes = inspect.getmembers(m, inspect.isclass)
|
||||
type_class = None
|
||||
for _, cl in classes:
|
||||
if cl != PythonType and issubclass(cl, PythonType):
|
||||
if type_class:
|
||||
raise cdist.Error("Only one python type class is "
|
||||
"supported, but at least two "
|
||||
"found: {}".format((type_class,
|
||||
cl, )))
|
||||
else:
|
||||
type_class = cl
|
||||
type_class = get_pytype_class(cdist_type)
|
||||
if type_class is not None:
|
||||
env = os.environ.copy()
|
||||
env.update(self.env)
|
||||
message_prefix = cdist_object.name
|
||||
|
|
|
@ -22,12 +22,11 @@
|
|||
|
||||
import logging
|
||||
import os
|
||||
import importlib.util
|
||||
import inspect
|
||||
import cdist
|
||||
import cdist.emulator
|
||||
from . import util
|
||||
from cdist.core.pytypes import PythonType, Command
|
||||
from cdist.core.pytypes import Command, get_pytype_class
|
||||
|
||||
|
||||
'''
|
||||
|
@ -248,27 +247,11 @@ class Manifest(object):
|
|||
|
||||
def run_py_type_manifest(self, cdist_object):
|
||||
cdist_type = cdist_object.cdist_type
|
||||
module_name = cdist_type.name
|
||||
file_path = os.path.join(cdist_type.absolute_path, '__init__.py')
|
||||
message_prefix = cdist_object.name
|
||||
if os.path.isfile(file_path):
|
||||
type_class = get_pytype_class(cdist_type)
|
||||
if type_class is not None:
|
||||
self.log.verbose("Running python type manifest for object %s",
|
||||
cdist_object.name)
|
||||
spec = importlib.util.spec_from_file_location(module_name,
|
||||
file_path)
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
classes = inspect.getmembers(m, inspect.isclass)
|
||||
type_class = None
|
||||
for _, cl in classes:
|
||||
if cl != PythonType and issubclass(cl, PythonType):
|
||||
if type_class:
|
||||
raise cdist.Error("Only one python type class is "
|
||||
"supported, but at least two "
|
||||
"found: {}".format((type_class,
|
||||
cl, )))
|
||||
else:
|
||||
type_class = cl
|
||||
message_prefix = cdist_object.name
|
||||
env = self.env_py_type_manifest(cdist_object)
|
||||
type_obj = type_class(env=env, cdist_object=cdist_object,
|
||||
local=self.local, remote=None,
|
||||
|
|
|
@ -4,6 +4,9 @@ import io
|
|||
import sys
|
||||
import re
|
||||
from cdist import message, Error
|
||||
import importlib.util
|
||||
import inspect
|
||||
import cdist
|
||||
|
||||
|
||||
__all__ = ["PythonType", "Command", "command"]
|
||||
|
@ -13,16 +16,18 @@ class PythonType:
|
|||
def __init__(self, env, cdist_object, local, remote, message_prefix=None):
|
||||
self.env = env
|
||||
self.cdist_object = cdist_object
|
||||
self.local = local
|
||||
self.remote = remote
|
||||
if self.cdist_object:
|
||||
self.object_id = cdist_object.object_id
|
||||
self.object_name = cdist_object.name
|
||||
self.cdist_type = cdist_object.cdist_type
|
||||
self.local = local
|
||||
self.remote = remote
|
||||
self.object_path = cdist_object.absolute_path
|
||||
self.type_path = cdist_object.cdist_type.absolute_path
|
||||
self.explorer_path = os.path.join(self.object_path, 'explorer')
|
||||
self.type_path = cdist_object.cdist_type.absolute_path
|
||||
self.parameters = cdist_object.parameters
|
||||
self.stdin_path = os.path.join(self.object_path, 'stdin')
|
||||
if self.local:
|
||||
self.log = logging.getLogger(
|
||||
self.local.target_host[0] + ':' + self.object_name)
|
||||
|
||||
|
@ -62,12 +67,6 @@ class PythonType:
|
|||
def die(self, msg):
|
||||
raise Error("{}: {}".format(self.cdist_object, msg))
|
||||
|
||||
def type_manifest(self):
|
||||
pass
|
||||
|
||||
def type_gencode(self):
|
||||
pass
|
||||
|
||||
def manifest(self, stdout=None, stderr=None):
|
||||
try:
|
||||
if self.message_prefix:
|
||||
|
@ -123,6 +122,15 @@ class PythonType:
|
|||
return match
|
||||
return None
|
||||
|
||||
def get_args_parser(self):
|
||||
pass
|
||||
|
||||
def type_manifest(self):
|
||||
pass
|
||||
|
||||
def type_gencode(self):
|
||||
pass
|
||||
|
||||
|
||||
class Command:
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
|
@ -160,3 +168,23 @@ class Command:
|
|||
|
||||
def command(name, *args, **kwargs):
|
||||
return Command(name, *args, **kwargs)
|
||||
|
||||
|
||||
def get_pytype_class(cdist_type):
|
||||
module_name = cdist_type.name
|
||||
file_path = os.path.join(cdist_type.absolute_path, '__init__.py')
|
||||
type_class = None
|
||||
if os.path.isfile(file_path):
|
||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
classes = inspect.getmembers(m, inspect.isclass)
|
||||
for _, cl in classes:
|
||||
if cl != PythonType and issubclass(cl, PythonType):
|
||||
if type_class:
|
||||
raise cdist.Error(
|
||||
"Only one python type class is supported, but at least"
|
||||
" two found: {}".format((type_class, cl, )))
|
||||
else:
|
||||
type_class = cl
|
||||
return type_class
|
||||
|
|
|
@ -30,6 +30,9 @@ import cdist
|
|||
from cdist import core
|
||||
from cdist import flock
|
||||
from cdist.core.manifest import Manifest
|
||||
import cdist.util.python_type_util as pytype_util
|
||||
from cdist.core.pytypes import get_pytype_class
|
||||
import inspect
|
||||
|
||||
|
||||
class MissingRequiredEnvironmentVariableError(cdist.Error):
|
||||
|
@ -100,7 +103,28 @@ class Emulator(object):
|
|||
def run(self):
|
||||
"""Emulate type commands (i.e. __file and co)"""
|
||||
|
||||
self.commandline()
|
||||
args_parser = None
|
||||
if pytype_util.is_python_type(self.cdist_type):
|
||||
type_class = get_pytype_class(self.cdist_type)
|
||||
if type_class is not None:
|
||||
# We only need to call parse_args so we need plain instance.
|
||||
type_obj = type_class(env=None, cdist_object=None, local=None,
|
||||
remote=None)
|
||||
if (hasattr(type_obj, 'get_args_parser') and
|
||||
inspect.ismethod(type_obj.get_args_parser)):
|
||||
args_parser = type_obj.get_args_parser()
|
||||
self.log.trace("Using python type argument parser")
|
||||
print("Using python type argument parser")
|
||||
if args_parser is None:
|
||||
# Fallback to classic way.
|
||||
args_parser = self.get_args_parser()
|
||||
self.log.trace("Fallback to classic argument parser")
|
||||
print("Fallback to classic argument parser")
|
||||
else:
|
||||
args_parser = self.get_args_parser()
|
||||
self.log.trace("Using emulator classic argument parser")
|
||||
print("Using emulator classic argument parser")
|
||||
self.commandline(args_parser)
|
||||
self.init_object()
|
||||
|
||||
# locking for parallel execution
|
||||
|
@ -131,9 +155,7 @@ class Emulator(object):
|
|||
|
||||
self.log = logging.getLogger(self.target_host[0])
|
||||
|
||||
def commandline(self):
|
||||
"""Parse command line"""
|
||||
|
||||
def get_args_parser(self):
|
||||
parser = argparse.ArgumentParser(add_help=False,
|
||||
argument_default=argparse.SUPPRESS)
|
||||
|
||||
|
@ -165,8 +187,10 @@ class Emulator(object):
|
|||
# If not singleton support one positional parameter
|
||||
if not self.cdist_type.is_singleton:
|
||||
parser.add_argument("object_id", nargs=1)
|
||||
return parser
|
||||
|
||||
# And finally parse/verify parameter
|
||||
def commandline(self, parser):
|
||||
"""Parse command line"""
|
||||
self.args = parser.parse_args(self.argv[1:])
|
||||
self.log.trace('Args: %s' % self.args)
|
||||
|
||||
|
|
|
@ -542,6 +542,9 @@ in shell, since this is the code that is directly executed at target host.
|
|||
When writing python type you can extend **cdist.core.pytypes.PythonType** class.
|
||||
You need to implement the following methods:
|
||||
|
||||
* **get_args_parser**: implementation should return **argparse.ArgumentParser** and if
|
||||
it is undefined or returned None then cdist falls back to classic type parameter
|
||||
definition and argument parsing
|
||||
* **type_manifest**: implementation should yield **cdist.core.pytypes.<type-name>**
|
||||
attribute function call result, or **yield from ()** if type does not use other types
|
||||
* **type_gencode**: implementation should return a string consisting of lines
|
||||
|
|
Loading…
Reference in a new issue