@highstate/backend 0.9.18 → 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-OU5OQBLB.js → chunk-I7BWSAN6.js} +3 -28
- package/dist/{chunk-OU5OQBLB.js.map → chunk-I7BWSAN6.js.map} +1 -1
- package/dist/chunk-VB4YL327.js +139 -0
- 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 +2 -1
- package/dist/index.js +7587 -7291
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +1 -1
- package/dist/library/package-resolution-worker.js.map +1 -1
- package/dist/library/worker/main.js +35 -29
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +2 -2
- package/package.json +18 -9
- 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 +30 -54
- package/src/artifact/factory.ts +6 -9
- package/src/artifact/local.ts +33 -46
- package/src/business/api-key.ts +24 -36
- package/src/business/artifact.test.ts +978 -0
- package/src/business/artifact.ts +136 -216
- package/src/business/evaluation.ts +328 -0
- package/src/business/index.ts +5 -2
- package/src/business/instance-lock.test.ts +1060 -0
- package/src/business/instance-lock.ts +387 -78
- package/src/business/instance-state.test.ts +735 -0
- package/src/business/instance-state.ts +582 -337
- 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 +168 -126
- package/src/business/project.ts +287 -179
- package/src/business/secret.test.ts +465 -130
- package/src/business/secret.ts +186 -217
- 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 +356 -579
- package/src/business/worker.ts +238 -339
- package/src/common/codebase.ts +65 -0
- package/src/common/index.ts +3 -5
- package/src/common/logger.ts +5 -0
- package/src/common/utils.ts +4 -3
- package/src/config.ts +10 -11
- 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 +3 -5
- package/src/library/factory.ts +1 -1
- package/src/library/local.ts +81 -26
- package/src/library/package-resolution-worker.ts +1 -1
- package/src/library/worker/evaluator.ts +40 -23
- package/src/library/worker/loader.lite.ts +1 -1
- package/src/library/worker/main.ts +3 -10
- package/src/library/worker/protocol.ts +0 -1
- package/src/lock/index.ts +0 -1
- package/src/lock/manager.ts +0 -10
- package/src/orchestrator/manager.ts +190 -104
- 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 +233 -578
- package/src/orchestrator/operation.ts +435 -948
- 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 +29 -13
- package/src/pubsub/memory.ts +31 -0
- package/src/runner/abstractions.ts +33 -41
- package/src/runner/artifact-env.ts +19 -8
- package/src/runner/factory.ts +6 -6
- package/src/runner/force-abort.ts +3 -6
- package/src/runner/local.ts +64 -67
- package/src/runner/pulumi.ts +23 -63
- package/src/services.ts +181 -123
- package/src/shared/models/backend/index.ts +3 -1
- package/src/shared/models/backend/library.ts +9 -1
- package/src/shared/models/backend/project.ts +43 -42
- 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 -26
- 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 -59
- package/src/shared/models/project/artifact.ts +16 -76
- package/src/shared/models/project/custom-status.ts +12 -0
- package/src/shared/models/project/index.ts +8 -7
- package/src/shared/models/project/lock.ts +10 -78
- package/src/shared/models/project/model.ts +19 -1
- 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 -89
- package/src/shared/models/project/service-account.ts +12 -17
- package/src/shared/models/project/state.ts +100 -407
- package/src/shared/models/project/terminal.ts +75 -88
- package/src/shared/models/project/trigger.ts +13 -49
- package/src/shared/models/project/unlock-method.ts +20 -26
- package/src/shared/models/project/worker.ts +89 -90
- package/src/shared/resolvers/graph-resolver.ts +21 -0
- package/src/shared/resolvers/index.ts +1 -1
- package/src/shared/resolvers/input-hash.ts +24 -14
- package/src/shared/resolvers/input.ts +1 -1
- package/src/shared/resolvers/registry.ts +5 -4
- package/src/shared/resolvers/state.ts +12 -1
- package/src/shared/resolvers/validation.ts +7 -3
- package/src/shared/utils/index.ts +1 -2
- 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 +5 -23
- package/src/unlock/memory.ts +9 -14
- package/src/worker/abstractions.ts +7 -4
- package/src/worker/docker.ts +14 -19
- package/src/worker/manager.ts +366 -97
- package/dist/chunk-NAAIDR4U.js +0 -8499
- package/dist/chunk-NAAIDR4U.js.map +0 -1
- package/dist/chunk-Y7DXREVO.js +0 -1745
- package/dist/chunk-Y7DXREVO.js.map +0 -1
- package/dist/magic-string.es-5ABAC4JN.js +0 -1292
- package/dist/magic-string.es-5ABAC4JN.js.map +0 -1
- package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +0 -356
- package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +0 -274
- package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +0 -223
- package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +0 -147
- package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +0 -280
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +0 -360
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +0 -215
- package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +0 -427
- package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +0 -217
- package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +0 -132
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +0 -454
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +0 -426
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +0 -372
- package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +0 -383
- package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +0 -245
- package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +0 -174
- package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +0 -432
- package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +0 -220
- package/src/business/backend-unlock.ts +0 -10
- package/src/common/clock.ts +0 -18
- package/src/common/performance.ts +0 -44
- package/src/common/random.ts +0 -68
- package/src/common/test/index.ts +0 -2
- package/src/common/test/render.ts +0 -98
- package/src/common/test/tracer.ts +0 -359
- 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 -100
- package/src/lock/test.ts +0 -108
- package/src/project/abstractions.ts +0 -78
- package/src/project/evaluation.ts +0 -248
- package/src/project/factory.ts +0 -11
- package/src/project/index.ts +0 -3
- package/src/project/local.ts +0 -417
- package/src/pubsub/local.ts +0 -36
- package/src/pubsub/validation.ts +0 -33
- package/src/shared/utils/args.ts +0 -25
- package/src/state/abstractions.ts +0 -289
- package/src/state/encryption.ts +0 -98
- package/src/state/factory.ts +0 -20
- package/src/state/index.ts +0 -7
- package/src/state/local/backend.ts +0 -106
- package/src/state/local/collection.ts +0 -361
- package/src/state/local/index.ts +0 -2
- package/src/state/manager.ts +0 -890
- package/src/state/memory/backend.ts +0 -70
- package/src/state/memory/collection.ts +0 -270
- package/src/state/memory/index.ts +0 -2
- package/src/state/repository/index.ts +0 -2
- package/src/state/repository/repository.index.ts +0 -193
- package/src/state/repository/repository.ts +0 -507
- package/src/state/test.ts +0 -457
- /package/src/{state → database/local}/keyring.ts +0 -0
|
@@ -1,509 +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 Project,
|
|
27
|
-
type ResolvedInstanceInput,
|
|
28
|
-
} from "../shared"
|
|
29
|
-
|
|
30
|
-
export type OperationPhase = "update" | "destroy" | "refresh"
|
|
7
|
+
import { mapValues } from "remeda"
|
|
31
8
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
private readonly instanceIdsToUpdate = new Set<string>()
|
|
37
|
-
private readonly instanceIdsToDestroy = new Set<string>()
|
|
38
|
-
|
|
39
|
-
private readonly instanceMap = new Map<string, InstanceModel>()
|
|
40
|
-
private readonly instanceChildrenMap = new Map<string, InstanceModel[]>()
|
|
41
|
-
|
|
42
|
-
private readonly initialStateMap = new Map<string, InstanceState>()
|
|
43
|
-
private readonly stateMap = new Map<string, InstanceState>()
|
|
44
|
-
private readonly dependentStateIdMap = new Map<string, Set<string>>()
|
|
45
|
-
private readonly stateChildIdMap = new Map<string, string[]>()
|
|
46
|
-
|
|
47
|
-
private readonly unitSourceHashMap = new Map<string, number>()
|
|
48
|
-
|
|
49
|
-
private inputResolver!: InputResolver
|
|
50
|
-
private readonly inputResolverNodes = new Map<string, InputResolverNode>()
|
|
51
|
-
|
|
52
|
-
private inputHashResolver!: InputHashResolver
|
|
53
|
-
private readonly inputHashNodes = new Map<string, InputHashNode>()
|
|
54
|
-
private readonly inputHashResolverLock = new BetterLock()
|
|
9
|
+
type AbortControllerPair = {
|
|
10
|
+
abortController: AbortController
|
|
11
|
+
forceAbortController: AbortController
|
|
12
|
+
}
|
|
55
13
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
>()
|
|
14
|
+
export class OperationWorkset {
|
|
15
|
+
readonly abortController = new AbortController()
|
|
16
|
+
private readonly forceAbortController = new AbortController()
|
|
60
17
|
|
|
61
|
-
|
|
18
|
+
readonly instanceAbortControllers = new Map<string, AbortControllerPair>()
|
|
62
19
|
|
|
20
|
+
private readonly lockedStateIds = new Set<string>()
|
|
63
21
|
private readonly lockEventEmitter = new EventEmitter()
|
|
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
|
-
if (!dependentStates) {
|
|
155
|
-
dependentStates = new Set<string>()
|
|
156
|
-
this.dependentStateIdMap.set(dependencyId, dependentStates)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
dependentStates.add(instanceId)
|
|
101
|
+
markInstanceLocked(stateId: string): void {
|
|
102
|
+
this.lockedStateIds.add(stateId)
|
|
103
|
+
this.lockEventEmitter.emit(stateId)
|
|
160
104
|
}
|
|
161
105
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.logger.debug(
|
|
166
|
-
{ initialState },
|
|
167
|
-
`resetting state for instance "%s" to initial state`,
|
|
168
|
-
instanceId,
|
|
169
|
-
)
|
|
106
|
+
markInstanceUnlocked(stateId: string): void {
|
|
107
|
+
this.lockedStateIds.delete(stateId)
|
|
108
|
+
}
|
|
170
109
|
|
|
171
|
-
|
|
110
|
+
async setupOperationStates(): Promise<void> {
|
|
111
|
+
const patches = await this.instanceStateService.createOperationStates(
|
|
172
112
|
this.project.id,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
)
|
|
122
|
+
|
|
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
|
+
}),
|
|
176
138
|
)
|
|
177
139
|
|
|
178
|
-
|
|
179
|
-
|
|
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)
|
|
180
144
|
}
|
|
181
145
|
}
|
|
182
146
|
|
|
183
|
-
|
|
184
|
-
const
|
|
147
|
+
async updateState(instanceId: InstanceId, options: UpdateOperationStateOptions): Promise<void> {
|
|
148
|
+
const state = this.context.getState(instanceId)
|
|
185
149
|
|
|
186
|
-
|
|
187
|
-
const state = this.initialStateMap.get(instanceId)
|
|
188
|
-
if (state) {
|
|
189
|
-
instanceStates.push(state)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
await this.instanceStateService.updateInstanceStates(
|
|
150
|
+
const patch = await this.instanceStateService.updateOperationState(
|
|
194
151
|
this.project.id,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
152
|
+
state.id,
|
|
153
|
+
this.operationId,
|
|
154
|
+
options,
|
|
198
155
|
)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
public getAffectedCompositeChildren(instanceId: string): InstanceModel[] {
|
|
202
|
-
const children = this.instanceChildrenMap.get(instanceId)
|
|
203
|
-
if (!children) {
|
|
204
|
-
return []
|
|
205
|
-
}
|
|
206
156
|
|
|
207
|
-
if (
|
|
208
|
-
|
|
157
|
+
if (state.parentInstanceId) {
|
|
158
|
+
// TODO: update all updates in single transaction
|
|
159
|
+
await this.recalculateCompositeInstanceState(state.parentInstanceId)
|
|
209
160
|
}
|
|
210
161
|
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
public getState(instanceId: string): InstanceState | undefined {
|
|
215
|
-
return this.stateMap.get(instanceId)
|
|
162
|
+
Object.assign(state, patch)
|
|
216
163
|
}
|
|
217
164
|
|
|
218
|
-
|
|
165
|
+
getAffectedCompositeChildren(instanceId: InstanceId): InstanceId[] {
|
|
219
166
|
if (this.currentPhase === "destroy") {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return state.parentId
|
|
167
|
+
// when destroying, only consider children fixed in the state
|
|
168
|
+
return this.context
|
|
169
|
+
.getStateChildIds(instanceId)
|
|
170
|
+
.filter(child => this.phaseAffectedInstanceIds.has(child))
|
|
226
171
|
}
|
|
227
172
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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))
|
|
231
178
|
}
|
|
232
179
|
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
if (!dependentStateIds) {
|
|
236
|
-
return []
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return Array.from(dependentStateIds)
|
|
240
|
-
.map(id => this.stateMap.get(id))
|
|
241
|
-
.filter((state): state is InstanceState => !!state)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private async recalculateCompositeInstanceState(instanceId: string): Promise<void> {
|
|
245
|
-
const state = this.stateMap.get(instanceId)
|
|
246
|
-
if (!state) {
|
|
247
|
-
this.logger.warn(
|
|
248
|
-
`cannot recalculate composite instance state for "${instanceId}" because it is not in the state map`,
|
|
249
|
-
)
|
|
250
|
-
return
|
|
251
|
-
}
|
|
180
|
+
private async recalculateCompositeInstanceState(instanceId: InstanceId): Promise<void> {
|
|
181
|
+
const state = this.context.getState(instanceId)
|
|
252
182
|
|
|
253
183
|
let currentResourceCount = 0
|
|
254
184
|
let totalResourceCount = 0
|
|
185
|
+
let knownTotatalResourceCount = 0
|
|
255
186
|
|
|
256
|
-
const children = this.
|
|
187
|
+
const children = this.context.getStateChildIds(instanceId)
|
|
257
188
|
for (const childId of children) {
|
|
258
|
-
const child = this.
|
|
259
|
-
|
|
260
|
-
if (child?.operationStatus?.currentResourceCount) {
|
|
261
|
-
currentResourceCount += child.operationStatus.currentResourceCount
|
|
262
|
-
}
|
|
189
|
+
const child = this.context.getState(childId)
|
|
263
190
|
|
|
264
|
-
if (child?.
|
|
265
|
-
|
|
191
|
+
if (child?.lastOperationState?.currentResourceCount) {
|
|
192
|
+
currentResourceCount += child.lastOperationState.currentResourceCount
|
|
266
193
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const patch: InstanceStatePatch = {
|
|
270
|
-
id: instanceId,
|
|
271
|
-
operationStatus: {
|
|
272
|
-
status: this.getTransientStatusByOperationPhase(),
|
|
273
|
-
currentResourceCount,
|
|
274
|
-
totalResourceCount,
|
|
275
|
-
},
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
await this.patchState(patch)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private addInstance(instance: InstanceModel): void {
|
|
282
|
-
if (this.instanceMap.has(instance.id)) {
|
|
283
|
-
throw new Error(`Found multiple instances with the same ID: ${instance.id}`)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (!(instance.type in this.library.components)) {
|
|
287
|
-
this.logger.warn(
|
|
288
|
-
`ignoring instance "${instance.id}" because its type "${instance.type}" is not in the library`,
|
|
289
|
-
)
|
|
290
|
-
return
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
this.instanceMap.set(instance.id, instance)
|
|
294
194
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
children = []
|
|
299
|
-
this.instanceChildrenMap.set(instance.parentId, children)
|
|
195
|
+
if (child?.lastOperationState?.totalResourceCount) {
|
|
196
|
+
totalResourceCount += child.lastOperationState.totalResourceCount
|
|
197
|
+
knownTotatalResourceCount += 1
|
|
300
198
|
}
|
|
301
|
-
|
|
302
|
-
children.push(instance)
|
|
303
199
|
}
|
|
304
|
-
}
|
|
305
200
|
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
return
|
|
310
|
-
}
|
|
201
|
+
// extrapolate total resource count for other resources without total resource count
|
|
202
|
+
const averageTotalResourceCount =
|
|
203
|
+
knownTotatalResourceCount > 0 ? Math.round(totalResourceCount / knownTotatalResourceCount) : 0
|
|
311
204
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return
|
|
315
|
-
}
|
|
205
|
+
const notKnownTotalResourceCount = children.length - knownTotatalResourceCount
|
|
206
|
+
totalResourceCount += notKnownTotalResourceCount * averageTotalResourceCount
|
|
316
207
|
|
|
317
|
-
|
|
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
|
|
318
213
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(instance.id)
|
|
327
|
-
|
|
328
|
-
if (this.operation.options.forceUpdateDependencies) {
|
|
329
|
-
this.instanceIdsToUpdate.add(instanceId)
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
!state?.operationStatus ||
|
|
335
|
-
state.operationStatus.status === "error" ||
|
|
336
|
-
state.operationStatus.inputHash !== expectedInputHash
|
|
337
|
-
) {
|
|
338
|
-
this.instanceIdsToUpdate.add(instanceId)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// 1. extend affected instance IDs with their not-created or not-up-to-date dependencies (only for "update" operations)
|
|
343
|
-
for (const instanceId of this.operation.requestedInstanceIds) {
|
|
344
|
-
if (this.operation.type === "update") {
|
|
345
|
-
await traverse(instanceId)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
this.instanceIdsToUpdate.add(instanceId)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// 2. extend affected instance IDs with the children of the affected composite instances
|
|
352
|
-
const compositeInstanceQueue = Array.from(this.instanceIdsToUpdate)
|
|
353
|
-
while (compositeInstanceQueue.length > 0) {
|
|
354
|
-
const childId = compositeInstanceQueue.pop()!
|
|
355
|
-
const children = this.instanceChildrenMap.get(childId) ?? []
|
|
356
|
-
|
|
357
|
-
for (const child of children) {
|
|
358
|
-
compositeInstanceQueue.push(child.id)
|
|
359
|
-
|
|
360
|
-
if (this.operation.options.forceUpdateChildren) {
|
|
361
|
-
this.instanceIdsToUpdate.add(child.id)
|
|
362
|
-
continue
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const state = this.stateMap.get(child.id)
|
|
366
|
-
const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(child.id)
|
|
367
|
-
|
|
368
|
-
if (
|
|
369
|
-
!state?.operationStatus ||
|
|
370
|
-
state.operationStatus.status === "error" ||
|
|
371
|
-
state.operationStatus.inputHash !== expectedInputHash
|
|
372
|
-
) {
|
|
373
|
-
this.instanceIdsToUpdate.add(child.id)
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// 3. detect composite instance children and include their parents (recursively)
|
|
379
|
-
for (const instanceId of this.instanceIdsToUpdate) {
|
|
380
|
-
let instance = this.instanceMap.get(instanceId)
|
|
381
|
-
while (instance?.parentId) {
|
|
382
|
-
this.instanceIdsToUpdate.add(instance.parentId)
|
|
383
|
-
instance = this.instanceMap.get(instance.parentId)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// 4. if "allowPartialCompositeInstanceUpdates" is not set, include all children of the composite instances
|
|
388
|
-
if (!this.operation.options.allowPartialCompositeInstanceUpdates) {
|
|
389
|
-
for (const instanceId of this.instanceIdsToUpdate) {
|
|
390
|
-
const children = this.instanceChildrenMap.get(instanceId) ?? []
|
|
391
|
-
for (const child of children) {
|
|
392
|
-
this.instanceIdsToUpdate.add(child.id)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
this.operation.instanceIdsToUpdate = Array.from(this.instanceIdsToUpdate)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
private calculateInstanceIdsToDestroy() {
|
|
401
|
-
const traverse = (instanceKey: string) => {
|
|
402
|
-
if (this.instanceIdsToDestroy.has(instanceKey)) {
|
|
403
|
-
return
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const state = this.stateMap.get(instanceKey)
|
|
407
|
-
if (!state) {
|
|
408
|
-
return
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const dependentIds = this.dependentStateIdMap.get(instanceKey) ?? []
|
|
412
|
-
|
|
413
|
-
for (const dependentId of dependentIds) {
|
|
414
|
-
traverse(dependentId)
|
|
415
|
-
this.instanceIdsToDestroy.add(dependentId)
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (this.operation.type === "destroy" || this.operation.type === "recreate") {
|
|
420
|
-
// 1.a. extend affected instance IDs with their created dependents (if not forbidden by the operation options)
|
|
421
|
-
for (const instanceId of this.operation.requestedInstanceIds) {
|
|
422
|
-
const instance = this.instanceMap.get(instanceId)
|
|
423
|
-
if (!instance) {
|
|
424
|
-
throw new Error(`Instance not found: ${instanceId}`)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (this.operation.options.destroyDependentInstances) {
|
|
428
|
-
traverse(instance.id)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
this.instanceIdsToDestroy.add(instanceId)
|
|
432
|
-
}
|
|
433
|
-
} else if (this.operation.type === "update") {
|
|
434
|
-
// 1.b. find all children instances of the affected to-update instances which are in the state map, but not in the instance map
|
|
435
|
-
// in other words, this code cleans up child instances which are not produced by composite instances anymore
|
|
436
|
-
|
|
437
|
-
for (const instanceId of this.operation.instanceIdsToUpdate) {
|
|
438
|
-
const [type] = parseInstanceId(instanceId)
|
|
439
|
-
const component = this.library.components[type]
|
|
440
|
-
|
|
441
|
-
if (!component || isUnitModel(component)) {
|
|
442
|
-
// ignore non-composite instances
|
|
443
|
-
continue
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const childrenQueue = [...(this.stateChildIdMap.get(instanceId) ?? [])]
|
|
447
|
-
|
|
448
|
-
while (childrenQueue.length > 0) {
|
|
449
|
-
const childId = childrenQueue.pop()!
|
|
450
|
-
if (!this.instanceMap.has(childId)) {
|
|
451
|
-
this.instanceIdsToDestroy.add(childId)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
childrenQueue.push(...(this.stateChildIdMap.get(childId) ?? []))
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
} else {
|
|
458
|
-
return
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// 2. extend affected instance IDs with the children of the affected composite instances
|
|
462
|
-
const compositeInstanceQueue = Array.from(this.instanceIdsToDestroy)
|
|
463
|
-
while (compositeInstanceQueue.length > 0) {
|
|
464
|
-
const childId = compositeInstanceQueue.pop()!
|
|
465
|
-
const children = this.stateChildIdMap.get(childId) ?? []
|
|
466
|
-
|
|
467
|
-
for (const child of children) {
|
|
468
|
-
compositeInstanceQueue.push(child)
|
|
469
|
-
this.instanceIdsToDestroy.add(child)
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// 3. detect composite instance children and include their parents (recursively)
|
|
474
|
-
for (const instanceId of this.instanceIdsToDestroy) {
|
|
475
|
-
let state = this.stateMap.get(instanceId)
|
|
476
|
-
while (state?.parentId) {
|
|
477
|
-
this.instanceIdsToDestroy.add(state.parentId)
|
|
478
|
-
state = this.stateMap.get(state.parentId)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// 4. if "allowPartialCompositeInstanceUpdates" is not set, include all children of the composite instances
|
|
483
|
-
if (!this.operation.options.allowPartialCompositeInstanceUpdates) {
|
|
484
|
-
for (const instanceId of this.instanceIdsToDestroy) {
|
|
485
|
-
const children = this.stateChildIdMap.get(instanceId) ?? []
|
|
486
|
-
for (const childId of children) {
|
|
487
|
-
this.instanceIdsToDestroy.add(childId)
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
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
|
+
})
|
|
493
221
|
}
|
|
494
222
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (isUnitModel(component)) {
|
|
500
|
-
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
|
|
501
227
|
}
|
|
502
228
|
|
|
503
|
-
|
|
229
|
+
// for other phases, consider parent defined in the model
|
|
230
|
+
return this.context.getInstance(instanceId).parentId ?? null
|
|
504
231
|
}
|
|
505
232
|
|
|
506
|
-
|
|
233
|
+
getTransientStatusByOperationPhase(): InstanceOperationStatus {
|
|
507
234
|
switch (this.currentPhase) {
|
|
508
235
|
case "update":
|
|
509
236
|
return "updating"
|
|
@@ -514,180 +241,108 @@ export class OperationWorkset {
|
|
|
514
241
|
}
|
|
515
242
|
}
|
|
516
243
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
state: this.stateMap.get(instance.id),
|
|
526
|
-
sourceHash: this.getSourceHashIfApplicable(instance, component),
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
this.inputHashResolver.invalidate(instance.id)
|
|
530
|
-
await this.inputHashResolver.process()
|
|
531
|
-
|
|
532
|
-
return this.inputHashResolver.requireOutput(instance.id)
|
|
533
|
-
})
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Tries to acquire not-locked instances in the workset.
|
|
538
|
-
*
|
|
539
|
-
* Should not be called if the whole workset is already locked.
|
|
540
|
-
*
|
|
541
|
-
* If `instancesToLock` is provided, it will try to lock only those instances.
|
|
542
|
-
*/
|
|
543
|
-
public async tryLock(instancesToLock?: string[]): Promise<void> {
|
|
544
|
-
const [, lockedInstanceIds] = await this.instanceLockService.tryLockInstances(
|
|
545
|
-
this.project.id,
|
|
546
|
-
instancesToLock ?? Array.from(this.instanceIdsToLockIds),
|
|
547
|
-
{
|
|
548
|
-
title: "Operation Lock",
|
|
549
|
-
description: `The instance is locked for the ${this.operation.type} operation with ID "${this.operation.id}".`,
|
|
550
|
-
icon: "mdi:cog-sync",
|
|
551
|
-
},
|
|
552
|
-
{ type: "operation", operationId: this.operation.id },
|
|
553
|
-
true, // allow partial locking
|
|
554
|
-
)
|
|
555
|
-
|
|
556
|
-
// add locked instance IDs to the workset and remove them from the instanceIdsToLockIds
|
|
557
|
-
for (const instanceId of lockedInstanceIds) {
|
|
558
|
-
this.lockedInstanceIds.add(instanceId)
|
|
559
|
-
this.instanceIdsToLockIds.delete(instanceId)
|
|
560
|
-
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"
|
|
561
252
|
}
|
|
562
253
|
}
|
|
563
254
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
operation: Operation,
|
|
567
|
-
projectBackend: ProjectBackend,
|
|
568
|
-
libraryBackend: LibraryBackend,
|
|
569
|
-
stateManager: StateManager,
|
|
570
|
-
instanceLockService: InstanceLockService,
|
|
571
|
-
instanceStateService: InstanceStateService,
|
|
572
|
-
logger: Logger,
|
|
573
|
-
signal: AbortSignal,
|
|
574
|
-
): Promise<OperationWorkset> {
|
|
575
|
-
// TODO: use hotstate for virtual instances
|
|
576
|
-
const [library, { instances, hubs }, virtualInstances, states] = await Promise.all([
|
|
577
|
-
libraryBackend.loadLibrary(project.libraryId, signal),
|
|
578
|
-
projectBackend.getProjectModel(project, signal),
|
|
579
|
-
stateManager.getVirtualInstanceRepository(project.id).getAllItems(),
|
|
580
|
-
instanceStateService.getInstanceStates(project.id),
|
|
581
|
-
])
|
|
582
|
-
|
|
583
|
-
const workset = new OperationWorkset(
|
|
584
|
-
project,
|
|
585
|
-
operation,
|
|
586
|
-
library,
|
|
587
|
-
instanceLockService,
|
|
588
|
-
instanceStateService,
|
|
589
|
-
logger.child({ operationId: operation.id, service: "OperationWorkset" }),
|
|
590
|
-
)
|
|
255
|
+
getNextStableInstanceStatus(instanceId: InstanceId): InstanceStatus {
|
|
256
|
+
const state = this.context.getState(instanceId)
|
|
591
257
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
595
265
|
}
|
|
266
|
+
}
|
|
596
267
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (worksetInstance) {
|
|
601
|
-
worksetInstance.outputs = instance.outputs
|
|
602
|
-
worksetInstance.resolvedOutputs = instance.resolvedOutputs
|
|
603
|
-
} else if (instance.parentId) {
|
|
604
|
-
workset.addInstance(instance)
|
|
605
|
-
} else {
|
|
606
|
-
workset.logger.warn(
|
|
607
|
-
`ignoring virtual instance "${instance.id}" because it is not in the project or is not a part of known composite instance`,
|
|
608
|
-
)
|
|
609
|
-
continue
|
|
610
|
-
}
|
|
268
|
+
setupAbortControllersForAllInstances(): void {
|
|
269
|
+
for (const instanceId of this.allAffectedInstanceIds) {
|
|
270
|
+
this.setupInstanceAbortControllers(instanceId)
|
|
611
271
|
}
|
|
272
|
+
}
|
|
612
273
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
for (const unitSource of unitSources) {
|
|
619
|
-
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
|
|
620
278
|
}
|
|
621
279
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
`ignoring instance "${state.id}" from state because it is not in the project or is not a part of a composite instance`,
|
|
626
|
-
)
|
|
627
|
-
continue
|
|
628
|
-
}
|
|
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())
|
|
629
283
|
|
|
630
|
-
|
|
631
|
-
|
|
284
|
+
abortController.signal.addEventListener("abort", () => {
|
|
285
|
+
// notify frontend that the instance is being cancelled
|
|
286
|
+
this.updateState(instanceId, { operationState: { status: "cancelling" } })
|
|
287
|
+
})
|
|
632
288
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
workset.inputResolverNodes.set(`instance:${instance.id}`, {
|
|
636
|
-
kind: "instance",
|
|
637
|
-
instance,
|
|
638
|
-
component: library.components[instance.type],
|
|
639
|
-
})
|
|
640
|
-
}
|
|
289
|
+
const forceAbortController = new AbortController()
|
|
290
|
+
this.forceAbortController.signal.addEventListener("abort", () => forceAbortController.abort())
|
|
641
291
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
292
|
+
const pair: AbortControllerPair = { abortController, forceAbortController }
|
|
293
|
+
this.instanceAbortControllers.set(instanceId, pair)
|
|
645
294
|
|
|
646
|
-
|
|
647
|
-
|
|
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)
|
|
648
299
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// resolve inputs for all instances and pass outputs to input hash resolver
|
|
652
|
-
for (const instance of workset.instanceMap.values()) {
|
|
653
|
-
const output = workset.inputResolver.requireOutput(`instance:${instance.id}`)
|
|
654
|
-
if (output.kind !== "instance") {
|
|
655
|
-
throw new Error("Unexpected output kind")
|
|
656
|
-
}
|
|
300
|
+
abortController.signal.addEventListener("abort", () => childPair.abortController.abort())
|
|
657
301
|
|
|
658
|
-
|
|
302
|
+
forceAbortController.signal.addEventListener("abort", () =>
|
|
303
|
+
childPair.forceAbortController.abort(),
|
|
304
|
+
)
|
|
305
|
+
}
|
|
659
306
|
|
|
660
|
-
|
|
307
|
+
return pair
|
|
308
|
+
}
|
|
661
309
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
state: workset.stateMap.get(instance.id),
|
|
667
|
-
sourceHash: workset.getSourceHashIfApplicable(instance, component),
|
|
668
|
-
})
|
|
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}"`)
|
|
669
314
|
}
|
|
670
315
|
|
|
671
|
-
|
|
672
|
-
workset.inputHashResolver = new InputHashResolver(workset.inputHashNodes, logger)
|
|
673
|
-
workset.inputHashResolver.addAllNodesToWorkset()
|
|
316
|
+
const { abortController, forceAbortController } = abortControllerPair
|
|
674
317
|
|
|
675
|
-
|
|
318
|
+
// first try to cancel the operation gracefully
|
|
676
319
|
|
|
677
|
-
if (
|
|
678
|
-
|
|
320
|
+
if (!abortController.signal.aborted) {
|
|
321
|
+
abortController.abort()
|
|
322
|
+
return
|
|
679
323
|
}
|
|
680
324
|
|
|
681
|
-
|
|
325
|
+
if (!allowForceAbort) {
|
|
326
|
+
return
|
|
327
|
+
}
|
|
682
328
|
|
|
683
|
-
|
|
684
|
-
|
|
329
|
+
// then try to force cancel the operation
|
|
330
|
+
if (!forceAbortController.signal.aborted) {
|
|
331
|
+
forceAbortController.abort()
|
|
332
|
+
return
|
|
685
333
|
}
|
|
334
|
+
}
|
|
686
335
|
|
|
687
|
-
|
|
688
|
-
|
|
336
|
+
cancel(): void {
|
|
337
|
+
if (!this.abortController.signal.aborted) {
|
|
338
|
+
this.abortController.abort()
|
|
339
|
+
return
|
|
689
340
|
}
|
|
690
341
|
|
|
691
|
-
|
|
342
|
+
// then try to force cancel the operation
|
|
343
|
+
if (!this.forceAbortController.signal.aborted) {
|
|
344
|
+
this.forceAbortController.abort()
|
|
345
|
+
return
|
|
346
|
+
}
|
|
692
347
|
}
|
|
693
348
|
}
|