|
| 1 | +"""Component Checksum Registry API |
| 2 | +
|
| 3 | +This module provides a unified API for accessing OCI component checksums |
| 4 | +from JSON files, mirroring the toolchain registry pattern. |
| 5 | +
|
| 6 | +The component registry enables: |
| 7 | +1. Reproducible builds with digest-based pulls |
| 8 | +2. Security auditing via centralized checksums |
| 9 | +3. Air-gap builds with pre-verified components |
| 10 | +
|
| 11 | +Usage: |
| 12 | + load("//checksums:component_registry.bzl", "component_registry") |
| 13 | +
|
| 14 | + # Get component digest for reproducible pulls |
| 15 | + digest = component_registry.get_digest(ctx, "wasi-http-proxy", "0.2.6") |
| 16 | +
|
| 17 | + # Get full component info |
| 18 | + info = component_registry.get_info(ctx, "wasi-http-proxy", "0.2.6") |
| 19 | +""" |
| 20 | + |
| 21 | +def _load_component_json(repository_ctx, component_name): |
| 22 | + """Load component data from JSON file. |
| 23 | +
|
| 24 | + Args: |
| 25 | + repository_ctx: Repository context for file operations |
| 26 | + component_name: Name of the component (e.g., 'wasi-http-proxy') |
| 27 | +
|
| 28 | + Returns: |
| 29 | + Dict: Component data from JSON file |
| 30 | +
|
| 31 | + Raises: |
| 32 | + fail: If JSON file not found |
| 33 | + """ |
| 34 | + json_file = repository_ctx.path( |
| 35 | + Label("@rules_wasm_component//checksums/components:{}.json".format(component_name)), |
| 36 | + ) |
| 37 | + if not json_file.exists: |
| 38 | + fail("Component checksums not found: //checksums/components/{}.json\n".format(component_name) + |
| 39 | + "Add the component to the registry or use vendored_component attribute.") |
| 40 | + |
| 41 | + content = repository_ctx.read(json_file) |
| 42 | + return json.decode(content) |
| 43 | + |
| 44 | +def _get_component_digest(repository_ctx, component_name, version): |
| 45 | + """Get verified digest for a component version. |
| 46 | +
|
| 47 | + Args: |
| 48 | + repository_ctx: Repository context for file operations |
| 49 | + component_name: Name of the component (e.g., 'wasi-http-proxy') |
| 50 | + version: Version string (e.g., '0.2.6') |
| 51 | +
|
| 52 | + Returns: |
| 53 | + String: SHA256 digest in format 'sha256:abc123...', or None if not found |
| 54 | + """ |
| 55 | + component_data = _load_component_json(repository_ctx, component_name) |
| 56 | + |
| 57 | + versions = component_data.get("versions", {}) |
| 58 | + version_data = versions.get(version, {}) |
| 59 | + |
| 60 | + return version_data.get("digest") |
| 61 | + |
| 62 | +def _get_component_info(repository_ctx, component_name, version): |
| 63 | + """Get complete component information for a version. |
| 64 | +
|
| 65 | + Args: |
| 66 | + repository_ctx: Repository context for file operations |
| 67 | + component_name: Name of the component |
| 68 | + version: Version string |
| 69 | +
|
| 70 | + Returns: |
| 71 | + Dict: Complete version information including digest, wit_world, etc. |
| 72 | + """ |
| 73 | + component_data = _load_component_json(repository_ctx, component_name) |
| 74 | + |
| 75 | + versions = component_data.get("versions", {}) |
| 76 | + return versions.get(version) |
| 77 | + |
| 78 | +def _get_latest_version(repository_ctx, component_name): |
| 79 | + """Get latest available version for a component. |
| 80 | +
|
| 81 | + Args: |
| 82 | + repository_ctx: Repository context for file operations |
| 83 | + component_name: Name of the component |
| 84 | +
|
| 85 | + Returns: |
| 86 | + String: Latest version, or None if component not found |
| 87 | + """ |
| 88 | + component_data = _load_component_json(repository_ctx, component_name) |
| 89 | + return component_data.get("latest_version") |
| 90 | + |
| 91 | +def _get_oci_repository(repository_ctx, component_name): |
| 92 | + """Get OCI repository URL for a component. |
| 93 | +
|
| 94 | + Args: |
| 95 | + repository_ctx: Repository context for file operations |
| 96 | + component_name: Name of the component |
| 97 | +
|
| 98 | + Returns: |
| 99 | + String: OCI repository URL (e.g., 'ghcr.io/bytecodealliance/wasi-http-proxy') |
| 100 | + """ |
| 101 | + component_data = _load_component_json(repository_ctx, component_name) |
| 102 | + return component_data.get("oci_repository") |
| 103 | + |
| 104 | +def _list_versions(repository_ctx, component_name): |
| 105 | + """List all available versions for a component. |
| 106 | +
|
| 107 | + Args: |
| 108 | + repository_ctx: Repository context for file operations |
| 109 | + component_name: Name of the component |
| 110 | +
|
| 111 | + Returns: |
| 112 | + List: List of version strings |
| 113 | + """ |
| 114 | + component_data = _load_component_json(repository_ctx, component_name) |
| 115 | + versions = component_data.get("versions", {}) |
| 116 | + return list(versions.keys()) |
| 117 | + |
| 118 | +def _verify_digest(repository_ctx, component_name, version, actual_digest): |
| 119 | + """Verify a component's digest against the registry. |
| 120 | +
|
| 121 | + Args: |
| 122 | + repository_ctx: Repository context for file operations |
| 123 | + component_name: Name of the component |
| 124 | + version: Version string |
| 125 | + actual_digest: The digest to verify |
| 126 | +
|
| 127 | + Returns: |
| 128 | + Boolean: True if digest matches, False otherwise |
| 129 | + """ |
| 130 | + expected = _get_component_digest(repository_ctx, component_name, version) |
| 131 | + if not expected: |
| 132 | + return False |
| 133 | + return expected == actual_digest |
| 134 | + |
| 135 | +# Public API |
| 136 | +component_registry = struct( |
| 137 | + get_digest = _get_component_digest, |
| 138 | + get_info = _get_component_info, |
| 139 | + get_latest_version = _get_latest_version, |
| 140 | + get_oci_repository = _get_oci_repository, |
| 141 | + list_versions = _list_versions, |
| 142 | + verify_digest = _verify_digest, |
| 143 | +) |
0 commit comments