Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display persistent configs in safe mode #592

Merged
merged 8 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
"js-yaml": "^4.1.0",
"jsdom": "^24.0.0",
"matrix-appservice-bridge": "^9.0.1",
"matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@1.5.2",
"matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@1.5.2",
"matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@1.6.0",
"matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@1.6.0",
"parse-duration": "^1.0.2",
"pg": "^8.8.0",
"shell-quote": "^1.7.3",
Expand Down
28 changes: 22 additions & 6 deletions src/safemode/DraupnirSafeMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ import {
} from "../commands/interface-manager/MatrixPromptForAccept";
import { makeCommandDispatcherTimelineListener } from "./ManagementRoom";
import { SafeModeToggle } from "./SafeModeToggle";
import { Result } from "@gnuxie/typescript-result";
import { Result, isError } from "@gnuxie/typescript-result";
import {
renderSafeModeStatusInfo,
safeModeStatusInfo,
} from "./commands/StatusCommand";
import { wrapInRoot } from "../commands/interface-manager/MatrixHelpRenderer";
import { sendAndAnnotateWithRecoveryOptions } from "./commands/RecoverCommand";
import { StandardPersistentConfigEditor } from "./PersistentConfigEditor";

export class SafeModeDraupnir implements MatrixAdaptorContext {
public reactionHandler: MatrixReactionHandler;
Expand Down Expand Up @@ -118,11 +119,26 @@ export class SafeModeDraupnir implements MatrixAdaptorContext {

public startupComplete(): void {
void Task(
sendAndAnnotateWithRecoveryOptions(
this,
wrapInRoot(renderSafeModeStatusInfo(safeModeStatusInfo(this))),
{}
)
(async () => {
const editor = new StandardPersistentConfigEditor(this.client);
const configStatus = await editor.supplementStatusWithSafeModeCause(
this.cause
);
if (isError(configStatus)) {
return configStatus.elaborate(
"Failed to fetch draupnir's persistent configuration"
);
}
return await sendAndAnnotateWithRecoveryOptions(
this,
wrapInRoot(
renderSafeModeStatusInfo(
safeModeStatusInfo(this.cause, configStatus.ok)
)
),
{}
);
})()
);
}

Expand Down
143 changes: 143 additions & 0 deletions src/safemode/PersistentConfigEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: 2024 Gnuxie <[email protected]>
//
// SPDX-License-Identifier: AFL-3.0

import { Ok, Result, isError } from "@gnuxie/typescript-result";
import {
ConfigDescription,
ConfigParseError,
ConfigPropertyUseError,
MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE,
MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE,
MjolnirEnabledProtectionsDescription,
MjolnirEnabledProtectionsEventType,
MjolnirPolicyRoomsDescription,
MjolnirProtectedRoomsDescription,
PersistentConfigData,
StandardPersistentConfigData,
} from "matrix-protection-suite";
import {
BotSDKAccountDataConfigBackend,
MatrixSendClient,
} from "matrix-protection-suite-for-matrix-bot-sdk";
import { SafeModeCause, SafeModeReason } from "./SafeModeCause";

export type PersistentConfigStatus = {
description: ConfigDescription;
data: unknown;
error: ConfigParseError | undefined;
};

export interface PersistentConfigEditor {
getConfigAdaptors(): PersistentConfigData[];
requestConfigStatus(): Promise<Result<PersistentConfigStatus[]>>;
/**
* requestConfigStatus, but be sure to update the PeristentConfigStatus list
* with the ConfigPropertyUseError in the safe mode cause, if there is one.
*
* This is because `ConfigPropertyUseError`'s will not show up in parsing,
* only when creating Draupnir itself, and so they won't show up from just requesting
* the config alone.
*/
supplementStatusWithSafeModeCause(
cause: SafeModeCause
): Promise<Result<PersistentConfigStatus[]>>;
}

export class StandardPersistentConfigEditor implements PersistentConfigEditor {
private readonly configAdaptors: PersistentConfigData[] = [];
public constructor(client: MatrixSendClient) {
// We do some sweepy sweepy casting here because the ConfigMirror has methods
// that accept a specific shape, and obviously that means the type parameter
// becomes contravariant. I think the only way to fix this is to make the mirrors
// only work with the general shape rather than the specific one, in the way that
// the `remove` methods do, but I'm not convinced that works either, as those
// methods accept a Record that at least has the keys from the specific shape
// of the config. OK that's not why, because I tried to remove the toMirror method.
// I don't understand why it won't work then...
this.configAdaptors = [
new StandardPersistentConfigData(
MjolnirPolicyRoomsDescription as unknown as ConfigDescription,
new BotSDKAccountDataConfigBackend(
client,
MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE
)
),
new StandardPersistentConfigData(
MjolnirProtectedRoomsDescription as unknown as ConfigDescription,
new BotSDKAccountDataConfigBackend(
client,
MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE
)
),
new StandardPersistentConfigData(
MjolnirEnabledProtectionsDescription as unknown as ConfigDescription,
new BotSDKAccountDataConfigBackend(
client,
MjolnirEnabledProtectionsEventType
)
),
];
}
getConfigAdaptors(): PersistentConfigData[] {
return this.configAdaptors;
}

public async requestConfigStatus(): Promise<
Result<PersistentConfigStatus[]>
> {
const info: PersistentConfigStatus[] = [];
for (const adaptor of this.configAdaptors) {
const dataResult = await adaptor.requestConfig();
if (isError(dataResult)) {
if (dataResult.error instanceof ConfigParseError) {
info.push({
description: adaptor.description,
data: dataResult.error.config,
error: dataResult.error,
});
} else {
return dataResult;
}
} else {
info.push({
description: adaptor.description,
data: dataResult.ok,
error: undefined,
});
}
}
return Ok(info);
}
public async supplementStatusWithSafeModeCause(
cause: SafeModeCause
): Promise<Result<PersistentConfigStatus[]>> {
const info = await this.requestConfigStatus();
if (isError(info)) {
return info;
}
if (cause.reason === SafeModeReason.ByRequest) {
return Ok(info.ok);
}
if (!(cause.error instanceof ConfigPropertyUseError)) {
return Ok(info.ok);
}
const relevantStatus = info.ok.find(
(status) =>
status.description ===
(cause.error as ConfigPropertyUseError).configDescription
);
if (relevantStatus === undefined) {
throw new TypeError(
"The cause of the safe mode error was not found in the configuration status."
);
}
relevantStatus.error = new ConfigParseError(
"There was a problem when using a property in the configuration.",
relevantStatus.description as unknown as ConfigDescription,
[cause.error],
relevantStatus.data
);
return Ok(info.ok);
}
}
Loading
Loading