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: core library builds — tree GADT + lazy nodes, store, backend

+105 -23
+4 -1
bin/main.ml
··· 183 183 let clock = Eio.Stdenv.clock env in 184 184 let client = Oci.Fetch.client ~net ~clock in 185 185 let basename = Filename.basename file in 186 - Oci.push ~client ?username ?password name ~files:[ (basename, file) ] 186 + let desc = 187 + Oci.push ~client ?username ?password name ~files:[ (basename, file) ] 188 + in 189 + Fmt.pr "Pushed: %a@." Oci.Spec.Descriptor.pp desc 187 190 188 191 let push_cmd = 189 192 Cmd.v
+1
src/oci.ml
··· 9 9 10 10 let fetch = Fetch.run 11 11 let push = Push.run 12 + let push_index = Push.run_index 12 13 let list = List.list 13 14 let checkout ?platform = Checkout.run ?platform 14 15 let show = Show.run
+12 -2
src/oci.mli
··· 15 15 ?password:string -> 16 16 Image.t -> 17 17 files:(string * string) list -> 18 - unit 18 + Spec.Descriptor.t 19 19 (** [push ~client image ~files] pushes [files] as OCI artifact layers to the 20 - registry. *) 20 + registry and returns the manifest descriptor. *) 21 + 22 + val push_index : 23 + client:Fetch.client -> 24 + ?username:string -> 25 + ?password:string -> 26 + Image.t -> 27 + Spec.Index.t -> 28 + Spec.Descriptor.t 29 + (** [push_index ~client image index] pushes an OCI image index to the registry 30 + and returns the index descriptor. *) 21 31 22 32 val fetch : 23 33 ?show_progress:bool ->
+43 -12
src/push.ml
··· 60 60 Log.info (fun l -> l "Blob %a already exists, skipping" Digest.pp digest) 61 61 else upload_blob client ~token image ~digest ~content 62 62 63 - let put_manifest (Fetch.Client { net; clock }) ~token image ~tag manifest = 63 + let put_json (Fetch.Client { net; clock }) ~token image ~tag ~media_type_str 64 + json = 64 65 let base = Fmt.str "https://%s" (Image.registry image) in 65 66 let name = Image.repository image in 66 67 let url = Fmt.str "%s/v2/%s/manifests/%s" base name tag in 67 - let manifest_json = Manifest.OCI.to_string manifest in 68 68 Log.debug (fun l -> l "PUT %s" url); 69 69 Eio.Switch.run @@ fun sw -> 70 - let manifest_media_type = "application/vnd.oci.image.manifest.v1+json" in 71 - let mime = Requests.Mime.of_string manifest_media_type in 70 + let mime = Requests.Mime.of_string media_type_str in 72 71 let headers = 73 72 Requests.Headers.( 74 73 empty |> bearer token |> content_type mime 75 - |> content_length (Int64.of_int (String.length manifest_json))) 74 + |> content_length (Int64.of_int (String.length json))) 76 75 in 77 - let body = Requests.Body.of_string mime manifest_json in 76 + let body = Requests.Body.of_string mime json in 78 77 let resp = Requests.One.put ~sw ~clock ~net ~headers ~body url in 79 78 let status = Requests.Response.status_code resp in 80 79 if status <> 201 then 81 - Fmt.failwith "manifest push failed: %d %s" status 82 - (Requests.Response.text resp); 83 - let digest_str = 80 + Fmt.failwith "push failed: %d %s" status (Requests.Response.text resp); 81 + let digest = 84 82 match Requests.Response.header_string "Docker-Content-Digest" resp with 85 - | Some d -> d 86 - | None -> Digest.to_string (Digest.hash SHA256 manifest_json) 83 + | Some d -> ( 84 + match Digest.of_string d with 85 + | Ok d -> d 86 + | Error (`Msg _) -> Digest.hash SHA256 json) 87 + | None -> Digest.hash SHA256 json 88 + in 89 + let size = Int64.of_int (String.length json) in 90 + let media_type = 91 + match Media_type.of_string media_type_str with 92 + | Ok m -> m 93 + | Error (`Msg e) -> Fmt.failwith "media_type: %s" e 87 94 in 88 - Log.info (fun l -> l "Manifest pushed: %s" digest_str) 95 + let desc = Descriptor.v ~media_type ~size digest in 96 + Log.info (fun l -> l "Pushed: %a" Digest.pp digest); 97 + desc 98 + 99 + let put_manifest client ~token image ~tag manifest = 100 + let json = Manifest.OCI.to_string manifest in 101 + put_json client ~token image ~tag 102 + ~media_type_str:"application/vnd.oci.image.manifest.v1+json" json 103 + 104 + let put_index client ~token image ~tag index = 105 + let json = Index.to_string index in 106 + put_json client ~token image ~tag 107 + ~media_type_str:"application/vnd.oci.image.index.v1+json" json 89 108 90 109 let run ~client ?username ?password image ~files = 91 110 let credentials = ··· 139 158 ~config:config_descriptor layer_descriptors 140 159 in 141 160 put_manifest client ~token image ~tag manifest 161 + 162 + let run_index ~client ?username ?password image index = 163 + let credentials = 164 + match (username, password) with 165 + | Some u, Some p -> Some { Fetch.API.username = u; password = p } 166 + | _ -> None 167 + in 168 + let token = 169 + Fetch.API.get_token client ?credentials ~scope_actions:"push,pull" image 170 + in 171 + let tag = match Image.tag image with Some t -> t | None -> "latest" in 172 + put_index client ~token image ~tag index
+16 -4
src/push.mli
··· 1 1 (** Push OCI artifacts to remote container registries. *) 2 2 3 + open Oci_spec 4 + 3 5 val run : 4 6 client:Fetch.client -> 5 7 ?username:string -> 6 8 ?password:string -> 7 9 Image.t -> 8 10 files:(string * string) list -> 9 - unit 11 + Descriptor.t 10 12 (** [run ~client ?username ?password image ~files] pushes [files] as OCI 11 - artifact layers to the registry. Each entry in [files] is [(name, path)] 12 - where [name] is a label and [path] is the local file path. The image 13 - reference must include a tag. *) 13 + artifact layers to the registry and returns the manifest descriptor. Each 14 + entry in [files] is [(name, path)] where [name] is a label and [path] is the 15 + local file path. The image reference must include a tag. *) 16 + 17 + val run_index : 18 + client:Fetch.client -> 19 + ?username:string -> 20 + ?password:string -> 21 + Image.t -> 22 + Index.t -> 23 + Descriptor.t 24 + (** [run_index ~client ?username ?password image index] pushes an OCI image 25 + index to the registry and returns the index descriptor. *)
+4 -2
src/spec/descriptor.ml
··· 55 55 d.artifact_type) 56 56 |> Jsont.Object.finish 57 57 58 - let v ?platform ?data ~media_type ~size digest = 58 + let v ?platform ?data ?(annotations = []) ~media_type ~size digest = 59 59 let data = 60 60 match data with None -> None | Some d -> Some (Base64.encode d) 61 61 in ··· 64 64 size; 65 65 digest; 66 66 urls = []; 67 - annotations = []; 67 + annotations; 68 68 data; 69 69 platform; 70 70 artifact_type = None; 71 71 } 72 + 73 + let annotations t = t.annotations 72 74 73 75 let of_yojson json = 74 76 match Jsont_bytesrw.decode_string jsont (json_to_string json) with
+6 -1
src/spec/descriptor.mli
··· 17 17 val v : 18 18 ?platform:Platform.t -> 19 19 ?data:string -> 20 + ?annotations:(Annotation.t * string) list -> 20 21 media_type:Media_type.t -> 21 22 size:z -> 22 23 Oci_spec__Digest.t -> 23 24 t 24 - (** [v ?platform ?data ~media_type ~size digest] constructs a descriptor. *) 25 + (** [v ?platform ?data ?annotations ~media_type ~size digest] constructs a 26 + descriptor. *) 27 + 28 + val annotations : t -> (Annotation.t * string) list 29 + (** Return the annotations. *) 25 30 26 31 val pp : t Fmt.t 27 32 (** Pretty-print a descriptor. *)
+5
src/spec/index.ml
··· 61 61 62 62 let pp ppf t = pp_json ppf (to_yojson t) 63 63 let to_string = Fmt.to_to_string pp 64 + 65 + let v ?artifact_type ?subject ?(annotations = []) ?platform manifests = 66 + { version = V2; artifact_type; manifests; platform; subject; annotations } 67 + 64 68 let manifests t = t.manifests 65 69 let platform t = t.platform 70 + let annotations t = t.annotations
+13
src/spec/index.mli
··· 18 18 val to_string : t -> string 19 19 (** Serialize an image index to its JSON string representation. *) 20 20 21 + val v : 22 + ?artifact_type:string -> 23 + ?subject:Descriptor.t -> 24 + ?annotations:(Annotation.t * string) list -> 25 + ?platform:Platform.t -> 26 + Descriptor.t list -> 27 + t 28 + (** [v ?artifact_type ?subject ?annotations ?platform manifests] constructs an 29 + OCI image index. *) 30 + 21 31 val manifests : t -> Descriptor.t list 22 32 (** Return the list of manifest descriptors. *) 23 33 24 34 val platform : t -> Platform.t option 25 35 (** Return the optional platform constraint of the index. *) 36 + 37 + val annotations : t -> (Annotation.t * string) list 38 + (** Return the annotations. *)
+1 -1
test/spec/test_oci.ml
··· 44 44 ?password:string -> 45 45 Oci.Image.t -> 46 46 files:(string * string) list -> 47 - unit) 47 + Oci.Spec.Descriptor.t) 48 48 49 49 let test_submodule_reexports () = 50 50 (* Verify all submodules are accessible through Oci *)