Esempio n. 1
0
def _dumpFilesystemContents(targetDir, backupUser, backupGroup, compress=True):
    """
   Dumps complete listing of filesystem contents via C{ls -laR}.
   @param targetDir: Directory to write output file into.
   @param backupUser: User which should own the resulting file.
   @param backupGroup: Group which should own the resulting file.
   @param compress: Indicates whether to compress the output file.
   @raise IOError: If the dump fails for some reason.
   """
    (outputFile, filename) = _getOutputFile(targetDir, "ls-laR", compress)
    try:
        # Note: can't count on return status from 'ls', so we don't check it.
        command = resolveCommand(LS_COMMAND)
        executeCommand(command, [],
                       returnOutput=False,
                       ignoreStderr=True,
                       doNotLog=True,
                       outputFile=outputFile)
    finally:
        outputFile.close()
    if not os.path.exists(filename):
        raise IOError(
            "File [%s] does not seem to exist after filesystem contents dump finished."
            % filename)
    changeOwnership(filename, backupUser, backupGroup)
Esempio n. 2
0
    def openTray(self):
        """
      Opens the device's tray and leaves it open.

      This only works if the device has a tray and supports ejecting its media.
      We have no way to know if the tray is currently open or closed, so we
      just send the appropriate command and hope for the best.  If the device
      does not have a tray or does not support ejecting its media, then we do
      nothing.

      Starting with Debian wheezy on my backup hardware, I started seeing
      consistent problems with the eject command.  I couldn't tell whether
      these problems were due to the device management system or to the new
      kernel (3.2.0).  Initially, I saw simple eject failures, possibly because
      I was opening and closing the tray too quickly.  I worked around that
      behavior with the new ejectDelay flag.

      Later, I sometimes ran into issues after writing an image to a disc:
      eject would give errors like "unable to eject, last error: Inappropriate
      ioctl for device".  Various sources online (like Ubuntu bug #875543)
      suggested that the drive was being locked somehow, and that the
      workaround was to run 'eject -i off' to unlock it.  Sure enough, that
      fixed the problem for me, so now it's a normal error-handling strategy.

      @raise IOError: If there is an error talking to the device.
      """
        if self._deviceHasTray and self._deviceCanEject:
            command = resolveCommand(EJECT_COMMAND)
            args = [
                self.device,
            ]
            result = executeCommand(command, args)[0]
            if result != 0:
                logger.debug(
                    "Eject failed; attempting kludge of unlocking the tray before retrying."
                )
                self.unlockTray()
                result = executeCommand(command, args)[0]
                if result != 0:
                    raise IOError(
                        "Error (%d) executing eject command to open tray (failed even after unlocking tray)."
                        % result)
                logger.debug("Kludge was apparently successful.")
            if self.ejectDelay is not None:
                logger.debug(
                    "Per configuration, sleeping %d seconds after opening tray.",
                    self.ejectDelay)
                time.sleep(self.ejectDelay)
Esempio n. 3
0
def _encryptStagingDir(config, local, stagingDir, encryptedDir):
    """
   Encrypt a staging directory, creating a new directory in the process.
   @param config: Config object.
   @param stagingDir: Staging directory to use as source
   @param encryptedDir: Target directory into which encrypted files should be written
   """
    suCommand = resolveCommand(SU_COMMAND)
    files = FilesystemList()
    files.addDirContents(stagingDir)
    for cleartext in files:
        if os.path.isfile(cleartext):
            encrypted = "%s%s" % (encryptedDir,
                                  cleartext.replace(stagingDir, ""))
            if long(os.stat(cleartext).st_size) == 0:
                open(encrypted,
                     'a').close()  # don't bother encrypting empty files
            else:
                actualCommand = local.amazons3.encryptCommand.replace(
                    "${input}", cleartext).replace("${output}", encrypted)
                subdir = os.path.dirname(encrypted)
                if not os.path.isdir(subdir):
                    os.makedirs(subdir)
                    changeOwnership(subdir, config.options.backupUser,
                                    config.options.backupGroup)
                result = executeCommand(
                    suCommand,
                    [config.options.backupUser, "-c", actualCommand])[0]
                if result != 0:
                    raise IOError("Error [%d] encrypting [%s]." %
                                  (result, cleartext))
    logger.debug("Completed encrypting staging directory [%s] into [%s]",
                 stagingDir, encryptedDir)
