Write your own plugin

Writing plugins

To create your own primitive operations (such as Docker.build), you’ll probably want to use the Current_cache library. This handles all the details of starting builds, recording the results, managing log files, etc.

A minimal example looks something like this:

# #require "lwt,current,current.cache";;

# open Lwt.Infix;;
# open Current.Syntax;;

# module Frob = struct
    type t = No_context

    module Key = Current.String
    module Value = Current.String

    let id = "frob"

    let build No_context job _key =
      (* Wait in a queue or wait for resources here if needed. *)
      Current.Job.start job ~level:Current.Level.Harmless >>= fun () ->
      (* Do the work here. *)
      Lwt.return @@ Ok "frobbed"

    let pp f key = Fmt.pf f "frob %a" Key.pp key

    let auto_cancel = false
  end;;
...
# module FC = Current_cache.Make(Frob);;
...
# let frob key =
  Current.component "Frob" |>
  let> key = key in
  FC.get Frob.No_context key;;
...

The frob function is the one exposed to users. These functions always start by getting the actual values from their Current.t arguments using let> (and and>, for additional arguments). The Current.component line provides the label for the dot diagrams.

The build function does the real work. It should try to build the output value from the key and return it. If you start any operations that need to be cancelled, use Job.on_cancel to register a callback. job can be used for logging.

If you need extra context that shouldn’t be part of the key (e.g. ssh keys used to connect to a remote machine) then you can use the t type for that, replacing No_context with whatever you require. Note that asking for the same key with two different contexts will not perform two different builds.

The Key module describes the type of the inputs. It needs to provide a digest function for turning keys into unique strings. For short strings, you can just use the string itself (this is what Current.String does). For longer data, you might want to generate a SHA hash. For complex data types, you might want to convert to JSON, etc.

The Value module describes the outputs. You need to provide marshal and unmarshal functions for storing the results on disk. Note that for large values you can write the data elsewhere and just use its identifier as the value. For example, the value returned by Docker.build is just the image’s tag. The image itself is stored by Docker.

auto_cancel says whether OCurrent should cancel the job automatically if the build is no longer needed. For example, if you’re building a Git commit and then the user makes another commit, you can decide whether it would be better to continue with the old build (while also building the new commit), or abort it.

level gives a rough estimate of the cost or risk of the operation. You can use the --confirm option to decide which operations OCurrent will perform automatically.

As well as caching build operations (which take a key and produce a value), you can also cache outputs (which take a key and a value as input). For example, the Docker.tag operation above uses the output cache.

See the Current_cache API for more information.