Source code for ztpserver.repository

#
# Copyright (c) 2014, Arista Networks, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
#   Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
#   Neither the name of Arista Networks nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
'''
    MODULE:
        ztpserver.respository

    AUTHOR:
        Arista Networks

    DESCRIPTION:
        The repository module provides read and write access to files
        for ztpserver.  The repository module can perform basic file
        system like functionality for performing basid CRUD on
        files and well as reading and writing specific file contents.

    :copyright: Copyright (c) 2014, Arista Networks
    :license: BSD, see LICENSE for more details

'''

import hashlib
import logging
import mimetypes
import os

import ztpserver.serializers

from ztpserver.serializers import SerializerError

log = logging.getLogger(__name__)   #pylint: disable=C0103



[docs]def create_repository(path): if not os.path.exists(path): raise RepositoryError('%s not found' % path) return Repository(path)
[docs]class RepositoryError(Exception): ''' Base exception class for :py:class:`Repository` ''' pass
[docs]class FileObjectError(Exception): ''' Base exception class for :py:class:`FileObject` ''' pass
[docs]class FileObjectNotFound(RepositoryError): ''' Raised when a requested file is not found in the repository. This exception is a subclass of :py:class:`RespositoryError` ''' pass
[docs]class FileObject(object): ''' The :py:class:`FileObject` represents a single file entity in the repository. The instance provides convienent methods to read and write contents to the file using a specified serialization ''' def __init__(self, name, path=None, **kwargs): ''' The initialize method for :py:class:`FileObject` :param name: the name of the file :type name: str :param path: the base path of the file :type path: str :param content_type: the content type of the file (optional) :type content_type: str :returns: object ''' self.name = name if path is not None: self.name = os.path.join(path, name) self.type, self.encoding = mimetypes.guess_type(self.name) self.content_type = kwargs.get('content_type') def __repr__(self): return 'FileObject(name=%s, type=%s, encoding=%s, content_type=%s)' % \ (self.name, self.type, self.encoding, self.content_type)
[docs] def read(self, content_type=None, node_id=None): ''' Reads the contents from the file system :param content_type: defines the content_type of the file used to deserialize the object :type content_type: str :returns: object :raises: FileObjectError The read method will read the file from the file system, deserializing the contents as specified by the content_type argument. If the content_type argument is not specified, the read method will read the file as text. If any errors occur, a FileObjectError is raised. ''' try: self.content_type = content_type return ztpserver.serializers.load(self.name, content_type, node_id) except SerializerError as err: raise FileObjectError(err.message)
[docs] def write(self, contents, content_type=None): ''' Writes the contents to the file :param contents: specifies the contents to be written to the file :type contents: str :param content_type: defines the serialization format to use when saving the file :type content_type: str :returns: None :raises: FileObjectError The write method takes the contents argument and writes it to the file using the serialization specified in the content_type argument. If the content_type argument is not specified, the contents are written as string text. This method will overwrite any contents that previously existed for the FileObj instance. If any errors are encountered during the write operation, a FileObjectError is raised ''' try: ztpserver.serializers.dump(contents, self.name, content_type) self.content_type = content_type except SerializerError as err: raise FileObjectError(err.message)
[docs] def size(self): ''' Returns the size of the object in bytes. :raises: IOError ''' return os.path.getsize(self.name)
[docs] def hash(self): ''' Returns the SHA1 hash of the object. :raises: IOError ''' sha1 = hashlib.sha1() sha1.update(open(self.name).read()) #pylint: disable=E1101 return sha1.hexdigest()
[docs]class Repository(object): ''' The Respository class represents a repository of :py:class:`FileObject` instances. It is an abstract wrapper providing the ability to interact with persistently stored files. ''' def __init__(self, path): ''' The initialize method for :py:class:`Repository` :param path: the base path of the repository :type path: str :returns: object ''' self.path = path def __repr__(self): return "Repository(path=%s)" % self.path
[docs] def expand(self, file_path): ''' Expands a file_path to the full path to a file object :param file_path: the file path to expand :type file_path: str :returns: str -- the full path to the file This method is used to transform a relative file path into an absolute file path for identifying a file object resource ''' if file_path == '/': file_path = self.path elif not str(file_path).startswith(self.path): file_path = file_path[1:] if file_path[0] == '/' else file_path file_path = os.path.join(self.path, file_path) return file_path
[docs] def add_folder(self, folder_path): ''' Add a new folder to the repository :param folder_path: the full path of the folder to add :type folder_path: str :returns: str -- the full path to the new folder :raises: RespositoryError ''' try: folder_path = self.expand(folder_path) os.makedirs(folder_path) return folder_path except OSError as err: log.error('Failed to add folder %s (%s)' % (folder_path, err)) raise RepositoryError('Failed to add folder %s (%s)' % (folder_path, err))
[docs] def add_file(self, file_path, contents=None, content_type=None): ''' Adds a new :py:class:`FileObject` to the repository :param file_path: the full path of the file to add :type file_path: str :param contents: the contents to write to the file :type contents: str :param content_type: specifies the serialization to use for the file :type content_type: str :returns: :py:class:`FileObject` :raises: RespositoryError The add_file method allows for a new file to be added to the respository. If the file already exists, it is returned as an instance of :py:class:`FileObject`. If the file doesn't already exist and the contents argument is not None, then the file is created and the contents written to the file. The content_type argument provides the serialization to be used when saving the file. ''' file_path = self.expand(file_path) obj = FileObject(file_path) if contents: obj.write(contents, content_type) return obj
[docs] def exists(self, file_path): ''' Returns boolean if the file_path exists in the repository :param file_path: the file_path to check for existence :type file_path: str :returns: boolean -- True if it exists otherwise False ''' file_path = self.expand(file_path) return os.path.exists(file_path)
[docs] def get_file(self, file_path): ''' Returns an intance of :py:class:`FileObject` if it exists :param file_path: the file path of the instance to return :type file_path: str :returns: instance of :py:class:`FileObject` :raises: FileObjectNotFound This method will retrieve a file object instance if it exists in the repository. If the file does not exist then an error is raised ''' file_path = self.expand(file_path) if not self.exists(file_path): raise FileObjectNotFound('file not found (%s)' % file_path) return FileObject(file_path)
[docs] def delete_file(self, file_path): ''' Deletes an existing file in the respository :param file_path: the file path of the instance to delete :type file_path: str :returns: None :raises: RepositoryError ''' try: file_path = self.expand(file_path) os.remove(file_path) except (OSError, IOError) as err: log.error('Failed to delete file %s (%s)' % (file_path, err)) raise RepositoryError('Failed to delete file %s (%s)' % (file_path, err))