Esempio n. 4
0
def _dumpPartitionTable(targetDir, backupUser, backupGroup, compress=True):
    """
   Dumps information about the partition table via C{fdisk}.
   @param targetDir: Directory to write output file into.
   @param backupUser: User which should own the resulting file.
   @param backupGroup: Group which should own the resulting file.
   @param compress: Indicates whether to compress the output file.
   @raise IOError: If the dump fails for some reason.
   """
    if not os.path.exists(FDISK_PATH):
        logger.info(
            "Not executing partition table dump since %s doesn't seem to exist.",
            FDISK_PATH)
    elif not os.access(FDISK_PATH, os.X_OK):
        logger.info(
            "Not executing partition table dump since %s cannot be executed.",
            FDISK_PATH)
    else:
        (outputFile, filename) = _getOutputFile(targetDir, "fdisk-l", compress)
        try:
            command = resolveCommand(FDISK_COMMAND)
            result = executeCommand(command, [],
                                    returnOutput=False,
                                    ignoreStderr=True,
                                    outputFile=outputFile)[0]
            if result != 0:
                raise IOError("Error [%d] executing partition table dump." %
                              result)
        finally:
            outputFile.close()
        if not os.path.exists(filename):
            raise IOError(
                "File [%s] does not seem to exist after partition table dump finished."
                % filename)
        changeOwnership(filename, backupUser, backupGroup)
Esempio n. 5
0
    def _writeImage(self, newDisc, imagePath, entries, mediaLabel=None):
        """
      Writes an image to disc using either an entries list or an ISO image on
      disk.

      Callers are assumed to have done validation on paths, etc. before calling
      this method.

      @param newDisc: Indicates whether the disc should be re-initialized
      @param imagePath: Path to an ISO image on disk, or c{None} to use C{entries}
      @param entries: Mapping from path to graft point, or C{None} to use C{imagePath}

      @raise IOError: If the media could not be written to for some reason.
      """
        command = resolveCommand(GROWISOFS_COMMAND)
        args = DvdWriter._buildWriteArgs(newDisc,
                                         self.hardwareId,
                                         self._driveSpeed,
                                         imagePath,
                                         entries,
                                         mediaLabel,
                                         dryRun=False)
        (result, output) = executeCommand(command, args, returnOutput=True)
        if result != 0:
            DvdWriter._searchForOverburn(
                output)  # throws own exception if overburn condition is found
            raise IOError("Error (%d) executing command to write disc." %
                          result)
        self.refreshMedia()
Esempio n. 6
0
def backupDatabase(user, backupFile, database=None):
    """
   Backs up an individual PostgreSQL database, or all databases.

   This function backs up either a named local PostgreSQL database or all local
   PostgreSQL databases, using the passed in user for connectivity.
   This is I{always} a full backup.  There is no facility for incremental
   backups.

   The backup data will be written into the passed-in back file.  Normally,
   this would be an object as returned from C{open()}, but it is possible to
   use something like a C{GzipFile} to write compressed output.  The caller is
   responsible for closing the passed-in backup file.

   @note: Typically, you would use the C{root} user to back up all databases.

   @param user: User to use for connecting to the database.
   @type user: String representing PostgreSQL username.

   @param backupFile: File use for writing backup.
   @type backupFile: Python file object as from C{open()} or C{file()}.

   @param database: Name of the database to be backed up.
   @type database: String representing database name, or C{None} for all databases.

   @raise ValueError: If some value is missing or invalid.
   @raise IOError: If there is a problem executing the PostgreSQL dump.
   """
    args = []
    if user is not None:
        args.append('-U')
        args.append(user)

    if database is None:
        command = resolveCommand(POSTGRESQLDUMPALL_COMMAND)
    else:
        command = resolveCommand(POSTGRESQLDUMP_COMMAND)
        args.append(database)

    result = executeCommand(command,
                            args,
                            returnOutput=False,
                            ignoreStderr=True,
                            doNotLog=True,
                            outputFile=backupFile)[0]
    if result != 0:
        if database is None:
            raise IOError(
                "Error [%d] executing PostgreSQL database dump for all databases."
                % result)
        else:
            raise IOError(
                "Error [%d] executing PostgreSQL database dump for database [%s]."
                % (result, database))
