Skip to content
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
55 changes: 54 additions & 1 deletion apps/server/src/cli/connect.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import * as RelayClient from "@t3tools/shared/relayClient";
import { assert, it } from "@effect/vitest";
import * as Cause from "effect/Cause";
import * as Console from "effect/Console";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Logger from "effect/Logger";
import * as Option from "effect/Option";
import * as References from "effect/References";

import { acquireRelayClientForLink } from "./connect.ts";
import { acquireRelayClientForLink, reportCloudDisconnectResults } from "./connect.ts";

const managedExecutable = {
status: "available",
Expand Down Expand Up @@ -100,3 +105,51 @@ it.effect("reuses an available relay client executable without prompting", () =>
assert.equal(promptCalls, 0);
}),
);

it.effect("keeps disconnect causes in structured logs and out of console warnings", () => {
const warnings: ReadonlyArray<unknown>[] = [];
const logs: Readonly<Record<string, unknown>>[] = [];
const testConsole = {
...globalThis.console,
warn: (...args: ReadonlyArray<unknown>) => {
warnings.push(args);
},
} satisfies Console.Console;
const logger = Logger.make(({ fiber }) => {
logs.push(fiber.getRef(References.CurrentLogAnnotations));
});
const liveFailure = "live unlink private diagnostic";
const relayFailure = "relay revoke private diagnostic";

return reportCloudDisconnectResults({
clearAuthorization: true,
liveResult: {
status: "failed",
cause: Cause.fail(new Error(liveFailure)),
},
relayResult: Exit.failCause(Cause.die(new Error(relayFailure))),
}).pipe(
Effect.provideService(Console.Console, testConsole),
Effect.provide(Logger.layer([logger], { mergeWithExisting: false })),
Effect.tap(() =>
Effect.sync(() => {
assert.lengthOf(warnings, 2);
const warningText = warnings.flat().map(String).join("\n");
assert.include(warningText, "running server could not stop its tunnel");
assert.include(warningText, "Could not revoke the relay-side environment record");
assert.notInclude(warningText, liveFailure);
assert.notInclude(warningText, relayFailure);
assert.deepEqual(
logs.map(({ operation, clearAuthorization }) => ({ operation, clearAuthorization })),
[
{ operation: "live-server-unlink", clearAuthorization: true },
{ operation: "relay-environment-unlink", clearAuthorization: true },
],
);
const loggedCauses = logs.map((log) => String(log.cause)).join("\n");
assert.include(loggedCauses, liveFailure);
assert.include(loggedCauses, relayFailure);
}),
),
);
});
76 changes: 58 additions & 18 deletions apps/server/src/cli/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { RelayOkResponse } from "@t3tools/contracts/relay";
import * as RelayClient from "@t3tools/shared/relayClient";
import { withRelayClientTracing } from "@t3tools/shared/relayTracing";
import * as Cause from "effect/Cause";
import * as Console from "effect/Console";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -179,7 +180,7 @@ const withCloudCliSessionToken = <A, E, R>(
type LiveCloudActionResult =
| { readonly status: "not-running" }
| { readonly status: "succeeded" }
| { readonly status: "failed"; readonly cause: unknown };
| { readonly status: "failed"; readonly cause: Cause.Cause<unknown> };

const runLiveCloudUnlink = Effect.fn("cloud.cli.run_live_unlink")(function* () {
const config = yield* ServerConfig.ServerConfig;
Expand Down Expand Up @@ -211,6 +212,21 @@ type RelayUnlinkResult =
| { readonly status: "revoked" }
| { readonly status: "not-linked" };

type CloudDisconnectOperation = "live-server-unlink" | "relay-environment-unlink";

const logCloudDisconnectFailure = (
operation: CloudDisconnectOperation,
clearAuthorization: boolean,
cause: Cause.Cause<unknown>,
) =>
Effect.logWarning("T3 Connect disconnect operation failed.").pipe(
Effect.annotateLogs({
operation,
clearAuthorization,
cause: Cause.pretty(cause),
}),
);

const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(function* () {
const tokens = yield* CliTokenManager.CloudCliTokenManager;
const token = yield* tokens.getExisting;
Expand All @@ -236,6 +252,42 @@ const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(f
: ({ status: "not-linked" } satisfies RelayUnlinkResult);
});

export const reportCloudDisconnectResults = Effect.fn("cloud.cli.report_disconnect_results")(
function* (input: {
readonly clearAuthorization: boolean;
readonly liveResult: LiveCloudActionResult;
readonly relayResult: Exit.Exit<RelayUnlinkResult, unknown>;
}) {
if (input.liveResult.status === "failed") {
yield* logCloudDisconnectFailure(
"live-server-unlink",
input.clearAuthorization,
input.liveResult.cause,
);
yield* Console.warn(
"T3 Connect is disabled, but the running server could not stop its tunnel.\nRestart that server to stop the connector.",
);
} else {
yield* Console.log("T3 Connect is disabled locally.");
}

if (Exit.isFailure(input.relayResult)) {
yield* logCloudDisconnectFailure(
"relay-environment-unlink",
input.clearAuthorization,
input.relayResult.cause,
);
yield* Console.warn(
input.clearAuthorization
? "Could not revoke the relay-side environment record before signing out.\nThe stored CLI authorization was still removed locally."
: "Could not revoke the relay-side environment record yet.\nRun `t3 connect unlink` again when the relay is reachable.",
);
} else if (input.relayResult.value.status === "revoked") {
yield* Console.log("Revoked the relay-side environment record.");
}
},
);

const disconnectCloud = Effect.fn("cloud.cli.disconnect")(function* (options: {
readonly clearAuthorization: boolean;
}) {
Expand All @@ -249,23 +301,11 @@ const disconnectCloud = Effect.fn("cloud.cli.disconnect")(function* (options: {
yield* tokens.clear;
}

if (liveResult.status === "failed") {
yield* Console.warn(
`T3 Connect is disabled, but the running server could not stop its tunnel: ${String(liveResult.cause)}\nRestart that server to stop the connector.`,
);
} else {
yield* Console.log("T3 Connect is disabled locally.");
}

if (Exit.isFailure(relayResult)) {
yield* Console.warn(
options.clearAuthorization
? `Could not revoke the relay-side environment record before signing out: ${String(relayResult.cause)}\nThe stored CLI authorization was still removed locally.`
: `Could not revoke the relay-side environment record yet: ${String(relayResult.cause)}\nRun \`t3 connect unlink\` again when the relay is reachable.`,
);
} else if (relayResult.value.status === "revoked") {
yield* Console.log("Revoked the relay-side environment record.");
}
yield* reportCloudDisconnectResults({
clearAuthorization: options.clearAuthorization,
liveResult,
relayResult,
});

if (options.clearAuthorization) {
yield* Console.log("Signed out of T3 Connect locally.");
Expand Down
Loading