2011-09-26 09:18:36 +00:00
# -*- coding: utf-8 -*-
#
2013-08-28 13:39:17 +00:00
# 2011-2013 Nico Schottelius (nico-cdist at schottelius.org)
2013-08-29 20:32:26 +00:00
# 2012-2013 Steven Armstrong (steven-cdist at armstrong.cc)
2011-09-26 09:18:36 +00:00
#
# This file is part of cdist.
#
# cdist is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cdist is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see <http://www.gnu.org/licenses/>.
#
#
import argparse
import logging
import os
2012-06-04 12:11:34 +00:00
import sys
2011-09-26 09:18:36 +00:00
2011-09-26 22:49:12 +00:00
import cdist
2011-10-11 10:27:08 +00:00
from cdist import core
2011-09-26 09:21:04 +00:00
2013-08-19 11:34:29 +00:00
class MissingRequiredEnvironmentVariableError ( cdist . Error ) :
def __init__ ( self , name ) :
self . name = name
self . message = " Emulator requires the environment variable %s to be setup " % self . name
def __str__ ( self ) :
return self . message
2013-08-19 10:03:25 +00:00
2013-12-19 22:33:43 +00:00
class DefaultList ( list ) :
""" Helper class to allow default values for optional_multiple parameters.
@see https : / / groups . google . com / forum / #!msg/comp.lang.python/sAUvkJEDpRc/RnRymrzJVDYJ
"""
def __copy__ ( self ) :
return [ ]
@classmethod
def create ( cls , initial = None ) :
if initial :
2013-12-20 09:56:46 +00:00
return cls ( initial . split ( ' \n ' ) )
2013-12-19 22:33:43 +00:00
2011-10-15 00:31:40 +00:00
class Emulator ( object ) :
2012-11-19 11:04:07 +00:00
def __init__ ( self , argv , stdin = sys . stdin . buffer , env = os . environ ) :
2011-10-15 00:31:40 +00:00
self . argv = argv
2012-11-07 08:58:47 +00:00
self . stdin = stdin
2012-11-07 09:49:11 +00:00
self . env = env
2013-07-10 14:31:58 +00:00
self . object_id = ' '
2011-10-15 00:31:40 +00:00
2013-08-19 10:03:25 +00:00
try :
self . global_path = self . env [ ' __global ' ]
self . target_host = self . env [ ' __target_host ' ]
# Internally only
self . object_source = self . env [ ' __cdist_manifest ' ]
self . type_base_path = self . env [ ' __cdist_type_base_path ' ]
2012-02-12 00:27:25 +00:00
2013-08-19 11:34:29 +00:00
except KeyError as e :
raise MissingRequiredEnvironmentVariableError ( e . args [ 0 ] )
2011-10-18 11:32:36 +00:00
2011-10-15 00:31:40 +00:00
self . object_base_path = os . path . join ( self . global_path , " object " )
self . type_name = os . path . basename ( argv [ 0 ] )
2012-02-12 00:27:25 +00:00
self . cdist_type = core . CdistType ( self . type_base_path , self . type_name )
2011-10-15 00:31:40 +00:00
self . __init_log ( )
def run ( self ) :
""" Emulate type commands (i.e. __file and co) """
self . commandline ( )
self . setup_object ( )
2012-06-04 12:11:34 +00:00
self . save_stdin ( )
2011-10-15 00:31:40 +00:00
self . record_requirements ( )
2011-11-02 22:58:18 +00:00
self . record_auto_requirements ( )
2011-10-15 00:31:40 +00:00
self . log . debug ( " Finished %s %s " % ( self . cdist_object . path , self . parameters ) )
def __init_log ( self ) :
""" Setup logging facility """
2012-11-07 09:25:47 +00:00
if ' __cdist_debug ' in self . env :
2011-10-15 00:31:40 +00:00
logging . root . setLevel ( logging . DEBUG )
else :
logging . root . setLevel ( logging . INFO )
2013-09-02 08:49:11 +00:00
self . log = logging . getLogger ( self . target_host )
2011-10-15 00:31:40 +00:00
def commandline ( self ) :
""" Parse command line """
2012-11-19 14:17:46 +00:00
parser = argparse . ArgumentParser ( add_help = False , argument_default = argparse . SUPPRESS )
2011-10-15 00:31:40 +00:00
2012-06-04 20:01:32 +00:00
for parameter in self . cdist_type . required_parameters :
argument = " -- " + parameter
parser . add_argument ( argument , dest = parameter , action = ' store ' , required = True )
for parameter in self . cdist_type . required_multiple_parameters :
argument = " -- " + parameter
parser . add_argument ( argument , dest = parameter , action = ' append ' , required = True )
2011-10-15 00:31:40 +00:00
for parameter in self . cdist_type . optional_parameters :
argument = " -- " + parameter
2013-09-04 20:11:42 +00:00
parser . add_argument ( argument , dest = parameter , action = ' store ' , required = False ,
default = self . cdist_type . parameter_defaults . get ( parameter , None ) )
2012-06-04 20:01:32 +00:00
for parameter in self . cdist_type . optional_multiple_parameters :
2011-10-15 00:31:40 +00:00
argument = " -- " + parameter
2013-09-04 20:11:42 +00:00
parser . add_argument ( argument , dest = parameter , action = ' append ' , required = False ,
2013-12-19 22:33:43 +00:00
default = DefaultList . create ( self . cdist_type . parameter_defaults . get ( parameter , None ) ) )
2012-02-15 13:44:16 +00:00
for parameter in self . cdist_type . boolean_parameters :
argument = " -- " + parameter
parser . add_argument ( argument , dest = parameter , action = ' store_const ' , const = ' ' )
2011-10-15 00:31:40 +00:00
# If not singleton support one positional parameter
if not self . cdist_type . is_singleton :
parser . add_argument ( " object_id " , nargs = 1 )
# And finally parse/verify parameter
self . args = parser . parse_args ( self . argv [ 1 : ] )
2011-10-15 00:36:33 +00:00
self . log . debug ( ' Args: %s ' % self . args )
2011-10-15 00:31:40 +00:00
def setup_object ( self ) :
2012-02-13 06:45:10 +00:00
# Setup object_id - FIXME: unset / do not setup anymore!
2013-07-10 14:31:58 +00:00
if not self . cdist_type . is_singleton :
2011-10-15 00:31:40 +00:00
self . object_id = self . args . object_id [ 0 ]
del self . args . object_id
2011-10-14 13:52:53 +00:00
2011-10-15 00:31:40 +00:00
# Instantiate the cdist object we are defining
2012-02-11 23:08:18 +00:00
self . cdist_object = core . CdistObject ( self . cdist_type , self . object_base_path , self . object_id )
2011-10-14 13:52:53 +00:00
2011-10-15 00:31:40 +00:00
# Create object with given parameters
self . parameters = { }
for key , value in vars ( self . args ) . items ( ) :
if value is not None :
self . parameters [ key ] = value
2011-10-18 11:32:36 +00:00
2011-10-15 00:31:40 +00:00
if self . cdist_object . exists :
2011-10-16 16:38:22 +00:00
if self . cdist_object . parameters != self . parameters :
2011-10-15 00:31:40 +00:00
raise cdist . Error ( " Object %s already exists with conflicting parameters: \n %s : %s \n %s : %s "
2012-02-15 08:27:11 +00:00
% ( self . cdist_object . name , " " . join ( self . cdist_object . source ) , self . cdist_object . parameters , self . object_source , self . parameters )
2011-10-11 10:27:08 +00:00
)
2011-10-15 00:31:40 +00:00
else :
self . cdist_object . create ( )
self . cdist_object . parameters = self . parameters
2012-02-13 06:47:33 +00:00
# Record / Append source
self . cdist_object . source . append ( self . object_source )
2012-11-19 11:04:07 +00:00
chunk_size = 65536
def _read_stdin ( self ) :
return self . stdin . read ( self . chunk_size )
2012-06-04 12:11:34 +00:00
def save_stdin ( self ) :
""" If something is written to stdin, save it in the object as
$ __object / stdin so it can be accessed in manifest and gencode - *
scripts .
"""
2012-11-07 08:58:47 +00:00
if not self . stdin . isatty ( ) :
2012-06-04 12:11:34 +00:00
try :
# go directly to file instead of using CdistObject's api
# as that does not support streaming
path = os . path . join ( self . cdist_object . absolute_path , ' stdin ' )
2012-11-19 11:04:07 +00:00
with open ( path , ' wb ' ) as fd :
chunk = self . _read_stdin ( )
while chunk :
fd . write ( chunk )
chunk = self . _read_stdin ( )
2012-06-04 12:11:34 +00:00
except EnvironmentError as e :
raise cdist . Error ( ' Failed to read from stdin: %s ' % e )
2011-10-15 00:31:40 +00:00
def record_requirements ( self ) :
""" record requirements """
2012-11-07 09:25:47 +00:00
if " require " in self . env :
requirements = self . env [ ' require ' ]
2012-11-19 14:17:46 +00:00
self . log . debug ( " reqs = " + requirements )
2011-10-15 00:31:40 +00:00
for requirement in requirements . split ( " " ) :
2011-10-15 21:23:57 +00:00
# Ignore empty fields - probably the only field anyway
2012-02-13 07:44:09 +00:00
if len ( requirement ) == 0 : continue
2011-10-15 21:23:57 +00:00
2012-02-13 07:44:09 +00:00
# Raises an error, if object cannot be created
2013-05-27 14:36:20 +00:00
try :
cdist_object = self . cdist_object . object_from_name ( requirement )
2013-09-02 09:30:22 +00:00
except core . cdist_type . NoSuchTypeError as e :
self . log . error ( " %s requires object %s , but type %s does not exist (definded at %s ) " % ( self . cdist_object . name , requirement , e . name , self . object_source ) )
2013-05-27 14:36:20 +00:00
raise
2014-01-19 22:22:48 +00:00
except core . cdist_object . MissingObjectIdError as e :
self . log . error ( " %s requires object %s (without object id), but type %s is not a singleton (definded at %s ) " % ( self . cdist_object . name , requirement , e . type_name , self . object_source ) )
raise
2012-02-13 09:47:40 +00:00
2012-01-19 06:51:02 +00:00
self . log . debug ( " Recording requirement: " + requirement )
2012-11-19 14:17:46 +00:00
2012-02-14 10:12:17 +00:00
# Save the sanitised version, not the user supplied one
# (__file//bar => __file/bar)
# This ensures pattern matching is done against sanitised list
2012-11-19 14:17:46 +00:00
self . cdist_object . requirements . append ( cdist_object . name )
2011-09-26 09:18:36 +00:00
2011-11-02 22:58:18 +00:00
def record_auto_requirements ( self ) :
""" An object shall automatically depend on all objects that it defined in it ' s type manifest.
"""
2012-04-25 15:18:16 +00:00
# __object_name is the name of the object whose type manifest is currently executed
2012-11-07 09:25:47 +00:00
__object_name = self . env . get ( ' __object_name ' , None )
2011-11-02 22:58:18 +00:00
if __object_name :
2012-04-25 15:18:16 +00:00
# The object whose type manifest is currently run
parent = self . cdist_object . object_from_name ( __object_name )
# The object currently being defined
current_object = self . cdist_object
# As parent defined current_object it shall automatically depend on it.
# But only if the user hasn't said otherwise.
# Must prevent circular dependencies.
if not parent . name in current_object . requirements :
2012-05-03 08:16:08 +00:00
parent . autorequire . append ( current_object . name )