@highstate/backend 0.9.18 → 0.9.20
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-OU5OQBLB.js → chunk-I7BWSAN6.js} +3 -28
- package/dist/{chunk-OU5OQBLB.js.map → chunk-I7BWSAN6.js.map} +1 -1
- package/dist/chunk-RC6Q3XQQ.js +1547 -0
- package/dist/chunk-RC6Q3XQQ.js.map +1 -0
- 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 +7590 -7289
- 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 +32 -0
- package/prisma/project/artifact.prisma +52 -0
- package/prisma/project/custom-status.prisma +46 -0
- package/prisma/project/evaluation.prisma +45 -0
- package/prisma/project/instance.prisma +157 -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 +49 -0
- package/prisma/project/secret.prisma +54 -0
- package/prisma/project/service-account.prisma +42 -0
- package/prisma/project/terminal.prisma +107 -0
- package/prisma/project/trigger.prisma +37 -0
- package/prisma/project/unlock-method.prisma +46 -0
- package/prisma/project/worker.prisma +169 -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 +469 -130
- package/src/business/secret.ts +177 -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 +440 -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 +40 -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 +74 -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 +235 -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 +9 -2
- 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,88 +1,71 @@
|
|
|
1
|
-
import type { LibraryBackend } from "../library"
|
|
2
|
-
import type { StateManager } from "../state"
|
|
3
|
-
import type { ProjectBackend } from "../project"
|
|
4
|
-
import type {
|
|
5
|
-
RunnerArtifact,
|
|
6
|
-
RunnerBackend,
|
|
7
|
-
TypedUnitStateUpdate,
|
|
8
|
-
UnitStateUpdate,
|
|
9
|
-
} from "../runner"
|
|
10
1
|
import type { Logger } from "pino"
|
|
2
|
+
import type { ArtifactService } from "../artifact"
|
|
11
3
|
import type {
|
|
12
4
|
InstanceLockService,
|
|
5
|
+
InstanceStatePatch,
|
|
13
6
|
InstanceStateService,
|
|
14
7
|
OperationService,
|
|
8
|
+
ProjectModelService,
|
|
15
9
|
SecretService,
|
|
16
|
-
|
|
10
|
+
UnitExtraService,
|
|
17
11
|
} from "../business"
|
|
18
|
-
import type {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
12
|
+
import type { Operation, OperationUpdateInput, Project } from "../database"
|
|
13
|
+
import type { LibraryBackend } from "../library"
|
|
14
|
+
import type { RunnerBackend, TypedUnitStateUpdate, UnitStateUpdate } from "../runner"
|
|
21
15
|
import {
|
|
22
|
-
|
|
23
|
-
parseInstanceId,
|
|
24
|
-
type ComponentModel,
|
|
16
|
+
type InstanceId,
|
|
25
17
|
type InstanceModel,
|
|
26
|
-
|
|
18
|
+
parseInstanceId,
|
|
19
|
+
type TriggerInvocation,
|
|
20
|
+
type UnitConfig,
|
|
21
|
+
type VersionedName,
|
|
27
22
|
} from "@highstate/contract"
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
23
|
+
import { createId } from "@paralleldrive/cuid2"
|
|
24
|
+
import { mapValues } from "remeda"
|
|
25
|
+
import { AbortError, errorToString, isAbortErrorLike, waitForAbort } from "../common"
|
|
30
26
|
import {
|
|
31
27
|
type InstanceState,
|
|
32
|
-
|
|
33
|
-
type
|
|
34
|
-
type Page,
|
|
35
|
-
type Trigger,
|
|
36
|
-
type UnitTrigger,
|
|
37
|
-
type UnitTerminal,
|
|
38
|
-
type Terminal,
|
|
39
|
-
type TerminalSpec,
|
|
40
|
-
type TriggerInvocation,
|
|
28
|
+
isTransientInstanceOperationStatus,
|
|
29
|
+
type OperationPhase,
|
|
41
30
|
PromiseTracker,
|
|
42
|
-
|
|
43
|
-
type PageBlock,
|
|
44
|
-
type Project,
|
|
31
|
+
waitAll,
|
|
45
32
|
} from "../shared"
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
isAbortErrorLike,
|
|
50
|
-
PerformanceLogger,
|
|
51
|
-
stringToValue,
|
|
52
|
-
valueToString,
|
|
53
|
-
waitForAbort,
|
|
54
|
-
} from "../common"
|
|
55
|
-
import { OperationWorkset, type OperationPhase } from "./operation-workset"
|
|
33
|
+
import { OperationContext } from "./operation-context"
|
|
34
|
+
import { createOperationPlan } from "./operation-plan"
|
|
35
|
+
import { OperationWorkset } from "./operation-workset"
|
|
56
36
|
|
|
57
37
|
export class RuntimeOperation {
|
|
58
|
-
private readonly
|
|
59
|
-
private readonly forceAbortController = new AbortController()
|
|
60
|
-
|
|
61
|
-
private readonly instanceAbortControllers = new Map<string, AbortController>()
|
|
62
|
-
private readonly instanceForceAbortControllers = new Map<string, AbortController>()
|
|
63
|
-
|
|
64
|
-
private readonly instancePromiseMap = new Map<string, Promise<void>>()
|
|
38
|
+
private readonly instancePromiseMap = new Map<InstanceId, Promise<void>>()
|
|
65
39
|
private readonly promiseTracker = new PromiseTracker()
|
|
66
40
|
|
|
67
41
|
private workset!: OperationWorkset
|
|
42
|
+
private context!: OperationContext
|
|
43
|
+
|
|
44
|
+
private readonly unlockToken = createId()
|
|
68
45
|
|
|
69
46
|
constructor(
|
|
70
47
|
private readonly project: Project,
|
|
71
48
|
private readonly operation: Operation,
|
|
72
49
|
private readonly runnerBackend: RunnerBackend,
|
|
73
50
|
private readonly libraryBackend: LibraryBackend,
|
|
74
|
-
private readonly projectBackend: ProjectBackend,
|
|
75
51
|
private readonly artifactService: ArtifactService,
|
|
76
|
-
private readonly stateManager: StateManager,
|
|
77
52
|
private readonly instanceLockService: InstanceLockService,
|
|
78
53
|
private readonly operationService: OperationService,
|
|
79
54
|
private readonly secretService: SecretService,
|
|
80
55
|
private readonly instanceStateService: InstanceStateService,
|
|
81
|
-
private readonly
|
|
82
|
-
private readonly
|
|
56
|
+
private readonly projectModelService: ProjectModelService,
|
|
57
|
+
private readonly unitExtraService: UnitExtraService,
|
|
83
58
|
private readonly logger: Logger,
|
|
84
59
|
) {}
|
|
85
60
|
|
|
61
|
+
cancel(): void {
|
|
62
|
+
this.workset.cancel()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cancelInstance(instanceId: InstanceId): void {
|
|
66
|
+
this.workset.cancelInstance(instanceId)
|
|
67
|
+
}
|
|
68
|
+
|
|
86
69
|
async operateSafe(): Promise<void> {
|
|
87
70
|
try {
|
|
88
71
|
await this.operate()
|
|
@@ -92,21 +75,20 @@ export class RuntimeOperation {
|
|
|
92
75
|
} catch (error) {
|
|
93
76
|
if (isAbortErrorLike(error)) {
|
|
94
77
|
this.logger.info("the operation was cancelled")
|
|
95
|
-
this.operation.status = "cancelled"
|
|
96
|
-
this.operation.error = null
|
|
97
78
|
|
|
98
|
-
await this.
|
|
79
|
+
await this.updateOperation({ status: "cancelled" })
|
|
99
80
|
return
|
|
100
81
|
}
|
|
101
82
|
|
|
102
83
|
this.logger.error({ error }, "an error occurred while running the operation")
|
|
103
84
|
|
|
104
|
-
this.
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
await this.operationService.updateOperation(this.project.id, this.operation)
|
|
85
|
+
await this.updateOperation({ status: "failed" })
|
|
86
|
+
await this.writeOperationLog(errorToString(error))
|
|
108
87
|
} finally {
|
|
109
88
|
try {
|
|
89
|
+
this.promiseTracker.track(this.ensureInstancesUnlocked())
|
|
90
|
+
this.promiseTracker.track(this.ensureOperationStatesFinalized())
|
|
91
|
+
|
|
110
92
|
// ensure that all promises are resolved even if the operation failed
|
|
111
93
|
await this.promiseTracker.waitForAll()
|
|
112
94
|
} catch (error) {
|
|
@@ -121,261 +103,181 @@ export class RuntimeOperation {
|
|
|
121
103
|
private async operate(): Promise<void> {
|
|
122
104
|
this.logger.info("starting operation")
|
|
123
105
|
|
|
124
|
-
// create the
|
|
125
|
-
this.
|
|
126
|
-
this.project,
|
|
127
|
-
this.operation,
|
|
128
|
-
this.projectBackend,
|
|
106
|
+
// 1. create the context for the operation
|
|
107
|
+
this.context = await OperationContext.load(
|
|
108
|
+
this.project.id,
|
|
129
109
|
this.libraryBackend,
|
|
130
|
-
this.stateManager,
|
|
131
|
-
this.instanceLockService,
|
|
132
110
|
this.instanceStateService,
|
|
111
|
+
this.projectModelService,
|
|
133
112
|
this.logger,
|
|
134
|
-
this.abortController.signal,
|
|
135
113
|
)
|
|
136
114
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
115
|
+
// 2. create the plan for the operation (or use provided one)
|
|
116
|
+
let plan: OperationPhase[]
|
|
117
|
+
if (this.operation.phases) {
|
|
118
|
+
plan = this.operation.phases
|
|
119
|
+
} else {
|
|
120
|
+
plan = createOperationPlan(
|
|
121
|
+
this.context,
|
|
122
|
+
this.operation.type,
|
|
123
|
+
this.operation.requestedInstanceIds,
|
|
124
|
+
this.operation.options,
|
|
125
|
+
)
|
|
143
126
|
|
|
144
|
-
|
|
145
|
-
this.
|
|
146
|
-
lockAbortSignal.abort()
|
|
127
|
+
// persist the generated plan
|
|
128
|
+
await this.updateOperation({ phases: plan })
|
|
147
129
|
}
|
|
148
130
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
this.
|
|
152
|
-
|
|
131
|
+
// 3. create the workset to track the state of the operation
|
|
132
|
+
this.workset = new OperationWorkset(
|
|
133
|
+
this.project,
|
|
134
|
+
this.operation.id,
|
|
135
|
+
plan,
|
|
136
|
+
this.context,
|
|
137
|
+
this.instanceStateService,
|
|
138
|
+
)
|
|
153
139
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
await this.processOperation()
|
|
157
|
-
} finally {
|
|
158
|
-
lockAbortSignal.abort()
|
|
140
|
+
// 4. create operation states in datbase for all affected instances in all phases
|
|
141
|
+
await this.workset.setupOperationStates()
|
|
159
142
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await this.workset.restoreAffectedInitialStates()
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
143
|
+
// 5. setup abort controllers for all affected instances in all phases
|
|
144
|
+
this.workset.setupAbortControllersForAllInstances()
|
|
166
145
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.abortController.signal.addEventListener("abort", () => abortController.abort())
|
|
170
|
-
this.instanceAbortControllers.set(instanceId, abortController)
|
|
146
|
+
// 6. progressively lock instances and launch them as they get locked
|
|
147
|
+
this.launchLockAcquisitionSequence()
|
|
171
148
|
|
|
172
|
-
|
|
173
|
-
this.
|
|
174
|
-
this.instanceForceAbortControllers.set(instanceId, forceAbortController)
|
|
149
|
+
// 7. run the operation
|
|
150
|
+
await this.processOperation()
|
|
175
151
|
}
|
|
176
152
|
|
|
177
|
-
private async
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for await (const event of eventStream) {
|
|
181
|
-
if (event.type !== "unlocked") {
|
|
182
|
-
continue
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const instanceIdsToLock = event.instanceIds.filter(
|
|
186
|
-
//
|
|
187
|
-
instanceId => this.workset.instanceIdsToLockIds.has(instanceId),
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
if (instanceIdsToLock.length === 0) {
|
|
191
|
-
try {
|
|
192
|
-
await this.workset.tryLock(instanceIdsToLock)
|
|
193
|
-
} catch (error) {
|
|
194
|
-
this.logger.error({ error }, "failed to lock more instances during operation")
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (this.workset.instanceIdsToLockIds.size === 0) {
|
|
199
|
-
// no more instances to lock, stop listening for events
|
|
200
|
-
this.logger.debug("successfully locked all instances for the operation")
|
|
201
|
-
break
|
|
202
|
-
}
|
|
203
|
-
}
|
|
153
|
+
private async updateOperation(patch: OperationUpdateInput): Promise<void> {
|
|
154
|
+
Object.assign(this.operation, patch)
|
|
155
|
+
await this.operationService.updateOperation(this.project.id, this.operation.id, patch)
|
|
204
156
|
}
|
|
205
157
|
|
|
206
|
-
private async
|
|
207
|
-
this.
|
|
208
|
-
|
|
158
|
+
private async writeOperationLog(message: string): Promise<void> {
|
|
159
|
+
this.promiseTracker.track(
|
|
160
|
+
this.operationService.appendLog(this.project.id, this.operation.id, null, message),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
209
163
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.
|
|
213
|
-
|
|
164
|
+
private launchLockAcquisitionSequence(): void {
|
|
165
|
+
this.promiseTracker.track(
|
|
166
|
+
this.instanceLockService.lockInstances(
|
|
167
|
+
this.project.id,
|
|
168
|
+
Array.from(this.workset.allAffectedStateIds),
|
|
169
|
+
{
|
|
170
|
+
title: "Operation Lock",
|
|
171
|
+
description: `The instance is locked for the ${this.operation.type} operation "${this.operation.id}".`,
|
|
172
|
+
icon: "mdi:cog-sync",
|
|
173
|
+
},
|
|
174
|
+
async (_tx, newlyLockedStateIds) => {
|
|
175
|
+
// trigger all newly locked instances to start their processing
|
|
176
|
+
for (const stateId of newlyLockedStateIds) {
|
|
177
|
+
this.workset.markInstanceLocked(stateId)
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
true, // allow partial locks to allow independent free branches to run
|
|
181
|
+
this.workset.abortController.signal,
|
|
182
|
+
60_000, // wait up to 60 seconds for unlock events before retrying
|
|
183
|
+
this.unlockToken,
|
|
184
|
+
),
|
|
214
185
|
)
|
|
186
|
+
}
|
|
215
187
|
|
|
216
|
-
|
|
188
|
+
private async processOperation(): Promise<void> {
|
|
217
189
|
const errors: string[] = []
|
|
218
|
-
let hasAbortError = false
|
|
219
190
|
|
|
220
|
-
|
|
221
|
-
this.workset.
|
|
191
|
+
while (this.workset.hasRemainingPhases()) {
|
|
192
|
+
this.workset.nextPhase()
|
|
193
|
+
this.instancePromiseMap.clear()
|
|
222
194
|
|
|
223
|
-
|
|
224
|
-
for (const instanceId of this.workset.
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// they will be called by their parent instance
|
|
229
|
-
continue
|
|
230
|
-
}
|
|
195
|
+
// lauch all instances in this phase
|
|
196
|
+
for (const instanceId of this.workset.phaseAffectedInstanceIds) {
|
|
197
|
+
const instance = this.context.getInstance(instanceId)
|
|
198
|
+
const state = this.context.getState(instanceId)
|
|
199
|
+
const promise = this.getInstancePromiseForOperation(instance, state)
|
|
231
200
|
|
|
232
|
-
|
|
201
|
+
this.instancePromiseMap.set(instanceId, promise)
|
|
202
|
+
this.promiseTracker.track(promise)
|
|
233
203
|
}
|
|
234
204
|
|
|
235
|
-
|
|
236
|
-
this.
|
|
237
|
-
await this.operationService.updateOperation(this.project.id, this.operation)
|
|
238
|
-
|
|
239
|
-
const phaseResults = await Promise.allSettled(promises)
|
|
240
|
-
for (const result of phaseResults) {
|
|
241
|
-
if (result.status !== "rejected") {
|
|
242
|
-
continue
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (isAbortErrorLike(result.reason)) {
|
|
246
|
-
hasAbortError = true
|
|
247
|
-
} else {
|
|
248
|
-
errors.push(errorToString(result.reason))
|
|
249
|
-
}
|
|
250
|
-
}
|
|
205
|
+
// wait for all instances in this phase to complete
|
|
206
|
+
await this.promiseTracker.waitForAll()
|
|
251
207
|
|
|
252
|
-
this.logger.info(`
|
|
208
|
+
this.logger.info(`phase "%s" completed`, this.workset.currentPhase)
|
|
253
209
|
}
|
|
254
210
|
|
|
255
211
|
if (errors.length > 0) {
|
|
256
212
|
this.operation.status = "failed"
|
|
257
|
-
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
213
|
+
|
|
214
|
+
this.operationService.appendLog(
|
|
215
|
+
this.project.id,
|
|
216
|
+
this.operation.id,
|
|
217
|
+
null,
|
|
218
|
+
`Operation failed with the following errors:\n${errors.join("\n")}`,
|
|
219
|
+
)
|
|
262
220
|
} else {
|
|
263
221
|
this.operation.status = "completed"
|
|
264
|
-
this.operation.error = null
|
|
265
222
|
}
|
|
266
223
|
|
|
267
|
-
await this.operationService.
|
|
224
|
+
await this.operationService.markOperationFinished(
|
|
225
|
+
this.project.id,
|
|
226
|
+
this.operation.id,
|
|
227
|
+
this.operation.status,
|
|
228
|
+
)
|
|
268
229
|
|
|
269
230
|
this.logger.info("operation completed")
|
|
270
231
|
}
|
|
271
232
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (!abortController.signal.aborted) {
|
|
279
|
-
this.logger.info(`cancelling operation for instance "${instanceId}"`)
|
|
280
|
-
abortController.abort()
|
|
281
|
-
|
|
282
|
-
// just the UX feature to indicate that we are trying to cancel the operation
|
|
283
|
-
this.promiseTracker.track(
|
|
284
|
-
this.workset.patchState({
|
|
285
|
-
id: instanceId,
|
|
286
|
-
operationStatus: {
|
|
287
|
-
status: "cancelling",
|
|
288
|
-
},
|
|
289
|
-
}),
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
return
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// then try to force cancel the operation
|
|
296
|
-
const forceAbortController = this.instanceForceAbortControllers.get(instanceId)
|
|
297
|
-
if (!forceAbortController) {
|
|
298
|
-
throw new Error(`No force abort controller found for instance "${instanceId}"`)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!forceAbortController.signal.aborted) {
|
|
302
|
-
this.logger.info(`force cancelling operation for instance "${instanceId}"`)
|
|
303
|
-
forceAbortController.abort()
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
this.logger.warn(`operation for instance "${instanceId}" is already force cancelled`)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
cancel(): void {
|
|
311
|
-
if (!this.abortController.signal.aborted) {
|
|
312
|
-
this.logger.info("cancelling the operation")
|
|
313
|
-
this.abortController.abort()
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// then try to force cancel the operation
|
|
318
|
-
if (!this.forceAbortController.signal.aborted) {
|
|
319
|
-
this.logger.info("force cancelling the operation")
|
|
320
|
-
this.forceAbortController.abort()
|
|
321
|
-
return
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
this.logger.warn("the operation is already cancelled or force cancelled")
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private getInstancePromiseForOperation(instanceId: string): Promise<void> {
|
|
328
|
-
const [instanceType] = parseInstanceId(instanceId)
|
|
329
|
-
const component = this.workset.library.components[instanceType]
|
|
330
|
-
|
|
331
|
-
if (isUnitModel(component)) {
|
|
332
|
-
return this.getUnitPromise(instanceId)
|
|
233
|
+
private getInstancePromiseForOperation(
|
|
234
|
+
instance: InstanceModel,
|
|
235
|
+
state: InstanceState,
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
if (instance.kind === "unit") {
|
|
238
|
+
return this.getUnitPromise(instance, state)
|
|
333
239
|
}
|
|
334
240
|
|
|
335
|
-
return this.getCompositePromise(
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private getOperationPhases(): OperationPhase[] {
|
|
339
|
-
switch (this.operation.type) {
|
|
340
|
-
case "update": {
|
|
341
|
-
if (this.operation.instanceIdsToDestroy.length > 0) {
|
|
342
|
-
return ["destroy", "update"]
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return ["update"]
|
|
346
|
-
}
|
|
347
|
-
case "preview":
|
|
348
|
-
return ["update"]
|
|
349
|
-
case "recreate":
|
|
350
|
-
return ["destroy", "update"]
|
|
351
|
-
case "destroy":
|
|
352
|
-
return ["destroy"]
|
|
353
|
-
case "refresh":
|
|
354
|
-
return ["refresh"]
|
|
355
|
-
}
|
|
241
|
+
return this.getCompositePromise(instance)
|
|
356
242
|
}
|
|
357
243
|
|
|
358
|
-
private
|
|
244
|
+
private getUnitPromise(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
359
245
|
switch (this.workset.currentPhase) {
|
|
360
246
|
case "update": {
|
|
361
|
-
return this.updateUnit(
|
|
247
|
+
return this.updateUnit(instance, state)
|
|
362
248
|
}
|
|
363
249
|
case "destroy": {
|
|
364
|
-
return this.destroyUnit(
|
|
250
|
+
return this.destroyUnit(instance, state)
|
|
365
251
|
}
|
|
366
252
|
case "refresh": {
|
|
367
|
-
return this.refreshUnit(
|
|
253
|
+
return this.refreshUnit(instance, state)
|
|
368
254
|
}
|
|
369
255
|
}
|
|
370
256
|
}
|
|
371
257
|
|
|
372
|
-
private getCompositePromise(
|
|
373
|
-
return this.getInstancePromise(
|
|
374
|
-
|
|
258
|
+
private getCompositePromise(instance: InstanceModel): Promise<void> {
|
|
259
|
+
return this.getInstancePromise(instance.id, async logger => {
|
|
260
|
+
let instanceState: InstanceStatePatch | undefined
|
|
375
261
|
|
|
376
|
-
|
|
262
|
+
// set parent ID on update operation
|
|
263
|
+
if (this.workset.currentPhase === "update") {
|
|
264
|
+
if (instance.parentId) {
|
|
265
|
+
const parentState = this.context.getState(instance.parentId)
|
|
266
|
+
instanceState = { parentId: parentState.id }
|
|
267
|
+
} else {
|
|
268
|
+
instanceState = { parentId: null }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
await this.workset.updateState(instance.id, {
|
|
273
|
+
operationState: {
|
|
274
|
+
status: this.workset.getTransientStatusByOperationPhase(),
|
|
275
|
+
startedAt: new Date(),
|
|
276
|
+
},
|
|
277
|
+
instanceState,
|
|
278
|
+
})
|
|
377
279
|
|
|
378
|
-
const children = this.workset.getAffectedCompositeChildren(
|
|
280
|
+
const children = this.workset.getAffectedCompositeChildren(instance.id)
|
|
379
281
|
const childPromises: Promise<void>[] = []
|
|
380
282
|
|
|
381
283
|
if (children.length) {
|
|
@@ -385,65 +287,37 @@ export class RuntimeOperation {
|
|
|
385
287
|
}
|
|
386
288
|
|
|
387
289
|
for (const child of children) {
|
|
388
|
-
|
|
389
|
-
// skip children that are not affected by the operation
|
|
390
|
-
continue
|
|
391
|
-
}
|
|
290
|
+
logger.debug(`waiting for child "%s"`, child)
|
|
392
291
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
try {
|
|
398
|
-
await Promise.all(childPromises)
|
|
292
|
+
const instance = this.context.getInstance(child)
|
|
293
|
+
const state = this.context.getState(child)
|
|
294
|
+
const promise = this.getInstancePromiseForOperation(instance, state)
|
|
399
295
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
296
|
+
childPromises.push(promise)
|
|
297
|
+
}
|
|
403
298
|
|
|
404
|
-
|
|
405
|
-
await this.clearOperationState(instanceId)
|
|
406
|
-
return
|
|
407
|
-
}
|
|
299
|
+
await waitAll(childPromises)
|
|
408
300
|
|
|
409
|
-
|
|
410
|
-
await this.workset.getUpToDateInputHashOutput(instance)
|
|
301
|
+
logger.debug("all children completed")
|
|
411
302
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
303
|
+
// update the instance and operation state after all children are completed in last phase
|
|
304
|
+
if (this.workset.isLastPhaseForInstance(instance.id)) {
|
|
305
|
+
await this.workset.updateState(instance.id, {
|
|
306
|
+
operationState: {
|
|
307
|
+
status: this.workset.getStableStatusByOperationPhase(),
|
|
308
|
+
finishedAt: new Date(),
|
|
418
309
|
},
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
await this.workset.restoreInitialState(instanceId)
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
await this.workset.patchState({
|
|
427
|
-
id: instanceId,
|
|
428
|
-
operationStatus: {
|
|
429
|
-
status: "error",
|
|
430
|
-
message: errorToString(error),
|
|
310
|
+
instanceState: {
|
|
311
|
+
status: this.workset.getNextStableInstanceStatus(instance.id),
|
|
312
|
+
message: null,
|
|
431
313
|
},
|
|
432
314
|
})
|
|
433
315
|
}
|
|
434
316
|
})
|
|
435
317
|
}
|
|
436
318
|
|
|
437
|
-
private updateUnit(
|
|
438
|
-
return this.getInstancePromise(
|
|
439
|
-
const instance = this.workset.getInstance(instanceId)
|
|
440
|
-
const component = this.workset.library.components[instance.type]
|
|
441
|
-
const perfLogger = new PerformanceLogger(logger)
|
|
442
|
-
perfLogger.log("starting update promise for instance")
|
|
443
|
-
|
|
444
|
-
await this.setInitialOperationStatus(instance)
|
|
445
|
-
perfLogger.log("initial operation status set")
|
|
446
|
-
|
|
319
|
+
private updateUnit(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
320
|
+
return this.getInstancePromise(instance.id, async (logger, signal, forceSignal) => {
|
|
447
321
|
await Promise.race([
|
|
448
322
|
this.updateUnitDependencies(instance.id, logger),
|
|
449
323
|
|
|
@@ -451,91 +325,81 @@ export class RuntimeOperation {
|
|
|
451
325
|
waitForAbort(signal),
|
|
452
326
|
])
|
|
453
327
|
|
|
454
|
-
if (!signal.aborted) {
|
|
455
|
-
perfLogger.log("dependencies updated")
|
|
456
|
-
}
|
|
457
|
-
|
|
458
328
|
signal.throwIfAborted()
|
|
459
329
|
|
|
460
330
|
logger.info("updating unit")
|
|
461
331
|
|
|
462
|
-
await this.workset.
|
|
463
|
-
|
|
464
|
-
operationStatus: {
|
|
332
|
+
await this.workset.updateState(instance.id, {
|
|
333
|
+
operationState: {
|
|
465
334
|
status: "updating",
|
|
466
|
-
|
|
467
|
-
totalResourceCount: 0,
|
|
335
|
+
startedAt: new Date(),
|
|
468
336
|
},
|
|
469
337
|
})
|
|
470
338
|
|
|
471
|
-
perfLogger.log("set 'updating' operation status")
|
|
472
339
|
signal.throwIfAborted()
|
|
473
340
|
|
|
474
|
-
const secrets = await this.secretService.getInstanceSecretValues(this.project,
|
|
341
|
+
const secrets = await this.secretService.getInstanceSecretValues(this.project.id, state.id)
|
|
475
342
|
|
|
476
|
-
perfLogger.log("secrets loaded")
|
|
477
343
|
signal.throwIfAborted()
|
|
478
344
|
|
|
479
|
-
const config = this.prepareUnitConfig(instance,
|
|
480
|
-
perfLogger.log("unit config prepared")
|
|
345
|
+
const config = this.prepareUnitConfig(instance, secrets)
|
|
481
346
|
|
|
482
|
-
//
|
|
483
|
-
const
|
|
484
|
-
const artifacts = await this.
|
|
485
|
-
.getArtifactHashIndexRepository(this.project.id)
|
|
486
|
-
.getManyItems(artifactHashes)
|
|
347
|
+
// collect artifacts authorized for this instance
|
|
348
|
+
const artifactIds = this.collectArtifactIdsForInstance(instance)
|
|
349
|
+
const artifacts = await this.artifactService.getArtifactsByIds(this.project.id, artifactIds)
|
|
487
350
|
|
|
488
|
-
logger.debug({ count:
|
|
489
|
-
perfLogger.log("artifact hashes collected")
|
|
351
|
+
logger.debug({ count: artifactIds.length }, "artifact ids collected from dependencies")
|
|
490
352
|
|
|
491
353
|
await this.runnerBackend[this.operation.type === "preview" ? "preview" : "update"]({
|
|
492
354
|
projectId: this.project.id,
|
|
493
355
|
libraryId: this.project.libraryId,
|
|
356
|
+
stateId: state.id,
|
|
494
357
|
instanceType: instance.type,
|
|
495
358
|
instanceName: instance.name,
|
|
496
359
|
config,
|
|
497
360
|
refresh: this.operation.options.refresh,
|
|
498
|
-
secrets
|
|
361
|
+
secrets,
|
|
499
362
|
artifacts,
|
|
500
363
|
signal,
|
|
501
364
|
forceSignal,
|
|
365
|
+
debug: this.operation.options.debug,
|
|
502
366
|
})
|
|
503
367
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
await this.watchStateStream(instance.type, instance.name, logger)
|
|
507
|
-
perfLogger.log("unit update completed")
|
|
368
|
+
await this.watchStateStream(state, instance.type, instance.name, logger)
|
|
508
369
|
logger.info("unit updated")
|
|
509
370
|
})
|
|
510
371
|
}
|
|
511
372
|
|
|
512
|
-
private async updateUnitDependencies(instanceId:
|
|
373
|
+
private async updateUnitDependencies(instanceId: InstanceId, logger: Logger): Promise<void> {
|
|
513
374
|
try {
|
|
514
|
-
const dependencies = this.
|
|
375
|
+
const dependencies = this.context.getDependencies(instanceId)
|
|
515
376
|
const dependencyPromises: Promise<void>[] = []
|
|
516
377
|
|
|
517
|
-
for (const
|
|
518
|
-
if (!this.
|
|
378
|
+
for (const dependency of dependencies) {
|
|
379
|
+
if (!this.workset.phaseAffectedInstanceIds.has(dependency.id)) {
|
|
519
380
|
// skip dependencies that are not affected by the operation
|
|
520
381
|
continue
|
|
521
382
|
}
|
|
522
383
|
|
|
523
|
-
|
|
524
|
-
|
|
384
|
+
const state = this.context.getState(dependency.id)
|
|
385
|
+
|
|
386
|
+
logger.debug(`waiting for dependency "%s"`, dependency.id)
|
|
387
|
+
dependencyPromises.push(this.getInstancePromiseForOperation(dependency, state))
|
|
525
388
|
}
|
|
526
389
|
|
|
527
|
-
await
|
|
390
|
+
await waitAll(dependencyPromises)
|
|
528
391
|
|
|
529
392
|
if (dependencies.length > 0) {
|
|
530
393
|
logger.info("all dependencies completed")
|
|
531
394
|
}
|
|
532
|
-
} catch {
|
|
395
|
+
} catch (error) {
|
|
533
396
|
// abort the instance if any dependency fails
|
|
534
|
-
throw new AbortError()
|
|
397
|
+
throw new AbortError("One of the dependencies failed", { cause: error })
|
|
535
398
|
}
|
|
536
399
|
}
|
|
537
400
|
|
|
538
401
|
private async processBeforeDestroyTriggers(
|
|
402
|
+
instance: InstanceModel,
|
|
539
403
|
state: InstanceState,
|
|
540
404
|
logger: Logger,
|
|
541
405
|
signal: AbortSignal,
|
|
@@ -546,15 +410,7 @@ export class RuntimeOperation {
|
|
|
546
410
|
return
|
|
547
411
|
}
|
|
548
412
|
|
|
549
|
-
const
|
|
550
|
-
const component = this.workset.library.components[instance.type]
|
|
551
|
-
|
|
552
|
-
// fetch triggers from state backend by their IDs
|
|
553
|
-
const triggerIds = Object.values(state.extra?.triggerIds ?? {})
|
|
554
|
-
|
|
555
|
-
const allTriggers = await this.stateManager
|
|
556
|
-
.getTriggerRepository(this.project.id)
|
|
557
|
-
.getManyItems(triggerIds)
|
|
413
|
+
const allTriggers = await this.unitExtraService.getInstanceTriggers(this.project.id, state.id)
|
|
558
414
|
|
|
559
415
|
const beforeDestroyTriggers = allTriggers.filter(
|
|
560
416
|
trigger => trigger.spec.type === "before-destroy",
|
|
@@ -564,95 +420,73 @@ export class RuntimeOperation {
|
|
|
564
420
|
return
|
|
565
421
|
}
|
|
566
422
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
name => state?.extra?.triggerIds?.[name] === trigger.id,
|
|
571
|
-
)
|
|
572
|
-
|
|
573
|
-
return { name: triggerName || "unknown" }
|
|
574
|
-
})
|
|
423
|
+
const invokedTriggers = beforeDestroyTriggers.map(trigger => ({
|
|
424
|
+
name: trigger.name,
|
|
425
|
+
}))
|
|
575
426
|
|
|
576
|
-
await this.workset.
|
|
577
|
-
|
|
578
|
-
operationStatus: {
|
|
579
|
-
status: "processing-triggers",
|
|
580
|
-
},
|
|
427
|
+
await this.workset.updateState(instance.id, {
|
|
428
|
+
operationState: { status: "processing_triggers" },
|
|
581
429
|
})
|
|
582
430
|
|
|
583
431
|
logger.info("updating unit to process before-destroy triggers...")
|
|
584
432
|
|
|
585
|
-
const secrets = await this.secretService.getInstanceSecretValues(this.project,
|
|
433
|
+
const secrets = await this.secretService.getInstanceSecretValues(this.project.id, state.id)
|
|
586
434
|
|
|
587
435
|
await this.runnerBackend.update({
|
|
588
436
|
projectId: this.project.id,
|
|
437
|
+
stateId: state.id,
|
|
589
438
|
libraryId: this.project.libraryId,
|
|
590
439
|
instanceType: instance.type,
|
|
591
440
|
instanceName: instance.name,
|
|
592
|
-
config: this.prepareUnitConfig(instance,
|
|
441
|
+
config: this.prepareUnitConfig(instance, secrets, invokedTriggers),
|
|
593
442
|
refresh: this.operation.options.refresh,
|
|
594
|
-
secrets
|
|
443
|
+
secrets,
|
|
595
444
|
signal,
|
|
596
445
|
forceSignal,
|
|
446
|
+
debug: this.operation.options.debug,
|
|
597
447
|
})
|
|
598
448
|
|
|
599
449
|
logger.debug("unit update requested")
|
|
600
450
|
|
|
601
|
-
await this.watchStateStream(instance.type, instance.name, logger)
|
|
451
|
+
await this.watchStateStream(state, instance.type, instance.name, logger)
|
|
602
452
|
logger.debug("before-destroy triggers processed")
|
|
603
453
|
}
|
|
604
454
|
|
|
605
|
-
private async destroyUnit(
|
|
606
|
-
return this.getInstancePromise(
|
|
607
|
-
await this.workset.patchState({
|
|
608
|
-
id: instanceId,
|
|
609
|
-
operationStatus: {
|
|
610
|
-
status: "pending",
|
|
611
|
-
},
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
signal.throwIfAborted()
|
|
615
|
-
|
|
616
|
-
const state = this.workset.getState(instanceId)
|
|
617
|
-
if (!state) {
|
|
618
|
-
logger.warn("state not found for unit, but destroy was requested")
|
|
619
|
-
return
|
|
620
|
-
}
|
|
621
|
-
|
|
455
|
+
private async destroyUnit(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
456
|
+
return this.getInstancePromise(instance.id, async (logger, signal, forceSignal) => {
|
|
622
457
|
const dependentPromises: Promise<void>[] = []
|
|
623
|
-
const dependents = this.
|
|
458
|
+
const dependents = this.context.getDependentStates(instance.id)
|
|
624
459
|
|
|
625
460
|
for (const dependent of dependents) {
|
|
626
|
-
if (
|
|
627
|
-
!this.operation.options.destroyDependentInstances &&
|
|
628
|
-
!this.operation.instanceIdsToDestroy.includes(dependent.id)
|
|
629
|
-
) {
|
|
461
|
+
if (!this.workset.phaseAffectedInstanceIds.has(dependent.instanceId)) {
|
|
630
462
|
// skip dependents that are not affected by the operation
|
|
631
463
|
continue
|
|
632
464
|
}
|
|
633
465
|
|
|
634
|
-
|
|
466
|
+
const instance = this.context.getInstance(dependent.instanceId)
|
|
467
|
+
dependentPromises.push(this.getInstancePromiseForOperation(instance, dependent))
|
|
635
468
|
}
|
|
636
469
|
|
|
637
|
-
await
|
|
470
|
+
await waitAll(dependentPromises)
|
|
638
471
|
signal.throwIfAborted()
|
|
639
472
|
|
|
640
|
-
await this.processBeforeDestroyTriggers(state, logger, signal, forceSignal)
|
|
473
|
+
await this.processBeforeDestroyTriggers(instance, state, logger, signal, forceSignal)
|
|
641
474
|
signal.throwIfAborted()
|
|
642
475
|
|
|
643
476
|
logger.info("destroying unit...")
|
|
644
477
|
|
|
645
|
-
await this.workset.
|
|
646
|
-
|
|
647
|
-
operationStatus: {
|
|
478
|
+
await this.workset.updateState(instance.id, {
|
|
479
|
+
operationState: {
|
|
648
480
|
status: "destroying",
|
|
481
|
+
startedAt: new Date(),
|
|
649
482
|
},
|
|
650
483
|
})
|
|
651
484
|
|
|
652
|
-
const [type, name] = parseInstanceId(
|
|
485
|
+
const [type, name] = parseInstanceId(instance.id)
|
|
653
486
|
|
|
654
487
|
await this.runnerBackend.destroy({
|
|
655
488
|
projectId: this.project.id,
|
|
489
|
+
stateId: state.id,
|
|
656
490
|
libraryId: this.project.libraryId,
|
|
657
491
|
instanceType: type,
|
|
658
492
|
instanceName: name,
|
|
@@ -661,89 +495,76 @@ export class RuntimeOperation {
|
|
|
661
495
|
forceSignal,
|
|
662
496
|
deleteUnreachable: this.operation.options.deleteUnreachableResources,
|
|
663
497
|
forceDeleteState: this.operation.options.forceDeleteState,
|
|
498
|
+
debug: this.operation.options.debug,
|
|
664
499
|
})
|
|
665
500
|
|
|
666
501
|
logger.debug("destroy request sent")
|
|
667
502
|
|
|
668
|
-
await this.watchStateStream(type, name, logger)
|
|
503
|
+
await this.watchStateStream(state, type, name, logger)
|
|
669
504
|
|
|
670
505
|
// clean up artifacts after successful destruction
|
|
671
|
-
|
|
672
|
-
const artifactIds = unique(Object.values(state.extra?.exportedArtifactIds ?? {}).flat())
|
|
673
|
-
|
|
674
|
-
await this.artifactService.removeUsages(
|
|
675
|
-
//
|
|
676
|
-
this.project.id,
|
|
677
|
-
artifactIds,
|
|
678
|
-
[{ type: "instance", instanceId: state.id }],
|
|
679
|
-
)
|
|
680
|
-
} catch (error) {
|
|
681
|
-
logger.warn({ error }, "failed to cleanup artifacts for destroyed instance")
|
|
682
|
-
}
|
|
506
|
+
await this.artifactService.clearInstanceArtifactReferences(this.project.id, instance.id)
|
|
683
507
|
|
|
684
508
|
logger.info("unit destroyed")
|
|
685
509
|
})
|
|
686
510
|
}
|
|
687
511
|
|
|
688
|
-
private async refreshUnit(
|
|
689
|
-
return this.getInstancePromise(
|
|
690
|
-
await this.workset.
|
|
691
|
-
|
|
692
|
-
operationStatus: {
|
|
512
|
+
private async refreshUnit(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
513
|
+
return this.getInstancePromise(instance.id, async (logger, signal, forceSignal) => {
|
|
514
|
+
await this.workset.updateState(instance.id, {
|
|
515
|
+
operationState: {
|
|
693
516
|
status: "refreshing",
|
|
694
|
-
|
|
695
|
-
totalResourceCount: 0,
|
|
517
|
+
startedAt: new Date(),
|
|
696
518
|
},
|
|
697
519
|
})
|
|
698
520
|
|
|
699
521
|
logger.info("refreshing unit...")
|
|
700
522
|
|
|
701
|
-
const [type, name] = parseInstanceId(
|
|
523
|
+
const [type, name] = parseInstanceId(instance.id)
|
|
702
524
|
|
|
703
525
|
await this.runnerBackend.refresh({
|
|
704
526
|
projectId: this.project.id,
|
|
527
|
+
stateId: state.id,
|
|
705
528
|
libraryId: this.project.libraryId,
|
|
706
529
|
instanceType: type,
|
|
707
530
|
instanceName: name,
|
|
708
531
|
signal,
|
|
709
532
|
forceSignal,
|
|
533
|
+
debug: this.operation.options.debug,
|
|
710
534
|
})
|
|
711
535
|
|
|
712
536
|
logger.debug("unit refresh requested")
|
|
713
537
|
|
|
714
|
-
await this.watchStateStream(type, name, logger)
|
|
538
|
+
await this.watchStateStream(state, type, name, logger)
|
|
715
539
|
logger.info("unit refreshed")
|
|
716
540
|
})
|
|
717
541
|
}
|
|
718
542
|
|
|
719
543
|
private async watchStateStream(
|
|
720
|
-
|
|
544
|
+
state: InstanceState,
|
|
545
|
+
instanceType: VersionedName,
|
|
721
546
|
instanceName: string,
|
|
722
547
|
logger: Logger,
|
|
723
548
|
): Promise<void> {
|
|
724
549
|
const stream = this.runnerBackend.watch({
|
|
725
550
|
projectId: this.project.id,
|
|
551
|
+
stateId: state.id,
|
|
726
552
|
libraryId: this.project.libraryId,
|
|
727
553
|
instanceType,
|
|
728
554
|
instanceName,
|
|
555
|
+
debug: this.operation.options.debug,
|
|
729
556
|
})
|
|
730
557
|
|
|
731
558
|
let update: UnitStateUpdate | undefined
|
|
732
559
|
|
|
733
560
|
for await (update of stream) {
|
|
734
561
|
try {
|
|
735
|
-
await this.handleUnitStateUpdate(update)
|
|
562
|
+
await this.handleUnitStateUpdate(update, state)
|
|
736
563
|
} catch (error) {
|
|
737
564
|
logger.error({ error }, "failed to handle unit state update")
|
|
738
565
|
}
|
|
739
566
|
|
|
740
567
|
if (update.type === "error") {
|
|
741
|
-
if (isAbortErrorLike(update.message)) {
|
|
742
|
-
// abort the unit if the returned error contains some abort-like pattern
|
|
743
|
-
// generally, this might not be safe, but for now, we assume that
|
|
744
|
-
throw new AbortError()
|
|
745
|
-
}
|
|
746
|
-
|
|
747
568
|
// rethrow the error to stop the execution of dependent units
|
|
748
569
|
throw new Error(`An error occurred while processing the unit "${update.unitId}"`, {
|
|
749
570
|
cause: update.message,
|
|
@@ -762,204 +583,155 @@ export class RuntimeOperation {
|
|
|
762
583
|
|
|
763
584
|
private prepareUnitConfig(
|
|
764
585
|
instance: InstanceModel,
|
|
765
|
-
|
|
586
|
+
secrets: Record<string, unknown>,
|
|
766
587
|
invokedTriggers: TriggerInvocation[] = [],
|
|
767
|
-
):
|
|
768
|
-
const
|
|
588
|
+
): UnitConfig {
|
|
589
|
+
const resolvedInputs = this.context.getResolvedInputs(instance.id)
|
|
769
590
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
591
|
+
return {
|
|
592
|
+
instanceId: instance.id,
|
|
593
|
+
args: instance.args ?? {},
|
|
594
|
+
inputs: mapValues(resolvedInputs ?? {}, input => input.map(value => value.input)),
|
|
595
|
+
invokedTriggers,
|
|
596
|
+
secretNames: Object.keys(secrets),
|
|
597
|
+
stateIdMap: this.context.getInstanceIdToStateIdMap(),
|
|
774
598
|
}
|
|
775
|
-
|
|
776
|
-
const instanceInputs = this.workset.resolvedInstanceInputs.get(instance.id) ?? {}
|
|
777
|
-
|
|
778
|
-
for (const key of Object.keys(component.inputs)) {
|
|
779
|
-
const inputs = instanceInputs[key] ?? []
|
|
780
|
-
|
|
781
|
-
config[`input.${key}`] = JSON.stringify(inputs.map(input => input.input))
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
config["$invokedTriggers"] = JSON.stringify(invokedTriggers)
|
|
785
|
-
|
|
786
|
-
return config
|
|
787
599
|
}
|
|
788
600
|
|
|
789
|
-
private async handleUnitStateUpdate(
|
|
601
|
+
private async handleUnitStateUpdate(
|
|
602
|
+
update: UnitStateUpdate,
|
|
603
|
+
state: InstanceState,
|
|
604
|
+
): Promise<void> {
|
|
790
605
|
switch (update.type) {
|
|
791
606
|
case "message":
|
|
792
|
-
this.handleUnitMessage(update)
|
|
607
|
+
this.handleUnitMessage(update, state)
|
|
793
608
|
return
|
|
794
609
|
case "progress":
|
|
795
610
|
await this.handleUnitProgress(update)
|
|
796
611
|
return
|
|
797
612
|
case "error":
|
|
798
|
-
await this.handleUnitError(update)
|
|
613
|
+
await this.handleUnitError(update, state)
|
|
799
614
|
return
|
|
800
615
|
case "completion":
|
|
801
|
-
await this.handleUnitCompletion(update)
|
|
616
|
+
await this.handleUnitCompletion(update, state)
|
|
802
617
|
return
|
|
803
618
|
}
|
|
804
619
|
}
|
|
805
620
|
|
|
806
|
-
private handleUnitMessage(update: TypedUnitStateUpdate<"message"
|
|
807
|
-
//
|
|
808
|
-
const instanceIdsInHierarchy: string[] = []
|
|
809
|
-
let instance: InstanceModel | null = this.workset.getInstance(update.unitId)
|
|
810
|
-
|
|
621
|
+
private handleUnitMessage(update: TypedUnitStateUpdate<"message">, state: InstanceState): void {
|
|
622
|
+
// append logs to the instance and all its parents
|
|
811
623
|
for (;;) {
|
|
812
|
-
|
|
624
|
+
this.promiseTracker.track(
|
|
625
|
+
this.operationService.appendLog(
|
|
626
|
+
this.project.id,
|
|
627
|
+
this.operation.id,
|
|
628
|
+
state.id,
|
|
629
|
+
update.message,
|
|
630
|
+
),
|
|
631
|
+
)
|
|
813
632
|
|
|
814
|
-
if (!
|
|
633
|
+
if (!state.parentInstanceId) {
|
|
815
634
|
break
|
|
816
635
|
}
|
|
817
636
|
|
|
818
|
-
|
|
637
|
+
state = this.context.getState(state.parentInstanceId)
|
|
819
638
|
}
|
|
820
|
-
|
|
821
|
-
// persist the log for all instances in the hierarchy
|
|
822
|
-
// TODO: batch
|
|
823
|
-
this.promiseTracker.track(
|
|
824
|
-
this.operationService.appendLogs(this.project.id, this.operation.id, instanceIdsInHierarchy, [
|
|
825
|
-
update.message,
|
|
826
|
-
]),
|
|
827
|
-
)
|
|
828
|
-
return
|
|
829
639
|
}
|
|
830
640
|
|
|
831
641
|
private async handleUnitProgress(update: TypedUnitStateUpdate<"progress">): Promise<void> {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (update.totalResourceCount !== undefined) {
|
|
840
|
-
patch.operationStatus.totalResourceCount = update.totalResourceCount
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
await this.workset.patchState(patch)
|
|
642
|
+
await this.workset.updateState(update.unitId, {
|
|
643
|
+
operationState: {
|
|
644
|
+
currentResourceCount: update.currentResourceCount,
|
|
645
|
+
totalResourceCount: update.totalResourceCount,
|
|
646
|
+
},
|
|
647
|
+
})
|
|
844
648
|
}
|
|
845
649
|
|
|
846
|
-
private async handleUnitError(
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
status: "error",
|
|
650
|
+
private async handleUnitError(
|
|
651
|
+
update: TypedUnitStateUpdate<"error">,
|
|
652
|
+
state: InstanceState,
|
|
653
|
+
): Promise<void> {
|
|
654
|
+
await this.workset.updateState(update.unitId, {
|
|
655
|
+
instanceState: {
|
|
656
|
+
// keep "deployed" status for initially deployed instances even if the operation was failed or cancelled
|
|
657
|
+
status: state.status === "deployed" ? "deployed" : "failed",
|
|
855
658
|
message: update.message,
|
|
856
659
|
},
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
660
|
+
operationState: {
|
|
661
|
+
status: isAbortErrorLike(update.message) ? "cancelled" : "failed",
|
|
662
|
+
finishedAt: new Date(),
|
|
663
|
+
},
|
|
664
|
+
})
|
|
860
665
|
}
|
|
861
666
|
|
|
862
|
-
private async handleUnitCompletion(
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const state = this.workset.getState(update.unitId)
|
|
870
|
-
if (!state) {
|
|
871
|
-
throw new Error(
|
|
872
|
-
`Cannot handle completion for unit "${update.unitId}" because its state is not found.`,
|
|
873
|
-
)
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
const instance = this.workset.getInstance(update.unitId)
|
|
667
|
+
private async handleUnitCompletion(
|
|
668
|
+
update: TypedUnitStateUpdate<"completion">,
|
|
669
|
+
state: InstanceState,
|
|
670
|
+
): Promise<void> {
|
|
671
|
+
const instance = this.context.getInstance(update.unitId)
|
|
877
672
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
operationStatus: { outputHash: update.outputHash },
|
|
883
|
-
})
|
|
673
|
+
const data: InstanceStatePatch = {
|
|
674
|
+
status: this.workset.getNextStableInstanceStatus(instance.id),
|
|
675
|
+
message: update.message,
|
|
676
|
+
statusFields: update.statusFields ?? null,
|
|
884
677
|
}
|
|
885
678
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
id: update.unitId,
|
|
891
|
-
operationStatus: {
|
|
892
|
-
status: "created",
|
|
893
|
-
message:
|
|
894
|
-
update.message ?? "The operation on this instance has been completed successfully.",
|
|
895
|
-
outputHash: update.outputHash ?? undefined,
|
|
896
|
-
inputHash,
|
|
897
|
-
dependencyOutputHash,
|
|
898
|
-
},
|
|
899
|
-
extra: {
|
|
900
|
-
statusFields: update.statusFields,
|
|
901
|
-
|
|
902
|
-
terminalIds: update.terminals
|
|
903
|
-
? this.processTerminals(update.unitId, update.terminals, state.extra?.terminalIds)
|
|
904
|
-
: null,
|
|
679
|
+
if (update.operationType !== "destroy") {
|
|
680
|
+
// давайте еще больше усложним и без того сложную штуку
|
|
681
|
+
// set output hash before calculating input hash to capture up-to-date output hash for dependencies
|
|
682
|
+
state.outputHash = update.outputHash ?? null
|
|
905
683
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
684
|
+
// recalculate the input and output hashes for the instance
|
|
685
|
+
const { inputHash, dependencyOutputHash } =
|
|
686
|
+
await this.context.getUpToDateInputHashOutput(instance)
|
|
909
687
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
688
|
+
data.inputHash = inputHash
|
|
689
|
+
data.dependencyOutputHash = dependencyOutputHash
|
|
690
|
+
data.outputHash = update.outputHash
|
|
913
691
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
692
|
+
// also update the parent ID
|
|
693
|
+
if (instance.parentId) {
|
|
694
|
+
const parentState = this.context.getState(instance.parentId)
|
|
695
|
+
data.parentId = parentState.id
|
|
696
|
+
} else {
|
|
697
|
+
data.parentId = null
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
data.message = null
|
|
701
|
+
data.inputHash = null
|
|
702
|
+
data.dependencyOutputHash = null
|
|
703
|
+
data.outputHash = null
|
|
704
|
+
data.parentId = null
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// update the operation state
|
|
708
|
+
await this.workset.updateState(instance.id, {
|
|
709
|
+
// TODO: honestly, it is not correct
|
|
710
|
+
// may be we should track operation phases separately
|
|
711
|
+
// or introduce status like "destroyed-before-recreation" (quite ugly though)
|
|
712
|
+
operationState: {
|
|
713
|
+
status: this.workset.getStableStatusByOperationPhase(),
|
|
714
|
+
finishedAt: new Date(),
|
|
934
715
|
},
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
if (update.secrets) {
|
|
938
|
-
const instance = this.workset.getInstance(update.unitId)
|
|
939
716
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
operationStatus: null,
|
|
955
|
-
extra: null,
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
await this.workset.patchState(patch)
|
|
717
|
+
// do not write instance state for non-last phases of the instance
|
|
718
|
+
instanceState: this.workset.isLastPhaseForInstance(instance.id) ? data : undefined,
|
|
719
|
+
|
|
720
|
+
// also do not write unit extra data for non-last phases of the instance
|
|
721
|
+
unitExtra: this.workset.isLastPhaseForInstance(instance.id)
|
|
722
|
+
? {
|
|
723
|
+
pages: update.pages ?? [],
|
|
724
|
+
terminals: update.terminals ?? [],
|
|
725
|
+
triggers: update.triggers ?? [],
|
|
726
|
+
workers: update.workers ?? [],
|
|
727
|
+
secrets: update.secrets ?? {},
|
|
728
|
+
}
|
|
729
|
+
: undefined,
|
|
730
|
+
})
|
|
959
731
|
}
|
|
960
732
|
|
|
961
733
|
private getInstancePromise(
|
|
962
|
-
instanceId:
|
|
734
|
+
instanceId: InstanceId,
|
|
963
735
|
fn: (logger: Logger, signal: AbortSignal, forceSignal: AbortSignal) => Promise<void>,
|
|
964
736
|
): Promise<void> {
|
|
965
737
|
let instancePromise = this.instancePromiseMap.get(instanceId)
|
|
@@ -967,30 +739,54 @@ export class RuntimeOperation {
|
|
|
967
739
|
return instancePromise
|
|
968
740
|
}
|
|
969
741
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
742
|
+
// "pending" -> "running" if at least one instance is running
|
|
743
|
+
if (this.operation.status === "pending") {
|
|
744
|
+
this.operation.status = "running"
|
|
745
|
+
this.promiseTracker.track(this.updateOperation({ status: this.operation.status }))
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const state = this.context.getState(instanceId)
|
|
973
749
|
|
|
974
|
-
|
|
750
|
+
const logger = this.logger.child({ instanceId }, { msgPrefix: `[${instanceId}] ` })
|
|
751
|
+
const abortControllerPair = this.workset.instanceAbortControllers.get(instanceId)
|
|
752
|
+
if (!abortControllerPair) {
|
|
975
753
|
throw new Error(`Abort controllers for instance "${instanceId}" are not initialized`)
|
|
976
754
|
}
|
|
977
755
|
|
|
756
|
+
const { abortController, forceAbortController } = abortControllerPair
|
|
757
|
+
|
|
978
758
|
instancePromise = this.workset
|
|
979
|
-
.
|
|
759
|
+
.waitForInstanceLock(state.id, abortController.signal)
|
|
980
760
|
.then(() => fn(logger, abortController.signal, forceAbortController.signal))
|
|
981
761
|
.catch(error => {
|
|
982
|
-
if (isAbortErrorLike(error)) {
|
|
983
|
-
// if the operation was aborted, restore the initial state of the instance
|
|
984
|
-
this.promiseTracker.track(this.workset.restoreInitialState(instanceId))
|
|
985
|
-
|
|
986
|
-
throw error
|
|
987
|
-
}
|
|
988
|
-
|
|
989
762
|
if (this.operation.status !== "failing") {
|
|
990
763
|
// report the failing status of the operation
|
|
991
764
|
this.operation.status = "failing"
|
|
765
|
+
this.promiseTracker.track(this.updateOperation({ status: this.operation.status }))
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (isTransientInstanceOperationStatus(state.lastOperationState?.status)) {
|
|
769
|
+
// if the underlying method did not correctly update the instance status, do it here
|
|
992
770
|
this.promiseTracker.track(
|
|
993
|
-
this.
|
|
771
|
+
this.workset.updateState(instanceId, {
|
|
772
|
+
operationState: {
|
|
773
|
+
status: isAbortErrorLike(error) ? "cancelled" : "failed",
|
|
774
|
+
finishedAt: new Date(),
|
|
775
|
+
},
|
|
776
|
+
instanceState: {
|
|
777
|
+
// keep "deployed" status for initially deployed instances even if the operation was failed or cancelled
|
|
778
|
+
status: state.status === "deployed" ? "deployed" : "failed",
|
|
779
|
+
},
|
|
780
|
+
}),
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
this.promiseTracker.track(
|
|
784
|
+
this.operationService.appendLog(
|
|
785
|
+
this.project.id,
|
|
786
|
+
this.operation.id,
|
|
787
|
+
state.id,
|
|
788
|
+
errorToString(error),
|
|
789
|
+
),
|
|
994
790
|
)
|
|
995
791
|
}
|
|
996
792
|
|
|
@@ -998,48 +794,67 @@ export class RuntimeOperation {
|
|
|
998
794
|
throw error
|
|
999
795
|
})
|
|
1000
796
|
.finally(() => {
|
|
797
|
+
if (!this.workset.isLastPhaseForInstance(instanceId)) {
|
|
798
|
+
// do not finalize the instance if it has more phases to run
|
|
799
|
+
return
|
|
800
|
+
}
|
|
801
|
+
|
|
1001
802
|
this.instancePromiseMap.delete(instanceId)
|
|
1002
803
|
|
|
1003
804
|
// TODO: ideally we should defer unlocking until all direct dependents are completed,
|
|
1004
805
|
// to ensure that they are received expected inputs from this instance
|
|
1005
806
|
this.promiseTracker.track(
|
|
1006
|
-
this.instanceLockService
|
|
807
|
+
this.instanceLockService
|
|
808
|
+
.unlockInstances(this.project.id, [state.id], this.unlockToken)
|
|
809
|
+
.then(() => this.workset.markInstanceUnlocked(state.id)),
|
|
1007
810
|
)
|
|
811
|
+
|
|
812
|
+
this.logger.debug(`promise for instance "%s" completed`, instanceId)
|
|
1008
813
|
})
|
|
1009
814
|
|
|
1010
815
|
this.instancePromiseMap.set(instanceId, instancePromise)
|
|
816
|
+
this.logger.trace(`created new promise for instance "%s"`, instanceId)
|
|
1011
817
|
|
|
1012
818
|
return instancePromise
|
|
1013
819
|
}
|
|
1014
820
|
|
|
1015
|
-
private async
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
parentId: instance.parentId,
|
|
1021
|
-
operationStatus: {
|
|
1022
|
-
status: "pending",
|
|
1023
|
-
message: "",
|
|
1024
|
-
dependencyIds: this.getInstanceDependencyIds(instance.id),
|
|
1025
|
-
latestOperationId: this.operation.id,
|
|
1026
|
-
inputHashNonce: state?.operationStatus?.inputHashNonce ?? randomBytes(4).readInt32LE(),
|
|
1027
|
-
args: instance.args,
|
|
1028
|
-
},
|
|
1029
|
-
})
|
|
1030
|
-
}
|
|
821
|
+
private async ensureInstancesUnlocked(): Promise<void> {
|
|
822
|
+
const lockedStateIds = Array.from(this.workset.getLockedStateIds())
|
|
823
|
+
if (lockedStateIds.length === 0) {
|
|
824
|
+
return
|
|
825
|
+
}
|
|
1031
826
|
|
|
1032
|
-
|
|
1033
|
-
const dependencies = new Set<string>()
|
|
1034
|
-
const instanceInputs = this.workset.resolvedInstanceInputs.get(instanceId) ?? {}
|
|
827
|
+
this.logger.warn("unlocking %d locked instances before shutting down", lockedStateIds.length)
|
|
1035
828
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
829
|
+
await this.instanceLockService.unlockInstances(
|
|
830
|
+
this.project.id,
|
|
831
|
+
lockedStateIds,
|
|
832
|
+
this.unlockToken,
|
|
833
|
+
)
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private async ensureOperationStatesFinalized(): Promise<void> {
|
|
837
|
+
const unfinishedStates = this.context.getUnfinishedOperationStates()
|
|
838
|
+
if (unfinishedStates.length === 0) {
|
|
839
|
+
return
|
|
1040
840
|
}
|
|
1041
841
|
|
|
1042
|
-
|
|
842
|
+
this.logger.warn(
|
|
843
|
+
"finalizing %d unfinished operation states before shutting down",
|
|
844
|
+
unfinishedStates.length,
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
for (const state of unfinishedStates) {
|
|
848
|
+
await this.workset.updateState(state.instanceId, {
|
|
849
|
+
operationState: {
|
|
850
|
+
status: "failed",
|
|
851
|
+
finishedAt: new Date(),
|
|
852
|
+
},
|
|
853
|
+
instanceState: {
|
|
854
|
+
status: state.status === "deployed" ? "deployed" : "failed",
|
|
855
|
+
},
|
|
856
|
+
})
|
|
857
|
+
}
|
|
1043
858
|
}
|
|
1044
859
|
|
|
1045
860
|
/**
|
|
@@ -1048,22 +863,22 @@ export class RuntimeOperation {
|
|
|
1048
863
|
*/
|
|
1049
864
|
private collectArtifactIdsForInstance(instance: InstanceModel): string[] {
|
|
1050
865
|
const artifactIds = new Set<string>()
|
|
1051
|
-
const instanceInputs = this.
|
|
866
|
+
const instanceInputs = this.context.getResolvedInputs(instance.id) ?? {}
|
|
1052
867
|
|
|
1053
868
|
for (const inputs of Object.values(instanceInputs)) {
|
|
1054
869
|
for (const input of inputs) {
|
|
1055
|
-
const dependencyState = this.
|
|
1056
|
-
if (!dependencyState
|
|
870
|
+
const dependencyState = this.context.getState(input.input.instanceId)
|
|
871
|
+
if (!dependencyState.exportedArtifactIds) {
|
|
1057
872
|
continue
|
|
1058
873
|
}
|
|
1059
874
|
|
|
1060
875
|
const outputKey = input.input.output
|
|
1061
|
-
const
|
|
1062
|
-
if (!
|
|
876
|
+
const outputArtifactIds = dependencyState.exportedArtifactIds[outputKey]
|
|
877
|
+
if (!outputArtifactIds) {
|
|
1063
878
|
continue
|
|
1064
879
|
}
|
|
1065
880
|
|
|
1066
|
-
for (const hash of
|
|
881
|
+
for (const hash of outputArtifactIds) {
|
|
1067
882
|
artifactIds.add(hash)
|
|
1068
883
|
}
|
|
1069
884
|
}
|
|
@@ -1071,327 +886,4 @@ export class RuntimeOperation {
|
|
|
1071
886
|
|
|
1072
887
|
return Array.from(artifactIds)
|
|
1073
888
|
}
|
|
1074
|
-
|
|
1075
|
-
/**
|
|
1076
|
-
* Collects secret IDs from dependencies based on the direct connections
|
|
1077
|
-
* from instance inputs to dependency outputs.
|
|
1078
|
-
*/
|
|
1079
|
-
private collectSecretIdsForInstance(instance: InstanceModel): Set<string> {
|
|
1080
|
-
const secretIds = new Set<string>()
|
|
1081
|
-
const instanceInputs = this.workset.resolvedInstanceInputs.get(instance.id) ?? {}
|
|
1082
|
-
|
|
1083
|
-
for (const inputs of Object.values(instanceInputs)) {
|
|
1084
|
-
for (const input of inputs) {
|
|
1085
|
-
const dependencyState = this.workset.getState(input.input.instanceId)
|
|
1086
|
-
if (!dependencyState?.extra?.exportedSecretIds) {
|
|
1087
|
-
continue
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
const outputKey = input.input.output
|
|
1091
|
-
const outputSecrets = dependencyState.extra.exportedSecretIds[outputKey]
|
|
1092
|
-
if (!outputSecrets) {
|
|
1093
|
-
continue
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
for (const secretId of outputSecrets) {
|
|
1097
|
-
secretIds.add(secretId)
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
return secretIds
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Updates the secrets for a specific instance.
|
|
1107
|
-
* Processes terminals from instance state update.
|
|
1108
|
-
*
|
|
1109
|
-
* Converts unit terminals to instance terminals and handles cleanup.
|
|
1110
|
-
*/
|
|
1111
|
-
private processTerminals(
|
|
1112
|
-
instanceId: string,
|
|
1113
|
-
unitTerminals: UnitTerminal[],
|
|
1114
|
-
currentTerminalIds: Record<string, string> | undefined,
|
|
1115
|
-
): Record<string, string> {
|
|
1116
|
-
const terminals: Terminal[] = unitTerminals.map(unitTerminal => ({
|
|
1117
|
-
id: currentTerminalIds?.[unitTerminal.name] ?? uuidv7(),
|
|
1118
|
-
instanceId,
|
|
1119
|
-
meta: unitTerminal.meta ?? {},
|
|
1120
|
-
}))
|
|
1121
|
-
|
|
1122
|
-
const terminalSpecEntries: [string, TerminalSpec][] = unitTerminals
|
|
1123
|
-
//
|
|
1124
|
-
.map((unitTerminal, index) => [terminals[index].id, unitTerminal.spec])
|
|
1125
|
-
|
|
1126
|
-
// create mapping from local name to UUID
|
|
1127
|
-
const newTerminalIds: Record<string, string> = {}
|
|
1128
|
-
for (let i = 0; i < unitTerminals.length; i++) {
|
|
1129
|
-
const unitTerminal = unitTerminals[i]
|
|
1130
|
-
const instanceTerminal = terminals[i]
|
|
1131
|
-
newTerminalIds[unitTerminal.name] = instanceTerminal.id
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
// find terminals that need to be deleted (old terminals not in new list)
|
|
1135
|
-
const terminalsToDelete = Object.values(currentTerminalIds ?? {}).filter(
|
|
1136
|
-
id => !Object.values(newTerminalIds).includes(id),
|
|
1137
|
-
)
|
|
1138
|
-
|
|
1139
|
-
if (terminals.length > 0) {
|
|
1140
|
-
this.promiseTracker.track(this.putTerminals(terminals, terminalSpecEntries))
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (terminalsToDelete.length > 0) {
|
|
1144
|
-
this.promiseTracker.track(this.deleteTerminals(terminalsToDelete))
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
return newTerminalIds
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
private async putTerminals(
|
|
1151
|
-
terminals: Terminal[],
|
|
1152
|
-
terminalSpecEntries: [string, TerminalSpec][],
|
|
1153
|
-
): Promise<void> {
|
|
1154
|
-
if (this.operation.type === "preview") {
|
|
1155
|
-
return
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
await using batch = this.stateManager.batch()
|
|
1159
|
-
|
|
1160
|
-
await Promise.all([
|
|
1161
|
-
this.stateManager.getTerminalRepository(this.project.id).putManyItems(terminals, batch),
|
|
1162
|
-
|
|
1163
|
-
this.stateManager
|
|
1164
|
-
.getTerminalSpecRepository(this.project.id)
|
|
1165
|
-
.putMany(terminalSpecEntries, batch),
|
|
1166
|
-
])
|
|
1167
|
-
|
|
1168
|
-
await batch.write()
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
private async deleteTerminals(terminalIds: string[]): Promise<void> {
|
|
1172
|
-
if (this.operation.type === "preview") {
|
|
1173
|
-
return
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
await using batch = this.stateManager.batch()
|
|
1177
|
-
|
|
1178
|
-
await Promise.all([
|
|
1179
|
-
this.stateManager.getTerminalRepository(this.project.id).deleteMany(terminalIds, batch),
|
|
1180
|
-
this.stateManager.getTerminalSpecRepository(this.project.id).deleteMany(terminalIds, batch),
|
|
1181
|
-
])
|
|
1182
|
-
|
|
1183
|
-
await batch.write()
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
/**
|
|
1187
|
-
* Processes pages from instance state update.
|
|
1188
|
-
*
|
|
1189
|
-
* Converts unit pages to instance pages and handles cleanup.
|
|
1190
|
-
*/
|
|
1191
|
-
private processPages(
|
|
1192
|
-
instanceId: string,
|
|
1193
|
-
unitPages: UnitPage[],
|
|
1194
|
-
currentPageIds: Record<string, string> | undefined,
|
|
1195
|
-
): Record<string, string> {
|
|
1196
|
-
const pages: Page[] = unitPages.map(unitPage => ({
|
|
1197
|
-
id: currentPageIds?.[unitPage.name] ?? uuidv7(),
|
|
1198
|
-
instanceId,
|
|
1199
|
-
meta: unitPage.meta,
|
|
1200
|
-
}))
|
|
1201
|
-
|
|
1202
|
-
const pageContentEntries: [string, PageBlock[]][] = unitPages
|
|
1203
|
-
//
|
|
1204
|
-
.map((unitPage, index) => [pages[index].id, unitPage.content])
|
|
1205
|
-
|
|
1206
|
-
// create mapping from local name to UUID
|
|
1207
|
-
const newPageIds: Record<string, string> = {}
|
|
1208
|
-
for (let i = 0; i < unitPages.length; i++) {
|
|
1209
|
-
const unitPage = unitPages[i]
|
|
1210
|
-
const instancePage = pages[i]
|
|
1211
|
-
newPageIds[unitPage.name] = instancePage.id
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// find pages that need to be deleted (old pages not in new list)
|
|
1215
|
-
const pagesToDelete = Object.values(currentPageIds ?? {}).filter(
|
|
1216
|
-
id => !Object.values(newPageIds).includes(id),
|
|
1217
|
-
)
|
|
1218
|
-
|
|
1219
|
-
if (pages.length > 0) {
|
|
1220
|
-
this.promiseTracker.track(this.putPages(pages, pageContentEntries))
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
if (pagesToDelete.length > 0) {
|
|
1224
|
-
this.promiseTracker.track(this.deletePages(pagesToDelete))
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
return newPageIds
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
private async putPages(
|
|
1231
|
-
pages: Page[],
|
|
1232
|
-
pageContentEntries: [string, PageBlock[]][],
|
|
1233
|
-
): Promise<void> {
|
|
1234
|
-
if (this.operation.type === "preview") {
|
|
1235
|
-
return
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
const batch = this.stateManager.batch()
|
|
1239
|
-
|
|
1240
|
-
await Promise.all([
|
|
1241
|
-
this.stateManager.getPageRepository(this.project.id).putManyItems(pages, batch),
|
|
1242
|
-
this.stateManager
|
|
1243
|
-
.getPageContentRepository(this.project.id)
|
|
1244
|
-
.putMany(pageContentEntries, batch),
|
|
1245
|
-
])
|
|
1246
|
-
|
|
1247
|
-
await batch.write()
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
private async deletePages(pageIds: string[]): Promise<void> {
|
|
1251
|
-
if (this.operation.type === "preview") {
|
|
1252
|
-
return
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
const batch = this.stateManager.batch()
|
|
1256
|
-
|
|
1257
|
-
await Promise.all([
|
|
1258
|
-
this.stateManager.getPageRepository(this.project.id).deleteMany(pageIds, batch),
|
|
1259
|
-
this.stateManager.getPageContentRepository(this.project.id).deleteMany(pageIds, batch),
|
|
1260
|
-
])
|
|
1261
|
-
|
|
1262
|
-
await batch.write()
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
/**
|
|
1266
|
-
* Processes triggers from instance state update.
|
|
1267
|
-
*
|
|
1268
|
-
* Converts unit triggers to instance triggers and handles cleanup.
|
|
1269
|
-
*/
|
|
1270
|
-
private processTriggers(
|
|
1271
|
-
instanceId: string,
|
|
1272
|
-
unitTriggers: UnitTrigger[],
|
|
1273
|
-
currentTriggerIds: Record<string, string> | undefined,
|
|
1274
|
-
): Record<string, string> {
|
|
1275
|
-
const triggers: Trigger[] = unitTriggers.map(unitTrigger => ({
|
|
1276
|
-
id: currentTriggerIds?.[unitTrigger.name] ?? uuidv7(),
|
|
1277
|
-
instanceId,
|
|
1278
|
-
meta: unitTrigger.meta ?? {},
|
|
1279
|
-
spec: unitTrigger.spec,
|
|
1280
|
-
}))
|
|
1281
|
-
|
|
1282
|
-
// create mapping from local name to UUID
|
|
1283
|
-
const newTriggerIds: Record<string, string> = {}
|
|
1284
|
-
for (let i = 0; i < unitTriggers.length; i++) {
|
|
1285
|
-
const unitTrigger = unitTriggers[i]
|
|
1286
|
-
newTriggerIds[unitTrigger.name] = triggers[i].id
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
// find triggers that need to be deleted (old triggers not in new list)
|
|
1290
|
-
const triggersToDelete = Object.values(currentTriggerIds ?? {}).filter(
|
|
1291
|
-
id => !Object.values(newTriggerIds).includes(id),
|
|
1292
|
-
)
|
|
1293
|
-
|
|
1294
|
-
if (triggers.length > 0) {
|
|
1295
|
-
this.promiseTracker.track(this.putTriggers(triggers))
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
if (triggersToDelete.length > 0) {
|
|
1299
|
-
this.promiseTracker.track(this.deleteTriggers(triggersToDelete))
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
return newTriggerIds
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
private async putTriggers(triggers: Trigger[]): Promise<void> {
|
|
1306
|
-
if (this.operation.type === "preview") {
|
|
1307
|
-
return
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
await this.stateManager.getTriggerRepository(this.project.id).putManyItems(triggers)
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
private async deleteTriggers(triggerIds: string[]): Promise<void> {
|
|
1314
|
-
if (this.operation.type === "preview") {
|
|
1315
|
-
return
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
await this.stateManager.getTriggerRepository(this.project.id).deleteMany(triggerIds)
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
/**
|
|
1322
|
-
* Processes artifacts from instance state update.
|
|
1323
|
-
*/
|
|
1324
|
-
private processArtifacts(
|
|
1325
|
-
instanceId: string,
|
|
1326
|
-
newOwnedArtifacts: Record<string, RunnerArtifact[]>,
|
|
1327
|
-
oldOwnedArtifactIds: Record<string, string[]> | undefined,
|
|
1328
|
-
): Record<string, string[]> {
|
|
1329
|
-
if (this.operation.type !== "preview") {
|
|
1330
|
-
// persist the new owned artifacts if this is not a preview operation
|
|
1331
|
-
this.promiseTracker.track(
|
|
1332
|
-
this.artifactService.updateUsage(
|
|
1333
|
-
this.project.id,
|
|
1334
|
-
{ type: "instance", instanceId },
|
|
1335
|
-
unique(Object.values(oldOwnedArtifactIds ?? {}).flat()),
|
|
1336
|
-
unique(
|
|
1337
|
-
Object.values(newOwnedArtifacts).flatMap(artifacts =>
|
|
1338
|
-
artifacts.map(artifact => artifact.id),
|
|
1339
|
-
),
|
|
1340
|
-
),
|
|
1341
|
-
),
|
|
1342
|
-
)
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
return mapValues(newOwnedArtifacts, artifacts => artifacts.map(artifact => artifact.id))
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
/**
|
|
1349
|
-
* Processes exported secrets from instance state update.
|
|
1350
|
-
*
|
|
1351
|
-
* Validates that all secret IDs are either present in the instance secrets or are in "exportedSecrets" of the direct dependencies
|
|
1352
|
-
* from connected inputs.
|
|
1353
|
-
*
|
|
1354
|
-
* Returns a mapping of output key to array of secret IDs exported via this output.
|
|
1355
|
-
*/
|
|
1356
|
-
private processExportedSecrets(
|
|
1357
|
-
instanceId: string,
|
|
1358
|
-
exportedSecrets: Record<string, Omit<UnitSecretModel, "value">[]>,
|
|
1359
|
-
): Record<string, string[]> {
|
|
1360
|
-
const instance = this.workset.getInstance(instanceId)
|
|
1361
|
-
const allowedSecretIds = this.collectSecretIdsForInstance(instance)
|
|
1362
|
-
|
|
1363
|
-
return mapValues(exportedSecrets, secrets => {
|
|
1364
|
-
const exportedSecretIds = secrets.map(secret => secret.id)
|
|
1365
|
-
for (const exportedSecretId of exportedSecretIds) {
|
|
1366
|
-
if (!allowedSecretIds.has(exportedSecretId)) {
|
|
1367
|
-
throw new Error(
|
|
1368
|
-
`The secret "${exportedSecretId}" is not allowed to be exported since it is not present in the instance secrets or in the exported secrets of connected dependencies.`,
|
|
1369
|
-
)
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
return exportedSecretIds
|
|
1374
|
-
})
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
/**
|
|
1378
|
-
* Updates the secrets for a specific instance.
|
|
1379
|
-
*/
|
|
1380
|
-
private async updateInstanceSecrets(
|
|
1381
|
-
instance: InstanceModel,
|
|
1382
|
-
secrets: Record<string, unknown>,
|
|
1383
|
-
): Promise<void> {
|
|
1384
|
-
if (this.operation.type === "preview") {
|
|
1385
|
-
// do not update secrets in preview mode
|
|
1386
|
-
return
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
await this.secretService.updateInstanceSecrets(
|
|
1390
|
-
this.project,
|
|
1391
|
-
instance.id,
|
|
1392
|
-
secrets,
|
|
1393
|
-
[], // no secrets to delete in this context
|
|
1394
|
-
false, // do not invalidate state even if the secrets were updated
|
|
1395
|
-
)
|
|
1396
|
-
}
|
|
1397
889
|
}
|