package passage

  1. Overview
  2. Docs

Source file util.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
open Printf

(** Display and conversion functions for paths and secret names *)
module Show = struct
  let show_path p = Path.project p

  let show_name name = Storage.Secret_name.project name

  let path_of_secret_name name = show_name name |> Path.inject

  let secret_name_of_path path = show_path path |> Storage.Secrets.build_secret_name
end

open Show

let die ?exn fmt =
  ksprintf
    (fun fmt ->
      let r =
        match exn with
        | None -> sprintf "%s" fmt
        | Some exn -> sprintf "%s: %s" fmt (Devkit.Exn.to_string exn)
      in
      failwith r)
    fmt

let verbose_eprintlf ?(verbose = false) fmt = if verbose then Devkit.eprintfn fmt else ksprintf (Fun.const ()) fmt

(** Recipients helper utilities for common patterns *)
module Recipients = struct
  (** Get recipients from secret name and handle "no recipients found" error *)
  let get_recipients_or_die secret_name =
    let recipients = Storage.Secrets.(get_recipients_from_path_exn @@ to_path secret_name) in
    match recipients with
    | [] ->
      die
        {|E: No recipients found (use "passage {create,new} folder/new_secret_name" to use recipients associated with $PASSAGE_IDENTITY instead)|}
        (show_name secret_name)
    | _ -> recipients
end

(** Secret helper utilities for common patterns *)
module Secret = struct
  (** Decrypt and parse a secret in one operation *)
  let decrypt_and_parse ?use_sudo ?(silence_stderr = false) secret_name =
    let plaintext = Storage.Secrets.decrypt_exn ?use_sudo ~silence_stderr secret_name in
    Secret.Validation.parse_exn plaintext

  (** Reconstruct a secret from parsed secret and new comments *)
  let reconstruct_secret ?comments { Secret.kind; text; _ } =
    match kind with
    | Secret.Singleline -> Secret.singleline_from_text_description text (Option.value ~default:"" comments)
    | Secret.Multiline -> Secret.multiline_from_text_description text (Option.value ~default:"" comments)

  (** Check if a secret exists, die with hint about create/new if not *)
  let check_exists_or_die secret_name =
    match Storage.Secrets.secret_exists secret_name with
    | false -> die "E: no such secret: %s.  Use \"new\" or \"create\" for new secrets." (show_name secret_name)
    | true -> ()

  (** Check if a secret exists at path, die with standard error if not *)
  let check_path_exists_or_die secret_name path =
    match Storage.Secrets.secret_exists_at path with
    | false -> die "E: no such secret: %s" (show_name secret_name)
    | true -> ()

  (** Decrypt secret with silent stderr - common pattern *)
  let decrypt_silently ?use_sudo secret_name = Storage.Secrets.decrypt_exn ?use_sudo ~silence_stderr:true secret_name

  (** Common recipient error messages *)
  let die_no_recipients_found path = die "E: no recipients found for %s" (show_path path)

  let die_failed_get_recipients ?exn msg =
    match exn with
    | Some e -> die ~exn:e "E: failed to get recipients"
    | None -> die "E: failed to get recipients: %s" msg

  (** Create a secret with new text but keeping existing secret's comments *)
  let reconstruct_with_new_text ~is_singleline ~new_text ~existing_comments =
    let comments = Option.value ~default:"" existing_comments in
    match is_singleline with
    | true -> Secret.singleline_from_text_description new_text comments
    | false -> Secret.multiline_from_text_description new_text comments
end