Source code for craft_parts.sources.snap_source

# -*- 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."""

import os
import shutil
import tempfile
from pathlib import Path
from typing import Optional, cast

import yaml
from overrides import overrides

from craft_parts.dirs import ProjectDirs
from craft_parts.utils import file_utils

from . import errors
from .base import FileSourceHandler


[docs]class SnapSource(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>. """ def __init__( self, source: str, part_src_dir: Path, *, cache_dir: Path, project_dirs: ProjectDirs, source_tag: Optional[str] = None, source_commit: Optional[str] = None, source_branch: Optional[str] = None, source_depth: Optional[int] = None, source_checksum: Optional[str] = None, **kwargs, ) -> None: super().__init__( source, part_src_dir, cache_dir=cache_dir, source_tag=source_tag, source_commit=source_commit, source_branch=source_branch, source_depth=source_depth, source_checksum=source_checksum, project_dirs=project_dirs, command="unsquashfs", **kwargs, ) if source_tag: raise errors.InvalidSourceOption(source_type="snap", option="source-tag") if source_commit: raise errors.InvalidSourceOption(source_type="snap", option="source-commit") if source_branch: raise errors.InvalidSourceOption(source_type="snap", option="source-branch") if source_depth: raise errors.InvalidSourceOption(source_type="snap", option="source-depth")
[docs] @overrides def provision( self, dst: Path, keep: bool = False, src: Optional[Path] = 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. """ if src: snap_file = src else: snap_file = self.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> with tempfile.TemporaryDirectory(prefix=str(snap_file.parent)) as temp_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 snap rename_paths = (os.path.join(temp_dir, d) for d in ["meta", "snap"]) rename_paths = (d for d in rename_paths if os.path.exists(d)) for rename in rename_paths: shutil.move(rename, f"{rename}.{snap_name}") file_utils.link_or_copy_tree( source_tree=temp_dir, destination_tree=str(dst) ) if not keep: 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: with open(os.path.join(snap_dir, "meta", "snap.yaml")) as snap_yaml: return cast(str, yaml.safe_load(snap_yaml)["name"]) except (FileNotFoundError, KeyError) as snap_error: raise errors.InvalidSnapPackage(snap) from snap_error