@homeflare/alchemy 0.33.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/alerting-provenance.d.ts +80 -0
- package/dist/grafana/alerting-provenance.d.ts.map +1 -0
- package/dist/grafana/contact-point-fixtures.d.ts +28 -0
- package/dist/grafana/contact-point-fixtures.d.ts.map +1 -0
- package/dist/grafana/contact-point.d.ts +99 -0
- package/dist/grafana/contact-point.d.ts.map +1 -0
- package/dist/grafana/dashboard-model.d.ts +8 -38
- package/dist/grafana/dashboard-model.d.ts.map +1 -1
- package/dist/grafana/datasource.d.ts +2 -9
- package/dist/grafana/datasource.d.ts.map +1 -1
- package/dist/grafana/index.d.ts +11 -4
- package/dist/grafana/index.d.ts.map +1 -1
- package/dist/grafana/index.js +339 -80
- package/dist/grafana/index.js.map +16 -7
- package/dist/grafana/message-template.d.ts +70 -0
- package/dist/grafana/message-template.d.ts.map +1 -0
- package/dist/grafana/mute-timing.d.ts +112 -0
- package/dist/grafana/mute-timing.d.ts.map +1 -0
- package/dist/grafana/providers.d.ts +1 -1
- package/dist/grafana/providers.d.ts.map +1 -1
- package/dist/grafana/secret-refs.d.ts +19 -0
- package/dist/grafana/secret-refs.d.ts.map +1 -0
- package/dist/grafana/subset-match.d.ts +2 -0
- package/dist/grafana/subset-match.d.ts.map +1 -0
- package/dist/linux/index.d.ts +1 -1
- package/dist/linux/index.d.ts.map +1 -1
- package/dist/linux/index.js +3 -1
- package/dist/linux/index.js.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 +147 -0
- package/docs/grafana.md +6 -3
- package/docs/upstream-conformance.md +38 -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/alerting-provenance.ts +109 -0
- package/src/grafana/contact-point-fixtures.ts +25 -0
- package/src/grafana/contact-point-provenance.test.ts +113 -0
- package/src/grafana/contact-point-secrets.test.ts +117 -0
- package/src/grafana/contact-point.test.ts +123 -0
- package/src/grafana/contact-point.ts +179 -0
- package/src/grafana/dashboard-model.ts +8 -57
- package/src/grafana/datasource.ts +5 -27
- package/src/grafana/index.ts +31 -4
- package/src/grafana/message-template.test.ts +212 -0
- package/src/grafana/message-template.ts +114 -0
- package/src/grafana/mute-timing.test.ts +227 -0
- package/src/grafana/mute-timing.ts +160 -0
- package/src/grafana/providers.ts +8 -0
- package/src/grafana/secret-refs.ts +48 -0
- package/src/grafana/subset-match.ts +71 -0
- package/src/linux/index-export.test.ts +25 -0
- package/src/linux/index.ts +15 -1
- 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,18 +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
|
|
42
|
+
import * as Effect3 from "effect/Effect";
|
|
43
|
+
|
|
44
|
+
// src/grafana/provisioned.ts
|
|
5
45
|
import * as Data from "effect/Data";
|
|
6
46
|
import * as Effect2 from "effect/Effect";
|
|
7
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
|
+
|
|
8
67
|
// src/grafana/resource.ts
|
|
9
68
|
import { isResolved } from "alchemy/Diff";
|
|
10
|
-
import * as
|
|
69
|
+
import * as Effect4 from "effect/Effect";
|
|
11
70
|
var grafanaOperations = (spec) => {
|
|
12
|
-
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)));
|
|
13
72
|
return {
|
|
14
73
|
read,
|
|
15
|
-
diff: (news, output) =>
|
|
74
|
+
diff: (news, output) => Effect4.gen(function* () {
|
|
16
75
|
if (output === undefined || !isResolved(news))
|
|
17
76
|
return;
|
|
18
77
|
const live = yield* read(news);
|
|
@@ -22,7 +81,7 @@ var grafanaOperations = (spec) => {
|
|
|
22
81
|
return { action: "noop" };
|
|
23
82
|
return spec.update === undefined ? { action: "replace" } : { action: "update" };
|
|
24
83
|
}),
|
|
25
|
-
reconcile: (news) =>
|
|
84
|
+
reconcile: (news) => Effect4.gen(function* () {
|
|
26
85
|
const before = yield* spec.fetchLive(news);
|
|
27
86
|
if (before === undefined) {
|
|
28
87
|
yield* spec.create(news);
|
|
@@ -34,11 +93,11 @@ var grafanaOperations = (spec) => {
|
|
|
34
93
|
}
|
|
35
94
|
const after = yield* read(news);
|
|
36
95
|
if (after === undefined) {
|
|
37
|
-
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."));
|
|
38
97
|
}
|
|
39
98
|
return after;
|
|
40
99
|
}),
|
|
41
|
-
destroy: (olds) =>
|
|
100
|
+
destroy: (olds) => Effect4.gen(function* () {
|
|
42
101
|
const live = yield* spec.fetchLive(olds);
|
|
43
102
|
if (live === undefined)
|
|
44
103
|
return;
|
|
@@ -49,7 +108,7 @@ var grafanaOperations = (spec) => {
|
|
|
49
108
|
var grafanaHandlers = (spec) => {
|
|
50
109
|
const ops = grafanaOperations(spec);
|
|
51
110
|
return {
|
|
52
|
-
list: () =>
|
|
111
|
+
list: () => Effect4.succeed([]),
|
|
53
112
|
read: ({ olds }) => ops.read(olds),
|
|
54
113
|
diff: ({ news, output }) => ops.diff(news, output),
|
|
55
114
|
reconcile: ({ news }) => ops.reconcile(news),
|
|
@@ -57,26 +116,111 @@ var grafanaHandlers = (spec) => {
|
|
|
57
116
|
};
|
|
58
117
|
};
|
|
59
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)));
|
|
60
193
|
// src/grafana/datasource.ts
|
|
61
|
-
|
|
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
|
+
|
|
199
|
+
// src/grafana/secret-refs.ts
|
|
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
|
|
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*
|
|
73
|
-
message: `${envVar} is unset. Export the value of
|
|
212
|
+
return yield* Effect6.fail(new GrafanaSecretRefUnsetError({
|
|
213
|
+
message: `${envVar} is unset. Export the value of ${describeField(field)} there — ` + "never as an Alchemy prop."
|
|
74
214
|
}));
|
|
75
215
|
}
|
|
76
216
|
out[field] = raw.trim();
|
|
77
217
|
}
|
|
78
218
|
return out;
|
|
79
219
|
});
|
|
220
|
+
|
|
221
|
+
// src/grafana/datasource.ts
|
|
222
|
+
var GrafanaDatasource = Resource2("Grafana.Datasource");
|
|
223
|
+
var resolveSecureJsonData = (refs) => resolveSecretRefs(refs, (field) => `secureJsonData.${field}`);
|
|
80
224
|
var fields = (props) => ({
|
|
81
225
|
name: props.name,
|
|
82
226
|
type: props.type,
|
|
@@ -90,7 +234,7 @@ var fields = (props) => ({
|
|
|
90
234
|
...props.user === undefined ? {} : { user: props.user },
|
|
91
235
|
...props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }
|
|
92
236
|
});
|
|
93
|
-
var
|
|
237
|
+
var spec2 = {
|
|
94
238
|
attributes: (live) => ({
|
|
95
239
|
access: live.access ?? "",
|
|
96
240
|
id: live.id ?? 0,
|
|
@@ -103,28 +247,28 @@ var spec = {
|
|
|
103
247
|
url: live.url ?? "",
|
|
104
248
|
version: live.version ?? 0
|
|
105
249
|
}),
|
|
106
|
-
create: (props) =>
|
|
250
|
+
create: (props) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.addDataSource({
|
|
107
251
|
...fields(props),
|
|
108
252
|
...secureJsonData === undefined ? {} : { secureJsonData },
|
|
109
253
|
uid: props.uid
|
|
110
254
|
})),
|
|
111
|
-
destroy: (props) =>
|
|
112
|
-
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))),
|
|
113
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),
|
|
114
|
-
update: (props, live) =>
|
|
258
|
+
update: (props, live) => Effect7.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) => grafana2.updateDataSourceByUID({
|
|
115
259
|
...fields(props),
|
|
116
260
|
...secureJsonData === undefined ? {} : { secureJsonData },
|
|
117
261
|
...live.version === undefined ? {} : { version: live.version },
|
|
118
262
|
uid: props.uid
|
|
119
263
|
}))
|
|
120
264
|
};
|
|
121
|
-
var
|
|
122
|
-
var GrafanaDatasourceProvider = () =>
|
|
265
|
+
var handlers2 = grafanaHandlers(spec2);
|
|
266
|
+
var GrafanaDatasourceProvider = () => Provider2.effect(GrafanaDatasource, Effect7.succeed(GrafanaDatasource.Provider.of(handlers2)));
|
|
123
267
|
// src/grafana/dashboard.ts
|
|
124
|
-
import { Resource as
|
|
125
|
-
import * as
|
|
126
|
-
import * as
|
|
127
|
-
import * as
|
|
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";
|
|
128
272
|
|
|
129
273
|
// src/grafana/dashboard-model.ts
|
|
130
274
|
var VOLATILE_FIELDS = ["id", "version", "iteration"];
|
|
@@ -141,36 +285,11 @@ var normalizeModel = (model, uid) => {
|
|
|
141
285
|
return normalized;
|
|
142
286
|
};
|
|
143
287
|
var modelTitle = (model) => typeof model.title === "string" ? model.title : "";
|
|
144
|
-
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
145
|
-
var declaredContentMatches = (declared, live) => {
|
|
146
|
-
if (declared == null)
|
|
147
|
-
return true;
|
|
148
|
-
if (Array.isArray(declared)) {
|
|
149
|
-
return Array.isArray(live) && declared.length === live.length && declared.every((item, index) => declaredContentMatches(item, live[index]));
|
|
150
|
-
}
|
|
151
|
-
if (isPlainObject(declared)) {
|
|
152
|
-
return isPlainObject(live) && Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]));
|
|
153
|
-
}
|
|
154
|
-
return declared === live;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// src/grafana/provisioned.ts
|
|
158
|
-
import * as Data2 from "effect/Data";
|
|
159
|
-
import * as Effect3 from "effect/Effect";
|
|
160
|
-
|
|
161
|
-
class GrafanaProvisionedObjectError extends Data2.TaggedError("GrafanaProvisionedObjectError") {
|
|
162
|
-
}
|
|
163
|
-
var refuseIfProvisioned = (kind, uid, owner) => {
|
|
164
|
-
const ownerClause = owner === undefined || owner === "" ? `(this Grafana instance did not report an owning file)` : `by ${owner}`;
|
|
165
|
-
return Effect3.fail(new GrafanaProvisionedObjectError({
|
|
166
|
-
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.`
|
|
167
|
-
}));
|
|
168
|
-
};
|
|
169
288
|
|
|
170
289
|
// src/grafana/dashboard.ts
|
|
171
|
-
var GrafanaDashboard =
|
|
290
|
+
var GrafanaDashboard = Resource3("Grafana.Dashboard");
|
|
172
291
|
var modelOf = (live) => live.dashboard !== null && typeof live.dashboard === "object" ? live.dashboard : {};
|
|
173
|
-
var
|
|
292
|
+
var spec3 = {
|
|
174
293
|
attributes: (live, props) => {
|
|
175
294
|
const model = normalizeModel(modelOf(live), props.uid);
|
|
176
295
|
return {
|
|
@@ -184,23 +303,23 @@ var spec2 = {
|
|
|
184
303
|
version: live.meta?.version ?? 0
|
|
185
304
|
};
|
|
186
305
|
},
|
|
187
|
-
create: (props) =>
|
|
306
|
+
create: (props) => grafana3.postDashboard({
|
|
188
307
|
dashboard: normalizeModel(props.dashboard, props.uid),
|
|
189
308
|
...props.folderUid === undefined ? {} : { folderUid: props.folderUid }
|
|
190
309
|
}),
|
|
191
|
-
destroy: (props, live) =>
|
|
310
|
+
destroy: (props, live) => Effect8.gen(function* () {
|
|
192
311
|
if (live.meta?.provisioned) {
|
|
193
312
|
return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
|
|
194
313
|
}
|
|
195
|
-
yield*
|
|
314
|
+
yield* grafana3.deleteDashboard({ uid: props.uid });
|
|
196
315
|
}),
|
|
197
|
-
fetchLive: (props) =>
|
|
316
|
+
fetchLive: (props) => grafana3.getDashboard({ uid: props.uid }).pipe(Effect8.catchTag("NotFound", () => Effect8.succeed(undefined))),
|
|
198
317
|
matches: (attributes, props) => (props.folderUid === undefined || attributes.folderUid === props.folderUid) && declaredContentMatches(normalizeModel(props.dashboard, props.uid), attributes.dashboard),
|
|
199
|
-
update: (props, live) =>
|
|
318
|
+
update: (props, live) => Effect8.gen(function* () {
|
|
200
319
|
if (live.meta?.provisioned) {
|
|
201
320
|
return yield* refuseIfProvisioned("Grafana.Dashboard", props.uid, live.meta.provisionedExternalId);
|
|
202
321
|
}
|
|
203
|
-
return yield*
|
|
322
|
+
return yield* grafana3.postDashboard({
|
|
204
323
|
dashboard: {
|
|
205
324
|
...normalizeModel(props.dashboard, props.uid),
|
|
206
325
|
...live.meta?.version === undefined ? {} : { version: live.meta.version }
|
|
@@ -209,21 +328,21 @@ var spec2 = {
|
|
|
209
328
|
});
|
|
210
329
|
})
|
|
211
330
|
};
|
|
212
|
-
var
|
|
213
|
-
var GrafanaDashboardProvider = () =>
|
|
331
|
+
var handlers3 = grafanaHandlers(spec3);
|
|
332
|
+
var GrafanaDashboardProvider = () => Provider3.effect(GrafanaDashboard, Effect8.succeed(GrafanaDashboard.Provider.of(handlers3)));
|
|
214
333
|
// src/grafana/folder.ts
|
|
215
|
-
import { Resource as
|
|
216
|
-
import * as
|
|
217
|
-
import * as
|
|
334
|
+
import { Resource as Resource4 } from "alchemy";
|
|
335
|
+
import * as Provider4 from "alchemy/Provider";
|
|
336
|
+
import * as grafana4 from "@distilled.cloud/grafana";
|
|
218
337
|
import * as Data3 from "effect/Data";
|
|
219
|
-
import * as
|
|
220
|
-
var GrafanaFolder =
|
|
338
|
+
import * as Effect9 from "effect/Effect";
|
|
339
|
+
var GrafanaFolder = Resource4("Grafana.Folder", {
|
|
221
340
|
defaultRemovalPolicy: "retain"
|
|
222
341
|
});
|
|
223
342
|
|
|
224
343
|
class GrafanaFolderReparentError extends Data3.TaggedError("GrafanaFolderReparentError") {
|
|
225
344
|
}
|
|
226
|
-
var
|
|
345
|
+
var spec4 = {
|
|
227
346
|
attributes: (live) => ({
|
|
228
347
|
id: live.id ?? 0,
|
|
229
348
|
managedBy: live.managedBy ?? "",
|
|
@@ -233,30 +352,30 @@ var spec3 = {
|
|
|
233
352
|
url: live.url ?? "",
|
|
234
353
|
version: live.version ?? 0
|
|
235
354
|
}),
|
|
236
|
-
create: (props) =>
|
|
355
|
+
create: (props) => grafana4.createFolder({
|
|
237
356
|
uid: props.uid,
|
|
238
357
|
title: props.title,
|
|
239
358
|
...props.description === undefined ? {} : { description: props.description },
|
|
240
359
|
...props.parentUid === undefined ? {} : { parentUid: props.parentUid }
|
|
241
360
|
}),
|
|
242
|
-
destroy: (props, live) =>
|
|
361
|
+
destroy: (props, live) => Effect9.gen(function* () {
|
|
243
362
|
if (live.managedBy) {
|
|
244
363
|
return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
|
|
245
364
|
}
|
|
246
|
-
yield*
|
|
365
|
+
yield* grafana4.deleteFolder({ folder_uid: props.uid });
|
|
247
366
|
}),
|
|
248
|
-
fetchLive: (props) =>
|
|
367
|
+
fetchLive: (props) => grafana4.getFolderByUID({ folder_uid: props.uid }).pipe(Effect9.catchTag("NotFound", () => Effect9.succeed(undefined))),
|
|
249
368
|
matches: (attributes, props) => attributes.title === props.title && (props.parentUid === undefined || attributes.parentUid === props.parentUid),
|
|
250
|
-
update: (props, live) =>
|
|
369
|
+
update: (props, live) => Effect9.gen(function* () {
|
|
251
370
|
if (live.managedBy) {
|
|
252
371
|
return yield* refuseIfProvisioned("Grafana.Folder", props.uid, live.managedBy);
|
|
253
372
|
}
|
|
254
373
|
if (props.parentUid !== undefined && props.parentUid !== (live.parentUid ?? undefined)) {
|
|
255
|
-
return yield*
|
|
374
|
+
return yield* Effect9.fail(new GrafanaFolderReparentError({
|
|
256
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.`
|
|
257
376
|
}));
|
|
258
377
|
}
|
|
259
|
-
return yield*
|
|
378
|
+
return yield* grafana4.updateFolder({
|
|
260
379
|
folder_uid: props.uid,
|
|
261
380
|
title: props.title,
|
|
262
381
|
...props.description === undefined ? {} : { description: props.description },
|
|
@@ -264,26 +383,162 @@ var spec3 = {
|
|
|
264
383
|
});
|
|
265
384
|
})
|
|
266
385
|
};
|
|
267
|
-
var
|
|
268
|
-
var GrafanaFolderProvider = () =>
|
|
386
|
+
var handlers4 = grafanaHandlers(spec4);
|
|
387
|
+
var GrafanaFolderProvider = () => Provider4.effect(GrafanaFolder, Effect9.succeed(GrafanaFolder.Provider.of(handlers4)));
|
|
388
|
+
// src/grafana/contact-point.ts
|
|
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");
|
|
394
|
+
var nonSecretSettings = (props) => {
|
|
395
|
+
const refs = props.secureSettingsRefs;
|
|
396
|
+
if (refs === undefined)
|
|
397
|
+
return props.settings;
|
|
398
|
+
const out = {};
|
|
399
|
+
for (const [key, value] of Object.entries(props.settings)) {
|
|
400
|
+
if (!(key in refs))
|
|
401
|
+
out[key] = value;
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
};
|
|
405
|
+
var resolveSettings = (props) => Effect10.map(resolveSecretRefs(props.secureSettingsRefs, (field) => `settings.${field}`), (secrets) => secrets === undefined ? props.settings : { ...props.settings, ...secrets });
|
|
406
|
+
var spec5 = {
|
|
407
|
+
attributes: (live) => ({
|
|
408
|
+
disableResolveMessage: live.disableResolveMessage ?? false,
|
|
409
|
+
name: live.name ?? "",
|
|
410
|
+
provenance: live.provenance,
|
|
411
|
+
settings: live.settings ?? {},
|
|
412
|
+
type: live.type,
|
|
413
|
+
uid: live.uid ?? ""
|
|
414
|
+
}),
|
|
415
|
+
create: (props) => Effect10.flatMap(resolveSettings(props), (settings) => grafana5.routePostContactpoints({
|
|
416
|
+
name: props.name,
|
|
417
|
+
settings,
|
|
418
|
+
type: props.type,
|
|
419
|
+
uid: props.uid,
|
|
420
|
+
...props.disableResolveMessage === undefined ? {} : { disableResolveMessage: props.disableResolveMessage }
|
|
421
|
+
})),
|
|
422
|
+
destroy: (props, live) => Effect10.gen(function* () {
|
|
423
|
+
if (isForeignProvenance(live.provenance)) {
|
|
424
|
+
return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
|
|
425
|
+
}
|
|
426
|
+
yield* grafana5.routeDeleteContactpoints({ UID: props.uid });
|
|
427
|
+
}),
|
|
428
|
+
fetchLive: (props) => grafana5.routeGetContactpoints({}).pipe(Effect10.map((points) => points.find((point) => point.uid === props.uid))),
|
|
429
|
+
matches: (attributes, props) => attributes.name === props.name && attributes.type === props.type && attributes.disableResolveMessage === (props.disableResolveMessage ?? false) && declaredContentMatches(nonSecretSettings(props), attributes.settings),
|
|
430
|
+
update: (props, live) => Effect10.gen(function* () {
|
|
431
|
+
if (isForeignProvenance(live.provenance)) {
|
|
432
|
+
return yield* refuseIfForeignProvenance("Grafana.ContactPoint", props.uid, live.provenance);
|
|
433
|
+
}
|
|
434
|
+
const settings = yield* resolveSettings(props);
|
|
435
|
+
yield* grafana5.routePutContactpoint({
|
|
436
|
+
UID: props.uid,
|
|
437
|
+
name: props.name,
|
|
438
|
+
settings,
|
|
439
|
+
type: props.type,
|
|
440
|
+
uid: props.uid,
|
|
441
|
+
...props.disableResolveMessage === undefined ? {} : { disableResolveMessage: props.disableResolveMessage }
|
|
442
|
+
});
|
|
443
|
+
})
|
|
444
|
+
};
|
|
445
|
+
var handlers5 = grafanaHandlers(spec5);
|
|
446
|
+
var GrafanaContactPointProvider = () => Provider5.effect(GrafanaContactPoint, Effect10.succeed(GrafanaContactPoint.Provider.of(handlers5)));
|
|
447
|
+
// src/grafana/message-template.ts
|
|
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 = {
|
|
454
|
+
attributes: (live) => ({
|
|
455
|
+
name: live.name ?? "",
|
|
456
|
+
provenance: live.provenance,
|
|
457
|
+
template: live.template ?? ""
|
|
458
|
+
}),
|
|
459
|
+
create: (props) => grafana6.routePutTemplate({ name: props.name, template: props.template }),
|
|
460
|
+
destroy: (props, live) => Effect11.gen(function* () {
|
|
461
|
+
if (isForeignProvenance(live.provenance)) {
|
|
462
|
+
return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
|
|
463
|
+
}
|
|
464
|
+
yield* grafana6.routeDeleteTemplate({ name: props.name });
|
|
465
|
+
}),
|
|
466
|
+
fetchLive: (props) => grafana6.routeGetTemplate({ name: props.name }).pipe(Effect11.catchTag("NotFound", () => Effect11.succeed(undefined))),
|
|
467
|
+
matches: (attributes, props) => attributes.template === props.template,
|
|
468
|
+
update: (props, live) => Effect11.gen(function* () {
|
|
469
|
+
if (isForeignProvenance(live.provenance)) {
|
|
470
|
+
return yield* refuseIfForeignProvenance("Grafana.MessageTemplate", props.name, live.provenance);
|
|
471
|
+
}
|
|
472
|
+
yield* grafana6.routePutTemplate({
|
|
473
|
+
name: props.name,
|
|
474
|
+
template: props.template,
|
|
475
|
+
...live.version === undefined ? {} : { version: live.version }
|
|
476
|
+
});
|
|
477
|
+
})
|
|
478
|
+
};
|
|
479
|
+
var handlers6 = grafanaHandlers(spec6);
|
|
480
|
+
var GrafanaMessageTemplateProvider = () => Provider6.effect(GrafanaMessageTemplate, Effect11.succeed(GrafanaMessageTemplate.Provider.of(handlers6)));
|
|
481
|
+
// src/grafana/mute-timing.ts
|
|
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 = {
|
|
488
|
+
attributes: (live) => ({
|
|
489
|
+
name: live.name ?? "",
|
|
490
|
+
provenance: live.provenance,
|
|
491
|
+
timeIntervals: live.time_intervals ?? []
|
|
492
|
+
}),
|
|
493
|
+
create: (props) => grafana7.routePostMuteTiming({
|
|
494
|
+
name: props.name,
|
|
495
|
+
time_intervals: props.timeIntervals
|
|
496
|
+
}),
|
|
497
|
+
destroy: (props, live) => Effect12.gen(function* () {
|
|
498
|
+
if (isForeignProvenance(live.provenance)) {
|
|
499
|
+
return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
|
|
500
|
+
}
|
|
501
|
+
yield* grafana7.routeDeleteMuteTiming({
|
|
502
|
+
name: props.name,
|
|
503
|
+
...live.version === undefined ? {} : { version: live.version }
|
|
504
|
+
});
|
|
505
|
+
}),
|
|
506
|
+
fetchLive: (props) => grafana7.routeGetMuteTiming({ name: props.name }).pipe(Effect12.map((live) => live), Effect12.catchTag("NotFound", () => Effect12.succeed(undefined))),
|
|
507
|
+
matches: (attributes, props) => declaredContentMatches(props.timeIntervals, attributes.timeIntervals),
|
|
508
|
+
update: (props, live) => Effect12.gen(function* () {
|
|
509
|
+
if (isForeignProvenance(live.provenance)) {
|
|
510
|
+
return yield* refuseIfForeignProvenance("Grafana.MuteTiming", props.name, live.provenance);
|
|
511
|
+
}
|
|
512
|
+
yield* grafana7.routePutMuteTiming({
|
|
513
|
+
name: props.name,
|
|
514
|
+
time_intervals: props.timeIntervals
|
|
515
|
+
});
|
|
516
|
+
})
|
|
517
|
+
};
|
|
518
|
+
var handlers7 = grafanaHandlers(spec7);
|
|
519
|
+
var GrafanaMuteTimingProvider = () => Provider7.effect(GrafanaMuteTiming, Effect12.succeed(GrafanaMuteTiming.Provider.of(handlers7)));
|
|
269
520
|
// src/grafana/credentials.ts
|
|
270
521
|
import { Credentials } from "@distilled.cloud/grafana/Credentials";
|
|
271
522
|
import { ConfigError } from "@distilled.cloud/grafana/Errors";
|
|
272
|
-
import * as
|
|
523
|
+
import * as Effect13 from "effect/Effect";
|
|
273
524
|
import * as Layer from "effect/Layer";
|
|
274
525
|
import * as Redacted from "effect/Redacted";
|
|
275
|
-
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({
|
|
276
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}.`
|
|
277
|
-
})) :
|
|
528
|
+
})) : Effect13.succeed({
|
|
278
529
|
apiBaseUrl: target.baseUrl.replace(/\/+$/, ""),
|
|
279
530
|
token: Redacted.make(raw.trim()),
|
|
280
531
|
...target.orgId === undefined ? {} : { orgId: target.orgId }
|
|
281
|
-
})),
|
|
532
|
+
})), Effect13.orDie);
|
|
282
533
|
var grafanaCredentials = (target) => Layer.succeed(Credentials, resolve(target));
|
|
283
534
|
// src/grafana/providers.ts
|
|
284
535
|
import * as Layer2 from "effect/Layer";
|
|
285
|
-
var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
|
|
536
|
+
var grafanaProviders = (target) => Layer2.mergeAll(GrafanaDatasourceProvider(), GrafanaFolderProvider(), GrafanaDashboardProvider(), GrafanaContactPointProvider(), GrafanaMuteTimingProvider(), GrafanaMessageTemplateProvider(), GrafanaAlertRuleGroupProvider()).pipe(Layer2.provide(grafanaCredentials(target)));
|
|
286
537
|
export {
|
|
538
|
+
GrafanaAlertRuleGroup,
|
|
539
|
+
GrafanaAlertRuleGroupProvider,
|
|
540
|
+
GrafanaContactPoint,
|
|
541
|
+
GrafanaContactPointProvider,
|
|
287
542
|
GrafanaDashboard,
|
|
288
543
|
GrafanaDashboardProvider,
|
|
289
544
|
GrafanaDatasource,
|
|
@@ -291,11 +546,15 @@ export {
|
|
|
291
546
|
GrafanaFolder,
|
|
292
547
|
GrafanaFolderProvider,
|
|
293
548
|
GrafanaFolderReparentError,
|
|
549
|
+
GrafanaMessageTemplate,
|
|
550
|
+
GrafanaMessageTemplateProvider,
|
|
551
|
+
GrafanaMuteTiming,
|
|
552
|
+
GrafanaMuteTimingProvider,
|
|
294
553
|
GrafanaProvisionedObjectError,
|
|
295
554
|
GrafanaSecretRefUnsetError,
|
|
296
555
|
grafanaCredentials,
|
|
297
556
|
grafanaProviders
|
|
298
557
|
};
|
|
299
558
|
|
|
300
|
-
//# debugId=
|
|
559
|
+
//# debugId=2878C6B6E21D1CEC64756E2164756E21
|
|
301
560
|
//# sourceMappingURL=index.js.map
|