# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2021-2024 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/>."""Layer management and helpers."""importhashlibimportloggingfromcraft_parts.partsimportPartlogger=logging.getLogger(__name__)
[docs]classLayerHash:"""The layer validation hash for a part."""def__init__(self,layer_hash:bytes)->None:self.digest=layer_hashdef__repr__(self)->str:returnself.hex()def__eq__(self,other:object)->bool:ifnotisinstance(other,LayerHash):returnFalsereturnself.digest==other.digest
[docs]@classmethoddeffor_part(cls,part:Part,*,previous_layer_hash:"LayerHash | None")->"LayerHash":"""Obtain the validation hash for a part. :param part: The part being processed. :param previous_layer_hash: The validation hash of the previous layer in the overlay stack. :returns: The validation hash computed for the layer corresponding to the given part. """hasher=hashlib.sha1()# noqa: S324ifprevious_layer_hash:hasher.update(previous_layer_hash.digest)forentryinpart.spec.overlay_packages:hasher.update(entry.encode())digest=hasher.digest()hasher=hashlib.sha1()# noqa: S324hasher.update(digest)forentryinpart.spec.overlay_files:hasher.update(entry.encode())digest=hasher.digest()hasher=hashlib.sha1()# noqa: S324hasher.update(digest)ifpart.spec.overlay_script:hasher.update(part.spec.overlay_script.encode())returncls(hasher.digest())
[docs]@classmethoddefload(cls,part:Part)->"LayerHash | None":"""Read the part layer validation hash from persistent state. :param part: The part whose layer hash will be loaded. :return: A layer hash object containing the loaded validation hash, or None if the file doesn't exist. """hash_file=part.part_state_dir/"layer_hash"ifnothash_file.exists():returnNonewithopen(hash_file)asfile:hex_string=file.readline()returncls(bytes.fromhex(hex_string))
[docs]defsave(self,part:Part)->None:"""Save the part layer validation hash to persistent storage. :param part: The part whose layer hash will be saved. """hash_file=part.part_state_dir/"layer_hash"hash_file.write_text(self.hex())
[docs]defhex(self)->str:"""Return the current hash value as a hexadecimal string."""returnself.digest.hex()
[docs]classLayerStateManager:"""An in-memory layer state management helper for action planning. :param part_list: The list of parts in the project. :param base_layer_hash: The verification hash of the overlay base layer. """def__init__(self,part_list:list[Part],base_layer_hash:LayerHash|None)->None:self._part_list=part_listself._base_layer_hash=base_layer_hashself._layer_hash:dict[str,LayerHash|None]={}forpartinpart_list:self.set_layer_hash(part,LayerHash.load(part))
[docs]defget_layer_hash(self,part:Part)->LayerHash|None:"""Obtain the layer hash for the given part."""returnself._layer_hash.get(part.name)
[docs]defset_layer_hash(self,part:Part,layer_hash:LayerHash|None)->None:"""Store the value of the layer hash for the given part."""self._layer_hash[part.name]=layer_hash
[docs]defcompute_layer_hash(self,part:Part)->LayerHash:"""Calculate the layer validation hash for the given part. :param part: The part being processed. :return: The validation hash of the layer corresponding to the given part. """index=self._part_list.index(part)ifindex>0:previous_layer_hash=self.get_layer_hash(self._part_list[index-1])else:previous_layer_hash=self._base_layer_hashreturnLayerHash.for_part(part,previous_layer_hash=previous_layer_hash)
[docs]defget_overlay_hash(self)->bytes:"""Obtain the overlay validation hash."""last_part=self._part_list[-1]overlay_hash=self.get_layer_hash(last_part)ifnotoverlay_hash:returnb""returnoverlay_hash.digest