class CdistObject(object): """Represents a cdist object. All interaction with objects in cdist should be done through this class. Directly accessing an object through the file system from python code is a bug. """ # Constants for use with Object.state STATE_UNDEF = "" STATE_PREPARED = "prepared" STATE_RUNNING = "running" STATE_DONE = "done" def __init__(self, cdist_type, base_path, object_marker, object_id): self.cdist_type = cdist_type # instance of Type self.base_path = base_path self.object_id = object_id self.object_marker = object_marker self.validate_object_id() self.sanitise_object_id() self.name = self.join_name(self.cdist_type.name, self.object_id) self.path = os.path.join(self.cdist_type.path, self.object_id, self.object_marker) self.absolute_path = os.path.join(self.base_path, self.path) self.code_local_path = os.path.join(self.path, "code-local") self.code_remote_path = os.path.join(self.path, "code-remote") self.parameter_path = os.path.join(self.path, "parameter") @classmethod def list_objects(cls, object_base_path, type_base_path, object_marker): """Return a list of object instances""" for object_name in cls.list_object_names(object_base_path, object_marker): type_name, object_id = cls.split_name(object_name) yield cls(cdist.core.CdistType(type_base_path, type_name), base_path=object_base_path, object_marker=object_marker, object_id=object_id) @classmethod def list_object_names(cls, object_base_path, object_marker): """Return a list of object names""" for path, dirs, files in os.walk(object_base_path): if object_marker in dirs: yield os.path.relpath(path, object_base_path) @classmethod def list_type_names(cls, object_base_path): """Return a list of type names""" return os.listdir(object_base_path) @staticmethod def split_name(object_name): """split_name('__type_name/the/object_id') -> ('__type_name', 'the/object_id') Split the given object name into it's type and object_id parts. """ type_name = object_name.split(os.sep)[0] object_id = os.sep.join(object_name.split(os.sep)[1:]) return type_name, object_id @staticmethod def join_name(type_name, object_id): """join_name('__type_name', 'the/object_id') -> __type_name/the/object_id' Join the given type_name and object_id into an object name. """ return os.path.join(type_name, object_id) def validate_object_id(self): if self.cdist_type.is_singleton and self.object_id: raise IllegalObjectIdError( 'singleton objects can\'t have a object_id') """Validate the given object_id and raise IllegalObjectIdError if it's not valid. """ if self.object_id: if self.object_marker in self.object_id.split(os.sep): raise IllegalObjectIdError( self.object_id, 'object_id may not contain \'%s\'' % self.object_marker) if '//' in self.object_id: raise IllegalObjectIdError(self.object_id, 'object_id may not contain //') if self.object_id == '.': raise IllegalObjectIdError(self.object_id, 'object_id may not be a .') # If no object_id and type is not singleton => error out if not self.object_id and not self.cdist_type.is_singleton: raise MissingObjectIdError(self.cdist_type.name) # Does not work: AttributeError: 'CdistObject' object has no attribute 'parameter_path' #"Type %s is not a singleton type - missing object id (parameters: %s)" % # (self.cdist_type.name, self.parameters)) def object_from_name(self, object_name): """Convenience method for creating an object instance from an object name. Mainly intended to create objects when resolving requirements. e.g: <CdistObject __foo/bar>.object_from_name('__other/object') -> <CdistObject __other/object> """ base_path = self.base_path type_path = self.cdist_type.base_path object_marker = self.object_marker type_name, object_id = self.split_name(object_name) cdist_type = self.cdist_type.__class__(type_path, type_name) return self.__class__(cdist_type, base_path, object_marker, object_id=object_id) def __repr__(self): return '<CdistObject %s>' % self.name def __eq__(self, other): """define equality as 'name is the same'""" return self.name == other.name def __hash__(self): return hash(self.name) def __lt__(self, other): return isinstance(other, self.__class__) and self.name < other.name def sanitise_object_id(self): """ Remove leading and trailing slash (one only) """ # Allow empty object id for singletons if self.object_id: # Remove leading slash if self.object_id[0] == '/': self.object_id = self.object_id[1:] # Remove trailing slash if self.object_id[-1] == '/': self.object_id = self.object_id[:-1] # FIXME: still needed? @property def explorer_path(self): """Create and return the relative path to this objects explorers""" # create absolute path path = os.path.join(self.absolute_path, "explorer") if not os.path.isdir(path): os.mkdir(path) # return relative path return os.path.join(self.path, "explorer") requirements = fsproperty.FileListProperty( lambda obj: os.path.join(obj.absolute_path, 'require')) autorequire = fsproperty.FileListProperty( lambda obj: os.path.join(obj.absolute_path, 'autorequire')) parameters = fsproperty.DirectoryDictProperty( lambda obj: os.path.join(obj.base_path, obj.parameter_path)) explorers = fsproperty.DirectoryDictProperty( lambda obj: os.path.join(obj.base_path, obj.explorer_path)) state = fsproperty.FileStringProperty( lambda obj: os.path.join(obj.absolute_path, "state")) source = fsproperty.FileListProperty( lambda obj: os.path.join(obj.absolute_path, "source")) code_local = fsproperty.FileStringProperty( lambda obj: os.path.join(obj.base_path, obj.code_local_path)) code_remote = fsproperty.FileStringProperty( lambda obj: os.path.join(obj.base_path, obj.code_remote_path)) @property def exists(self): """Checks wether this cdist object exists on the file systems.""" return os.path.exists(self.absolute_path) def create(self, allow_overwrite=False): """Create this cdist object on the filesystem. """ try: os.makedirs(self.absolute_path, exist_ok=allow_overwrite) absolute_parameter_path = os.path.join(self.base_path, self.parameter_path) os.makedirs(absolute_parameter_path, exist_ok=allow_overwrite) except EnvironmentError as error: raise cdist.Error( 'Error creating directories for cdist object: %s: %s' % (self, error)) def requirements_unfinished(self, requirements): """Return state whether requirements are satisfied""" object_list = [] for requirement in requirements: cdist_object = self.object_from_name(requirement) if not cdist_object.state == self.STATE_DONE: object_list.append(cdist_object) return object_list
class Object(object): """Represents a cdist object. All interaction with objects in cdist should be done through this class. Directly accessing an object through the file system from python code is a bug. """ # Constants for use with Object.state STATE_PREPARED = "prepared" STATE_RUNNING = "running" STATE_DONE = "done" @classmethod def list_objects(cls, object_base_path, type_base_path): """Return a list of object instances""" for object_name in cls.list_object_names(object_base_path): type_name, object_id = cls.split_name(object_name) yield cls(cdist.core.Type(type_base_path, type_name), object_base_path, object_id=object_id) @classmethod def list_type_names(cls, object_base_path): """Return a list of type names""" return os.listdir(object_base_path) @classmethod def list_object_names(cls, object_base_path): """Return a list of object names""" for path, dirs, files in os.walk(object_base_path): if OBJECT_MARKER in dirs: yield os.path.relpath(path, object_base_path) @staticmethod def split_name(object_name): """split_name('__type_name/the/object_id') -> ('__type_name', 'the/object_id') Split the given object name into it's type and object_id parts. """ type_name = object_name.split(os.sep)[0] # FIXME: allow object without object_id? e.g. for singleton object_id = os.sep.join(object_name.split(os.sep)[1:]) return type_name, object_id @staticmethod def join_name(type_name, object_id): """join_name('__type_name', 'the/object_id') -> __type_name/the/object_id' Join the given type_name and object_id into an object name. """ return os.path.join(type_name, object_id) def __init__(self, cdist_type, base_path, object_id=None): if object_id: if object_id.startswith('/'): raise IllegalObjectIdError(object_id, 'object_id may not start with /') if OBJECT_MARKER in object_id.split(os.sep): raise IllegalObjectIdError( object_id, 'object_id may not contain \'%s\'' % OBJECT_MARKER) self.type = cdist_type # instance of Type self.base_path = base_path self.object_id = object_id self.name = self.join_name(self.type.name, self.object_id) self.path = os.path.join(self.type.path, self.object_id, OBJECT_MARKER) self.absolute_path = os.path.join(self.base_path, self.path) self.code_local_path = os.path.join(self.path, "code-local") self.code_remote_path = os.path.join(self.path, "code-remote") self.parameter_path = os.path.join(self.path, "parameter") def __repr__(self): return '<Object %s>' % self.name def __eq__(self, other): """define equality as 'attributes are the same'""" return self.__dict__ == other.__dict__ def __lt__(self, other): return isinstance(other, self.__class__) and self.name < other.name def object_from_name(self, object_name): """Convenience method for creating an object instance from an object name. Mainly intended to create objects when resolving requirements. e.g: <Object __foo/bar>.object_from_name('__other/object') -> <Object __other/object> """ type_path = self.type.base_path base_path = self.base_path type_name, object_id = self.split_name(object_name) return self.__class__(self.type.__class__(type_path, type_name), base_path, object_id=object_id) # FIXME: still needed? @property def explorer_path(self): """Create and return the relative path to this objects explorers""" # create absolute path path = os.path.join(self.absolute_path, "explorer") if not os.path.isdir(path): os.mkdir(path) # return relative path return os.path.join(self.path, "explorer") requirements = fsproperty.FileListProperty( lambda obj: os.path.join(obj.absolute_path, 'require')) parameters = fsproperty.DirectoryDictProperty( lambda obj: os.path.join(obj.base_path, obj.parameter_path)) explorers = fsproperty.DirectoryDictProperty( lambda obj: os.path.join(obj.base_path, obj.explorer_path)) changed = fsproperty.FileBooleanProperty( lambda obj: os.path.join(obj.absolute_path, "changed")) state = fsproperty.FileStringProperty( lambda obj: os.path.join(obj.absolute_path, "state")) source = fsproperty.FileListProperty( lambda obj: os.path.join(obj.absolute_path, "source")) code_local = fsproperty.FileStringProperty( lambda obj: os.path.join(obj.base_path, obj.code_local_path)) code_remote = fsproperty.FileStringProperty( lambda obj: os.path.join(obj.base_path, obj.code_remote_path)) @property def exists(self): """Checks wether this cdist object exists on the file systems.""" return os.path.exists(self.absolute_path) def create(self): """Create this cdist object on the filesystem. """ try: os.makedirs(self.absolute_path, exist_ok=False) absolute_parameter_path = os.path.join(self.base_path, self.parameter_path) os.makedirs(absolute_parameter_path, exist_ok=False) except EnvironmentError as error: raise cdist.Error( 'Error creating directories for cdist object: %s: %s' % (self, error))