OCI runtime spec types and runc command wrapper
0
fork

Configure Feed

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

WIP: various changes

+126 -93
+1 -1
.ocamlformat
··· 1 - version=0.27.0 1 + version=0.28.1
+1
dune-project
··· 24 24 (fmt (>= 0.9)) 25 25 (logs (>= 0.7)) 26 26 (jsont (>= 0.1)) 27 + (bytesrw (>= 0.1)) 27 28 (alcotest :with-test)))
+1 -1
lib/dune
··· 1 1 (library 2 2 (name runc) 3 3 (public_name runc) 4 - (libraries eio fmt logs jsont)) 4 + (libraries eio fmt logs jsont jsont.bytesrw))
+63 -43
lib/runc.ml
··· 17 17 18 18 let jsont = 19 19 Jsont.Object.map ~kind:"user" (fun uid gid additional_gids -> 20 - { uid; gid; additional_gids }) 20 + { uid; gid; additional_gids = Option.value ~default:[] additional_gids }) 21 21 |> Jsont.Object.mem "uid" Jsont.int ~enc:(fun u -> u.uid) 22 22 |> Jsont.Object.mem "gid" Jsont.int ~enc:(fun u -> u.gid) 23 23 |> Jsont.Object.opt_mem "additionalGids" (Jsont.list Jsont.int) ··· 44 44 in 45 45 v ~typ:"bind" ~source ~options destination 46 46 47 - let proc = v ~typ:"proc" ~source:"proc" ~options:[ "nosuid"; "noexec"; "nodev" ] "/proc" 47 + let proc = 48 + v ~typ:"proc" ~source:"proc" 49 + ~options:[ "nosuid"; "noexec"; "nodev" ] 50 + "/proc" 48 51 49 52 let sysfs = 50 53 v ~typ:"sysfs" ~source:"sysfs" ··· 88 91 89 92 let jsont = 90 93 Jsont.Object.map ~kind:"mount" (fun destination typ source options -> 91 - { destination; typ; source; options }) 94 + { destination; typ; source; options = Option.value ~default:[] options }) 92 95 |> Jsont.Object.mem "destination" Jsont.string ~enc:(fun m -> m.destination) 93 96 |> Jsont.Object.mem "type" Jsont.string ~enc:(fun m -> m.typ) 94 97 |> Jsont.Object.mem "source" Jsont.string ~enc:(fun m -> m.source) 95 98 |> Jsont.Object.opt_mem "options" (Jsont.list Jsont.string) ~enc:(fun m -> 96 - if m.options = [] then None else Some m.options) 99 + if m.options = [] then None else Some m.options) 97 100 |> Jsont.Object.finish 98 101 end 99 102 100 103 module Namespace = struct 101 104 type typ = Pid | Network | Ipc | Uts | Mount | User | Cgroup 102 - 103 105 type t = { typ : typ; path : string option } 104 106 105 107 let v ?path typ = { typ; path } ··· 129 131 let typ_jsont = 130 132 Jsont.string 131 133 |> Jsont.recode ~dec:(fun s -> 132 - match typ_of_string s with Ok t -> t | Error e -> failwith e) 134 + match typ_of_string s with Ok t -> t | Error e -> failwith e) 133 135 |> Jsont.recode ~enc:typ_to_string 134 136 135 137 let jsont = ··· 189 191 190 192 let default = 191 193 let caps = Capability.default in 192 - { bounding = caps; effective = caps; inheritable = caps; permitted = caps; ambient = [] } 194 + { 195 + bounding = caps; 196 + effective = caps; 197 + inheritable = caps; 198 + permitted = caps; 199 + ambient = []; 200 + } 193 201 194 202 let jsont = 195 203 let string_list = Jsont.list Jsont.string in 196 204 Jsont.Object.map ~kind:"capabilities" 197 205 (fun bounding effective inheritable permitted ambient -> 198 - { bounding; effective; inheritable; permitted; ambient }) 206 + { 207 + bounding; 208 + effective; 209 + inheritable; 210 + permitted; 211 + ambient = Option.value ~default:[] ambient; 212 + }) 199 213 |> Jsont.Object.mem "bounding" string_list ~enc:(fun c -> c.bounding) 200 214 |> Jsont.Object.mem "effective" string_list ~enc:(fun c -> c.effective) 201 215 |> Jsont.Object.mem "inheritable" string_list ~enc:(fun c -> c.inheritable) 202 216 |> Jsont.Object.mem "permitted" string_list ~enc:(fun c -> c.permitted) 203 217 |> Jsont.Object.opt_mem "ambient" string_list ~enc:(fun c -> 204 - if c.ambient = [] then None else Some c.ambient) 218 + if c.ambient = [] then None else Some c.ambient) 205 219 |> Jsont.Object.finish 206 220 end 207 221 208 222 module Rlimit = struct 209 223 type typ = Nofile | Nproc | Core | Memlock 210 - 211 224 type t = { typ : typ; hard : int; soft : int } 212 225 213 226 let v ~hard ~soft typ = { typ; hard; soft } ··· 252 265 let env_of_string s = 253 266 match String.index_opt s '=' with 254 267 | None -> (s, "") 255 - | Some i -> (String.sub s 0 i, String.sub s (i + 1) (String.length s - i - 1)) 268 + | Some i -> 269 + (String.sub s 0 i, String.sub s (i + 1) (String.length s - i - 1)) 256 270 257 271 let jsont = 258 272 Jsont.Object.map ~kind:"process" 259 273 (fun terminal user args env cwd capabilities rlimits no_new_privileges -> 260 - { terminal; user; args; env; cwd; capabilities; rlimits; no_new_privileges }) 274 + { 275 + terminal; 276 + user; 277 + args; 278 + env; 279 + cwd; 280 + capabilities; 281 + rlimits; 282 + no_new_privileges; 283 + }) 261 284 |> Jsont.Object.mem "terminal" Jsont.bool ~enc:(fun p -> p.terminal) 262 285 |> Jsont.Object.mem "user" User.jsont ~enc:(fun p -> p.user) 263 286 |> Jsont.Object.mem "args" (Jsont.list Jsont.string) ~enc:(fun p -> p.args) 264 287 |> Jsont.Object.mem "env" 265 288 (Jsont.list 266 - (Jsont.string 267 - |> Jsont.recode ~dec:env_of_string ~enc:env_to_string)) 289 + (Jsont.string |> Jsont.recode ~dec:env_of_string ~enc:env_to_string)) 268 290 ~enc:(fun p -> p.env) 269 291 |> Jsont.Object.mem "cwd" Jsont.string ~enc:(fun p -> p.cwd) 270 - |> Jsont.Object.mem "capabilities" Capabilities.jsont 271 - ~enc:(fun p -> p.capabilities) 272 - |> Jsont.Object.mem "rlimits" (Jsont.list Rlimit.jsont) 273 - ~enc:(fun p -> p.rlimits) 274 - |> Jsont.Object.mem "noNewPrivileges" Jsont.bool 275 - ~enc:(fun p -> p.no_new_privileges) 292 + |> Jsont.Object.mem "capabilities" Capabilities.jsont ~enc:(fun p -> 293 + p.capabilities) 294 + |> Jsont.Object.mem "rlimits" (Jsont.list Rlimit.jsont) ~enc:(fun p -> 295 + p.rlimits) 296 + |> Jsont.Object.mem "noNewPrivileges" Jsont.bool ~enc:(fun p -> 297 + p.no_new_privileges) 276 298 |> Jsont.Object.finish 277 299 end 278 300 ··· 310 332 ] 311 333 312 334 let default_readonly_paths = 313 - [ 314 - "/proc/bus"; 315 - "/proc/fs"; 316 - "/proc/irq"; 317 - "/proc/sys"; 318 - "/proc/sysrq-trigger"; 319 - ] 335 + [ "/proc/bus"; "/proc/fs"; "/proc/irq"; "/proc/sys"; "/proc/sysrq-trigger" ] 320 336 321 - let v ?(namespaces = Namespace.default) 322 - ?(masked_paths = default_masked_paths) 337 + let v ?(namespaces = Namespace.default) ?(masked_paths = default_masked_paths) 323 338 ?(readonly_paths = default_readonly_paths) () = 324 339 { namespaces; masked_paths; readonly_paths } 325 340 ··· 327 342 Jsont.Object.map ~kind:"linux" 328 343 (fun namespaces masked_paths readonly_paths -> 329 344 { namespaces; masked_paths; readonly_paths }) 330 - |> Jsont.Object.mem "namespaces" (Jsont.list Namespace.jsont) 331 - ~enc:(fun l -> l.namespaces) 332 - |> Jsont.Object.mem "maskedPaths" (Jsont.list Jsont.string) 333 - ~enc:(fun l -> l.masked_paths) 334 - |> Jsont.Object.mem "readonlyPaths" (Jsont.list Jsont.string) 335 - ~enc:(fun l -> l.readonly_paths) 345 + |> Jsont.Object.mem "namespaces" (Jsont.list Namespace.jsont) ~enc:(fun l -> 346 + l.namespaces) 347 + |> Jsont.Object.mem "maskedPaths" (Jsont.list Jsont.string) ~enc:(fun l -> 348 + l.masked_paths) 349 + |> Jsont.Object.mem "readonlyPaths" (Jsont.list Jsont.string) ~enc:(fun l -> 350 + l.readonly_paths) 336 351 |> Jsont.Object.finish 337 352 end 338 353 ··· 346 361 linux : Linux.t; 347 362 } 348 363 349 - let v ?(oci_version = "1.0.2") ?(hostname = "container") ?(mounts = Mount.default) 350 - ?(linux = Linux.v ()) ~process ~root () = 364 + let v ?(oci_version = "1.0.2") ?(hostname = "container") 365 + ?(mounts = Mount.default) ?(linux = Linux.v ()) ~process ~root () = 351 366 { oci_version; process; root; hostname; mounts; linux } 352 367 353 368 let jsont = ··· 358 373 |> Jsont.Object.mem "process" Process.jsont ~enc:(fun c -> c.process) 359 374 |> Jsont.Object.mem "root" Root.jsont ~enc:(fun c -> c.root) 360 375 |> Jsont.Object.mem "hostname" Jsont.string ~enc:(fun c -> c.hostname) 361 - |> Jsont.Object.mem "mounts" (Jsont.list Mount.jsont) ~enc:(fun c -> c.mounts) 376 + |> Jsont.Object.mem "mounts" (Jsont.list Mount.jsont) ~enc:(fun c -> 377 + c.mounts) 362 378 |> Jsont.Object.mem "linux" Linux.jsont ~enc:(fun c -> c.linux) 363 379 |> Jsont.Object.finish 364 380 365 381 let to_json config = 366 - Jsont.encode Jsont.json_string jsont config |> Result.get_ok 382 + match Jsont_bytesrw.encode_string jsont config with 383 + | Ok s -> s 384 + | Error e -> failwith (Jsont.Error.to_string e) 367 385 368 386 let of_json str = 369 - Jsont.decode Jsont.json_string jsont str 387 + match Jsont_bytesrw.decode_string jsont str with 388 + | Ok t -> Ok t 389 + | Error e -> Error (Jsont.Error.to_string e) 370 390 end 371 391 372 392 (** {1 Runc Commands} *) ··· 388 408 let process_mgr = Eio.Stdenv.process_mgr env in 389 409 { sw; process_mgr; state_dir } 390 410 391 - let runc t args = 392 - "runc" :: "--root" :: t.state_dir :: args 411 + let runc t args = "runc" :: "--root" :: t.state_dir :: args 393 412 394 413 let run_runc t args = 395 414 let cmd = runc t args in ··· 417 436 container.status <- `Running 418 437 419 438 let kill t container signal = 420 - Log.info (fun m -> m "Killing container %s with signal %d" container.id signal); 439 + Log.info (fun m -> 440 + m "Killing container %s with signal %d" container.id signal); 421 441 run_runc_result t [ "kill"; container.id; string_of_int signal ] 422 442 423 443 let delete ?(force = false) t container =
-1
lib/runc.mli
··· 54 54 val cgroup : t 55 55 val mqueue : t 56 56 val default : t list 57 - 58 57 val jsont : t Jsont.t 59 58 end 60 59
+1
runc.opam
··· 15 15 "fmt" {>= "0.9"} 16 16 "logs" {>= "0.7"} 17 17 "jsont" {>= "0.1"} 18 + "bytesrw" {>= "0.1"} 18 19 "alcotest" {with-test} 19 20 "odoc" {with-doc} 20 21 ]
+1 -1
test/dune
··· 1 1 (test 2 2 (name test) 3 - (libraries runc alcotest)) 3 + (libraries runc alcotest jsont jsont.bytesrw))
+58 -46
test/test_runc.ml
··· 19 19 20 20 let json_testable = Alcotest.testable Fmt.string String.equal 21 21 22 + let encode_json jsont v = 23 + match Jsont_bytesrw.encode_string jsont v with 24 + | Ok s -> s 25 + | Error e -> failwith (Jsont.Error.to_string e) 26 + 22 27 (** {2 User tests} *) 23 28 24 29 let test_user_root () = 25 30 let user = Runc.User.root in 26 - let json = Jsont.encode Jsont.json_string Runc.User.jsont user |> Result.get_ok in 31 + let json = encode_json Runc.User.jsont user |> Result.get_ok in 27 32 Alcotest.(check bool) "contains uid 0" true (String.length json > 0); 28 - Alcotest.(check bool) "has uid field" true 33 + Alcotest.(check bool) 34 + "has uid field" true 29 35 (is_substring ~affix:"\"uid\"" json) 30 36 31 37 let test_user_custom () = 32 38 let user = Runc.User.v ~uid:1000 ~gid:1000 ~additional_gids:[ 100; 200 ] () in 33 - let json = Jsont.encode Jsont.json_string Runc.User.jsont user |> Result.get_ok in 34 - Alcotest.(check bool) "has uid 1000" true 35 - (is_substring ~affix:"1000" json); 36 - Alcotest.(check bool) "has additional gids" true 39 + let json = encode_json Runc.User.jsont user |> Result.get_ok in 40 + Alcotest.(check bool) "has uid 1000" true (is_substring ~affix:"1000" json); 41 + Alcotest.(check bool) 42 + "has additional gids" true 37 43 (is_substring ~affix:"additionalGids" json) 38 44 39 45 (** {2 Mount tests} *) 40 46 41 47 let test_mount_proc () = 42 - let json = 43 - Jsont.encode Jsont.json_string Runc.Mount.jsont Runc.Mount.proc |> Result.get_ok 44 - in 45 - Alcotest.(check bool) "destination is /proc" true 48 + let json = encode_json Runc.Mount.jsont Runc.Mount.proc in 49 + Alcotest.(check bool) 50 + "destination is /proc" true 46 51 (is_substring ~affix:"/proc" json); 47 - Alcotest.(check bool) "type is proc" true 52 + Alcotest.(check bool) 53 + "type is proc" true 48 54 (is_substring ~affix:"\"type\":\"proc\"" json) 49 55 50 56 let test_mount_bind () = 51 57 let mount = Runc.Mount.bind ~source:"/host/path" "/container/path" in 52 - let json = Jsont.encode Jsont.json_string Runc.Mount.jsont mount |> Result.get_ok in 53 - Alcotest.(check bool) "has bind option" true 54 - (is_substring ~affix:"bind" json) 58 + let json = encode_json Runc.Mount.jsont mount |> Result.get_ok in 59 + Alcotest.(check bool) "has bind option" true (is_substring ~affix:"bind" json) 55 60 56 61 let test_mount_bind_readonly () = 57 - let mount = Runc.Mount.bind ~readonly:true ~source:"/host/path" "/container/path" in 58 - let json = Jsont.encode Jsont.json_string Runc.Mount.jsont mount |> Result.get_ok in 62 + let mount = 63 + Runc.Mount.bind ~readonly:true ~source:"/host/path" "/container/path" 64 + in 65 + let json = encode_json Runc.Mount.jsont mount |> Result.get_ok in 59 66 Alcotest.(check bool) "has ro option" true (is_substring ~affix:"ro" json) 60 67 61 68 let test_mount_default () = ··· 74 81 75 82 let test_namespace_json () = 76 83 let ns = Runc.Namespace.v Runc.Namespace.Pid in 77 - let json = Jsont.encode Jsont.json_string Runc.Namespace.jsont ns |> Result.get_ok in 78 - Alcotest.(check bool) "type is pid" true 84 + let json = encode_json Runc.Namespace.jsont ns |> Result.get_ok in 85 + Alcotest.(check bool) 86 + "type is pid" true 79 87 (is_substring ~affix:"\"type\":\"pid\"" json) 80 88 81 89 let test_namespace_with_path () = 82 90 let ns = Runc.Namespace.v ~path:"/proc/1/ns/net" Runc.Namespace.Network in 83 - let json = Jsont.encode Jsont.json_string Runc.Namespace.jsont ns |> Result.get_ok in 84 - Alcotest.(check bool) "has path" true 91 + let json = encode_json Runc.Namespace.jsont ns |> Result.get_ok in 92 + Alcotest.(check bool) 93 + "has path" true 85 94 (is_substring ~affix:"/proc/1/ns/net" json) 86 95 87 96 (** {2 Capability tests} *) ··· 91 100 Alcotest.(check bool) "has capabilities" true (count > 0) 92 101 93 102 let test_capabilities_default () = 94 - let json = 95 - Jsont.encode Jsont.json_string Runc.Capabilities.jsont Runc.Capabilities.default 96 - |> Result.get_ok 97 - in 98 - Alcotest.(check bool) "has bounding" true 103 + let json = encode_json Runc.Capabilities.jsont Runc.Capabilities.default in 104 + Alcotest.(check bool) 105 + "has bounding" true 99 106 (is_substring ~affix:"bounding" json); 100 - Alcotest.(check bool) "has CAP_CHOWN" true 107 + Alcotest.(check bool) 108 + "has CAP_CHOWN" true 101 109 (is_substring ~affix:"CAP_CHOWN" json) 102 110 103 111 (** {2 Process tests} *) 104 112 105 113 let test_process_simple () = 106 114 let process = Runc.Process.v [ "/bin/echo"; "hello" ] in 107 - let json = 108 - Jsont.encode Jsont.json_string Runc.Process.jsont process |> Result.get_ok 109 - in 115 + let json = encode_json Runc.Process.jsont process in 110 116 Alcotest.(check bool) "has args" true (is_substring ~affix:"args" json); 111 117 Alcotest.(check bool) "has echo" true (is_substring ~affix:"echo" json) 112 118 113 119 let test_process_with_env () = 114 - let process = Runc.Process.v ~env:[ ("PATH", "/bin"); ("HOME", "/root") ] [ "/bin/sh" ] in 115 - let json = 116 - Jsont.encode Jsont.json_string Runc.Process.jsont process |> Result.get_ok 120 + let process = 121 + Runc.Process.v ~env:[ ("PATH", "/bin"); ("HOME", "/root") ] [ "/bin/sh" ] 117 122 in 118 - Alcotest.(check bool) "has PATH env" true 123 + let json = encode_json Runc.Process.jsont process in 124 + Alcotest.(check bool) 125 + "has PATH env" true 119 126 (is_substring ~affix:"PATH=/bin" json); 120 - Alcotest.(check bool) "has HOME env" true 127 + Alcotest.(check bool) 128 + "has HOME env" true 121 129 (is_substring ~affix:"HOME=/root" json) 122 130 123 131 (** {2 Root tests} *) 124 132 125 133 let test_root_simple () = 126 134 let root = Runc.Root.v "rootfs" in 127 - let json = Jsont.encode Jsont.json_string Runc.Root.jsont root |> Result.get_ok in 135 + let json = encode_json Runc.Root.jsont root |> Result.get_ok in 128 136 Alcotest.(check bool) "has path" true (is_substring ~affix:"rootfs" json); 129 - Alcotest.(check bool) "readonly is false" true 137 + Alcotest.(check bool) 138 + "readonly is false" true 130 139 (is_substring ~affix:"\"readonly\":false" json) 131 140 132 141 let test_root_readonly () = 133 142 let root = Runc.Root.v ~readonly:true "rootfs" in 134 - let json = Jsont.encode Jsont.json_string Runc.Root.jsont root |> Result.get_ok in 135 - Alcotest.(check bool) "readonly is true" true 143 + let json = encode_json Runc.Root.jsont root |> Result.get_ok in 144 + Alcotest.(check bool) 145 + "readonly is true" true 136 146 (is_substring ~affix:"\"readonly\":true" json) 137 147 138 148 (** {2 Linux tests} *) 139 149 140 150 let test_linux_default () = 141 151 let linux = Runc.Linux.v () in 142 - let json = Jsont.encode Jsont.json_string Runc.Linux.jsont linux |> Result.get_ok in 143 - Alcotest.(check bool) "has namespaces" true 152 + let json = encode_json Runc.Linux.jsont linux |> Result.get_ok in 153 + Alcotest.(check bool) 154 + "has namespaces" true 144 155 (is_substring ~affix:"namespaces" json); 145 - Alcotest.(check bool) "has masked paths" true 156 + Alcotest.(check bool) 157 + "has masked paths" true 146 158 (is_substring ~affix:"maskedPaths" json) 147 159 148 160 let test_linux_masked_paths () = ··· 156 168 let root = Runc.Root.v "rootfs" in 157 169 let config = Runc.Config.v ~process ~root () in 158 170 let json = Runc.Config.to_json config in 159 - Alcotest.(check bool) "has ociVersion" true 171 + Alcotest.(check bool) 172 + "has ociVersion" true 160 173 (is_substring ~affix:"ociVersion" json); 161 174 Alcotest.(check bool) "has process" true (is_substring ~affix:"process" json); 162 175 Alcotest.(check bool) "has root" true (is_substring ~affix:"root" json) ··· 164 177 let test_config_roundtrip () = 165 178 let process = Runc.Process.v [ "/bin/sh"; "-c"; "echo test" ] in 166 179 let root = Runc.Root.v ~readonly:true "rootfs" in 167 - let config = 168 - Runc.Config.v ~hostname:"test-container" ~process ~root () 169 - in 180 + let config = Runc.Config.v ~hostname:"test-container" ~process ~root () in 170 181 let json = Runc.Config.to_json config in 171 182 match Runc.Config.of_json json with 172 183 | Ok config' -> ··· 200 211 ( "capability", 201 212 [ 202 213 Alcotest.test_case "default" `Quick test_capability_default; 203 - Alcotest.test_case "capabilities default" `Quick test_capabilities_default; 214 + Alcotest.test_case "capabilities default" `Quick 215 + test_capabilities_default; 204 216 ] ); 205 217 ( "process", 206 218 [