@truefoundry/tfy-infra-engine 0.0.0-canary.edab06d
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/README.md +287 -0
- package/dist/index.d.mts +589 -0
- package/dist/index.d.ts +589 -0
- package/dist/index.js +1155 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1109 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# @truefoundry/tfy-infra-engine
|
|
2
|
+
|
|
3
|
+
Config-driven HCL templating engine for infrastructure generation with integrity verification.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @truefoundry/tfy-infra-engine
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createEngine } from '@truefoundry/tfy-infra-engine';
|
|
15
|
+
import type { Template, Envelope } from '@truefoundry/tfy-infra-engine';
|
|
16
|
+
|
|
17
|
+
const engine = createEngine();
|
|
18
|
+
|
|
19
|
+
// The engine accepts pre-resolved Template objects.
|
|
20
|
+
// The caller is responsible for fetching/resolving templates.
|
|
21
|
+
const template: Template = {
|
|
22
|
+
metadata: { name: 'sample', version: '0.1.0' },
|
|
23
|
+
jsonSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
required: ['name'],
|
|
26
|
+
properties: {
|
|
27
|
+
name: { type: 'string' },
|
|
28
|
+
environment: { type: 'string', default: 'dev' },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
files: new Map([['tfy_main.tf', '# HCL template content']]),
|
|
32
|
+
source: 'file:///path/to/template',
|
|
33
|
+
version: { major: 'v1', semver: '0.1.0', full: 'v1/0.1.0' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const result = await engine.install({
|
|
37
|
+
template,
|
|
38
|
+
inputs: { name: 'my-cluster', environment: 'production' },
|
|
39
|
+
intentId: 'cluster-prod-001',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// result.files is a Map<string, FileEntry> (zone-tagged)
|
|
43
|
+
for (const [path, entry] of result.files) {
|
|
44
|
+
console.log(`${path} [${entry.zone}]:\n${entry.content}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// result.manifest is a JSON Manifest v1.0
|
|
48
|
+
console.log('Aggregate Hash:', result.manifest.aggregateHash);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- **Pure transformer**: Accepts pre-resolved `Template` objects -- no I/O for fetching
|
|
54
|
+
- **JSON Schema validation**: Validate inputs against JSON Schema Draft-07 using AJV with `useDefaults`, `allErrors`, and `discriminator` support
|
|
55
|
+
- **Handlebars templating**: 7 built-in helpers for HCL generation
|
|
56
|
+
- **HCL formatting**: Automatic formatting with `tofu fmt`
|
|
57
|
+
- **Deterministic output**: Byte-identical output for identical inputs
|
|
58
|
+
- **Integrity verification**: Zone-based file ownership, `@tfy-status` headers, JSON Manifest, and drift detection
|
|
59
|
+
- **Four-operation API**: `install`, `verify`, `upgrade`, and `hashOnly`
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
### `createEngine()`
|
|
64
|
+
|
|
65
|
+
Create a new engine instance. Takes no parameters.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const engine = createEngine();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `engine.install(envelope)`
|
|
72
|
+
|
|
73
|
+
Generate all files for a new cluster. Returns zone-tagged files with `@tfy-status` headers on platform files and a JSON Manifest.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const result = await engine.install({
|
|
77
|
+
template, // Pre-resolved Template object
|
|
78
|
+
inputs: { name: 'test' },
|
|
79
|
+
intentId: 'cluster-001',
|
|
80
|
+
options: { skipFormat: false },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// result.files: Map<string, FileEntry>
|
|
84
|
+
// result.manifest: Manifest (JSON v1.0)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `engine.verify(currentFiles, previousManifest)`
|
|
88
|
+
|
|
89
|
+
Check integrity of existing files against a Manifest. Returns a structured drift report.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const result = await engine.verify(currentFiles, previousManifest);
|
|
93
|
+
if (!result.driftReport.valid) {
|
|
94
|
+
for (const entry of result.driftReport.entries) {
|
|
95
|
+
console.log(`${entry.path}: ${entry.type} -- ${entry.details}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `engine.upgrade(envelope, currentFiles, previousManifest)`
|
|
101
|
+
|
|
102
|
+
Update platform files to new template versions. Performs pre-upgrade drift detection and source mismatch blocking.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const result = await engine.upgrade(envelope, currentFiles, previousManifest);
|
|
106
|
+
if (result.sourceBlocked) {
|
|
107
|
+
console.log('Upgrade blocked: template source mismatch');
|
|
108
|
+
} else if (!result.driftReport.valid) {
|
|
109
|
+
console.log('Pre-upgrade drift detected');
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `engine.hashOnly(envelope)`
|
|
114
|
+
|
|
115
|
+
Calculate the expected Aggregate Platform Hash without generating files. Used by the Control Plane for fleet status comparison.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const result = await engine.hashOnly(envelope);
|
|
119
|
+
console.log('Expected hash:', result.aggregateHash);
|
|
120
|
+
console.log('File count:', result.fileCount);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Input Validation (AJV-based)
|
|
124
|
+
|
|
125
|
+
Validate inputs against a template's JSON Schema. Exported as public utilities.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
createInputValidator,
|
|
130
|
+
validateAndApplyDefaults,
|
|
131
|
+
validateJsonSchemaStructure,
|
|
132
|
+
} from '@truefoundry/tfy-infra-engine';
|
|
133
|
+
|
|
134
|
+
// Check schema is valid
|
|
135
|
+
validateJsonSchemaStructure(template.jsonSchema); // Throws if not object with properties
|
|
136
|
+
|
|
137
|
+
// Compile and validate (AJV resolves local $ref pointers natively)
|
|
138
|
+
const validator = createInputValidator(template.jsonSchema);
|
|
139
|
+
const inputs = { name: 'my-cluster' };
|
|
140
|
+
const result = validateAndApplyDefaults(validator, inputs);
|
|
141
|
+
|
|
142
|
+
if (!result.valid) {
|
|
143
|
+
console.error('Validation errors:', result.errors);
|
|
144
|
+
}
|
|
145
|
+
// inputs now has defaults applied in-place
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Template JSON Validation
|
|
149
|
+
|
|
150
|
+
Validate the top-level structure of a `template.json` file:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { validateTemplateJson } from '@truefoundry/tfy-infra-engine';
|
|
154
|
+
|
|
155
|
+
const parsed = JSON.parse(fs.readFileSync('template.json', 'utf-8'));
|
|
156
|
+
const { metadata, jsonSchema } = validateTemplateJson(parsed);
|
|
157
|
+
// metadata: { name, version, description? }
|
|
158
|
+
// jsonSchema: the raw JSON Schema object
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Envelope Configuration
|
|
162
|
+
|
|
163
|
+
The envelope is the engine's input configuration:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
{
|
|
167
|
+
template: templateObject, // Pre-resolved Template object
|
|
168
|
+
inputs: { name: 'my-cluster' }, // Input values
|
|
169
|
+
intentId: 'cluster-prod-001', // Cluster identity (required)
|
|
170
|
+
platformPrefix: 'tfy_', // Platform Zone filename prefix (default: "tfy_")
|
|
171
|
+
options: {
|
|
172
|
+
skipFormat: false, // Skip tofu fmt
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Template Object
|
|
178
|
+
|
|
179
|
+
The `Template` type represents a fully resolved template:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
interface Template {
|
|
183
|
+
metadata: TemplateMetadata; // { name, version, description? }
|
|
184
|
+
jsonSchema: JSONSchema7; // JSON Schema Draft-07 for input validation
|
|
185
|
+
files: Map<string, string>; // HBS template files from src/ (rendered via Handlebars)
|
|
186
|
+
staticFiles: Map<string, string>; // Static files from src/ (copied verbatim)
|
|
187
|
+
source: string; // Source URI for tracking
|
|
188
|
+
version: VersionInfo; // Version metadata
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Integrity Engine
|
|
193
|
+
|
|
194
|
+
### Zone Classification
|
|
195
|
+
|
|
196
|
+
Files are classified into two zones based on filename prefix:
|
|
197
|
+
|
|
198
|
+
- **Platform Zone** (`tfy_*` prefix, `manifest.json`): Engine-managed, includes `@tfy-status` headers
|
|
199
|
+
- **User Zone** (no prefix): Customer-owned, excluded from integrity tracking
|
|
200
|
+
|
|
201
|
+
### `@tfy-status` Header
|
|
202
|
+
|
|
203
|
+
Platform Zone files include a structured metadata header:
|
|
204
|
+
|
|
205
|
+
```hcl
|
|
206
|
+
# @tfy-status:begin
|
|
207
|
+
# {"managed":true,"source":"github://...","version":"v2.4.1","intent_id":"cluster-001","content_hash":"sha256:..."}
|
|
208
|
+
# @tfy-status:end
|
|
209
|
+
|
|
210
|
+
resource "aws_eks_cluster" "main" {
|
|
211
|
+
...
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### JSON Manifest
|
|
216
|
+
|
|
217
|
+
Each install/upgrade produces a `manifest.json` containing:
|
|
218
|
+
|
|
219
|
+
- Cluster identity (`intentId`)
|
|
220
|
+
- Template source and version
|
|
221
|
+
- Aggregate Platform Hash
|
|
222
|
+
- Per-file hashes, sources, versions, and zones
|
|
223
|
+
|
|
224
|
+
### Drift Detection
|
|
225
|
+
|
|
226
|
+
The `verify()` operation detects five types of drift:
|
|
227
|
+
|
|
228
|
+
| Type | Description |
|
|
229
|
+
|------|-------------|
|
|
230
|
+
| `content_drift` | File content hash doesn't match Manifest |
|
|
231
|
+
| `metadata_inconsistency` | Header fields don't match Manifest entry |
|
|
232
|
+
| `source_mismatch` | File source differs from incoming source |
|
|
233
|
+
| `missing_file` | File in Manifest but not on disk |
|
|
234
|
+
| `unexpected_file` | Platform-prefixed file not in Manifest |
|
|
235
|
+
|
|
236
|
+
## Handlebars Helpers
|
|
237
|
+
|
|
238
|
+
The engine provides 7 focused helpers for HCL generation:
|
|
239
|
+
|
|
240
|
+
### HCL Formatting
|
|
241
|
+
- `toTerraformValue` - Convert any value to HCL syntax
|
|
242
|
+
|
|
243
|
+
### Logic
|
|
244
|
+
- `eq` - Strict equality comparison
|
|
245
|
+
- `and` - Logical AND (all arguments truthy)
|
|
246
|
+
- `or` - Logical OR (any argument truthy)
|
|
247
|
+
|
|
248
|
+
### Type Checking
|
|
249
|
+
- `isString` - Check if value is a string
|
|
250
|
+
- `isDefined` - Check if value is defined (not null/undefined)
|
|
251
|
+
- `isNotEmpty` - Check if value is not empty
|
|
252
|
+
|
|
253
|
+
## Error Handling
|
|
254
|
+
|
|
255
|
+
All errors are instances of `EngineError` with typed error codes:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { EngineError, EngineErrorCode } from '@truefoundry/tfy-infra-engine';
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await engine.install(envelope);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (error instanceof EngineError) {
|
|
264
|
+
switch (error.code) {
|
|
265
|
+
case EngineErrorCode.ENVELOPE_VALIDATION_FAILED:
|
|
266
|
+
console.log('Invalid envelope:', error.message);
|
|
267
|
+
break;
|
|
268
|
+
case EngineErrorCode.INPUT_VALIDATION_FAILED:
|
|
269
|
+
console.log('Invalid inputs:', error.details);
|
|
270
|
+
break;
|
|
271
|
+
case EngineErrorCode.TEMPLATE_JSON_NOT_FOUND:
|
|
272
|
+
console.log('template.json not found in template directory');
|
|
273
|
+
break;
|
|
274
|
+
case EngineErrorCode.TEMPLATE_JSON_INVALID:
|
|
275
|
+
console.log('template.json has invalid structure:', error.message);
|
|
276
|
+
break;
|
|
277
|
+
case EngineErrorCode.TOFU_NOT_FOUND:
|
|
278
|
+
console.log('Install OpenTofu or use skipFormat option');
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|