Add support for python type defined argument parser
This commit is contained in:
		
					parent
					
						
							
								00f85be81b
							
						
					
				
			
			
				commit
				
					
						ac8c9fa842
					
				
			
		
					 6 changed files with 96 additions and 65 deletions
				
			
		| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
'''
 | 
					'''
 | 
				
			||||||
| 
						 | 
					@ -248,27 +247,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,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,9 @@ import cdist
 | 
				
			||||||
from cdist import core
 | 
					from cdist import core
 | 
				
			||||||
from cdist import flock
 | 
					from cdist import flock
 | 
				
			||||||
from cdist.core.manifest import Manifest
 | 
					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):
 | 
					class MissingRequiredEnvironmentVariableError(cdist.Error):
 | 
				
			||||||
| 
						 | 
					@ -100,7 +103,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
 | 
				
			||||||
| 
						 | 
					@ -131,9 +155,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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,8 +187,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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue