# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2025 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/>."""FilesystemMounts models."""fromcollections.abcimportIteratorfromtypingimportAnnotated,Any,Literal,castfrompydanticimport(BaseModel,ConfigDict,Field,RootModel,ValidationError,field_validator,)fromcraft_partsimporterrors,featuresfromcraft_parts.constraintsimportSingleEntryDict,UniqueList
[docs]classFilesystemMountItem(BaseModel):"""FilesystemMountItem maps a mountpoint to a device."""model_config=ConfigDict(validate_assignment=True,extra="forbid",populate_by_name=True,)mount:str=Field(min_length=1)device:str=Field(min_length=1)def__hash__(self)->int:returnstr.__hash__(self.mount)def__eq__(self,other:object)->bool:iftype(other)istype(self):returnself.mount==cast(FilesystemMountItem,other).mountreturnFalse
[docs]@classmethoddefunmarshal(cls,data:dict[str,Any])->"FilesystemMountItem":"""Create and populate a new ``FilesystemMountItem`` object from dictionary data. The unmarshal method validates entries in the input dictionary, populating the corresponding fields in the data object. :param data: The dictionary data to unmarshal. :return: The newly created object. :raise TypeError: If data is not a dictionary. """ifnotisinstance(data,dict):raiseTypeError("Filesystem input data must be a dictionary.")returncls.model_validate(data)
[docs]defmarshal(self)->dict[str,Any]:"""Create a dictionary containing the filesystem_mount item data. :return: The newly created dictionary. """returnself.model_dump(by_alias=True)
[docs]classFilesystemMount(RootModel):"""FilesystemMount defines the order in which devices should be mounted."""root:Annotated[UniqueList[FilesystemMountItem],Field(min_length=1)]def__iter__(self)->Iterator[FilesystemMountItem]:# type: ignore[override]returniter(self.root)
[docs]@field_validator("root",mode="after")@classmethoddeffirst_maps_to_slash(cls,value:list[FilesystemMountItem])->list[FilesystemMountItem]:"""Make sure the first item in the list maps the '/' mount."""ifvalue[0].mount!="/":raiseValueError("The first entry in a filesystem must map the '/' mount.")returnvalue
[docs]@classmethoddefunmarshal(cls,data:list[dict[str,Any]])->"FilesystemMount":"""Create and populate a new ``FilesystemMount`` object from list. The unmarshal method validates entries in the input list, populating the corresponding fields in the data object. :param data: The list to unmarshal. :return: The newly created object. :raise TypeError: If data is not a list. :raise pydantic.ValidationError: If the data fails validation. """ifnotisinstance(data,list):raiseTypeError("Filesystem entry must be a list.")returncls.model_validate([FilesystemMountItem.unmarshal(item)foritemindata])
[docs]defmarshal(self)->list[dict[str,Any]]:"""Create a list containing the filesystem_mount data. :return: The newly created list. """returncast(list[dict[str,Any]],self.model_dump(by_alias=True))
[docs]defvalidate_filesystem_mount(data:list[dict[str,Any]])->None:"""Validate a filesystem_mount. :param data: The filesystem mount data to validate. :raises: ValueError if the filesystem mount is not valid. """FilesystemMount.unmarshal(data)
[docs]defvalidate_filesystem_mounts(filesystem_mounts:dict[str,Any]|None)->None:"""Validate the filesystems section. If filesystems are defined then both partition and overlay features must be enabled. A filesystem_mounts dict must only have a single "default" entry. The first entry in default must map the '/' mount. :raises: FilesystemMountError if the filesystem mounts are not valid. """ifnotfilesystem_mounts:returnif(notfeatures.Features().enable_partitionsornotfeatures.Features().enable_overlay):raiseerrors.FilesystemMountError(brief="Missing features to use filesystems",resolution="Enable both the partition and overlay features.",)iflen(filesystem_mounts)>1:raiseerrors.FilesystemMountError(brief="Only one filesystem can be defined.",resolution="Reduce the filesystems section to a single entry.",)default_filesystem_mount=filesystem_mounts.get("default")ifdefault_filesystem_mountisNone:raiseerrors.FilesystemMountError(brief="'default' filesystem missing.",resolution="Define a 'default' entry in the filesystems section.",)try:validate_filesystem_mount(default_filesystem_mount)exceptValidationErroraserr:raiseerrors.FilesystemMountError.from_validation_error(error_list=err.errors(),)fromerrexceptTypeErroraserr:raiseerrors.FilesystemMountError(brief="Filesystem validation failed.",details=str(err),)fromerr