summaryrefslogtreecommitdiff
path: root/src/session.ml
blob: f8bc843f220a3c597d2d231941b815b58c644e4e (plain)
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
module CT = Vyos1x.Config_tree
module IC = Vyos1x.Internal.Make(CT)
module CC = Commitd_client.Commit
module CD = Vyos1x.Config_diff
module VT = Vyos1x.Vytree
module RT = Vyos1x.Reference_tree
module D = Directories
module FP = FilePath

exception Session_error of string

type cfg_op =
    | CfgSet of string list * string option * CT.value_behaviour
    | CfgDelete of string list * string	option

type world = {
    mutable running_config: CT.t;
    mutable reference_tree: RT.t;
    vyconf_config: Vyconf_config.t;
    dirs: Directories.t
}

type session_data = {
    proposed_config : CT.t;
    modified: bool;
    conf_mode: bool;
    changeset: cfg_op list;
    client_app: string;
    user: string;
    client_pid: int32;
}

let make world client_app user pid = {
    proposed_config = world.running_config;
    modified = false;
    conf_mode = false;
    changeset = [];
    client_app = client_app;
    user = user;
    client_pid = pid;
}

let string_of_op op =
    match op with
    | CfgSet (path, value, _) ->
        let path_str = Vyos1x.Util.string_of_list path in
        (match value with
         | None -> Printf.sprintf "set %s" path_str
         | Some v -> Printf.sprintf "set %s \"%s\"" path_str v)
    | CfgDelete (path, value) ->
        let path_str = Vyos1x.Util.string_of_list path in
        (match value with
         | None -> Printf.sprintf "delete %s" path_str
         | Some v -> Printf.sprintf "delete %s \"%s\"" path_str v)


let set_modified s =
    if s.modified = true then s
    else {s with modified = true}

let apply_cfg_op w op config =
    let config =
    match op with
    | CfgSet (path, value, value_behaviour) ->
        begin
        try
            CT.set config path value value_behaviour |>
            (fun c -> RT.set_tag_data w.reference_tree c path) |>
            (fun c -> RT.set_leaf_data w.reference_tree c path)
        with
        | CT.Useless_set | CT.Duplicate_value -> config
        end
    | CfgDelete (path, value) -> 
        begin
        try
            CT.delete config path value |>
            (fun c -> CT.prune_delete c path)
        with
        | VT.Nonexistent_path | CT.No_such_value -> config
        end
    in config

let rec apply_changes w changeset config =
    match changeset with
    | [] -> config
    | c :: cs -> apply_changes w cs (apply_cfg_op w c config)

let validate w _s path =
    try
        RT.validate_path D.(w.dirs.validators) w.reference_tree path
    with RT.Validation_error x -> raise (Session_error x)

let validate_tree w t =
    let out = RT.validate_tree D.(w.dirs.validators) w.reference_tree t in
    match out with
    | "" -> ()
    | _ -> raise (Session_error out)

let split_path w path =
    RT.split_path w.reference_tree path

let get_proposed_config w s =
    let c = w.running_config in
    apply_changes w (List.rev s.changeset) c

let update_set w changeset path =
    let path, value = split_path w path in
    let refpath = RT.refpath w.reference_tree path in
    let value_behaviour = if RT.is_multi w.reference_tree refpath then CT.AddValue else CT.ReplaceValue in
    let op = CfgSet (path, value, value_behaviour) in
    (op :: changeset)

let update_delete w changeset path =
    let path, value = split_path w path in
    let op = CfgDelete (path, value) in
    (op :: changeset)

let get_changeset w lt rt =
    let diff = CD.diff_tree [] lt rt in
    let add_tree = CT.get_subtree diff ["add"] in
    let del_tree = CT.get_subtree diff ["del"] in
    let add_changeset =
        List.fold_left (update_set w) [] (CT.value_paths_of_tree add_tree)
    in
    let del_changeset =
        List.fold_left (update_delete w) [] (CT.value_paths_of_tree del_tree)
    in
    add_changeset @ del_changeset

