-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdump-config.nix
317 lines (306 loc) · 9.41 KB
/
dump-config.nix
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
let
pkgs = import <nixpkgs> { };
lib = pkgs.lib;
currentHostname = builtins.head (builtins.match "([a-zA-Z0-9]+)\n" (builtins.readFile "/etc/hostname"));
flake = (builtins.getFlake (toString ./.));
hostConfig = flake.nixosConfigurations.${currentHostname}.config;
configGood = {
config = {
hostname = "foo";
networking = {
ip = "1.1.1.1";
# port = { dev = "/dev/eth0"; gateway = "1.1.1.1"; };
# firewall = throw "foo";
};
};
};
configBad = {
config = {
hostname = "foo";
networking = {
ip = "1.1.1.1";
firewall = throw "foo";
};
};
pkgs = "some pkgs";
};
configBadList = lib.recursiveUpdate configBad {
config.packages = [
"foo"
"bar"
3
(throw "fizz")
({ buzz = { fizz = "fizz"; buzz = throw "buzz"; }; })
];
};
configBadRec = rec {
config = {
hostname = "foo";
networking = {
ip = "1.1.1.1";
firewall = throw "foo";
hostConfig = config;
foo = someFn "bar";
bar = okFn config.networking.bar;
};
};
pkgs = "some pkgs";
someFn = (str: throw "someFn" "some ${str}");
okFn = (str: "some ${str}");
errInSeen = throw "foo";
};
configEvilRec = rec {
config = {
hostname = "foo";
networking = {
ip = "1.1.1.1";
# firewall = thisVariableDoesNotExist;
hostConfig = config;
};
};
pkgs = "some pkgs";
};
e = { x = throw ""; };
sanitizerFn1 = (config:
lib.mapAttrsRecursive
(path: value:
let
result = builtins.tryEval (builtins.deepSeq value value);
in
(if result.success then result.value else "error")
)
config
);
sanitizerFn2 = (config:
(builtins.tryEval (builtins.deepSeq config config)).success
);
tryEvalRecursive = (e: builtins.tryEval (builtins.deepSeq e e));
# not recursive, but works:
sanitizerFn3 = (config:
lib.mapAttrs
(name: value:
let
result = tryEvalRecursive value;
in
(if result.success then result.value else "error")
)
config
);
# the sanitizers here remove errors from an nix expression, replacing them with error strings. Thus a throwing nix expression becomes evaluatable.
# needs to use shallow tryEval to yield correct results
sanitizingError = (tryEvalResult:
"Error: value of type ${builtins.typeOf tryEvalResult.value} cannot be evaluated due to an error (throw or assert)"
);
sanitize = (value:
let
resultShallow = builtins.tryEval value;
valueType = builtins.typeOf resultShallow.value;
in
(if resultShallow.success then
(if (valueType == "set") then
(sanitizerAttrset value)
else (if (valueType == "list") then
(sanitizerList value)
else
resultShallow.value
))
else
(if (valueType == "set") then
(sanitizerAttrset value)
else (if (valueType == "list") then
(sanitizerList value)
else
(sanitizingError resultShallow)
))
)
);
# leaf can be any type except containers (set, list)
sanitizeLeaf = (leaf: path:
let
resultShallow = builtins.tryEval leaf;
valueType = builtins.typeOf resultShallow.value;
isAttrset = valueType == "set";
hasFunctor = builtins.hasAttr "__functor" resultShallow.value;
in
(if resultShallow.success then
(if (isAttrset && hasFunctor) then
(lib.trace "Error found in ${path2String path}" (sanitizingError resultShallow))
else
resultShallow.value
)
else
(lib.trace "Error found in ${path2String path}" (sanitizingError resultShallow))
)
);
sanitizerAttrset = (config:
lib.mapAttrs
(name: value:
(sanitize value)
)
config
);
# recursively map attributes of recursive attrsets
mapRecAttrsRec = (config: path: visited:
lib.mapAttrs
(name: value: let
type = builtins.typeOf value;
isAttrset = type == "set";
currentPath = path ++ [name];
in
if isAttrset then
mapRecAttrsRec value currentPath (visited ++ [currentPath])
else
"${currentPath}" # terminate recursion (return)
)
config
);
# recursively list all attribute paths of a recursive (infinite) attrset
listRecAttrsRec = (attrset: path:
lib.mapAttrsToList
(name: value: let
currentPath = path ++ [name];
type = builtins.typeOf value;
isAttrset = type == "set";
childrenPaths = (
if isAttrset then
listRecAttrsRec value currentPath
else
[]
);
in (lib.trace (childrenPaths) (
[currentPath]
++ childrenPaths
))
)
attrset
);
# constructor so that i can add checks or documentation if i want to
# TODO rename to mkNode
mkValue = (args:
# mytype: { attrset | leaf | reference}
# value: the value of this node. If mytype==reference, then the referencing path.
{ inherit (args) mytype path value; }
);
trace = (expr: ret: lib.trace (builtins.deepSeq expr expr) ret);
# recursively list all attribute paths of a recursive (infinite) attrset
listRecAttrsRec2 = (attrset: path: seen: let
parent = mkValue {
mytype = "attrset";
inherit path;
value = attrset;
};
# TODO i guess we can also throw in attrsets when it contains a name like "${throw foo}"
getChildren = attrset: lib.mapAttrsToList (name: value:
{ inherit value; path = path ++ [name]; }
) attrset;
children = getChildren attrset;
childPaths = (seen: child: let
childValue = sanitizeLeaf child.value child.path;
type = builtins.typeOf childValue;
isAttrset = type == "set";
isDerivation = lib.isDerivation childValue;
firstSeenFn = (expr:
lib.findFirst (node: (sanitizeLeaf node.value node.path) == expr) null seen
);
firstSeen = firstSeenFn childValue;
isSeen = firstSeen != null;
in
(if isAttrset then
(if isSeen then
[(mkValue {
mytype = "reference";
value = firstSeen.path;
inherit (child) path;
})]
else (if isDerivation then
[(mkValue {
inherit (child) path;
value = "Error: isDerivation TODO (just refer derivation hash here)";
mytype = "leaf";
})]
else
(listRecAttrsRec2 childValue child.path seen)
)
)
else
[(mkValue {
inherit (child) path;
value = childValue;
mytype = "leaf";
})]
)
);
childrenPathsFlat = lib.foldl
(list: child: let
seen' = seen ++ list ++ [parent];
in
(list ++ (childPaths seen' child))
)
[]
children
;
msg = {
inherit path;
};
in
trace msg
([parent] ++ childrenPathsFlat)
);
sanitizerList = (config:
builtins.map
(value:
(sanitize value)
)
config
);
nodes = listRecAttrsRec2 configBadRec [] [];
# nodes = listRecAttrsRec2 hostConfig.networking [] [];
path2String = path: builtins.concatStringsSep "." path;
attrsetUpdates = builtins.map (node:
if node.mytype == "attrset" then
{
# TODO not sure how to create an empty attrset here. If there is already something in the attrset, it is just replaced with an empty one.
path = [ "TODO" ];
update = old: true;
}
else (if node.mytype == "reference" then
# use references to break recursion
{
path = node.path;
update = old: "reference#${path2String node.value}";
}
else
# actual values
{
path = node.path;
update = old: node.value;
}
)
) nodes;
result = lib.updateManyAttrsByPath attrsetUpdates {};
in
result
# nodes
# sanitizerFn1 nixosConfig
# (sanitizerAttrsetRec configBadRec).config.hostname
# (mapRecAttrsRec configGood [] [])
# sanitizerAttrset hostConfig.networking
# sanitizerAttrset configBad
# listRecAttrsRec2 configGood [] []
# listRecAttrsRec2 configGood [] []
# builtins.toJSON configBadRec
# This currently doesnt work on recursive configurations:
# works: nix-repl> :p rec { a = { b = 1; c= a; }; }
# but we cannot work on it without infinite recursion: :p lib.mapAttrsRecursive (name: value: if name == "b" then 2 else value) rec { a = { b = 1; c= a; }; }
# we could just set b, that requires knowledge of the name "b" at evaluation time.
# Instead we want to (1) filter for values matching a criterion (throws)
# and then (2) apply a map operation on them
#
# Simplification:
# Given a recursive attrset, we want to find all attrs, that fulfill a condition (say `value == "needle"`)
# `:p` in `nix repl` does this, but i need it as a nix function.
# `lib.mapAttrsRecursiveCond` has no path/key-name available to `cond`. Hence we cannot use it to avoid recursion.
# Should we just build the list of visited paths ourselves?
# Next issue: nixos config is refusing to evalute because of internal errors like `attribute missing`.
# Discussed solutions: https://github.com/NixOS/nix/issues/356
# https://github.com/NixOS/nix/pull/5564 use this patch for a project-custom nix