1 import Base 2 import ./json.bend as J 3 import ./core.bend as C 4 5 # Finalizer 6 # ========= 7 # 8 # kube-runtime's finalizer helper, decided purely. An object a controller 9 # cleans up after carries the controller's finalizer: 10 # 11 # - live, without it: add it (and reconcile on the change that follows); 12 # - live, with it: apply; 13 # - deleting, with it: clean up, then remove it so deletion proceeds; 14 # - deleting, without it: nothing left to do. 15 # 16 # Every patch is a JSON patch that first tests what it read, so it fails 17 # (and the reconcile retries) instead of clobbering another controller's 18 # finalizer, as kube-runtime's does. 19 20 type FState is Data: 21 FApply{} 22 FAdd{patch: J.Json} 23 FCleanup{patch: J.Json} 24 FDone{} 25 26 def Fin.op(op: String, path: String, v: J.Json) -> J.Json: 27 J.Json.obj([J.KV{"op", J.JStr{op}}, J.KV{"path", J.JStr{path}}, J.KV{"value", v}]) 28 29 def Fin.strs(xs: List<&2, String>) -> List<&2, J.Json>: 30 match xs: 31 case Nil{}: 32 Nil{} 33 case Con{x, t}: 34 J.JStr{x} <> Fin.strs(t) 35 36 def Fin.add_patch.go(fs: List<&2, String>, name: String, empty: Bool) -> J.Json: 37 match empty: 38 case True{}: 39 J.Json.arr([Fin.op("test", "/metadata/finalizers", J.JNull{}), 40 Fin.op("add", "/metadata/finalizers", J.Json.arr([J.JStr{name}]))]) 41 case False{}: 42 J.Json.arr([Fin.op("test", "/metadata/finalizers", J.Json.arr(Fin.strs(fs))), 43 Fin.op("add", "/metadata/finalizers/-", J.JStr{name})]) 44 45 # The patch that adds name to finalizers fs. 46 def Fin.add_patch(+fs: List<&2, String>, name: String) -> J.Json: 47 Fin.add_patch.go(fs, name, List.is_empty(&2, String, fs)) 48 49 # The patch that removes the finalizer at index i, if it is still name. 50 def Fin.remove_patch(+i: U32, name: String) -> J.Json: 51 J.Json.arr([Fin.op("test", "/metadata/finalizers/" ++ U32.show(i), J.JStr{name}), 52 J.Json.obj([J.KV{"op", J.JStr{"remove"}}, J.KV{"path", J.JStr{"/metadata/finalizers/" ++ U32.show(i)}}])]) 53 54 def Fin.index.if(+i: U32, rest: Maybe<&2, U32>, hit: Bool) -> Maybe<&2, U32>: 55 match hit: 56 case True{}: 57 Some{i} 58 case False{}: 59 rest 60 61 # Where name sits among the finalizers, counting from i. 62 def Fin.index(fs: List<&2, String>, +name: String, +i: U32) -> Maybe<&2, U32>: 63 match fs: 64 case Nil{}: 65 None{} 66 case Con{f, t}: 67 Fin.index.if(i, Fin.index(t, name, (i + 1 : U32)), String.eq(f, name)) 68 69 def Fin.state.go(+fs: List<&2, String>, +name: String, at: Maybe<&2, U32>, deleting: Bool) -> 70 FState: 71 match at deleting: 72 case Some{i} False{}: 73 FApply{} 74 case Some{i} True{}: 75 FCleanup{Fin.remove_patch(i, name)} 76 case None{} False{}: 77 FAdd{Fin.add_patch(fs, name)} 78 case None{} True{}: 79 FDone{} 80 81 # What the object needs, for the finalizer called name. 82 def Fin.state(+o: J.Json, +name: String) -> FState: 83 Fin.state.go(C.Obj.finalizers(o), name, Fin.index(C.Obj.finalizers(o), name, 0), 84 C.Obj.is_deleting(o))