@quadnix/octo 0.1.0 → 0.2.0

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.
Files changed (221) hide show
  1. package/README.md +108 -21
  2. package/dist/app.type.d.ts +141 -22
  3. package/dist/app.type.js.map +1 -1
  4. package/dist/decorators/model.decorator.d.ts +3 -5
  5. package/dist/decorators/model.decorator.js +1 -6
  6. package/dist/decorators/model.decorator.js.map +1 -1
  7. package/dist/decorators/on-event.decorator.d.ts +2 -2
  8. package/dist/decorators/on-event.decorator.js.map +1 -1
  9. package/dist/decorators/resource.decorator.d.ts +50 -1
  10. package/dist/decorators/resource.decorator.js +38 -1
  11. package/dist/decorators/resource.decorator.js.map +1 -1
  12. package/dist/errors/schema.error.d.ts +8 -1
  13. package/dist/errors/schema.error.js +5 -1
  14. package/dist/errors/schema.error.js.map +1 -1
  15. package/dist/errors/terraform.error.d.ts +4 -3
  16. package/dist/errors/terraform.error.js +4 -3
  17. package/dist/errors/terraform.error.js.map +1 -1
  18. package/dist/errors/transaction.error.d.ts +32 -2
  19. package/dist/errors/transaction.error.js +22 -0
  20. package/dist/errors/transaction.error.js.map +1 -1
  21. package/dist/events/action.event.d.ts +8 -2
  22. package/dist/events/action.event.js +2 -2
  23. package/dist/events/action.event.js.map +1 -1
  24. package/dist/events/command.event.d.ts +43 -0
  25. package/dist/events/command.event.js +33 -0
  26. package/dist/events/command.event.js.map +1 -0
  27. package/dist/events/event.model.d.ts +7 -5
  28. package/dist/events/event.model.js +5 -3
  29. package/dist/events/event.model.js.map +1 -1
  30. package/dist/events/hook/commit.hook.event.d.ts +1 -1
  31. package/dist/events/hook/commit.hook.event.js.map +1 -1
  32. package/dist/events/hook/hook.event.d.ts +1 -1
  33. package/dist/events/hook/hook.event.js.map +1 -1
  34. package/dist/events/hook/model.hook.event.d.ts +1 -1
  35. package/dist/events/hook/model.hook.event.js.map +1 -1
  36. package/dist/events/hook/resource.hook.event.d.ts +1 -1
  37. package/dist/events/hook/resource.hook.event.js.map +1 -1
  38. package/dist/events/index.d.ts +2 -0
  39. package/dist/events/index.js +2 -0
  40. package/dist/events/index.js.map +1 -1
  41. package/dist/events/mode-result.event.d.ts +1 -1
  42. package/dist/events/mode-result.event.js.map +1 -1
  43. package/dist/events/mode.event.d.ts +8 -4
  44. package/dist/events/mode.event.js.map +1 -1
  45. package/dist/events/module.event.d.ts +7 -3
  46. package/dist/events/module.event.js +3 -2
  47. package/dist/events/module.event.js.map +1 -1
  48. package/dist/events/registration.event.d.ts +1 -1
  49. package/dist/events/registration.event.js.map +1 -1
  50. package/dist/events/serialization.event.d.ts +1 -1
  51. package/dist/events/serialization.event.js.map +1 -1
  52. package/dist/events/state.event.d.ts +102 -0
  53. package/dist/events/state.event.js +69 -0
  54. package/dist/events/state.event.js.map +1 -0
  55. package/dist/events/terraform.event.d.ts +37 -1
  56. package/dist/events/terraform.event.js +24 -0
  57. package/dist/events/terraform.event.js.map +1 -1
  58. package/dist/events/transaction.event.d.ts +3 -3
  59. package/dist/events/transaction.event.js +1 -1
  60. package/dist/events/transaction.event.js.map +1 -1
  61. package/dist/functions/container/container.js +1 -1
  62. package/dist/functions/container/container.js.map +1 -1
  63. package/dist/functions/container/test-container.js +2 -2
  64. package/dist/functions/container/test-container.js.map +1 -1
  65. package/dist/functions/dependency/dependency.d.ts +9 -22
  66. package/dist/functions/dependency/dependency.js +5 -1
  67. package/dist/functions/dependency/dependency.js.map +1 -1
  68. package/dist/functions/diff/diff.d.ts +14 -7
  69. package/dist/functions/diff/diff.js.map +1 -1
  70. package/dist/functions/node/node-action.abstract.d.ts +8 -10
  71. package/dist/functions/node/node-action.abstract.js +10 -16
  72. package/dist/functions/node/node-action.abstract.js.map +1 -1
  73. package/dist/functions/node/node.abstract.js +21 -10
  74. package/dist/functions/node/node.abstract.js.map +1 -1
  75. package/dist/functions/node/node.interface.d.ts +2 -1
  76. package/dist/functions/schema/schema.js +1 -1
  77. package/dist/functions/schema/schema.js.map +1 -1
  78. package/dist/index.d.ts +8 -6
  79. package/dist/index.js +6 -4
  80. package/dist/index.js.map +1 -1
  81. package/dist/index.type.d.ts +5 -0
  82. package/dist/index.type.js +2 -0
  83. package/dist/index.type.js.map +1 -0
  84. package/dist/main.d.ts +4 -3
  85. package/dist/main.js +27 -11
  86. package/dist/main.js.map +1 -1
  87. package/dist/models/account/account.model.d.ts +6 -0
  88. package/dist/models/account/account.model.js +16 -0
  89. package/dist/models/account/account.model.js.map +1 -1
  90. package/dist/models/app/app.model.d.ts +0 -10
  91. package/dist/models/app/app.model.js +0 -30
  92. package/dist/models/app/app.model.js.map +1 -1
  93. package/dist/models/deployment/deployment.schema.d.ts +4 -0
  94. package/dist/models/deployment/deployment.schema.js +4 -0
  95. package/dist/models/deployment/deployment.schema.js.map +1 -1
  96. package/dist/models/environment/environment.model.d.ts +2 -2
  97. package/dist/models/environment/environment.model.js +2 -2
  98. package/dist/models/execution/execution.model.d.ts +1 -1
  99. package/dist/models/execution/execution.model.js +1 -1
  100. package/dist/models/image/image.model.d.ts +4 -3
  101. package/dist/models/image/image.model.js +7 -6
  102. package/dist/models/image/image.model.js.map +1 -1
  103. package/dist/models/model.abstract.js +1 -1
  104. package/dist/models/model.abstract.js.map +1 -1
  105. package/dist/models/pipeline/pipeline.model.d.ts +1 -1
  106. package/dist/models/pipeline/pipeline.model.js +1 -1
  107. package/dist/models/server/server.model.d.ts +2 -2
  108. package/dist/models/server/server.model.js +2 -2
  109. package/dist/models/service/service.model.d.ts +28 -11
  110. package/dist/models/service/service.model.js +56 -16
  111. package/dist/models/service/service.model.js.map +1 -1
  112. package/dist/models/service/service.schema.d.ts +46 -2
  113. package/dist/models/service/service.schema.js +54 -2
  114. package/dist/models/service/service.schema.js.map +1 -1
  115. package/dist/modes/commit.mode.d.ts +2 -2
  116. package/dist/modes/commit.mode.js +2 -2
  117. package/dist/modes/generate.mode.d.ts +2 -1
  118. package/dist/modes/generate.mode.js +3 -2
  119. package/dist/modes/generate.mode.js.map +1 -1
  120. package/dist/modes/run-action.mode.d.ts +1 -1
  121. package/dist/modes/run-action.mode.js +1 -1
  122. package/dist/modes/validate.mode.d.ts +24 -0
  123. package/dist/modes/validate.mode.js +89 -11
  124. package/dist/modes/validate.mode.js.map +1 -1
  125. package/dist/modules/module.container.d.ts +7 -1
  126. package/dist/modules/module.container.js +41 -5
  127. package/dist/modules/module.container.js.map +1 -1
  128. package/dist/modules/test-module.container.js +35 -83
  129. package/dist/modules/test-module.container.js.map +1 -1
  130. package/dist/overlays/overlay.abstract.js +1 -1
  131. package/dist/overlays/overlay.abstract.js.map +1 -1
  132. package/dist/resources/resource-action.interface.d.ts +4 -2
  133. package/dist/resources/resource.abstract.d.ts +50 -8
  134. package/dist/resources/resource.abstract.js +63 -32
  135. package/dist/resources/resource.abstract.js.map +1 -1
  136. package/dist/resources/resource.interface.d.ts +0 -2
  137. package/dist/resources/resource.schema.d.ts +11 -1
  138. package/dist/resources/resource.schema.js +11 -1
  139. package/dist/resources/resource.schema.js.map +1 -1
  140. package/dist/services/event/event.service.d.ts +2 -2
  141. package/dist/services/event/event.service.js.map +1 -1
  142. package/dist/services/input/input.service.d.ts +30 -2
  143. package/dist/services/input/input.service.js +71 -4
  144. package/dist/services/input/input.service.js.map +1 -1
  145. package/dist/services/serialization/model/model-serialization.service.d.ts +4 -6
  146. package/dist/services/serialization/model/model-serialization.service.js +20 -17
  147. package/dist/services/serialization/model/model-serialization.service.js.map +1 -1
  148. package/dist/services/serialization/resource/resource-serialization.service.d.ts +7 -6
  149. package/dist/services/serialization/resource/resource-serialization.service.js +25 -12
  150. package/dist/services/serialization/resource/resource-serialization.service.js.map +1 -1
  151. package/dist/services/state-management/local.state-provider.d.ts +25 -9
  152. package/dist/services/state-management/local.state-provider.js +127 -21
  153. package/dist/services/state-management/local.state-provider.js.map +1 -1
  154. package/dist/services/state-management/remote.state-provider.d.ts +216 -0
  155. package/dist/services/state-management/remote.state-provider.js +645 -0
  156. package/dist/services/state-management/remote.state-provider.js.map +1 -0
  157. package/dist/services/state-management/state-management.service.d.ts +83 -40
  158. package/dist/services/state-management/state-management.service.js +130 -26
  159. package/dist/services/state-management/state-management.service.js.map +1 -1
  160. package/dist/services/state-management/state-provider.interface.d.ts +50 -9
  161. package/dist/services/state-management/state-provider.interface.js +14 -1
  162. package/dist/services/state-management/state-provider.interface.js.map +1 -1
  163. package/dist/services/state-management/terraform-state.service.d.ts +32 -9
  164. package/dist/services/state-management/terraform-state.service.js +64 -21
  165. package/dist/services/state-management/terraform-state.service.js.map +1 -1
  166. package/dist/services/state-management/test.state-provider.d.ts +11 -10
  167. package/dist/services/state-management/test.state-provider.js +35 -14
  168. package/dist/services/state-management/test.state-provider.js.map +1 -1
  169. package/dist/services/terraform/terraform.service.d.ts +57 -3
  170. package/dist/services/terraform/terraform.service.js +95 -8
  171. package/dist/services/terraform/terraform.service.js.map +1 -1
  172. package/dist/services/terraform/terraform.type.js +1 -1
  173. package/dist/services/terraform/terraform.type.js.map +1 -1
  174. package/dist/services/transaction/transaction.service.d.ts +29 -5
  175. package/dist/services/transaction/transaction.service.js +198 -34
  176. package/dist/services/transaction/transaction.service.js.map +1 -1
  177. package/dist/services/validation/validation.service.d.ts +2 -2
  178. package/dist/{functions → utilities}/diff/diff.utility.d.ts +2 -1
  179. package/dist/{functions → utilities}/diff/diff.utility.js +4 -3
  180. package/dist/utilities/diff/diff.utility.js.map +1 -0
  181. package/dist/utilities/file/file.utility.d.ts +6 -7
  182. package/dist/utilities/file/file.utility.js +13 -68
  183. package/dist/utilities/file/file.utility.js.map +1 -1
  184. package/dist/utilities/hcl/hcl.utility.d.ts +83 -16
  185. package/dist/utilities/hcl/hcl.utility.js +200 -27
  186. package/dist/utilities/hcl/hcl.utility.js.map +1 -1
  187. package/dist/utilities/http/http.utility.d.ts +57 -0
  188. package/dist/utilities/http/http.utility.js +98 -0
  189. package/dist/utilities/http/http.utility.js.map +1 -0
  190. package/dist/utilities/string/string.utility.d.ts +23 -3
  191. package/dist/utilities/string/string.utility.js +29 -3
  192. package/dist/utilities/string/string.utility.js.map +1 -1
  193. package/dist/utilities/terraform/terraform.utility.d.ts +70 -21
  194. package/dist/utilities/terraform/terraform.utility.js +255 -66
  195. package/dist/utilities/terraform/terraform.utility.js.map +1 -1
  196. package/dist/utilities/test-helpers/test-classes.d.ts +19 -0
  197. package/dist/utilities/test-helpers/test-classes.js +22 -0
  198. package/dist/utilities/test-helpers/test-classes.js.map +1 -1
  199. package/dist/utilities/test-helpers/test-models.d.ts +8 -1
  200. package/dist/utilities/test-helpers/test-models.js +25 -12
  201. package/dist/utilities/test-helpers/test-models.js.map +1 -1
  202. package/dist/utilities/test-helpers/test-modes.d.ts +1 -1
  203. package/dist/utilities/test-helpers/test-modes.js +36 -13
  204. package/dist/utilities/test-helpers/test-modes.js.map +1 -1
  205. package/dist/utilities/test-helpers/test-modules.d.ts +6 -0
  206. package/dist/utilities/test-helpers/test-modules.js +6 -0
  207. package/dist/utilities/test-helpers/test-modules.js.map +1 -1
  208. package/dist/utilities/test-helpers/test-overlays.d.ts +3 -1
  209. package/dist/utilities/test-helpers/test-overlays.js +8 -2
  210. package/dist/utilities/test-helpers/test-overlays.js.map +1 -1
  211. package/dist/utilities/test-helpers/test-resources.d.ts +4 -2
  212. package/dist/utilities/test-helpers/test-resources.js +22 -20
  213. package/dist/utilities/test-helpers/test-resources.js.map +1 -1
  214. package/package.json +7 -2
  215. package/dist/functions/diff/diff.utility.js.map +0 -1
  216. package/dist/services/state-management/file-app-lock.utility.d.ts +0 -43
  217. package/dist/services/state-management/file-app-lock.utility.js +0 -124
  218. package/dist/services/state-management/file-app-lock.utility.js.map +0 -1
  219. package/dist/services/state-management/local-encryption.state-provider.d.ts +0 -71
  220. package/dist/services/state-management/local-encryption.state-provider.js +0 -116
  221. package/dist/services/state-management/local-encryption.state-provider.js.map +0 -1
