swrun.bend
cswap-bend-cli@0.1.0.0 · 210 lines · 37 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/fs.bend as Fs
4import cswap-bend-cli@0.1.0.0/act.bend as Act
5import cswap-bend-cli@0.1.0.0/store.bend as Store
6import cswap-bend-cli@0.1.0.0/swbase.bend as B
7import cswap-bend-cli@0.1.0.0/swcreds.bend as C
8import cswap-bend-cli@0.1.0.0/swlive.bend as L
9import cswap-bend-cli@0.1.0.0/fs/write.bend as FsW
10import cswap-bend-cli@0.1.0.0/store/base.bend as SBase
11import cswap-bend-cli@0.1.0.0/store/io.bend as SIo
12import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
13
14
15
16
17
18def ActM(-A: Type) -> Type:
19 Act.of(A)
20
21def ActM.pure(-A: Type, x: A) -> Act.of(A):
22 Act.pure(A, x)
23
24def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
25 Act.bind(A, B, m, f)
26
27def need(+ok: Bool, +msg: String) -> Act.of(Unit):
28 Act.guard(ok, 4, msg)
29
30def reinstruct(+slot: String) -> String:
31 "Re-add with: cswap --add-account --slot " ++ slot
32
33def oauth_present(+j: Json.Json) -> Bool:
34 match j:
35 case Json.JObj{Json.JField{k, v, t}}:
36 True{}
37 case _:
38 False{}
39
40
41def backup_outgoing(+k: C.CKind, +root: String, +cur: String, +email: String, +creds: String, +cfg_text: String) -> Act.of(Unit):
42 match k:
43 case C.KOwnBytes{}:
44 SIo.write_config(root, cur, email, cfg_text)
45 case C.KWiped{}:
46 SIo.write_config(root, cur, email, cfg_text)
47 case C.KForeignSynced{s}:
48 Act.pure(Unit, Unit{})
49 case C.KForeign{s}:
50 Act.pure(Unit, Unit{})
51 case C.KAlien{}:
52 Act.pure(Unit, Unit{})
53 case _:
54 do ActM<Unit>:
55 SIo.write_cred(root, cur, email, creds)
56 SIo.write_config(root, cur, email, cfg_text)
57
58def salvage(+bad: Bool, +ctx: SBase.Ctx, +ts: String) -> Act.of(Unit):
59 Act.cond(Unit, bad, FsW.rename(SBase.global_config_path(ctx), SBase.global_config_path(ctx) ++ ".corrupt-" ++ ts), Act.pure(Unit, Unit{}))
60
61
62
63def new_config(+l: L.Live, +oauth: Json.Json, +tcreds: String, +tcfg: Json.Json) -> Json.Json:
64 Store.sel.j(L.lv_cfg_ok(l), B.activate_config(L.lv_cfg(l), oauth, tcreds), tcfg)
65
66def live_salvage(+ctx: SBase.Ctx, +ts: String, +l: L.Live) -> Act.of(Unit):
67 salvage(Bool.not(L.lv_cfg_ok(l)) && B.is_some_str(L.lv_cfg_text(l)), ctx, ts)
68
69def activate_files(+ctx: SBase.Ctx, +ts: String, +l: L.Live, +oauth: Json.Json, +tcreds: String, +tcfg: Json.Json) -> Act.of(Unit):
70 do ActM<Unit>:
71 L.write_live_creds(ctx, C.prepare_activation(tcreds, L.lv_creds(l)))
72 live_salvage(ctx, ts, l)
73 L.write_cfg(ctx, new_config(l, oauth, tcreds, tcfg))
74
75def final_steps(+ctx: SBase.Ctx, +root: String, +target: String, +ts: String, +seq: Json.Json, +l: L.Live, +oauth: Json.Json, +tcreds: String, +tcfg: Json.Json) -> Act.of(Unit):
76 do ActM<Unit>:
77 activate_files(ctx, ts, l, oauth, tcreds, tcfg)
78 SIo.write_seq(root, SSeq.stamp(SSeq.set_active(seq, SSeq.str_u32(target)), ts))
79
80def transact(+ctx: SBase.Ctx, +root: String, +ts: String, +seq_text: String, +l: L.Live, steps: Act.of(Unit)) -> Act.of(Unit):
81 IO.bind(Fs.Res(Unit), Fs.Res(Unit), Act.on_fail(Unit, steps, L.restore(ctx, root, L.lv_creds_text(l), L.lv_cfg_text(l), seq_text)), r => IO.pure(Fs.Res(Unit), L.wrap_failure(r)))
82
83def archive_for_a(+root: String, +cts: String, +l: L.Live, +tcreds: String) -> Act.of(Unit):
84 L.archive_if(SBase.nonempty(L.lv_creds(l)) && Bool.not(String.eq(L.lv_creds(l), tcreds)), root, L.lv_email(l), L.lv_creds(l), cts)
85
86def finish_a(+root: String, +target: String, +temail: String, +l: L.Live, +oauth: Json.Json, +cur: Maybe<&2, String>) -> Act.of(Json.Json):
87 do ActM<Json.Json>:
88 SIo.log(root, "INFO", "Activated account " ++ target ++ " (live login " ++ SBase.sel.s(String.is_empty(L.lv_email(l)), "absent", "was " ++ L.lv_email(l)) ++ ")")
89 return B.op_json(B.from_ref(L.lv_email(l), cur), B.ref(Some{target}, temail), [], SSeq.field_str(oauth, "organizationUuid"))
90
91def switch_a(+ctx: SBase.Ctx, +root: String, +target: String, +temail: String, +ts: String, +cts: String, +seq_text: String, +seq: Json.Json, +l: L.Live, +oauth: Json.Json, +tcreds: String, +tcfg: Json.Json, +cur: Maybe<&2, String>) -> Act.of(Json.Json):
92 do ActM<Json.Json>:
93 archive_for_a(root, cts, l, tcreds)
94 transact(ctx, root, ts, seq_text, l, final_steps(ctx, root, target, ts, seq, l, oauth, tcreds, tcfg))
95 finish_a(root, target, temail, l, oauth, cur)
96
97def live_present(+l: L.Live) -> Act.of(Unit):
98 Act.guard(SBase.nonempty(L.lv_creds(l)), 5, "Current account credential is empty (Keychain unreadable?); refusing to overwrite its backup")
99
100def classify_in(+pre: L.Pre, +seq: Json.Json, +cur: String, +l: L.Live, +backup: String, +others: List<&2, C.Other>) -> C.CKind:
101 C.final_kind(C.classify(L.lv_creds(l), backup, others), seq, cur, L.lv_creds(l), others, L.pre_prof(pre, L.lv_creds(l)))
102
103def classify_b(+pre: L.Pre, +root: String, +seq: Json.Json, +cur: String, +l: L.Live) -> Act.of(C.CKind):
104 do ActM<C.CKind>:
105 live_present(l)
106 backup : String <- SIo.read_cred(root, cur, L.lv_email(l))
107 +others : List<&2, C.Other> <- L.other_creds(root, seq, B.slots_of(seq), cur)
108 return classify_in(pre, seq, cur, l, backup, others)
109
110def backup_out(+k: C.CKind, +root: String, +cur: String, +l: L.Live) -> Act.of(Unit):
111 backup_outgoing(k, root, cur, L.lv_email(l), L.lv_creds(l), B.text_or(L.lv_cfg_text(l)))
112
113def save_b(+k: C.CKind, +root: String, +cur: String, +ts: String, +l: L.Live) -> Act.of(Unit):
114 do ActM<Unit>:
115 L.archive_if(C.preserve(k), root, L.lv_email(l), L.lv_creds(l), SBase.compact_ts(ts))
116 backup_out(k, root, cur, l)
117
118def finish_b(+k: C.CKind, +root: String, +target: String, +temail: String, +l: L.Live, +oauth: Json.Json, +cur: String) -> Act.of(Json.Json):
119 do ActM<Json.Json>:
120 SIo.log(root, "INFO", "Switched from account " ++ cur ++ " to " ++ target)
121 return B.op_json(B.from_ref(L.lv_email(l), Some{cur}), B.ref(Some{target}, temail), C.kind_warning(k, cur), SSeq.field_str(oauth, "organizationUuid"))
122
123def switch_b(+pre: L.Pre, +ctx: SBase.Ctx, +root: String, +target: String, +temail: String, +ts: String, +seq_text: String, +seq: Json.Json, +l: L.Live, +oauth: Json.Json, +tcreds: String, +tcfg: Json.Json, +cur: String) -> Act.of(Json.Json):
124 do ActM<Json.Json>:
125 +k : C.CKind <- classify_b(pre, root, seq, cur, l)
126 save_b(k, root, cur, ts, l)
127 transact(ctx, root, ts, seq_text, l, final_steps(ctx, root, target, ts, seq, l, oauth, tcreds, tcfg))
128 finish_b(k, root, target, temail, l, oauth, cur)
129
130def perform5(+case_a: Bool, +pre: L.Pre, +ctx: SBase.Ctx, +root: String, +target: String, +ts: String, +seq_text: String, +seq: Json.Json, +temail: String, +l: L.Live, +tcreds: String, +tcfg: Json.Json, +oauth: Json.Json) -> Act.of(Json.Json):
131 Act.cond(Json.Json, case_a,
132 switch_a(ctx, root, target, temail, ts, SBase.compact_ts(ts), seq_text, seq, l, oauth, tcreds, tcfg, L.lv_slot(l, seq)),
133 switch_b(pre, ctx, root, target, temail, ts, seq_text, seq, l, oauth, tcreds, tcfg, SSeq.str_or(L.lv_slot(l, seq), "")))
134
135def perform4(+ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, +seq: Json.Json, +temail: String, +l: L.Live, +tcreds: String, +tcfg: Json.Json, +oauth: Json.Json) -> Act.of(Json.Json):
136 perform5(force || String.is_empty(L.lv_email(l)) || Bool.not(B.is_some_str(L.lv_slot(l, seq))), pre, ctx, root, target, ts, seq_text, seq, temail, l, tcreds, tcfg, oauth)
137
138def perform7(+ok: Bool, +ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, +seq: Json.Json, +temail: String, +l: L.Live, +tcreds: String, +tcfg: Json.Json) -> Act.of(Json.Json):
139 Act.cond(Json.Json, ok,
140 perform4(ctx, root, target, force, pre, ts, seq_text, seq, temail, l, tcreds, tcfg, SSeq.get_or("oauthAccount", tcfg, Json.JNull{})),
141 Act.fail(Json.Json, 4, "Invalid oauthAccount in backup"))
142
143def perform6(+ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, +seq: Json.Json, +temail: String, +l: L.Live, +tcreds: String, parsed: Result<&2, &2, String, Json.Json>) -> Act.of(Json.Json):
144 match parsed:
145 case Fail{e}:
146 Act.fail(Json.Json, 4, "Invalid backup config: " ++ e)
147 case Done{+tcfg}:
148 perform7(oauth_present(SSeq.get_or("oauthAccount", tcfg, Json.JNull{})), ctx, root, target, force, pre, ts, seq_text, seq, temail, l, tcreds, tcfg)
149
150def tcfg_need(+target: String, +tcfg_text: Maybe<&2, String>) -> Act.of(Unit):
151 need(SBase.nonempty(B.text_or(tcfg_text)), "Account-" ++ target ++ " has no stored config backup. " ++ reinstruct(target))
152
153def perform3b(+ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, +seq: Json.Json, +temail: String, +l: L.Live, +tcreds: String) -> Act.of(Json.Json):
154 do ActM<Json.Json>:
155 +tcfg_text : Maybe<&2, String> <- L.read_text_opt(SBase.config_path(root, target, temail))
156 tcfg_need(target, tcfg_text)
157 perform6(ctx, root, target, force, pre, ts, seq_text, seq, temail, l, tcreds, Json.parse(B.text_or(tcfg_text)))
158
159def perform3(+ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, +seq: Json.Json, +temail: String) -> Act.of(Json.Json):
160 do ActM<Json.Json>:
161 l : L.Live <- L.read_live(ctx)
162 +tcreds : String <- SIo.read_cred(root, target, temail)
163 need(SBase.nonempty(tcreds), "Account-" ++ target ++ " has no stored credentials. " ++ reinstruct(target))
164 perform3b(ctx, root, target, force, pre, ts, seq_text, seq, temail, l, tcreds)
165
166def perform2b(+ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, +seq: Json.Json, +rec: Maybe<&2, Json.Json>) -> Act.of(Json.Json):
167 match rec:
168 case None{}:
169 Act.fail(Json.Json, 3, "Account-" ++ target ++ " does not exist")
170 case Some{r}:
171 perform3(ctx, root, target, force, pre, ts, seq_text, seq, SSeq.field_str(r, "email"))
172
173def perform2(+ctx: SBase.Ctx, +root: String, +target: String, +force: Bool, +pre: L.Pre, +ts: String, +seq_text: String, parsed: Result<&2, &2, String, Json.Json>) -> Act.of(Json.Json):
174 match parsed:
175 case Fail{e}:
176 Act.fail(Json.Json, 1, SBase.seq_path(root) ++ " exists but could not be parsed (" ++ e ++ "). Repair or move it, then retry — refusing to overwrite it unread.")
177 case Done{+seq}:
178 perform2b(ctx, root, target, force, pre, ts, seq_text, seq, SSeq.slot_record(seq, target))
179
180
181def perform_locked(+ctx: SBase.Ctx, +target: String, +force: Bool, +pre: L.Pre, +ts: String) -> Act.of(Json.Json):
182 do ActM<Json.Json>:
183 +seq_text : String <- Fs.read_file(SBase.seq_path(SBase.root_of(ctx)))
184 perform2(ctx, SBase.root_of(ctx), target, force, pre, ts, seq_text, Json.parse(seq_text))
185
186def verify2(+l: L.Live, +target: String, +temail: String, +torg: String) -> Act.of(Unit):
187 Act.guard(String.eq(L.lv_email(l), temail) && String.eq(L.lv_org(l), torg), B.e_overridden(), B.override_msg(target, temail, L.lv_email(l)))
188
189
190
191
192
193
194def verify_live(+ctx: SBase.Ctx, +delay_ms: U32, +target: String, +temail: String, +torg: String) -> Act.of(Unit):
195 do ActM<Unit>:
196 Act.lift(Unit, IO.sleep(delay_ms))
197 l : L.Live <- L.read_live(ctx)
198 verify2(l, target, temail, torg)
199
200def do_switch2(+ctx: SBase.Ctx, +target: String, +force: Bool, +opts: L.Opts, +pre: L.Pre) -> Act.of(Json.Json):
201 do ActM<Json.Json>:
202 ts : String <- SIo.now_iso()
203 +op : Json.Json <- L.with_locks(Json.Json, ctx, perform_locked(ctx, target, force, pre, ts))
204 verify_live(ctx, L.opts_delay(opts), target, SSeq.field_str(SSeq.get_or("to", op, Json.JNull{}), "email"), SSeq.field_str(op, "targetOrg"))
205 return op
206
207def do_switch(+ctx: SBase.Ctx, +target: String, +force: Bool, +opts: L.Opts) -> Act.of(Json.Json):
208 do ActM<Json.Json>:
209 pre : L.Pre <- Act.cond(L.Pre, force, Act.pure(L.Pre, L.no_pre()), L.prefetch(ctx, L.opts_oracle(opts)))
210 do_switch2(ctx, target, force, opts, pre)