@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,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,80 @@ 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,
|
|
502
365
|
})
|
|
503
366
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
await this.watchStateStream(instance.type, instance.name, logger)
|
|
507
|
-
perfLogger.log("unit update completed")
|
|
367
|
+
await this.watchStateStream(state, instance.type, instance.name, logger)
|
|
508
368
|
logger.info("unit updated")
|
|
509
369
|
})
|
|
510
370
|
}
|
|
511
371
|
|
|
512
|
-
private async updateUnitDependencies(instanceId:
|
|
372
|
+
private async updateUnitDependencies(instanceId: InstanceId, logger: Logger): Promise<void> {
|
|
513
373
|
try {
|
|
514
|
-
const dependencies = this.
|
|
374
|
+
const dependencies = this.context.getDependencies(instanceId)
|
|
515
375
|
const dependencyPromises: Promise<void>[] = []
|
|
516
376
|
|
|
517
|
-
for (const
|
|
518
|
-
if (!this.
|
|
377
|
+
for (const dependency of dependencies) {
|
|
378
|
+
if (!this.workset.phaseAffectedInstanceIds.has(dependency.id)) {
|
|
519
379
|
// skip dependencies that are not affected by the operation
|
|
520
380
|
continue
|
|
521
381
|
}
|
|
522
382
|
|
|
523
|
-
|
|
524
|
-
|
|
383
|
+
const state = this.context.getState(dependency.id)
|
|
384
|
+
|
|
385
|
+
logger.debug(`waiting for dependency "%s"`, dependency.id)
|
|
386
|
+
dependencyPromises.push(this.getInstancePromiseForOperation(dependency, state))
|
|
525
387
|
}
|
|
526
388
|
|
|
527
|
-
await
|
|
389
|
+
await waitAll(dependencyPromises)
|
|
528
390
|
|
|
529
391
|
if (dependencies.length > 0) {
|
|
530
392
|
logger.info("all dependencies completed")
|
|
531
393
|
}
|
|
532
|
-
} catch {
|
|
394
|
+
} catch (error) {
|
|
533
395
|
// abort the instance if any dependency fails
|
|
534
|
-
throw new AbortError()
|
|
396
|
+
throw new AbortError("One of the dependencies failed", { cause: error })
|
|
535
397
|
}
|
|
536
398
|
}
|
|
537
399
|
|
|
538
400
|
private async processBeforeDestroyTriggers(
|
|
401
|
+
instance: InstanceModel,
|
|
539
402
|
state: InstanceState,
|
|
540
403
|
logger: Logger,
|
|
541
404
|
signal: AbortSignal,
|
|
@@ -546,15 +409,7 @@ export class RuntimeOperation {
|
|
|
546
409
|
return
|
|
547
410
|
}
|
|
548
411
|
|
|
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)
|
|
412
|
+
const allTriggers = await this.unitExtraService.getInstanceTriggers(this.project.id, state.id)
|
|
558
413
|
|
|
559
414
|
const beforeDestroyTriggers = allTriggers.filter(
|
|
560
415
|
trigger => trigger.spec.type === "before-destroy",
|
|
@@ -564,95 +419,72 @@ export class RuntimeOperation {
|
|
|
564
419
|
return
|
|
565
420
|
}
|
|
566
421
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
name => state?.extra?.triggerIds?.[name] === trigger.id,
|
|
571
|
-
)
|
|
572
|
-
|
|
573
|
-
return { name: triggerName || "unknown" }
|
|
574
|
-
})
|
|
422
|
+
const invokedTriggers = beforeDestroyTriggers.map(trigger => ({
|
|
423
|
+
name: trigger.name,
|
|
424
|
+
}))
|
|
575
425
|
|
|
576
|
-
await this.workset.
|
|
577
|
-
|
|
578
|
-
operationStatus: {
|
|
579
|
-
status: "processing-triggers",
|
|
580
|
-
},
|
|
426
|
+
await this.workset.updateState(instance.id, {
|
|
427
|
+
operationState: { status: "processing_triggers" },
|
|
581
428
|
})
|
|
582
429
|
|
|
583
430
|
logger.info("updating unit to process before-destroy triggers...")
|
|
584
431
|
|
|
585
|
-
const secrets = await this.secretService.getInstanceSecretValues(this.project,
|
|
432
|
+
const secrets = await this.secretService.getInstanceSecretValues(this.project.id, state.id)
|
|
586
433
|
|
|
587
434
|
await this.runnerBackend.update({
|
|
588
435
|
projectId: this.project.id,
|
|
436
|
+
stateId: state.id,
|
|
589
437
|
libraryId: this.project.libraryId,
|
|
590
438
|
instanceType: instance.type,
|
|
591
439
|
instanceName: instance.name,
|
|
592
|
-
config: this.prepareUnitConfig(instance,
|
|
440
|
+
config: this.prepareUnitConfig(instance, secrets, invokedTriggers),
|
|
593
441
|
refresh: this.operation.options.refresh,
|
|
594
|
-
secrets
|
|
442
|
+
secrets,
|
|
595
443
|
signal,
|
|
596
444
|
forceSignal,
|
|
597
445
|
})
|
|
598
446
|
|
|
599
447
|
logger.debug("unit update requested")
|
|
600
448
|
|
|
601
|
-
await this.watchStateStream(instance.type, instance.name, logger)
|
|
449
|
+
await this.watchStateStream(state, instance.type, instance.name, logger)
|
|
602
450
|
logger.debug("before-destroy triggers processed")
|
|
603
451
|
}
|
|
604
452
|
|
|
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
|
-
|
|
453
|
+
private async destroyUnit(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
454
|
+
return this.getInstancePromise(instance.id, async (logger, signal, forceSignal) => {
|
|
622
455
|
const dependentPromises: Promise<void>[] = []
|
|
623
|
-
const dependents = this.
|
|
456
|
+
const dependents = this.context.getDependentStates(instance.id)
|
|
624
457
|
|
|
625
458
|
for (const dependent of dependents) {
|
|
626
|
-
if (
|
|
627
|
-
!this.operation.options.destroyDependentInstances &&
|
|
628
|
-
!this.operation.instanceIdsToDestroy.includes(dependent.id)
|
|
629
|
-
) {
|
|
459
|
+
if (!this.workset.phaseAffectedInstanceIds.has(dependent.instanceId)) {
|
|
630
460
|
// skip dependents that are not affected by the operation
|
|
631
461
|
continue
|
|
632
462
|
}
|
|
633
463
|
|
|
634
|
-
|
|
464
|
+
const instance = this.context.getInstance(dependent.instanceId)
|
|
465
|
+
dependentPromises.push(this.getInstancePromiseForOperation(instance, dependent))
|
|
635
466
|
}
|
|
636
467
|
|
|
637
|
-
await
|
|
468
|
+
await waitAll(dependentPromises)
|
|
638
469
|
signal.throwIfAborted()
|
|
639
470
|
|
|
640
|
-
await this.processBeforeDestroyTriggers(state, logger, signal, forceSignal)
|
|
471
|
+
await this.processBeforeDestroyTriggers(instance, state, logger, signal, forceSignal)
|
|
641
472
|
signal.throwIfAborted()
|
|
642
473
|
|
|
643
474
|
logger.info("destroying unit...")
|
|
644
475
|
|
|
645
|
-
await this.workset.
|
|
646
|
-
|
|
647
|
-
operationStatus: {
|
|
476
|
+
await this.workset.updateState(instance.id, {
|
|
477
|
+
operationState: {
|
|
648
478
|
status: "destroying",
|
|
479
|
+
startedAt: new Date(),
|
|
649
480
|
},
|
|
650
481
|
})
|
|
651
482
|
|
|
652
|
-
const [type, name] = parseInstanceId(
|
|
483
|
+
const [type, name] = parseInstanceId(instance.id)
|
|
653
484
|
|
|
654
485
|
await this.runnerBackend.destroy({
|
|
655
486
|
projectId: this.project.id,
|
|
487
|
+
stateId: state.id,
|
|
656
488
|
libraryId: this.project.libraryId,
|
|
657
489
|
instanceType: type,
|
|
658
490
|
instanceName: name,
|
|
@@ -665,43 +497,31 @@ export class RuntimeOperation {
|
|
|
665
497
|
|
|
666
498
|
logger.debug("destroy request sent")
|
|
667
499
|
|
|
668
|
-
await this.watchStateStream(type, name, logger)
|
|
500
|
+
await this.watchStateStream(state, type, name, logger)
|
|
669
501
|
|
|
670
502
|
// 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
|
-
}
|
|
503
|
+
await this.artifactService.clearInstanceArtifactReferences(this.project.id, instance.id)
|
|
683
504
|
|
|
684
505
|
logger.info("unit destroyed")
|
|
685
506
|
})
|
|
686
507
|
}
|
|
687
508
|
|
|
688
|
-
private async refreshUnit(
|
|
689
|
-
return this.getInstancePromise(
|
|
690
|
-
await this.workset.
|
|
691
|
-
|
|
692
|
-
operationStatus: {
|
|
509
|
+
private async refreshUnit(instance: InstanceModel, state: InstanceState): Promise<void> {
|
|
510
|
+
return this.getInstancePromise(instance.id, async (logger, signal, forceSignal) => {
|
|
511
|
+
await this.workset.updateState(instance.id, {
|
|
512
|
+
operationState: {
|
|
693
513
|
status: "refreshing",
|
|
694
|
-
|
|
695
|
-
totalResourceCount: 0,
|
|
514
|
+
startedAt: new Date(),
|
|
696
515
|
},
|
|
697
516
|
})
|
|
698
517
|
|
|
699
518
|
logger.info("refreshing unit...")
|
|
700
519
|
|
|
701
|
-
const [type, name] = parseInstanceId(
|
|
520
|
+
const [type, name] = parseInstanceId(instance.id)
|
|
702
521
|
|
|
703
522
|
await this.runnerBackend.refresh({
|
|
704
523
|
projectId: this.project.id,
|
|
524
|
+
stateId: state.id,
|
|
705
525
|
libraryId: this.project.libraryId,
|
|
706
526
|
instanceType: type,
|
|
707
527
|
instanceName: name,
|
|
@@ -711,18 +531,20 @@ export class RuntimeOperation {
|
|
|
711
531
|
|
|
712
532
|
logger.debug("unit refresh requested")
|
|
713
533
|
|
|
714
|
-
await this.watchStateStream(type, name, logger)
|
|
534
|
+
await this.watchStateStream(state, type, name, logger)
|
|
715
535
|
logger.info("unit refreshed")
|
|
716
536
|
})
|
|
717
537
|
}
|
|
718
538
|
|
|
719
539
|
private async watchStateStream(
|
|
720
|
-
|
|
540
|
+
state: InstanceState,
|
|
541
|
+
instanceType: VersionedName,
|
|
721
542
|
instanceName: string,
|
|
722
543
|
logger: Logger,
|
|
723
544
|
): Promise<void> {
|
|
724
545
|
const stream = this.runnerBackend.watch({
|
|
725
546
|
projectId: this.project.id,
|
|
547
|
+
stateId: state.id,
|
|
726
548
|
libraryId: this.project.libraryId,
|
|
727
549
|
instanceType,
|
|
728
550
|
instanceName,
|
|
@@ -732,18 +554,12 @@ export class RuntimeOperation {
|
|
|
732
554
|
|
|
733
555
|
for await (update of stream) {
|
|
734
556
|
try {
|
|
735
|
-
await this.handleUnitStateUpdate(update)
|
|
557
|
+
await this.handleUnitStateUpdate(update, state)
|
|
736
558
|
} catch (error) {
|
|
737
559
|
logger.error({ error }, "failed to handle unit state update")
|
|
738
560
|
}
|
|
739
561
|
|
|
740
562
|
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
563
|
// rethrow the error to stop the execution of dependent units
|
|
748
564
|
throw new Error(`An error occurred while processing the unit "${update.unitId}"`, {
|
|
749
565
|
cause: update.message,
|
|
@@ -762,204 +578,155 @@ export class RuntimeOperation {
|
|
|
762
578
|
|
|
763
579
|
private prepareUnitConfig(
|
|
764
580
|
instance: InstanceModel,
|
|
765
|
-
|
|
581
|
+
secrets: Record<string, unknown>,
|
|
766
582
|
invokedTriggers: TriggerInvocation[] = [],
|
|
767
|
-
):
|
|
768
|
-
const
|
|
583
|
+
): UnitConfig {
|
|
584
|
+
const resolvedInputs = this.context.getResolvedInputs(instance.id)
|
|
769
585
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
586
|
+
return {
|
|
587
|
+
instanceId: instance.id,
|
|
588
|
+
args: instance.args ?? {},
|
|
589
|
+
inputs: mapValues(resolvedInputs ?? {}, input => input.map(value => value.input)),
|
|
590
|
+
invokedTriggers,
|
|
591
|
+
secretNames: Object.keys(secrets),
|
|
592
|
+
stateIdMap: this.context.getInstanceIdToStateIdMap(),
|
|
774
593
|
}
|
|
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
594
|
}
|
|
788
595
|
|
|
789
|
-
private async handleUnitStateUpdate(
|
|
596
|
+
private async handleUnitStateUpdate(
|
|
597
|
+
update: UnitStateUpdate,
|
|
598
|
+
state: InstanceState,
|
|
599
|
+
): Promise<void> {
|
|
790
600
|
switch (update.type) {
|
|
791
601
|
case "message":
|
|
792
|
-
this.handleUnitMessage(update)
|
|
602
|
+
this.handleUnitMessage(update, state)
|
|
793
603
|
return
|
|
794
604
|
case "progress":
|
|
795
605
|
await this.handleUnitProgress(update)
|
|
796
606
|
return
|
|
797
607
|
case "error":
|
|
798
|
-
await this.handleUnitError(update)
|
|
608
|
+
await this.handleUnitError(update, state)
|
|
799
609
|
return
|
|
800
610
|
case "completion":
|
|
801
|
-
await this.handleUnitCompletion(update)
|
|
611
|
+
await this.handleUnitCompletion(update, state)
|
|
802
612
|
return
|
|
803
613
|
}
|
|
804
614
|
}
|
|
805
615
|
|
|
806
|
-
private handleUnitMessage(update: TypedUnitStateUpdate<"message"
|
|
807
|
-
//
|
|
808
|
-
const instanceIdsInHierarchy: string[] = []
|
|
809
|
-
let instance: InstanceModel | null = this.workset.getInstance(update.unitId)
|
|
810
|
-
|
|
616
|
+
private handleUnitMessage(update: TypedUnitStateUpdate<"message">, state: InstanceState): void {
|
|
617
|
+
// append logs to the instance and all its parents
|
|
811
618
|
for (;;) {
|
|
812
|
-
|
|
619
|
+
this.promiseTracker.track(
|
|
620
|
+
this.operationService.appendLog(
|
|
621
|
+
this.project.id,
|
|
622
|
+
this.operation.id,
|
|
623
|
+
state.id,
|
|
624
|
+
update.message,
|
|
625
|
+
),
|
|
626
|
+
)
|
|
813
627
|
|
|
814
|
-
if (!
|
|
628
|
+
if (!state.parentInstanceId) {
|
|
815
629
|
break
|
|
816
630
|
}
|
|
817
631
|
|
|
818
|
-
|
|
632
|
+
state = this.context.getState(state.parentInstanceId)
|
|
819
633
|
}
|
|
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
634
|
}
|
|
830
635
|
|
|
831
636
|
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)
|
|
637
|
+
await this.workset.updateState(update.unitId, {
|
|
638
|
+
operationState: {
|
|
639
|
+
currentResourceCount: update.currentResourceCount,
|
|
640
|
+
totalResourceCount: update.totalResourceCount,
|
|
641
|
+
},
|
|
642
|
+
})
|
|
844
643
|
}
|
|
845
644
|
|
|
846
|
-
private async handleUnitError(
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
status: "error",
|
|
645
|
+
private async handleUnitError(
|
|
646
|
+
update: TypedUnitStateUpdate<"error">,
|
|
647
|
+
state: InstanceState,
|
|
648
|
+
): Promise<void> {
|
|
649
|
+
await this.workset.updateState(update.unitId, {
|
|
650
|
+
instanceState: {
|
|
651
|
+
// keep "deployed" status for initially deployed instances even if the operation was failed or cancelled
|
|
652
|
+
status: state.status === "deployed" ? "deployed" : "failed",
|
|
855
653
|
message: update.message,
|
|
856
654
|
},
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
655
|
+
operationState: {
|
|
656
|
+
status: isAbortErrorLike(update.message) ? "cancelled" : "failed",
|
|
657
|
+
finishedAt: new Date(),
|
|
658
|
+
},
|
|
659
|
+
})
|
|
860
660
|
}
|
|
861
661
|
|
|
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)
|
|
662
|
+
private async handleUnitCompletion(
|
|
663
|
+
update: TypedUnitStateUpdate<"completion">,
|
|
664
|
+
state: InstanceState,
|
|
665
|
+
): Promise<void> {
|
|
666
|
+
const instance = this.context.getInstance(update.unitId)
|
|
877
667
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
operationStatus: { outputHash: update.outputHash },
|
|
883
|
-
})
|
|
668
|
+
const data: InstanceStatePatch = {
|
|
669
|
+
status: this.workset.getNextStableInstanceStatus(instance.id),
|
|
670
|
+
message: update.message,
|
|
671
|
+
statusFields: update.statusFields ?? null,
|
|
884
672
|
}
|
|
885
673
|
|
|
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,
|
|
674
|
+
if (update.operationType !== "destroy") {
|
|
675
|
+
// давайте еще больше усложним и без того сложную штуку
|
|
676
|
+
// set output hash before calculating input hash to capture up-to-date output hash for dependencies
|
|
677
|
+
state.outputHash = update.outputHash ?? null
|
|
905
678
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
679
|
+
// recalculate the input and output hashes for the instance
|
|
680
|
+
const { inputHash, dependencyOutputHash } =
|
|
681
|
+
await this.context.getUpToDateInputHashOutput(instance)
|
|
909
682
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
683
|
+
data.inputHash = inputHash
|
|
684
|
+
data.dependencyOutputHash = dependencyOutputHash
|
|
685
|
+
data.outputHash = update.outputHash
|
|
913
686
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
687
|
+
// also update the parent ID
|
|
688
|
+
if (instance.parentId) {
|
|
689
|
+
const parentState = this.context.getState(instance.parentId)
|
|
690
|
+
data.parentId = parentState.id
|
|
691
|
+
} else {
|
|
692
|
+
data.parentId = null
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
data.message = null
|
|
696
|
+
data.inputHash = null
|
|
697
|
+
data.dependencyOutputHash = null
|
|
698
|
+
data.outputHash = null
|
|
699
|
+
data.parentId = null
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// update the operation state
|
|
703
|
+
await this.workset.updateState(instance.id, {
|
|
704
|
+
// TODO: honestly, it is not correct
|
|
705
|
+
// may be we should track operation phases separately
|
|
706
|
+
// or introduce status like "destroyed-before-recreation" (quite ugly though)
|
|
707
|
+
operationState: {
|
|
708
|
+
status: this.workset.getStableStatusByOperationPhase(),
|
|
709
|
+
finishedAt: new Date(),
|
|
934
710
|
},
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
if (update.secrets) {
|
|
938
|
-
const instance = this.workset.getInstance(update.unitId)
|
|
939
711
|
|
|
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)
|
|
712
|
+
// do not write instance state for non-last phases of the instance
|
|
713
|
+
instanceState: this.workset.isLastPhaseForInstance(instance.id) ? data : undefined,
|
|
714
|
+
|
|
715
|
+
// also do not write unit extra data for non-last phases of the instance
|
|
716
|
+
unitExtra: this.workset.isLastPhaseForInstance(instance.id)
|
|
717
|
+
? {
|
|
718
|
+
pages: update.pages ?? [],
|
|
719
|
+
terminals: update.terminals ?? [],
|
|
720
|
+
triggers: update.triggers ?? [],
|
|
721
|
+
workers: update.workers ?? [],
|
|
722
|
+
secrets: update.secrets ?? {},
|
|
723
|
+
}
|
|
724
|
+
: undefined,
|
|
725
|
+
})
|
|
959
726
|
}
|
|
960
727
|
|
|
961
728
|
private getInstancePromise(
|
|
962
|
-
instanceId:
|
|
729
|
+
instanceId: InstanceId,
|
|
963
730
|
fn: (logger: Logger, signal: AbortSignal, forceSignal: AbortSignal) => Promise<void>,
|
|
964
731
|
): Promise<void> {
|
|
965
732
|
let instancePromise = this.instancePromiseMap.get(instanceId)
|
|
@@ -967,30 +734,54 @@ export class RuntimeOperation {
|
|
|
967
734
|
return instancePromise
|
|
968
735
|
}
|
|
969
736
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
737
|
+
// "pending" -> "running" if at least one instance is running
|
|
738
|
+
if (this.operation.status === "pending") {
|
|
739
|
+
this.operation.status = "running"
|
|
740
|
+
this.promiseTracker.track(this.updateOperation({ status: this.operation.status }))
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const state = this.context.getState(instanceId)
|
|
973
744
|
|
|
974
|
-
|
|
745
|
+
const logger = this.logger.child({ instanceId }, { msgPrefix: `[${instanceId}] ` })
|
|
746
|
+
const abortControllerPair = this.workset.instanceAbortControllers.get(instanceId)
|
|
747
|
+
if (!abortControllerPair) {
|
|
975
748
|
throw new Error(`Abort controllers for instance "${instanceId}" are not initialized`)
|
|
976
749
|
}
|
|
977
750
|
|
|
751
|
+
const { abortController, forceAbortController } = abortControllerPair
|
|
752
|
+
|
|
978
753
|
instancePromise = this.workset
|
|
979
|
-
.
|
|
754
|
+
.waitForInstanceLock(state.id, abortController.signal)
|
|
980
755
|
.then(() => fn(logger, abortController.signal, forceAbortController.signal))
|
|
981
756
|
.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
757
|
if (this.operation.status !== "failing") {
|
|
990
758
|
// report the failing status of the operation
|
|
991
759
|
this.operation.status = "failing"
|
|
760
|
+
this.promiseTracker.track(this.updateOperation({ status: this.operation.status }))
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (isTransientInstanceOperationStatus(state.lastOperationState?.status)) {
|
|
764
|
+
// if the underlying method did not correctly update the instance status, do it here
|
|
992
765
|
this.promiseTracker.track(
|
|
993
|
-
this.
|
|
766
|
+
this.workset.updateState(instanceId, {
|
|
767
|
+
operationState: {
|
|
768
|
+
status: isAbortErrorLike(error) ? "cancelled" : "failed",
|
|
769
|
+
finishedAt: new Date(),
|
|
770
|
+
},
|
|
771
|
+
instanceState: {
|
|
772
|
+
// keep "deployed" status for initially deployed instances even if the operation was failed or cancelled
|
|
773
|
+
status: state.status === "deployed" ? "deployed" : "failed",
|
|
774
|
+
},
|
|
775
|
+
}),
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
this.promiseTracker.track(
|
|
779
|
+
this.operationService.appendLog(
|
|
780
|
+
this.project.id,
|
|
781
|
+
this.operation.id,
|
|
782
|
+
state.id,
|
|
783
|
+
errorToString(error),
|
|
784
|
+
),
|
|
994
785
|
)
|
|
995
786
|
}
|
|
996
787
|
|
|
@@ -998,48 +789,67 @@ export class RuntimeOperation {
|
|
|
998
789
|
throw error
|
|
999
790
|
})
|
|
1000
791
|
.finally(() => {
|
|
792
|
+
if (!this.workset.isLastPhaseForInstance(instanceId)) {
|
|
793
|
+
// do not finalize the instance if it has more phases to run
|
|
794
|
+
return
|
|
795
|
+
}
|
|
796
|
+
|
|
1001
797
|
this.instancePromiseMap.delete(instanceId)
|
|
1002
798
|
|
|
1003
799
|
// TODO: ideally we should defer unlocking until all direct dependents are completed,
|
|
1004
800
|
// to ensure that they are received expected inputs from this instance
|
|
1005
801
|
this.promiseTracker.track(
|
|
1006
|
-
this.instanceLockService
|
|
802
|
+
this.instanceLockService
|
|
803
|
+
.unlockInstances(this.project.id, [state.id], this.unlockToken)
|
|
804
|
+
.then(() => this.workset.markInstanceUnlocked(state.id)),
|
|
1007
805
|
)
|
|
806
|
+
|
|
807
|
+
this.logger.debug(`promise for instance "%s" completed`, instanceId)
|
|
1008
808
|
})
|
|
1009
809
|
|
|
1010
810
|
this.instancePromiseMap.set(instanceId, instancePromise)
|
|
811
|
+
this.logger.trace(`created new promise for instance "%s"`, instanceId)
|
|
1011
812
|
|
|
1012
813
|
return instancePromise
|
|
1013
814
|
}
|
|
1014
815
|
|
|
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
|
-
}
|
|
816
|
+
private async ensureInstancesUnlocked(): Promise<void> {
|
|
817
|
+
const lockedStateIds = Array.from(this.workset.getLockedStateIds())
|
|
818
|
+
if (lockedStateIds.length === 0) {
|
|
819
|
+
return
|
|
820
|
+
}
|
|
1031
821
|
|
|
1032
|
-
|
|
1033
|
-
const dependencies = new Set<string>()
|
|
1034
|
-
const instanceInputs = this.workset.resolvedInstanceInputs.get(instanceId) ?? {}
|
|
822
|
+
this.logger.warn("unlocking %d locked instances before shutting down", lockedStateIds.length)
|
|
1035
823
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
824
|
+
await this.instanceLockService.unlockInstances(
|
|
825
|
+
this.project.id,
|
|
826
|
+
lockedStateIds,
|
|
827
|
+
this.unlockToken,
|
|
828
|
+
)
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private async ensureOperationStatesFinalized(): Promise<void> {
|
|
832
|
+
const unfinishedStates = this.context.getUnfinishedOperationStates()
|
|
833
|
+
if (unfinishedStates.length === 0) {
|
|
834
|
+
return
|
|
1040
835
|
}
|
|
1041
836
|
|
|
1042
|
-
|
|
837
|
+
this.logger.warn(
|
|
838
|
+
"finalizing %d unfinished operation states before shutting down",
|
|
839
|
+
unfinishedStates.length,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
for (const state of unfinishedStates) {
|
|
843
|
+
await this.workset.updateState(state.instanceId, {
|
|
844
|
+
operationState: {
|
|
845
|
+
status: "failed",
|
|
846
|
+
finishedAt: new Date(),
|
|
847
|
+
},
|
|
848
|
+
instanceState: {
|
|
849
|
+
status: state.status === "deployed" ? "deployed" : "failed",
|
|
850
|
+
},
|
|
851
|
+
})
|
|
852
|
+
}
|
|
1043
853
|
}
|
|
1044
854
|
|
|
1045
855
|
/**
|
|
@@ -1048,22 +858,22 @@ export class RuntimeOperation {
|
|
|
1048
858
|
*/
|
|
1049
859
|
private collectArtifactIdsForInstance(instance: InstanceModel): string[] {
|
|
1050
860
|
const artifactIds = new Set<string>()
|
|
1051
|
-
const instanceInputs = this.
|
|
861
|
+
const instanceInputs = this.context.getResolvedInputs(instance.id) ?? {}
|
|
1052
862
|
|
|
1053
863
|
for (const inputs of Object.values(instanceInputs)) {
|
|
1054
864
|
for (const input of inputs) {
|
|
1055
|
-
const dependencyState = this.
|
|
1056
|
-
if (!dependencyState
|
|
865
|
+
const dependencyState = this.context.getState(input.input.instanceId)
|
|
866
|
+
if (!dependencyState.exportedArtifactIds) {
|
|
1057
867
|
continue
|
|
1058
868
|
}
|
|
1059
869
|
|
|
1060
870
|
const outputKey = input.input.output
|
|
1061
|
-
const
|
|
1062
|
-
if (!
|
|
871
|
+
const outputArtifactIds = dependencyState.exportedArtifactIds[outputKey]
|
|
872
|
+
if (!outputArtifactIds) {
|
|
1063
873
|
continue
|
|
1064
874
|
}
|
|
1065
875
|
|
|
1066
|
-
for (const hash of
|
|
876
|
+
for (const hash of outputArtifactIds) {
|
|
1067
877
|
artifactIds.add(hash)
|
|
1068
878
|
}
|
|
1069
879
|
}
|
|
@@ -1071,327 +881,4 @@ export class RuntimeOperation {
|
|
|
1071
881
|
|
|
1072
882
|
return Array.from(artifactIds)
|
|
1073
883
|
}
|
|
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
884
|
}
|