@@ -16,6 +16,7 @@ import { StateManagementService } from './state-management.service.js';
16
16
  */
17
17
  export declare class TerraformStateService {
18
18
  private checkInQueue;
19
+ private isPeriodicCheckInRunning;
19
20
  private periodicCheckIn;
20
21
  private readonly stateManagementService;
21
22
  /**
@@ -23,11 +24,13 @@ export declare class TerraformStateService {
23
24
  */
24
25
  constructor(stateManagementService: StateManagementService);
25
26
  /**
26
- * Writes every module folder's terraform state to the state provider.
27
+ * Writes every module folder's terraform state to the state provider, as one change.
27
28
  *
28
29
  * A folder holding no state file, or one recording no resources, has its state **deleted** rather
29
30
  * than written - that is a module terraform has destroyed, and a file recording nothing is a file
30
- * worth nobody's history.
31
+ * worth nobody's history. A file that does not parse is left out, keeping the provider's copy:
32
+ * terraform rewrites the file in place, so a check-in during a command can read it empty or half
33
+ * written.
31
34
  *
32
35
  * Check-ins are serialized, so the one on a command's return never races the one on the timer.
33
36
  */
@@ -36,23 +39,43 @@ export declare class TerraformStateService {
36
39
  * Writes the state provider's terraform state into every module folder.
37
40
  *
38
41
  * A module the provider holds nothing for keeps whatever its folder already has, which is what
39
- * adopts the state of a stack that ran with a local backend until now. The check-in that follows
40
- * hands that state to the provider, and from then on the provider is the one that answers.
42
+ * adopts the state of a stack that ran with a local backend until now. A folder whose state is
43
+ * ahead of the provider's keeps it too, which is what a run killed before its last check-in
44
+ * leaves behind. The check-in that follows hands either to the provider.
41
45
  */
42
46
  checkOut(terragruntDir: string, moduleIds: string[]): Promise<void>;
43
47
  /**
44
- * Starts checking state in on a timer, bounding what a killed runner loses to one interval.
48
+ * The state moves the last generate recorded, each from one module folder into another.
45
49
  */
46
- startPeriodicCheckIn(terragruntDir: string, moduleIds: string[]): void;
50
+ getRecordedMoves(): Promise<{
51
+ addresses: {
52
+ from: string;
53
+ to: string;
54
+ }[];
55
+ fromModuleId: string;
56
+ moduleId: string;
57
+ outputs: {
58
+ from: string;
59
+ to: string;
60
+ }[];
61
+ }[]>;
62
+ /**
63
+ * Starts checking state in every `intervalInMs`, bounding what a killed runner loses to one interval.
64
+ */
65
+ startPeriodicCheckIn(terragruntDir: string, moduleIds: string[], intervalInMs: number): void;
47
66
  /**
48
67
  * Stops the timer {@link TerraformStateService.startPeriodicCheckIn} started.
49
68
  */
50
69
  stopPeriodicCheckIn(): void;
51
70
  /**
52
- * Whether a terraform state file records nothing, which is what a destroyed module is left with.
53
- * State that cannot be parsed is treated as holding resources, so an unreadable file is kept.
71
+ * Whether the folder's terraform state is a later write of the same state the provider holds: the
72
+ * same `lineage`, and a higher `serial`. State that cannot be read or parsed is never ahead.
73
+ */
74
+ private isFolderStateAhead;
75
+ /**
76
+ * A terraform state file's contents, or `undefined` when it does not parse.
54
77
  */
55
- private holdsNoResources;
78
+ private parseState;
56
79
  /**
57
80
  * The name one module's terraform state is stored under, alongside Octo's own state files.
58
81
  */
@@ -10,7 +10,6 @@ import { join } from 'node:path';
10
10
  import { Factory } from '../../decorators/factory.decorator.js';
11
11
  import { Container } from '../../functions/container/container.js';
12
12
  import { StateManagementService } from './state-management.service.js';
13
- const CHECK_IN_INTERVAL_IN_MS = 60 * 1000;
14
13
  const TERRAFORM_STATE_FILE_NAME = 'terraform.tfstate';
15
14
  /**
16
15
  * Moves each module folder's `terraform.tfstate` in and out of Octo's {@link IStateProvider}.
@@ -33,14 +32,17 @@ export class TerraformStateService {
33
32
  */
34
33
  constructor(stateManagementService) {
35
34
  this.checkInQueue = Promise.resolve();
35
+ this.isPeriodicCheckInRunning = false;
36
36
  this.stateManagementService = stateManagementService;
37
37
  }
38
38
  /**
39
- * Writes every module folder's terraform state to the state provider.
39
+ * Writes every module folder's terraform state to the state provider, as one change.
40
40
  *
41
41
  * A folder holding no state file, or one recording no resources, has its state **deleted** rather
42
42
  * than written - that is a module terraform has destroyed, and a file recording nothing is a file
43
- * worth nobody's history.
43
+ * worth nobody's history. A file that does not parse is left out, keeping the provider's copy:
44
+ * terraform rewrites the file in place, so a check-in during a command can read it empty or half
45
+ * written.
44
46
  *
45
47
  * Check-ins are serialized, so the one on a command's return never races the one on the timer.
46
48
  */
@@ -52,8 +54,9 @@ export class TerraformStateService {
52
54
  * Writes the state provider's terraform state into every module folder.
53
55
  *
54
56
  * A module the provider holds nothing for keeps whatever its folder already has, which is what
55
- * adopts the state of a stack that ran with a local backend until now. The check-in that follows
56
- * hands that state to the provider, and from then on the provider is the one that answers.
57
+ * adopts the state of a stack that ran with a local backend until now. A folder whose state is
58
+ * ahead of the provider's keeps it too, which is what a run killed before its last check-in
59
+ * leaves behind. The check-in that follows hands either to the provider.
57
60
  */
58
61
  async checkOut(terragruntDir, moduleIds) {
59
62
  for (const moduleId of moduleIds) {
@@ -67,21 +70,41 @@ export class TerraformStateService {
67
70
  }
68
71
  throw error;
69
72
  }
70
- await writeFile(join(terragruntDir, moduleId, TERRAFORM_STATE_FILE_NAME), data);
73
+ const stateFilePath = join(terragruntDir, moduleId, TERRAFORM_STATE_FILE_NAME);
74
+ if (await this.isFolderStateAhead(stateFilePath, data)) {
75
+ continue;
76
+ }
77
+ await writeFile(stateFilePath, data);
71
78
  }
72
79
  }
73
80
  /**
74
- * Starts checking state in on a timer, bounding what a killed runner loses to one interval.
81
+ * The state moves the last generate recorded, each from one module folder into another.
82
+ */
83
+ async getRecordedMoves() {
84
+ const { userData } = await this.stateManagementService.getModelState(this.stateManagementService.modelStateFileName);
85
+ return (userData.terraformFolders ?? []).flatMap(({ moduleId, moves = [] }) => moves.map(({ addresses, fromModuleId, outputs }) => ({ addresses, fromModuleId, moduleId, outputs })));
86
+ }
87
+ /**
88
+ * Starts checking state in every `intervalInMs`, bounding what a killed runner loses to one interval.
75
89
  */
76
- startPeriodicCheckIn(terragruntDir, moduleIds) {
90
+ startPeriodicCheckIn(terragruntDir, moduleIds, intervalInMs) {
77
91
  if (this.periodicCheckIn) {
78
92
  return;
79
93
  }
80
94
  this.periodicCheckIn = setInterval(() => {
95
+ // Skip the tick while the last one is still checking in, rather than waiting behind it.
96
+ if (this.isPeriodicCheckInRunning) {
97
+ return;
98
+ }
99
+ this.isPeriodicCheckInRunning = true;
81
100
  // A tick that fails is not worth aborting the terraform command that is still running - the
82
101
  // check-in on its return reports the failure instead.
83
- void this.checkIn(terragruntDir, moduleIds).catch(() => { });
84
- }, CHECK_IN_INTERVAL_IN_MS);
102
+ void this.checkIn(terragruntDir, moduleIds)
103
+ .catch(() => { })
104
+ .finally(() => {
105
+ this.isPeriodicCheckInRunning = false;
106
+ });
107
+ }, intervalInMs);
85
108
  }
86
109
  /**
87
110
  * Stops the timer {@link TerraformStateService.startPeriodicCheckIn} started.
@@ -93,18 +116,33 @@ export class TerraformStateService {
93
116
  }
94
117
  }
95
118
  /**
96
- * Whether a terraform state file records nothing, which is what a destroyed module is left with.
97
- * State that cannot be parsed is treated as holding resources, so an unreadable file is kept.
119
+ * Whether the folder's terraform state is a later write of the same state the provider holds: the
120
+ * same `lineage`, and a higher `serial`. State that cannot be read or parsed is never ahead.
98
121
  */
99
- holdsNoResources(data) {
122
+ async isFolderStateAhead(stateFilePath, providerData) {
100
123
  try {
101
- const state = JSON.parse(data.toString());
102
- return Array.isArray(state.resources) && state.resources.length === 0;
124
+ const folderState = JSON.parse(await readFile(stateFilePath, 'utf-8'));
125
+ const providerState = JSON.parse(providerData.toString());
126
+ return (typeof folderState.serial === 'number' &&
127
+ typeof providerState.serial === 'number' &&
128
+ folderState.lineage === providerState.lineage &&
129
+ folderState.serial > providerState.serial);
103
130
  }
104
131
  catch {
105
132
  return false;
106
133
  }
107
134
  }
135
+ /**
136
+ * A terraform state file's contents, or `undefined` when it does not parse.
137
+ */
138
+ parseState(data) {
139
+ try {
140
+ return JSON.parse(data.toString());
141
+ }
142
+ catch {
143
+ return undefined;
144
+ }
145
+ }
108
146
  /**
109
147
  * The name one module's terraform state is stored under, alongside Octo's own state files.
110
148
  */
@@ -115,21 +153,26 @@ export class TerraformStateService {
115
153
  * The body of a check-in, run one at a time through {@link TerraformStateService.checkIn}.
116
154
  */
117
155
  async writeStates(terragruntDir, moduleIds) {
156
+ const stateFiles = [];
118
157
  for (const moduleId of moduleIds) {
119
158
  const stateFilePath = join(terragruntDir, moduleId, TERRAFORM_STATE_FILE_NAME);
120
159
  const stateFileName = this.stateFileName(moduleId);
160
+ // Delete the state of a module with no state file.
121
161
  if (!existsSync(stateFilePath)) {
122
- await this.stateManagementService.deleteState(stateFileName);
162
+ stateFiles.push({ data: undefined, stateFileName });
123
163
  continue;
124
164
  }
165
+ // Skip a file terraform is still writing.
125
166
  const data = await readFile(stateFilePath);
126
- if (this.holdsNoResources(data)) {
127
- await this.stateManagementService.deleteState(stateFileName);
128
- }
129
- else {
130
- await this.stateManagementService.saveState(stateFileName, data);
167
+ const state = this.parseState(data);
168
+ if (!state) {
169
+ continue;
131
170
  }
171
+ // Delete the state of a module recording no resources, and write every other.
172
+ const holdsNoResources = Array.isArray(state.resources) && state.resources.length === 0;
173
+ stateFiles.push({ data: holdsNoResources ? undefined : data, stateFileName });
132
174
  }
175
+ await this.stateManagementService.saveStates(stateFiles);
133
176
  }
134
177
  }
135
178
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"terraform-state.service.js","sourceRoot":"","sources":["../../../src/services/state-management/terraform-state.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terraform-state.service.js","sourceRoot":"","sources":["../../../src/services/state-management/terraform-state.service.ts"],"names":[],"mappings":"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"}
@@ -9,10 +9,6 @@ import type { IStateProvider } from './state-provider.interface.js';
9
9
  export declare class TestStateProvider implements IStateProvider {
10
10
  private readonly localState;
11
11
  private lockId;
12
- /**
13
- * Deletes a state file. Deleting one that is not there is not an error.
14
- */
15
- deleteState(stateFileName: string): Promise<void>;
16
12
  /**
17
13
  * The id of the lock currently held, or `undefined` when the app is unlocked.
18
14
  */
@@ -34,17 +30,22 @@ export declare class TestStateProvider implements IStateProvider {
34
30
  */
35
31
  listStates(prefix: string): Promise<string[]>;
36
32
  /**
37
- * Takes the app lock.
33
+ * Takes the app lock. With `lockId`, resumes the run already holding it.
38
34
  *
39
- * @throws {@link TransactionError} When a lock is held, naming the id that holds it.
35
+ * @throws {@link TransactionError} When a lock is held, naming the id that holds it. When `lockId`
36
+ * is given and is not the lock held.
37
+ */
38
+ lockApp(lockId?: string): ReturnType<IStateProvider['lockApp']>;
39
+ /**
40
+ * Does nothing: the state lives in this process alone, so it is always the newest.
40
41
  */
41
- lockApp(): Promise<{
42
- lockId: string;
42
+ refreshState(): Promise<{
43
+ replaced: boolean;
43
44
  }>;
44
45
  /**
45
- * Writes a state file, overwriting whatever was there.
46
+ * Writes state files, overwriting whatever was there and deleting each one given no `data`.
46
47
  */
47
- saveState(stateFileName: string, data: Buffer): Promise<void>;
48
+ saveStates(stateFiles: Parameters<IStateProvider['saveStates']>[0]): Promise<void>;
48
49
  /**
49
50
  * Releases the app lock.
50
51
  *
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { TransactionError } from '../../errors/index.js';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { AppLockError, TransactionError } from '../../errors/index.js';
3
4
  /**
4
5
  * Keeps state in memory, and discards it when the process ends. For tests.
5
6
  *
@@ -12,12 +13,6 @@ export class TestStateProvider {
12
13
  this.localState = {};
13
14
  this.lockId = undefined;
14
15
  }
15
- /**
16
- * Deletes a state file. Deleting one that is not there is not an error.
17
- */
18
- async deleteState(stateFileName) {
19
- delete this.localState[stateFileName];
20
- }
21
16
  /**
22
17
  * The id of the lock currently held, or `undefined` when the app is unlocked.
23
18
  */
@@ -56,22 +51,48 @@ export class TestStateProvider {
56
51
  return Object.keys(this.localState).filter((stateFileName) => stateFileName.startsWith(prefix));
57
52
  }
58
53
  /**
59
- * Takes the app lock.
54
+ * Takes the app lock. With `lockId`, resumes the run already holding it.
60
55
  *
61
- * @throws {@link TransactionError} When a lock is held, naming the id that holds it.
56
+ * @throws {@link TransactionError} When a lock is held, naming the id that holds it. When `lockId`
57
+ * is given and is not the lock held.
62
58
  */
63
- async lockApp() {
59
+ async lockApp(lockId) {
60
+ if (lockId) {
61
+ if (this.lockId !== lockId) {
62
+ throw new AppLockError(`App is not locked with lock ID "${lockId}"!`, { reason: 'not-held' });
63
+ }
64
+ return { lockId };
65
+ }
64
66
  if (this.lockId) {
65
- throw new TransactionError(`App already locked with lock ID "${this.lockId}"!`);
67
+ throw new AppLockError(`App already locked with lock ID "${this.lockId}"!`, {
68
+ holder: { lockId: this.lockId },
69
+ reason: 'held',
70
+ });
66
71
  }
67
72
  this.lockId = randomUUID();
68
73
  return { lockId: this.lockId };
69
74
  }
70
75
  /**
71
- * Writes a state file, overwriting whatever was there.
76
+ * Does nothing: the state lives in this process alone, so it is always the newest.
72
77
  */
73
- async saveState(stateFileName, data) {
74
- this.localState[stateFileName] = data;
78
+ async refreshState() {
79
+ return { replaced: false };
80
+ }
81
+ /**
82
+ * Writes state files, overwriting whatever was there and deleting each one given no `data`.
83
+ */
84
+ async saveStates(stateFiles) {
85
+ for (const stateFile of stateFiles) {
86
+ if ('filePath' in stateFile) {
87
+ this.localState[stateFile.stateFileName] = await readFile(stateFile.filePath);
88
+ }
89
+ else if (stateFile.data === undefined) {
90
+ delete this.localState[stateFile.stateFileName];
91
+ }
92
+ else {
93
+ this.localState[stateFile.stateFileName] = stateFile.data;
94
+ }
95
+ }
75
96
  }
76
97
  /**
77
98
  * Releases the app lock.
@@ -1 +1 @@
1
- {"version":3,"file":"test.state-provider.js","sourceRoot":"","sources":["../../../src/services/state-management/test.state-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD;;;;;;GAMG;AACH,MAAM,OAAO,iBAAiB;IAA9B;QACmB,eAAU,GAA8B,EAAE,CAAC;QAEpD,WAAM,GAAuB,SAAS,CAAC;IAsFjD,CAAC;IApFC;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,aAAqB;QACrC,OAAO,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,aAAqB;QAClC,IAAI,CAAC,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,MAAc;QAC9B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,EAAE,CAAC,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAClG,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,gBAAgB,CAAC,oCAAoC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QAE3B,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,aAAqB,EAAE,IAAY;QACjD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS,CAAC,MAAc;QAC5B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;IAC1B,CAAC;CACF"}
1
+ {"version":3,"file":"test.state-provider.js","sourceRoot":"","sources":["../../../src/services/state-management/test.state-provider.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,10 @@ import { HclExpression, TerraformData, type TerraformLiteralType, TerraformResou
7
7
  * - Commit (tfstate outputs -> response population).
8
8
  */
9
9
  interface OctoTerraformResourceMapping {
10
+ /**
11
+ * Whether destroying the octo resource loses data, as its class declares.
12
+ */
13
+ destroyLosesData: boolean;
10
14
  /**
11
15
  * Set for external resources only: the single output carrying the entire response map.
12
16
  */
@@ -53,8 +57,15 @@ export interface OctoTerraformResourceScope<TResponse extends BaseResourceSchema
53
57
  * Emits one `resource "<type>" "<name>"` block for this octo resource.
54
58
  * Multiple calls chain via `depends_on` in call order.
55
59
  * The provider alias is applied automatically when the resource was registered with a provider context.
60
+ *
61
+ * Pass `options.chain: false` to keep a block out of the chain: it depends on neither the block
62
+ * before it nor is depended on by the block after it, and waits only on what its attributes
63
+ * reference. Use it for a block emitted once per item, like a file upload, where a chain would run
64
+ * the items one at a time and store every earlier address in each item's terraform state.
56
65
  */
57
- addTerraformResource(type: string, name: string, spec?: Record<string, unknown>): TerraformResource;
66
+ addTerraformResource(type: string, name: string, spec?: Record<string, unknown>, options?: {
67
+ chain?: boolean;
68
+ }): TerraformResource;
58
69
  /**
59
70
  * Declares the outputs for this resource — one entry per response key.
60
71
  * Every response key must be declared; the commit phase reads exactly these outputs to
@@ -119,6 +130,18 @@ export interface TerraformModuleScope {
119
130
  */
120
131
  getRef<T extends UnknownResource>(resource: T, key: keyof ResourceSchema<T>['response'] & string): HclExpression;
121
132
  getRef<S extends BaseResourceSchema>(resource: MatchingResource<S>, key: keyof S['response'] & string): HclExpression;
133
+ /**
134
+ * Joins text and references into one quoted terraform string, for when a value is another
135
+ * resource's output with text of this resource's own around it. Each reference resolves as
136
+ * {@link TerraformModuleScope.getRef} does, so a producer in another folder is still wired in.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * // "https://${var.distribution_DomainName}/api/orders"
141
+ * terraform.interpolate('https://', terraform.getRef(distribution, 'DomainName'), '/api/orders');
142
+ * ```
143
+ */
144
+ interpolate(...parts: (string | HclExpression)[]): HclExpression;
122
145
  /**
123
146
  * Wraps a value in a `jsonencode(...)` call.
124
147
  */
@@ -262,12 +285,25 @@ export declare class TerraformService {
262
285
  * expected rather than unattributed.
263
286
  */
264
287
  getResourceReferrers(): Map<string, Set<string>>;
288
+ /**
289
+ * Records that a resource takes over the terraform state of a resource octo committed, so terraform
290
+ * updates or replaces the infrastructure in place instead of creating the new resource beside the old.
291
+ * Within one folder, the folder renders a `moved` block from each of the old resource's addresses to
292
+ * the new one's. Between folders, the folder record lists the move, and `TerraformUtility`
293
+ * moves the state before terraform runs.
294
+ */
295
+ moveResource(fromModuleId: string, fromResourceId: string, toModuleId: string, toResourceId: string): void;
265
296
  /**
266
297
  * Phase 2 of rendering: prints the four files of every folder from the wiring computed in
267
298
  * phase 1. Makes no decisions — when a {@link RefHclNode} renders, its answer is looked up in the
268
299
  * folder's precomputed `resolvedRefs`. Cross-folder dependency cycles throw (in phase 1).
300
+ *
301
+ * A folder also keeps every provider block `previousFolders` recorded for it that none of its
302
+ * resources use any more, so terraform can still destroy what that provider created. A resource
303
+ * whose id moved to another account, for one, leaves its old address in the folder's state, bound
304
+ * to the old account's provider.
269
305
  */
270
- renderAllModules(): Map<string, TerraformModuleFiles>;
306
+ renderAllModules(previousFolders?: TerraformFolderOutput[]): Map<string, TerraformModuleFiles>;
271
307
  /**
272
308
  * Prints the four files of an **empty** folder from a recorded folder: a `main.tf` with the
273
309
  * config block rebuilt from the recorded `required_providers` entries (plus null/external when
@@ -346,6 +382,20 @@ export declare class TerraformService {
346
382
  * registered wins, since the alias only routes the region.
347
383
  */
348
384
  private findProvider;
385
+ /**
386
+ * The moves of a folder's record: the resources whose state moves in from another folder, with the
387
+ * outputs their consumers in other folders read.
388
+ */
389
+ private getFolderMoves;
390
+ /**
391
+ * The addresses a resource's state moves between. A block named `<resourceId><suffix>` moves from the
392
+ * block the old resource named `<its resourceId><suffix>`; a `from` the state does not hold moves nothing.
393
+ */
394
+ private getMoveAddresses;
395
+ /**
396
+ * The `moved` addresses of a folder, for the resources taking over a state the same folder holds.
397
+ */
398
+ private getMovedAddresses;
349
399
  /**
350
400
  * @internal
351
401
  */
@@ -354,6 +404,10 @@ export declare class TerraformService {
354
404
  * @internal
355
405
  */
356
406
  private getRef;
407
+ /**
408
+ * @internal
409
+ */
410
+ private interpolate;
357
411
  /**
358
412
  * @internal
359
413
  */
@@ -388,7 +442,7 @@ export declare class TerraformService {
388
442
  private renderConfigBlock;
389
443
  /**
390
444
  * Prints one folder's `terragrunt.hcl`: a `dependency` block per producer folder (with
391
- * `mock_outputs`, shallow-merged over any applied state, so `run-all plan` works both before the
445
+ * `mock_outputs`, shallow-merged over any applied state, so `run --all -- plan` works both before the
392
446
  * first apply and when intent renames a producer resource), and the `inputs` map wiring each auto
393
447
  * variable to the producer's output.
394
448
  */