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 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,18 +16,20 @@ class PythonType:
 | 
			
		|||
    def __init__(self, env, cdist_object, local, remote, message_prefix=None):
 | 
			
		||||
        self.env = env
 | 
			
		||||
        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.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.parameters = cdist_object.parameters
 | 
			
		||||
        self.stdin_path = os.path.join(self.object_path, 'stdin')
 | 
			
		||||
        self.log = logging.getLogger(
 | 
			
		||||
            self.local.target_host[0] + ':' + self.object_name)
 | 
			
		||||
        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.object_path = cdist_object.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)
 | 
			
		||||
 | 
			
		||||
        self.message_prefix = message_prefix
 | 
			
		||||
        self.message = None
 | 
			
		||||
| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue