Add support for python type defined argument parser

This commit is contained in:
Darko Poljak 2019-06-22 12:55:56 +02:00
parent 4d55297a64
commit 1b7daee1c9
6 changed files with 96 additions and 65 deletions

View file

@ -1,7 +1,7 @@
import os import os
import re import re
import sys
from cdist.core.pytypes import * from cdist.core.pytypes import *
import argparse
class FileType(PythonType): class FileType(PythonType):
@ -101,3 +101,15 @@ class FileType(PythonType):
self.die('Unknown state {}'.format(state_should)) self.die('Unknown state {}'.format(state_should))
return "\n".join(code) 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

View file

@ -22,10 +22,8 @@
# #
import os import os
import importlib.util
import inspect import inspect
import cdist from cdist.core.pytypes import get_pytype_class
from cdist.core.pytypes import PythonType
from . import util from . import util
@ -122,25 +120,8 @@ class Code(object):
def run_py(self, cdist_object): def run_py(self, cdist_object):
cdist_type = cdist_object.cdist_type cdist_type = cdist_object.cdist_type
module_name = cdist_type.name type_class = get_pytype_class(cdist_type)
file_path = os.path.join(cdist_type.absolute_path, '__init__.py') if type_class is not 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)
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
env = os.environ.copy() env = os.environ.copy()
env.update(self.env) env.update(self.env)
message_prefix = cdist_object.name message_prefix = cdist_object.name

View file

@ -22,12 +22,11 @@
import logging import logging
import os import os
import importlib.util
import inspect import inspect
import cdist import cdist
import cdist.emulator import cdist.emulator
from . import util from . import util
from cdist.core.pytypes import PythonType, Command from cdist.core.pytypes import Command, get_pytype_class
''' '''
@ -234,27 +233,11 @@ class Manifest(object):
def run_py_type_manifest(self, cdist_object): def run_py_type_manifest(self, cdist_object):
cdist_type = cdist_object.cdist_type cdist_type = cdist_object.cdist_type
module_name = cdist_type.name type_class = get_pytype_class(cdist_type)
file_path = os.path.join(cdist_type.absolute_path, '__init__.py') if type_class is not None:
message_prefix = cdist_object.name
if os.path.isfile(file_path):
self.log.verbose("Running python type manifest for object %s", self.log.verbose("Running python type manifest for object %s",
cdist_object.name) cdist_object.name)
spec = importlib.util.spec_from_file_location(module_name, message_prefix = cdist_object.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
env = self.env_py_type_manifest(cdist_object) env = self.env_py_type_manifest(cdist_object)
type_obj = type_class(env=env, cdist_object=cdist_object, type_obj = type_class(env=env, cdist_object=cdist_object,
local=self.local, remote=None, local=self.local, remote=None,

View file

@ -4,6 +4,9 @@ import io
import sys import sys
import re import re
from cdist import message, Error from cdist import message, Error
import importlib.util
import inspect
import cdist
__all__ = ["PythonType", "Command", "command"] __all__ = ["PythonType", "Command", "command"]
@ -13,18 +16,20 @@ class PythonType:
def __init__(self, env, cdist_object, local, remote, message_prefix=None): def __init__(self, env, cdist_object, local, remote, message_prefix=None):
self.env = env self.env = env
self.cdist_object = cdist_object self.cdist_object = 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.local = local
self.remote = remote self.remote = remote
self.object_path = cdist_object.absolute_path if self.cdist_object:
self.type_path = cdist_object.cdist_type.absolute_path self.object_id = cdist_object.object_id
self.explorer_path = os.path.join(self.object_path, 'explorer') self.object_name = cdist_object.name
self.parameters = cdist_object.parameters self.cdist_type = cdist_object.cdist_type
self.stdin_path = os.path.join(self.object_path, 'stdin') self.object_path = cdist_object.absolute_path
self.log = logging.getLogger( self.explorer_path = os.path.join(self.object_path, 'explorer')
self.local.target_host[0] + ':' + self.object_name) 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)
self.message_prefix = message_prefix self.message_prefix = message_prefix
self.message = None self.message = None
@ -62,12 +67,6 @@ class PythonType:
def die(self, msg): def die(self, msg):
raise Error("{}: {}".format(self.cdist_object, 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): def manifest(self, stdout=None, stderr=None):
try: try:
if self.message_prefix: if self.message_prefix:
@ -123,6 +122,15 @@ class PythonType:
return match return match
return None return None
def get_args_parser(self):
pass
def type_manifest(self):
pass
def type_gencode(self):
pass
class Command: class Command:
def __init__(self, name, *args, **kwargs): def __init__(self, name, *args, **kwargs):
@ -160,3 +168,23 @@ class Command:
def command(name, *args, **kwargs): def command(name, *args, **kwargs):
return 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

View file

@ -29,6 +29,9 @@ import sys
import cdist import cdist
from cdist import core from cdist import core
from cdist import flock from cdist import flock
import cdist.util.python_type_util as pytype_util
from cdist.core.pytypes import get_pytype_class
import inspect
class MissingRequiredEnvironmentVariableError(cdist.Error): class MissingRequiredEnvironmentVariableError(cdist.Error):
@ -94,7 +97,28 @@ class Emulator(object):
def run(self): def run(self):
"""Emulate type commands (i.e. __file and co)""" """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() self.init_object()
# locking for parallel execution # locking for parallel execution
@ -125,9 +149,7 @@ class Emulator(object):
self.log = logging.getLogger(self.target_host[0]) self.log = logging.getLogger(self.target_host[0])
def commandline(self): def get_args_parser(self):
"""Parse command line"""
parser = argparse.ArgumentParser(add_help=False, parser = argparse.ArgumentParser(add_help=False,
argument_default=argparse.SUPPRESS) argument_default=argparse.SUPPRESS)
@ -159,8 +181,10 @@ class Emulator(object):
# If not singleton support one positional parameter # If not singleton support one positional parameter
if not self.cdist_type.is_singleton: if not self.cdist_type.is_singleton:
parser.add_argument("object_id", nargs=1) 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.args = parser.parse_args(self.argv[1:])
self.log.trace('Args: %s' % self.args) self.log.trace('Args: %s' % self.args)

View file

@ -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. When writing python type you can extend **cdist.core.pytypes.PythonType** class.
You need to implement the following methods: 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>** * **type_manifest**: implementation should yield **cdist.core.pytypes.<type-name>**
attribute function call result, or **yield from ()** if type does not use other types attribute function call result, or **yield from ()** if type does not use other types
* **type_gencode**: implementation should return a string consisting of lines * **type_gencode**: implementation should return a string consisting of lines