Esempio n. 7
0
def availableLocales():
    """
   Returns a list of available locales on the system
   @return: List of string locale names
   """
    locales = []
    output = executeCommand(["locale"], [
        "-a",
    ],
                            returnOutput=True,
                            ignoreStderr=True)[1]
    for line in output:
        locales.append(line.rstrip())
    return locales
Esempio n. 8
0
def _synchronizeBucket(sourceDir, s3BucketUrl):
    """
   Synchronize a local directory to an Amazon S3 bucket.
   @param sourceDir: Local source directory
   @param s3BucketUrl: Target S3 bucket URL
   """
    # Since at least early 2015, 'aws s3 sync' is always recursive and the
    # --recursive option is useless.  They eventually removed it and now using
    # it causes an error.  See: https://github.com/aws/aws-cli/issues/1170
    logger.info("Synchronizing local source directory up to Amazon S3.")
    args = ["s3", "sync", sourceDir, s3BucketUrl, "--delete"]
    result = executeCommand(AWS_COMMAND, args, returnOutput=False)[0]
    if result != 0:
        raise IOError("Error [%d] calling AWS CLI synchronize bucket." %
                      result)
Esempio n. 9
0
 def unlockTray(self):
     """
   Unlocks the device's tray via 'eject -i off'.
   @raise IOError: If there is an error talking to the device.
   """
     command = resolveCommand(EJECT_COMMAND)
     args = [
         "-i",
         "off",
         self.device,
     ]
     result = executeCommand(command, args)[0]
     if result != 0:
         raise IOError(
             "Error (%d) executing eject command to unlock tray." % result)
Esempio n. 10
0
def _confirmGpgRecipient(recipient):
    """
   Confirms that a recipient's public key is known to GPG.
   Throws an exception if there is a problem, or returns normally otherwise.
   @param recipient: Recipient name
   @raise IOError: If the recipient's public key is not known to GPG.
   """
    command = resolveCommand(GPG_COMMAND)
    args = [
        "--batch",
        "-k",
        recipient,
    ]  # should use --with-colons if the output will be parsed
    result = executeCommand(command, args)[0]
    if result != 0:
        raise IOError("GPG unable to find public key for [%s]." % recipient)
Esempio n. 11
0
def _clearExistingBackup(config, s3BucketUrl):
    """
   Clear any existing backup files for an S3 bucket URL.
   @param config: Config object.
   @param s3BucketUrl: S3 bucket URL associated with the staging directory
   """
    suCommand = resolveCommand(SU_COMMAND)
    awsCommand = resolveCommand(AWS_COMMAND)
    actualCommand = "%s s3 rm --recursive %s/" % (awsCommand[0], s3BucketUrl)
    result = executeCommand(
        suCommand, [config.options.backupUser, "-c", actualCommand])[0]
    if result != 0:
        raise IOError(
            "Error [%d] calling AWS CLI to clear existing backup for [%s]." %
            (result, s3BucketUrl))
    logger.debug("Completed clearing any existing backup in S3 for [%s]",
                 s3BucketUrl)
Esempio n. 12
0
def _verifyUpload(config, stagingDir, s3BucketUrl):
    """
   Verify that a staging directory was properly uploaded to the Amazon S3 cloud.
   @param config: Config object.
   @param stagingDir: Staging directory to verify
   @param s3BucketUrl: S3 bucket URL associated with the staging directory
   """
    (bucket, prefix) = s3BucketUrl.replace("s3://", "").split("/", 1)
    suCommand = resolveCommand(SU_COMMAND)
    awsCommand = resolveCommand(AWS_COMMAND)
    query = "Contents[].{Key: Key, Size: Size}"
    actualCommand = "%s s3api list-objects --bucket %s --prefix %s --query '%s'" % (
        awsCommand[0], bucket, prefix, query)
    (result,
     data) = executeCommand(suCommand,
                            [config.options.backupUser, "-c", actualCommand],
                            returnOutput=True)
    if result != 0:
        raise IOError("Error [%d] calling AWS CLI verify upload to [%s]." %
                      (result, s3BucketUrl))
    contents = {}
    for entry in json.loads("".join(data)):
        key = entry["Key"].replace(prefix, "")
        size = long(entry["Size"])
        contents[key] = size
    files = FilesystemList()
    files.excludeBasenamePatterns = [
        r"cback\..*",
    ]  # because these are excluded from the upload
    files.addDirContents(stagingDir)
    for entry in files:
        if os.path.isfile(entry):
            key = entry.replace(stagingDir, "")
            size = long(os.stat(entry).st_size)
            if not key in contents:
                raise IOError("File was apparently not uploaded: [%s]" % entry)
            else:
                if size != contents[key]:
                    raise IOError(
                        "File size differs [%s], expected %s bytes but got %s bytes"
                        % (entry, size, contents[key]))
    logger.debug("Completed verifying upload from [%s] to [%s].", stagingDir,
                 s3BucketUrl)
