Coverage for src/gitlabracadabra/packages/helm.py: 100%
47 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-14 23:10 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-14 23:10 +0200
1#
2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
17from __future__ import annotations
19from logging import getLogger
20from urllib.parse import urljoin
22from requests import codes
23from yaml import safe_load as yaml_safe_load
25from gitlabracadabra.matchers import Matcher
26from gitlabracadabra.packages.package_file import PackageFile
27from gitlabracadabra.packages.source import Source
29logger = getLogger(__name__)
32class Helm(Source):
33 """Helm repository."""
35 def __init__(
36 self,
37 *,
38 log_prefix: str = "",
39 repo_url: str,
40 package_name: str,
41 versions: list[str] | None = None,
42 semver: str | None = None,
43 limit: int | None = 1,
44 channel: str | None = None,
45 ) -> None:
46 """Initialize a Helm repository object.
48 Args:
49 log_prefix: Log prefix.
50 repo_url: Helm repository URL.
51 package_name: Package name.
52 versions: List of versions.
53 semver: Semantic version.
54 limit: Keep at most n latest versions.
55 channel: Destination channel.
56 """
57 super().__init__()
58 self._log_prefix = log_prefix
59 self._repo_url = repo_url
60 self._package_name = package_name
61 self._versions = versions or ["/.*/"]
62 self._semver = semver or "*"
63 self._limit = limit
64 self._channel = channel or "stable"
66 def __str__(self) -> str:
67 """Return string representation.
69 Returns:
70 A string.
71 """
72 return f"Helm charts repository (url={self._repo_url})"
74 @property
75 def package_files(self) -> list[PackageFile]:
76 """Return list of package files.
78 Returns:
79 List of package files.
80 """
81 package_entries = self._get_helm_index().get("entries", {})
82 package_matches = Matcher(
83 self._package_name,
84 None,
85 log_prefix=self._log_prefix,
86 ).match(
87 list(package_entries.keys()),
88 )
89 package_files: list[PackageFile] = []
90 for package_match in package_matches:
91 package_entry = package_entries[package_match.group(0)]
92 package_versions = {package_dict.get("version", "0"): package_dict for package_dict in package_entry}
93 matches = Matcher(
94 self._versions,
95 self._semver,
96 self._limit,
97 log_prefix=self._log_prefix,
98 ).match(
99 list(package_versions.keys()),
100 )
101 for match in matches:
102 package_files.append(self._package_file(package_versions[match[0]])) # noqa: PERF401
103 if not package_files:
104 logger.warning(
105 "%sPackage not found %s for Helm index %s",
106 self._log_prefix,
107 self._package_name,
108 self._repo_index_url,
109 )
110 return package_files
112 def _get_helm_index(self) -> dict:
113 index_response = self.session.request("get", self._repo_index_url)
114 if index_response.status_code != codes["ok"]:
115 logger.warning(
116 "%sUnexpected HTTP status for Helm index %s: received %i %s",
117 self._log_prefix,
118 self._repo_index_url,
119 index_response.status_code,
120 index_response.reason,
121 )
122 return {}
123 return yaml_safe_load(index_response.content) # type: ignore
125 @property
126 def _repo_index_url(self) -> str:
127 return f"{self._repo_url}/index.yaml"
129 def _package_file(self, package_dict: dict) -> PackageFile:
130 url = urljoin(self._repo_index_url, package_dict.get("urls", []).pop())
131 return PackageFile(
132 url,
133 "helm",
134 package_dict.get("name", self._package_name),
135 package_dict.get("version", "0"),
136 metadata={"channel": self._channel},
137 )