3 # Copyright 2016 RIFT.IO Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
25 class ArchiveError(Exception):
30 """ Get number of bytes of content within file hdl
31 Set the file position to original position before returning
34 Number of bytes in the hdl file object
37 hdl
.seek(0, os
.SEEK_END
)
44 class TarPackageArchive(object):
45 """ This class represents a package stored within a tar.gz archive file """
46 def __init__(self
, log
, tar_file_hdl
, mode
="r"):
48 self
._tar
_filehdl
= tar_file_hdl
51 self
._tarfile
= tarfile
.open(fileobj
=tar_file_hdl
, mode
=mode
)
56 def from_package(cls
, log
, pkg
, tar_file_hdl
):
57 """ Creates a TarPackageArchive from a existing Package
61 pkg - a DescriptorPackage instance
62 tar_file_hdl - a writeable file handle to write tar archive data
65 A TarPackageArchive instance
68 def set_common_tarinfo_fields(tar_info
):
69 tar_info
.uid
= os
.getuid()
70 tar_info
.gid
= os
.getgid()
71 tar_info
.mtime
= time
.time()
72 tar_info
.uname
= "rift"
73 tar_info
.gname
= "rift"
75 archive
= TarPackageArchive(log
, tar_file_hdl
, mode
='w:gz')
76 for pkg_file
in pkg
.files
:
77 tar_info
= tarfile
.TarInfo(name
=pkg_file
)
78 tar_info
.type = tarfile
.REGTYPE
79 tar_info
.mode
= pkg
.get_file_mode(pkg_file
)
80 set_common_tarinfo_fields(tar_info
)
81 with pkg
.open(pkg_file
) as pkg_file_hdl
:
82 tar_info
.size
= get_size(pkg_file_hdl
)
83 archive
.tarfile
.addfile(tar_info
, pkg_file_hdl
)
85 for pkg_dir
in pkg
.dirs
:
86 tar_info
= tarfile
.TarInfo(name
=pkg_dir
)
87 tar_info
.type = tarfile
.DIRTYPE
89 set_common_tarinfo_fields(tar_info
)
90 archive
.tarfile
.addfile(tar_info
)
92 archive
.load_archive()
98 return "TarPackageArchive(%s)" % self
._tar
_filehdl
104 """ Close the opened tarfile"""
105 if self
._tarfile
is not None:
106 self
._tarfile
.close()
109 def load_archive(self
):
110 self
._tar
_infos
= {info
.name
: info
for info
in self
._tarfile
.getmembers() if info
.name
}
118 """ The list of file members within the tar file """
119 return [name
for name
in self
._tar
_infos
if tarfile
.TarInfo
.isfile(self
._tar
_infos
[name
])]
121 def open_file(self
, rel_file_path
):
122 """ Opens a file within the archive as read-only, byte mode.
125 rel_file_path - The file path within the archive to open
128 A file like object (see tarfile.extractfile())
131 FileNotFoundError - The file could not be found within the archive.
132 ArchiveError - The file could not be opened for some generic reason.
134 if rel_file_path
not in self
._tar
_infos
:
135 raise FileNotFoundError("Could not find %s in tar file", rel_file_path
)
138 return self
._tarfile
.extractfile(rel_file_path
)
139 except tarfile
.TarError
as e
:
140 msg
= "Failed to read file {} from tarfile {}: {}".format(
141 rel_file_path
, self
._tar
_filehdl
, str(e
)
144 raise ArchiveError(msg
) from e
146 def create_package(self
):
147 """ Creates a Descriptor package from the archive contents """
148 pkg
= package
.DescriptorPackage
.from_package_files(self
._log
, self
.open_file
, self
.filenames
)
149 for pkg_file
in self
.filenames
:
150 pkg
.add_file(pkg_file
, self
._tar
_infos
[pkg_file
].mode
)