Esempio n. 13
0
    def _retrieveSectorsUsed(self):
        """
      Retrieves the number of sectors used on the current media.

      This is a little ugly.  We need to call growisofs in "dry-run" mode and
      parse some information from its output.  However, to do that, we need to
      create a dummy file that we can pass to the command -- and we have to
      make sure to remove it later.

      Once growisofs has been run, then we call C{_parseSectorsUsed} to parse
      the output and calculate the number of sectors used on the media.

      @return: Number of sectors used on the media
      """
        tempdir = tempfile.mkdtemp()
        try:
            entries = {tempdir: None}
            args = DvdWriter._buildWriteArgs(False,
                                             self.hardwareId,
                                             self.driveSpeed,
                                             None,
                                             entries,
                                             None,
                                             dryRun=True)
            command = resolveCommand(GROWISOFS_COMMAND)
            (result, output) = executeCommand(command, args, returnOutput=True)
            if result != 0:
                logger.debug(
                    "Error (%d) calling growisofs to read sectors used.",
                    result)
                logger.warn(
                    "Unable to read disc (might not be initialized); returning zero sectors used."
                )
                return 0.0
            sectorsUsed = DvdWriter._parseSectorsUsed(output)
            logger.debug("Determined sectors used as %s", sectorsUsed)
            return sectorsUsed
        finally:
            if os.path.exists(tempdir):
                try:
                    os.rmdir(tempdir)
                except:
                    pass
Esempio n. 14
0
def readMediaLabel(devicePath):
    """
   Reads the media label (volume name) from the indicated device.
   The volume name is read using the C{volname} command.
   @param devicePath: Device path to read from
   @return: Media label as a string, or None if there is no name or it could not be read.
   """
    args = [
        devicePath,
    ]
    command = resolveCommand(VOLNAME_COMMAND)
    (result, output) = executeCommand(command,
                                      args,
                                      returnOutput=True,
                                      ignoreStderr=True)
    if result != 0:
        return None
    if output is None or len(output) < 1:
        return None
    return output[0].rstrip()
Esempio n. 15
0
    def writeImage(self, imagePath):
        """
      Writes this image to disk using the image path.

      @param imagePath: Path to write image out as
      @type imagePath: String representing a path on disk

      @raise IOError: If there is an error writing the image to disk.
      @raise ValueError: If there are no filesystem entries in the image
      @raise ValueError: If a path cannot be encoded properly.
      """
        imagePath = encodePath(imagePath)
        if len(self.entries.keys()) == 0:
            raise ValueError("Image does not contain any entries.")
        args = self._buildWriteArgs(self.entries, imagePath)
        command = resolveCommand(MKISOFS_COMMAND)
        (result, output) = executeCommand(command, args, returnOutput=False)
        if result != 0:
            raise IOError(
                "Error (%d) executing mkisofs command to build image." %
                result)
Esempio n. 16
0
def _encryptFileWithGpg(sourcePath, recipient):
    """
   Encrypts the indicated source file using GPG.

   The encrypted file will be in GPG's binary output format and will have the
   same name as the source file plus a C{".gpg"} extension.  The source file
   will not be modified or removed by this function call.

   @param sourcePath: Absolute path of file to be encrypted.
   @param recipient: Recipient name to be passed to GPG's C{"-r"} option

   @return: Path to the newly-created encrypted file.

   @raise IOError: If there is a problem encrypting the file.
   """
    encryptedPath = "%s.gpg" % sourcePath
    command = resolveCommand(GPG_COMMAND)
    args = [
        "--batch",
        "--yes",
        "-e",
        "-r",
        recipient,
        "-o",
        encryptedPath,
        sourcePath,
    ]
    result = executeCommand(command, args)[0]
    if result != 0:
        raise IOError("Error [%d] calling gpg to encrypt [%s]." %
                      (result, sourcePath))
    if not os.path.exists(encryptedPath):
        raise IOError(
            "After call to [%s], encrypted file [%s] does not exist." %
            (command, encryptedPath))
    logger.debug("Completed encrypting file [%s] to [%s].", sourcePath,
                 encryptedPath)
    return encryptedPath
