feat: add CI deployment and live progress
This commit is contained in:
+107
-15
@@ -98,7 +98,20 @@ export type CredentialMetadata = {
|
||||
secretName?: string;
|
||||
};
|
||||
|
||||
export type OperationExecution = { signal?: AbortSignal };
|
||||
export type OperationProgressEmitter = (event: {
|
||||
resource: {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
namespace?: string;
|
||||
};
|
||||
phase: "apply" | "wait" | "delete";
|
||||
state: "started" | "succeeded" | "failed" | "aborted";
|
||||
}) => Promise<void>;
|
||||
export type OperationExecution = {
|
||||
signal?: AbortSignal;
|
||||
emit?: OperationProgressEmitter;
|
||||
};
|
||||
|
||||
export type ManagementDependencies = {
|
||||
readNamespace(project: string): Promise<NamespaceRecord | undefined>;
|
||||
@@ -222,6 +235,20 @@ function resourceName(resource: KubernetesObject): string {
|
||||
return name;
|
||||
}
|
||||
|
||||
function resourceProgressIdentity(
|
||||
resource: Pick<
|
||||
ResourceIdentity,
|
||||
"apiVersion" | "kind" | "name" | "namespace"
|
||||
>,
|
||||
) {
|
||||
return {
|
||||
apiVersion: resource.apiVersion,
|
||||
kind: resource.kind,
|
||||
name: resource.name,
|
||||
...(resource.namespace && { namespace: resource.namespace }),
|
||||
};
|
||||
}
|
||||
|
||||
function assertResourceOwnership(
|
||||
workspace: Workspace,
|
||||
resource: KubernetesObject,
|
||||
@@ -426,8 +453,22 @@ export function createManagementService(dependencies: ManagementDependencies) {
|
||||
}
|
||||
}
|
||||
for (const resource of resources) {
|
||||
throwIfExecutionAborted(execution);
|
||||
await dependencies.deleteResource(resource, execution);
|
||||
const event = {
|
||||
resource: resourceProgressIdentity(resource),
|
||||
phase: "delete" as const,
|
||||
};
|
||||
try {
|
||||
throwIfExecutionAborted(execution);
|
||||
await execution?.emit?.({ ...event, state: "started" });
|
||||
await dependencies.deleteResource(resource, execution);
|
||||
await execution?.emit?.({ ...event, state: "succeeded" });
|
||||
} catch (error) {
|
||||
await execution?.emit?.({
|
||||
...event,
|
||||
state: execution?.signal?.aborted ? "aborted" : "failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +563,12 @@ export function createManagementService(dependencies: ManagementDependencies) {
|
||||
}
|
||||
for (const name of selected) {
|
||||
throwIfExecutionAborted(execution);
|
||||
await dependencies.scaleDeployment(workspace.project, name, 0, execution);
|
||||
await dependencies.scaleDeployment(
|
||||
workspace.project,
|
||||
name,
|
||||
0,
|
||||
execution,
|
||||
);
|
||||
}
|
||||
return selected;
|
||||
},
|
||||
@@ -535,7 +581,11 @@ export function createManagementService(dependencies: ManagementDependencies) {
|
||||
const selected = await targets(workspace, names);
|
||||
for (const name of selected) {
|
||||
throwIfExecutionAborted(execution);
|
||||
await dependencies.restartDeployment(workspace.project, name, execution);
|
||||
await dependencies.restartDeployment(
|
||||
workspace.project,
|
||||
name,
|
||||
execution,
|
||||
);
|
||||
}
|
||||
return selected;
|
||||
},
|
||||
@@ -554,7 +604,11 @@ export function createManagementService(dependencies: ManagementDependencies) {
|
||||
);
|
||||
for (const candidate of candidates) {
|
||||
throwIfExecutionAborted(execution);
|
||||
await dependencies.rollbackDeployment(workspace.project, candidate, execution);
|
||||
await dependencies.rollbackDeployment(
|
||||
workspace.project,
|
||||
candidate,
|
||||
execution,
|
||||
);
|
||||
}
|
||||
for (const candidate of candidates) {
|
||||
throwIfExecutionAborted(execution);
|
||||
@@ -710,8 +764,27 @@ export function createManagementService(dependencies: ManagementDependencies) {
|
||||
for (const resource of sortResources(
|
||||
resources.map((item) => labelDesired(workspace, item)),
|
||||
)) {
|
||||
throwIfExecutionAborted(execution);
|
||||
applied.push(await dependencies.applyResource(resource, execution));
|
||||
const event = {
|
||||
resource: resourceProgressIdentity({
|
||||
apiVersion: resource.apiVersion!,
|
||||
kind: resource.kind!,
|
||||
name: resourceName(resource),
|
||||
namespace: resource.metadata?.namespace,
|
||||
}),
|
||||
phase: "apply" as const,
|
||||
};
|
||||
try {
|
||||
throwIfExecutionAborted(execution);
|
||||
await execution?.emit?.({ ...event, state: "started" });
|
||||
applied.push(await dependencies.applyResource(resource, execution));
|
||||
await execution?.emit?.({ ...event, state: "succeeded" });
|
||||
} catch (error) {
|
||||
await execution?.emit?.({
|
||||
...event,
|
||||
state: execution?.signal?.aborted ? "aborted" : "failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
},
|
||||
@@ -724,13 +797,32 @@ export function createManagementService(dependencies: ManagementDependencies) {
|
||||
): Promise<void> {
|
||||
const selected = await targets(workspace, deploymentTargets);
|
||||
for (const name of selected) {
|
||||
throwIfExecutionAborted(execution);
|
||||
await dependencies.waitForDeployment(
|
||||
workspace.project,
|
||||
name,
|
||||
timeoutMs,
|
||||
execution,
|
||||
);
|
||||
const event = {
|
||||
resource: {
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deployment",
|
||||
name,
|
||||
namespace: workspace.project,
|
||||
},
|
||||
phase: "wait" as const,
|
||||
};
|
||||
try {
|
||||
throwIfExecutionAborted(execution);
|
||||
await execution?.emit?.({ ...event, state: "started" });
|
||||
await dependencies.waitForDeployment(
|
||||
workspace.project,
|
||||
name,
|
||||
timeoutMs,
|
||||
execution,
|
||||
);
|
||||
await execution?.emit?.({ ...event, state: "succeeded" });
|
||||
} catch (error) {
|
||||
await execution?.emit?.({
|
||||
...event,
|
||||
state: execution?.signal?.aborted ? "aborted" : "failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user