# Copyright (C) 2022-2025 Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
from __future__ import annotations
from datetime import datetime
import json
from pathlib import Path
import string
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple
import attr
import iso8601
from swh.loader.core.utils import (
EMPTY_AUTHOR,
Person,
cached_method,
get_url_body,
release_name,
)
from swh.loader.package.loader import (
BasePackageInfo,
PackageLoader,
RawExtrinsicMetadataCore,
)
from swh.model.model import (
MetadataAuthority,
MetadataAuthorityType,
ObjectType,
Release,
Sha1Git,
TimestampWithTimezone,
)
from swh.storage.interface import StorageInterface
[docs]
@attr.s
class PuppetPackageInfo(BasePackageInfo):
name = attr.ib(type=str)
"""Name of the package"""
filename = attr.ib(type=str)
"""Archive (tar.gz) file name"""
version = attr.ib(type=str)
"""Current version"""
last_modified = attr.ib(type=datetime)
"""Module last update date as release date"""
sha256_checksum = attr.ib(type=str)
EXTID_TYPE = "puppet-module-sha256"
MANIFEST_FORMAT = string.Template("$name $version $filename $sha256_checksum")
[docs]
class PuppetLoader(PackageLoader[PuppetPackageInfo]):
visit_type = "puppet"
FORGEAPI_BASE_URL = "https://forgeapi.puppet.com"
METADATA_URL = FORGEAPI_BASE_URL + "/v3/releases?module={module}"
def __init__(
self,
storage: StorageInterface,
url: str,
artifacts: List[Dict[str, Any]],
**kwargs,
):
super().__init__(storage=storage, url=url, **kwargs)
self.url = url
self.artifacts: Dict[str, Dict] = {
artifact["version"]: artifact for artifact in artifacts
}
split_url = url.split("/")
self.module_name = "-".join(split_url[-2:])
[docs]
def get_versions(self) -> Sequence[str]:
"""Get all released versions of a Puppet module
Returns:
A sequence of versions
Example::
["0.1.1", "0.10.2"]
"""
return list(self.artifacts)
[docs]
def get_package_info(self, version: str) -> Iterator[Tuple[str, PuppetPackageInfo]]:
"""Get release name and package information from version
Args:
version: Package version (e.g: "0.1.0")
Returns:
Iterator of tuple (release_name, p_info)
"""
data = self.artifacts[version]
url = data["url"]
filename = data["filename"]
last_modified = iso8601.parse_date(data["last_update"])
p_info = PuppetPackageInfo.from_metadata(
url=url,
module_name=self.module_name,
filename=filename,
version=version,
last_modified=last_modified,
extrinsic_metadata=self.extrinsic_metadata(),
)
yield release_name(version), p_info
[docs]
def build_release(
self, p_info: PuppetPackageInfo, uncompressed_path: str, directory: Sha1Git
) -> Optional[Release]:
# Extract intrinsic metadata from uncompressed_path/{dirname}/metadata.json
intrinsic_metadata = extract_intrinsic_metadata(Path(uncompressed_path))
version: str = intrinsic_metadata["version"]
assert version == p_info.version
_author = intrinsic_metadata.get("author")
if isinstance(_author, list) and _author:
author_name = _author[0]
else:
author_name = _author
author = (
Person.from_fullname(author_name.encode()) if author_name else EMPTY_AUTHOR
)
message = (
f"Synthetic release for Puppet source package {p_info.name} "
f"version {version}\n"
)
if isinstance(_author, list) and len(_author) > 1:
message += "\n"
for person in _author[1:]:
message += f"Co-authored-by: {person}\n"
return Release(
name=version.encode(),
author=author,
date=TimestampWithTimezone.from_datetime(p_info.last_modified),
message=message.encode(),
target_type=ObjectType.DIRECTORY,
target=directory,
synthetic=True,
)