Esempio n. 17
0
    def closeTray(self):
        """
      Closes the device's tray.

      This only works if the device has a tray and supports ejecting its media.
      We have no way to know if the tray is currently open or closed, so we
      just send the appropriate command and hope for the best.  If the device
      does not have a tray or does not support ejecting its media, then we do
      nothing.

      @raise IOError: If there is an error talking to the device.
      """
        if self._deviceHasTray and self._deviceCanEject:
            command = resolveCommand(EJECT_COMMAND)
            args = [
                "-t",
                self.device,
            ]
            result = executeCommand(command, args)[0]
            if result != 0:
                raise IOError(
                    "Error (%d) executing eject command to close tray." %
                    result)
Esempio n. 18
0
def _dumpDebianPackages(targetDir, backupUser, backupGroup, compress=True):
    """
   Dumps a list of currently installed Debian packages via C{dpkg}.
   @param targetDir: Directory to write output file into.
   @param backupUser: User which should own the resulting file.
   @param backupGroup: Group which should own the resulting file.
   @param compress: Indicates whether to compress the output file.
   @raise IOError: If the dump fails for some reason.
   """
    if not os.path.exists(DPKG_PATH):
        logger.info(
            "Not executing Debian package dump since %s doesn't seem to exist.",
            DPKG_PATH)
    elif not os.access(DPKG_PATH, os.X_OK):
        logger.info(
            "Not executing Debian package dump since %s cannot be executed.",
            DPKG_PATH)
    else:
        (outputFile, filename) = _getOutputFile(targetDir, "dpkg-selections",
                                                compress)
        try:
            command = resolveCommand(DPKG_COMMAND)
            result = executeCommand(command, [],
                                    returnOutput=False,
                                    ignoreStderr=True,
                                    doNotLog=True,
                                    outputFile=outputFile)[0]
            if result != 0:
                raise IOError("Error [%d] executing Debian package dump." %
                              result)
        finally:
            outputFile.close()
        if not os.path.exists(filename):
            raise IOError(
                "File [%s] does not seem to exist after Debian package dump finished."
                % filename)
        changeOwnership(filename, backupUser, backupGroup)
Esempio n. 19
0
 def _getEstimatedSize(self, entries):
     """
   Returns the estimated size (in bytes) for the passed-in entries dictionary.
   @return: Estimated size of the image, in bytes.
   @raise IOError: If there is a problem calling C{mkisofs}.
   """
     args = self._buildSizeArgs(entries)
     command = resolveCommand(MKISOFS_COMMAND)
     (result, output) = executeCommand(command,
                                       args,
                                       returnOutput=True,
                                       ignoreStderr=True)
     if result != 0:
         raise IOError(
             "Error (%d) executing mkisofs command to estimate size." %
             result)
     if len(output) != 1:
         raise IOError("Unable to parse mkisofs output.")
     try:
         sectors = float(output[0])
         size = convertSize(sectors, UNIT_SECTORS, UNIT_BYTES)
         return size
     except:
         raise IOError("Unable to parse mkisofs output.")
Esempio n. 20
0
def _uploadStagingDir(config, stagingDir, s3BucketUrl):
    """
   Upload the contents of a staging directory out to the Amazon S3 cloud.
   @param config: Config object.
   @param stagingDir: Staging directory to upload
   @param s3BucketUrl: S3 bucket URL associated with the staging directory
   """
    # The version of awscli in Debian stretch (1.11.13-1) has a problem
    # uploading empty files, due to running with Python 3 rather than Python 2
    # as the upstream maintainers intended.  To work around this, I'm explicitly
    # excluding files like cback.stage, cback.collect, etc. which should be the
    # only empty files we ever try to copy.  See: https://github.com/aws/aws-cli/issues/2403
    suCommand = resolveCommand(SU_COMMAND)
    awsCommand = resolveCommand(AWS_COMMAND)
    actualCommand = "%s s3 cp --recursive --exclude \"*cback.*\" %s/ %s/" % (
        awsCommand[0], stagingDir, s3BucketUrl)
    result = executeCommand(
        suCommand, [config.options.backupUser, "-c", actualCommand])[0]
    if result != 0:
        raise IOError(
            "Error [%d] calling AWS CLI to upload staging directory to [%s]." %
            (result, s3BucketUrl))
    logger.debug("Completed uploading staging dir [%s] to [%s]", stagingDir,
                 s3BucketUrl)