let set w s path =
    let _ = validate w s path in
    let changeset' = update_set w s.changeset path in
    { s with changeset = changeset' }

let delete w s path =
    let changeset' = update_delete w s.changeset path in
    { s with changeset = changeset' }

let discard _w s =
    { s with changeset = []; }

let session_changed w s =
    (* structural equality test requires consistent ordering, which is
     * practised, but may be unreliable; test actual difference
     *)
    let c = get_proposed_config w s in
    let diff = CD.diff_tree [] w.running_config c in
    let add_tree = CT.get_subtree diff ["add"] in
    let del_tree = CT.get_subtree diff ["del"] in
    (del_tree <> CT.default) || (add_tree <> CT.default)

let load w s file cached =
    let ct =
        if cached then
            try
                Ok (IC.read_internal file)
            with Vyos1x.Internal.Read_error e ->
                Error e
        else
            Vyos1x.Config_file.load_config file
    in
    match ct with
    | Error e -> raise (Session_error (Printf.sprintf "Error loading config: %s" e))
    | Ok config ->
        validate_tree w config;
        { s with changeset = get_changeset w w.running_config config; }

let merge w s file destructive =
    let ct = Vyos1x.Config_file.load_config file in
    match ct with
    | Error e -> raise (Session_error (Printf.sprintf "Error loading config: %s" e))
    | Ok config ->
        let () = validate_tree w config in
        let proposed = get_proposed_config w s in
        let merged = CD.tree_merge ~destructive:destructive proposed config
        in
        { s with changeset = get_changeset w w.running_config merged; }

let save w s file =
    let ct = w.running_config in
    let res = Vyos1x.Config_file.save_config ct file in
    match res with
    | Error e -> raise (Session_error (Printf.sprintf "Error saving config: %s" e))
    | Ok () -> s

let prepare_commit ?(dry_run=false) w config id =
    let at = w.running_config in
    let rt = w.reference_tree in
    let vc = w.vyconf_config in
    let () =
        try
            IC.write_internal at (FP.concat vc.session_dir vc.running_cache)
        with
            Vyos1x.Internal.Write_error msg -> raise (Session_error msg)
    in
    let () =
        try
            IC.write_internal config (FP.concat vc.session_dir vc.session_cache)
        with
            Vyos1x.Internal.Write_error msg -> raise (Session_error msg)
    in
    CC.make_commit_data ~dry_run:dry_run rt at config id

let get_config w s id =
    let at = w.running_config in
    let wt = get_proposed_config w s in
    let vc = w.vyconf_config in
    let running_cache = Printf.sprintf "%s_%s" vc.running_cache id in
    let session_cache = Printf.sprintf "%s_%s" vc.session_cache id in
    let () =
        try
            IC.write_internal at (FP.concat vc.session_dir running_cache)
        with
            Vyos1x.Internal.Write_error msg -> raise (Session_error msg)
    in
    let () =
        try
            IC.write_internal wt (FP.concat vc.session_dir session_cache)
        with
            Vyos1x.Internal.Write_error msg -> raise (Session_error msg)
    in id

let cleanup_config w id =
    let remove_file file =
        if Sys.file_exists file then
            Sys.remove file
    in
    let vc = w.vyconf_config in
    let running_cache = Printf.sprintf "%s_%s" vc.running_cache id in
    let session_cache = Printf.sprintf "%s_%s" vc.session_cache id in
    remove_file (FP.concat vc.session_dir running_cache);
    remove_file (FP.concat vc.session_dir session_cache)

let get_value w s path =
    let c = get_proposed_config w s in
    if not (VT.exists c path) then
        raise (Session_error ("Config path does not exist"))
    else let refpath = RT.refpath w.reference_tree path in
    if not (RT.is_leaf w.reference_tree refpath) then
        raise (Session_error "Cannot get a value of a non-leaf node")
    else if (RT.is_multi w.reference_tree refpath) then
        raise (Session_error "This node can have more than one value")
    else if (RT.is_valueless w.reference_tree refpath) then
        raise (Session_error "This node can have more than one value")
    else CT.get_value c path

let get_values w s path =
    let c = get_proposed_config w s in
    if not (VT.exists c path) then
        raise (Session_error ("Config path does not exist"))
    else let refpath = RT.refpath w.reference_tree path in
    if not (RT.is_leaf w.reference_tree refpath) then
        raise (Session_error "Cannot get a value of a non-leaf node")
    else if not (RT.is_multi w.reference_tree refpath) then
        raise (Session_error "This node can have only one value")
    else CT.get_values c path

let list_children w s path =
    let c = get_proposed_config w s in
    if not (VT.exists c path) then
        raise (Session_error ("Config path does not exist"))
    else let refpath = RT.refpath w.reference_tree path in
    if (RT.is_leaf w.reference_tree refpath) then
        raise (Session_error "Cannot list children of a leaf node")
    else VT.children_of_path c path

let exists w s path =
    let c = get_proposed_config w s in
    VT.exists c path

let show_config w s path fmt =
    let open Vyconf_connect.Vyconf_pbt in
    let c = get_proposed_config w s in
    if (path <> []) && not (VT.exists c path) then
        raise (Session_error ("Path does not exist")) 
    else
        let node = c in
        match fmt with
        | Curly -> CT.render_at_level node path
        | Json ->
            let node =
                (match path with [] -> c |
                                 _ as ps -> VT.get c ps) in
            CT.to_yojson node |> Yojson.Safe.pretty_to_string