# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2021 Canonical Ltd.## This program is free software; you can redistribute it and/or# modify it under the terms of the GNU Lesser General Public# License version 3 as published by the Free Software Foundation.## This program 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# Lesser General Public License for more details.## You should have received a copy of the GNU Lesser General Public License# along with this program. If not, see <http://www.gnu.org/licenses/>."""The snap source handler."""importosimportshutilimporttempfilefrompathlibimportPathfromtypingimportLiteral,castimportyamlfromoverridesimportoverridesfromcraft_parts.utilsimportfile_utilsfrom.importerrorsfrom.baseimport(BaseFileSourceModel,FileSourceHandler,get_json_extra_schema,get_model_config,)
[docs]classSnapSourceModel(BaseFileSourceModel,frozen=True):# type: ignore[misc]"""Pydantic model for a snap file source."""model_config=get_model_config(get_json_extra_schema(r"\.snap$"))source_type:Literal["snap"]="snap"
[docs]classSnapSource(FileSourceHandler):"""Handles downloading and extractions for a snap source. On provision, the meta directory is renamed to meta.<snap-name> and, if present, the same applies for the snap directory which shall be renamed to snap.<snap-name>. """source_model=SnapSourceModel
[docs]@overridesdefprovision(self,dst:Path,keep:bool=False,# noqa: FBT001, FBT002src:Path|None=None,)->None:"""Provision the snap source. :param dst: The destination directory to provision to. :param keep: Whether to keep the snap after provisioning is complete. :param src: Force a new source to use for extraction. raises errors.InvalidSnap: If trying to provision an invalid snap. """snap_file=srcifsrcelseself.part_src_dir/os.path.basename(self.source)snap_file=snap_file.resolve()# unsquashfs [options] filesystem [directories or files to extract]# options:# -force: if file already exists then overwrite# -dest <pathname>: unsquash to <pathname>withtempfile.TemporaryDirectory(prefix=str(snap_file.parent))astemp_dir:extract_command=["unsquashfs","-force","-dest",temp_dir,snap_file,]self._run_output(extract_command)snap_name=_get_snap_name(snap_file.name,temp_dir)# Rename meta and snap dirs from the snaprename_paths=(os.path.join(temp_dir,d)fordin["meta","snap"])rename_paths=(dfordinrename_pathsifos.path.exists(d))forrenameinrename_paths:shutil.move(rename,f"{rename}.{snap_name}")file_utils.link_or_copy_tree(source_tree=temp_dir,destination_tree=str(dst))ifnotkeep:os.remove(snap_file)
def_get_snap_name(snap:str,snap_dir:str)->str:"""Obtain the snap name from the snap details file. :param snap: The snap package file. :param snap_dir: The location of the unsquashed snap contents. :return: The snap name. """try:withopen(os.path.join(snap_dir,"meta","snap.yaml"))assnap_yaml:returncast(str,yaml.safe_load(snap_yaml)["name"])except(FileNotFoundError,KeyError)assnap_error:raiseerrors.InvalidSnapPackage(snap)fromsnap_error