Esempio n. 21
0
def _splitFile(sourcePath,
               splitSize,
               backupUser,
               backupGroup,
               removeSource=False):
    """
   Splits the source file into chunks of the indicated size.

   The split files will be owned by the indicated backup user and group.  If
   C{removeSource} is C{True}, then the source file will be removed after it is
   successfully split.

   @param sourcePath: Absolute path of the source file to split
   @param splitSize: Encryption mode (only "gpg" is allowed)
   @param backupUser: User that target files should be owned by
   @param backupGroup: Group that target files should be owned by
   @param removeSource: Indicates whether to remove the source file

   @raise IOError: If there is a problem accessing, splitting or removing the source file.
   """
    cwd = os.getcwd()
    try:
        if not os.path.exists(sourcePath):
            raise ValueError("Source path [%s] does not exist." % sourcePath)
        dirname = os.path.dirname(sourcePath)
        filename = os.path.basename(sourcePath)
        prefix = "%s_" % filename
        bytes = int(splitSize.bytes)  # pylint: disable=W0622
        os.chdir(
            dirname
        )  # need to operate from directory that we want files written to
        command = resolveCommand(SPLIT_COMMAND)
        args = [
            "--verbose",
            "--numeric-suffixes",
            "--suffix-length=5",
            "--bytes=%d" % bytes,
            filename,
            prefix,
        ]
        (result, output) = executeCommand(command,
                                          args,
                                          returnOutput=True,
                                          ignoreStderr=False)
        if result != 0:
            raise IOError("Error [%d] calling split for [%s]." %
                          (result, sourcePath))
        pattern = re.compile(r"(creating file [`'])(%s)(.*)(')" % prefix)
        match = pattern.search(output[-1:][0])
        if match is None:
            raise IOError("Unable to parse output from split command.")
        value = int(match.group(3).strip())
        for index in range(0, value):
            path = "%s%05d" % (prefix, index)
            if not os.path.exists(path):
                raise IOError(
                    "After call to split, expected file [%s] does not exist." %
                    path)
            changeOwnership(path, backupUser, backupGroup)
        if removeSource:
            if os.path.exists(sourcePath):
                try:
                    os.remove(sourcePath)
                    logger.debug("Completed removing old file [%s].",
                                 sourcePath)
                except:
                    raise IOError(
                        "Failed to remove file [%s] after splitting it." %
                        (sourcePath))
    finally:
        os.chdir(cwd)
Esempio n. 22
0
def _verifyBucketContents(sourceDir, sourceFiles, s3BucketUrl):
    """
   Verify that a source directory is equivalent to an Amazon S3 bucket.
   @param sourceDir: Local source directory
   @param sourceFiles: Filesystem list containing contents of source directory
   @param s3BucketUrl: Target S3 bucket URL
   """
    # As of this writing, the documentation for the S3 API that we're using
    # below says that up to 1000 elements at a time are returned, and that we
    # have to manually handle pagination by looking for the IsTruncated element.
    # However, in practice, this is not true.  I have been testing with
    # "aws-cli/1.4.4 Python/2.7.3 Linux/3.2.0-4-686-pae", installed through PIP.
    # No matter how many items exist in my bucket and prefix, I get back a
    # single JSON result.  I've tested with buckets containing nearly 6000
    # elements.
    #
    # If I turn on debugging, it's clear that underneath, something in the API
    # is executing multiple list-object requests against AWS, and stiching
    # results together to give me back the final JSON result.  The debug output
    # clearly incldues multiple requests, and each XML response (except for the
    # final one) contains <IsTruncated>true</IsTruncated>.
    #
    # This feature is not mentioned in the offical changelog for any of the
    # releases going back to 1.0.0.  It appears to happen in the botocore
    # library, but I'll admit I can't actually find the code that implements it.
    # For now, all I can do is rely on this behavior and hope that the
    # documentation is out-of-date.  I'm not going to write code that tries to
    # parse out IsTruncated if I can't actually test that code.

    (bucket, prefix) = s3BucketUrl.replace("s3://", "").split("/", 1)

    query = "Contents[].{Key: Key, Size: Size}"
    args = [
        "s3api",
        "list-objects",
        "--bucket",
        bucket,
        "--prefix",
        prefix,
        "--query",
        query,
    ]
    (result, data) = executeCommand(AWS_COMMAND, args, returnOutput=True)
    if result != 0:
        raise IOError("Error [%d] calling AWS CLI verify bucket contents." %
                      result)

    contents = {}
    for entry in json.loads("".join(data)):
        key = entry["Key"].replace(prefix, "")
        size = long(entry["Size"])
        contents[key] = size

    failed = False
    for entry in sourceFiles:
        if os.path.isfile(entry):
            key = entry.replace(sourceDir, "")
            size = long(os.stat(entry).st_size)
            if not key in contents:
                logger.error("File was apparently not uploaded: [%s]", entry)
                failed = True
            else:
                if size != contents[key]:
                    logger.error(
                        "File size differs [%s]: expected %s bytes but got %s bytes",
                        entry, size, contents[key])
                    failed = True

    if not failed:
        logger.info(
            "Completed verifying Amazon S3 bucket contents (no problems found)."
        )
    else:
        logger.error(
            "There were differences between source directory and target S3 bucket."
        )
        raise ValueError(
            "There were differences between source directory and target S3 bucket."
        )
