Skip to content

Commit 1f6ca39

Browse files
author
Jose Nogueira
committed
move remaining cmds to lib
1 parent 4ff4e64 commit 1f6ca39

File tree

6 files changed

+237
-203
lines changed

6 files changed

+237
-203
lines changed

bin/main.ml

Lines changed: 41 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -15,61 +15,6 @@ type output_mode =
1515

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

18-
module Edit = struct
19-
let new_secret_recipients_notice =
20-
{|
21-
If you are adding a new secret in a new folder, please keep recipients to a minimum and include the following:
22-
- @root
23-
- yourself
24-
- people who help manage the secret, or people who would have access to it anyway
25-
- people who need access to do their job
26-
- servers/clusters that will consume the secret
27-
28-
If the secret is a staging secret, its only recipient should be @everyone.
29-
|}
30-
31-
let show_recipients_notice_if_true cond = if cond then prerr_endline new_secret_recipients_notice
32-
33-
let edit_secret ?(self_fallback = false) ?(verbose = false) secret_name ~allow_retry ~get_updated_secret =
34-
let raw_secret_name = show_name secret_name in
35-
let original_secret =
36-
match Storage.Secrets.secret_exists secret_name with
37-
| false -> None
38-
| true ->
39-
try Devkit.some @@ Storage.Secrets.decrypt_exn secret_name
40-
with exn -> Shell.die ~exn "E: failed to decrypt %s" raw_secret_name
41-
in
42-
try
43-
let updated_secret = get_updated_secret original_secret in
44-
match updated_secret, original_secret with
45-
| Ok updated_secret, Some original_secret when updated_secret = original_secret ->
46-
prerr_endline "I: secret unchanged"
47-
| Error e, _ -> Shell.die "E: %s" e
48-
| Ok updated_secret, _ ->
49-
let secret_path = path_of_secret_name secret_name in
50-
let secret_recipients' = Storage.Secrets.get_recipients_from_path_exn secret_path in
51-
let secret_recipients =
52-
if secret_recipients' = [] && self_fallback then (
53-
let own_recipients = Storage.Secrets.recipients_of_own_id () in
54-
let () = Commands.Recipients.add_recipients_if_none_exists own_recipients secret_path in
55-
Storage.Secrets.get_recipients_from_path_exn secret_path)
56-
else secret_recipients'
57-
in
58-
if secret_recipients = [] then Exn.fail "E: no recipients specified for this secret"
59-
else (
60-
let is_first_secret_in_new_folder = Option.is_none original_secret && secret_recipients' = [] in
61-
match allow_retry with
62-
| true ->
63-
show_recipients_notice_if_true is_first_secret_in_new_folder;
64-
Retry.encrypt_with_retry ~plaintext:updated_secret ~secret_name secret_recipients
65-
| false ->
66-
try
67-
show_recipients_notice_if_true is_first_secret_in_new_folder;
68-
Storage.Secrets.encrypt_exn ~verbose ~plaintext:updated_secret ~secret_name secret_recipients
69-
with exn -> Exn.fail ~exn "E: encrypting %s failed" raw_secret_name)
70-
with Failure s -> Shell.die "%s" s
71-
end
72-
7318
module Converters = struct
7419
let secret_arg =
7520
let parse secret_name = try Ok (Storage.Secrets.build_secret_name secret_name) with Failure s -> Error (`Msg s) in
@@ -179,43 +124,24 @@ module Rm_who = struct
179124
end
180125

181126
module Create = struct
182-
let create_new_secret_from_stdin verbose secret_name comment_opt =
183-
let create_new_secret verbose secret_name =
184-
Edit.edit_secret secret_name ~verbose ~self_fallback:true ~allow_retry:false ~get_updated_secret:(fun _ ->
185-
let () = input_help_if_user_input () in
186-
match get_valid_input_from_stdin_exn () with
187-
| Error e -> Shell.die "E: %s" e
188-
| Ok plaintext_secret ->
189-
let parsed_secret = Secret.Validation.parse_exn plaintext_secret in
190-
let comment =
191-
match comment_opt, parsed_secret.comments with
192-
| Some comment, None | None, Some comment -> comment
193-
| None, None -> ""
194-
| Some _, Some _ ->
195-
Shell.die
196-
"E: secret text already contains comments. Either use the secret text with comments or use the \
197-
--comment flag."
198-
in
199-
(match Validation.validate_comments comment with
200-
| Error e -> Shell.die "E: invalid comment format: %s" e
201-
| Ok c -> Ok (reconstruct_secret ~comments:(Some c) parsed_secret)))
202-
in
203-
match Storage.Secrets.secret_exists secret_name with
204-
| true -> Shell.die "E: refusing to create: a secret by that name already exists"
205-
| false ->
127+
let create_new_secret_from_stdin ~comments secret_name =
206128
try
207-
let path = Storage.Secrets.(to_path secret_name) in
208-
Invariant.die_if_invariant_fails ~op_string:"create" path;
209-
create_new_secret verbose secret_name
129+
match get_valid_input_from_stdin_exn () with
130+
| Error e -> Shell.die "E: %s" e
131+
| Ok plaintext_secret -> Commands.Create.add ~comments secret_name plaintext_secret
210132
with Failure s -> Shell.die "%s" s
211-
212133
let create =
213134
let doc =
214135
{| creates a new secret from stdin. If the folder doesn't have recipients specified already,
215136
tries to set them to the ones associated with \${PASSAGE_IDENTITY} |}
216137
in
217138
let info = Cmd.info "create" ~doc in
218-
let term = Term.(const (create_new_secret_from_stdin false) $ Flags.secret_name $ Flags.comment) in
139+
let term =
140+
Term.(
141+
const (fun secret_name comments -> create_new_secret_from_stdin ~comments secret_name)
142+
$ Flags.secret_name
143+
$ Flags.comment)
144+
in
219145
Cmd.v info term
220146
end
221147

@@ -226,7 +152,8 @@ module Edit_cmd = struct
226152
Invariant.run_if_recipient ~op_string:"edit secret"
227153
~path:Storage.Secrets.(to_path secret_name)
228154
~f:(fun () ->
229-
Edit.edit_secret secret_name ~allow_retry:true ~get_updated_secret:(fun initial ->
155+
Commands.Edit.edit_secret secret_name ~allow_retry:Retry.encrypt_with_retry
156+
~get_updated_secret:(fun initial ->
230157
let initial_content =
231158
Option.map (fun i -> i ^ Secret.format_explainer) initial
232159
|> Option.value ~default:Secret.format_explainer
@@ -273,7 +200,7 @@ module Edit_comments = struct
273200
| None, true -> prerr_endline "I: comments unchanged"
274201
| Some old, false when old = new_comments -> prerr_endline "I: comments unchanged"
275202
| _ ->
276-
let updated_secret = reconstruct_secret ~comments:(Some new_comments) parsed_secret in
203+
let updated_secret = reconstruct_secret ~comments:new_comments parsed_secret in
277204
let secret_recipients = Util.Recipients.get_recipients_or_die secret_name in
278205
(try Retry.encrypt_with_retry ~plaintext:updated_secret ~secret_name secret_recipients
279206
with exn -> Shell.die ~exn "E: encrypting %s failed" (show_name secret_name)))
@@ -468,7 +395,7 @@ Checking for validity of own secrets. Use -v flag to break down per secret
468395
| Upgrade, SingleLineLegacy ->
469396
(try
470397
let parsed_secret = Secret.Validation.parse_exn secret_text in
471-
let upgraded_secret = reconstruct_secret ~comments:parsed_secret.comments parsed_secret in
398+
let upgraded_secret = reconstruct_secret ?comments:parsed_secret.comments parsed_secret in
472399
let recipients = Util.Recipients.get_recipients_or_die secret_name in
473400
Storage.Secrets.encrypt_exn ~verbose:false ~plaintext:upgraded_secret ~secret_name recipients;
474401
Devkit.eprintfn "I: updated %s" (show_name secret_name);
@@ -515,7 +442,8 @@ module Init = struct
515442
end
516443

517444
module List_ = struct
518-
let list_secrets path = try Commands.List_.list_secrets path with Failure s -> Shell.die "%s" s
445+
let list_secrets path =
446+
try Commands.List_.list_secrets path |> List.iter print_endline with Failure s -> Shell.die "%s" s
519447

520448
let list =
521449
let doc = "recursively list all secrets" in
@@ -531,30 +459,26 @@ module List_ = struct
531459
end
532460

533461
module New = struct
534-
let create_new_secret verbose secret_name =
535-
let () =
536-
Edit.edit_secret ~verbose ~self_fallback:true secret_name ~allow_retry:true ~get_updated_secret:(fun initial ->
537-
let initial_content = Option.value ~default:Secret.format_explainer initial in
538-
Editor.edit_with_validation ~initial:initial_content ~validate:Validation.validate_secret ())
539-
in
540-
let original_recipients = Storage.Secrets.(get_recipients_from_path_exn @@ to_path secret_name) in
541-
Edit.show_recipients_notice_if_true (original_recipients = []);
542-
if yesno "Edit recipients for this secret?" then edit_recipients secret_name
543-
544-
let create_new_secret' secret_name =
545-
if Storage.Secrets.secret_exists secret_name then
546-
Shell.die "E: refusing to create: a secret by that name already exists"
547-
else (
548-
try
549-
let path = Storage.Secrets.(to_path secret_name) in
550-
let () = Invariant.die_if_invariant_fails ~op_string:"create" path in
551-
create_new_secret false secret_name
552-
with Failure s -> Shell.die "%s" s)
462+
let create_new_secret secret_name =
463+
try
464+
let wiz secret_name =
465+
let () =
466+
Commands.Edit.edit_secret ~self_fallback:true secret_name ~allow_retry:Retry.encrypt_with_retry
467+
~get_updated_secret:(fun initial ->
468+
let initial_content = Option.value ~default:Secret.format_explainer initial in
469+
Editor.edit_with_validation ~initial:initial_content ~validate:Validation.validate_secret ())
470+
in
471+
let original_recipients = Storage.Secrets.(get_recipients_from_path_exn @@ to_path secret_name) in
472+
Commands.Edit.show_recipients_notice_if_true (original_recipients = []);
473+
if yesno "Edit recipients for this secret?" then edit_recipients secret_name
474+
in
475+
Commands.Create.bare ~f:wiz secret_name
476+
with Failure s -> Shell.die "%s" s
553477

554478
let new_ =
555479
let doc = "interactive creation of a new single-line secret" in
556480
let info = Cmd.info "new" ~doc in
557-
let term = Term.(const create_new_secret' $ Flags.secret_name) in
481+
let term = Term.(const create_new_secret $ Flags.secret_name) in
558482
Cmd.v info term
559483
end
560484

@@ -594,47 +518,8 @@ end
594518
module Replace = struct
595519
let replace_secret secret_name =
596520
try
597-
let recipients = Util.Recipients.get_recipients_or_die secret_name in
598-
Invariant.run_if_recipient ~op_string:"replace secret"
599-
~path:Storage.Secrets.(to_path secret_name)
600-
~f:(fun () ->
601-
let () = input_help_if_user_input () in
602-
(* We don't need to run validation for the input here since we will be replacing only the secret
603-
and not the whole file *)
604-
let new_secret_plaintext = In_channel.input_all stdin in
605-
if new_secret_plaintext = "" then Shell.die "E: invalid input, empty secrets are not allowed.";
606-
let is_singleline_secret =
607-
(* New secret is single line if doesn't have a newline character or if it has only one,
608-
at the end of the first line. This input isn't supposed to follow the storage format,
609-
it only contains a secret and no comments *)
610-
match String.split_on_char '\n' new_secret_plaintext with
611-
| [ _ ] | [ _; "" ] -> true
612-
| _ -> false
613-
in
614-
let updated_secret =
615-
match Storage.Secrets.secret_exists secret_name with
616-
| false ->
617-
(* if the secret doesn't exist yet, create a new secret with the right format *)
618-
(match is_singleline_secret with
619-
| true -> new_secret_plaintext
620-
| false -> "\n\n" ^ new_secret_plaintext)
621-
| true ->
622-
(* if there is already a secret, recreate or replace it *)
623-
let original_secret =
624-
try
625-
let secret_plaintext = Storage.Secrets.decrypt_exn ~silence_stderr:true secret_name in
626-
Secret.Validation.parse_exn secret_plaintext
627-
with _e ->
628-
Shell.die
629-
"E: unable to parse secret %s's format. If we proceed, the comments will be lost. Aborting. Please \
630-
use the edit command to replace and fix this secret."
631-
(show_name secret_name)
632-
in
633-
reconstruct_with_new_text ~is_singleline:is_singleline_secret ~new_text:new_secret_plaintext
634-
~existing_comments:original_secret.comments
635-
in
636-
try Storage.Secrets.encrypt_exn ~verbose:false ~plaintext:updated_secret ~secret_name recipients
637-
with exn -> Shell.die ~exn "E: encrypting %s failed" (show_name secret_name))
521+
let () = input_help_if_user_input () in
522+
Commands.Replace.replace_secret secret_name (In_channel.input_all stdin)
638523
with Failure s -> Shell.die "%s" s
639524

640525
let replace =
@@ -650,29 +535,11 @@ end
650535
module Replace_comments = struct
651536
let replace_comment secret_name =
652537
try
653-
let secret_name_str = show_name secret_name in
654-
let recipients = Util.Recipients.get_recipients_or_die secret_name in
655-
Invariant.run_if_recipient ~op_string:"replace comments"
656-
~path:Storage.Secrets.(to_path secret_name)
657-
~f:(fun () ->
658-
match Storage.Secrets.secret_exists secret_name with
659-
| false -> Shell.die "E: no such secret: %s" secret_name_str
660-
| true ->
661-
let original_secret =
662-
try decrypt_and_parse ~silence_stderr:true secret_name
663-
with _e ->
664-
Shell.die
665-
"E: unable to parse secret %s's format. Please fix it before replacing the comments,or use the edit \
666-
command"
667-
secret_name_str
668-
in
669-
let new_comments =
670-
get_comments ?initial:original_secret.comments
671-
~help_message:"Please type the new comments and then do Ctrl+d twice to terminate input" ()
672-
in
673-
let updated_secret = reconstruct_secret ~comments:(Some new_comments) original_secret in
674-
(try Storage.Secrets.encrypt_exn ~verbose:false ~plaintext:updated_secret ~secret_name recipients
675-
with exn -> Shell.die ~exn "E: encrypting %s failed" secret_name_str))
538+
let get_new_comments original_comments =
539+
get_comments ?initial:original_comments
540+
~help_message:"Please type the new comments and then do Ctrl+d twice to terminate input" ()
541+
in
542+
Commands.Replace.replace_comment secret_name get_new_comments
676543
with Failure s -> Shell.die "%s" s
677544

678545
let replace_comments =
@@ -688,16 +555,8 @@ module Rm = struct
688555
Arg.(value & flag & info [ "f"; "force" ] ~doc)
689556

690557
let rm_secrets verbose paths force =
691-
let rm_result ~path ~force =
692-
let is_directory = Path.is_directory (Path.abs path) in
693-
let string_path = show_path path in
694-
if force then Storage.Secrets.rm ~is_directory path
695-
else (
696-
match yesno_tty_check (sprintf "Are you sure you want to delete %s?" string_path) with
697-
| NoTTY | TTY true -> Storage.Secrets.rm ~is_directory path
698-
| TTY false -> Storage.Secrets.Skipped)
699-
in
700-
try Commands.Rm.rm_secrets ~verbose ~paths ~force ~f:rm_result with Failure s -> Shell.die "%s" s
558+
let confirm ~path = yesno (sprintf "Are you sure you want to delete %s?" (show_path path)) in
559+
try Commands.Rm.rm_secrets ~verbose ~paths ~force ~confirm () with Failure s -> Shell.die "%s" s
701560

702561
let rm =
703562
let doc = "remove a secret or a folder and its secrets" in

bin/prompt.ml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,18 @@ type prompt_reply =
1010
let is_TTY = Unix.isatty Unix.stdin
1111

1212
let yesno prompt =
13-
let () = Printf.printf "%s [y/N] " prompt in
14-
let () = flush stdout in
15-
let ans = read_line () in
16-
match ans with
13+
let () = Printf.printf "%s [y/N] %!" prompt in
14+
match read_line () with
1715
| "Y" | "y" -> true
1816
| _ -> false
1917

2018
let yesno_tty_check prompt =
2119
match is_TTY with
2220
| false -> NoTTY
2321
| true ->
24-
let () = Printf.printf "%s [y/N] %!" prompt in
25-
(match read_line () with
26-
| "Y" | "y" -> TTY true
27-
| _ -> TTY false)
22+
match yesno prompt with
23+
| true -> TTY true
24+
| false -> TTY false
2825

2926
let input_help_if_user_input ?(msg = "Please type the secret and then do Ctrl+d twice to terminate input") () =
3027
match is_TTY with

0 commit comments

Comments
 (0)