@homeflare/alchemy 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/grafana/alert-rule-group-drop-warning.d.ts +25 -0
- package/dist/grafana/alert-rule-group-drop-warning.d.ts.map +1 -0
- package/dist/grafana/alert-rule-group-fixtures.d.ts +73 -0
- package/dist/grafana/alert-rule-group-fixtures.d.ts.map +1 -0
- package/dist/grafana/alert-rule-group-model.d.ts +28 -0
- package/dist/grafana/alert-rule-group-model.d.ts.map +1 -0
- package/dist/grafana/alert-rule-group.d.ts +60 -0
- package/dist/grafana/alert-rule-group.d.ts.map +1 -0
- package/dist/grafana/index.d.ts +5 -4
- package/dist/grafana/index.d.ts.map +1 -1
- package/dist/grafana/index.js +241 -139
- package/dist/grafana/index.js.map +11 -8
- package/dist/grafana/providers.d.ts +1 -1
- package/dist/grafana/providers.d.ts.map +1 -1
- package/dist/proxmox/index.js +349 -287
- package/dist/proxmox/index.js.map +8 -7
- package/dist/proxmox/network-apply-read.d.ts +12 -0
- package/dist/proxmox/network-apply-read.d.ts.map +1 -1
- package/dist/proxmox/network-apply.d.ts +2 -0
- package/dist/proxmox/network-apply.d.ts.map +1 -1
- package/dist/proxmox/node-network-form.d.ts +86 -54
- package/dist/proxmox/node-network-form.d.ts.map +1 -1
- package/dist/proxmox/node-network-wire.d.ts +100 -0
- package/dist/proxmox/node-network-wire.d.ts.map +1 -0
- package/dist/proxmox/node-network.d.ts +21 -106
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/docs/grafana-alerting-rules.md +143 -0
- package/docs/grafana-alerting.md +5 -3
- package/docs/grafana.md +4 -2
- package/docs/upstream-conformance.md +16 -4
- package/package.json +1 -1
- package/src/grafana/alert-rule-group-drop-warning.test.ts +107 -0
- package/src/grafana/alert-rule-group-drop-warning.ts +41 -0
- package/src/grafana/alert-rule-group-fixtures.ts +46 -0
- package/src/grafana/alert-rule-group-model.test.ts +75 -0
- package/src/grafana/alert-rule-group-model.ts +66 -0
- package/src/grafana/alert-rule-group-provenance.test.ts +116 -0
- package/src/grafana/alert-rule-group.test.ts +164 -0
- package/src/grafana/alert-rule-group.ts +231 -0
- package/src/grafana/index.ts +11 -4
- package/src/grafana/providers.ts +2 -0
- package/src/proxmox/constraints-host-forms.test.ts +2 -2
- package/src/proxmox/network-apply-read.ts +12 -0
- package/src/proxmox/network-apply.ts +2 -0
- package/src/proxmox/node-network-form.ts +93 -99
- package/src/proxmox/node-network-read-failure.test.ts +206 -0
- package/src/proxmox/node-network-wire.ts +239 -0
- package/src/proxmox/node-network.ts +155 -167
package/dist/grafana/index.js
CHANGED
|
@@ -1,17 +1,77 @@
|
|
|
1
|
-
// src/grafana/
|
|
1
|
+
// src/grafana/alert-rule-group.ts
|
|
2
|
+
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
2
3
|
import { Resource } from "alchemy";
|
|
3
4
|
import * as Provider from "alchemy/Provider";
|
|
4
5
|
import * as grafana from "@distilled.cloud/grafana";
|
|
6
|
+
import * as Effect5 from "effect/Effect";
|
|
7
|
+
|
|
8
|
+
// src/grafana/alert-rule-group-drop-warning.ts
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
var warnOnDroppedRules = (folderUid, group, declaredUids, liveRules) => Effect.gen(function* () {
|
|
11
|
+
const dropped = (liveRules ?? []).filter((rule) => typeof rule.uid === "string").filter((rule) => !declaredUids.has(rule.uid));
|
|
12
|
+
if (dropped.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
const names = dropped.map((rule) => `${typeof rule.title === "string" ? rule.title : "(untitled)"} (${rule.uid})`).join(", ");
|
|
15
|
+
yield* Effect.logWarning(`Grafana.AlertRuleGroup ${folderUid}/${group}: this update REMOVES ${dropped.length} ` + `rule(s) not in the declaration: ${names}. Whole-group replacement is by design — add ` + `them to the declaration to keep them, or give them their own group.`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// src/grafana/alert-rule-group-model.ts
|
|
19
|
+
var RULE_VOLATILE_FIELDS = ["id", "updated", "provenance"];
|
|
20
|
+
var normalizeRule = (rule, folderUid, group) => {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [key, value] of Object.entries(rule)) {
|
|
23
|
+
if (key === "folderUID" || key === "ruleGroup" || key === "orgID")
|
|
24
|
+
continue;
|
|
25
|
+
if (RULE_VOLATILE_FIELDS.includes(key))
|
|
26
|
+
continue;
|
|
27
|
+
out[key] = value;
|
|
28
|
+
}
|
|
29
|
+
out.folderUID = folderUid;
|
|
30
|
+
out.ruleGroup = group;
|
|
31
|
+
out.orgID = 1;
|
|
32
|
+
return out;
|
|
33
|
+
};
|
|
34
|
+
var toWireRule = (rule, folderUid, group) => ({
|
|
35
|
+
...rule,
|
|
36
|
+
folderUID: folderUid,
|
|
37
|
+
orgID: 1,
|
|
38
|
+
ruleGroup: group
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/grafana/alerting-provenance.ts
|
|
5
42
|
import * as Effect3 from "effect/Effect";
|
|
6
43
|
|
|
44
|
+
// src/grafana/provisioned.ts
|
|
45
|
+
import * as Data from "effect/Data";
|
|
46
|
+
import * as Effect2 from "effect/Effect";
|
|
47
|
+
|
|
48
|
+
class GrafanaProvisionedObjectError extends Data.TaggedError("GrafanaProvisionedObjectError") {
|
|
49
|
+
}
|
|
50
|
+
var refuseIfProvisioned = (kind, uid, owner) => {
|
|
51
|
+
const ownerClause = owner === undefined || owner === "" ? `(this Grafana instance did not report an owning file)` : `by ${owner}`;
|
|
52
|
+
return Effect2.fail(new GrafanaProvisionedObjectError({
|
|
53
|
+
message: `${kind} '${uid}' is file-provisioned ${ownerClause} and read-only through the classic ` + `write API this family calls. Edit the provisioning file instead — declaring this ` + `object read-only (a declaration matching what is live) is safe and will never reach ` + `this refusal.`
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/grafana/alerting-provenance.ts
|
|
58
|
+
var WRITABLE_PROVENANCES = new Set(["", "api"]);
|
|
59
|
+
var isForeignProvenance = (provenance) => provenance === undefined || !WRITABLE_PROVENANCES.has(provenance);
|
|
60
|
+
var refuseIfForeignProvenance = (kind, id, provenance) => {
|
|
61
|
+
const reason = provenance === undefined ? `did not report a provenance at all — this family cannot tell whether it is safe to own` : `has provenance '${provenance}', not '(none)' or 'api'`;
|
|
62
|
+
return Effect3.fail(new GrafanaProvisionedObjectError({
|
|
63
|
+
message: `${kind} '${id}' ${reason} — something other than this API may own it (file ` + `provisioning, the Prometheus-rules importer, or similar), and writing here could take ` + `over or corrupt an object this family does not actually control. Edit it at its actual ` + `source instead — declaring this object read-only (a declaration matching what is live) ` + `is safe and will never reach this refusal.`
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
|
|
7
67
|
// src/grafana/resource.ts
|
|
8
68
|
import { isResolved } from "alchemy/Diff";
|
|
9
|
-
import * as
|
|
69
|
+
import * as Effect4 from "effect/Effect";
|
|
10
70
|
var grafanaOperations = (spec) => {
|
|
11
|
-
const read = (props) => spec.fetchLive(props).pipe(
|
|
71
|
+
const read = (props) => spec.fetchLive(props).pipe(Effect4.map((live) => live === undefined ? undefined : spec.attributes(live, props)));
|
|
12
72
|
return {
|
|
13
73
|
read,
|
|
14
|
-
diff: (news, output) =>
|
|
74
|
+
diff: (news, output) => Effect4.gen(function* () {
|
|
15
75
|
if (output === undefined || !isResolved(news))
|
|
16
76
|
return;
|
|
17
77
|
const live = yield* read(news);
|
|
@@ -21,7 +81,7 @@ var grafanaOperations = (spec) => {
|
|
|
21
81
|
return { action: "noop" };
|
|
22
82
|
return spec.update === undefined ? { action: "replace" } : { action: "update" };
|
|
23
83
|
}),
|
|
24
|
-
reconcile: (news) =>
|
|
84
|
+
reconcile: (news) => Effect4.gen(function* () {
|
|
25
85
|
const before = yield* spec.fetchLive(news);
|
|
26
86
|
if (before === undefined) {
|
|
27
87
|
yield* spec.create(news);
|
|
@@ -33,11 +93,11 @@ var grafanaOperations = (spec) => {
|
|
|
33
93
|
}
|
|
34
94
|
const after = yield* read(news);
|
|
35
95
|
if (after === undefined) {
|
|
36
|
-
return yield*
|
|
96
|
+
return yield* Effect4.die(new Error("grafanaOperations: the write returned no error but the object is still absent. " + "Read back rather than trusting the response status."));
|
|
37
97
|
}
|
|
38
98
|
return after;
|
|
39
99
|
}),
|
|
40
|
-
destroy: (olds) =>
|
|
100
|
+
destroy: (olds) => Effect4.gen(function* () {
|
|
41
101
|
const live = yield* spec.fetchLive(olds);
|
|
42
102
|
if (live === undefined)
|
|
43
103
|
return;
|
|
@@ -48,7 +108,7 @@ var grafanaOperations = (spec) => {
|
|
|
48
108
|
var grafanaHandlers = (spec) => {
|
|
49
109
|
const ops = grafanaOperations(spec);
|
|
50
110
|
return {
|
|
51
|
-
list: () =>
|
|
111
|
+
list: () => Effect4.succeed([]),
|
|
52
112
|
read: ({ olds }) => ops.read(olds),
|
|
53
113
|
diff: ({ news, output }) => ops.diff(news, output),
|
|
54
114
|
reconcile: ({ news }) => ops.reconcile(news),
|
|
@@ -56,20 +116,100 @@ var grafanaHandlers = (spec) => {
|
|
|
56
116
|
};
|
|
57
117
|
};
|
|
58
118
|
|
|
119
|
+
// src/grafana/subset-match.ts
|
|
120
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
121
|
+
var declaredContentMatches = (declared, live) => {
|
|
122
|
+
if (declared == null)
|
|
123
|
+
return true;
|
|
124
|
+
if (Array.isArray(declared)) {
|
|
125
|
+
return Array.isArray(live) && declared.length === live.length && declared.every((item, index) => declaredContentMatches(item, live[index]));
|
|
126
|
+
}
|
|
127
|
+
if (isPlainObject(declared)) {
|
|
128
|
+
return isPlainObject(live) && Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]));
|
|
129
|
+
}
|
|
130
|
+
return declared === live;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/grafana/alert-rule-group.ts
|
|
134
|
+
var GrafanaAlertRuleGroup = Resource("Grafana.AlertRuleGroup", {
|
|
135
|
+
defaultRemovalPolicy: "retain"
|
|
136
|
+
});
|
|
137
|
+
var findForeignRule = (rules) => (rules ?? []).find((rule) => isForeignProvenance(rule.provenance));
|
|
138
|
+
var body = (props) => ({
|
|
139
|
+
folderUid: props.folderUid,
|
|
140
|
+
interval: props.interval,
|
|
141
|
+
rules: props.rules.map((rule) => toWireRule(rule, props.folderUid, props.group)),
|
|
142
|
+
title: props.group
|
|
143
|
+
});
|
|
144
|
+
var refuseForForeignRule = (props, foreign) => refuseIfForeignProvenance("Grafana.AlertRuleGroup", `${props.folderUid}/${props.group}`, foreign.provenance);
|
|
145
|
+
var declaredUids = (props) => new Set(props.rules.map((rule) => rule.uid));
|
|
146
|
+
var spec = {
|
|
147
|
+
attributes: (live, props) => ({
|
|
148
|
+
folderUid: live.folderUid ?? props.folderUid,
|
|
149
|
+
group: props.group,
|
|
150
|
+
interval: live.interval ?? 0,
|
|
151
|
+
rules: (live.rules ?? []).map((rule) => normalizeRule(rule, props.folderUid, props.group))
|
|
152
|
+
}),
|
|
153
|
+
create: (props) => grafana.routePutAlertRuleGroup({
|
|
154
|
+
FolderUID: props.folderUid,
|
|
155
|
+
Group: props.group,
|
|
156
|
+
...body(props)
|
|
157
|
+
}),
|
|
158
|
+
destroy: (props, live) => Effect5.gen(function* () {
|
|
159
|
+
const foreign = findForeignRule(live.rules);
|
|
160
|
+
if (foreign !== undefined)
|
|
161
|
+
return yield* refuseForForeignRule(props, foreign);
|
|
162
|
+
yield* grafana.routeDeleteAlertRuleGroup({ FolderUID: props.folderUid, Group: props.group });
|
|
163
|
+
}),
|
|
164
|
+
fetchLive: (props) => grafana.routeGetAlertRuleGroup({ FolderUID: props.folderUid, Group: props.group }).pipe(Effect5.catchTag("NotFound", () => Effect5.succeed(undefined))),
|
|
165
|
+
matches: (attributes, props) => attributes.interval === props.interval && declaredContentMatches(props.rules.map((rule) => normalizeRule(rule, props.folderUid, props.group)), attributes.rules),
|
|
166
|
+
update: (props, live) => Effect5.gen(function* () {
|
|
167
|
+
const foreign = findForeignRule(live.rules);
|
|
168
|
+
if (foreign !== undefined)
|
|
169
|
+
return yield* refuseForForeignRule(props, foreign);
|
|
170
|
+
yield* warnOnDroppedRules(props.folderUid, props.group, declaredUids(props), live.rules);
|
|
171
|
+
yield* grafana.routePutAlertRuleGroup({
|
|
172
|
+
FolderUID: props.folderUid,
|
|
173
|
+
Group: props.group,
|
|
174
|
+
...body(props)
|
|
175
|
+
});
|
|
176
|
+
})
|
|
177
|
+
};
|
|
178
|
+
var diff = ({
|
|
179
|
+
news,
|
|
180
|
+
output
|
|
181
|
+
}) => Effect5.gen(function* () {
|
|
182
|
+
if (output === undefined || !isResolved2(news))
|
|
183
|
+
return;
|
|
184
|
+
const live = yield* spec.fetchLive(news);
|
|
185
|
+
if (live === undefined)
|
|
186
|
+
return { action: "update" };
|
|
187
|
+
yield* warnOnDroppedRules(news.folderUid, news.group, declaredUids(news), live.rules);
|
|
188
|
+
const mapped = spec.attributes(live, news);
|
|
189
|
+
return mapped !== undefined && spec.matches(mapped, news) ? { action: "noop" } : { action: "update" };
|
|
190
|
+
});
|
|
191
|
+
var handlers = { ...grafanaHandlers(spec), diff };
|
|
192
|
+
var GrafanaAlertRuleGroupProvider = () => Provider.effect(GrafanaAlertRuleGroup, Effect5.succeed(GrafanaAlertRuleGroup.Provider.of(handlers)));
|
|
193
|
+
// src/grafana/datasource.ts
|
|
194
|
+
import { Resource as Resource2 } from "alchemy";
|
|
195
|
+
import * as Provider2 from "alchemy/Provider";
|
|
196
|
+
import * as grafana2 from "@distilled.cloud/grafana";
|
|
197
|
+
import * as Effect7 from "effect/Effect";
|
|
198
|
+
|
|
59
199
|
// src/grafana/secret-refs.ts
|
|
60
|
-
import * as
|
|
61
|
-
import * as
|
|
200
|
+
import * as Data2 from "effect/Data";
|
|
201
|
+
import * as Effect6 from "effect/Effect";
|
|
62
202
|
|
|
63
|
-
class GrafanaSecretRefUnsetError extends
|
|
203
|
+
class GrafanaSecretRefUnsetError extends Data2.TaggedError("GrafanaSecretRefUnsetError") {
|
|
64
204
|
}
|
|
65
|
-
var resolveSecretRefs = (refs, describeField) =>
|
|
205
|
+
var resolveSecretRefs = (refs, describeField) => Effect6.gen(function* () {
|
|
66
206
|
if (refs === undefined)
|
|
67
207
|
return;
|
|
68
208
|
const out = {};
|
|
69
209
|
for (const [field, envVar] of Object.entries(refs)) {
|
|
70
210
|
const raw = process.env[envVar];
|
|
71
211
|
if (raw === undefined || raw.trim() === "") {
|
|
72
|
-
return yield*
|
|
212
|
+
return yield* Effect6.fail(new GrafanaSecretRefUnsetError({
|
|
73
213
|
message: `${envVar} is unset. Export the value of ${describeField(field)} there — ` + "never as an Alchemy prop."
|
|
74
214
|
}));
|
|
75
215
|
}
|
|
@@ -79,7 +219,7 @@ var resolveSecretRefs = (refs, describeField) => Effect2.gen(function* () {
|
|
|
79
219
|
});
|
|
80
220
|
|
|
81
221
|
// src/grafana/datasource.ts
|
|
82
|
-
var GrafanaDatasource =
|
|
222
|
+
var GrafanaDatasource = Resource2("Grafana.Datasource");
|
|
83
223
|
var resolveSecureJsonData = (refs) => resolveSecretRefs(refs, (field) => `secureJsonData.${field}`);
|
|
84
224
|
var fields = (props) => ({
|
|
85
225
|
name: props.name,
|
|
@@ -94,7 +234,7 @@ var fields = (props) => ({
|
|
|
94
234
|
...props.user === undefined ? {} : { user: props.user },
|
|
95
235
|
...props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }
|
|
96
236
|
});
|
|
97
|
-
var
|
|
237
|
+
var spec2 = {
|
|
98
238
|
attributes: (live) => ({
|
|
99
239
|
access: live.access ?? "",
|
|
100
240
|
id: live.id ?? 0,
|
|
@@ -107,42 +247,28 @@ var spec = {
|
|
|
107
247
|
url: live.url ?? "",
|
|
108
248
|
version: live.version ?? 0
|
|
109
249
|
}),
|
|
110
|
-
create: (props) =>
|
|
250
|
+
create: (props) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.addDataSource({
|
|
111
251
|
...fields(props),
|
|
112
252
|
...secureJsonData === undefined ? {} : { secureJsonData },
|
|
113
253
|
uid: props.uid
|
|
114
254
|
})),
|
|
115
|
-
destroy: (props) =>
|
|
116
|
-
fetchLive: (props) =>
|
|
255
|
+
destroy: (props) => grafana2.deleteDataSourceByUID({ uid: props.uid }),
|
|
256
|
+
fetchLive: (props) => grafana2.getDataSourceByUID({ uid: props.uid }).pipe(Effect7.catchTag("NotFound", () => Effect7.succeed(undefined))),
|
|
117
257
|
matches: (attributes, props) => (props.name === undefined || attributes.name === props.name) && (props.type === undefined || attributes.type === props.type) && (props.url === undefined || attributes.url === props.url) && (props.access === undefined || attributes.access === props.access) && (props.isDefault === undefined || attributes.isDefault === props.isDefault),
|
|
118
|
-
update: (props, live) =>
|
|
258
|
+
update: (props, live) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.updateDataSourceByUID({
|
|
119
259
|
...fields(props),
|
|
120
260
|
...secureJsonData === undefined ? {} : { secureJsonData },
|
|
121
261
|
...live.version === undefined ? {} : { version: live.version },
|
|
122
262
|
uid: props.uid
|
|
123
263
|
}))
|
|
124
264
|
};
|
|
125
|
-
var
|
|
126
|
-
var GrafanaDatasourceProvider = () =>
|
|
265
|
+
var handlers2 = grafanaHandlers(spec2);
|
|
266
|
+
var GrafanaDatasourceProvider = () => Provider2.effect(GrafanaDatasource, Effect7.succeed(GrafanaDatasource.Provider.of(handlers2)));
|
|
127
267
|
// src/grafana/dashboard.ts
|
|
128
|
-
import { Resource as
|
|
129
|
-
import * as
|
|
130
|
-
import * as
|
|
131
|
-
import * as
|
|
132
|
-
|
|
133
|
-
// src/grafana/subset-match.ts
|
|
134
|
-
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
135
|
-
var declaredContentMatches = (declared, live) => {
|
|
136
|
-
if (declared == null)
|
|
137
|
-
return true;
|
|
138
|
-
if (Array.isArray(declared)) {
|
|
139
|
-
return Array.isArray(live) && declared.length === live.length && declared.every((item, index) => declaredContentMatches(item, live[index]));
|
|
140
|
-
}
|
|
141
|
-
if (isPlainObject(declared)) {
|
|
142
|
-
return isPlainObject(live) && Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]));
|
|
143
|
-
}
|
|
144
|
-
return declared === live;
|
|
145
|
-
};
|
|
268
|
+
import { Resource as Resource3 } from "alchemy";
|
|
269
|
+
import * as Provider3 from "alchemy/Provider";
|
|
270
|
+
import * as grafana3 from "@distilled.cloud/grafana";
|
|
271
|
+
import * as Effect8 from "effect/Effect";
|
|
146
272
|
|
|
147
273
|
// src/grafana/dashboard-model.ts
|
|
148
274
|
var VOLATILE_FIELDS = ["id", "version", "iteration"];
|
|
@@ -160,23 +286,10 @@ var normalizeModel = (model, uid) => {
|
|
|
160
286
|
};
|
|
161
287
|
var modelTitle = (model) => typeof model.title === "string" ? model.title : "";
|
|
162
288
|
|
|
163
|
-
// src/grafana/provisioned.ts
|
|
164
|
-
import * as Data2 from "effect/Data";
|
|
165
|
-
import * as Effect4 from "effect/Effect";
|
|
166
|
-
|
|
167
|
-
class GrafanaProvisionedObjectError extends Data2.TaggedError("GrafanaProvisionedObjectError") {
|
|
168
|
-
}
|
|
169
|
-
var refuseIfProvisioned = (kind, uid, owner) => {
|
|
170
|
-
const ownerClause = owner === undefined || owner === "" ? `(this Grafana instance did not report an owning file)` : `by ${owner}`;
|
|
171
|
-
return Effect4.fail(new GrafanaProvisionedObjectError({
|
|
172
|
-
message: `${kind} '${uid}' is file-provisioned ${ownerClause} and read-only through the classic ` + `write API this family calls. Edit the provisioning file instead — declaring this ` + `object read-only (a declaration matching what is live) is safe and will never reach ` + `this refusal.`
|
|
173
|
-
}));
|
|
174
|
-
};
|
|
175
|
-
|
|
176
289
|
// src/grafana/dashboard.ts
|
|
177
|
-
var GrafanaDashboard =
|
|
290
|
+
var GrafanaDashboard = Resource3("Grafana.Dashboard");
|
|
178
291
|
var modelOf = (live) => live.dashboard !== null && typeof live.dashboard === "object" ? live.dashboard : {};
|
|
179
|
-
var
|
|
292
|
+
var spec3 = {
|
|
180
293
|
attributes: (live, props) => {
|
|
181
294
|
const model = normalizeModel(modelOf(live), props.uid);
|
|
182
295
|
return {
|
|
@@ -190,23 +303,23 @@ var spec2 = {
|
|
|
190
303
|
version: live.meta?.version ?? 0
|
|
191
304
|
};
|
|
192
305
|
},
|
|
193
|
-
create: (props) =>
|
|
306
|
+
create: (props) => grafana3.postDashboard({
|
|
194
307
|
dashboard: normalizeModel(props.dashboard, props.uid),
|
|
195
308
|
...props.folderUid === undefined ? {} : { folderUid: props.folderUid }
|
|
196
309
|
}),
|
|
197
|
-
destroy: (props, live) =>
|
|
310
|
+
destroy: (props, live) => Effect8.gen(function* () {
|
|
198
311
|
if (live.meta?.provisioned) {
|
|
199
312
|
return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
|
|
200
313
|
}
|
|
201
|
-
yield*
|
|
314
|
+
yield* grafana3.deleteDashboard({ uid: props.uid });
|
|
202
315
|
}),
|
|
203
|
-
fetchLive: (props) =>
|
|
316
|
+
fetchLive: (props) => grafana3.getDashboard({ uid: props.uid }).pipe(Effect8.catchTag("NotFound", () => Effect8.succeed(undefined))),
|
|
204
317
|
matches: (attributes, props) => (props.folderUid === undefined || attributes.folderUid === props.folderUid) && declaredContentMatches(normalizeModel(props.dashboard, props.uid), attributes.dashboard),
|
|
205
|
-
update: (props, live) =>
|
|
318
|
+
update: (props, live) => Effect8.gen(function* () {
|
|
206
319
|
if (live.meta?.provisioned) {
|
|
207
320
|
return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
|
|
208
321
|
}
|
|
209
|
-
return yield*
|
|
322
|
+
return yield* grafana3.postDashboard({
|
|
210
323
|
dashboard: {
|
|
211
324
|
...normalizeModel(props.dashboard, props.uid),
|
|
212
325
|
...live.meta?.version === undefined ? {} : { version: live.meta.version }
|
|
@@ -215,21 +328,21 @@ var spec2 = {
|
|
|
215
328
|
});
|
|
216
329
|
})
|
|
217
330
|
};
|
|
218
|
-
var
|
|
219
|
-
var GrafanaDashboardProvider = () =>
|
|
331
|
+
var handlers3 = grafanaHandlers(spec3);
|
|
332
|
+
var GrafanaDashboardProvider = () => Provider3.effect(GrafanaDashboard, Effect8.succeed(GrafanaDashboard.Provider.of(handlers3)));
|
|
220
333
|
// src/grafana/folder.ts
|
|
221
|
-
import { Resource as
|
|
222
|
-
import * as
|
|
223
|
-
import * as
|
|
334
|
+
import { Resource as Resource4 } from "alchemy";
|
|
335
|
+
import * as Provider4 from "alchemy/Provider";
|
|
336
|
+
import * as grafana4 from "@distilled.cloud/grafana";
|
|
224
337
|
import * as Data3 from "effect/Data";
|
|
225
|
-
import * as
|
|
226
|
-
var GrafanaFolder =
|
|
338
|
+
import * as Effect9 from "effect/Effect";
|
|
339
|
+
var GrafanaFolder = Resource4("Grafana.Folder", {
|
|
227
340
|
defaultRemovalPolicy: "retain"
|
|
228
341
|
});
|
|
229
342
|
|
|
230
343
|
class GrafanaFolderReparentError extends Data3.TaggedError("GrafanaFolderReparentError") {
|
|
231
344
|
}
|
|
232
|
-
var
|
|
345
|
+
var spec4 = {
|
|
233
346
|
attributes: (live) => ({
|
|
234
347
|
id: live.id ?? 0,
|
|
235
348
|
managedBy: live.managedBy ?? "",
|
|
@@ -239,30 +352,30 @@ var spec3 = {
|
|
|
239
352
|
url: live.url ?? "",
|
|
240
353
|
version: live.version ?? 0
|
|
241
354
|
}),
|
|
242
|
-
create: (props) =>
|
|
355
|
+
create: (props) => grafana4.createFolder({
|
|
243
356
|
uid: props.uid,
|
|
244
357
|
title: props.title,
|
|
245
358
|
...props.description === undefined ? {} : { description: props.description },
|
|
246
359
|
...props.parentUid === undefined ? {} : { parentUid: props.parentUid }
|
|
247
360
|
}),
|
|
248
|
-
destroy: (props, live) =>
|
|
361
|
+
destroy: (props, live) => Effect9.gen(function* () {
|
|
249
362
|
if (live.managedBy) {
|
|
250
363
|
return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
|
|
251
364
|
}
|
|
252
|
-
yield*
|
|
365
|
+
yield* grafana4.deleteFolder({ folder_uid: props.uid });
|
|
253
366
|
}),
|
|
254
|
-
fetchLive: (props) =>
|
|
367
|
+
fetchLive: (props) => grafana4.getFolderByUID({ folder_uid: props.uid }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined))),
|
|
255
368
|
matches: (attributes, props) => attributes.title === props.title && (props.parentUid === undefined || attributes.parentUid === props.parentUid),
|
|
256
|
-
update: (props, live) =>
|
|
369
|
+
update: (props, live) => Effect9.gen(function* () {
|
|
257
370
|
if (live.managedBy) {
|
|
258
371
|
return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
|
|
259
372
|
}
|
|
260
373
|
if (props.parentUid !== undefined && props.parentUid !== (live.parentUid ?? undefined)) {
|
|
261
|
-
return yield*
|
|
374
|
+
return yield* Effect9.fail(new GrafanaFolderReparentError({
|
|
262
375
|
message: `Grafana.Folder '${props.uid}' declares parentUid '${props.parentUid}' but is ` + `live under '${live.parentUid ?? "(root)"}'. PUT /folders/{uid} cannot reparent a ` + `folder — move it by hand in Grafana, then update the declaration to match.`
|
|
263
376
|
}));
|
|
264
377
|
}
|
|
265
|
-
return yield*
|
|
378
|
+
return yield* grafana4.updateFolder({
|
|
266
379
|
folder_uid: props.uid,
|
|
267
380
|
title: props.title,
|
|
268
381
|
...props.description === undefined ? {} : { description: props.description },
|
|
@@ -270,27 +383,14 @@ var spec3 = {
|
|
|
270
383
|
});
|
|
271
384
|
})
|
|
272
385
|
};
|
|
273
|
-
var
|
|
274
|
-
var GrafanaFolderProvider = () =>
|
|
275
|
-
// src/grafana/contact-point.ts
|
|
276
|
-
import { Resource as Resource4 } from "alchemy";
|
|
277
|
-
import * as Provider4 from "alchemy/Provider";
|
|
278
|
-
import * as grafana4 from "@distilled.cloud/grafana";
|
|
279
|
-
import * as Effect8 from "effect/Effect";
|
|
280
|
-
|
|
281
|
-
// src/grafana/alerting-provenance.ts
|
|
282
|
-
import * as Effect7 from "effect/Effect";
|
|
283
|
-
var WRITABLE_PROVENANCES = new Set(["", "api"]);
|
|
284
|
-
var isForeignProvenance = (provenance) => provenance === undefined || !WRITABLE_PROVENANCES.has(provenance);
|
|
285
|
-
var refuseIfForeignProvenance = (kind, id, provenance) => {
|
|
286
|
-
const reason = provenance === undefined ? `did not report a provenance at all — this family cannot tell whether it is safe to own` : `has provenance '${provenance}', not '(none)' or 'api'`;
|
|
287
|
-
return Effect7.fail(new GrafanaProvisionedObjectError({
|
|
288
|
-
message: `${kind} '${id}' ${reason} — something other than this API may own it (file ` + `provisioning, the Prometheus-rules importer, or similar), and writing here could take ` + `over or corrupt an object this family does not actually control. Edit it at its actual ` + `source instead — declaring this object read-only (a declaration matching what is live) ` + `is safe and will never reach this refusal.`
|
|
289
|
-
}));
|
|
290
|
-
};
|
|
291
|
-
|
|
386
|
+
var handlers4 = grafanaHandlers(spec4);
|
|
387
|
+
var GrafanaFolderProvider = () => Provider4.effect(GrafanaFolder, Effect9.succeed(GrafanaFolder.Provider.of(handlers4)));
|
|
292
388
|
// src/grafana/contact-point.ts
|
|
293
|
-
|
|
389
|
+
import { Resource as Resource5 } from "alchemy";
|
|
390
|
+
import * as Provider5 from "alchemy/Provider";
|
|
391
|
+
import * as grafana5 from "@distilled.cloud/grafana";
|
|
392
|
+
import * as Effect10 from "effect/Effect";
|
|
393
|
+
var GrafanaContactPoint = Resource5("Grafana.ContactPoint");
|
|
294
394
|
var nonSecretSettings = (props) => {
|
|
295
395
|
const refs = props.secureSettingsRefs;
|
|
296
396
|
if (refs === undefined)
|
|
@@ -302,8 +402,8 @@ var nonSecretSettings = (props) => {
|
|
|
302
402
|
}
|
|
303
403
|
return out;
|
|
304
404
|
};
|
|
305
|
-
var resolveSettings = (props) =>
|
|
306
|
-
var
|
|
405
|
+
var resolveSettings = (props) => Effect10.map(resolveSecretRefs(props.secureSettingsRefs, (field) => `settings.${field}`), (secrets) => secrets === undefined ? props.settings : { ...props.settings, ...secrets });
|
|
406
|
+
var spec5 = {
|
|
307
407
|
attributes: (live) => ({
|
|
308
408
|
disableResolveMessage: live.disableResolveMessage ?? false,
|
|
309
409
|
name: live.name ?? "",
|
|
@@ -312,27 +412,27 @@ var spec4 = {
|
|
|
312
412
|
type: live.type,
|
|
313
413
|
uid: live.uid ?? ""
|
|
314
414
|
}),
|
|
315
|
-
create: (props) =>
|
|
415
|
+
create: (props) => Effect10.flatMap(resolveSettings(props), (settings) => grafana5.routePostContactpoints({
|
|
316
416
|
name: props.name,
|
|
317
417
|
settings,
|
|
318
418
|
type: props.type,
|
|
319
419
|
uid: props.uid,
|
|
320
420
|
...props.disableResolveMessage === undefined ? {} : { disableResolveMessage: props.disableResolveMessage }
|
|
321
421
|
})),
|
|
322
|
-
destroy: (props, live) =>
|
|
422
|
+
destroy: (props, live) => Effect10.gen(function* () {
|
|
323
423
|
if (isForeignProvenance(live.provenance)) {
|
|
324
424
|
return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
|
|
325
425
|
}
|
|
326
|
-
yield*
|
|
426
|
+
yield* grafana5.routeDeleteContactpoints({ UID: props.uid });
|
|
327
427
|
}),
|
|
328
|
-
fetchLive: (props) =>
|
|
428
|
+
fetchLive: (props) => grafana5.routeGetContactpoints({}).pipe(Effect10.map((points) => points.find((point) => point.uid === props.uid))),
|
|
329
429
|
matches: (attributes, props) => attributes.name === props.name && attributes.type === props.type && attributes.disableResolveMessage === (props.disableResolveMessage ?? false) && declaredContentMatches(nonSecretSettings(props), attributes.settings),
|
|
330
|
-
update: (props, live) =>
|
|
430
|
+
update: (props, live) => Effect10.gen(function* () {
|
|
331
431
|
if (isForeignProvenance(live.provenance)) {
|
|
332
432
|
return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
|
|
333
433
|
}
|
|
334
434
|
const settings = yield* resolveSettings(props);
|
|
335
|
-
yield*
|
|
435
|
+
yield* grafana5.routePutContactpoint({
|
|
336
436
|
UID: props.uid,
|
|
337
437
|
name: props.name,
|
|
338
438
|
settings,
|
|
@@ -342,99 +442,101 @@ var spec4 = {
|
|
|
342
442
|
});
|
|
343
443
|
})
|
|
344
444
|
};
|
|
345
|
-
var
|
|
346
|
-
var GrafanaContactPointProvider = () =>
|
|
445
|
+
var handlers5 = grafanaHandlers(spec5);
|
|
446
|
+
var GrafanaContactPointProvider = () => Provider5.effect(GrafanaContactPoint, Effect10.succeed(GrafanaContactPoint.Provider.of(handlers5)));
|
|
347
447
|
// src/grafana/message-template.ts
|
|
348
|
-
import { Resource as
|
|
349
|
-
import * as
|
|
350
|
-
import * as
|
|
351
|
-
import * as
|
|
352
|
-
var GrafanaMessageTemplate =
|
|
353
|
-
var
|
|
448
|
+
import { Resource as Resource6 } from "alchemy";
|
|
449
|
+
import * as Provider6 from "alchemy/Provider";
|
|
450
|
+
import * as grafana6 from "@distilled.cloud/grafana";
|
|
451
|
+
import * as Effect11 from "effect/Effect";
|
|
452
|
+
var GrafanaMessageTemplate = Resource6("Grafana.MessageTemplate");
|
|
453
|
+
var spec6 = {
|
|
354
454
|
attributes: (live) => ({
|
|
355
455
|
name: live.name ?? "",
|
|
356
456
|
provenance: live.provenance,
|
|
357
457
|
template: live.template ?? ""
|
|
358
458
|
}),
|
|
359
|
-
create: (props) =>
|
|
360
|
-
destroy: (props, live) =>
|
|
459
|
+
create: (props) => grafana6.routePutTemplate({ name: props.name, template: props.template }),
|
|
460
|
+
destroy: (props, live) => Effect11.gen(function* () {
|
|
361
461
|
if (isForeignProvenance(live.provenance)) {
|
|
362
462
|
return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
|
|
363
463
|
}
|
|
364
|
-
yield*
|
|
464
|
+
yield* grafana6.routeDeleteTemplate({ name: props.name });
|
|
365
465
|
}),
|
|
366
|
-
fetchLive: (props) =>
|
|
466
|
+
fetchLive: (props) => grafana6.routeGetTemplate({ name: props.name }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
|
|
367
467
|
matches: (attributes, props) => attributes.template === props.template,
|
|
368
|
-
update: (props, live) =>
|
|
468
|
+
update: (props, live) => Effect11.gen(function* () {
|
|
369
469
|
if (isForeignProvenance(live.provenance)) {
|
|
370
470
|
return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
|
|
371
471
|
}
|
|
372
|
-
yield*
|
|
472
|
+
yield* grafana6.routePutTemplate({
|
|
373
473
|
name: props.name,
|
|
374
474
|
template: props.template,
|
|
375
475
|
...live.version === undefined ? {} : { version: live.version }
|
|
376
476
|
});
|
|
377
477
|
})
|
|
378
478
|
};
|
|
379
|
-
var
|
|
380
|
-
var GrafanaMessageTemplateProvider = () =>
|
|
479
|
+
var handlers6 = grafanaHandlers(spec6);
|
|
480
|
+
var GrafanaMessageTemplateProvider = () => Provider6.effect(GrafanaMessageTemplate, Effect11.succeed(GrafanaMessageTemplate.Provider.of(handlers6)));
|
|
381
481
|
// src/grafana/mute-timing.ts
|
|
382
|
-
import { Resource as
|
|
383
|
-
import * as
|
|
384
|
-
import * as
|
|
385
|
-
import * as
|
|
386
|
-
var GrafanaMuteTiming =
|
|
387
|
-
var
|
|
482
|
+
import { Resource as Resource7 } from "alchemy";
|
|
483
|
+
import * as Provider7 from "alchemy/Provider";
|
|
484
|
+
import * as grafana7 from "@distilled.cloud/grafana";
|
|
485
|
+
import * as Effect12 from "effect/Effect";
|
|
486
|
+
var GrafanaMuteTiming = Resource7("Grafana.MuteTiming");
|
|
487
|
+
var spec7 = {
|
|
388
488
|
attributes: (live) => ({
|
|
389
489
|
name: live.name ?? "",
|
|
390
490
|
provenance: live.provenance,
|
|
391
491
|
timeIntervals: live.time_intervals ?? []
|
|
392
492
|
}),
|
|
393
|
-
create: (props) =>
|
|
493
|
+
create: (props) => grafana7.routePostMuteTiming({
|
|
394
494
|
name: props.name,
|
|
395
495
|
time_intervals: props.timeIntervals
|
|
396
496
|
}),
|
|
397
|
-
destroy: (props, live) =>
|
|
497
|
+
destroy: (props, live) => Effect12.gen(function* () {
|
|
398
498
|
if (isForeignProvenance(live.provenance)) {
|
|
399
499
|
return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
|
|
400
500
|
}
|
|
401
|
-
yield*
|
|
501
|
+
yield* grafana7.routeDeleteMuteTiming({
|
|
402
502
|
name: props.name,
|
|
403
503
|
...live.version === undefined ? {} : { version: live.version }
|
|
404
504
|
});
|
|
405
505
|
}),
|
|
406
|
-
fetchLive: (props) =>
|
|
506
|
+
fetchLive: (props) => grafana7.routeGetMuteTiming({ name: props.name }).pipe(Effect12.map((live) => live), Effect12.catchTag("NotFound", () => Effect12.succeed(undefined))),
|
|
407
507
|
matches: (attributes, props) => declaredContentMatches(props.timeIntervals, attributes.timeIntervals),
|
|
408
|
-
update: (props, live) =>
|
|
508
|
+
update: (props, live) => Effect12.gen(function* () {
|
|
409
509
|
if (isForeignProvenance(live.provenance)) {
|
|
410
510
|
return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
|
|
411
511
|
}
|
|
412
|
-
yield*
|
|
512
|
+
yield* grafana7.routePutMuteTiming({
|
|
413
513
|
name: props.name,
|
|
414
514
|
time_intervals: props.timeIntervals
|
|
415
515
|
});
|
|
416
516
|
})
|
|
417
517
|
};
|
|
418
|
-
var
|
|
419
|
-
var GrafanaMuteTimingProvider = () =>
|
|
518
|
+
var handlers7 = grafanaHandlers(spec7);
|
|
519
|
+
var GrafanaMuteTimingProvider = () => Provider7.effect(GrafanaMuteTiming, Effect12.succeed(GrafanaMuteTiming.Provider.of(handlers7)));
|
|
420
520
|
// src/grafana/credentials.ts
|
|
421
521
|
import { Credentials } from "@distilled.cloud/grafana/Credentials";
|
|
422
522
|
import { ConfigError } from "@distilled.cloud/grafana/Errors";
|
|
423
|
-
import * as
|
|
523
|
+
import * as Effect13 from "effect/Effect";
|
|
424
524
|
import * as Layer from "effect/Layer";
|
|
425
525
|
import * as Redacted from "effect/Redacted";
|
|
426
|
-
var resolve = (target) =>
|
|
526
|
+
var resolve = (target) => Effect13.sync(() => process.env[target.tokenEnv]).pipe(Effect13.flatMap((raw) => raw === undefined || raw.trim() === "" ? Effect13.fail(new ConfigError({
|
|
427
527
|
message: `${target.tokenEnv} is not set. Export the Grafana service-account token there ` + `— never as an Alchemy prop — before running a stack that declares a resource ` + `against ${target.baseUrl}.`
|
|
428
|
-
})) :
|
|
528
|
+
})) : Effect13.succeed({
|
|
429
529
|
apiBaseUrl: target.baseUrl.replace(/\/+$/, ""),
|
|
430
530
|
token: Redacted.make(raw.trim()),
|
|
431
531
|
...target.orgId === undefined ? {} : { orgId: target.orgId }
|
|
432
|
-
})),
|
|
532
|
+
})), Effect13.orDie);
|
|
433
533
|
var grafanaCredentials = (target) => Layer.succeed(Credentials, resolve(target));
|
|
434
534
|
// src/grafana/providers.ts
|
|
435
535
|
import * as Layer2 from "effect/Layer";
|
|
436
|
-
var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider(), GrafanaContactPointProvider(), GrafanaMuteTimingProvider(), GrafanaMessageTemplateProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
|
|
536
|
+
var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider(), GrafanaContactPointProvider(), GrafanaMuteTimingProvider(), GrafanaMessageTemplateProvider(), GrafanaAlertRuleGroupProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
|
|
437
537
|
export {
|
|
538
|
+
GrafanaAlertRuleGroup,
|
|
539
|
+
GrafanaAlertRuleGroupProvider,
|
|
438
540
|
GrafanaContactPoint,
|
|
439
541
|
GrafanaContactPointProvider,
|
|
440
542
|
GrafanaDashboard,
|
|
@@ -454,5 +556,5 @@ export {
|
|
|
454
556
|
grafanaProviders
|
|
455
557
|
};
|
|
456
558
|
|
|
457
|
-
//# debugId=
|
|
559
|
+
//# debugId=2878C6B6E21D1CEC64756E2164756E21
|
|
458
560
|
//# sourceMappingURL=index.js.map
|