Esempio n. 23
0
def backupDatabase(user, password, backupFile, database=None):
    """
   Backs up an individual MySQL database, or all databases.

   This function backs up either a named local MySQL database or all local
   MySQL databases, using the passed-in user and password (if provided) for
   connectivity.  This function call I{always} results a full backup.  There is
   no facility for incremental backups.

   The backup data will be written into the passed-in backup file.  Normally,
   this would be an object as returned from C{open()}, but it is possible to
   use something like a C{GzipFile} to write compressed output.  The caller is
   responsible for closing the passed-in backup file.

   Often, the "root" database user will be used when backing up all databases.
   An alternative is to create a separate MySQL "backup" user and grant that
   user rights to read (but not write) all of the databases that will be backed
   up.

   This function accepts a username and password.  However, you probably do not
   want to pass those values in.  This is because they will be provided to
   C{mysqldump} via the command-line C{--user} and C{--password} switches,
   which will be visible to other users in the process listing.

   Instead, you should configure the username and password in one of MySQL's
   configuration files.  Typically, this would be done by putting a stanza like
   this in C{/root/.my.cnf}, to provide C{mysqldump} with the root database
   username and its password::

      [mysqldump]
      user     = root
      password = <secret>

   If you are executing this function as some system user other than root, then
   the C{.my.cnf} file would be placed in the home directory of that user.  In
   either case, make sure to set restrictive permissions (typically, mode
   C{0600}) on C{.my.cnf} to make sure that other users cannot read the file.

   @param user: User to use for connecting to the database (if any)
   @type user: String representing MySQL username, or C{None}

   @param password: Password associated with user (if any)
   @type password: String representing MySQL password, or C{None}

   @param backupFile: File use for writing backup.
   @type backupFile: Python file object as from C{open()} or C{file()}.

   @param database: Name of the database to be backed up.
   @type database: String representing database name, or C{None} for all databases.

   @raise ValueError: If some value is missing or invalid.
   @raise IOError: If there is a problem executing the MySQL dump.
   """
    args = [
        "-all",
        "--flush-logs",
        "--opt",
    ]
    if user is not None:
        logger.warn(
            "Warning: MySQL username will be visible in process listing (consider using ~/.my.cnf)."
        )
        args.append("--user=%s" % user)
    if password is not None:
        logger.warn(
            "Warning: MySQL password will be visible in process listing (consider using ~/.my.cnf)."
        )
        args.append("--password=%s" % password)
    if database is None:
        args.insert(0, "--all-databases")
    else:
        args.insert(0, "--databases")
        args.append(database)
    command = resolveCommand(MYSQLDUMP_COMMAND)
    result = executeCommand(command,
                            args,
                            returnOutput=False,
                            ignoreStderr=True,
                            doNotLog=True,
                            outputFile=backupFile)[0]
    if result != 0:
        if database is None:
            raise IOError(
                "Error [%d] executing MySQL database dump for all databases." %
                result)
        else:
            raise IOError(
                "Error [%d] executing MySQL database dump for database [%s]." %
                (result, database))