|
| 1 | +import importlib.metadata |
1 | 2 | import logging |
2 | 3 | import os |
3 | 4 | import re |
4 | 5 | import signal |
5 | 6 | import sys |
6 | 7 | from datetime import datetime |
| 8 | +from pathlib import Path |
| 9 | +from tempfile import TemporaryDirectory |
| 10 | +from typing import Any, Dict, List, Optional |
7 | 11 |
|
8 | | -import importlib.metadata |
9 | 12 | import pytz |
10 | 13 | import yaml |
11 | 14 |
|
| 15 | +from gardenlinux.constants import GL_REPOSITORY_URL |
| 16 | +from gardenlinux.flavors import Parser |
| 17 | +from gardenlinux.git import Repository |
| 18 | +from gardenlinux.s3 import Bucket |
| 19 | + |
| 20 | + |
12 | 21 | ERROR_CODES = { |
13 | 22 | "validation_error": 1, |
14 | 23 | "subprocess_output_error": 2, |
|
50 | 59 | "ARTIFACTS_S3_BUCKET_NAME": "gardenlinux-github-releases", |
51 | 60 | "ARTIFACTS_S3_PREFIX": "objects/", |
52 | 61 | "ARTIFACTS_S3_BASE_URL": ("https://gardenlinux-github-releases.s3.amazonaws.com"), |
| 62 | + "ARTIFACTS_S3_CACHE_FILE": "artifacts-cache.json", |
53 | 63 | # Garden Linux repository |
54 | 64 | "GL_REPO_NAME": "gardenlinux", |
55 | 65 | "GL_REPO_OWNER": "gardenlinux", |
@@ -144,3 +154,142 @@ def ignore_aliases(self, data): |
144 | 154 |
|
145 | 155 |
|
146 | 156 | signal.signal(signal.SIGPIPE, handle_broken_pipe_error) |
| 157 | + |
| 158 | + |
| 159 | +def get_flavors_from_git(commit: str) -> List[str]: |
| 160 | + """ |
| 161 | + Get flavors from Git repository using gardenlinux library. |
| 162 | +
|
| 163 | + Args: |
| 164 | + commit: Git commit hash (or 'latest') |
| 165 | +
|
| 166 | + Returns: |
| 167 | + List of flavor strings |
| 168 | + """ |
| 169 | + |
| 170 | + try: |
| 171 | + with TemporaryDirectory() as git_directory: |
| 172 | + # Use gardenlinux Repository class for sparse checkout with pygit2 |
| 173 | + Repository.checkout_repo_sparse( |
| 174 | + git_directory=git_directory, |
| 175 | + repo_url=GL_REPOSITORY_URL, |
| 176 | + commit=commit if commit != "latest" else None, |
| 177 | + pathspecs=["flavors.yaml"], # Only checkout the flavors.yaml file |
| 178 | + ) |
| 179 | + |
| 180 | + flavors_file = Path(git_directory, "flavors.yaml") |
| 181 | + if flavors_file.exists(): |
| 182 | + with flavors_file.open("r") as fp: |
| 183 | + flavors_data = fp.read() |
| 184 | + flavors_yaml = yaml.safe_load(flavors_data) |
| 185 | + parser = Parser(flavors_yaml) |
| 186 | + combinations = parser.filter() |
| 187 | + all_flavors = set(combination for _, combination in combinations) |
| 188 | + flavors = sorted(all_flavors) |
| 189 | + logging.info(f"Found {len(flavors)} flavors in Git") |
| 190 | + return flavors |
| 191 | + else: |
| 192 | + logging.warning("flavors.yaml not found in repository") |
| 193 | + return [] |
| 194 | + except Exception as exc: |
| 195 | + logging.debug(f"Could not get flavors from Git: {exc}") |
| 196 | + return [] |
| 197 | + |
| 198 | + |
| 199 | +def get_s3_artifacts_data( |
| 200 | + bucket_name: str, |
| 201 | + prefix: str, |
| 202 | + cache_file: Optional[str] = None, |
| 203 | + cache_ttl: int = 3600, |
| 204 | +) -> Optional[Dict]: |
| 205 | + """ |
| 206 | + Get S3 artifacts data using gardenlinux library with caching support. |
| 207 | +
|
| 208 | + Args: |
| 209 | + bucket_name: S3 bucket name |
| 210 | + prefix: S3 prefix |
| 211 | + cache_file: Optional cache file path for S3 object keys |
| 212 | + cache_ttl: Cache time-to-live in seconds (default: 1 hour) |
| 213 | +
|
| 214 | + Returns: |
| 215 | + Dictionary containing S3 artifacts data with 'index' and 'artifacts' keys |
| 216 | + """ |
| 217 | + |
| 218 | + try: |
| 219 | + bucket = Bucket(bucket_name) |
| 220 | + |
| 221 | + artifacts = bucket.read_cache_file_or_filter( |
| 222 | + cache_file, cache_ttl=cache_ttl, Prefix=prefix |
| 223 | + ) |
| 224 | + |
| 225 | + index = {} |
| 226 | + for key in artifacts: |
| 227 | + try: |
| 228 | + parts = key.split("/") |
| 229 | + if len(parts) >= 3: |
| 230 | + version_commit = parts[1] |
| 231 | + if "-" in version_commit: |
| 232 | + version_part, commit_part = version_commit.split("-", 1) |
| 233 | + if version_part not in index: |
| 234 | + index[version_part] = [] |
| 235 | + index[version_part].append(key) |
| 236 | + except Exception as e: |
| 237 | + logging.debug(f"Could not parse version from key {key}: {e}") |
| 238 | + |
| 239 | + result = {"index": index, "artifacts": artifacts} |
| 240 | + logging.info(f"Found {len(artifacts)} artifacts and {len(index)} index entries") |
| 241 | + return result |
| 242 | + except Exception as e: |
| 243 | + logging.error(f"Error getting S3 artifacts: {e}") |
| 244 | + return None |
| 245 | + |
| 246 | + |
| 247 | +def get_flavors_from_s3_artifacts( |
| 248 | + artifacts_data: Dict, version: Dict[str, Any], commit: str |
| 249 | +) -> List[str]: |
| 250 | + """ |
| 251 | + Extract flavors from S3 artifacts data. |
| 252 | +
|
| 253 | + Args: |
| 254 | + artifacts_data: S3 artifacts data dictionary |
| 255 | + version: Version dictionary with major, minor, micro |
| 256 | + commit: Git commit hash |
| 257 | +
|
| 258 | + Returns: |
| 259 | + List of flavor strings |
| 260 | + """ |
| 261 | + |
| 262 | + try: |
| 263 | + version_info = f"{version['major']}.{version.get('minor', 0)}" |
| 264 | + commit_short = commit[:8] |
| 265 | + |
| 266 | + # Try index lookup first |
| 267 | + search_key = f"{version_info}-{commit_short}" |
| 268 | + if search_key in artifacts_data.get("index", {}): |
| 269 | + flavors = artifacts_data["index"][search_key] |
| 270 | + logging.debug(f"Found flavors in S3 index for {search_key}") |
| 271 | + return flavors |
| 272 | + else: |
| 273 | + # Search through artifacts |
| 274 | + found_flavors = set() |
| 275 | + for key in artifacts_data.get("artifacts", []): |
| 276 | + if version_info in key and commit_short in key: |
| 277 | + try: |
| 278 | + parts = key.split("/") |
| 279 | + if len(parts) >= 2: |
| 280 | + flavor_with_version = parts[1] |
| 281 | + flavor = flavor_with_version.rsplit(f"-{version_info}", 1)[ |
| 282 | + 0 |
| 283 | + ] |
| 284 | + if flavor: |
| 285 | + found_flavors.add(flavor) |
| 286 | + except Exception as e: |
| 287 | + logging.debug(f"Error parsing artifact key {key}: {e}") |
| 288 | + continue |
| 289 | + flavors = sorted(found_flavors) |
| 290 | + if flavors: |
| 291 | + logging.info(f"Found {len(flavors)} flavors in S3 artifacts") |
| 292 | + return flavors |
| 293 | + except Exception as e: |
| 294 | + logging.error(f"Error processing S3 artifacts: {e}") |
| 295 | + return [] |
0 commit comments