querysub 0.252.0 → 0.254.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/package.json +2 -2
- package/src/3-path-functions/PathFunctionRunner.ts +1 -0
- package/src/4-deploy/git.ts +34 -1
- package/src/deployManager/MachinesPage.tsx +7 -10
- package/src/deployManager/components/MachineDetailPage.tsx +13 -21
- package/src/deployManager/components/MachinesListPage.tsx +59 -48
- package/src/deployManager/components/ServiceDetailPage.tsx +7 -9
- package/src/deployManager/components/ServicesListPage.tsx +41 -31
- package/src/deployManager/components/deployButtons.tsx +217 -0
- package/src/deployManager/machineApplyMainCode.ts +10 -4
- package/src/deployManager/machineController.ts +36 -4
- package/src/deployManager/machineSchema.ts +134 -45
- package/src/deployManager/spec.txt +3 -62
- package/src/deployManager/urlParams.ts +0 -1
- package/src/diagnostics/errorLogs/ErrorLogController.ts +1 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +6 -0
- package/src/library-components/SyncedController.ts +91 -31
- package/src/misc/formatJSX.tsx +6 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { qreact } from "../../4-dom/qreact";
|
|
3
|
+
import { MachineServiceController, MachineInfo, ServiceConfig } from "../machineSchema";
|
|
4
|
+
import { showFullscreenModal } from "../../5-diagnostics/FullscreenModal";
|
|
5
|
+
import { InputLabel } from "../../library-components/InputLabel";
|
|
6
|
+
import { css } from "../../4-dom/css";
|
|
7
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
8
|
+
import { formatTimeJSX } from "../../misc/formatJSX";
|
|
9
|
+
import { MachineController } from "../machineController";
|
|
10
|
+
import { closeAllModals } from "../../5-diagnostics/Modal";
|
|
11
|
+
|
|
12
|
+
module.hotreload = true;
|
|
13
|
+
|
|
14
|
+
function bigEmoji(emoji: string, topOffset = 0) {
|
|
15
|
+
return <span className={css.fontSize(26).marginTop(topOffset)}>{emoji}</span>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class RenderGitRefInfo extends qreact.Component<{
|
|
19
|
+
gitRef: string;
|
|
20
|
+
isQuerysub?: boolean;
|
|
21
|
+
}> {
|
|
22
|
+
render() {
|
|
23
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
24
|
+
let gitRefInfo = controller.getGitRefInfo({
|
|
25
|
+
ref: this.props.gitRef,
|
|
26
|
+
useQuerysub: this.props.isQuerysub,
|
|
27
|
+
});
|
|
28
|
+
if (!gitRefInfo) return undefined;
|
|
29
|
+
return <div className={css.fontWeight("normal")}>{formatTimeJSX(gitRefInfo.time)} AGO {gitRefInfo.description}</div>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const buttonStyle = css.pad2(12, 8).button.bord2(0, 0, 20).fontWeight("bold").vbox(0).alignItems("center");
|
|
34
|
+
|
|
35
|
+
export class UpdateButtons extends qreact.Component<{
|
|
36
|
+
services: ServiceConfig[];
|
|
37
|
+
}> {
|
|
38
|
+
render() {
|
|
39
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
40
|
+
const gitInfo = controller.getGitInfo();
|
|
41
|
+
|
|
42
|
+
let outdatedServices = this.props.services.filter(service => {
|
|
43
|
+
let { gitRef, repoUrl } = service.parameters;
|
|
44
|
+
return repoUrl === gitInfo?.repoUrl && gitRef !== gitInfo?.latestRef;
|
|
45
|
+
});
|
|
46
|
+
let outdatedRefs = outdatedServices.map(service => service.parameters.gitRef);
|
|
47
|
+
|
|
48
|
+
return <>
|
|
49
|
+
{!!gitInfo?.querysubUncommitted.length && <button
|
|
50
|
+
className={buttonStyle.hsl(260, 60, 55)}
|
|
51
|
+
title={gitInfo?.querysubUncommitted.join("\n")}
|
|
52
|
+
onClick={() => {
|
|
53
|
+
showFullscreenModal({
|
|
54
|
+
content: <div>
|
|
55
|
+
<InputLabel
|
|
56
|
+
label="Commit message (shift+enter to commit, escape to cancel)"
|
|
57
|
+
value={""}
|
|
58
|
+
textarea
|
|
59
|
+
fillWidth
|
|
60
|
+
focusOnMount
|
|
61
|
+
onChangeValue={async value => {
|
|
62
|
+
if (!value) return;
|
|
63
|
+
await controller.commitPushAndPublishQuerysub.promise(value);
|
|
64
|
+
closeAllModals();
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
<div className={css.vbox(5)}>
|
|
68
|
+
{gitInfo?.querysubUncommitted.map(change => {
|
|
69
|
+
return <div>
|
|
70
|
+
{change}
|
|
71
|
+
</div>;
|
|
72
|
+
})}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
});
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<div>
|
|
79
|
+
{bigEmoji("📚", -5)}
|
|
80
|
+
Publish & Commit & Push Querysub & Update Package.json ({gitInfo?.querysubUncommitted.length} Files Changed)
|
|
81
|
+
</div>
|
|
82
|
+
<RenderGitRefInfo gitRef={gitInfo.querysubRef} isQuerysub />
|
|
83
|
+
</button>}
|
|
84
|
+
{!!gitInfo?.uncommitted.length && <button
|
|
85
|
+
className={buttonStyle.hsl(210, 40, 50)}
|
|
86
|
+
title={gitInfo?.uncommitted.join("\n")}
|
|
87
|
+
onClick={() => {
|
|
88
|
+
showFullscreenModal({
|
|
89
|
+
content: <div>
|
|
90
|
+
<InputLabel
|
|
91
|
+
label="Commit message (shift+enter to commit, escape to cancel)"
|
|
92
|
+
value={""}
|
|
93
|
+
textarea
|
|
94
|
+
fillWidth
|
|
95
|
+
focusOnMount
|
|
96
|
+
onChangeValue={async value => {
|
|
97
|
+
if (!value) return;
|
|
98
|
+
await controller.commitPushService.promise(value);
|
|
99
|
+
closeAllModals();
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
<div className={css.vbox(5)}>
|
|
103
|
+
{gitInfo?.uncommitted.map(change => {
|
|
104
|
+
return <div>
|
|
105
|
+
{change}
|
|
106
|
+
</div>;
|
|
107
|
+
})}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
});
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{bigEmoji("⬆️")} <span>Commit & Push ({gitInfo?.uncommitted.length} Files Changed)</span>
|
|
114
|
+
</button>}
|
|
115
|
+
|
|
116
|
+
{outdatedServices.length > 0 && gitInfo &&
|
|
117
|
+
<button
|
|
118
|
+
className={buttonStyle.hsl(120, 70, 50)}
|
|
119
|
+
onClick={async () => {
|
|
120
|
+
for (let service of outdatedServices) {
|
|
121
|
+
service.parameters.gitRef = gitInfo.latestRef;
|
|
122
|
+
}
|
|
123
|
+
await controller.setServiceConfigs.promise(outdatedServices);
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<div>
|
|
127
|
+
{bigEmoji("🎯")} <span>Deploy & Restart All ({outdatedServices.length}) Services</span>
|
|
128
|
+
</div>
|
|
129
|
+
<div className={css.hbox(5)}>
|
|
130
|
+
<b>New</b>
|
|
131
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
132
|
+
</div>
|
|
133
|
+
{outdatedRefs.map(ref => <div className={css.hbox(5)}>
|
|
134
|
+
<b>Current</b>
|
|
135
|
+
<RenderGitRefInfo gitRef={ref} />
|
|
136
|
+
</div>)}
|
|
137
|
+
</button>
|
|
138
|
+
}
|
|
139
|
+
</>;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export class UpdateServiceButtons extends qreact.Component<{
|
|
144
|
+
service: ServiceConfig;
|
|
145
|
+
}> {
|
|
146
|
+
render() {
|
|
147
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
148
|
+
const gitInfo = controller.getGitInfo();
|
|
149
|
+
let gitRef = this.props.service.parameters.gitRef;
|
|
150
|
+
let repoUrl = this.props.service.parameters.repoUrl;
|
|
151
|
+
if (gitInfo?.repoUrl !== repoUrl) return undefined;
|
|
152
|
+
|
|
153
|
+
return <>
|
|
154
|
+
{gitRef !== gitInfo.latestRef && <button
|
|
155
|
+
className={buttonStyle.hsl(120, 70, 50).alignSelf("stretch")}
|
|
156
|
+
onClick={async () => {
|
|
157
|
+
this.props.service.parameters.gitRef = gitInfo.latestRef;
|
|
158
|
+
await controller.setServiceConfigs.promise([this.props.service]);
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<div>
|
|
162
|
+
{bigEmoji("🎯", -4)} <span>Deploy & Restart</span>
|
|
163
|
+
</div>
|
|
164
|
+
<div className={css.hbox(5)}>
|
|
165
|
+
<b>New</b>
|
|
166
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
167
|
+
</div>
|
|
168
|
+
<div className={css.hbox(5)}>
|
|
169
|
+
<b>Current</b>
|
|
170
|
+
<RenderGitRefInfo gitRef={gitRef} />
|
|
171
|
+
</div>
|
|
172
|
+
</button>}
|
|
173
|
+
</>;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export class DeployMachineButtons extends qreact.Component<{
|
|
178
|
+
machines: MachineInfo[];
|
|
179
|
+
}> {
|
|
180
|
+
render() {
|
|
181
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
182
|
+
const gitInfo = controller.getGitInfo();
|
|
183
|
+
if (!gitInfo) return undefined;
|
|
184
|
+
|
|
185
|
+
let outdatedMachines = this.props.machines.filter(machine => {
|
|
186
|
+
return machine.repoUrl === gitInfo.repoUrl && machine.gitRef !== gitInfo.latestRef;
|
|
187
|
+
});
|
|
188
|
+
let oudatedRefs = outdatedMachines.map(machine => machine.gitRef);
|
|
189
|
+
|
|
190
|
+
return <>
|
|
191
|
+
{outdatedMachines.length > 0 && <div
|
|
192
|
+
className={buttonStyle.alignSelf("stretch").hsl(120, 70, 50)}
|
|
193
|
+
onClick={async () => {
|
|
194
|
+
let machineController = MachineController(SocketFunction.browserNodeId());
|
|
195
|
+
await Promise.all(outdatedMachines.map(machine => {
|
|
196
|
+
return machineController.deployMachineFromBrowser.promise({
|
|
197
|
+
machineNodeId: machine.applyNodeId,
|
|
198
|
+
gitRef: gitInfo.latestRef,
|
|
199
|
+
});
|
|
200
|
+
}));
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
<div>
|
|
204
|
+
{bigEmoji("🎯", -4)} <span>Deploy {outdatedMachines.length} Machines</span>
|
|
205
|
+
</div>
|
|
206
|
+
<div className={css.hbox(5)}>
|
|
207
|
+
<b>New</b>
|
|
208
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
209
|
+
</div>
|
|
210
|
+
{oudatedRefs.map(ref => <div className={css.hbox(5)}>
|
|
211
|
+
<b>Current</b>
|
|
212
|
+
<RenderGitRefInfo gitRef={ref} />
|
|
213
|
+
</div>)}
|
|
214
|
+
</div>}
|
|
215
|
+
</>;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -4,7 +4,7 @@ import { measureWrap } from "socket-function/src/profiling/measure";
|
|
|
4
4
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
5
5
|
import { getOurNodeId, getOurNodeIdAssert } from "../-f-node-discovery/NodeDiscovery";
|
|
6
6
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
7
|
-
import { MACHINE_RESYNC_INTERVAL,
|
|
7
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, serviceConfigs, SERVICE_FOLDER, machineInfos } from "./machineSchema";
|
|
8
8
|
import { runPromise } from "../functional/runCommand";
|
|
9
9
|
import { getExternalIP } from "socket-function/src/networking";
|
|
10
10
|
import { errorToUndefined, errorToUndefinedSilent } from "../errors";
|
|
@@ -18,7 +18,7 @@ import os from "os";
|
|
|
18
18
|
import fs from "fs";
|
|
19
19
|
import { spawn, ChildProcess } from "child_process";
|
|
20
20
|
import { lazy } from "socket-function/src/caching";
|
|
21
|
-
import { getGitRefLive, setGitRef } from "../4-deploy/git";
|
|
21
|
+
import { getGitRefLive, getGitURLLive, setGitRef } from "../4-deploy/git";
|
|
22
22
|
import { blue, green, magenta, red } from "socket-function/src/formatting/logColors";
|
|
23
23
|
import { shutdown } from "../diagnostics/periodic";
|
|
24
24
|
import { onServiceConfigChange } from "./machineController";
|
|
@@ -34,6 +34,8 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
34
34
|
heartbeat: Date.now(),
|
|
35
35
|
info: {},
|
|
36
36
|
services: {},
|
|
37
|
+
repoUrl: "",
|
|
38
|
+
gitRef: "",
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
machineInfo.info.hostnamectl = await errorToUndefinedSilent(runPromise("hostnamectl")) || "";
|
|
@@ -41,6 +43,9 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
41
43
|
machineInfo.info.lscpu = await errorToUndefinedSilent(runPromise("lscpu")) || "";
|
|
42
44
|
machineInfo.info.id = await errorToUndefinedSilent(runPromise("id")) || await errorToUndefinedSilent(runPromise("whoami")) || "";
|
|
43
45
|
|
|
46
|
+
machineInfo.repoUrl = await getGitURLLive(".");
|
|
47
|
+
machineInfo.gitRef = await getGitRefLive(".");
|
|
48
|
+
|
|
44
49
|
return machineInfo;
|
|
45
50
|
});
|
|
46
51
|
|
|
@@ -78,7 +83,7 @@ export async function streamScreenOutput(config: {
|
|
|
78
83
|
try {
|
|
79
84
|
await serialOnData(data);
|
|
80
85
|
} catch (e: any) {
|
|
81
|
-
console.log(`Callback for stream output ${screenName} failed. It probably just disconnected, almost certainly not an error: ${e.
|
|
86
|
+
console.log(`Callback for stream output ${screenName} failed. It probably just disconnected, almost certainly not an error: ${e.message}`);
|
|
82
87
|
await stop();
|
|
83
88
|
} finally {
|
|
84
89
|
pendingDataCalls--;
|
|
@@ -425,7 +430,7 @@ async function quickIsOutdated() {
|
|
|
425
430
|
index: i,
|
|
426
431
|
});
|
|
427
432
|
let screen = screens.find(x => x.screenName === screenName);
|
|
428
|
-
if (!screen) return true;
|
|
433
|
+
if (!screen?.isProcessRunning) return true;
|
|
429
434
|
|
|
430
435
|
let folder = root + screenName + "/";
|
|
431
436
|
await fs.promises.mkdir(folder, { recursive: true });
|
|
@@ -433,6 +438,7 @@ async function quickIsOutdated() {
|
|
|
433
438
|
if (!fs.existsSync(parameterPath)) return true;
|
|
434
439
|
let prevParameters = await fs.promises.readFile(parameterPath, "utf8");
|
|
435
440
|
if (prevParameters !== newParametersString) return true;
|
|
441
|
+
|
|
436
442
|
}
|
|
437
443
|
}
|
|
438
444
|
return false;
|
|
@@ -8,6 +8,10 @@ import { isNode } from "typesafecss";
|
|
|
8
8
|
import { streamScreenOutput } from "./machineApplyMainCode";
|
|
9
9
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
10
10
|
import { getPathStr2 } from "../path";
|
|
11
|
+
import { getGitURLLive, setGitRef } from "../4-deploy/git";
|
|
12
|
+
import os from "os";
|
|
13
|
+
import { runPromise } from "../functional/runCommand";
|
|
14
|
+
import { getSyncedController } from "../library-components/SyncedController";
|
|
11
15
|
|
|
12
16
|
const POLL_INTERVAL = timeInMinute * 15;
|
|
13
17
|
|
|
@@ -78,27 +82,55 @@ class MachineControllerBase {
|
|
|
78
82
|
}) {
|
|
79
83
|
let caller = SocketFunction.getCaller();
|
|
80
84
|
forwardedCallbacks.set(config.callbackId, caller.nodeId);
|
|
81
|
-
await MachineController
|
|
85
|
+
await MachineController(config.nodeId).streamScreenOutput.promise({
|
|
82
86
|
key: config.key,
|
|
83
87
|
index: config.index,
|
|
84
88
|
callbackId: config.callbackId,
|
|
85
89
|
});
|
|
86
90
|
}
|
|
91
|
+
|
|
92
|
+
public async deployMachineFromBrowser(config: {
|
|
93
|
+
machineNodeId: string;
|
|
94
|
+
gitRef: string;
|
|
95
|
+
}) {
|
|
96
|
+
await MachineController(config.machineNodeId).deployMachine.promise({
|
|
97
|
+
gitRef: config.gitRef,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async deployMachine(config: {
|
|
102
|
+
gitRef: string;
|
|
103
|
+
}) {
|
|
104
|
+
let gitFolder = os.homedir() + "/machine-alwaysup";
|
|
105
|
+
await setGitRef({
|
|
106
|
+
gitFolder,
|
|
107
|
+
gitRef: config.gitRef,
|
|
108
|
+
});
|
|
109
|
+
await runPromise("bash machine-startup.sh", { cwd: os.homedir() });
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
}
|
|
88
113
|
|
|
89
114
|
let forwardedCallbacks = new Map<string, string>();
|
|
90
115
|
|
|
91
|
-
const MachineController = SocketFunction.register(
|
|
116
|
+
export const MachineController = getSyncedController(SocketFunction.register(
|
|
92
117
|
"machine-controller-c3157d4a-580c-4e76-9dc9-072dd92e70af",
|
|
93
118
|
() => new MachineControllerBase(),
|
|
94
119
|
() => ({
|
|
95
120
|
streamScreenOutput: {},
|
|
96
121
|
watchOtherScreenOutput: {},
|
|
122
|
+
deployMachineFromBrowser: {},
|
|
123
|
+
deployMachine: {},
|
|
97
124
|
}),
|
|
98
125
|
() => ({
|
|
99
126
|
hooks: [assertIsManagementUser],
|
|
100
127
|
}),
|
|
101
|
-
)
|
|
128
|
+
), {
|
|
129
|
+
writes: {
|
|
130
|
+
deployMachineFromBrowser: ["MachineInfo"],
|
|
131
|
+
deployMachine: ["MachineInfo"],
|
|
132
|
+
}
|
|
133
|
+
});
|
|
102
134
|
|
|
103
135
|
let callbacks = new Map<string, (data: string) => Promise<void>>();
|
|
104
136
|
export async function watchScreenOutput(config: {
|
|
@@ -110,7 +142,7 @@ export async function watchScreenOutput(config: {
|
|
|
110
142
|
}) {
|
|
111
143
|
let callbackId = config.callbackId;
|
|
112
144
|
callbacks.set(callbackId, config.onData);
|
|
113
|
-
await MachineController
|
|
145
|
+
await MachineController(SocketFunction.browserNodeId()).watchOtherScreenOutput.promise({
|
|
114
146
|
nodeId: config.nodeId,
|
|
115
147
|
key: config.key,
|
|
116
148
|
index: config.index,
|
|
@@ -13,12 +13,42 @@ import { getOwnMachineId } from "../-a-auth/certs";
|
|
|
13
13
|
import { runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
14
14
|
import { lazy } from "socket-function/src/caching";
|
|
15
15
|
import { errorToUndefinedSilent } from "../errors";
|
|
16
|
-
import { getGitRefLive, getGitURLLive } from "../4-deploy/git";
|
|
16
|
+
import { commitAndPush, getGitRefInfo, getGitRefLive, getGitURLLive, getGitUncommitted, getLatestRefOnUpstreamBranch, setGitRef } from "../4-deploy/git";
|
|
17
17
|
import { OnServiceChange } from "./machineController";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import { runPromise } from "../functional/runCommand";
|
|
18
21
|
|
|
19
22
|
export const SERVICE_FOLDER = "machine-services/";
|
|
20
23
|
export const MACHINE_RESYNC_INTERVAL = timeInMinute * 15;
|
|
21
24
|
|
|
25
|
+
export type MachineInfo = {
|
|
26
|
+
machineId: string;
|
|
27
|
+
|
|
28
|
+
// Used to tell the apply tool to update it's configs now
|
|
29
|
+
applyNodeId: string;
|
|
30
|
+
|
|
31
|
+
heartbeat: number;
|
|
32
|
+
/*
|
|
33
|
+
// TODO: ShowMore on each of the infos, so large ones are fine.
|
|
34
|
+
hostnamectl (fallback to hostname)
|
|
35
|
+
getExternalIP()
|
|
36
|
+
lscpu
|
|
37
|
+
id (fallback to whoami)
|
|
38
|
+
*/
|
|
39
|
+
info: Record<string, string>;
|
|
40
|
+
|
|
41
|
+
repoUrl: string;
|
|
42
|
+
gitRef: string;
|
|
43
|
+
|
|
44
|
+
services: Record<string, {
|
|
45
|
+
lastLaunchedTime: number;
|
|
46
|
+
errorFromLastRun: string;
|
|
47
|
+
// Only times launched for the current applyNodeId, but... still very useful.
|
|
48
|
+
totalTimesLaunched: number;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
|
|
22
52
|
|
|
23
53
|
export type ServiceConfig = {
|
|
24
54
|
/** Just a random id to manage the service */
|
|
@@ -50,38 +80,13 @@ export type ServiceConfig = {
|
|
|
50
80
|
lastUpdatedTime: number;
|
|
51
81
|
};
|
|
52
82
|
};
|
|
53
|
-
|
|
54
|
-
export type MachineInfo = {
|
|
55
|
-
machineId: string;
|
|
56
|
-
|
|
57
|
-
// Used to tell the apply tool to update it's configs now
|
|
58
|
-
applyNodeId: string;
|
|
59
|
-
|
|
60
|
-
heartbeat: number;
|
|
61
|
-
/*
|
|
62
|
-
// TODO: ShowMore on each of the infos, so large ones are fine.
|
|
63
|
-
hostnamectl (fallback to hostname)
|
|
64
|
-
getExternalIP()
|
|
65
|
-
lscpu
|
|
66
|
-
id (fallback to whoami)
|
|
67
|
-
*/
|
|
68
|
-
info: Record<string, string>;
|
|
69
|
-
|
|
70
|
-
services: Record<string, {
|
|
71
|
-
lastLaunchedTime: number;
|
|
72
|
-
errorFromLastRun: string;
|
|
73
|
-
// Only times launched for the current applyNodeId, but... still very useful.
|
|
74
|
-
totalTimesLaunched: number;
|
|
75
|
-
}>;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => nestArchives("machines/service-configs/", getArchivesBackblaze(getDomain())));
|
|
79
83
|
export const machineInfos = archiveJSONT<MachineInfo>(() => nestArchives("machines/machine-heartbeats/", getArchivesBackblaze(getDomain())));
|
|
84
|
+
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => nestArchives("machines/service-configs/", getArchivesBackblaze(getDomain())));
|
|
80
85
|
|
|
81
86
|
|
|
82
|
-
export class
|
|
87
|
+
export class MachineServiceControllerBase {
|
|
83
88
|
|
|
84
|
-
public async getMachineList()
|
|
89
|
+
public async getMachineList() {
|
|
85
90
|
return [...new Set([
|
|
86
91
|
...(await machineInfos.keys()),
|
|
87
92
|
])];
|
|
@@ -151,17 +156,28 @@ export class DeployControllerBase {
|
|
|
151
156
|
await this.notifyMachines(config.machineIds, []);
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
public async
|
|
155
|
-
|
|
156
|
-
let
|
|
159
|
+
public async setServiceConfigs(configs: ServiceConfig[]) {
|
|
160
|
+
let newMachines = new Set<string>();
|
|
161
|
+
let oldMachines = new Set<string>();
|
|
162
|
+
for (let config of configs) {
|
|
163
|
+
let serviceId = config.serviceId;
|
|
164
|
+
config.info.lastUpdatedTime = Date.now();
|
|
165
|
+
let serviceConfig = await serviceConfigs.get(serviceId);
|
|
157
166
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
if (!serviceConfig) {
|
|
168
|
+
throw new Error(`Service ${serviceId} does not exist`);
|
|
169
|
+
}
|
|
161
170
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
// Set the new service config
|
|
172
|
+
await serviceConfigs.set(serviceId, config);
|
|
173
|
+
for (let machineId of config.machineIds) {
|
|
174
|
+
newMachines.add(machineId);
|
|
175
|
+
}
|
|
176
|
+
for (let machineId of serviceConfig.machineIds) {
|
|
177
|
+
oldMachines.add(machineId);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
await this.notifyMachines([...newMachines], [...oldMachines]);
|
|
165
181
|
}
|
|
166
182
|
|
|
167
183
|
public async deleteServiceConfig(serviceId: string) {
|
|
@@ -180,19 +196,86 @@ export class DeployControllerBase {
|
|
|
180
196
|
}
|
|
181
197
|
public async getGitInfo() {
|
|
182
198
|
let repoUrl = await getGitURLLive();
|
|
183
|
-
let
|
|
199
|
+
let uncommitted = await getGitUncommitted(".");
|
|
200
|
+
let latestRef = await getLatestRefOnUpstreamBranch(".");
|
|
201
|
+
let querysubFolder = path.resolve("../querysub");
|
|
202
|
+
let querysubRef = "";
|
|
203
|
+
let querysubUncommitted: string[] = [];
|
|
204
|
+
if (fs.existsSync(querysubFolder)) {
|
|
205
|
+
querysubRef = await getGitRefLive(querysubFolder);
|
|
206
|
+
querysubUncommitted = await getGitUncommitted(querysubFolder);
|
|
207
|
+
}
|
|
184
208
|
return {
|
|
185
209
|
repoUrl,
|
|
186
|
-
|
|
210
|
+
latestRef,
|
|
211
|
+
uncommitted,
|
|
212
|
+
querysubRef,
|
|
213
|
+
querysubUncommitted,
|
|
187
214
|
};
|
|
188
215
|
}
|
|
216
|
+
|
|
217
|
+
public async getGitRefInfo(config: {
|
|
218
|
+
ref: string;
|
|
219
|
+
useQuerysub?: boolean;
|
|
220
|
+
}) {
|
|
221
|
+
if (!config.ref) return undefined;
|
|
222
|
+
try {
|
|
223
|
+
let gitDir = ".";
|
|
224
|
+
if (config.useQuerysub) {
|
|
225
|
+
gitDir = path.resolve("../querysub");
|
|
226
|
+
}
|
|
227
|
+
return await getGitRefInfo({
|
|
228
|
+
gitDir,
|
|
229
|
+
ref: config.ref,
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(error);
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public async commitPushService(commitMessage: string) {
|
|
238
|
+
await commitAndPush({
|
|
239
|
+
gitDir: ".",
|
|
240
|
+
message: commitMessage,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
public async commitPushAndPublishQuerysub(commitMessage: string) {
|
|
244
|
+
let querysubFolder = path.resolve("../querysub");
|
|
245
|
+
if (!fs.existsSync(querysubFolder)) {
|
|
246
|
+
throw new Error(`Querysub folder does not exist at ${querysubFolder}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Increment minor version and publish to npm with yarn
|
|
250
|
+
await runPromise("yarn version --minor --no-git-tag-version", { cwd: querysubFolder });
|
|
251
|
+
await runPromise("yarn publish --non-interactive", { cwd: querysubFolder });
|
|
252
|
+
|
|
253
|
+
// Update package.json reference in our actual repo (version only, no install)
|
|
254
|
+
let querysubPackageJson = JSON.parse(fs.readFileSync(path.join(querysubFolder, "package.json"), "utf8"));
|
|
255
|
+
let newVersion = querysubPackageJson.version;
|
|
256
|
+
|
|
257
|
+
let currentPackageJsonPath = path.join(".", "package.json");
|
|
258
|
+
let currentPackageJson = JSON.parse(fs.readFileSync(currentPackageJsonPath, "utf8"));
|
|
259
|
+
|
|
260
|
+
if (!currentPackageJson.dependencies?.querysub) {
|
|
261
|
+
throw new Error(`Querysub is not a dependency in package.json?`);
|
|
262
|
+
}
|
|
263
|
+
currentPackageJson.dependencies.querysub = `^${newVersion}`;
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(currentPackageJsonPath, JSON.stringify(currentPackageJson, null, 2));
|
|
266
|
+
|
|
267
|
+
await commitAndPush({
|
|
268
|
+
gitDir: querysubFolder,
|
|
269
|
+
message: commitMessage,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
189
272
|
}
|
|
190
273
|
|
|
191
274
|
|
|
192
|
-
export const
|
|
275
|
+
export const MachineServiceController = getSyncedController(
|
|
193
276
|
SocketFunction.register(
|
|
194
|
-
"
|
|
195
|
-
() => new
|
|
277
|
+
"machine-controller-eda94f05-5e4d-4f5a-b1c1-98613fba60b8",
|
|
278
|
+
() => new MachineServiceControllerBase(),
|
|
196
279
|
() => ({
|
|
197
280
|
getMachineList: {},
|
|
198
281
|
deleteMachineIds: {},
|
|
@@ -202,10 +285,13 @@ export const DeployController = getSyncedController(
|
|
|
202
285
|
getServiceList: {},
|
|
203
286
|
getServiceConfig: {},
|
|
204
287
|
addServiceConfig: {},
|
|
205
|
-
|
|
288
|
+
setServiceConfigs: {},
|
|
206
289
|
getServiceConfigType: {},
|
|
207
290
|
deleteServiceConfig: {},
|
|
208
291
|
getGitInfo: {},
|
|
292
|
+
getGitRefInfo: {},
|
|
293
|
+
commitPushService: {},
|
|
294
|
+
commitPushAndPublishQuerysub: {},
|
|
209
295
|
}),
|
|
210
296
|
() => ({
|
|
211
297
|
hooks: [assertIsManagementUser],
|
|
@@ -217,14 +303,17 @@ export const DeployController = getSyncedController(
|
|
|
217
303
|
addMachineInfo: ["MachineInfo", "MachineInfoList"],
|
|
218
304
|
setMachineInfo: ["MachineInfo"],
|
|
219
305
|
addServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
220
|
-
|
|
306
|
+
setServiceConfigs: ["ServiceConfig"],
|
|
221
307
|
deleteServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
308
|
+
commitPushService: ["gitInfo", "ServiceConfig"],
|
|
309
|
+
commitPushAndPublishQuerysub: ["gitInfo", "ServiceConfig"],
|
|
222
310
|
},
|
|
223
311
|
reads: {
|
|
224
312
|
getMachineList: ["MachineInfoList"],
|
|
225
313
|
getMachineInfo: ["MachineInfo"],
|
|
226
314
|
getServiceList: ["ServiceConfigList"],
|
|
227
315
|
getServiceConfig: ["ServiceConfig"],
|
|
316
|
+
getGitInfo: ["gitInfo"],
|
|
228
317
|
}
|
|
229
318
|
}
|
|
230
319
|
);
|