@highstate/backend 0.9.16 → 0.9.19
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/chunk-5WVU2AK4.js +1535 -0
- package/dist/chunk-5WVU2AK4.js.map +1 -0
- package/dist/chunk-I7BWSAN6.js +49 -0
- package/dist/chunk-I7BWSAN6.js.map +1 -0
- package/dist/{chunk-RCB4AFGD.js → chunk-VB4YL327.js} +51 -71
- package/dist/chunk-VB4YL327.js.map +1 -0
- package/dist/database/local/prisma.config.js +26 -0
- package/dist/database/local/prisma.config.js.map +1 -0
- package/dist/highstate.manifest.json +5 -4
- package/dist/index.js +7676 -6634
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +8 -6
- package/dist/library/package-resolution-worker.js.map +1 -1
- package/dist/library/worker/main.js +63 -58
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +3 -216
- package/dist/shared/index.js.map +1 -1
- package/package.json +23 -11
- package/prisma/backend/_schema/layout.prisma +7 -0
- package/prisma/backend/_schema/library.prisma +17 -0
- package/prisma/backend/_schema/project.prisma +101 -0
- package/prisma/backend/_schema/pulumi.prisma +17 -0
- package/prisma/backend/postgresql/main.prisma +17 -0
- package/prisma/backend/sqlite/main.prisma +17 -0
- package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
- package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
- package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
- package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
- package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
- package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
- package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
- package/prisma/project/api-key.prisma +27 -0
- package/prisma/project/artifact.prisma +52 -0
- package/prisma/project/custom-status.prisma +46 -0
- package/prisma/project/evaluation.prisma +35 -0
- package/prisma/project/instance.prisma +160 -0
- package/prisma/project/layout.prisma +23 -0
- package/prisma/project/lock.prisma +18 -0
- package/prisma/project/main.prisma +17 -0
- package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
- package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
- package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
- package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
- package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
- package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
- package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
- package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
- package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
- package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
- package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
- package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
- package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
- package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
- package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
- package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
- package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
- package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
- package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
- package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
- package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
- package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
- package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
- package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
- package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
- package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
- package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
- package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
- package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
- package/prisma/project/migrations/migration_lock.toml +3 -0
- package/prisma/project/model.prisma +37 -0
- package/prisma/project/operation.prisma +148 -0
- package/prisma/project/page.prisma +41 -0
- package/prisma/project/secret.prisma +42 -0
- package/prisma/project/service-account.prisma +36 -0
- package/prisma/project/terminal.prisma +90 -0
- package/prisma/project/trigger.prisma +31 -0
- package/prisma/project/unlock-method.prisma +32 -0
- package/prisma/project/worker.prisma +138 -0
- package/src/artifact/abstractions.ts +13 -13
- package/src/artifact/encryption.ts +31 -15
- package/src/artifact/factory.ts +7 -10
- package/src/artifact/local.ts +33 -50
- package/src/business/api-key.ts +24 -36
- package/src/business/artifact.test.ts +978 -0
- package/src/business/artifact.ts +136 -215
- package/src/business/evaluation.ts +328 -0
- package/src/business/index.ts +5 -1
- package/src/business/instance-lock.test.ts +1060 -0
- package/src/business/instance-lock.ts +387 -77
- package/src/business/instance-state.test.ts +735 -0
- package/src/business/instance-state.ts +604 -217
- package/src/business/operation.test.ts +439 -0
- package/src/business/operation.ts +174 -208
- package/src/business/project-model.ts +258 -0
- package/src/business/project-unlock.ts +172 -112
- package/src/business/project.ts +407 -0
- package/src/business/secret.test.ts +513 -0
- package/src/business/secret.ts +194 -131
- package/src/business/settings.test.ts +695 -0
- package/src/business/settings.ts +855 -0
- package/src/business/terminal-session.ts +90 -0
- package/src/business/unit-extra.test.ts +539 -0
- package/src/business/unit-extra.ts +160 -0
- package/src/business/worker.test.ts +391 -0
- package/src/business/worker.ts +250 -114
- package/src/common/codebase.ts +65 -0
- package/src/common/index.ts +3 -2
- package/src/common/logger.ts +5 -0
- package/src/common/utils.ts +4 -3
- package/src/config.ts +15 -12
- package/src/database/_generated/backend/postgresql/client.ts +72 -0
- package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
- package/src/database/_generated/backend/postgresql/enums.ts +13 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
- package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
- package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
- package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
- package/src/database/_generated/backend/postgresql/models.ts +16 -0
- package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
- package/src/database/_generated/backend/sqlite/client.ts +72 -0
- package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
- package/src/database/_generated/backend/sqlite/enums.ts +13 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
- package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
- package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
- package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
- package/src/database/_generated/backend/sqlite/models.ts +16 -0
- package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
- package/src/database/_generated/project/client.ts +204 -0
- package/src/database/_generated/project/commonInputTypes.ts +827 -0
- package/src/database/_generated/project/enums.ts +104 -0
- package/src/database/_generated/project/internal/class.ts +479 -0
- package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
- package/src/database/_generated/project/models/ApiKey.ts +1506 -0
- package/src/database/_generated/project/models/Artifact.ts +2051 -0
- package/src/database/_generated/project/models/HubModel.ts +1125 -0
- package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
- package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
- package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
- package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
- package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
- package/src/database/_generated/project/models/InstanceState.ts +4613 -0
- package/src/database/_generated/project/models/Operation.ts +1647 -0
- package/src/database/_generated/project/models/OperationLog.ts +1455 -0
- package/src/database/_generated/project/models/Page.ts +1838 -0
- package/src/database/_generated/project/models/Secret.ts +1692 -0
- package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
- package/src/database/_generated/project/models/Terminal.ts +2038 -0
- package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
- package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
- package/src/database/_generated/project/models/Trigger.ts +1430 -0
- package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
- package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
- package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
- package/src/database/_generated/project/models/Worker.ts +1459 -0
- package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
- package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
- package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
- package/src/database/_generated/project/models.ts +35 -0
- package/src/database/_generated/project/pjtg.ts +182 -0
- package/src/database/abstractions.ts +19 -0
- package/src/database/factory.ts +37 -0
- package/src/database/index.ts +6 -0
- package/src/database/local/backend.ts +134 -0
- package/src/database/local/index.ts +3 -0
- package/src/database/local/meta.ts +46 -0
- package/src/database/local/prisma.config.ts +25 -0
- package/src/database/local/project.ts +39 -0
- package/src/database/manager.ts +181 -0
- package/src/database/migrate.ts +35 -0
- package/src/database/prisma.ts +56 -0
- package/src/database/well-known.ts +38 -0
- package/src/index.ts +4 -4
- package/src/library/abstractions.ts +21 -14
- package/src/library/factory.ts +1 -1
- package/src/library/local.ts +86 -38
- package/src/library/package-resolution-worker.ts +1 -1
- package/src/library/worker/evaluator.ts +61 -48
- package/src/library/worker/loader.lite.ts +14 -1
- package/src/library/worker/main.ts +9 -16
- package/src/library/worker/protocol.ts +0 -12
- package/src/lock/manager.ts +12 -7
- package/src/orchestrator/manager.ts +198 -131
- package/src/orchestrator/operation-context.ts +357 -0
- package/src/orchestrator/operation-plan.destroy.test.md +357 -0
- package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
- package/src/orchestrator/operation-plan.fixtures.ts +213 -0
- package/src/orchestrator/operation-plan.md +198 -0
- package/src/orchestrator/operation-plan.refresh.test.md +199 -0
- package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
- package/src/orchestrator/operation-plan.ts +709 -0
- package/src/orchestrator/operation-plan.update.test.md +485 -0
- package/src/orchestrator/operation-plan.update.test.ts +1066 -0
- package/src/orchestrator/operation-workset.ts +235 -583
- package/src/orchestrator/operation.ts +446 -904
- package/src/orchestrator/plan-test-builder.ts +267 -0
- package/src/project-model/abstractions.ts +118 -0
- package/src/project-model/backends/codebase.ts +365 -0
- package/src/project-model/backends/database.ts +440 -0
- package/src/project-model/errors.ts +81 -0
- package/src/project-model/factory.ts +24 -0
- package/src/project-model/index.ts +4 -0
- package/src/project-model/utils.test.ts +544 -0
- package/src/project-model/utils.ts +242 -0
- package/src/pubsub/abstractions.ts +10 -1
- package/src/pubsub/factory.ts +4 -4
- package/src/pubsub/index.ts +1 -0
- package/src/pubsub/manager.ts +49 -25
- package/src/pubsub/memory.ts +31 -0
- package/src/runner/abstractions.ts +38 -26
- package/src/runner/artifact-env.ts +17 -6
- package/src/runner/factory.ts +6 -6
- package/src/runner/force-abort.ts +3 -6
- package/src/runner/local.ts +79 -72
- package/src/runner/pulumi.ts +26 -63
- package/src/services.ts +214 -103
- package/src/shared/models/backend/index.ts +3 -1
- package/src/shared/models/backend/library.ts +12 -4
- package/src/shared/models/backend/project.ts +43 -23
- package/src/shared/models/backend/pulumi.ts +14 -0
- package/src/shared/models/backend/unlock-method.ts +1 -1
- package/src/shared/models/backend/well-known.ts +58 -0
- package/src/shared/models/base.ts +40 -109
- package/src/shared/models/errors.ts +82 -1
- package/src/shared/models/index.ts +3 -2
- package/src/shared/models/prisma.ts +36 -0
- package/src/shared/models/project/api-key.ts +37 -56
- package/src/shared/models/project/artifact.ts +15 -105
- package/src/shared/models/project/custom-status.ts +12 -0
- package/src/shared/models/project/index.ts +9 -9
- package/src/shared/models/project/lock.ts +10 -78
- package/src/shared/models/project/model.ts +32 -0
- package/src/shared/models/project/operation.ts +222 -99
- package/src/shared/models/project/page.ts +37 -48
- package/src/shared/models/project/secret.ts +29 -103
- package/src/shared/models/project/service-account.ts +12 -17
- package/src/shared/models/project/state.ts +100 -390
- package/src/shared/models/project/terminal.ts +75 -89
- package/src/shared/models/project/trigger.ts +13 -49
- package/src/shared/models/project/unlock-method.ts +21 -20
- package/src/shared/models/project/worker.ts +89 -88
- package/src/shared/resolvers/graph-resolver.ts +62 -26
- package/src/shared/resolvers/index.ts +1 -1
- package/src/shared/resolvers/input-hash.ts +24 -14
- package/src/shared/resolvers/input.ts +48 -6
- package/src/shared/resolvers/registry.ts +5 -4
- package/src/shared/resolvers/state.ts +12 -1
- package/src/shared/resolvers/validation.ts +29 -9
- package/src/shared/utils/index.ts +1 -1
- package/src/shared/utils/promise-tracker.ts +30 -3
- package/src/terminal/abstractions.ts +1 -1
- package/src/terminal/docker.ts +3 -3
- package/src/terminal/manager.ts +102 -118
- package/src/test-utils/database.ts +119 -0
- package/src/test-utils/index.ts +2 -0
- package/src/test-utils/services.ts +134 -0
- package/src/unlock/abstractions.ts +31 -0
- package/src/unlock/index.ts +2 -0
- package/src/unlock/memory.ts +27 -0
- package/src/worker/abstractions.ts +7 -4
- package/src/worker/docker.ts +14 -19
- package/src/worker/manager.ts +376 -79
- package/dist/chunk-RCB4AFGD.js.map +0 -1
- package/dist/chunk-WHALQHEZ.js +0 -2017
- package/dist/chunk-WHALQHEZ.js.map +0 -1
- package/src/business/backend-unlock.ts +0 -10
- package/src/common/performance.ts +0 -44
- package/src/hotstate/abstractions.ts +0 -48
- package/src/hotstate/factory.ts +0 -17
- package/src/hotstate/index.ts +0 -3
- package/src/hotstate/manager.ts +0 -192
- package/src/hotstate/memory.ts +0 -100
- package/src/hotstate/validation.ts +0 -101
- package/src/project/abstractions.ts +0 -102
- package/src/project/factory.ts +0 -11
- package/src/project/index.ts +0 -3
- package/src/project/local.ts +0 -469
- package/src/project/manager.ts +0 -574
- package/src/pubsub/local.ts +0 -36
- package/src/pubsub/validation.ts +0 -33
- package/src/shared/models/project/component.ts +0 -45
- package/src/shared/models/project/instance.ts +0 -74
- package/src/state/abstractions.ts +0 -450
- package/src/state/encryption.ts +0 -59
- package/src/state/factory.ts +0 -20
- package/src/state/index.ts +0 -6
- package/src/state/local/backend.ts +0 -299
- package/src/state/local/collection.ts +0 -342
- package/src/state/local/index.ts +0 -2
- package/src/state/manager.ts +0 -819
- package/src/state/repository/index.ts +0 -2
- package/src/state/repository/repository.index.ts +0 -193
- package/src/state/repository/repository.ts +0 -458
- /package/src/{state → database/local}/keyring.ts +0 -0
|
@@ -1,499 +1,236 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
1
|
+
import type { InstanceId } from "@highstate/contract"
|
|
2
|
+
import type { InstanceStateService, UpdateOperationStateOptions } from "../business"
|
|
3
|
+
import type { InstanceOperationStatus } from "../database"
|
|
4
|
+
import type { InstanceStatus, OperationPhase, OperationPhaseType, ProjectOutput } from "../shared"
|
|
5
|
+
import type { OperationContext } from "./operation-context"
|
|
6
6
|
import { EventEmitter, on } from "node:events"
|
|
7
|
-
import {
|
|
8
|
-
isUnitModel,
|
|
9
|
-
parseInstanceId,
|
|
10
|
-
type ComponentModel,
|
|
11
|
-
type InstanceModel,
|
|
12
|
-
} from "@highstate/contract"
|
|
13
|
-
import { unique } from "remeda"
|
|
14
|
-
import { BetterLock } from "better-lock"
|
|
15
|
-
import {
|
|
16
|
-
InputHashResolver,
|
|
17
|
-
InputResolver,
|
|
18
|
-
type InputHashNode,
|
|
19
|
-
type InputHashOutput,
|
|
20
|
-
type InputResolverNode,
|
|
21
|
-
type InstanceOperataionStatusEnum,
|
|
22
|
-
type InstanceState,
|
|
23
|
-
type InstanceStatePatch,
|
|
24
|
-
type LibraryModel,
|
|
25
|
-
type Operation,
|
|
26
|
-
type ResolvedInstanceInput,
|
|
27
|
-
} from "../shared"
|
|
28
|
-
|
|
29
|
-
export type OperationPhase = "update" | "destroy" | "refresh"
|
|
7
|
+
import { mapValues } from "remeda"
|
|
30
8
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
private readonly instanceIdsToUpdate = new Set<string>()
|
|
36
|
-
private readonly instanceIdsToDestroy = new Set<string>()
|
|
37
|
-
|
|
38
|
-
private readonly instanceMap = new Map<string, InstanceModel>()
|
|
39
|
-
private readonly instanceChildrenMap = new Map<string, InstanceModel[]>()
|
|
40
|
-
|
|
41
|
-
private readonly initialStateMap = new Map<string, InstanceState>()
|
|
42
|
-
private readonly stateMap = new Map<string, InstanceState>()
|
|
43
|
-
private readonly dependentStateIdMap = new Map<string, Set<string>>()
|
|
44
|
-
private readonly stateChildIdMap = new Map<string, string[]>()
|
|
45
|
-
|
|
46
|
-
private readonly unitSourceHashMap = new Map<string, number>()
|
|
47
|
-
|
|
48
|
-
private inputResolver!: InputResolver
|
|
49
|
-
private readonly inputResolverNodes = new Map<string, InputResolverNode>()
|
|
50
|
-
|
|
51
|
-
private inputHashResolver!: InputHashResolver
|
|
52
|
-
private readonly inputHashNodes = new Map<string, InputHashNode>()
|
|
53
|
-
private readonly inputHashResolverLock = new BetterLock()
|
|
9
|
+
type AbortControllerPair = {
|
|
10
|
+
abortController: AbortController
|
|
11
|
+
forceAbortController: AbortController
|
|
12
|
+
}
|
|
54
13
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
>()
|
|
14
|
+
export class OperationWorkset {
|
|
15
|
+
readonly abortController = new AbortController()
|
|
16
|
+
private readonly forceAbortController = new AbortController()
|
|
59
17
|
|
|
60
|
-
|
|
18
|
+
readonly instanceAbortControllers = new Map<string, AbortControllerPair>()
|
|
61
19
|
|
|
20
|
+
private readonly lockedStateIds = new Set<string>()
|
|
62
21
|
private readonly lockEventEmitter = new EventEmitter()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
22
|
+
private currentPhaseIndex = -1
|
|
23
|
+
|
|
24
|
+
currentPhase!: OperationPhaseType
|
|
25
|
+
allAffectedInstanceIds!: ReadonlySet<InstanceId>
|
|
26
|
+
allAffectedStateIds!: ReadonlySet<string>
|
|
27
|
+
phaseAffectedInstanceIds!: ReadonlySet<InstanceId>
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
readonly project: ProjectOutput,
|
|
31
|
+
readonly operationId: string,
|
|
32
|
+
readonly phases: OperationPhase[],
|
|
33
|
+
private readonly context: OperationContext,
|
|
70
34
|
private readonly instanceStateService: InstanceStateService,
|
|
71
|
-
|
|
72
|
-
|
|
35
|
+
) {
|
|
36
|
+
const affectedInstanceIds = new Set<InstanceId>()
|
|
37
|
+
const affectedStateIds = new Set<string>()
|
|
73
38
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
39
|
+
for (const phase of phases) {
|
|
40
|
+
for (const instance of phase.instances) {
|
|
41
|
+
affectedInstanceIds.add(instance.id)
|
|
78
42
|
|
|
79
|
-
|
|
80
|
-
|
|
43
|
+
const state = this.context.getState(instance.id)
|
|
44
|
+
affectedStateIds.add(state.id)
|
|
45
|
+
}
|
|
81
46
|
}
|
|
82
|
-
}
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!instance) {
|
|
87
|
-
throw new Error(`Instance with ID ${instanceId} not found in the operation workset`)
|
|
88
|
-
}
|
|
48
|
+
this.allAffectedInstanceIds = affectedInstanceIds
|
|
49
|
+
this.allAffectedStateIds = affectedStateIds
|
|
89
50
|
|
|
90
|
-
|
|
51
|
+
// we will basically create one listener per affected instance
|
|
52
|
+
this.lockEventEmitter.setMaxListeners(this.allAffectedInstanceIds.size)
|
|
91
53
|
}
|
|
92
54
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return Array.from(this.instanceIdsToDestroy)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return Array.from(this.instanceIdsToUpdate)
|
|
55
|
+
hasRemainingPhases(): boolean {
|
|
56
|
+
return this.currentPhaseIndex < this.phases.length - 1
|
|
99
57
|
}
|
|
100
58
|
|
|
101
|
-
|
|
102
|
-
return
|
|
59
|
+
getLockedStateIds(): Iterable<string> {
|
|
60
|
+
return this.lockedStateIds
|
|
103
61
|
}
|
|
104
62
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
63
|
+
isLastPhaseForInstance(instanceId: InstanceId): boolean {
|
|
64
|
+
if (!this.hasRemainingPhases()) {
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
108
67
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
68
|
+
// TODO: create instance id sets for each phase on initialization to speed this up
|
|
69
|
+
for (let i = this.currentPhaseIndex + 1; i < this.phases.length; i++) {
|
|
70
|
+
if (this.phases[i].instances.find(i => i.id === instanceId)) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
112
73
|
}
|
|
113
74
|
|
|
114
|
-
return
|
|
75
|
+
return true
|
|
115
76
|
}
|
|
116
77
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.operation,
|
|
121
|
-
patch,
|
|
122
|
-
)
|
|
123
|
-
this.stateMap.set(state.id, state)
|
|
124
|
-
|
|
125
|
-
if (state.parentId) {
|
|
126
|
-
await this.recalculateCompositeInstanceState(state.parentId)
|
|
78
|
+
nextPhase(): void {
|
|
79
|
+
if (!this.hasRemainingPhases()) {
|
|
80
|
+
throw new Error("No remaining phases")
|
|
127
81
|
}
|
|
128
82
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
public setState(state: InstanceState): void {
|
|
133
|
-
this.stateMap.set(state.id, state)
|
|
134
|
-
this.initialStateMap.set(state.id, state)
|
|
83
|
+
this.currentPhaseIndex++
|
|
84
|
+
this.currentPhase = this.phases[this.currentPhaseIndex].type
|
|
135
85
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.stateChildIdMap.set(state.parentId, children)
|
|
141
|
-
}
|
|
86
|
+
this.phaseAffectedInstanceIds = new Set(
|
|
87
|
+
this.phases[this.currentPhaseIndex].instances.map(i => i.id),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
142
90
|
|
|
143
|
-
|
|
91
|
+
async waitForInstanceLock(stateId: string, signal: AbortSignal): Promise<void> {
|
|
92
|
+
if (this.lockedStateIds.has(stateId)) {
|
|
93
|
+
return
|
|
144
94
|
}
|
|
145
95
|
|
|
146
|
-
for (const
|
|
147
|
-
|
|
96
|
+
for await (const _ of on(this.lockEventEmitter, stateId, { signal })) {
|
|
97
|
+
return
|
|
148
98
|
}
|
|
149
99
|
}
|
|
150
100
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
dependentStates = new Set<string>()
|
|
156
|
-
this.dependentStateIdMap.set(dependencyId, dependentStates)
|
|
157
|
-
}
|
|
101
|
+
markInstanceLocked(stateId: string): void {
|
|
102
|
+
this.lockedStateIds.add(stateId)
|
|
103
|
+
this.lockEventEmitter.emit(stateId)
|
|
104
|
+
}
|
|
158
105
|
|
|
159
|
-
|
|
106
|
+
markInstanceUnlocked(stateId: string): void {
|
|
107
|
+
this.lockedStateIds.delete(stateId)
|
|
160
108
|
}
|
|
161
109
|
|
|
162
|
-
|
|
163
|
-
const
|
|
110
|
+
async setupOperationStates(): Promise<void> {
|
|
111
|
+
const patches = await this.instanceStateService.createOperationStates(
|
|
112
|
+
this.project.id,
|
|
113
|
+
Array.from(this.allAffectedInstanceIds).map(instanceId => {
|
|
114
|
+
const instance = this.context.getInstance(instanceId)
|
|
115
|
+
const state = this.context.getState(instanceId)
|
|
116
|
+
|
|
117
|
+
const resolvedInputs = mapValues(
|
|
118
|
+
//
|
|
119
|
+
this.context.getResolvedInputs(instance.id) ?? {},
|
|
120
|
+
inputs => inputs.map(input => input.input),
|
|
121
|
+
)
|
|
164
122
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
stateId: state.id,
|
|
126
|
+
operationId: this.operationId,
|
|
127
|
+
status: "pending",
|
|
128
|
+
model: instance,
|
|
129
|
+
resolvedInputs,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
status: state.status === "undeployed" ? "attempted" : state.status,
|
|
133
|
+
model: instance,
|
|
134
|
+
resolvedInputs,
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
}),
|
|
169
138
|
)
|
|
170
139
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
140
|
+
for (const patch of patches) {
|
|
141
|
+
// biome-ignore lint/style/noNonNullAssertion: we know it's there (should be (please))
|
|
142
|
+
const state = this.context.getState(patch.instanceId!)
|
|
143
|
+
Object.assign(state, patch)
|
|
175
144
|
}
|
|
176
145
|
}
|
|
177
146
|
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
for (const instanceId of this.getAffectedInstanceIds()) {
|
|
182
|
-
const state = this.initialStateMap.get(instanceId)
|
|
183
|
-
if (state) {
|
|
184
|
-
instanceStates.push(state)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
147
|
+
async updateState(instanceId: InstanceId, options: UpdateOperationStateOptions): Promise<void> {
|
|
148
|
+
const state = this.context.getState(instanceId)
|
|
187
149
|
|
|
188
|
-
await this.instanceStateService.
|
|
189
|
-
|
|
150
|
+
const patch = await this.instanceStateService.updateOperationState(
|
|
151
|
+
this.project.id,
|
|
152
|
+
state.id,
|
|
153
|
+
this.operationId,
|
|
154
|
+
options,
|
|
155
|
+
)
|
|
190
156
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return []
|
|
157
|
+
if (state.parentInstanceId) {
|
|
158
|
+
// TODO: update all updates in single transaction
|
|
159
|
+
await this.recalculateCompositeInstanceState(state.parentInstanceId)
|
|
195
160
|
}
|
|
196
161
|
|
|
197
|
-
|
|
198
|
-
return children.filter(child => this.instanceIdsToDestroy.has(child.id))
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return children.filter(child => this.instanceIdsToUpdate.has(child.id))
|
|
162
|
+
Object.assign(state, patch)
|
|
202
163
|
}
|
|
203
164
|
|
|
204
|
-
|
|
205
|
-
return this.stateMap.get(instanceId)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
public getParentId(instanceId: string): string | undefined {
|
|
165
|
+
getAffectedCompositeChildren(instanceId: InstanceId): InstanceId[] {
|
|
209
166
|
if (this.currentPhase === "destroy") {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return state.parentId
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const instance = this.getInstance(instanceId)
|
|
219
|
-
|
|
220
|
-
return instance.parentId
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
public getDependentStates(instanceId: string): InstanceState[] {
|
|
224
|
-
const dependentStateIds = this.dependentStateIdMap.get(instanceId)
|
|
225
|
-
if (!dependentStateIds) {
|
|
226
|
-
return []
|
|
167
|
+
// when destroying, only consider children fixed in the state
|
|
168
|
+
return this.context
|
|
169
|
+
.getStateChildIds(instanceId)
|
|
170
|
+
.filter(child => this.phaseAffectedInstanceIds.has(child))
|
|
227
171
|
}
|
|
228
172
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.
|
|
173
|
+
// for other phases, consider all children defined in the model
|
|
174
|
+
return this.context
|
|
175
|
+
.getInstanceChildren(instanceId)
|
|
176
|
+
.map(child => child.id)
|
|
177
|
+
.filter(childId => this.phaseAffectedInstanceIds.has(childId))
|
|
232
178
|
}
|
|
233
179
|
|
|
234
|
-
private async recalculateCompositeInstanceState(instanceId:
|
|
235
|
-
const state = this.
|
|
236
|
-
if (!state) {
|
|
237
|
-
this.logger.warn(
|
|
238
|
-
`cannot recalculate composite instance state for "${instanceId}" because it is not in the state map`,
|
|
239
|
-
)
|
|
240
|
-
return
|
|
241
|
-
}
|
|
180
|
+
private async recalculateCompositeInstanceState(instanceId: InstanceId): Promise<void> {
|
|
181
|
+
const state = this.context.getState(instanceId)
|
|
242
182
|
|
|
243
183
|
let currentResourceCount = 0
|
|
244
184
|
let totalResourceCount = 0
|
|
185
|
+
let knownTotatalResourceCount = 0
|
|
245
186
|
|
|
246
|
-
const children = this.
|
|
187
|
+
const children = this.context.getStateChildIds(instanceId)
|
|
247
188
|
for (const childId of children) {
|
|
248
|
-
const child = this.
|
|
189
|
+
const child = this.context.getState(childId)
|
|
249
190
|
|
|
250
|
-
if (child?.
|
|
251
|
-
currentResourceCount += child.
|
|
191
|
+
if (child?.lastOperationState?.currentResourceCount) {
|
|
192
|
+
currentResourceCount += child.lastOperationState.currentResourceCount
|
|
252
193
|
}
|
|
253
194
|
|
|
254
|
-
if (child?.
|
|
255
|
-
totalResourceCount += child.
|
|
195
|
+
if (child?.lastOperationState?.totalResourceCount) {
|
|
196
|
+
totalResourceCount += child.lastOperationState.totalResourceCount
|
|
197
|
+
knownTotatalResourceCount += 1
|
|
256
198
|
}
|
|
257
199
|
}
|
|
258
200
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
status: this.getTransientStatusByOperationPhase(),
|
|
263
|
-
currentResourceCount,
|
|
264
|
-
totalResourceCount,
|
|
265
|
-
},
|
|
266
|
-
}
|
|
201
|
+
// extrapolate total resource count for other resources without total resource count
|
|
202
|
+
const averageTotalResourceCount =
|
|
203
|
+
knownTotatalResourceCount > 0 ? Math.round(totalResourceCount / knownTotatalResourceCount) : 0
|
|
267
204
|
|
|
268
|
-
|
|
269
|
-
|
|
205
|
+
const notKnownTotalResourceCount = children.length - knownTotatalResourceCount
|
|
206
|
+
totalResourceCount += notKnownTotalResourceCount * averageTotalResourceCount
|
|
270
207
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
208
|
+
const finalTotalResourceCount =
|
|
209
|
+
this.currentPhase === "destroy" && state.lastOperationState?.totalResourceCount
|
|
210
|
+
? // do not override totalResourceCount with lower values when destroying instances
|
|
211
|
+
Math.min(totalResourceCount, state.lastOperationState.totalResourceCount)
|
|
212
|
+
: totalResourceCount
|
|
275
213
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.instanceMap.set(instance.id, instance)
|
|
284
|
-
|
|
285
|
-
if (instance.parentId) {
|
|
286
|
-
let children = this.instanceChildrenMap.get(instance.parentId)
|
|
287
|
-
if (!children) {
|
|
288
|
-
children = []
|
|
289
|
-
this.instanceChildrenMap.set(instance.parentId, children)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
children.push(instance)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private async calculateInstanceIdsToUpdate(): Promise<void> {
|
|
297
|
-
const traverse = async (instanceId: string) => {
|
|
298
|
-
if (this.instanceIdsToUpdate.has(instanceId)) {
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const instance = this.instanceMap.get(instanceId)
|
|
303
|
-
if (!instance) {
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const instanceInputs = this.resolvedInstanceInputs.get(instance.id) ?? {}
|
|
308
|
-
|
|
309
|
-
for (const inputs of Object.values(instanceInputs)) {
|
|
310
|
-
for (const input of inputs) {
|
|
311
|
-
await traverse(input.input.instanceId)
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const state = this.stateMap.get(instance.id)
|
|
316
|
-
const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(instance.id)
|
|
317
|
-
|
|
318
|
-
if (this.operation.options.forceUpdateDependencies) {
|
|
319
|
-
this.instanceIdsToUpdate.add(instanceId)
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (
|
|
324
|
-
!state?.operationStatus ||
|
|
325
|
-
state.operationStatus.status === "error" ||
|
|
326
|
-
state.operationStatus.inputHash !== expectedInputHash
|
|
327
|
-
) {
|
|
328
|
-
this.instanceIdsToUpdate.add(instanceId)
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// 1. extend affected instance IDs with their not-created or not-up-to-date dependencies (only for "update" operations)
|
|
333
|
-
for (const instanceId of this.operation.requestedInstanceIds) {
|
|
334
|
-
if (this.operation.type === "update") {
|
|
335
|
-
await traverse(instanceId)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
this.instanceIdsToUpdate.add(instanceId)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// 2. extend affected instance IDs with the children of the affected composite instances
|
|
342
|
-
const compositeInstanceQueue = Array.from(this.instanceIdsToUpdate)
|
|
343
|
-
while (compositeInstanceQueue.length > 0) {
|
|
344
|
-
const childId = compositeInstanceQueue.pop()!
|
|
345
|
-
const children = this.instanceChildrenMap.get(childId) ?? []
|
|
346
|
-
|
|
347
|
-
for (const child of children) {
|
|
348
|
-
compositeInstanceQueue.push(child.id)
|
|
349
|
-
|
|
350
|
-
if (this.operation.options.forceUpdateChildren) {
|
|
351
|
-
this.instanceIdsToUpdate.add(child.id)
|
|
352
|
-
continue
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const state = this.stateMap.get(child.id)
|
|
356
|
-
const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(child.id)
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
!state?.operationStatus ||
|
|
360
|
-
state.operationStatus.status === "error" ||
|
|
361
|
-
state.operationStatus.inputHash !== expectedInputHash
|
|
362
|
-
) {
|
|
363
|
-
this.instanceIdsToUpdate.add(child.id)
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// 3. detect composite instance children and include their parents (recursively)
|
|
369
|
-
for (const instanceId of this.instanceIdsToUpdate) {
|
|
370
|
-
let instance = this.instanceMap.get(instanceId)
|
|
371
|
-
while (instance?.parentId) {
|
|
372
|
-
this.instanceIdsToUpdate.add(instance.parentId)
|
|
373
|
-
instance = this.instanceMap.get(instance.parentId)
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// 4. if "allowPartialCompositeInstanceUpdates" is not set, include all children of the composite instances
|
|
378
|
-
if (!this.operation.options.allowPartialCompositeInstanceUpdates) {
|
|
379
|
-
for (const instanceId of this.instanceIdsToUpdate) {
|
|
380
|
-
const children = this.instanceChildrenMap.get(instanceId) ?? []
|
|
381
|
-
for (const child of children) {
|
|
382
|
-
this.instanceIdsToUpdate.add(child.id)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
this.operation.instanceIdsToUpdate = Array.from(this.instanceIdsToUpdate)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
private calculateInstanceIdsToDestroy() {
|
|
391
|
-
const traverse = (instanceKey: string) => {
|
|
392
|
-
if (this.instanceIdsToDestroy.has(instanceKey)) {
|
|
393
|
-
return
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const state = this.stateMap.get(instanceKey)
|
|
397
|
-
if (!state) {
|
|
398
|
-
return
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const dependentIds = this.dependentStateIdMap.get(instanceKey) ?? []
|
|
402
|
-
|
|
403
|
-
for (const dependentId of dependentIds) {
|
|
404
|
-
traverse(dependentId)
|
|
405
|
-
this.instanceIdsToDestroy.add(dependentId)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (this.operation.type === "destroy" || this.operation.type === "recreate") {
|
|
410
|
-
// 1.a. extend affected instance IDs with their created dependents (if not forbidden by the operation options)
|
|
411
|
-
for (const instanceId of this.operation.requestedInstanceIds) {
|
|
412
|
-
const instance = this.instanceMap.get(instanceId)
|
|
413
|
-
if (!instance) {
|
|
414
|
-
throw new Error(`Instance not found: ${instanceId}`)
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (this.operation.options.destroyDependentInstances) {
|
|
418
|
-
traverse(instance.id)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
this.instanceIdsToDestroy.add(instanceId)
|
|
422
|
-
}
|
|
423
|
-
} else if (this.operation.type === "update") {
|
|
424
|
-
// 1.b. find all children instances of the affected to-update instances which are in the state map, but not in the instance map
|
|
425
|
-
// in other words, this code cleans up child instances which are not produced by composite instances anymore
|
|
426
|
-
|
|
427
|
-
for (const instanceId of this.operation.instanceIdsToUpdate) {
|
|
428
|
-
const [type] = parseInstanceId(instanceId)
|
|
429
|
-
const component = this.library.components[type]
|
|
430
|
-
|
|
431
|
-
if (!component || isUnitModel(component)) {
|
|
432
|
-
// ignore non-composite instances
|
|
433
|
-
continue
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const childrenQueue = [...(this.stateChildIdMap.get(instanceId) ?? [])]
|
|
437
|
-
|
|
438
|
-
while (childrenQueue.length > 0) {
|
|
439
|
-
const childId = childrenQueue.pop()!
|
|
440
|
-
if (!this.instanceMap.has(childId)) {
|
|
441
|
-
this.instanceIdsToDestroy.add(childId)
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
childrenQueue.push(...(this.stateChildIdMap.get(childId) ?? []))
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
return
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// 2. extend affected instance IDs with the children of the affected composite instances
|
|
452
|
-
const compositeInstanceQueue = Array.from(this.instanceIdsToDestroy)
|
|
453
|
-
while (compositeInstanceQueue.length > 0) {
|
|
454
|
-
const childId = compositeInstanceQueue.pop()!
|
|
455
|
-
const children = this.stateChildIdMap.get(childId) ?? []
|
|
456
|
-
|
|
457
|
-
for (const child of children) {
|
|
458
|
-
compositeInstanceQueue.push(child)
|
|
459
|
-
this.instanceIdsToDestroy.add(child)
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// 3. detect composite instance children and include their parents (recursively)
|
|
464
|
-
for (const instanceId of this.instanceIdsToDestroy) {
|
|
465
|
-
let state = this.stateMap.get(instanceId)
|
|
466
|
-
while (state?.parentId) {
|
|
467
|
-
this.instanceIdsToDestroy.add(state.parentId)
|
|
468
|
-
state = this.stateMap.get(state.parentId)
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// 4. if "allowPartialCompositeInstanceUpdates" is not set, include all children of the composite instances
|
|
473
|
-
if (!this.operation.options.allowPartialCompositeInstanceUpdates) {
|
|
474
|
-
for (const instanceId of this.instanceIdsToDestroy) {
|
|
475
|
-
const children = this.stateChildIdMap.get(instanceId) ?? []
|
|
476
|
-
for (const childId of children) {
|
|
477
|
-
this.instanceIdsToDestroy.add(childId)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
this.operation.instanceIdsToDestroy = Array.from(this.instanceIdsToDestroy)
|
|
214
|
+
await this.updateState(instanceId, {
|
|
215
|
+
operationState: {
|
|
216
|
+
status: this.getTransientStatusByOperationPhase(),
|
|
217
|
+
currentResourceCount,
|
|
218
|
+
totalResourceCount: finalTotalResourceCount,
|
|
219
|
+
},
|
|
220
|
+
})
|
|
483
221
|
}
|
|
484
222
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (isUnitModel(component)) {
|
|
490
|
-
return this.unitSourceHashMap.get(instance.type)
|
|
223
|
+
getPhaseParentId(instanceId: InstanceId): InstanceId | null {
|
|
224
|
+
if (this.currentPhase === "destroy") {
|
|
225
|
+
// when destroying, only consider parent fixed in the state
|
|
226
|
+
return this.context.getState(instanceId).parentInstanceId ?? null
|
|
491
227
|
}
|
|
492
228
|
|
|
493
|
-
|
|
229
|
+
// for other phases, consider parent defined in the model
|
|
230
|
+
return this.context.getInstance(instanceId).parentId ?? null
|
|
494
231
|
}
|
|
495
232
|
|
|
496
|
-
|
|
233
|
+
getTransientStatusByOperationPhase(): InstanceOperationStatus {
|
|
497
234
|
switch (this.currentPhase) {
|
|
498
235
|
case "update":
|
|
499
236
|
return "updating"
|
|
@@ -504,193 +241,108 @@ export class OperationWorkset {
|
|
|
504
241
|
}
|
|
505
242
|
}
|
|
506
243
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
state: this.stateMap.get(instance.id),
|
|
516
|
-
sourceHash: this.getSourceHashIfApplicable(instance, component),
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
this.inputHashResolver.invalidate(instance.id)
|
|
520
|
-
await this.inputHashResolver.process()
|
|
521
|
-
|
|
522
|
-
return this.inputHashResolver.requireOutput(instance.id)
|
|
523
|
-
})
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* Tries to acquire not-locked instances in the workset.
|
|
528
|
-
*
|
|
529
|
-
* Should not be called if the whole workset is already locked.
|
|
530
|
-
*
|
|
531
|
-
* If `instancesToLock` is provided, it will try to lock only those instances.
|
|
532
|
-
*/
|
|
533
|
-
public async tryLock(instancesToLock?: string[]): Promise<void> {
|
|
534
|
-
const [, lockedInstanceIds] = await this.instanceLockService.tryLockInstances(
|
|
535
|
-
this.projectId,
|
|
536
|
-
instancesToLock ?? Array.from(this.instanceIdsToLockIds),
|
|
537
|
-
{
|
|
538
|
-
title: "Operation Lock",
|
|
539
|
-
description: `The instance is locked for the ${this.operation.type} operation with ID "${this.operation.id}".`,
|
|
540
|
-
icon: "mdi:cog-sync",
|
|
541
|
-
},
|
|
542
|
-
{ type: "operation", operationId: this.operation.id },
|
|
543
|
-
true, // allow partial locking
|
|
544
|
-
)
|
|
545
|
-
|
|
546
|
-
// add locked instance IDs to the workset and remove them from the instanceIdsToLockIds
|
|
547
|
-
for (const instanceId of lockedInstanceIds) {
|
|
548
|
-
this.lockedInstanceIds.add(instanceId)
|
|
549
|
-
this.instanceIdsToLockIds.delete(instanceId)
|
|
550
|
-
this.lockEventEmitter.emit(instanceId, null)
|
|
244
|
+
getStableStatusByOperationPhase(): InstanceOperationStatus {
|
|
245
|
+
switch (this.currentPhase) {
|
|
246
|
+
case "update":
|
|
247
|
+
return "updated"
|
|
248
|
+
case "destroy":
|
|
249
|
+
return "destroyed"
|
|
250
|
+
case "refresh":
|
|
251
|
+
return "refreshed"
|
|
551
252
|
}
|
|
552
253
|
}
|
|
553
254
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
operation: Operation,
|
|
557
|
-
projectBackend: ProjectBackend,
|
|
558
|
-
libraryBackend: LibraryBackend,
|
|
559
|
-
stateManager: StateManager,
|
|
560
|
-
instanceLockService: InstanceLockService,
|
|
561
|
-
instanceStateService: InstanceStateService,
|
|
562
|
-
logger: Logger,
|
|
563
|
-
signal: AbortSignal,
|
|
564
|
-
): Promise<OperationWorkset> {
|
|
565
|
-
// First get project info to obtain libraryId
|
|
566
|
-
const projectInfo = await projectBackend.getProjectInfo(projectId, signal)
|
|
567
|
-
|
|
568
|
-
// Use StateManager for instance states, StateBackend directly for composite instances
|
|
569
|
-
// TODO: use hotstate for composite instances as well
|
|
570
|
-
const compositeInstanceRepo = stateManager.getCompositeInstanceRepository(projectId)
|
|
571
|
-
|
|
572
|
-
const [library, project, compositeInstances, states] = await Promise.all([
|
|
573
|
-
libraryBackend.loadLibrary(projectInfo.libraryId, signal),
|
|
574
|
-
projectBackend.getProject(projectId, signal),
|
|
575
|
-
compositeInstanceRepo.getAllItems(),
|
|
576
|
-
instanceStateService.getInstanceStates(projectId),
|
|
577
|
-
])
|
|
578
|
-
|
|
579
|
-
const workset = new OperationWorkset(
|
|
580
|
-
projectId,
|
|
581
|
-
operation,
|
|
582
|
-
library,
|
|
583
|
-
projectInfo.libraryId,
|
|
584
|
-
instanceLockService,
|
|
585
|
-
instanceStateService,
|
|
586
|
-
logger.child({ operationId: operation.id, service: "OperationWorkset" }),
|
|
587
|
-
)
|
|
255
|
+
getNextStableInstanceStatus(instanceId: InstanceId): InstanceStatus {
|
|
256
|
+
const state = this.context.getState(instanceId)
|
|
588
257
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
258
|
+
switch (this.currentPhase) {
|
|
259
|
+
case "update":
|
|
260
|
+
return "deployed"
|
|
261
|
+
case "destroy":
|
|
262
|
+
return "undeployed"
|
|
263
|
+
case "refresh":
|
|
264
|
+
return state.status // do not change instance status when refreshing
|
|
592
265
|
}
|
|
266
|
+
}
|
|
593
267
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (worksetInstance) {
|
|
598
|
-
worksetInstance.outputs = instance.instance.outputs
|
|
599
|
-
worksetInstance.resolvedOutputs = instance.instance.resolvedOutputs
|
|
600
|
-
} else if (instance.instance.parentId) {
|
|
601
|
-
workset.addInstance(instance.instance)
|
|
602
|
-
} else {
|
|
603
|
-
workset.logger.warn(
|
|
604
|
-
`ignoring instance "${instance.instance.id}" from composite instance state because it is not in the project or is not a part of known composite instance`,
|
|
605
|
-
)
|
|
606
|
-
continue
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
for (const child of instance.children) {
|
|
610
|
-
if (!workset.instanceMap.has(child.id)) {
|
|
611
|
-
workset.addInstance(child)
|
|
612
|
-
}
|
|
613
|
-
}
|
|
268
|
+
setupAbortControllersForAllInstances(): void {
|
|
269
|
+
for (const instanceId of this.allAffectedInstanceIds) {
|
|
270
|
+
this.setupInstanceAbortControllers(instanceId)
|
|
614
271
|
}
|
|
272
|
+
}
|
|
615
273
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
for (const unitSource of unitSources) {
|
|
622
|
-
workset.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash)
|
|
274
|
+
private setupInstanceAbortControllers(instanceId: InstanceId): AbortControllerPair {
|
|
275
|
+
const existingPair = this.instanceAbortControllers.get(instanceId)
|
|
276
|
+
if (existingPair) {
|
|
277
|
+
return existingPair
|
|
623
278
|
}
|
|
624
279
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
`ignoring instance "${state.id}" from state because it is not in the project or is not a part of a composite instance`,
|
|
629
|
-
)
|
|
630
|
-
continue
|
|
631
|
-
}
|
|
280
|
+
// create abort controllers and setup them to abort when the operation is aborted
|
|
281
|
+
const abortController = new AbortController()
|
|
282
|
+
this.abortController.signal.addEventListener("abort", () => abortController.abort())
|
|
632
283
|
|
|
633
|
-
|
|
634
|
-
|
|
284
|
+
abortController.signal.addEventListener("abort", () => {
|
|
285
|
+
// notify frontend that the instance is being cancelled
|
|
286
|
+
this.updateState(instanceId, { operationState: { status: "cancelling" } })
|
|
287
|
+
})
|
|
635
288
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
workset.inputResolverNodes.set(`instance:${instance.id}`, {
|
|
639
|
-
kind: "instance",
|
|
640
|
-
instance,
|
|
641
|
-
component: library.components[instance.type],
|
|
642
|
-
})
|
|
643
|
-
}
|
|
289
|
+
const forceAbortController = new AbortController()
|
|
290
|
+
this.forceAbortController.signal.addEventListener("abort", () => forceAbortController.abort())
|
|
644
291
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
292
|
+
const pair: AbortControllerPair = { abortController, forceAbortController }
|
|
293
|
+
this.instanceAbortControllers.set(instanceId, pair)
|
|
648
294
|
|
|
649
|
-
|
|
650
|
-
|
|
295
|
+
// abort if the parent instance is cancelled
|
|
296
|
+
const children = this.context.getInstanceChildren(instanceId)
|
|
297
|
+
for (const child of children) {
|
|
298
|
+
const childPair = this.setupInstanceAbortControllers(child.id)
|
|
651
299
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
// resolve inputs for all instances and pass outputs to input hash resolver
|
|
655
|
-
for (const instance of workset.instanceMap.values()) {
|
|
656
|
-
const output = workset.inputResolver.requireOutput(`instance:${instance.id}`)
|
|
657
|
-
if (output.kind !== "instance") {
|
|
658
|
-
throw new Error("Unexpected output kind")
|
|
659
|
-
}
|
|
300
|
+
abortController.signal.addEventListener("abort", () => childPair.abortController.abort())
|
|
660
301
|
|
|
661
|
-
|
|
302
|
+
forceAbortController.signal.addEventListener("abort", () =>
|
|
303
|
+
childPair.forceAbortController.abort(),
|
|
304
|
+
)
|
|
305
|
+
}
|
|
662
306
|
|
|
663
|
-
|
|
307
|
+
return pair
|
|
308
|
+
}
|
|
664
309
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
state: workset.stateMap.get(instance.id),
|
|
670
|
-
sourceHash: workset.getSourceHashIfApplicable(instance, component),
|
|
671
|
-
})
|
|
310
|
+
cancelInstance(instanceId: InstanceId, allowForceAbort = true): void {
|
|
311
|
+
const abortControllerPair = this.instanceAbortControllers.get(instanceId)
|
|
312
|
+
if (!abortControllerPair) {
|
|
313
|
+
throw new Error(`No abort controller found for instance "${instanceId}"`)
|
|
672
314
|
}
|
|
673
315
|
|
|
674
|
-
|
|
675
|
-
workset.inputHashResolver = new InputHashResolver(workset.inputHashNodes, logger)
|
|
676
|
-
workset.inputHashResolver.addAllNodesToWorkset()
|
|
316
|
+
const { abortController, forceAbortController } = abortControllerPair
|
|
677
317
|
|
|
678
|
-
|
|
318
|
+
// first try to cancel the operation gracefully
|
|
679
319
|
|
|
680
|
-
if (
|
|
681
|
-
|
|
320
|
+
if (!abortController.signal.aborted) {
|
|
321
|
+
abortController.abort()
|
|
322
|
+
return
|
|
682
323
|
}
|
|
683
324
|
|
|
684
|
-
|
|
325
|
+
if (!allowForceAbort) {
|
|
326
|
+
return
|
|
327
|
+
}
|
|
685
328
|
|
|
686
|
-
|
|
687
|
-
|
|
329
|
+
// then try to force cancel the operation
|
|
330
|
+
if (!forceAbortController.signal.aborted) {
|
|
331
|
+
forceAbortController.abort()
|
|
332
|
+
return
|
|
688
333
|
}
|
|
334
|
+
}
|
|
689
335
|
|
|
690
|
-
|
|
691
|
-
|
|
336
|
+
cancel(): void {
|
|
337
|
+
if (!this.abortController.signal.aborted) {
|
|
338
|
+
this.abortController.abort()
|
|
339
|
+
return
|
|
692
340
|
}
|
|
693
341
|
|
|
694
|
-
|
|
342
|
+
// then try to force cancel the operation
|
|
343
|
+
if (!this.forceAbortController.signal.aborted) {
|
|
344
|
+
this.forceAbortController.abort()
|
|
345
|
+
return
|
|
346
|
+
}
|
|
695
347
|
}
|
|
696
348
|
}
|