@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
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import type { InstanceId, InstanceModel } from "@highstate/contract"
|
|
2
|
+
import type {
|
|
3
|
+
InstanceState,
|
|
4
|
+
OperationOptions,
|
|
5
|
+
OperationPhase,
|
|
6
|
+
OperationPhaseInstance,
|
|
7
|
+
OperationType,
|
|
8
|
+
} from "../shared"
|
|
9
|
+
import type { OperationContext } from "./operation-context"
|
|
10
|
+
import { isVirtualGhostInstance } from "../shared"
|
|
11
|
+
|
|
12
|
+
type CompositeType = "unknown" | "compositional" | "substantive"
|
|
13
|
+
type InclusionReason =
|
|
14
|
+
| "explicit"
|
|
15
|
+
| "dependency"
|
|
16
|
+
| "dependent_cascade"
|
|
17
|
+
| "composite_child"
|
|
18
|
+
| "parent_composite"
|
|
19
|
+
| "ghost_cleanup"
|
|
20
|
+
|
|
21
|
+
interface WorkState {
|
|
22
|
+
included: Map<InstanceId, InclusionReason>
|
|
23
|
+
compositeTypes: Map<InstanceId, CompositeType>
|
|
24
|
+
pendingWork: Set<InstanceId>
|
|
25
|
+
changed: boolean
|
|
26
|
+
// track relationships for message generation
|
|
27
|
+
dependencyRequiredBy: Map<InstanceId, InstanceId> // dependency -> dependent
|
|
28
|
+
childTriggeringParent: Map<InstanceId, InstanceId> // parent -> child that caused inclusion
|
|
29
|
+
forceFlags: Map<InstanceId, "dependencies" | "children"> // track force reasons
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createOperationPlan(
|
|
33
|
+
context: OperationContext,
|
|
34
|
+
type: OperationType,
|
|
35
|
+
requestedInstanceIds: string[],
|
|
36
|
+
options: OperationOptions,
|
|
37
|
+
): OperationPhase[] {
|
|
38
|
+
// handle preview restrictions
|
|
39
|
+
if (type === "preview") {
|
|
40
|
+
validatePreviewRestrictions(context, requestedInstanceIds)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// initialize work state
|
|
44
|
+
const workState: WorkState = {
|
|
45
|
+
included: new Map(),
|
|
46
|
+
compositeTypes: new Map(),
|
|
47
|
+
pendingWork: new Set(),
|
|
48
|
+
changed: true,
|
|
49
|
+
dependencyRequiredBy: new Map(),
|
|
50
|
+
childTriggeringParent: new Map(),
|
|
51
|
+
forceFlags: new Map(),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// seed with explicit requests
|
|
55
|
+
for (const instanceId of requestedInstanceIds) {
|
|
56
|
+
workState.included.set(instanceId as InstanceId, "explicit")
|
|
57
|
+
workState.pendingWork.add(instanceId as InstanceId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// work loop - iterate until stabilized
|
|
61
|
+
let iteration = 0
|
|
62
|
+
while (workState.changed && iteration < 100) {
|
|
63
|
+
iteration++
|
|
64
|
+
workState.changed = false
|
|
65
|
+
|
|
66
|
+
const workItems = Array.from(workState.pendingWork)
|
|
67
|
+
workState.pendingWork.clear()
|
|
68
|
+
|
|
69
|
+
for (const instanceId of workItems) {
|
|
70
|
+
processInstance(instanceId, workState, context, options, type)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ensure all instances get at least one chance to be processed
|
|
74
|
+
if (iteration === 1) {
|
|
75
|
+
for (const instanceId of context.getInstanceIds()) {
|
|
76
|
+
if (!workState.pendingWork.has(instanceId) && !workItems.includes(instanceId)) {
|
|
77
|
+
workState.pendingWork.add(instanceId)
|
|
78
|
+
workState.changed = true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (iteration >= 100) {
|
|
85
|
+
throw new Error(`Operation plan creation did not converge after 100 iterations`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// create ordered phases
|
|
89
|
+
return createOrderedPhases(workState, context, type, options)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function validatePreviewRestrictions(
|
|
93
|
+
context: OperationContext,
|
|
94
|
+
requestedInstanceIds: string[],
|
|
95
|
+
): void {
|
|
96
|
+
for (const instanceId of requestedInstanceIds) {
|
|
97
|
+
const dependents = context.getDependentStates(instanceId as InstanceId)
|
|
98
|
+
if (dependents.length > 0) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Preview operation not allowed for instance ${instanceId} - has dependent instances`,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function processInstance(
|
|
107
|
+
instanceId: InstanceId,
|
|
108
|
+
workState: WorkState,
|
|
109
|
+
context: OperationContext,
|
|
110
|
+
options: OperationOptions,
|
|
111
|
+
operationType: OperationType,
|
|
112
|
+
): void {
|
|
113
|
+
const instance = context.getInstance(instanceId)
|
|
114
|
+
|
|
115
|
+
// update composite classification
|
|
116
|
+
updateCompositeClassification(instance, workState, context)
|
|
117
|
+
|
|
118
|
+
// apply operation-specific inclusion rules
|
|
119
|
+
if (operationType === "update" || operationType === "preview") {
|
|
120
|
+
processUpdateInclusions(instance, workState, context, options)
|
|
121
|
+
}
|
|
122
|
+
if (operationType === "refresh") {
|
|
123
|
+
processRefreshInclusions(instance, workState, context, options)
|
|
124
|
+
}
|
|
125
|
+
if (operationType === "destroy" || operationType === "recreate") {
|
|
126
|
+
processDestroyInclusions(instance, workState, context, options)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// propagate to related instances
|
|
130
|
+
propagateToRelated(instance, workState)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function updateCompositeClassification(
|
|
134
|
+
instance: ReturnType<OperationContext["getInstance"]>,
|
|
135
|
+
workState: WorkState,
|
|
136
|
+
context: OperationContext,
|
|
137
|
+
): void {
|
|
138
|
+
if (instance.kind !== "composite") {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const currentType = workState.compositeTypes.get(instance.id) ?? "unknown"
|
|
143
|
+
let newType: CompositeType = "unknown"
|
|
144
|
+
const inclusionReason = workState.included.get(instance.id)
|
|
145
|
+
|
|
146
|
+
// check if explicitly requested
|
|
147
|
+
if (inclusionReason === "explicit") {
|
|
148
|
+
newType = "substantive"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (workState.included.has(instance.id) && inclusionReason !== "explicit") {
|
|
152
|
+
// check if any children are included due to external dependencies
|
|
153
|
+
const children = context.getInstanceChildren(instance.id)
|
|
154
|
+
let hasExternalDependencyChildren = false
|
|
155
|
+
for (const child of children) {
|
|
156
|
+
const reason = workState.included.get(child.id)
|
|
157
|
+
if (reason === "dependency" || reason === "dependent_cascade") {
|
|
158
|
+
// check if this dependency is external to this composite
|
|
159
|
+
const requiredBy = workState.dependencyRequiredBy.get(child.id)
|
|
160
|
+
if (requiredBy) {
|
|
161
|
+
const requiredByInstance = context.getInstance(requiredBy)
|
|
162
|
+
// if the requiring instance is not a child of this composite, it's external
|
|
163
|
+
if (requiredByInstance.parentId !== instance.id) {
|
|
164
|
+
hasExternalDependencyChildren = true
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
newType = hasExternalDependencyChildren ? "substantive" : "compositional"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// if classification changed, mark for re-processing
|
|
175
|
+
if (newType !== currentType) {
|
|
176
|
+
workState.compositeTypes.set(instance.id, newType)
|
|
177
|
+
workState.pendingWork.add(instance.id)
|
|
178
|
+
workState.changed = true
|
|
179
|
+
|
|
180
|
+
// re-process children when parent classification changes
|
|
181
|
+
const children = context.getInstanceChildren(instance.id)
|
|
182
|
+
for (const child of children) {
|
|
183
|
+
workState.pendingWork.add(child.id)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function processUpdateInclusions(
|
|
189
|
+
instance: InstanceModel,
|
|
190
|
+
workState: WorkState,
|
|
191
|
+
context: OperationContext,
|
|
192
|
+
options: OperationOptions,
|
|
193
|
+
): void {
|
|
194
|
+
// check if should be included as composite child
|
|
195
|
+
if (instance.parentId) {
|
|
196
|
+
// check if this instance is a descendant of any substantive composite
|
|
197
|
+
const substantiveAncestor = findSubstantiveAncestor(instance.parentId, workState, context)
|
|
198
|
+
|
|
199
|
+
if (substantiveAncestor) {
|
|
200
|
+
const isInstanceOutdated = isOutdated(instance, context)
|
|
201
|
+
const shouldInclude =
|
|
202
|
+
options.forceUpdateChildren ||
|
|
203
|
+
(!options.allowPartialCompositeInstanceUpdate && isInstanceOutdated)
|
|
204
|
+
|
|
205
|
+
if (shouldInclude && !workState.included.has(instance.id)) {
|
|
206
|
+
include(instance.id, "composite_child", workState, {
|
|
207
|
+
forceFlag: options.forceUpdateChildren ? "children" : undefined,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// process dependencies if this instance is included
|
|
214
|
+
if (workState.included.has(instance.id)) {
|
|
215
|
+
const dependencies = context.getDependencies(instance.id)
|
|
216
|
+
for (const depInstance of dependencies) {
|
|
217
|
+
const shouldInclude = options.forceUpdateDependencies || isOutdated(depInstance, context)
|
|
218
|
+
|
|
219
|
+
if (shouldInclude && !workState.included.has(depInstance.id)) {
|
|
220
|
+
include(depInstance.id, "dependency", workState, {
|
|
221
|
+
requiredBy: instance.id,
|
|
222
|
+
forceFlag: options.forceUpdateDependencies ? "dependencies" : undefined,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function processRefreshInclusions(
|
|
230
|
+
instance: InstanceModel,
|
|
231
|
+
workState: WorkState,
|
|
232
|
+
context: OperationContext,
|
|
233
|
+
options: OperationOptions,
|
|
234
|
+
): void {
|
|
235
|
+
// check if should be included as composite child
|
|
236
|
+
if (instance.parentId) {
|
|
237
|
+
// check if this instance is a descendant of any substantive composite
|
|
238
|
+
const substantiveAncestor = findSubstantiveAncestor(instance.parentId, workState, context)
|
|
239
|
+
|
|
240
|
+
if (substantiveAncestor) {
|
|
241
|
+
const isInstanceOutdated = isOutdated(instance, context)
|
|
242
|
+
const shouldInclude =
|
|
243
|
+
options.forceUpdateChildren ||
|
|
244
|
+
(!options.allowPartialCompositeInstanceUpdate && isInstanceOutdated)
|
|
245
|
+
|
|
246
|
+
if (shouldInclude && !workState.included.has(instance.id)) {
|
|
247
|
+
include(instance.id, "composite_child", workState, {
|
|
248
|
+
forceFlag: options.forceUpdateChildren ? "children" : undefined,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// process dependencies if this instance is included
|
|
255
|
+
// key difference: only include dependencies if forced, not if outdated
|
|
256
|
+
if (workState.included.has(instance.id)) {
|
|
257
|
+
const dependencies = context.getDependencies(instance.id)
|
|
258
|
+
for (const depInstance of dependencies) {
|
|
259
|
+
const shouldInclude = options.forceUpdateDependencies
|
|
260
|
+
|
|
261
|
+
if (shouldInclude && !workState.included.has(depInstance.id)) {
|
|
262
|
+
include(depInstance.id, "dependency", workState, {
|
|
263
|
+
requiredBy: instance.id,
|
|
264
|
+
forceFlag: options.forceUpdateDependencies ? "dependencies" : undefined,
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function processDestroyInclusions(
|
|
272
|
+
instance: InstanceModel,
|
|
273
|
+
workState: WorkState,
|
|
274
|
+
context: OperationContext,
|
|
275
|
+
options: OperationOptions,
|
|
276
|
+
): void {
|
|
277
|
+
// check if should be included as composite child
|
|
278
|
+
if (instance.parentId) {
|
|
279
|
+
const parentType = workState.compositeTypes.get(instance.parentId)
|
|
280
|
+
if (parentType === "substantive" && !workState.included.has(instance.id)) {
|
|
281
|
+
// all children of substantive composites being destroyed must be included
|
|
282
|
+
// when partial destruction is disabled
|
|
283
|
+
if (!options.allowPartialCompositeInstanceDestruction) {
|
|
284
|
+
include(instance.id, "composite_child", workState)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// process dependents if this instance is included and cascade enabled
|
|
290
|
+
if (workState.included.has(instance.id) && options.destroyDependentInstances) {
|
|
291
|
+
const dependentStates = context.getDependentStates(instance.id)
|
|
292
|
+
|
|
293
|
+
for (const dependentState of dependentStates) {
|
|
294
|
+
if (!workState.included.has(dependentState.instanceId)) {
|
|
295
|
+
include(dependentState.instanceId, "dependent_cascade", workState, {
|
|
296
|
+
requiredBy: instance.id,
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function propagateToRelated(instance: InstanceModel, workState: WorkState): void {
|
|
304
|
+
// check if this instance should propagate upward
|
|
305
|
+
// composites included as "parent_composite" should not propagate upward (compositional boundary)
|
|
306
|
+
if (instance.kind === "composite") {
|
|
307
|
+
const inclusionReason = workState.included.get(instance.id)
|
|
308
|
+
if (inclusionReason === "parent_composite") {
|
|
309
|
+
// compositional boundary - don't propagate upward
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// propagate upward to parent if instance is included
|
|
315
|
+
if (
|
|
316
|
+
workState.included.has(instance.id) &&
|
|
317
|
+
instance.parentId &&
|
|
318
|
+
!workState.included.has(instance.parentId)
|
|
319
|
+
) {
|
|
320
|
+
include(instance.parentId, "parent_composite", workState, {
|
|
321
|
+
triggeringChild: instance.id,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function findSubstantiveAncestor(
|
|
327
|
+
instanceId: InstanceId,
|
|
328
|
+
workState: WorkState,
|
|
329
|
+
context: OperationContext,
|
|
330
|
+
): InstanceId | null {
|
|
331
|
+
let currentId: InstanceId | undefined = instanceId
|
|
332
|
+
|
|
333
|
+
// walk up the parent chain looking for a substantive composite
|
|
334
|
+
while (currentId) {
|
|
335
|
+
const compositeType = workState.compositeTypes.get(currentId)
|
|
336
|
+
if (compositeType === "substantive") {
|
|
337
|
+
return currentId
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const instance = context.getInstance(currentId)
|
|
341
|
+
currentId = instance.parentId
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return null
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function include(
|
|
348
|
+
instanceId: InstanceId,
|
|
349
|
+
reason: InclusionReason,
|
|
350
|
+
workState: WorkState,
|
|
351
|
+
context?: {
|
|
352
|
+
requiredBy?: InstanceId
|
|
353
|
+
triggeringChild?: InstanceId
|
|
354
|
+
forceFlag?: "dependencies" | "children"
|
|
355
|
+
},
|
|
356
|
+
): void {
|
|
357
|
+
const existing = workState.included.get(instanceId)
|
|
358
|
+
if (existing) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
workState.included.set(instanceId, reason)
|
|
363
|
+
workState.pendingWork.add(instanceId)
|
|
364
|
+
workState.changed = true
|
|
365
|
+
|
|
366
|
+
// track relationships for message generation
|
|
367
|
+
if (context?.requiredBy) {
|
|
368
|
+
workState.dependencyRequiredBy.set(instanceId, context.requiredBy)
|
|
369
|
+
}
|
|
370
|
+
if (context?.triggeringChild) {
|
|
371
|
+
workState.childTriggeringParent.set(instanceId, context.triggeringChild)
|
|
372
|
+
}
|
|
373
|
+
if (context?.forceFlag) {
|
|
374
|
+
workState.forceFlags.set(instanceId, context.forceFlag)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function createOrderedPhases(
|
|
379
|
+
workState: WorkState,
|
|
380
|
+
context: OperationContext,
|
|
381
|
+
type: OperationType,
|
|
382
|
+
options: OperationOptions,
|
|
383
|
+
): OperationPhase[] {
|
|
384
|
+
const phases: OperationPhase[] = []
|
|
385
|
+
const includedIds = Array.from(workState.included.keys())
|
|
386
|
+
|
|
387
|
+
if (type === "update" || type === "preview" || type === "refresh") {
|
|
388
|
+
// filter instances that actually need updating
|
|
389
|
+
const instancesNeedingUpdate = includedIds.filter(id => {
|
|
390
|
+
const instance = context.getInstance(id)
|
|
391
|
+
const inclusionReason = workState.included.get(id)
|
|
392
|
+
|
|
393
|
+
// always include if outdated or forced
|
|
394
|
+
if (isOutdated(instance, context)) return true
|
|
395
|
+
if (inclusionReason === "dependency" && options.forceUpdateDependencies) return true
|
|
396
|
+
if (inclusionReason === "composite_child" && options.forceUpdateChildren) return true
|
|
397
|
+
|
|
398
|
+
// include explicit requests, but composites only if they have non-ghost children
|
|
399
|
+
if (inclusionReason === "explicit") {
|
|
400
|
+
if (instance.kind === "composite") {
|
|
401
|
+
// for composites, only include if they have non-ghost children that need updating
|
|
402
|
+
const children = context.getInstanceChildren(id)
|
|
403
|
+
return children.some(child => {
|
|
404
|
+
if (!workState.included.has(child.id)) return false
|
|
405
|
+
const childState = context.getState(child.id)
|
|
406
|
+
return !isVirtualGhostInstance(childState)
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
return true
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// include parent composites only if they have children needing updates
|
|
413
|
+
if (inclusionReason === "parent_composite") {
|
|
414
|
+
const children = context.getInstanceChildren(id)
|
|
415
|
+
return children.some(
|
|
416
|
+
child =>
|
|
417
|
+
workState.included.has(child.id) &&
|
|
418
|
+
workState.included.get(child.id) !== "parent_composite",
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// include other types (dependency, composite_child, etc.)
|
|
423
|
+
return true
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
const updateInstances = topologicalSort(instancesNeedingUpdate, context, false)
|
|
427
|
+
.map(id => createPhaseInstance(id, context, workState))
|
|
428
|
+
.filter(inst => inst !== null) as OperationPhaseInstance[]
|
|
429
|
+
|
|
430
|
+
if (updateInstances.length > 0) {
|
|
431
|
+
const phaseType = type === "refresh" ? "refresh" : "update"
|
|
432
|
+
phases.push({ type: phaseType, instances: updateInstances })
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// handle ghost cleanup for updates (but not for refresh operations)
|
|
436
|
+
if (type !== "refresh") {
|
|
437
|
+
const compositesNeedingGhostCleanup = new Set<InstanceId>()
|
|
438
|
+
for (const instanceId of includedIds) {
|
|
439
|
+
const instance = context.getInstance(instanceId)
|
|
440
|
+
if (instance.kind !== "composite") continue
|
|
441
|
+
|
|
442
|
+
const compositeType = workState.compositeTypes.get(instanceId)
|
|
443
|
+
if (compositeType !== "substantive") continue
|
|
444
|
+
|
|
445
|
+
// check if this composite has ghost children
|
|
446
|
+
const children = context.getInstanceChildren(instanceId)
|
|
447
|
+
const hasGhostChildren = children.some(child => {
|
|
448
|
+
const state = context.getState(child.id)
|
|
449
|
+
return isVirtualGhostInstance(state)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
if (hasGhostChildren) {
|
|
453
|
+
compositesNeedingGhostCleanup.add(instanceId)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const ghostInstances = findGhostCleanup(context, compositesNeedingGhostCleanup)
|
|
457
|
+
|
|
458
|
+
if (ghostInstances.length > 0) {
|
|
459
|
+
const sortedGhosts = topologicalSort(
|
|
460
|
+
ghostInstances.map(g => g.id),
|
|
461
|
+
context,
|
|
462
|
+
true,
|
|
463
|
+
)
|
|
464
|
+
.map(id => createPhaseInstance(id, context, workState))
|
|
465
|
+
.filter(inst => inst !== null) as OperationPhaseInstance[]
|
|
466
|
+
|
|
467
|
+
if (sortedGhosts.length > 0) {
|
|
468
|
+
phases.push({ type: "destroy", instances: sortedGhosts })
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (type === "destroy") {
|
|
475
|
+
const destroyInstances = topologicalSort(includedIds, context, true)
|
|
476
|
+
.map(id => createPhaseInstance(id, context, workState))
|
|
477
|
+
.filter(inst => inst !== null) as OperationPhaseInstance[]
|
|
478
|
+
|
|
479
|
+
if (destroyInstances.length > 0) {
|
|
480
|
+
phases.push({ type: "destroy", instances: destroyInstances })
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (type === "recreate") {
|
|
485
|
+
const destroyInstances = topologicalSort(includedIds, context, true)
|
|
486
|
+
.map(id => createPhaseInstance(id, context, workState))
|
|
487
|
+
.filter(inst => inst !== null) as OperationPhaseInstance[]
|
|
488
|
+
|
|
489
|
+
const updateInstances = topologicalSort(includedIds, context, false)
|
|
490
|
+
.map(id => createPhaseInstance(id, context, workState))
|
|
491
|
+
.filter(inst => inst !== null) as OperationPhaseInstance[]
|
|
492
|
+
|
|
493
|
+
if (destroyInstances.length > 0) {
|
|
494
|
+
phases.push({ type: "destroy", instances: destroyInstances })
|
|
495
|
+
}
|
|
496
|
+
if (updateInstances.length > 0) {
|
|
497
|
+
phases.push({ type: "update", instances: updateInstances })
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return phases
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function createPhaseInstance(
|
|
505
|
+
instanceId: InstanceId,
|
|
506
|
+
context: OperationContext,
|
|
507
|
+
workState?: WorkState,
|
|
508
|
+
): OperationPhaseInstance | null {
|
|
509
|
+
const instance = context.getInstance(instanceId)
|
|
510
|
+
let message = "included in operation" // fallback
|
|
511
|
+
|
|
512
|
+
if (workState) {
|
|
513
|
+
const inclusionReason = workState.included.get(instanceId)
|
|
514
|
+
const requiredBy = workState.dependencyRequiredBy.get(instanceId)
|
|
515
|
+
const triggeringChild = workState.childTriggeringParent.get(instanceId)
|
|
516
|
+
const forceFlag = workState.forceFlags.get(instanceId)
|
|
517
|
+
const instanceState = context.getState(instanceId)
|
|
518
|
+
|
|
519
|
+
message = generateContextualMessage(
|
|
520
|
+
context,
|
|
521
|
+
instanceId,
|
|
522
|
+
inclusionReason,
|
|
523
|
+
instanceState,
|
|
524
|
+
requiredBy,
|
|
525
|
+
triggeringChild,
|
|
526
|
+
forceFlag,
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
id: instanceId,
|
|
532
|
+
parentId: instance.parentId,
|
|
533
|
+
message,
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function generateContextualMessage(
|
|
538
|
+
context: OperationContext,
|
|
539
|
+
instanceId: InstanceId,
|
|
540
|
+
inclusionReason: InclusionReason | undefined,
|
|
541
|
+
instanceState?: InstanceState,
|
|
542
|
+
requiredBy?: InstanceId,
|
|
543
|
+
triggeringChild?: InstanceId,
|
|
544
|
+
forceFlag?: "dependencies" | "children",
|
|
545
|
+
): string {
|
|
546
|
+
function getInstanceStateType(
|
|
547
|
+
state?: InstanceState,
|
|
548
|
+
): "failed" | "undeployed" | "changed" | "up-to-date" {
|
|
549
|
+
if (!state) return "undeployed"
|
|
550
|
+
if (state.status === "failed") return "failed"
|
|
551
|
+
if (state.status === "undeployed") return "undeployed"
|
|
552
|
+
|
|
553
|
+
const instance = context.getInstance(instanceId)
|
|
554
|
+
|
|
555
|
+
// composites are containers and cannot be changed/outdated
|
|
556
|
+
if (instance.kind === "composite") {
|
|
557
|
+
return "up-to-date"
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// check if changed by using same logic as isOutdated (for units only)
|
|
561
|
+
const { inputHash: expectedHash } = context.inputHashResolver.requireOutput(instanceId)
|
|
562
|
+
if (state.inputHash !== expectedHash) return "changed"
|
|
563
|
+
|
|
564
|
+
return "up-to-date"
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const stateType = getInstanceStateType(instanceState)
|
|
568
|
+
|
|
569
|
+
switch (inclusionReason) {
|
|
570
|
+
case "explicit":
|
|
571
|
+
return "explicitly requested"
|
|
572
|
+
|
|
573
|
+
case "dependency":
|
|
574
|
+
if (forceFlag === "dependencies") {
|
|
575
|
+
return `required by "${requiredBy}" (forced by options)`
|
|
576
|
+
}
|
|
577
|
+
switch (stateType) {
|
|
578
|
+
case "failed":
|
|
579
|
+
return `failed and required by "${requiredBy}"`
|
|
580
|
+
case "undeployed":
|
|
581
|
+
return `undeployed and required by "${requiredBy}"`
|
|
582
|
+
case "changed":
|
|
583
|
+
return `changed and required by "${requiredBy}"`
|
|
584
|
+
default:
|
|
585
|
+
return `required by "${requiredBy}"`
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
case "dependent_cascade":
|
|
589
|
+
return `dependent of destroyed "${requiredBy}"`
|
|
590
|
+
|
|
591
|
+
case "composite_child":
|
|
592
|
+
if (forceFlag === "children") {
|
|
593
|
+
return "child of included parent (forced by options)"
|
|
594
|
+
}
|
|
595
|
+
switch (stateType) {
|
|
596
|
+
case "failed":
|
|
597
|
+
return "failed and child of included parent"
|
|
598
|
+
case "undeployed":
|
|
599
|
+
return "undeployed and child of included parent"
|
|
600
|
+
case "changed":
|
|
601
|
+
return "changed and child of included parent"
|
|
602
|
+
default:
|
|
603
|
+
return "child of included parent"
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
case "parent_composite":
|
|
607
|
+
return `parent of included child "${triggeringChild}"`
|
|
608
|
+
|
|
609
|
+
case "ghost_cleanup":
|
|
610
|
+
return "ghost cleanup"
|
|
611
|
+
|
|
612
|
+
default:
|
|
613
|
+
return "included in operation"
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function findGhostCleanup(
|
|
618
|
+
context: OperationContext,
|
|
619
|
+
compositesNeedingGhostCleanup: Set<InstanceId>,
|
|
620
|
+
): OperationPhaseInstance[] {
|
|
621
|
+
const ghosts: OperationPhaseInstance[] = []
|
|
622
|
+
|
|
623
|
+
// find ghost instances and their parent composites that need cleanup
|
|
624
|
+
for (const instanceId of compositesNeedingGhostCleanup) {
|
|
625
|
+
const instance = context.getInstance(instanceId)
|
|
626
|
+
if (instance.kind !== "composite") continue
|
|
627
|
+
|
|
628
|
+
// add the composite itself for destroy if needed
|
|
629
|
+
ghosts.push({
|
|
630
|
+
id: instanceId,
|
|
631
|
+
parentId: instance.parentId,
|
|
632
|
+
message: "included in operation",
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
// find ghost children
|
|
636
|
+
const children = context.getInstanceChildren(instanceId)
|
|
637
|
+
for (const child of children) {
|
|
638
|
+
const state = context.getState(child.id)
|
|
639
|
+
if (isVirtualGhostInstance(state)) {
|
|
640
|
+
ghosts.push({
|
|
641
|
+
id: child.id,
|
|
642
|
+
parentId: child.parentId,
|
|
643
|
+
message: "ghost cleanup",
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return ghosts
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function isOutdated(instance: InstanceModel, context: OperationContext): boolean {
|
|
653
|
+
// composite instances cannot be outdated - they are containers, not deployable units
|
|
654
|
+
if (instance.kind === "composite") {
|
|
655
|
+
return false
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const state = context.getState(instance.id)
|
|
659
|
+
|
|
660
|
+
if (state.status === "failed" || state.status === "undeployed") {
|
|
661
|
+
return true
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// check if input hash has changed by comparing with expected hash from resolver
|
|
665
|
+
const { inputHash: expectedHash } = context.inputHashResolver.requireOutput(instance.id)
|
|
666
|
+
return state.inputHash !== expectedHash
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function topologicalSort(
|
|
670
|
+
instanceIds: InstanceId[],
|
|
671
|
+
context: OperationContext,
|
|
672
|
+
reverse: boolean,
|
|
673
|
+
): InstanceId[] {
|
|
674
|
+
// simple topological sort implementation
|
|
675
|
+
const visited = new Set<InstanceId>()
|
|
676
|
+
const result: InstanceId[] = []
|
|
677
|
+
const visiting = new Set<InstanceId>()
|
|
678
|
+
|
|
679
|
+
function visit(instanceId: InstanceId): void {
|
|
680
|
+
if (visiting.has(instanceId)) {
|
|
681
|
+
// circular dependency detected - skip for now
|
|
682
|
+
return
|
|
683
|
+
}
|
|
684
|
+
if (visited.has(instanceId)) {
|
|
685
|
+
return
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
visiting.add(instanceId)
|
|
689
|
+
|
|
690
|
+
const related = reverse
|
|
691
|
+
? context.getDependentStates(instanceId).map(state => state.instanceId)
|
|
692
|
+
: context.getDependencies(instanceId).map(dep => dep.id)
|
|
693
|
+
for (const relatedId of related) {
|
|
694
|
+
if (instanceIds.includes(relatedId)) {
|
|
695
|
+
visit(relatedId)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
visiting.delete(instanceId)
|
|
700
|
+
visited.add(instanceId)
|
|
701
|
+
result.push(instanceId)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
for (const instanceId of instanceIds) {
|
|
705
|
+
visit(instanceId)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return result
|
|
709
|
+
}
|