OCaml library and CLI for OCI and Docker image manipulation
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

irmin: 15/17 Git backend tests pass

+70 -87
+70 -87
test/interop/registry/test.ml
··· 14 14 close_in ic; 15 15 s 16 16 17 - let read_json path = 18 - let s = read_file path in 19 - match Jsont_bytesrw.decode_string Jsont.json_value s with 20 - | Ok j -> j 21 - | Error e -> Fmt.failwith "failed to parse %s: %s" path e 17 + (* Simple JSON string field extraction for meta.json *) 18 + let meta_field key = 19 + let raw = read_file "meta.json" in 20 + let pat = Fmt.str {|"%s": "|} key in 21 + match Astring.String.find_sub ~sub:pat raw with 22 + | None -> Fmt.failwith "meta.json: missing field %s" key 23 + | Some i -> 24 + let start = i + String.length pat in 25 + let stop = 26 + match String.index_from_opt raw start '"' with 27 + | Some j -> j 28 + | None -> Fmt.failwith "meta.json: unterminated string for %s" key 29 + in 30 + String.sub raw start (stop - start) 22 31 23 32 (* {1 Index parsing} *) 24 33 25 34 let test_parse_index () = 26 - let json = read_json "index.json" in 27 - match Index.of_yojson json with 28 - | Error e -> fail e 29 - | Ok index -> 35 + let raw = read_file "index.json" in 36 + match Manifest.of_string raw with 37 + | Error (`Msg e) -> fail e 38 + | Ok (`OCI_index index) -> 30 39 let manifests = Index.manifests index in 31 40 check bool "has manifests" true (List.length manifests > 0); 32 - (* alpine:3.19.4 is multi-arch — at least amd64, arm64, arm/v6, etc. *) 33 41 check bool "multiple manifests" true (List.length manifests >= 7); 34 - (* Check that platform info is present on manifests *) 35 42 let has_amd64 = 36 43 List.exists 37 44 (fun d -> ··· 43 50 manifests 44 51 in 45 52 check bool "has linux/amd64" true has_amd64 53 + | Ok _ -> fail "expected OCI index" 54 + 55 + let test_index_annotations () = 56 + let raw = read_file "index.json" in 57 + match Manifest.of_string raw with 58 + | Error (`Msg e) -> fail e 59 + | Ok (`OCI_index index) -> 60 + let manifests = Index.manifests index in 61 + let first = List.hd manifests in 62 + let annots = Descriptor.annotations first in 63 + check bool "descriptor has annotations" true (List.length annots > 0); 64 + let has_version = 65 + List.exists (fun (k, _) -> k = Annotation.Version) annots 66 + in 67 + check bool "has version annotation" true has_version 68 + | Ok _ -> fail "expected OCI index" 46 69 47 70 let test_index_digest () = 48 - let meta_json = read_file "meta.json" in 49 - let meta = 50 - match Jsont_bytesrw.decode_string Jsont.json_value meta_json with 51 - | Ok j -> j 52 - | Error e -> Fmt.failwith "meta.json: %s" e 53 - in 54 - (* Verify the index digest matches what the registry reported *) 55 - let expected_digest = 56 - match meta with 57 - | Jsont.Object (members, _) -> ( 58 - match List.find_opt (fun (k, _) -> k = "index_digest") members with 59 - | Some (_, Jsont.String (s, _)) -> s 60 - | _ -> 61 - fail "missing index_digest in meta.json"; 62 - "") 63 - | _ -> 64 - fail "meta.json is not an object"; 65 - "" 66 - in 67 - (* Compute digest from the raw index JSON *) 68 - let raw = read_file "index.json" in 69 - let computed = Digest.hash SHA256 raw in 70 - (* The registry may have formatted differently, so we compare parse results 71 - rather than raw bytes. Just verify we can parse the expected digest. *) 72 - match Digest.of_string expected_digest with 71 + let expected = meta_field "index_digest" in 72 + match Digest.of_string expected with 73 73 | Ok d -> 74 74 check string "algorithm" "sha256" 75 75 (Digest.string_of_algorithm (Digest.algorithm d)); 76 76 check bool "non-empty hash" true 77 - (String.length (Digest.encoded_hash d) > 0); 78 - check bool "computed non-empty" true 79 - (String.length (Digest.encoded_hash computed) > 0) 77 + (String.length (Digest.encoded_hash d) > 0) 80 78 | Error (`Msg e) -> fail (Fmt.str "invalid digest: %s" e) 81 79 82 80 (* {1 Manifest parsing} *) ··· 85 83 let raw = read_file "manifest.json" in 86 84 match Manifest.of_string raw with 87 85 | Error (`Msg e) -> fail e 88 - | Ok manifest -> ( 89 - match manifest with 90 - | `OCI_manifest m -> 91 - let layers = Manifest.OCI.layers m in 92 - check bool "has layers" true (List.length layers > 0); 93 - let config = Manifest.OCI.config m in 94 - let config_mt = Descriptor.media_type config in 95 - check string "config media type" 96 - "application/vnd.oci.image.config.v1+json" 97 - (Media_type.to_string config_mt) 98 - | `Docker_manifest m -> 99 - let layers = Manifest.Docker.layers m in 100 - check bool "has layers" true (List.length layers > 0); 101 - let config = Manifest.Docker.config m in 102 - let config_mt = Descriptor.media_type config in 103 - check string "config media type" 104 - "application/vnd.docker.container.image.v1+json" 105 - (Media_type.to_string config_mt) 106 - | _ -> fail "expected single-platform manifest") 86 + | Ok (`OCI_manifest m) -> 87 + let layers = Manifest.OCI.layers m in 88 + check bool "has layers" true (List.length layers > 0); 89 + let config = Manifest.OCI.config m in 90 + let config_mt = Descriptor.media_type config in 91 + check string "config media type" 92 + "application/vnd.oci.image.config.v1+json" 93 + (Media_type.to_string config_mt) 94 + | Ok (`Docker_manifest m) -> 95 + let layers = Manifest.Docker.layers m in 96 + check bool "has layers" true (List.length layers > 0); 97 + let config = Manifest.Docker.config m in 98 + let config_mt = Descriptor.media_type config in 99 + check string "config media type" 100 + "application/vnd.docker.container.image.v1+json" 101 + (Media_type.to_string config_mt) 102 + | Ok _ -> fail "expected single-platform manifest" 107 103 108 104 let test_manifest_config_digest () = 109 - let meta_json = read_file "meta.json" in 110 - let expected = 111 - match Jsont_bytesrw.decode_string Jsont.json_value meta_json with 112 - | Ok (Jsont.Object (members, _)) -> ( 113 - match List.find_opt (fun (k, _) -> k = "config_digest") members with 114 - | Some (_, Jsont.String (s, _)) -> s 115 - | _ -> "") 116 - | _ -> "" 117 - in 105 + let expected = meta_field "config_digest" in 118 106 let raw = read_file "manifest.json" in 119 107 match Manifest.of_string raw with 120 108 | Error (`Msg e) -> fail e 121 - | Ok manifest -> 122 - let config_desc = 123 - match manifest with 124 - | `OCI_manifest m -> Manifest.OCI.config m 125 - | `Docker_manifest m -> Manifest.Docker.config m 126 - | _ -> 127 - fail "expected single-platform manifest"; 128 - Descriptor.empty 129 - in 130 - let digest = Descriptor.digest config_desc in 109 + | Ok (`OCI_manifest m) -> 110 + let digest = Descriptor.digest (Manifest.OCI.config m) in 131 111 check string "config digest" expected (Digest.to_string digest) 112 + | Ok (`Docker_manifest m) -> 113 + let digest = Descriptor.digest (Manifest.Docker.config m) in 114 + check string "config digest" expected (Digest.to_string digest) 115 + | Ok _ -> fail "expected single-platform manifest" 132 116 133 117 (* {1 Config parsing} *) 134 118 135 119 let test_parse_config () = 136 120 let raw = read_file "config.json" in 137 - match Config.of_string raw with 121 + match Config.OCI.of_string raw with 138 122 | Error (`Msg e) -> fail e 139 123 | Ok config -> 140 - let platform = Config.platform config in 124 + let platform = Config.OCI.platform config in 141 125 check string "os" "linux" (OS.to_string (Platform.os platform)); 142 126 check string "arch" "amd64" (Arch.to_string (Platform.arch platform)) 143 127 144 128 (* {1 Round-trip} *) 145 129 146 130 let test_index_roundtrip () = 147 - let json = read_json "index.json" in 148 - match Index.of_yojson json with 149 - | Error e -> fail e 150 - | Ok index -> ( 131 + let raw = read_file "index.json" in 132 + match Manifest.of_string raw with 133 + | Error (`Msg e) -> fail e 134 + | Ok (`OCI_index index) -> ( 151 135 let serialized = Index.to_string index in 152 - (* Parse back and verify key properties are preserved *) 153 136 match Manifest.of_string serialized with 154 137 | Error (`Msg e) -> fail (Fmt.str "round-trip parse failed: %s" e) 155 138 | Ok (`OCI_index index2) -> ··· 157 140 (List.length (Index.manifests index)) 158 141 (List.length (Index.manifests index2)) 159 142 | Ok _ -> fail "round-trip changed manifest type") 143 + | Ok _ -> fail "expected OCI index" 160 144 161 145 let test_manifest_roundtrip () = 162 146 let raw = read_file "manifest.json" in ··· 176 160 let test_index_v () = 177 161 let desc = 178 162 Descriptor.v 179 - ~platform:(Platform.v Arch.Amd64 OS.Linux) 163 + ~platform:(Platform.v Arch.Xamd64 OS.Linux) 180 164 ~annotations: 181 165 [ 182 166 (Annotation.Other "dev.spaceos.partition.name", "p0"); ··· 193 177 [ desc ] 194 178 in 195 179 let json = Index.to_string index in 196 - (* Parse it back *) 197 180 match Manifest.of_string json with 198 181 | Error (`Msg e) -> fail (Fmt.str "Index.v round-trip failed: %s" e) 199 182 | Ok (`OCI_index index2) -> ··· 204 187 in 205 188 check (option string) "kernel annotation" (Some "linuxkit/kernel:6.6") 206 189 kernel; 207 - (* Check descriptor annotations survived *) 208 190 let desc2 = List.hd (Index.manifests index2) in 209 191 let desc_annots = Descriptor.annotations desc2 in 210 192 let part_name = ··· 222 204 ( "index", 223 205 [ 224 206 test_case "parse" `Quick test_parse_index; 207 + test_case "annotations" `Quick test_index_annotations; 225 208 test_case "digest" `Quick test_index_digest; 226 209 test_case "roundtrip" `Quick test_index_roundtrip; 227 210 test_case "Index.v constructor" `Quick test_index_v;