@terraforge/terraform 0.0.8 → 0.0.10
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 +54 -1
- package/cli/build-package.ts +42 -15
- package/dist/index.d.ts +7 -2
- package/dist/index.js +90 -42
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,6 +3,59 @@
|
|
|
3
3
|
|
|
4
4
|
This package is used to build Terraform bridge packages that can be used with @terraforge/core.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
It works in 3 steps:
|
|
7
|
+
|
|
8
|
+
1. It provides a build script that generates the typescript typings for your Terraform bridge package.
|
|
9
|
+
|
|
10
|
+
2. It provides a proxy api that can we used to interact with Terraform resources.
|
|
11
|
+
|
|
12
|
+
3. It will install the Terraform provider that you specify in your bridge package.
|
|
13
|
+
|
|
14
|
+
### Terraform Plugin installation location
|
|
7
15
|
|
|
8
16
|
The default installation location is `~/.terraforge/plugins`.
|
|
17
|
+
|
|
18
|
+
## Usage Guide
|
|
19
|
+
|
|
20
|
+
### Step 1. Create a package.json
|
|
21
|
+
Create a package.json for the bridge package you want to create.
|
|
22
|
+
In the `package.json` file, you will need to specify the provider details that your package is for.
|
|
23
|
+
Example:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"name": "@terraforge/aws",
|
|
27
|
+
"version": "1.0.0",
|
|
28
|
+
"provider": { // These are the terraform provider details that your package is for.
|
|
29
|
+
"org": "hashicorp",
|
|
30
|
+
"type": "aws",
|
|
31
|
+
"version": "1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"type": "module",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"module": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"types": "./dist/index.d.ts"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "bun ../terraform/cli/build-package",
|
|
47
|
+
"prepublishOnly": "bun run build"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@terraforge/terraform": "workspace:*",
|
|
51
|
+
"@terraforge/core": "workspace:*"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Step 2. Publish your package
|
|
57
|
+
You can now publish your package to npm or any other package registry.
|
|
58
|
+
The prepublish script will build the package files inside the `dist` directory before publishing.
|
|
59
|
+
|
|
60
|
+
### Step 3. There is no step 3
|
|
61
|
+
Sit back and relax, your package is now published and ready to be used.
|
package/cli/build-package.ts
CHANGED
|
@@ -13,25 +13,30 @@ const packageData = (await Bun.file('./package.json').json()) as {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
if (!packageData
|
|
16
|
+
if (!packageData) {
|
|
17
17
|
console.error('Failed to read package.json')
|
|
18
18
|
process.exit(1)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const providerData = packageData.provider
|
|
22
22
|
|
|
23
|
+
if (!providerData) {
|
|
24
|
+
console.error('Missing required property: provider')
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
if (!providerData.version) {
|
|
24
|
-
console.error('Missing required
|
|
29
|
+
console.error('Missing required property: provider.version')
|
|
25
30
|
process.exit(1)
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
if (!providerData.org) {
|
|
29
|
-
console.error('Missing required
|
|
34
|
+
console.error('Missing required property: provider.org')
|
|
30
35
|
process.exit(1)
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
if (!providerData.type) {
|
|
34
|
-
console.error('Missing required
|
|
39
|
+
console.error('Missing required property: provider.type')
|
|
35
40
|
process.exit(1)
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -60,29 +65,51 @@ console.log('')
|
|
|
60
65
|
console.log('Loading provider plugin...')
|
|
61
66
|
|
|
62
67
|
const plugin = await load()
|
|
68
|
+
|
|
69
|
+
console.log('Provider plugin loaded.')
|
|
70
|
+
|
|
63
71
|
const schema = plugin.schema()
|
|
64
|
-
const types = generateTypes(
|
|
65
|
-
{
|
|
66
|
-
[type]: schema.provider,
|
|
67
|
-
},
|
|
68
|
-
schema.resources,
|
|
69
|
-
schema.dataSources
|
|
70
|
-
)
|
|
71
72
|
|
|
72
73
|
await plugin.stop()
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
// const installTypes = generateInstallHelperFunctions(type)
|
|
76
|
+
// await Bun.write(`./dist/install.d.ts`, installTypes)
|
|
77
|
+
|
|
78
|
+
// const providerTypes = generateProviderFactoryTypes(type, schema.provider)
|
|
79
|
+
// await Bun.write(`./dist/provider.d.ts`, providerTypes)
|
|
80
|
+
|
|
81
|
+
// const resourceTypes = generateResourceTypes(schema.resources)
|
|
82
|
+
// await Bun.write(`./dist/resources.d.ts`, resourceTypes)
|
|
83
|
+
|
|
84
|
+
// const dataSourceTypes = generateResourceTypes(schema.dataSources)
|
|
85
|
+
// await Bun.write(`./dist/data-sources.d.ts`, dataSourceTypes)
|
|
86
|
+
|
|
87
|
+
// await Bun.write(
|
|
88
|
+
// `./dist/index.d.ts`,
|
|
89
|
+
// `
|
|
90
|
+
// /// <reference path="./install.d.ts" />
|
|
91
|
+
// /// <reference path="./provider.d.ts" />
|
|
92
|
+
// /// <reference path="./resources.d.ts" />
|
|
93
|
+
// /// <reference path="./data-sources.d.ts" />
|
|
94
|
+
|
|
95
|
+
// export { aws }
|
|
96
|
+
// `
|
|
97
|
+
// )
|
|
98
|
+
|
|
99
|
+
await Bun.write(`./dist/index.d.ts`, generateTypes(type, schema.provider, schema.resources, schema.dataSources))
|
|
100
|
+
|
|
75
101
|
await Bun.write(
|
|
76
102
|
`./dist/index.js`,
|
|
77
103
|
`
|
|
78
|
-
import {
|
|
104
|
+
import { createTerraformProxy } from '@terraforge/terraform'
|
|
79
105
|
|
|
80
|
-
export const ${type} =
|
|
106
|
+
export const ${type} = createTerraformProxy({
|
|
81
107
|
namespace: '${type}',
|
|
82
108
|
provider: { org: '${org}', type: '${type}', version: '${version}' },
|
|
83
109
|
})
|
|
84
110
|
`
|
|
85
111
|
)
|
|
86
112
|
|
|
87
|
-
console.log('
|
|
113
|
+
console.log('')
|
|
114
|
+
console.log('Package done building.')
|
|
88
115
|
process.exit(0)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Provider, State as State$1, GetProps, CreateProps, UpdateProps, DeleteProps, GetDataProps } from '@terraforge/core';
|
|
1
|
+
import { Provider, State as State$1, GetProps, CreateProps, UpdateProps, DeleteProps, PlanProps, GetDataProps } from '@terraforge/core';
|
|
2
2
|
|
|
3
3
|
type Property = {
|
|
4
4
|
description?: string;
|
|
@@ -20,7 +20,7 @@ type Property = {
|
|
|
20
20
|
type: 'unknown';
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
declare const generateTypes: (
|
|
23
|
+
declare const generateTypes: (namespace: string, provider: Property, resources: Record<string, Property>, dataSources: Record<string, Property>) => string;
|
|
24
24
|
|
|
25
25
|
type State = Record<string, unknown>;
|
|
26
26
|
type Plugin = Readonly<{
|
|
@@ -66,6 +66,11 @@ declare class TerraformProvider implements Provider {
|
|
|
66
66
|
state: State;
|
|
67
67
|
}>;
|
|
68
68
|
deleteResource({ type, state }: DeleteProps): Promise<void>;
|
|
69
|
+
planResourceChange({ type, priorState, proposedState }: PlanProps): Promise<{
|
|
70
|
+
version: number;
|
|
71
|
+
requiresReplacement: boolean;
|
|
72
|
+
state: State;
|
|
73
|
+
}>;
|
|
69
74
|
getData({ type, state }: GetDataProps): Promise<{
|
|
70
75
|
state: State;
|
|
71
76
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -3,49 +3,56 @@ import { camelCase, pascalCase } from "change-case";
|
|
|
3
3
|
var tab = (indent) => {
|
|
4
4
|
return " ".repeat(indent);
|
|
5
5
|
};
|
|
6
|
-
var generateTypes = (
|
|
6
|
+
var generateTypes = (namespace, provider, resources, dataSources) => {
|
|
7
7
|
return [
|
|
8
8
|
generateImport("c", "@terraforge/core"),
|
|
9
9
|
generateImport("t", "@terraforge/terraform"),
|
|
10
10
|
"type _Record<T> = Record<string, T>",
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}),
|
|
16
|
-
generateNamespace(resources, (name, prop, indent) => {
|
|
17
|
-
const typeName = pascalCase(name);
|
|
18
|
-
return [
|
|
19
|
-
// `${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
|
|
20
|
-
// `${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
|
|
21
|
-
// `${tab(indent)}export declare const ${typeName}: ResourceClass<${typeName}Input, ${typeName}Output>`,
|
|
22
|
-
`${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
|
|
23
|
-
`${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
|
|
24
|
-
`${tab(indent)}export class ${typeName} {`,
|
|
25
|
-
`${tab(indent + 1)}constructor(parent: c.Group, id: string, props: ${typeName}Input, config?:c.ResourceConfig)`,
|
|
26
|
-
// `${tab(indent + 1)}readonly $: c.ResourceMeta<${typeName}Input, ${typeName}Output>`,
|
|
27
|
-
generateClassProperties(prop, indent + 1),
|
|
28
|
-
`${tab(indent)}}`
|
|
29
|
-
].join("\n\n");
|
|
30
|
-
}),
|
|
31
|
-
generateNamespace(dataSources, (name, prop, indent) => {
|
|
32
|
-
const typeName = pascalCase(name);
|
|
33
|
-
return [
|
|
34
|
-
`${tab(indent)}export type Get${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
|
|
35
|
-
`${tab(indent)}export type Get${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
|
|
36
|
-
`${tab(indent)}export const get${typeName}:c.DataSourceFunction<Get${typeName}Input, Get${typeName}Output>`
|
|
37
|
-
].join("\n\n");
|
|
38
|
-
})
|
|
11
|
+
generateInstallHelperFunctions(namespace),
|
|
12
|
+
generateProviderFactoryTypes(namespace, provider),
|
|
13
|
+
generateResourceTypes(resources),
|
|
14
|
+
generateDataSourceTypes(dataSources)
|
|
39
15
|
].join("\n\n");
|
|
40
16
|
};
|
|
17
|
+
var generateResourceTypes = (resources) => {
|
|
18
|
+
return generateNamespace(resources, (name, prop, indent) => {
|
|
19
|
+
const typeName = pascalCase(name);
|
|
20
|
+
return [
|
|
21
|
+
`${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
|
|
22
|
+
`${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
|
|
23
|
+
`${tab(indent)}export class ${typeName} {`,
|
|
24
|
+
`${tab(indent + 1)}constructor(parent: c.Group, id: string, props: ${typeName}Input, config?:c.ResourceConfig)`,
|
|
25
|
+
generateClassProperties(prop, indent + 1),
|
|
26
|
+
`${tab(indent)}}`
|
|
27
|
+
].join("\n\n");
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
var generateDataSourceTypes = (dataSources) => {
|
|
31
|
+
return generateNamespace(dataSources, (name, prop, indent) => {
|
|
32
|
+
const typeName = pascalCase(name);
|
|
33
|
+
return [
|
|
34
|
+
`${tab(indent)}export type Get${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
|
|
35
|
+
`${tab(indent)}export type Get${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
|
|
36
|
+
`${tab(indent)}export const get${typeName}:c.DataSourceFunction<Get${typeName}Input, Get${typeName}Output>`
|
|
37
|
+
].join("\n\n");
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
var generateProviderFactoryTypes = (namespace, provider) => {
|
|
41
|
+
const typeName = namespace.toLowerCase();
|
|
42
|
+
return `export declare function ${typeName}(props: ${generatePropertyInputConst(provider, 0)}, config?: t.TerraformProviderConfig): t.TerraformProvider`;
|
|
43
|
+
};
|
|
41
44
|
var generateImport = (name, from) => {
|
|
42
45
|
return `import * as ${name} from '${from}'`;
|
|
43
46
|
};
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
var generateInstallHelperFunctions = (namespace) => {
|
|
48
|
+
const typeName = namespace.toLowerCase();
|
|
49
|
+
return [
|
|
50
|
+
`export declare namespace ${typeName} {`,
|
|
51
|
+
`${tab(1)}export function install(props?: t.InstallProps): Promise<void>`,
|
|
52
|
+
`${tab(1)}export function uninstall(props?: t.InstallProps): Promise<void>`,
|
|
53
|
+
`${tab(1)}export function isInstalled(props?: t.InstallProps): Promise<boolean>`,
|
|
54
|
+
`}`
|
|
55
|
+
].join("\n");
|
|
49
56
|
};
|
|
50
57
|
var generatePropertyInputConst = (prop, indent) => {
|
|
51
58
|
return generateValue(prop, {
|
|
@@ -71,8 +78,8 @@ var generatePropertyInputType = (prop, indent) => {
|
|
|
71
78
|
};
|
|
72
79
|
var generatePropertyOutputType = (prop, indent) => {
|
|
73
80
|
return generateValue(prop, {
|
|
74
|
-
indent: indent + 1,
|
|
75
81
|
depth: 0,
|
|
82
|
+
indent: indent + 1,
|
|
76
83
|
wrap: (v, p, ctx) => ctx.depth === 1 ? p.optional && !p.computed ? `c.OptionalOutput<${v}>` : `c.Output<${v}>` : v,
|
|
77
84
|
filter: () => true,
|
|
78
85
|
readonly: true,
|
|
@@ -278,6 +285,15 @@ var TerraformProvider = class {
|
|
|
278
285
|
throw error;
|
|
279
286
|
}
|
|
280
287
|
}
|
|
288
|
+
async planResourceChange({ type, priorState, proposedState }) {
|
|
289
|
+
const plugin = await this.configure();
|
|
290
|
+
const result = await plugin.planResourceChange(type, priorState, proposedState);
|
|
291
|
+
return {
|
|
292
|
+
version: 0,
|
|
293
|
+
requiresReplacement: result.requiresReplace.length > 0,
|
|
294
|
+
state: result.plannedState
|
|
295
|
+
};
|
|
296
|
+
}
|
|
281
297
|
async getData({ type, state }) {
|
|
282
298
|
const plugin = await this.configure();
|
|
283
299
|
const data = await plugin.readDataSource(type, state);
|
|
@@ -1047,9 +1063,9 @@ var createPluginClient = async (props) => {
|
|
|
1047
1063
|
// src/plugin/download.ts
|
|
1048
1064
|
import { createDebugger as createDebugger2 } from "@terraforge/core";
|
|
1049
1065
|
import jszip from "jszip";
|
|
1050
|
-
import { mkdir, stat, writeFile } from "fs/promises";
|
|
1066
|
+
import { mkdir, rm, stat, writeFile } from "fs/promises";
|
|
1051
1067
|
import { homedir } from "os";
|
|
1052
|
-
import { join } from "path";
|
|
1068
|
+
import { dirname, join } from "path";
|
|
1053
1069
|
|
|
1054
1070
|
// src/plugin/registry.ts
|
|
1055
1071
|
import { arch, platform } from "os";
|
|
@@ -1136,15 +1152,33 @@ var exists = async (file) => {
|
|
|
1136
1152
|
};
|
|
1137
1153
|
var debug2 = createDebugger2("Downloader");
|
|
1138
1154
|
var installPath = join(homedir(), ".terraforge", "plugins");
|
|
1155
|
+
var getInstallPath = (props) => {
|
|
1156
|
+
const dir = props.location ?? installPath;
|
|
1157
|
+
const file = join(dir, `${props.org}-${props.type}-${props.version}`);
|
|
1158
|
+
return file;
|
|
1159
|
+
};
|
|
1160
|
+
var isPluginInstalled = (props) => {
|
|
1161
|
+
return exists(getInstallPath(props));
|
|
1162
|
+
};
|
|
1163
|
+
var deletePlugin = async (props) => {
|
|
1164
|
+
const file = getInstallPath(props);
|
|
1165
|
+
const isAlreadyInstalled = await isPluginInstalled(props);
|
|
1166
|
+
if (isAlreadyInstalled) {
|
|
1167
|
+
debug2(props.type, "deleting...");
|
|
1168
|
+
await rm(file);
|
|
1169
|
+
debug2(props.type, "deleted");
|
|
1170
|
+
} else {
|
|
1171
|
+
debug2(props.type, "not installed");
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1139
1174
|
var downloadPlugin = async (props) => {
|
|
1140
1175
|
if (props.version === "latest") {
|
|
1141
1176
|
const { latest } = await getProviderVersions(props.org, props.type);
|
|
1142
1177
|
props.version = latest;
|
|
1143
1178
|
}
|
|
1144
|
-
const
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
if (!exist) {
|
|
1179
|
+
const file = getInstallPath(props);
|
|
1180
|
+
const isAlreadyInstalled = await isPluginInstalled(props);
|
|
1181
|
+
if (!isAlreadyInstalled) {
|
|
1148
1182
|
debug2(props.type, "downloading...");
|
|
1149
1183
|
const info = await getProviderDownloadUrl(props.org, props.type, props.version);
|
|
1150
1184
|
const res = await fetch(info.url);
|
|
@@ -1156,7 +1190,7 @@ var downloadPlugin = async (props) => {
|
|
|
1156
1190
|
}
|
|
1157
1191
|
const binary = await zipped.async("nodebuffer");
|
|
1158
1192
|
debug2(props.type, "done");
|
|
1159
|
-
await mkdir(
|
|
1193
|
+
await mkdir(dirname(file), { recursive: true });
|
|
1160
1194
|
await writeFile(file, binary, {
|
|
1161
1195
|
mode: 509
|
|
1162
1196
|
});
|
|
@@ -1863,6 +1897,8 @@ var createClassProxy = (construct, get) => {
|
|
|
1863
1897
|
var createRecursiveProxy = ({
|
|
1864
1898
|
provider,
|
|
1865
1899
|
install,
|
|
1900
|
+
uninstall,
|
|
1901
|
+
isInstalled,
|
|
1866
1902
|
resource,
|
|
1867
1903
|
dataSource
|
|
1868
1904
|
}) => {
|
|
@@ -1890,6 +1926,12 @@ var createRecursiveProxy = ({
|
|
|
1890
1926
|
if (key === "install") {
|
|
1891
1927
|
return install;
|
|
1892
1928
|
}
|
|
1929
|
+
if (key === "uninstall") {
|
|
1930
|
+
return uninstall;
|
|
1931
|
+
}
|
|
1932
|
+
if (key === "isInstalled") {
|
|
1933
|
+
return isInstalled;
|
|
1934
|
+
}
|
|
1893
1935
|
return findNextProxy([], key);
|
|
1894
1936
|
});
|
|
1895
1937
|
};
|
|
@@ -1909,6 +1951,12 @@ var createTerraformProxy = (props) => {
|
|
|
1909
1951
|
async install(installProps) {
|
|
1910
1952
|
await downloadPlugin({ ...props.provider, ...installProps });
|
|
1911
1953
|
},
|
|
1954
|
+
async uninstall(installProps) {
|
|
1955
|
+
await deletePlugin({ ...props.provider, ...installProps });
|
|
1956
|
+
},
|
|
1957
|
+
isInstalled(installProps) {
|
|
1958
|
+
return isPluginInstalled({ ...props.provider, ...installProps });
|
|
1959
|
+
},
|
|
1912
1960
|
resource: (ns, parent, id, input, config) => {
|
|
1913
1961
|
const type = snakeCase2([props.namespace, ...ns].join("_"));
|
|
1914
1962
|
const provider = `terraform:${props.namespace}:${config?.provider ?? "default"}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terraforge/terraform",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsup src/index.ts --format esm --dts --clean --out-dir ./dist",
|
|
25
|
-
"prepublishOnly": "bun run build",
|
|
25
|
+
"prepublishOnly": "if bun run test; then bun run build; else exit; fi",
|
|
26
26
|
"test": "bun test"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|