|
8 | 8 | from pyinfra import host |
9 | 9 | from pyinfra.api import OperationError, operation |
10 | 10 | from pyinfra.facts.gpg import GpgKeyrings |
| 11 | +from pyinfra.facts import files as file_facts |
11 | 12 |
|
12 | 13 | from . import files |
13 | 14 |
|
@@ -277,6 +278,64 @@ def key( |
277 | 278 | # After validation, we know dest is not None for installation |
278 | 279 | assert dest is not None, "dest should not be None after validation" |
279 | 280 |
|
| 281 | + # Check if key already exists (for idempotence) |
| 282 | + if keyid: |
| 283 | + # If we have keyid(s), check if they exist in the destination keyring |
| 284 | + try: |
| 285 | + dest_dir = str(PurePosixPath(dest).parent) |
| 286 | + keyring_fact = host.get_fact(GpgKeyrings, [dest_dir]) |
| 287 | + |
| 288 | + # keyring_fact contains keyring paths as keys |
| 289 | + # Check if our destination keyring exists in the fact |
| 290 | + keyring_info = keyring_fact.get(dest) |
| 291 | + |
| 292 | + if keyring_info: |
| 293 | + existing_keys = keyring_info["keys"] |
| 294 | + keyids_to_check = keyid if isinstance(keyid, list) else [keyid] |
| 295 | + |
| 296 | + # Check if all requested keys already exist |
| 297 | + all_keys_exist = True |
| 298 | + for kid in keyids_to_check: |
| 299 | + # Remove 0x prefix if present for comparison |
| 300 | + clean_keyid = kid.replace("0x", "").replace("0X", "").upper() |
| 301 | + key_exists = any( |
| 302 | + clean_keyid in existing_key_id.upper() |
| 303 | + or existing_key_id.upper().endswith(clean_keyid) |
| 304 | + for existing_key_id in existing_keys.keys() |
| 305 | + ) |
| 306 | + if not key_exists: |
| 307 | + all_keys_exist = False |
| 308 | + break |
| 309 | + |
| 310 | + if all_keys_exist: |
| 311 | + # All keys already exist, ensure file permissions are correct |
| 312 | + yield from files.file._inner( |
| 313 | + path=dest, |
| 314 | + mode=mode, |
| 315 | + present=True, |
| 316 | + ) |
| 317 | + host.noop(f"GPG keys {keyid} already exist in {dest}") |
| 318 | + return |
| 319 | + except (KeyError, AttributeError): |
| 320 | + # Fact not available or incomplete, proceed with installation |
| 321 | + pass |
| 322 | + else: |
| 323 | + # If no keyid specified, check if destination file exists (for file/URL sources) |
| 324 | + try: |
| 325 | + file_fact = host.get_fact(file_facts.File, dest) |
| 326 | + if file_fact: |
| 327 | + # File exists, ensure permissions are correct |
| 328 | + yield from files.file._inner( |
| 329 | + path=dest, |
| 330 | + mode=mode, |
| 331 | + present=True, |
| 332 | + ) |
| 333 | + host.noop(f"GPG keyring {dest} already exists") |
| 334 | + return |
| 335 | + except (KeyError, AttributeError): |
| 336 | + # Fact not available, proceed with installation |
| 337 | + pass |
| 338 | + |
280 | 339 | # Ensure destination directory exists |
281 | 340 | dest_dir = str(PurePosixPath(dest).parent) |
282 | 341 | yield from files.directory._inner( |
|
0 commit comments