From 8f95a5c8339db6321516e77ea98744255dd62d09 Mon Sep 17 00:00:00 2001 From: David Thor Date: Thu, 14 Dec 2023 10:35:02 -0500 Subject: [PATCH] WIP: got most resources creating. Just a few more kinks to work out --- .vscode/settings.json | 4 +- bin/component-generator.ts | 3 +- bin/datacenter-generator.ts | 6 +- bin/environment-generator.ts | 5 +- bin/module-generator.ts | 126 + bin/provider-generator.ts | 40 - bin/resource-generator.ts | 10 +- deno.lock | 116 +- examples/datacenters/local/datacenter.arc | 2 - .../datacenters/local/deployment/Dockerfile | 14 +- .../local/deployment/Pulumi.module.yaml | 2 + .../datacenters/local/deployment/module.yml | 7 + .../datacenters/local/docker-build/Dockerfile | 14 +- .../datacenters/local/docker-build/module.yml | 7 + examples/datacenters/local/md5docker.sh | 7 + examples/datacenters/local/network/Dockerfile | 25 +- examples/datacenters/local/network/module.yml | 7 + .../datacenters/local/postgres-db/Dockerfile | 19 + .../datacenters/local/postgres-db/module.yml | 5 + examples/datacenters/local/secret/Dockerfile | 19 + examples/datacenters/local/secret/module.yml | 5 + examples/datacenters/local/volume/Dockerfile | 25 +- examples/datacenters/local/volume/module.yml | 7 + src/@resources/cronjob/inputs.schema.json | 5 +- src/@resources/cronjob/outputs.schema.json | 5 +- src/@resources/database/inputs.schema.json | 5 +- src/@resources/database/outputs.schema.json | 5 +- .../databaseUser/inputs.schema.json | 5 +- .../databaseUser/outputs.schema.json | 5 +- src/@resources/deployment/inputs.schema.json | 5 +- src/@resources/deployment/outputs.schema.json | 5 +- src/@resources/dockerBuild/inputs.schema.json | 5 +- .../dockerBuild/outputs.schema.json | 5 +- src/@resources/ingress/inputs.schema.json | 5 +- src/@resources/ingress/outputs.schema.json | 5 +- src/@resources/parser.ts | 6 +- src/@resources/secret/inputs.schema.json | 5 +- src/@resources/secret/outputs.schema.json | 5 +- src/@resources/service/inputs.schema.json | 5 +- src/@resources/service/outputs.schema.json | 5 +- src/@resources/types.ts | 3 +- src/@resources/volume/inputs.schema.json | 5 +- src/@resources/volume/outputs.schema.json | 5 +- src/commands/build/component.ts | 67 +- src/commands/build/datacenter.ts | 15 +- src/commands/build/module.ts | 94 +- src/commands/common/datacenter.ts | 20 +- src/commands/common/infra-renderer.ts | 4 +- src/components/component-schema.ts | 6004 ++++++++--------- src/components/schema.ts | 3 +- src/components/v1/schema.json | 2 +- src/components/v2/schema.json | 2 +- src/datacenter-modules/client.ts | 72 - src/datacenter-modules/index.ts | 3 - src/datacenter-modules/server.ts | 121 - src/datacenter-modules/types.ts | 42 - src/datacenters/datacenter-schema.ts | 2979 ++++---- src/datacenters/datacenter.schema.json | 343 +- src/datacenters/datacenter.ts | 17 +- src/datacenters/parser.ts | 2 +- src/datacenters/schema.ts | 10 +- .../v1/__tests__/datacenter.test.ts | 93 +- src/datacenters/v1/errors.ts | 2 +- src/datacenters/v1/index.ts | 43 +- src/datacenters/v1/schema.json | 343 +- src/environments/environment-schema.ts | 334 +- src/environments/parser.ts | 2 +- src/environments/schema.ts | 12 +- src/environments/v1/schema.json | 2 +- src/graphs/infra/__tests__/graph.test.ts | 1 - src/graphs/infra/node.ts | 140 +- src/modules/index.ts | 2 + src/modules/module-schema.ts | 147 + src/modules/module.schema.json | 147 + src/modules/module.ts | 74 + src/modules/parser.ts | 51 + src/modules/schema.ts | 26 + src/modules/schema.ts.stache | 32 + src/modules/v1/index.ts | 96 + src/modules/v1/schema.json | 141 + src/oci/image-manifest.ts | 2 + src/oci/image-repository.ts | 5 +- src/utils/build.ts | 62 + src/utils/command.ts | 50 +- src/utils/datacenter-store.ts | 82 - src/utils/docker.ts | 36 + src/utils/environment-store.ts | 102 - src/utils/jobs.ts | 1 - src/utils/logger.ts | 30 - src/utils/string.ts | 14 - src/utils/task-manager.ts | 61 - 91 files changed, 6227 insertions(+), 6280 deletions(-) create mode 100755 bin/module-generator.ts delete mode 100755 bin/provider-generator.ts create mode 100644 examples/datacenters/local/deployment/Pulumi.module.yaml create mode 100644 examples/datacenters/local/deployment/module.yml create mode 100644 examples/datacenters/local/docker-build/module.yml create mode 100755 examples/datacenters/local/md5docker.sh create mode 100644 examples/datacenters/local/network/module.yml create mode 100644 examples/datacenters/local/postgres-db/Dockerfile create mode 100644 examples/datacenters/local/postgres-db/module.yml create mode 100644 examples/datacenters/local/secret/Dockerfile create mode 100644 examples/datacenters/local/secret/module.yml create mode 100644 examples/datacenters/local/volume/module.yml delete mode 100644 src/datacenter-modules/client.ts delete mode 100644 src/datacenter-modules/index.ts delete mode 100644 src/datacenter-modules/server.ts delete mode 100644 src/datacenter-modules/types.ts create mode 100644 src/modules/index.ts create mode 100644 src/modules/module-schema.ts create mode 100644 src/modules/module.schema.json create mode 100644 src/modules/module.ts create mode 100644 src/modules/parser.ts create mode 100644 src/modules/schema.ts create mode 100644 src/modules/schema.ts.stache create mode 100644 src/modules/v1/index.ts create mode 100644 src/modules/v1/schema.json create mode 100644 src/utils/build.ts delete mode 100644 src/utils/datacenter-store.ts create mode 100644 src/utils/docker.ts delete mode 100644 src/utils/environment-store.ts delete mode 100644 src/utils/jobs.ts delete mode 100644 src/utils/logger.ts delete mode 100644 src/utils/string.ts delete mode 100644 src/utils/task-manager.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 40dd8f216..fee198de3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,8 +9,8 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": true + "source.fixAll": "explicit", + "source.organizeImports": "explicit" }, "prettier.enable": false, "deno.documentPreloadLimit": 0, diff --git a/bin/component-generator.ts b/bin/component-generator.ts index fc4ff4fe3..af80f5b8e 100755 --- a/bin/component-generator.ts +++ b/bin/component-generator.ts @@ -54,7 +54,7 @@ for (const version of all_versions) { args: [ 'run', '--allow-read', - 'npm:ts-json-schema-generator', + 'npm:ts-json-schema-generator@1.5.0', '--path', path.join(build_dir, 'src/components', version, 'index.ts'), '--expose', @@ -68,6 +68,7 @@ for (const version of all_versions) { stdout: 'piped', }); const type_schema = JSON.parse(type_schema_string); + type_schema.$schema = 'https://json-schema.org/draft/2019-09/schema'; await Deno.writeTextFile(path.join(components_dir, version, './schema.json'), JSON.stringify(type_schema, null, 2)); } diff --git a/bin/datacenter-generator.ts b/bin/datacenter-generator.ts index 385eaf7a1..e79ac255f 100755 --- a/bin/datacenter-generator.ts +++ b/bin/datacenter-generator.ts @@ -67,6 +67,7 @@ for (const version of all_versions) { stdout: 'piped', }); const type_schema = JSON.parse(type_schema_string); + type_schema.$schema = 'https://json-schema.org/draft/2019-09/schema'; await Deno.writeTextFile(path.join(datacenters_dir, version, './schema.json'), JSON.stringify(type_schema, null, 2)); } @@ -74,7 +75,7 @@ const { stdout: type_schema_string, stderr: err } = await exec('deno', { args: [ 'run', '--allow-read', - 'npm:ts-json-schema-generator', + 'npm:ts-json-schema-generator@1.5.0', '--path', path.join(build_dir, 'src', 'datacenters', 'schema.ts'), '--expose', @@ -97,7 +98,7 @@ let type_schema = JSON.parse(type_schema_string); if (type_schema.definitions.DatacenterSchema.anyOf) { type_schema = { oneOf: type_schema.definitions.DatacenterSchema.anyOf, - $schema: type_schema.$schema, + $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'https://architect.io/.schemas/datacenter.json', type: 'object', required: ['version'], @@ -107,6 +108,7 @@ if (type_schema.definitions.DatacenterSchema.anyOf) { }; } else { type_schema.$id = 'https://architect.io/.schemas/datacenter.json'; + type_schema.$schema = 'https://json-schema.org/draft/2019-09/schema'; } await Deno.writeTextFile(path.join(datacenters_dir, './datacenter.schema.json'), JSON.stringify(type_schema, null, 2)); diff --git a/bin/environment-generator.ts b/bin/environment-generator.ts index 4a7587622..16beff5c1 100755 --- a/bin/environment-generator.ts +++ b/bin/environment-generator.ts @@ -54,7 +54,7 @@ for (const version of all_versions) { args: [ 'run', '--allow-read', - 'npm:ts-json-schema-generator', + 'npm:ts-json-schema-generator@1.5.0', '--path', path.join(build_dir, 'src/environments', version, 'index.ts'), '--expose', @@ -68,6 +68,7 @@ for (const version of all_versions) { stdout: 'piped', }); const type_schema = JSON.parse(type_schema_string); + type_schema.$schema = 'https://json-schema.org/draft/2019-09/schema'; await Deno.writeTextFile(path.join(environments_dir, version, './schema.json'), JSON.stringify(type_schema, null, 2)); } @@ -75,7 +76,7 @@ const { stdout: type_schema_string, stderr: err } = await exec('deno', { args: [ 'run', '--allow-read', - 'npm:ts-json-schema-generator', + 'npm:ts-json-schema-generator@1.5.0', '--path', path.join(build_dir, 'src/environments/schema.ts'), '--expose', diff --git a/bin/module-generator.ts b/bin/module-generator.ts new file mode 100755 index 000000000..593656180 --- /dev/null +++ b/bin/module-generator.ts @@ -0,0 +1,126 @@ +import { build, emptyDir } from 'dnt'; +import Mustache from 'npm:mustache'; +import * as path from 'std/path/mod.ts'; +import { exec } from '../src/utils/command.ts'; + +const __dirname = new URL('.', import.meta.url).pathname; +const modules_dir = path.join(__dirname, '../src/modules'); +const build_dir = path.join(__dirname, 'build'); + +await emptyDir(build_dir); + +const all_versions = []; +for await (const dirEntry of Deno.readDir(modules_dir)) { + if (dirEntry.isDirectory && dirEntry.name !== '__tests__') { + all_versions.push(dirEntry.name); + } +} +all_versions.sort((a, b) => a.localeCompare(b)); + +// Create the updated schema.ts file for all available schemas. +Deno.writeTextFile( + path.join(modules_dir, 'schema.ts'), + Mustache.render(await Deno.readTextFile(path.join(modules_dir, 'schema.ts.stache')), { + versions: all_versions, + }), +); + +// Builds the schema into an npm package. This will convert files to .js and .d.ts with +// Deno shims so that "ts-json-schema-generator" can be run on it and infer types properly. +await build({ + typeCheck: false, + test: false, + scriptModule: false, + entryPoints: [path.join(modules_dir, 'schema.ts')], + outDir: build_dir, + compilerOptions: { + lib: ['ES2022'], + target: 'ES2020', + }, + shims: { + deno: true, + }, + package: { + name: 'datacenter-module-schema', + version: '0.0.1', + description: 'module schema transpilation', + }, +}); + +console.log('Finishing building temp package, generating JSON schema...'); + +for (const version of all_versions) { + const { stdout: type_schema_string } = await exec('deno', { + args: [ + 'run', + '--allow-read', + 'npm:ts-json-schema-generator@1.5.0', + '--path', + path.join(build_dir, 'src/modules', version, 'index.ts'), + '--expose', + 'none', + '--type', + 'DatacenterModule' + version.toUpperCase(), + '--tsconfig', + path.join(__dirname, '../tsconfig.json'), + '--no-type-check', + ], + stdout: 'piped', + }); + const type_schema = JSON.parse(type_schema_string); + type_schema.$schema = 'https://json-schema.org/draft/2019-09/schema'; + await Deno.writeTextFile(path.join(modules_dir, version, './schema.json'), JSON.stringify(type_schema, null, 2)); +} + +const { stdout: type_schema_string, stderr: err } = await exec('deno', { + args: [ + 'run', + '--allow-read', + 'npm:ts-json-schema-generator@1.5.0', + '--path', + path.join(build_dir, 'src/modules/schema.ts'), + '--expose', + 'none', + '--type', + 'DatacenterModuleSchema', + '--tsconfig', + path.join(__dirname, '../tsconfig.json'), + '--no-type-check', + ], +}); + +if (err.length > 0) { + console.error('Failed to generate module schema') + console.error(err); + Deno.exit(1); +} + +let type_schema = JSON.parse(type_schema_string); +if (type_schema.definitions.DatacenterModuleSchema.anyOf) { + type_schema = { + oneOf: type_schema.definitions.DatacenterModuleSchema.anyOf, + $schema: 'https://json-schema.org/draft/2019-09/schema', + $id: 'https://architect.io/.schemas/module.json', + type: 'object', + required: ['version'], + discriminator: { + propertyName: 'version', + }, + }; +} else { + type_schema.$schema = 'https://json-schema.org/draft/2019-09/schema'; + type_schema.$id = 'https://architect.io/.schemas/module.json'; +} + +await Deno.writeTextFile( + path.join(modules_dir, './module.schema.json'), + JSON.stringify(type_schema, null, 2), +); + +await Deno.writeTextFile( + path.join(modules_dir, './module-schema.ts'), + `export default ${JSON.stringify(type_schema, null, 2)}`, +); +console.log(`Done! Updated schema is located at ${path.join(modules_dir, './module.schema.json')}`); + +Deno.removeSync(build_dir, { recursive: true }); diff --git a/bin/provider-generator.ts b/bin/provider-generator.ts deleted file mode 100755 index f19cd0e40..000000000 --- a/bin/provider-generator.ts +++ /dev/null @@ -1,40 +0,0 @@ -import Mustache from 'npm:mustache'; -import * as path from 'std/path/mod.ts'; - -const __dirname = new URL('.', import.meta.url).pathname; -const providers_dir = path.join(__dirname, '../src/@providers/'); - -const all_providers = []; -for await (const dirEntry of Deno.readDir(providers_dir)) { - if (dirEntry.isDirectory) { - all_providers.push(dirEntry.name); - } -} - -all_providers.sort((a, b) => a.localeCompare(b)); - -type ProviderTypeFileOptions = { - provider_list: { - name: string; - slug: string; - }[]; -}; - -const provider_type_file_options: ProviderTypeFileOptions = { - provider_list: [], -}; - -for (const type of all_providers) { - provider_type_file_options.provider_list.push({ - name: type.replace(/-([\dA-Za-z])/g, (g) => g[1].toUpperCase()), - slug: type, - }); -} - -Deno.writeTextFileSync( - path.join(providers_dir, 'supported-providers.ts'), - Mustache.render( - await Deno.readTextFile(path.join(providers_dir, 'supported-providers.ts.stache')), - provider_type_file_options, - ), -); diff --git a/bin/resource-generator.ts b/bin/resource-generator.ts index ec953be5a..9b35454e5 100755 --- a/bin/resource-generator.ts +++ b/bin/resource-generator.ts @@ -58,7 +58,7 @@ for (const type of all_types) { args: [ 'run', '--allow-read', - 'npm:ts-json-schema-generator', + 'npm:ts-json-schema-generator@1.5.0', '--path', path.join(build_dir, 'src', type.name, 'inputs.ts'), '--type', @@ -70,6 +70,9 @@ for (const type of all_types) { }); const typeSchema = JSON.parse(typeSchemaString); + typeSchema.$schema = 'https://json-schema.org/draft/2019-09/schema'; + typeSchema.$id = 'https://architect.io/.schemas/resources/' + type.name + '/inputs.json'; + await Deno.writeTextFile( path.join(resources_dir, type.name, './inputs.schema.json'), JSON.stringify(typeSchema, null, 2), @@ -84,7 +87,7 @@ for (const type of all_types) { args: [ 'run', '--allow-read', - 'npm:ts-json-schema-generator', + 'npm:ts-json-schema-generator@1.5.0', '--path', path.join(build_dir, 'src', type.name, 'outputs.ts'), '--type', @@ -96,6 +99,9 @@ for (const type of all_types) { }); const typeSchema = JSON.parse(typeSchemaString); + typeSchema.$schema = 'https://json-schema.org/draft/2019-09/schema'; + typeSchema.$id = 'https://architect.io/.schemas/resources/' + type.name + '/outputs.json'; + await Deno.writeTextFile( path.join(resources_dir, type.name, './outputs.schema.json'), JSON.stringify(typeSchema, null, 2), diff --git a/deno.lock b/deno.lock index 0024314f4..1a12181c7 100644 --- a/deno.lock +++ b/deno.lock @@ -6,11 +6,12 @@ "npm:@types/pg@^8.6.5": "npm:@types/pg@8.10.9", "npm:mustache": "npm:mustache@4.2.0", "npm:pg@8.8.0": "npm:pg@8.8.0", - "npm:ts-json-schema-generator": "npm:ts-json-schema-generator@1.2.0" + "npm:ts-json-schema-generator": "npm:ts-json-schema-generator@1.5.0", + "npm:ts-json-schema-generator@1.5.0": "npm:ts-json-schema-generator@1.5.0" }, "npm": { - "@types/json-schema@7.0.12": { - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dependencies": {} }, "@types/node@18.16.19": { @@ -39,8 +40,8 @@ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", "dependencies": {} }, - "commander@9.5.0": { - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "commander@11.1.0": { + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dependencies": {} }, "fs.realpath@1.0.0": { @@ -210,20 +211,20 @@ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dependencies": {} }, - "ts-json-schema-generator@1.2.0": { - "integrity": "sha512-tUMeO3ZvA12d3HHh7T/AK8W5hmUhDRNtqWRHSMN3ZRbUFt+UmV0oX8k1RK4SA+a+BKNHpmW2v06MS49e8Fi3Yg==", + "ts-json-schema-generator@1.5.0": { + "integrity": "sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==", "dependencies": { - "@types/json-schema": "@types/json-schema@7.0.12", - "commander": "commander@9.5.0", + "@types/json-schema": "@types/json-schema@7.0.15", + "commander": "commander@11.1.0", "glob": "glob@8.1.0", "json5": "json5@2.2.3", "normalize-path": "normalize-path@3.0.0", "safe-stable-stringify": "safe-stable-stringify@2.4.3", - "typescript": "typescript@4.9.5" + "typescript": "typescript@5.3.3" } }, - "typescript@4.9.5": { - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "typescript@5.3.3": { + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dependencies": {} }, "wrappy@1.0.2": { @@ -612,22 +613,7 @@ "https://deno.land/std@0.192.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f", "https://deno.land/std@0.192.0/testing/bdd.ts": "59f7f7503066d66a12e50ace81bfffae5b735b6be1208f5684b630ae6b4de1d0", "https://deno.land/std@0.192.0/testing/mock.ts": "220ed9b8151cb2cac141043d4cfea7c47673fab5d18d1c1f0943297c8afb5d13", - "https://deno.land/std@0.195.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.195.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.195.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", "https://deno.land/std@0.195.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d", - "https://deno.land/std@0.195.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", - "https://deno.land/std@0.195.0/fs/copy.ts": "b4f7fe87190d7b310c88a2d9ff845210c0a2b7b0a094ec509747359023beb7d6", - "https://deno.land/std@0.195.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", - "https://deno.land/std@0.195.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.195.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.195.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.195.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.195.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.195.0/path/mod.ts": "f065032a7189404fdac3ad1a1551a9ac84751d2f25c431e101787846c86c79ef", - "https://deno.land/std@0.195.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.195.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.195.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12", "https://deno.land/std@0.50.0/path/_constants.ts": "f6c332625f21d49d5a69414ba0956ac784dbf4b26a278041308e4914ba1c7e2e", "https://deno.land/std@0.50.0/path/_util.ts": "b678a7ecbac6b04c1166832ae54e1024c0431dd2b340b013c46eb2956ab24d4c", "https://deno.land/std@0.50.0/path/interface.ts": "89f6e68b0e3bba1401a740c8d688290957de028ed86f95eafe76fe93790ae450", @@ -821,82 +807,6 @@ "https://esm.sh/v124/acorn-loose@8.4.0/X-ZS8q/denonext/acorn-loose.mjs": "14c27dc175609579a9c52b3003f86a1a0b1f06db4197720cbdb84a1cf1ca98f7", "https://esm.sh/v124/acorn@8.11.0": "5c027fd61ad3c4c00a3d19ea92587e07bcc044dd0ce4604ff9c15fcd10507e83", "https://esm.sh/v124/acorn@8.11.0/denonext/acorn.mjs": "5cfa11057423a0a2ce11224cf82f6c5616e12c72c48b3362ba8b778292872f54", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/2019.js": "46e3436284f2efe40b2c6b6d072793283f2f06a6c7592505785122f364ea64a1", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile.js": "1378daa49af3b45021e3b8d40d3a3180da04e50241714cdc0de444f84d2b9eab", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/codegen.js": "092106f381af11db8a32f3df3a9cb8d2794d4865ede8786f045db00d6fb46206", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/codegen/code.js": "d467da34c2949832b4368bb3aa9c0abff077b04bb30f5a475317d3dff476a73a", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/codegen/scope.js": "ec8cf20cad6c485e34116974856b785224a9732d7adad085b05e0ec820065a67", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/errors.js": "b1dd7b46143e06c44f505110bfa073803b6e6bbaa01b72a32a780c86ea13193a", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/names.js": "411531e4dca8bee76dd868468b51701e99fec99a2ce8023f50eecbff0aa2ee6e", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/ref_error.js": "a3aaa89f0323cca4089c9762ee2e3b353d3f89bb871990cea91d3d6da3432a79", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/resolve.js": "0ab575b72e1ab9f1a79b14ca51f81af932a4002c07a43851f300e2cbe1de69ad", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/rules.js": "9a059833a0a38366b807d371fa2dc65e387f5badde34b370e1abc4710bd7917f", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/util.js": "c64f495f88dcc9b6af1e03f31a849235e4310470a74d5ec44a84944307e3f9cc", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate.js": "6484462b77df6320117afbbd0edbdb3208b0c89ae8d164f05810a46ab8dcfed6", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate/applicability.js": "d50d084b87afb8a03f88a5faffb3c95e7a89f0377420336e9432a9bdd01d8b65", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate/boolSchema.js": "5bb50ab8f73f675414cfb5ab5a468a41d8e9ec3dc4ba4f628c55c535ae203175", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate/dataType.js": "4311b4c8b478b448f666c948afbf2738614f4a5df17eb7cbabd4e161885fa7ed", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate/defaults.js": "a4a72dac5bbeb80440a267e580226184e4e392be65111785921b3b919d7af865", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate/keyword.js": "35ce4ae2365f87f665754eb4e9f1c6c10fc540c812566009e269207f454e11fd", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/compile/validate/subschema.js": "2e0e570955bef26c6030d9baa5b1a824574866041a3fd5b2201c4eb5686fb01f", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/core.js": "0d6670e6b6de3387ed2a6cd2fe7e7ec836498a353701852ef2ff7e1363cb780d", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/refs/json-schema-2019-09.js": "d8ebc7fc774215a297fc804cf8fb91ed8061955faf63ec643eb8e21c6c687046", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/runtime/equal.js": "67c313860f2ac014da284e4b1ffc453f7c537a459f57e0cddd1dc93208d45262", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/runtime/ucs2length.js": "c1808bcef70a0da2f0f4074de8ad827184db50fa082896770fb63e0f9120f5f7", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/runtime/uri.js": "35c9c5472336b11f43fbc3658b747dfefbd081bef1118071f68316a2acb55e9e", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/runtime/validation_error.js": "291b014d16c4dd5f68317c3c8131d2b426573b3238033c27c0277701b5c7f9aa", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator.js": "37d6d557cb42c6aaad1b98ddd58362eba628c5fae202aa46ce75535c19f92a18", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/additionalItems.js": "67685be156c248532af5c64f08bdf216f0fc4f213974f66bd5fc74513292ad09", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/additionalProperties.js": "9d031ad70e1dba07aeba5f8ef7ce1fd4377c2a5efe05e86b09139ae8aeb925bf", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/allOf.js": "430e7d85a9cb124d0f845fb0e3e4bcafb0e6475925319e8c4ebd390d0d936543", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/anyOf.js": "35ea0062ed5dbf13c8c7b4add66665ad46abc5072c8812ac0e6df6b4ef144069", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/contains.js": "86a23d3497ce1f7fd1f9cba9a109e199a26d8d3ec77878610bb41236ff0b059d", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/dependencies.js": "150bb6d810728b4811018296a7bd080fe52ec117905b29612446c3dac679a04d", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/dependentSchemas.js": "44c848a508b01659e495fcbab90c6dc9ff49541e5990db2d2fb5301edb83cd1e", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/if.js": "26b17db767c24f5f58c09ff02643b4c945c9e58f05fdc25b2d4383b6917fd753", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/items.js": "c74a01d2f70b492f30403c5f934ec16d86aa97d22a59621519ee02ea08d22c65", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/items2020.js": "601a0437ba54ef2a0802c4a331e5eab86248bc8f71be44fdb53de3933831b65e", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/not.js": "f7e8cb2c5baae656d0b0dde86331704de39f0ce5db08bf5ceb445a9c943e5842", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/oneOf.js": "5014fb39cecb2e1b9f300cb6f23d3fd41336e75073244f587627b7ca7bb1dbe1", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/patternProperties.js": "38487dd941f835c5803e1f3ff2e1e09ac01883b8ee15a68d07271942d37675c3", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/prefixItems.js": "e7fa9bd524ddd784b689b1badfbde411d66808ddec04e42237c34177fb334393", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/properties.js": "17df86946d32b28cafd712a56bf7eaab36cef5243cbe4c1600a31e3d2f4fd5f8", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/propertyNames.js": "ad89ab1d78dd5dad2cdcc3b986bdf5b2a8e734d59e41897cec5d05977dbca8a5", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/applicator/thenElse.js": "431bb9e7f39f091ff7a8b16b953b48a8e7e8b229ad3193662062764d51cc9d2c", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/code.js": "68439a222c5e621689018edae27adc87f9d6a15aac5eda8e6d62a3c14395ee39", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/core.js": "f0e7debbb2aeb4b85a3e994eaaa4a6651b1f14da7cac6f732a56d4935790fb38", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/core/id.js": "7e22249fb857b03eb525f1d0af5a1a7c34a926a244ab7b1b74a8fb66c78a9ba3", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/core/ref.js": "7b5dbe5f060d693cb2343b8f4d6d6a72e9dcdb229649c20a6476ade4907ca116", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/discriminator.js": "a3cabc58d1c7f70910c2daf1a3494b8e8c9659f4622feb26c4e1bcf8ae55512f", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/discriminator/types.js": "6bdf5bb4b0d3a0ff7d682b771ba74b35ae8fc6bed5d51f51bc169e142fb96754", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/draft7.js": "bc8956f43e43f0e9d922915a2187e44589a834ce95816ee7409d947820a423df", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/dynamic.js": "76f2cfdc2e2fcfb440a8bf5ee204aa09be2abb3fbad460b26d9f202b7d72953f", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/dynamic/dynamicAnchor.js": "32a9dc1bc30d4a5d4e27a3f9f72232bfcaaa90345274f759a769976c15f43446", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/dynamic/dynamicRef.js": "0e2d40bec486a8dda2735477800a841fc91a2374112d2a7d015c29a4c723a8b8", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/dynamic/recursiveAnchor.js": "11c54c6121e80064c2faefcb569296856a185f5b4b49983e6964f4c44c170e72", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/dynamic/recursiveRef.js": "54bec184ce6bcb550bbeda4c430cde54defa880baa314c1c390ec870a9d4317b", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/format.js": "83cff2241b50109cf4f38babed8a2c46a80479c347370ca409a80bd79352b9f2", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/format/format.js": "48ec551d8a99b3feeaeb2f63131e39d0a0f54539edd825233dd844a0bde446b4", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/metadata.js": "34d26433134bd71ec016e42393d2157f1d18b3f64244b041bb8f474c3f17e5e8", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/next.js": "8c7f43aee25741fbf1d2038c3a70dc708cbd90ec954c7675e025fc15de0739ee", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/unevaluated.js": "f8da742c2256a94832917852f6d728474e931256fb51b3b0f7be2e05e31d4626", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/unevaluated/unevaluatedItems.js": "a9baa444bb623742fabdcd544a962e5f1830ce6ac163235829df40030423f69b", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/unevaluated/unevaluatedProperties.js": "0e792a2348a8f3bdce88c0e052ae234b28e914d4dd7a374f3b36dcd65f24a60a", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation.js": "4c6ddcf2cc75b748b9f1558985d8324e5291e1e1de980f1fb8295ce81aa299e2", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/const.js": "30fd3e9ac9bea4b624ebee5d035089e2017148d787fec11f88be5d89bcdce43d", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/dependentRequired.js": "f0030eeeb06e9344ede95f88790f0e0910ee5befb21449493725abffb6a5363d", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/enum.js": "16270f5b5f868c9a2daa4c1287b146d3cbbb07f6aa9ee186a6d8506bddfb259b", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/limitContains.js": "92267764743650aa11a90e523fc5f08cf4105c3a8c562a4d469c3f719ec17a03", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/limitItems.js": "bb7b91b0c0fd7c8743aa2a752beb9064b1351c2fbbabae6ed4bf7253e33ca16d", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/limitLength.js": "6eac7a3eaf9993c2abfc54a1fcd6590ce002ad6b813c4b6e639e41d5e7273300", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/limitNumber.js": "76949d424bce959d70c8f0d37408b4426424d762bc9b9803a9445b1dca7093ea", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/limitProperties.js": "ee2041d31122907a584a703781ad240181fb53bf88d828c14b98a004be441547", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/multipleOf.js": "2fd75abe7efe7e8c8fd3f7a2cc1f65d0a960687a4ca2dff0d040368f0b701249", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/pattern.js": "1b8d65fdf083c30992901ab9558d1bede4f3605eaa795d81472bc5ef2c58fbc9", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/required.js": "945988a3e33e21322f240fa5304a3dbbb4a80b2cf8c56032bfd27df9294c9793", - "https://esm.sh/v124/ajv@8.11.0/denonext/dist/vocabularies/validation/uniqueItems.js": "77bf5a6f1bdd56129e0a217865e9d2beb2fd6623a6b288cc4b5af93c49cad69b", - "https://esm.sh/v124/ajv@8.11.0/dist/2019.js": "646df403ae1a0d94045d530fe3b7ca65e4bd4a1163e6041d34bc9d321bb7334f", - "https://esm.sh/v124/ajv@8.11.0/dist/core": "7af19c4e1b07edc97c97d9a4e1b2859154d626fc9d10b618c8fda3b178ce26c0", "https://esm.sh/v124/ajv@8.12.0": "7621f26ca63271c26d3c0d3812be6a826809bb56036db0bf3ffb3e82bb7482d1", "https://esm.sh/v124/ajv@8.12.0/denonext/ajv.mjs": "dd2ce45ec01492d9d6bf91bc6bf0e2608fc8977d70cc4bbc0d2d7c1453d514b9", "https://esm.sh/v124/ajv@8.12.0/denonext/dist/2019.js": "26aae2b10cb7a43fe6527beae125ade89406357ddb58019e63d3414ca7c6a3f2", diff --git a/examples/datacenters/local/datacenter.arc b/examples/datacenters/local/datacenter.arc index e4927446b..4f5bb51b1 100644 --- a/examples/datacenters/local/datacenter.arc +++ b/examples/datacenters/local/datacenter.arc @@ -73,7 +73,6 @@ environment { module "database" { build = "./postgres-db" - plugin = "opentofu" volume { host_path = "/var/run/docker.sock" @@ -153,7 +152,6 @@ environment { secret { module "secret" { build = "./secret" - plugin = "opentofu" inputs = { filename = "${var.secretsDir}--${environment.name}--${node.component}--${node.name}.json" content = node.inputs.data diff --git a/examples/datacenters/local/deployment/Dockerfile b/examples/datacenters/local/deployment/Dockerfile index 5d88633db..37ee780ca 100644 --- a/examples/datacenters/local/deployment/Dockerfile +++ b/examples/datacenters/local/deployment/Dockerfile @@ -2,15 +2,25 @@ FROM docker RUN apk add --update nodejs npm curl git ca-certificates -RUN wget https://get.pulumi.com/releases/sdk/pulumi-v3.92.0-linux-x64.tar.gz && \ +RUN wget https://get.pulumi.com/releases/sdk/pulumi-v3.96.2-linux-x64.tar.gz && \ mkdir -p /tmp/pulumi && \ - tar zxf pulumi-v3.92.0-linux-x64.tar.gz -C /tmp/pulumi && \ + tar zxf pulumi-v3.96.2-linux-x64.tar.gz -C /tmp/pulumi && \ mkdir -p /app/.pulumi/bin && \ cp /tmp/pulumi/pulumi/* /app/.pulumi/bin/ + +RUN wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 && \ + mv jq-linux-amd64 /usr/local/bin/jq && \ + chmod +x /usr/local/bin/jq + ENV PATH=$PATH:/app/.pulumi/bin/ +WORKDIR /app + COPY . . RUN npm install +ENV PULUMI_CONFIG_PASSPHRASE="passphrase" + +ENTRYPOINT [] CMD ["pulumi"] \ No newline at end of file diff --git a/examples/datacenters/local/deployment/Pulumi.module.yaml b/examples/datacenters/local/deployment/Pulumi.module.yaml new file mode 100644 index 000000000..926852e39 --- /dev/null +++ b/examples/datacenters/local/deployment/Pulumi.module.yaml @@ -0,0 +1,2 @@ +config: + deployment:key: '"value"' diff --git a/examples/datacenters/local/deployment/module.yml b/examples/datacenters/local/deployment/module.yml new file mode 100644 index 000000000..068a6ef9b --- /dev/null +++ b/examples/datacenters/local/deployment/module.yml @@ -0,0 +1,7 @@ +init: pulumi login --local && pulumi stack init module --non-interactive +plan: pulumi preview --stack module --non-interactive +import: pulumi stack import --stack module --file $STATE_FILE --non-interactive +export: pulumi stack export --stack module --file $STATE_FILE --non-interactive +outputs: pulumi stack output --stack module --json --show-secrets --non-interactive > $OUTPUT_FILE +apply: pulumi config --stack module --non-interactive set-all $(echo $INPUTS | jq -r "to_entries | map(\"--path --plaintext \" + .key + \"=\" + (.value | tostring)) | join(\" \")") && pulumi up --stack module --non-interactive --yes +destroy: pulumi destroy --stack module --non-interactive --yes diff --git a/examples/datacenters/local/docker-build/Dockerfile b/examples/datacenters/local/docker-build/Dockerfile index 5d88633db..37ee780ca 100644 --- a/examples/datacenters/local/docker-build/Dockerfile +++ b/examples/datacenters/local/docker-build/Dockerfile @@ -2,15 +2,25 @@ FROM docker RUN apk add --update nodejs npm curl git ca-certificates -RUN wget https://get.pulumi.com/releases/sdk/pulumi-v3.92.0-linux-x64.tar.gz && \ +RUN wget https://get.pulumi.com/releases/sdk/pulumi-v3.96.2-linux-x64.tar.gz && \ mkdir -p /tmp/pulumi && \ - tar zxf pulumi-v3.92.0-linux-x64.tar.gz -C /tmp/pulumi && \ + tar zxf pulumi-v3.96.2-linux-x64.tar.gz -C /tmp/pulumi && \ mkdir -p /app/.pulumi/bin && \ cp /tmp/pulumi/pulumi/* /app/.pulumi/bin/ + +RUN wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 && \ + mv jq-linux-amd64 /usr/local/bin/jq && \ + chmod +x /usr/local/bin/jq + ENV PATH=$PATH:/app/.pulumi/bin/ +WORKDIR /app + COPY . . RUN npm install +ENV PULUMI_CONFIG_PASSPHRASE="passphrase" + +ENTRYPOINT [] CMD ["pulumi"] \ No newline at end of file diff --git a/examples/datacenters/local/docker-build/module.yml b/examples/datacenters/local/docker-build/module.yml new file mode 100644 index 000000000..068a6ef9b --- /dev/null +++ b/examples/datacenters/local/docker-build/module.yml @@ -0,0 +1,7 @@ +init: pulumi login --local && pulumi stack init module --non-interactive +plan: pulumi preview --stack module --non-interactive +import: pulumi stack import --stack module --file $STATE_FILE --non-interactive +export: pulumi stack export --stack module --file $STATE_FILE --non-interactive +outputs: pulumi stack output --stack module --json --show-secrets --non-interactive > $OUTPUT_FILE +apply: pulumi config --stack module --non-interactive set-all $(echo $INPUTS | jq -r "to_entries | map(\"--path --plaintext \" + .key + \"=\" + (.value | tostring)) | join(\" \")") && pulumi up --stack module --non-interactive --yes +destroy: pulumi destroy --stack module --non-interactive --yes diff --git a/examples/datacenters/local/md5docker.sh b/examples/datacenters/local/md5docker.sh new file mode 100755 index 000000000..b4debb09f --- /dev/null +++ b/examples/datacenters/local/md5docker.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +docker create $1 | { + read cid + docker export $cid | tar Oxv 2>&1 | shasum -a 256 + docker rm $cid > /dev/null +} \ No newline at end of file diff --git a/examples/datacenters/local/network/Dockerfile b/examples/datacenters/local/network/Dockerfile index c2cb0473b..37ee780ca 100644 --- a/examples/datacenters/local/network/Dockerfile +++ b/examples/datacenters/local/network/Dockerfile @@ -1,5 +1,26 @@ -FROM pulumi/pulumi-nodejs +FROM docker + +RUN apk add --update nodejs npm curl git ca-certificates + +RUN wget https://get.pulumi.com/releases/sdk/pulumi-v3.96.2-linux-x64.tar.gz && \ + mkdir -p /tmp/pulumi && \ + tar zxf pulumi-v3.96.2-linux-x64.tar.gz -C /tmp/pulumi && \ + mkdir -p /app/.pulumi/bin && \ + cp /tmp/pulumi/pulumi/* /app/.pulumi/bin/ + +RUN wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 && \ + mv jq-linux-amd64 /usr/local/bin/jq && \ + chmod +x /usr/local/bin/jq + +ENV PATH=$PATH:/app/.pulumi/bin/ + +WORKDIR /app COPY . . -RUN npm install \ No newline at end of file +RUN npm install + +ENV PULUMI_CONFIG_PASSPHRASE="passphrase" + +ENTRYPOINT [] +CMD ["pulumi"] \ No newline at end of file diff --git a/examples/datacenters/local/network/module.yml b/examples/datacenters/local/network/module.yml new file mode 100644 index 000000000..068a6ef9b --- /dev/null +++ b/examples/datacenters/local/network/module.yml @@ -0,0 +1,7 @@ +init: pulumi login --local && pulumi stack init module --non-interactive +plan: pulumi preview --stack module --non-interactive +import: pulumi stack import --stack module --file $STATE_FILE --non-interactive +export: pulumi stack export --stack module --file $STATE_FILE --non-interactive +outputs: pulumi stack output --stack module --json --show-secrets --non-interactive > $OUTPUT_FILE +apply: pulumi config --stack module --non-interactive set-all $(echo $INPUTS | jq -r "to_entries | map(\"--path --plaintext \" + .key + \"=\" + (.value | tostring)) | join(\" \")") && pulumi up --stack module --non-interactive --yes +destroy: pulumi destroy --stack module --non-interactive --yes diff --git a/examples/datacenters/local/postgres-db/Dockerfile b/examples/datacenters/local/postgres-db/Dockerfile new file mode 100644 index 000000000..35cc3d9f0 --- /dev/null +++ b/examples/datacenters/local/postgres-db/Dockerfile @@ -0,0 +1,19 @@ +# This dockerfile is used to build OpenTofu modules - the module files are copied in and terraform is installed +# so that the plugin can use terraform commands to plan/apply the module. +FROM alpine:3 + +# Needed for terraform modules that include a remote source +RUN apk add --no-cache git + +RUN wget https://github.com/opentofu/opentofu/releases/download/v1.6.0-beta3/tofu_1.6.0-beta3_amd64.apk +RUN apk add --allow-untrusted tofu_1.6.0-beta3_amd64.apk + +RUN wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 && \ + mv jq-linux-amd64 /usr/local/bin/jq && \ + chmod +x /usr/local/bin/jq + +WORKDIR /app + +COPY . . + +ENTRYPOINT [] \ No newline at end of file diff --git a/examples/datacenters/local/postgres-db/module.yml b/examples/datacenters/local/postgres-db/module.yml new file mode 100644 index 000000000..6675554f1 --- /dev/null +++ b/examples/datacenters/local/postgres-db/module.yml @@ -0,0 +1,5 @@ +init: tofu init +plan: tofu plan -input=false -state=$STATE_FILE -detailed-exitcode +outputs: tofu output -state=$STATE_FILE -json +apply: VARS=$(echo $INPUTS | jq -r "to_entries | map(\"-var \" + .key + \"=\" + (.value | tostring)) | join(\" \")") && tofu apply -state=$STATE_FILE -auto-approve $VARS +destroy: tofu destroy -state=$STATE_FILE -auto-approve diff --git a/examples/datacenters/local/secret/Dockerfile b/examples/datacenters/local/secret/Dockerfile new file mode 100644 index 000000000..35cc3d9f0 --- /dev/null +++ b/examples/datacenters/local/secret/Dockerfile @@ -0,0 +1,19 @@ +# This dockerfile is used to build OpenTofu modules - the module files are copied in and terraform is installed +# so that the plugin can use terraform commands to plan/apply the module. +FROM alpine:3 + +# Needed for terraform modules that include a remote source +RUN apk add --no-cache git + +RUN wget https://github.com/opentofu/opentofu/releases/download/v1.6.0-beta3/tofu_1.6.0-beta3_amd64.apk +RUN apk add --allow-untrusted tofu_1.6.0-beta3_amd64.apk + +RUN wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 && \ + mv jq-linux-amd64 /usr/local/bin/jq && \ + chmod +x /usr/local/bin/jq + +WORKDIR /app + +COPY . . + +ENTRYPOINT [] \ No newline at end of file diff --git a/examples/datacenters/local/secret/module.yml b/examples/datacenters/local/secret/module.yml new file mode 100644 index 000000000..6675554f1 --- /dev/null +++ b/examples/datacenters/local/secret/module.yml @@ -0,0 +1,5 @@ +init: tofu init +plan: tofu plan -input=false -state=$STATE_FILE -detailed-exitcode +outputs: tofu output -state=$STATE_FILE -json +apply: VARS=$(echo $INPUTS | jq -r "to_entries | map(\"-var \" + .key + \"=\" + (.value | tostring)) | join(\" \")") && tofu apply -state=$STATE_FILE -auto-approve $VARS +destroy: tofu destroy -state=$STATE_FILE -auto-approve diff --git a/examples/datacenters/local/volume/Dockerfile b/examples/datacenters/local/volume/Dockerfile index c2cb0473b..37ee780ca 100644 --- a/examples/datacenters/local/volume/Dockerfile +++ b/examples/datacenters/local/volume/Dockerfile @@ -1,5 +1,26 @@ -FROM pulumi/pulumi-nodejs +FROM docker + +RUN apk add --update nodejs npm curl git ca-certificates + +RUN wget https://get.pulumi.com/releases/sdk/pulumi-v3.96.2-linux-x64.tar.gz && \ + mkdir -p /tmp/pulumi && \ + tar zxf pulumi-v3.96.2-linux-x64.tar.gz -C /tmp/pulumi && \ + mkdir -p /app/.pulumi/bin && \ + cp /tmp/pulumi/pulumi/* /app/.pulumi/bin/ + +RUN wget https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 && \ + mv jq-linux-amd64 /usr/local/bin/jq && \ + chmod +x /usr/local/bin/jq + +ENV PATH=$PATH:/app/.pulumi/bin/ + +WORKDIR /app COPY . . -RUN npm install \ No newline at end of file +RUN npm install + +ENV PULUMI_CONFIG_PASSPHRASE="passphrase" + +ENTRYPOINT [] +CMD ["pulumi"] \ No newline at end of file diff --git a/examples/datacenters/local/volume/module.yml b/examples/datacenters/local/volume/module.yml new file mode 100644 index 000000000..09f40bc51 --- /dev/null +++ b/examples/datacenters/local/volume/module.yml @@ -0,0 +1,7 @@ +init: pulumi login --local && pulumi stack init module --non-interactive +plan: pulumi preview --stack module --non-interactive +import: pulumi stack import --stack module --file $STATE_FILE --non-interactive +export: pulumi stack export --stack module --file $STATE_FILE --non-interactive +outputs: pulumi stack output --stack module --json --show-secrets --non-interactive > $OUTPUT_FILE +apply: pulumi config --stack module --non-interactive set-all $(echo $INPUTS | jq -r "to_entries | map(\"--path --plaintext \" + .key + \"=\" + (.value | tostring)) | join(\" \") | join(\" \")") && pulumi up --stack module --non-interactive --yes +destroy: pulumi destroy --stack module --non-interactive --yes diff --git a/src/@resources/cronjob/inputs.schema.json b/src/@resources/cronjob/inputs.schema.json index 5cf571791..49f9e2b82 100644 --- a/src/@resources/cronjob/inputs.schema.json +++ b/src/@resources/cronjob/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/CronjobInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "CronjobInputs": { "additionalProperties": false, @@ -142,5 +142,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/cronjob/inputs.json" } \ No newline at end of file diff --git a/src/@resources/cronjob/outputs.schema.json b/src/@resources/cronjob/outputs.schema.json index f7255b50a..534b5b228 100644 --- a/src/@resources/cronjob/outputs.schema.json +++ b/src/@resources/cronjob/outputs.schema.json @@ -1,10 +1,11 @@ { "$ref": "#/definitions/CronjobOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "CronjobOutputs": { "additionalProperties": {}, "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/cronjob/outputs.json" } \ No newline at end of file diff --git a/src/@resources/database/inputs.schema.json b/src/@resources/database/inputs.schema.json index 3275c0ae2..33950e4e1 100644 --- a/src/@resources/database/inputs.schema.json +++ b/src/@resources/database/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DatabaseInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DatabaseInputs": { "additionalProperties": false, @@ -34,5 +34,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/database/inputs.json" } \ No newline at end of file diff --git a/src/@resources/database/outputs.schema.json b/src/@resources/database/outputs.schema.json index fa4c4e091..4f41c5f84 100644 --- a/src/@resources/database/outputs.schema.json +++ b/src/@resources/database/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DatabaseOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DatabaseOutputs": { "additionalProperties": false, @@ -73,5 +73,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/database/outputs.json" } \ No newline at end of file diff --git a/src/@resources/databaseUser/inputs.schema.json b/src/@resources/databaseUser/inputs.schema.json index ca3770b70..adc49a6aa 100644 --- a/src/@resources/databaseUser/inputs.schema.json +++ b/src/@resources/databaseUser/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DatabaseUserApplyInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DatabaseUserApplyInputs": { "additionalProperties": false, @@ -70,5 +70,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/databaseUser/inputs.json" } \ No newline at end of file diff --git a/src/@resources/databaseUser/outputs.schema.json b/src/@resources/databaseUser/outputs.schema.json index de967fe57..607a951d3 100644 --- a/src/@resources/databaseUser/outputs.schema.json +++ b/src/@resources/databaseUser/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DatabaseUserApplyOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DatabaseUserApplyOutputs": { "additionalProperties": false, @@ -73,5 +73,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/databaseUser/outputs.json" } \ No newline at end of file diff --git a/src/@resources/deployment/inputs.schema.json b/src/@resources/deployment/inputs.schema.json index b433ef445..81249ad42 100644 --- a/src/@resources/deployment/inputs.schema.json +++ b/src/@resources/deployment/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DeploymentInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DeploymentInputs": { "additionalProperties": false, @@ -443,5 +443,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/deployment/inputs.json" } \ No newline at end of file diff --git a/src/@resources/deployment/outputs.schema.json b/src/@resources/deployment/outputs.schema.json index 060d00922..a10fb4a22 100644 --- a/src/@resources/deployment/outputs.schema.json +++ b/src/@resources/deployment/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DeploymentOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DeploymentOutputs": { "additionalProperties": false, @@ -20,5 +20,6 @@ }, "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/deployment/outputs.json" } \ No newline at end of file diff --git a/src/@resources/dockerBuild/inputs.schema.json b/src/@resources/dockerBuild/inputs.schema.json index 4ed265482..4158f1643 100644 --- a/src/@resources/dockerBuild/inputs.schema.json +++ b/src/@resources/dockerBuild/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DockerBuildInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DockerBuildInputs": { "additionalProperties": false, @@ -46,5 +46,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/dockerBuild/inputs.json" } \ No newline at end of file diff --git a/src/@resources/dockerBuild/outputs.schema.json b/src/@resources/dockerBuild/outputs.schema.json index 7d4733c4a..313ee33a4 100644 --- a/src/@resources/dockerBuild/outputs.schema.json +++ b/src/@resources/dockerBuild/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DockerBuildOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DockerBuildOutputs": { "additionalProperties": false, @@ -18,5 +18,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/dockerBuild/outputs.json" } \ No newline at end of file diff --git a/src/@resources/ingress/inputs.schema.json b/src/@resources/ingress/inputs.schema.json index 7bb1a907c..7ab057d0a 100644 --- a/src/@resources/ingress/inputs.schema.json +++ b/src/@resources/ingress/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/IngressRuleInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "IngressRuleInputs": { "additionalProperties": false, @@ -114,5 +114,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/ingress/inputs.json" } \ No newline at end of file diff --git a/src/@resources/ingress/outputs.schema.json b/src/@resources/ingress/outputs.schema.json index 08de61c29..bc01db7d1 100644 --- a/src/@resources/ingress/outputs.schema.json +++ b/src/@resources/ingress/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/IngressRuleOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "IngressRuleOutputs": { "additionalProperties": false, @@ -83,5 +83,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/ingress/outputs.json" } \ No newline at end of file diff --git a/src/@resources/parser.ts b/src/@resources/parser.ts index b97b2fec1..2cd0b4ad4 100644 --- a/src/@resources/parser.ts +++ b/src/@resources/parser.ts @@ -1,10 +1,8 @@ -import Ajv2019 from 'https://esm.sh/v124/ajv@8.12.0'; +import Ajv2019 from 'https://esm.sh/v124/ajv@8.12.0/dist/2019.js'; import yaml from 'js-yaml'; import * as path from 'std/path/mod.ts'; import { ResourceInputs, ResourceOutputs, ResourceType, ResourceTypeList } from './types.ts'; -const ajv = new Ajv2019({ strict: false, discriminator: true }); - export const parseSpecificResourceInputs = async ( type: T, input: Record | string, @@ -34,6 +32,7 @@ export const parseSpecificResourceInputs = async ( const __dirname = new URL('.', import.meta.url).pathname; const input_schema_file_contents = Deno.readTextFileSync(path.join(__dirname, type, 'inputs.schema.json')); const inputSchema = JSON.parse(input_schema_file_contents); + const ajv = new Ajv2019({ strict: false, discriminator: true }); const resource_validator = ajv.compile(inputSchema); if (!resource_validator(raw_obj)) { @@ -72,6 +71,7 @@ export const parseResourceOutputs = ( const __dirname = new URL('.', import.meta.url).pathname; const output_schema_file_contents = Deno.readTextFileSync(path.join(__dirname, type, 'outputs.schema.json')); const outputSchema = JSON.parse(output_schema_file_contents); + const ajv = new Ajv2019({ strict: false, discriminator: true }); const outputValidator = ajv.compile(outputSchema); if (!outputValidator(raw_obj)) { diff --git a/src/@resources/secret/inputs.schema.json b/src/@resources/secret/inputs.schema.json index fc45a5824..2bcb1a4e6 100644 --- a/src/@resources/secret/inputs.schema.json +++ b/src/@resources/secret/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/SecretInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "SecretInputs": { "additionalProperties": false, @@ -33,5 +33,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/secret/inputs.json" } \ No newline at end of file diff --git a/src/@resources/secret/outputs.schema.json b/src/@resources/secret/outputs.schema.json index 4a897aa70..407e559b0 100644 --- a/src/@resources/secret/outputs.schema.json +++ b/src/@resources/secret/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/SecretOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "SecretOutputs": { "additionalProperties": false, @@ -18,5 +18,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/secret/outputs.json" } \ No newline at end of file diff --git a/src/@resources/service/inputs.schema.json b/src/@resources/service/inputs.schema.json index eb08d8b67..830dbf1d0 100644 --- a/src/@resources/service/inputs.schema.json +++ b/src/@resources/service/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/ServiceInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "ServiceInputs": { "anyOf": [ @@ -65,5 +65,6 @@ } ] } - } + }, + "$id": "https://architect.io/.schemas/resources/service/inputs.json" } \ No newline at end of file diff --git a/src/@resources/service/outputs.schema.json b/src/@resources/service/outputs.schema.json index 5a9e8d517..8a086eabc 100644 --- a/src/@resources/service/outputs.schema.json +++ b/src/@resources/service/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/ServiceOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "ServiceOutputs": { "additionalProperties": false, @@ -64,5 +64,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/service/outputs.json" } \ No newline at end of file diff --git a/src/@resources/types.ts b/src/@resources/types.ts index 94ea31d62..9db8aee7c 100644 --- a/src/@resources/types.ts +++ b/src/@resources/types.ts @@ -26,7 +26,8 @@ export type ResourceType = | 'ingress' | 'secret' | 'service' - | 'volume'; + | 'volume' +; export const ResourceTypeList: ResourceType[] = [ 'cronjob', diff --git a/src/@resources/volume/inputs.schema.json b/src/@resources/volume/inputs.schema.json index da0dea9ef..0f424b58b 100644 --- a/src/@resources/volume/inputs.schema.json +++ b/src/@resources/volume/inputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/VolumeInputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "VolumeInputs": { "additionalProperties": false, @@ -25,5 +25,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/volume/inputs.json" } \ No newline at end of file diff --git a/src/@resources/volume/outputs.schema.json b/src/@resources/volume/outputs.schema.json index b6cb43fd4..3c4032ea8 100644 --- a/src/@resources/volume/outputs.schema.json +++ b/src/@resources/volume/outputs.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/VolumeOutputs", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "VolumeOutputs": { "additionalProperties": false, @@ -18,5 +18,6 @@ ], "type": "object" } - } + }, + "$id": "https://architect.io/.schemas/resources/volume/outputs.json" } \ No newline at end of file diff --git a/src/commands/build/component.ts b/src/commands/build/component.ts index a64bdc460..f28f637ba 100644 --- a/src/commands/build/component.ts +++ b/src/commands/build/component.ts @@ -1,4 +1,3 @@ -import * as mod from 'https://deno.land/std@0.195.0/fs/copy.ts'; import * as path from 'std/path/mod.ts'; import { Component, parseComponent } from '../../components/index.ts'; import { verifyDocker } from '../../docker/helper.ts'; @@ -107,36 +106,6 @@ const ComponentBuildCommand = BaseCommand() build_args.push(build_context); await getDigest(build_args, options.verbose); return imageRepository.toString(); - }, async (build_options) => { - imageRepository.tag = imageRepository.tag + '-' + build_options.deployment_name + '-volumes-' + - build_options.volume_name; - console.log( - 'Building image for volume', - build_options.deployment_name + '.volumes.' + build_options.volume_name, - ); - - // Create the directory for the new volume container - const tmpDir = await Deno.makeTempDir(); - await mod.copy(build_options.host_path, path.join(tmpDir, 'contents')); - Deno.writeTextFileSync( - path.join(tmpDir, 'Dockerfile'), - ` - FROM alpine:latest - WORKDIR /app - COPY ./contents . - CMD ["sh", "-c", "cp -r ./* $TARGET_DIR"] - `, - ); - - // Publish the volume container - const buildArgs = ['build', '--push', '--tag', imageRepository.toString()]; - if (options.verbose) { - buildArgs.push('--quiet'); - } - buildArgs.push(tmpDir); - - await getDigest(buildArgs, options.verbose); - return imageRepository.toString(); }); // Tag and push the component itself @@ -175,33 +144,6 @@ const ComponentBuildCommand = BaseCommand() buildArgs.push(path.join(Deno.cwd(), context, build_options.context)); } - return getDigest(buildArgs, options.verbose); - }, async (build_options) => { - console.log( - 'Building image for volume', - build_options.deployment_name + '.volumes.' + build_options.volume_name, - ); - - // Create the directory for the new volume container - const tmpDir = await Deno.makeTempDir(); - await mod.copy(build_options.host_path, path.join(tmpDir, 'contents')); - Deno.writeTextFileSync( - path.join(tmpDir, 'Dockerfile'), - ` - FROM alpine:latest - WORKDIR /app - COPY ./contents . - CMD ["sh", "-c", "cp -r ./* $TARGET_DIR"] - `, - ); - - // Publish the volume container - const buildArgs = ['build']; - if (options.verbose) { - buildArgs.push('--quiet'); - } - buildArgs.push(tmpDir); - return getDigest(buildArgs, options.verbose); }); @@ -211,17 +153,10 @@ const ComponentBuildCommand = BaseCommand() for (const tag of options.tag) { component = await component.tag(async (sourceRef: string, targetName: string) => { const imageRepository = new ImageRepository(tag); - const targetRef = imageRepository.toString() + '-deployments-' + targetName; + const targetRef = imageRepository.toString() + '-' + targetName; await exec('docker', { args: ['tag', sourceRef, targetRef] }); console.log(`Deployment Tagged: ${targetRef}`); return targetRef; - }, async (digest: string, deploymentName: string, volumeName: string) => { - const imageRepository = new ImageRepository(tag); - const targetRef = imageRepository.toString() + '-deployments-' + deploymentName + '-volumes-' + volumeName; - - await exec('docker', { args: ['tag', digest, targetRef] }); - console.log(`Volume Tagged: ${targetRef}`); - return targetRef; }); const component_digest = await command_helper.componentStore.add(component); diff --git a/src/commands/build/datacenter.ts b/src/commands/build/datacenter.ts index f35510f76..06e0e6605 100644 --- a/src/commands/build/datacenter.ts +++ b/src/commands/build/datacenter.ts @@ -1,11 +1,11 @@ import { isAbsolute } from 'https://deno.land/std@0.50.0/path/posix.ts'; import * as path from 'std/path/mod.ts'; import winston from 'winston'; -import { ModuleServer } from '../../datacenter-modules/server.ts'; import { Datacenter } from '../../datacenters/datacenter.ts'; import { parseDatacenter } from '../../datacenters/parser.ts'; import { verifyDocker } from '../../docker/helper.ts'; import { ImageRepository } from '../../oci/index.ts'; +import { buildModuleFromDirectory } from '../../utils/build.ts'; import { exec } from '../../utils/command.ts'; import { BaseCommand, CommandHelper, GlobalOptions } from '../base-command.ts'; @@ -56,18 +56,7 @@ async function build_action(options: BuildOptions, context_file: string): Promis ? build_options.context : path.join(context, build_options.context); console.log(`Building module: ${build_dir}`); - const server = new ModuleServer(build_options.plugin); - const client = await server.start(build_dir); - try { - const build = await client.build({ directory: build_dir }, { logger }); - client.close(); - await server.stop(); - return build.image; - } catch (e) { - client.close(); - await server.stop(); - throw new Error(e); - } + return buildModuleFromDirectory(build_dir); }); const digest = await command_helper.datacenterStore.add(datacenter); diff --git a/src/commands/build/module.ts b/src/commands/build/module.ts index 0fbb89176..5b24df683 100644 --- a/src/commands/build/module.ts +++ b/src/commands/build/module.ts @@ -1,19 +1,14 @@ import { isAbsolute } from 'https://deno.land/std@0.50.0/path/posix.ts'; import * as path from 'std/path/mod.ts'; import winston from 'winston'; -import { ModuleServer } from '../../datacenter-modules/server.ts'; -import { isPlugin, PluginArray } from '../../datacenter-modules/types.ts'; import { verifyDocker } from '../../docker/helper.ts'; -import { ImageRepository } from '../../oci/index.ts'; -import { exec } from '../../utils/command.ts'; +import { buildModuleFromDirectory } from '../../utils/build.ts'; import { BaseCommand, GlobalOptions } from '../base-command.ts'; -import { module_push_action } from '../push/module.ts'; type BuildOptions = { tag?: string[]; name?: string; platform?: string; - plugin: string; push: boolean; verbose: boolean; } & GlobalOptions; @@ -22,65 +17,46 @@ const ModuleBuildCommand = BaseCommand() .description('Build a module for use within a datacenter') .arguments('') // 'Path to the module to build' .option('-n, --name ', 'Name of this module image') - .option('-p, --plugin ', 'Plugin this module is built with', { default: 'pulumi' }) .option('-t, --tag ', 'Tags to assign to the built module image', { collect: true }) .option('--platform ', 'Target platform for the build') .option('--push [push:boolean]', 'Push the tagged images after buliding', { default: false }) .option('-v, --verbose [verbose:boolean]', 'Turn on verbose logs', { default: false }) - .action(build_action); + .action(async (options: BuildOptions, context_file: string) => { + verifyDocker(); -async function build_action(options: BuildOptions, context_file: string): Promise { - verifyDocker(); - if (!isPlugin(options.plugin)) { - console.log(`Invalid value for plugin: ${options.plugin}. Valid plugin values: ${PluginArray.join(', ')}`); - Deno.exit(1); - } - - if (options.push && (!options.tag || options.tag.length <= 0)) { - console.error('Cannot use --push flag without at least one --tag'); - Deno.exit(1); - } - - const logger = options.verbose - ? winston.createLogger({ - level: 'info', - format: winston.format.printf(({ message }) => message), - transports: [new winston.transports.Console()], - }) - : undefined; - - const context_relative = !Deno.lstatSync(context_file).isFile ? context_file : path.dirname(context_file); - const context = isAbsolute(context_relative) ? context_relative : path.join(Deno.cwd(), context_relative); - // Default module name to being the folder name of the module - const module_name = options.name || context.split(path.SEP).at(-1) || 'module'; - - console.log(`Building module ${module_name} at: ${context}`); - - const server = new ModuleServer(options.plugin); - const client = await server.start(context); - let image; - try { - const build = await client.build({ directory: context, platform: options.platform }, { logger }); - client.close(); - await server.stop(); - image = build.image; - } catch (e) { - client.close(); - await server.stop(); - throw new Error(e); - } - - if (options.tag) { - for (const tag of options.tag) { - const imageRepository = new ImageRepository(`${module_name}:${tag}`); - await exec('docker', { args: ['tag', image, imageRepository.toString()] }); - console.log(`Module Tagged: ${imageRepository.toString()}`); + if (options.push && (!options.tag || options.tag.length <= 0)) { + console.error('Cannot use --push flag without at least one --tag'); + Deno.exit(1); + } - if (options.push) { - module_push_action({ verbose: options.verbose }, imageRepository.toString()); - } + const logger = options.verbose + ? winston.createLogger({ + level: 'info', + format: winston.format.printf(({ message }) => message), + transports: [new winston.transports.Console()], + }) + : undefined; + + const context_relative = !Deno.lstatSync(context_file).isFile ? context_file : path.dirname(context_file); + const context = isAbsolute(context_relative) ? context_relative : path.join(Deno.cwd(), context_relative); + // Default module name to being the folder name of the module + const module_name = options.name || context.split(path.SEP).at(-1) || 'module'; + + console.log(`Building module ${module_name} at: ${context}`); + try { + await buildModuleFromDirectory(context, { + logger, + platform: options.platform, + tags: options.tag, + push: options.push, + }); + + console.log('%cBuild successful', 'color: green'); + } catch (err) { + console.error('%cBuild failed', 'color: red'); + console.error(err.message); + Deno.exit(1); } - } -} + }); export default ModuleBuildCommand; diff --git a/src/commands/common/datacenter.ts b/src/commands/common/datacenter.ts index 4e40edfa0..fa2f22fe1 100644 --- a/src/commands/common/datacenter.ts +++ b/src/commands/common/datacenter.ts @@ -1,6 +1,5 @@ import * as path from 'std/path/mod.ts'; import winston, { Logger } from 'winston'; -import { ModuleServer } from '../../datacenter-modules/index.ts'; import { Datacenter, DatacenterRecord, @@ -9,6 +8,7 @@ import { ParsedVariablesMetadata, } from '../../datacenters/index.ts'; import { InfraGraph } from '../../graphs/index.ts'; +import { buildModuleFromDirectory } from '../../utils/build.ts'; import { topologicalSort } from '../../utils/sorting.ts'; import { Inputs } from './inputs.ts'; @@ -157,23 +157,9 @@ export class DatacenterUtils { if (!path.isAbsolute(path.dirname(context))) { module_path = path.resolve(module_path); } - console.log(`Building module: ${module_path}`); - const server = new ModuleServer(build_options.plugin); - let client; - try { - client = await server.start(module_path); - const res = await client.build({ - directory: module_path, - }, { logger }); - client.close(); - return res.image; - } finally { - if (client) { - client.close(); - } - await server.stop(); - } + console.log(`Building module: ${module_path}`); + return buildModuleFromDirectory(module_path, { logger }); }); } } diff --git a/src/commands/common/infra-renderer.ts b/src/commands/common/infra-renderer.ts index 5a6b199f5..726911511 100644 --- a/src/commands/common/infra-renderer.ts +++ b/src/commands/common/infra-renderer.ts @@ -22,7 +22,7 @@ export class InfraRenderer { } private graphToTableOutput(graph: InfraGraph): string { - const headers = ['Name', 'Plugin']; + const headers = ['Name']; const showEnvironment = graph.nodes.some((s) => s.environment); const showComponent = graph.nodes.some((s) => s.component); @@ -48,7 +48,7 @@ export class InfraRenderer { 0, ) .map((node: InfraGraphNode) => { - const row = [node.name, node.plugin]; + const row = [node.name]; if (showComponent) { row.push(node.component || ''); diff --git a/src/components/component-schema.ts b/src/components/component-schema.ts index 4aa442ca0..f0677184c 100644 --- a/src/components/component-schema.ts +++ b/src/components/component-schema.ts @@ -1,3897 +1,3851 @@ export default { - 'oneOf': [ + "oneOf": [ { - 'additionalProperties': false, - 'properties': { - 'databases': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'description': 'A human-readable description of the database and its purpose', - 'type': 'string', - }, - 'type': { - 'description': 'Type of database and version required by the application', - 'type': 'string', + "additionalProperties": false, + "properties": { + "databases": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "description": "A human-readable description of the database and its purpose", + "type": "string" }, + "type": { + "description": "Type of database and version required by the application", + "type": "string" + } }, - 'required': [ - 'type', + "required": [ + "type" ], - 'type': 'object', + "type": "object" }, - 'description': 'A set of databases required by the component', - 'type': 'object', + "description": "A set of databases required by the component", + "type": "object" }, - 'dependencies': { - 'additionalProperties': { - 'anyOf': [ + "dependencies": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'component': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "component": { + "type": "string" }, - 'inputs': { - 'additionalProperties': { - 'items': { - 'type': 'string', + "inputs": { + "additionalProperties": { + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'component', + "required": [ + "component" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': 'A set of components and associated versions that this component depends on.', - 'type': 'object', + "description": "A set of components and associated versions that this component depends on.", + "type": "object" }, - 'description': { - 'description': 'A human-readable description of the component and what it should be used for', - 'type': 'string', + "description": { + "description": "A human-readable description of the component and what it should be used for", + "type": "string" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'description': { - 'description': 'A human-readable description of the interface and how it should be used.', - 'type': 'string', - }, - 'ingress': { - 'additionalProperties': false, - 'description': 'Ingress configuration to allow the interface to be exposed publicly', - 'properties': { - 'internal': { - 'default': false, - 'description': - 'Indicates whether the ingress rule should be attached to a public or private load balancer', - 'type': 'boolean', - }, - 'path': { - 'description': 'Path that the interface should be exposed under', - 'type': 'string', - }, - 'subdomain': { - 'description': 'Subdomain the interface should be accessed on', - 'type': 'string', - }, + "additionalProperties": false, + "properties": { + "description": { + "description": "A human-readable description of the interface and how it should be used.", + "type": "string" + }, + "ingress": { + "additionalProperties": false, + "description": "Ingress configuration to allow the interface to be exposed publicly", + "properties": { + "internal": { + "default": false, + "description": "Indicates whether the ingress rule should be attached to a public or private load balancer", + "type": "boolean" + }, + "path": { + "description": "Path that the interface should be exposed under", + "type": "string" + }, + "subdomain": { + "description": "Subdomain the interface should be accessed on", + "type": "string" + } }, - 'type': 'object', - }, - 'url': { - 'description': - 'The url of the downstream interfaces that should be exposed. This will usually be a reference to one of your services interfaces.', - 'type': 'string', + "type": "object" }, + "url": { + "description": "The url of the downstream interfaces that should be exposed. This will usually be a reference to one of your services interfaces.", + "type": "string" + } }, - 'required': [ - 'url', + "required": [ + "url" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': - 'A dictionary of named interfaces that the component makes available to upstreams, including other components via dependencies or environments via interface mapping.\n\nInterfaces can either be an object describing the interface, or a string shorthand that directly applies to the `url` value.', - 'type': 'object', + "description": "A dictionary of named interfaces that the component makes available to upstreams, including other components via dependencies or environments via interface mapping.\n\nInterfaces can either be an object describing the interface, or a string shorthand that directly applies to the `url` value.", + "type": "object" }, - 'keywords': { - 'description': - 'An array of keywords that can be used to index the component and make it discoverable for others', - 'items': { - 'type': 'string', + "keywords": { + "description": "An array of keywords that can be used to index the component and make it discoverable for others", + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'name': { - 'description': 'Unique name of the component. Must be of the format, /', - 'type': 'string', + "name": { + "description": "Unique name of the component. Must be of the format, /", + "type": "string" }, - 'parameters': { - 'additionalProperties': { - 'anyOf': [ + "parameters": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'default': { - 'description': - 'The default value to apply to the parameter when one wasn\'t provided by the operator', - 'type': [ - 'string', - 'number', - ], - }, - 'description': { - 'description': - 'A human-readable description of the parameter, how it should be used, and what kinds of values it supports.', - 'type': 'string', - }, - 'merge': { - 'default': false, - 'description': 'Whether or not to merge results from multiple sources into a single array', - 'type': 'boolean', - }, - 'required': { - 'default': false, - 'description': 'A boolean indicating whether or not an operator is required ot provide a value', - 'type': 'boolean', - }, + "additionalProperties": false, + "properties": { + "default": { + "description": "The default value to apply to the parameter when one wasn't provided by the operator", + "type": [ + "string", + "number" + ] + }, + "description": { + "description": "A human-readable description of the parameter, how it should be used, and what kinds of values it supports.", + "type": "string" + }, + "merge": { + "default": false, + "description": "Whether or not to merge results from multiple sources into a single array", + "type": "boolean" + }, + "required": { + "default": false, + "description": "A boolean indicating whether or not an operator is required ot provide a value", + "type": "boolean" + } }, - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': - 'A dictionary of named parameters that this component uses to configure services.\n\nParameters can either be an object describing the parameter or a string shorthand that directly applies to the `default` value.\n\nThis is an alias for the `inputs` field.', - 'type': 'object', + "description": "A dictionary of named parameters that this component uses to configure services.\n\nParameters can either be an object describing the parameter or a string shorthand that directly applies to the `default` value.\n\nThis is an alias for the `inputs` field.", + "type": "object" }, - 'secrets': { - 'additionalProperties': { - 'anyOf': [ + "secrets": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'default': { - 'description': - 'The default value to apply to the parameter when one wasn\'t provided by the operator', - 'type': [ - 'string', - 'number', - ], - }, - 'description': { - 'description': - 'A human-readable description of the parameter, how it should be used, and what kinds of values it supports.', - 'type': 'string', - }, - 'merge': { - 'default': false, - 'description': 'Whether or not to merge results from multiple sources into a single array', - 'type': 'boolean', - }, - 'required': { - 'default': false, - 'description': 'A boolean indicating whether or not an operator is required ot provide a value', - 'type': 'boolean', - }, + "additionalProperties": false, + "properties": { + "default": { + "description": "The default value to apply to the parameter when one wasn't provided by the operator", + "type": [ + "string", + "number" + ] + }, + "description": { + "description": "A human-readable description of the parameter, how it should be used, and what kinds of values it supports.", + "type": "string" + }, + "merge": { + "default": false, + "description": "Whether or not to merge results from multiple sources into a single array", + "type": "boolean" + }, + "required": { + "default": false, + "description": "A boolean indicating whether or not an operator is required ot provide a value", + "type": "boolean" + } }, - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': - 'A dictionary of named parameters that this component uses to configure services.\n\nParameters can either be an object describing the parameter or a string shorthand that directly applies to the `default` value.\n\nThis is an alias for the `inputs` field.', - 'type': 'object', + "description": "A dictionary of named parameters that this component uses to configure services.\n\nParameters can either be an object describing the parameter or a string shorthand that directly applies to the `default` value.\n\nThis is an alias for the `inputs` field.", + "type": "object" }, - 'services': { - 'additionalProperties': { - 'anyOf': [ + "services": { + "additionalProperties": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'command': { - 'anyOf': [ + "additionalProperties": false, + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'context': { - 'type': 'string', + "context": { + "type": "string" }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', + "dockerfile": { + "type": "string" }, + "target": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'depends_on': { - 'items': { - 'type': 'string', + "depends_on": { + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'host': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "host": { + "type": "string" }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'path': { - 'type': 'string', - }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', - }, - 'password': { - 'type': 'string', + "type": "object" }, - 'port': { - 'type': [ - 'number', - 'string', - ], + "password": { + "type": "string" }, - 'protocol': { - 'type': 'string', + "port": { + "type": [ + "number", + "string" + ] }, - 'url': { - 'type': 'string', + "protocol": { + "type": "string" }, - 'username': { - 'type': 'string', + "url": { + "type": "string" }, + "username": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, { - 'type': 'number', + "type": "number" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'type': 'string', + "language": { + "type": "string" }, - 'liveness_probe': { - 'additionalProperties': false, - 'properties': { - 'command': { - 'anyOf': [ + "liveness_probe": { + "additionalProperties": false, + "properties": { + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], - }, - 'failure_threshold': { - 'type': [ - 'number', - 'string', - ], - }, - 'initial_delay': { - 'type': 'string', - }, - 'interval': { - 'type': 'string', - }, - 'path': { - 'type': 'string', - }, - 'port': { - 'type': [ - 'number', - 'string', - ], - }, - 'success_threshold': { - 'type': [ - 'number', - 'string', - ], - }, - 'timeout': { - 'type': 'string', - }, - }, - 'type': 'object', - }, - 'memory': { - 'type': 'string', - }, - 'platform': { - 'type': 'string', - }, - 'replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'scaling': { - 'additionalProperties': false, - 'properties': { - 'max_replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'metrics': { - 'additionalProperties': false, - 'properties': { - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'memory': { - 'type': 'string', + "type": "string" + } + ] + }, + "failure_threshold": { + "type": [ + "number", + "string" + ] + }, + "initial_delay": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "path": { + "type": "string" + }, + "port": { + "type": [ + "number", + "string" + ] + }, + "success_threshold": { + "type": [ + "number", + "string" + ] + }, + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "memory": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "replicas": { + "type": [ + "number", + "string" + ] + }, + "scaling": { + "additionalProperties": false, + "properties": { + "max_replicas": { + "type": [ + "number", + "string" + ] + }, + "metrics": { + "additionalProperties": false, + "properties": { + "cpu": { + "type": [ + "number", + "string" + ] }, + "memory": { + "type": "string" + } }, - 'type': 'object', - }, - 'min_replicas': { - 'type': [ - 'number', - 'string', - ], + "type": "object" }, + "min_replicas": { + "type": [ + "number", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'depends_on': { - 'description': 'Specify other services that you want to wait for before starting this one', - 'items': { - 'type': 'string', + "depends_on": { + "description": "Specify other services that you want to wait for before starting this one", + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - ], - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'type': 'number', + "type": "number" }, { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'host': { - 'type': 'string', - }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'path': { - 'type': 'string', - }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', - }, - 'password': { - 'type': 'string', + "type": "object" }, - 'port': { - 'type': [ - 'number', - 'string', - ], + "password": { + "type": "string" }, - 'protocol': { - 'type': 'string', + "port": { + "type": [ + "number", + "string" + ] }, - 'url': { - 'type': 'string', + "protocol": { + "type": "string" }, - 'username': { - 'type': 'string', + "url": { + "type": "string" }, + "username": { + "type": "string" + } }, - 'required': [ - 'port', + "required": [ + "port" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', - }, - 'language': { - 'deprecated': true, - 'type': 'string', - }, - 'liveness_probe': { - 'additionalProperties': false, - 'description': 'Task run continuously to determine if each service replica is healthy', - 'properties': { - 'command': { - 'anyOf': [ + "type": "object" + }, + "language": { + "deprecated": true, + "type": "string" + }, + "liveness_probe": { + "additionalProperties": false, + "description": "Task run continuously to determine if each service replica is healthy", + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], - 'description': - 'Command that runs the http check. This field is disjunctive with `path` and `port` (only one of `command` or `path`/`port` can be set).', - }, - 'failure_threshold': { - 'default': 3, - 'description': - 'The number of times to retry a failed health check before the container is considered unhealthy', - 'type': [ - 'number', - 'string', + "type": "array" + } ], - }, - 'initial_delay': { - 'default': '0s', - 'description': 'Delays the check from running for the specified amount of time', - 'type': 'string', - }, - 'interval': { - 'default': '30s', - 'description': - 'The time period in seconds between each health check execution. You may specify any value between: 5s and 300s', - 'type': 'string', - }, - 'path': { - 'deprecated': true, - 'type': 'string', - }, - 'port': { - 'deprecated': true, - 'type': [ - 'number', - 'string', - ], - }, - 'success_threshold': { - 'default': 1, - 'description': - 'The number of times to retry a health check before the container is considered healthy', - 'type': [ - 'number', - 'string', - ], - }, - 'timeout': { - 'default': '5s', - 'description': - 'The time period to wait for a health check to succeed before it is considered a failure. You may specify any value between 2s and 60s.', - 'type': 'string', - }, + "description": "Command that runs the http check. This field is disjunctive with `path` and `port` (only one of `command` or `path`/`port` can be set)." + }, + "failure_threshold": { + "default": 3, + "description": "The number of times to retry a failed health check before the container is considered unhealthy", + "type": [ + "number", + "string" + ] + }, + "initial_delay": { + "default": "0s", + "description": "Delays the check from running for the specified amount of time", + "type": "string" + }, + "interval": { + "default": "30s", + "description": "The time period in seconds between each health check execution. You may specify any value between: 5s and 300s", + "type": "string" + }, + "path": { + "deprecated": true, + "type": "string" + }, + "port": { + "deprecated": true, + "type": [ + "number", + "string" + ] + }, + "success_threshold": { + "default": 1, + "description": "The number of times to retry a health check before the container is considered healthy", + "type": [ + "number", + "string" + ] + }, + "timeout": { + "default": "5s", + "description": "The time period to wait for a health check to succeed before it is considered a failure. You may specify any value between 2s and 60s.", + "type": "string" + } }, - 'type': 'object', - }, - 'memory': { - 'type': 'string', - }, - 'platform': { - 'type': 'string', - }, - 'replicas': { - 'description': 'Number of replicas of the deployment to run', - 'type': [ - 'number', - 'string', - ], - }, - 'scaling': { - 'additionalProperties': false, - 'description': 'Autoscaling rules for the deployment', - 'properties': { - 'max_replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'metrics': { - 'additionalProperties': false, - 'description': 'Metrics to be used to trigger autoscaling', - 'properties': { - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'memory': { - 'type': 'string', - }, - }, - 'type': 'object', - }, - 'min_replicas': { - 'type': [ - 'number', - 'string', - ], - }, + "type": "object" + }, + "memory": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "replicas": { + "description": "Number of replicas of the deployment to run", + "type": [ + "number", + "string" + ] + }, + "scaling": { + "additionalProperties": false, + "description": "Autoscaling rules for the deployment", + "properties": { + "max_replicas": { + "type": [ + "number", + "string" + ] + }, + "metrics": { + "additionalProperties": false, + "description": "Metrics to be used to trigger autoscaling", + "properties": { + "cpu": { + "type": [ + "number", + "string" + ] + }, + "memory": { + "type": "string" + } + }, + "type": "object" + }, + "min_replicas": { + "type": [ + "number", + "string" + ] + } }, - 'required': [ - 'min_replicas', - 'max_replicas', - 'metrics', + "required": [ + "min_replicas", + "max_replicas", + "metrics" ], - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'host_path': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'mount_path': { - 'type': 'string', - }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'image', + "required": [ + "image" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', - }, - 'type': 'object', - }, - 'context': { - 'type': 'string', - }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', - }, + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "context": { + "type": "string" + }, + "dockerfile": { + "type": "string" + }, + "target": { + "type": "string" + } }, - 'required': [ - 'context', + "required": [ + "context" ], - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'context': { - 'type': 'string', + "context": { + "type": "string" }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', + "dockerfile": { + "type": "string" }, + "target": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'depends_on': { - 'items': { - 'type': 'string', + "depends_on": { + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'host': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "host": { + "type": "string" }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'path': { - 'type': 'string', - }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', - }, - 'password': { - 'type': 'string', + "type": "object" }, - 'port': { - 'type': [ - 'number', - 'string', - ], + "password": { + "type": "string" }, - 'protocol': { - 'type': 'string', + "port": { + "type": [ + "number", + "string" + ] }, - 'url': { - 'type': 'string', + "protocol": { + "type": "string" }, - 'username': { - 'type': 'string', + "url": { + "type": "string" }, + "username": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, { - 'type': 'number', + "type": "number" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'type': 'string', + "language": { + "type": "string" }, - 'liveness_probe': { - 'additionalProperties': false, - 'properties': { - 'command': { - 'anyOf': [ + "liveness_probe": { + "additionalProperties": false, + "properties": { + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], - }, - 'failure_threshold': { - 'type': [ - 'number', - 'string', - ], - }, - 'initial_delay': { - 'type': 'string', - }, - 'interval': { - 'type': 'string', - }, - 'path': { - 'type': 'string', - }, - 'port': { - 'type': [ - 'number', - 'string', - ], - }, - 'success_threshold': { - 'type': [ - 'number', - 'string', - ], - }, - 'timeout': { - 'type': 'string', - }, - }, - 'type': 'object', - }, - 'memory': { - 'type': 'string', - }, - 'platform': { - 'type': 'string', - }, - 'replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'scaling': { - 'additionalProperties': false, - 'properties': { - 'max_replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'metrics': { - 'additionalProperties': false, - 'properties': { - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'memory': { - 'type': 'string', + "type": "string" + } + ] + }, + "failure_threshold": { + "type": [ + "number", + "string" + ] + }, + "initial_delay": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "path": { + "type": "string" + }, + "port": { + "type": [ + "number", + "string" + ] + }, + "success_threshold": { + "type": [ + "number", + "string" + ] + }, + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "memory": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "replicas": { + "type": [ + "number", + "string" + ] + }, + "scaling": { + "additionalProperties": false, + "properties": { + "max_replicas": { + "type": [ + "number", + "string" + ] + }, + "metrics": { + "additionalProperties": false, + "properties": { + "cpu": { + "type": [ + "number", + "string" + ] }, + "memory": { + "type": "string" + } }, - 'type': 'object', - }, - 'min_replicas': { - 'type': [ - 'number', - 'string', - ], + "type": "object" }, + "min_replicas": { + "type": [ + "number", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'depends_on': { - 'description': 'Specify other services that you want to wait for before starting this one', - 'items': { - 'type': 'string', + "depends_on": { + "description": "Specify other services that you want to wait for before starting this one", + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - ], - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'type': 'number', + "type": "number" }, { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'host': { - 'type': 'string', - }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', - }, - 'path': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'password': { - 'type': 'string', + "password": { + "type": "string" }, - 'port': { - 'type': [ - 'number', - 'string', - ], - }, - 'protocol': { - 'type': 'string', + "port": { + "type": [ + "number", + "string" + ] }, - 'url': { - 'type': 'string', + "protocol": { + "type": "string" }, - 'username': { - 'type': 'string', + "url": { + "type": "string" }, + "username": { + "type": "string" + } }, - 'required': [ - 'port', + "required": [ + "port" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', - }, - 'language': { - 'deprecated': true, - 'type': 'string', - }, - 'liveness_probe': { - 'additionalProperties': false, - 'description': 'Task run continuously to determine if each service replica is healthy', - 'properties': { - 'command': { - 'anyOf': [ + "type": "object" + }, + "language": { + "deprecated": true, + "type": "string" + }, + "liveness_probe": { + "additionalProperties": false, + "description": "Task run continuously to determine if each service replica is healthy", + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], - 'description': - 'Command that runs the http check. This field is disjunctive with `path` and `port` (only one of `command` or `path`/`port` can be set).', - }, - 'failure_threshold': { - 'default': 3, - 'description': - 'The number of times to retry a failed health check before the container is considered unhealthy', - 'type': [ - 'number', - 'string', + "type": "array" + } ], - }, - 'initial_delay': { - 'default': '0s', - 'description': 'Delays the check from running for the specified amount of time', - 'type': 'string', - }, - 'interval': { - 'default': '30s', - 'description': - 'The time period in seconds between each health check execution. You may specify any value between: 5s and 300s', - 'type': 'string', - }, - 'path': { - 'deprecated': true, - 'type': 'string', - }, - 'port': { - 'deprecated': true, - 'type': [ - 'number', - 'string', - ], - }, - 'success_threshold': { - 'default': 1, - 'description': - 'The number of times to retry a health check before the container is considered healthy', - 'type': [ - 'number', - 'string', - ], - }, - 'timeout': { - 'default': '5s', - 'description': - 'The time period to wait for a health check to succeed before it is considered a failure. You may specify any value between 2s and 60s.', - 'type': 'string', - }, + "description": "Command that runs the http check. This field is disjunctive with `path` and `port` (only one of `command` or `path`/`port` can be set)." + }, + "failure_threshold": { + "default": 3, + "description": "The number of times to retry a failed health check before the container is considered unhealthy", + "type": [ + "number", + "string" + ] + }, + "initial_delay": { + "default": "0s", + "description": "Delays the check from running for the specified amount of time", + "type": "string" + }, + "interval": { + "default": "30s", + "description": "The time period in seconds between each health check execution. You may specify any value between: 5s and 300s", + "type": "string" + }, + "path": { + "deprecated": true, + "type": "string" + }, + "port": { + "deprecated": true, + "type": [ + "number", + "string" + ] + }, + "success_threshold": { + "default": 1, + "description": "The number of times to retry a health check before the container is considered healthy", + "type": [ + "number", + "string" + ] + }, + "timeout": { + "default": "5s", + "description": "The time period to wait for a health check to succeed before it is considered a failure. You may specify any value between 2s and 60s.", + "type": "string" + } }, - 'type': 'object', - }, - 'memory': { - 'type': 'string', - }, - 'platform': { - 'type': 'string', - }, - 'replicas': { - 'description': 'Number of replicas of the deployment to run', - 'type': [ - 'number', - 'string', - ], - }, - 'scaling': { - 'additionalProperties': false, - 'description': 'Autoscaling rules for the deployment', - 'properties': { - 'max_replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'metrics': { - 'additionalProperties': false, - 'description': 'Metrics to be used to trigger autoscaling', - 'properties': { - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'memory': { - 'type': 'string', - }, - }, - 'type': 'object', - }, - 'min_replicas': { - 'type': [ - 'number', - 'string', - ], - }, + "type": "object" + }, + "memory": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "replicas": { + "description": "Number of replicas of the deployment to run", + "type": [ + "number", + "string" + ] + }, + "scaling": { + "additionalProperties": false, + "description": "Autoscaling rules for the deployment", + "properties": { + "max_replicas": { + "type": [ + "number", + "string" + ] + }, + "metrics": { + "additionalProperties": false, + "description": "Metrics to be used to trigger autoscaling", + "properties": { + "cpu": { + "type": [ + "number", + "string" + ] + }, + "memory": { + "type": "string" + } + }, + "type": "object" + }, + "min_replicas": { + "type": [ + "number", + "string" + ] + } }, - 'required': [ - 'min_replicas', - 'max_replicas', - 'metrics', + "required": [ + "min_replicas", + "max_replicas", + "metrics" ], - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'build', + "required": [ + "build" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', - }, - 'type': 'object', - }, - 'context': { - 'type': 'string', - }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', - }, + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "context": { + "type": "string" + }, + "dockerfile": { + "type": "string" + }, + "target": { + "type": "string" + } }, - 'required': [ - 'context', + "required": [ + "context" ], - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', - }, - 'context': { - 'type': 'string', + "type": "object" }, - 'dockerfile': { - 'type': 'string', + "context": { + "type": "string" }, - 'target': { - 'type': 'string', + "dockerfile": { + "type": "string" }, + "target": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'depends_on': { - 'items': { - 'type': 'string', + "depends_on": { + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'host': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "host": { + "type": "string" }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', - }, - 'path': { - 'type': 'string', + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'password': { - 'type': 'string', + "password": { + "type": "string" }, - 'port': { - 'type': [ - 'number', - 'string', - ], - }, - 'protocol': { - 'type': 'string', + "port": { + "type": [ + "number", + "string" + ] }, - 'url': { - 'type': 'string', + "protocol": { + "type": "string" }, - 'username': { - 'type': 'string', + "url": { + "type": "string" }, + "username": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, { - 'type': 'number', + "type": "number" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'type': 'string', + "language": { + "type": "string" }, - 'liveness_probe': { - 'additionalProperties': false, - 'properties': { - 'command': { - 'anyOf': [ + "liveness_probe": { + "additionalProperties": false, + "properties": { + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], - }, - 'failure_threshold': { - 'type': [ - 'number', - 'string', - ], - }, - 'initial_delay': { - 'type': 'string', - }, - 'interval': { - 'type': 'string', - }, - 'path': { - 'type': 'string', - }, - 'port': { - 'type': [ - 'number', - 'string', - ], - }, - 'success_threshold': { - 'type': [ - 'number', - 'string', - ], - }, - 'timeout': { - 'type': 'string', - }, - }, - 'type': 'object', - }, - 'memory': { - 'type': 'string', - }, - 'platform': { - 'type': 'string', - }, - 'replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'scaling': { - 'additionalProperties': false, - 'properties': { - 'max_replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'metrics': { - 'additionalProperties': false, - 'properties': { - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'memory': { - 'type': 'string', + "type": "string" + } + ] + }, + "failure_threshold": { + "type": [ + "number", + "string" + ] + }, + "initial_delay": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "path": { + "type": "string" + }, + "port": { + "type": [ + "number", + "string" + ] + }, + "success_threshold": { + "type": [ + "number", + "string" + ] + }, + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "memory": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "replicas": { + "type": [ + "number", + "string" + ] + }, + "scaling": { + "additionalProperties": false, + "properties": { + "max_replicas": { + "type": [ + "number", + "string" + ] + }, + "metrics": { + "additionalProperties": false, + "properties": { + "cpu": { + "type": [ + "number", + "string" + ] }, + "memory": { + "type": "string" + } }, - 'type': 'object', - }, - 'min_replicas': { - 'type': [ - 'number', - 'string', - ], + "type": "object" }, + "min_replicas": { + "type": [ + "number", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'depends_on': { - 'description': 'Specify other services that you want to wait for before starting this one', - 'items': { - 'type': 'string', + "depends_on": { + "description": "Specify other services that you want to wait for before starting this one", + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - ], - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'interfaces': { - 'additionalProperties': { - 'anyOf': [ + "interfaces": { + "additionalProperties": { + "anyOf": [ { - 'type': 'number', + "type": "number" }, { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'host': { - 'type': 'string', - }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', - }, - 'path': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'password': { - 'type': 'string', + "password": { + "type": "string" }, - 'port': { - 'type': [ - 'number', - 'string', - ], - }, - 'protocol': { - 'type': 'string', + "port": { + "type": [ + "number", + "string" + ] }, - 'url': { - 'type': 'string', + "protocol": { + "type": "string" }, - 'username': { - 'type': 'string', + "url": { + "type": "string" }, + "username": { + "type": "string" + } }, - 'required': [ - 'port', + "required": [ + "port" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', - }, - 'language': { - 'deprecated': true, - 'type': 'string', - }, - 'liveness_probe': { - 'additionalProperties': false, - 'description': 'Task run continuously to determine if each service replica is healthy', - 'properties': { - 'command': { - 'anyOf': [ + "type": "object" + }, + "language": { + "deprecated": true, + "type": "string" + }, + "liveness_probe": { + "additionalProperties": false, + "description": "Task run continuously to determine if each service replica is healthy", + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], - 'description': - 'Command that runs the http check. This field is disjunctive with `path` and `port` (only one of `command` or `path`/`port` can be set).', - }, - 'failure_threshold': { - 'default': 3, - 'description': - 'The number of times to retry a failed health check before the container is considered unhealthy', - 'type': [ - 'number', - 'string', - ], - }, - 'initial_delay': { - 'default': '0s', - 'description': 'Delays the check from running for the specified amount of time', - 'type': 'string', - }, - 'interval': { - 'default': '30s', - 'description': - 'The time period in seconds between each health check execution. You may specify any value between: 5s and 300s', - 'type': 'string', - }, - 'path': { - 'deprecated': true, - 'type': 'string', - }, - 'port': { - 'deprecated': true, - 'type': [ - 'number', - 'string', - ], - }, - 'success_threshold': { - 'default': 1, - 'description': - 'The number of times to retry a health check before the container is considered healthy', - 'type': [ - 'number', - 'string', + "type": "array" + } ], - }, - 'timeout': { - 'default': '5s', - 'description': - 'The time period to wait for a health check to succeed before it is considered a failure. You may specify any value between 2s and 60s.', - 'type': 'string', - }, + "description": "Command that runs the http check. This field is disjunctive with `path` and `port` (only one of `command` or `path`/`port` can be set)." + }, + "failure_threshold": { + "default": 3, + "description": "The number of times to retry a failed health check before the container is considered unhealthy", + "type": [ + "number", + "string" + ] + }, + "initial_delay": { + "default": "0s", + "description": "Delays the check from running for the specified amount of time", + "type": "string" + }, + "interval": { + "default": "30s", + "description": "The time period in seconds between each health check execution. You may specify any value between: 5s and 300s", + "type": "string" + }, + "path": { + "deprecated": true, + "type": "string" + }, + "port": { + "deprecated": true, + "type": [ + "number", + "string" + ] + }, + "success_threshold": { + "default": 1, + "description": "The number of times to retry a health check before the container is considered healthy", + "type": [ + "number", + "string" + ] + }, + "timeout": { + "default": "5s", + "description": "The time period to wait for a health check to succeed before it is considered a failure. You may specify any value between 2s and 60s.", + "type": "string" + } }, - 'type': 'object', - }, - 'memory': { - 'type': 'string', - }, - 'platform': { - 'type': 'string', - }, - 'replicas': { - 'description': 'Number of replicas of the deployment to run', - 'type': [ - 'number', - 'string', - ], - }, - 'scaling': { - 'additionalProperties': false, - 'description': 'Autoscaling rules for the deployment', - 'properties': { - 'max_replicas': { - 'type': [ - 'number', - 'string', - ], - }, - 'metrics': { - 'additionalProperties': false, - 'description': 'Metrics to be used to trigger autoscaling', - 'properties': { - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'memory': { - 'type': 'string', - }, - }, - 'type': 'object', - }, - 'min_replicas': { - 'type': [ - 'number', - 'string', - ], - }, + "type": "object" + }, + "memory": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "replicas": { + "description": "Number of replicas of the deployment to run", + "type": [ + "number", + "string" + ] + }, + "scaling": { + "additionalProperties": false, + "description": "Autoscaling rules for the deployment", + "properties": { + "max_replicas": { + "type": [ + "number", + "string" + ] + }, + "metrics": { + "additionalProperties": false, + "description": "Metrics to be used to trigger autoscaling", + "properties": { + "cpu": { + "type": [ + "number", + "string" + ] + }, + "memory": { + "type": "string" + } + }, + "type": "object" + }, + "min_replicas": { + "type": [ + "number", + "string" + ] + } }, - 'required': [ - 'min_replicas', - 'max_replicas', - 'metrics', + "required": [ + "min_replicas", + "max_replicas", + "metrics" ], - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'build', - 'image', + "required": [ + "build", + "image" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': 'A set of named services that need to be run and persisted in order to power this component.', - 'type': 'object', + "description": "A set of named services that need to be run and persisted in order to power this component.", + "type": "object" }, - 'static': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'description': - 'Instructions on how to build the code into static files to be put into the `directory`. Builds will be done just-in-time (JIT) for deployments to ensure that environment variables are specific to the target environment.', - 'properties': { - 'command': { - 'description': 'Command to run to build the code', - 'type': 'string', - }, - 'context': { - 'description': 'Folder to consider the context for the build relative to the component root', - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - 'boolean', - ], + "static": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "description": "Instructions on how to build the code into static files to be put into the `directory`. Builds will be done just-in-time (JIT) for deployments to ensure that environment variables are specific to the target environment.", + "properties": { + "command": { + "description": "Command to run to build the code", + "type": "string" + }, + "context": { + "description": "Folder to consider the context for the build relative to the component root", + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number", + "boolean" + ] }, - 'description': 'Environment variables to inject into the build process', - 'type': 'object', - }, + "description": "Environment variables to inject into the build process", + "type": "object" + } }, - 'required': [ - 'context', - 'command', + "required": [ + "context", + "command" ], - 'type': 'object', + "type": "object" }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'command': { - 'type': 'string', - }, - 'context': { - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - 'boolean', - ], - }, - 'type': 'object', - }, + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "command": { + "type": "string" + }, + "context": { + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number", + "boolean" + ] + }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'directory': { - 'type': 'string', + "directory": { + "type": "string" }, - 'ingress': { - 'additionalProperties': false, - 'properties': { - 'internal': { - 'type': 'boolean', - }, - 'path': { - 'type': 'string', + "ingress": { + "additionalProperties": false, + "properties": { + "internal": { + "type": "boolean" }, - 'subdomain': { - 'type': 'string', + "path": { + "type": "string" }, + "subdomain": { + "type": "string" + } }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'directory': { - 'description': 'Directory containing the static assets to be shipped to the bucket', - 'type': 'string', + "directory": { + "description": "Directory containing the static assets to be shipped to the bucket", + "type": "string" }, - 'ingress': { - 'additionalProperties': false, - 'description': 'Configure a DNS route to point to this static bucket', - 'properties': { - 'internal': { - 'default': false, - 'description': 'Whether or not this bucket should be exposed internally vs externally', - 'type': 'boolean', - }, - 'path': { - 'description': 'Sub-path to listen on when re-routing traffic from the ingress rule to the bucket', - 'type': 'string', - }, - 'subdomain': { - 'description': 'Subdomain to use for the ingress rule', - 'type': 'string', - }, + "ingress": { + "additionalProperties": false, + "description": "Configure a DNS route to point to this static bucket", + "properties": { + "internal": { + "default": false, + "description": "Whether or not this bucket should be exposed internally vs externally", + "type": "boolean" + }, + "path": { + "description": "Sub-path to listen on when re-routing traffic from the ingress rule to the bucket", + "type": "string" + }, + "subdomain": { + "description": "Subdomain to use for the ingress rule", + "type": "string" + } }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'directory', + "required": [ + "directory" ], - 'type': 'object', + "type": "object" }, - 'description': 'A set of static asset buckets to create and load with content.', - 'type': 'object', + "description": "A set of static asset buckets to create and load with content.", + "type": "object" }, - 'tasks': { - 'additionalProperties': { - 'anyOf': [ + "tasks": { + "additionalProperties": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'command': { - 'anyOf': [ + "additionalProperties": false, + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'context': { - 'type': 'string', + "context": { + "type": "string" }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', + "dockerfile": { + "type": "string" }, + "target": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], - }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'entrypoint': { - 'anyOf': [ + "type": "string" + } + ] + }, + "cpu": { + "type": [ + "number", + "string" + ] + }, + "entrypoint": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'type': 'string', + "language": { + "type": "string" }, - 'memory': { - 'type': 'string', + "memory": { + "type": "string" }, - 'platform': { - 'type': 'string', + "platform": { + "type": "string" }, - 'schedule': { - 'type': 'string', + "schedule": { + "type": "string" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'host_path': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'mount_path': { - 'type': 'string', - }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - ], - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'deprecated': true, - 'type': 'string', + "language": { + "deprecated": true, + "type": "string" }, - 'memory': { - 'type': 'string', + "memory": { + "type": "string" }, - 'platform': { - 'type': 'string', + "platform": { + "type": "string" }, - 'schedule': { - 'type': 'string', + "schedule": { + "type": "string" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'image', + "required": [ + "image" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', - }, - 'type': 'object', - }, - 'context': { - 'type': 'string', - }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', - }, + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "context": { + "type": "string" + }, + "dockerfile": { + "type": "string" + }, + "target": { + "type": "string" + } }, - 'required': [ - 'context', + "required": [ + "context" ], - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'context': { - 'type': 'string', + "context": { + "type": "string" }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', + "dockerfile": { + "type": "string" }, + "target": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], - }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'entrypoint': { - 'anyOf': [ + "type": "string" + } + ] + }, + "cpu": { + "type": [ + "number", + "string" + ] + }, + "entrypoint": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'type': 'string', + "language": { + "type": "string" }, - 'memory': { - 'type': 'string', + "memory": { + "type": "string" }, - 'platform': { - 'type': 'string', + "platform": { + "type": "string" }, - 'schedule': { - 'type': 'string', + "schedule": { + "type": "string" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'host_path': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'mount_path': { - 'type': 'string', - }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - ], - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'deprecated': true, - 'type': 'string', + "language": { + "deprecated": true, + "type": "string" }, - 'memory': { - 'type': 'string', + "memory": { + "type": "string" }, - 'platform': { - 'type': 'string', + "platform": { + "type": "string" }, - 'schedule': { - 'type': 'string', + "schedule": { + "type": "string" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'build', + "required": [ + "build" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', - }, - 'type': 'object', - }, - 'context': { - 'type': 'string', - }, - 'dockerfile': { - 'type': 'string', - }, - 'target': { - 'type': 'string', - }, + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "context": { + "type": "string" + }, + "dockerfile": { + "type": "string" + }, + "target": { + "type": "string" + } }, - 'required': [ - 'context', + "required": [ + "context" ], - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], + "cpu": { + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "properties": { + "build": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', - }, - 'context': { - 'type': 'string', + "type": "object" }, - 'dockerfile': { - 'type': 'string', + "context": { + "type": "string" }, - 'target': { - 'type': 'string', + "dockerfile": { + "type": "string" }, + "target": { + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], - }, - 'cpu': { - 'type': [ - 'number', - 'string', - ], - }, - 'entrypoint': { - 'anyOf': [ + "type": "string" + } + ] + }, + "cpu": { + "type": [ + "number", + "string" + ] + }, + "entrypoint": { + "anyOf": [ { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, { - 'type': 'string', - }, - ], + "type": "string" + } + ] }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'type': 'string', + "language": { + "type": "string" }, - 'memory': { - 'type': 'string', + "memory": { + "type": "string" }, - 'platform': { - 'type': 'string', + "platform": { + "type": "string" }, - 'schedule': { - 'type': 'string', + "schedule": { + "type": "string" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - ], - }, - 'environment': { - 'additionalProperties': { - 'type': [ - 'string', - 'number', - ], + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "environment": { + "additionalProperties": { + "type": [ + "string", + "number" + ] }, - 'type': 'object', + "type": "object" }, - 'image': { - 'type': 'string', + "image": { + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'type': 'object', + "type": "object" }, - 'language': { - 'deprecated': true, - 'type': 'string', + "language": { + "deprecated": true, + "type": "string" }, - 'memory': { - 'type': 'string', + "memory": { + "type": "string" }, - 'platform': { - 'type': 'string', + "platform": { + "type": "string" }, - 'schedule': { - 'type': 'string', + "schedule": { + "type": "string" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'type': 'string', - }, - 'host_path': { - 'type': 'string', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" }, - 'image': { - 'type': 'string', + "host_path": { + "type": "string" }, - 'mount_path': { - 'type': 'string', + "image": { + "type": "string" }, - 'readonly': { - 'type': [ - 'boolean', - 'string', - ], + "mount_path": { + "type": "string" }, + "readonly": { + "type": [ + "boolean", + "string" + ] + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'build', - 'image', + "required": [ + "build", + "image" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': - 'A set of scheduled and triggerable tasks that get registered alongside the component. Tasks are great for data translation, reporting, and much more.', - 'type': 'object', + "description": "A set of scheduled and triggerable tasks that get registered alongside the component. Tasks are great for data translation, reporting, and much more.", + "type": "object" }, - 'variables': { - 'additionalProperties': { - 'anyOf': [ + "variables": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'default': { - 'description': - 'The default value to apply to the parameter when one wasn\'t provided by the operator', - 'type': [ - 'string', - 'number', - ], - }, - 'description': { - 'description': - 'A human-readable description of the parameter, how it should be used, and what kinds of values it supports.', - 'type': 'string', - }, - 'merge': { - 'default': false, - 'description': 'Whether or not to merge results from multiple sources into a single array', - 'type': 'boolean', - }, - 'required': { - 'default': false, - 'description': 'A boolean indicating whether or not an operator is required ot provide a value', - 'type': 'boolean', - }, + "additionalProperties": false, + "properties": { + "default": { + "description": "The default value to apply to the parameter when one wasn't provided by the operator", + "type": [ + "string", + "number" + ] + }, + "description": { + "description": "A human-readable description of the parameter, how it should be used, and what kinds of values it supports.", + "type": "string" + }, + "merge": { + "default": false, + "description": "Whether or not to merge results from multiple sources into a single array", + "type": "boolean" + }, + "required": { + "default": false, + "description": "A boolean indicating whether or not an operator is required ot provide a value", + "type": "boolean" + } }, - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': - 'A dictionary of named parameters that this component uses to configure services.\n\nParameters can either be an object describing the parameter or a string shorthand that directly applies to the `default` value.\n\nThis is an alias for the `parameters` field.', - 'type': 'object', - }, - 'version': { - 'const': 'v1', - 'type': 'string', + "description": "A dictionary of named parameters that this component uses to configure services.\n\nParameters can either be an object describing the parameter or a string shorthand that directly applies to the `default` value.\n\nThis is an alias for the `parameters` field.", + "type": "object" }, + "version": { + "const": "v1", + "type": "string" + } }, - 'required': [ - 'version', + "required": [ + "version" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'builds': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "builds": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'description': 'A set of arguments to pass to the build job', - 'examples': [ + "description": "A set of arguments to pass to the build job", + "examples": [ { - 'BUILDKIT_INLINE_CACHE': '1', - }, + "BUILDKIT_INLINE_CACHE": "1" + } ], - 'type': 'object', + "type": "object" }, - 'context': { - 'description': 'Path to the folder containing the code to build', - 'examples': [ - './', + "context": { + "description": "Path to the folder containing the code to build", + "examples": [ + "./" ], - 'type': 'string', + "type": "string" }, - 'debug': { - 'additionalProperties': false, - 'description': 'Debugging options for the build step', - 'properties': { - 'args': { - 'additionalProperties': { - 'type': 'string', + "debug": { + "additionalProperties": false, + "description": "Debugging options for the build step", + "properties": { + "args": { + "additionalProperties": { + "type": "string" }, - 'description': 'A set of arguments to pass to the build job', - 'examples': [ + "description": "A set of arguments to pass to the build job", + "examples": [ { - 'BUILDKIT_INLINE_CACHE': '1', - }, + "BUILDKIT_INLINE_CACHE": "1" + } ], - 'type': 'object', + "type": "object" }, - 'context': { - 'description': 'Path to the folder containing the code to build', - 'examples': [ - './', + "context": { + "description": "Path to the folder containing the code to build", + "examples": [ + "./" ], - 'type': 'string', + "type": "string" }, - 'description': { - 'description': 'Description of the build artifact', - 'examples': [ - 'Builds the source code for the application', + "description": { + "description": "Description of the build artifact", + "examples": [ + "Builds the source code for the application" ], - 'type': 'string', - }, - 'dockerfile': { - 'default': 'Dockerfile', - 'description': 'The path to the dockerfile defining this build step', - 'type': 'string', - }, - 'image': { - 'description': - 'The resulting image that was created once the build is complete. This will change whenever the component is tagged as well.', - 'examples': [ - 'my-registry.com/my-app:latest', - ], - 'type': 'string', + "type": "string" + }, + "dockerfile": { + "default": "Dockerfile", + "description": "The path to the dockerfile defining this build step", + "type": "string" }, - 'target': { - 'description': 'The docker target to use during the build process', - 'examples': [ - 'builder', + "image": { + "description": "The resulting image that was created once the build is complete. This will change whenever the component is tagged as well.", + "examples": [ + "my-registry.com/my-app:latest" ], - 'type': 'string', + "type": "string" }, + "target": { + "description": "The docker target to use during the build process", + "examples": [ + "builder" + ], + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'description': { - 'description': 'Description of the build artifact', - 'examples': [ - 'Builds the source code for the application', + "description": { + "description": "Description of the build artifact", + "examples": [ + "Builds the source code for the application" ], - 'type': 'string', + "type": "string" }, - 'dockerfile': { - 'default': 'Dockerfile', - 'description': 'The path to the dockerfile defining this build step', - 'type': 'string', + "dockerfile": { + "default": "Dockerfile", + "description": "The path to the dockerfile defining this build step", + "type": "string" }, - 'image': { - 'description': - 'The resulting image that was created once the build is complete. This will change whenever the component is tagged as well.', - 'examples': [ - 'my-registry.com/my-app:latest', + "image": { + "description": "The resulting image that was created once the build is complete. This will change whenever the component is tagged as well.", + "examples": [ + "my-registry.com/my-app:latest" ], - 'type': 'string', + "type": "string" }, - 'target': { - 'description': 'The docker target to use during the build process', - 'examples': [ - 'builder', + "target": { + "description": "The docker target to use during the build process", + "examples": [ + "builder" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'context', + "required": [ + "context" ], - 'type': 'object', + "type": "object" }, - 'description': 'A set of build jobs to run to power other deployments or tasks', - 'type': 'object', + "description": "A set of build jobs to run to power other deployments or tasks", + "type": "object" }, - 'databases': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'description': { - 'default': '', - 'description': 'A human-readable description of the use-case for the database', - 'type': 'string', + "databases": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "default": "", + "description": "A human-readable description of the use-case for the database", + "type": "string" }, - 'migrate': { - 'additionalProperties': false, - 'description': 'Configuration details for how to run database migrations', - 'properties': { - 'command': { - 'anyOf': [ + "migrate": { + "additionalProperties": false, + "description": "Configuration details for how to run database migrations", + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'description': 'A command to run in the container to execute the migration', - 'examples': [ + "description": "A command to run in the container to execute the migration", + "examples": [ [ - 'npm', - 'run', - 'migrate', - ], - ], - }, - 'entrypoint': { - 'anyOf': [ + "npm", + "run", + "migrate" + ] + ] + }, + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'default': [ - '', + "default": [ + "" ], - 'description': 'An entrypoint to use for the docker container', + "description": "An entrypoint to use for the docker container" }, - 'environment': { - 'additionalProperties': { - 'anyOf': [ + "environment": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'type': 'number', + "type": "number" }, { - 'type': 'boolean', + "type": "boolean" }, { - 'type': 'null', + "type": "null" }, { - 'not': {}, - }, - ], + "not": {} + } + ] }, - 'description': 'Environment variables to set in the container', - 'examples': [ + "description": "Environment variables to set in the container", + "examples": [ { - 'DATABASE_URL': '${{ databases.auth.url }}', - }, + "DATABASE_URL": "${{ databases.auth.url }}" + } ], - 'type': 'object', + "type": "object" }, - 'image': { - 'description': 'The docker image containing the migration tooling and files', - 'examples': [ - '${{ builds.api.image }}', + "image": { + "description": "The docker image containing the migration tooling and files", + "examples": [ + "${{ builds.api.image }}" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'image', + "required": [ + "image" ], - 'type': 'object', + "type": "object" }, - 'seed': { - 'additionalProperties': false, - 'description': 'Configuration details for how to seed the database', - 'properties': { - 'command': { - 'anyOf': [ + "seed": { + "additionalProperties": false, + "description": "Configuration details for how to seed the database", + "properties": { + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'description': 'A command to run in the container to execute the seeding', - 'examples': [ + "description": "A command to run in the container to execute the seeding", + "examples": [ [ - 'npm', - 'run', - 'seed', - ], - ], - }, - 'entrypoint': { - 'anyOf': [ + "npm", + "run", + "seed" + ] + ] + }, + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'default': [ - '', + "default": [ + "" ], - 'description': 'An entrypoint to use for the docker container', + "description": "An entrypoint to use for the docker container" }, - 'environment': { - 'additionalProperties': { - 'anyOf': [ + "environment": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'type': 'number', + "type": "number" }, { - 'type': 'boolean', + "type": "boolean" }, { - 'type': 'null', + "type": "null" }, { - 'not': {}, - }, - ], + "not": {} + } + ] }, - 'description': 'Environment variables to set in the container', - 'examples': [ + "description": "Environment variables to set in the container", + "examples": [ { - 'DATABASE_URL': '${{ databases.auth.url }}', - }, + "DATABASE_URL": "${{ databases.auth.url }}" + } ], - 'type': 'object', + "type": "object" }, - 'image': { - 'description': 'The docker image containing the seeding tooling and files', - 'examples': [ - '${{ builds.api.image }}', + "image": { + "description": "The docker image containing the seeding tooling and files", + "examples": [ + "${{ builds.api.image }}" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'image', + "required": [ + "image" ], - 'type': 'object', + "type": "object" }, - 'type': { - 'description': 'The type of database and version to use', - 'examples': [ - 'postgres:15', - 'mysql:8', - 'redis:5', + "type": { + "description": "The type of database and version to use", + "examples": [ + "postgres:15", + "mysql:8", + "redis:5" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'type', + "required": [ + "type" ], - 'type': 'object', + "type": "object" }, - 'description': 'A set of databases that this component requires', - 'type': 'object', + "description": "A set of databases that this component requires", + "type": "object" }, - 'dependencies': { - 'additionalProperties': { - 'anyOf': [ + "dependencies": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'additionalProperties': false, - 'properties': { - 'component': { - 'description': 'The repo the component is in', - 'type': 'string', - }, - 'variables': { - 'additionalProperties': { - 'anyOf': [ + "additionalProperties": false, + "properties": { + "component": { + "description": "The repo the component is in", + "type": "string" + }, + "variables": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'description': 'Input values to provide to the component if `merge` is turned on', - 'examples': [ + "description": "Input values to provide to the component if `merge` is turned on", + "examples": [ { - 'allowed_return_urls': [ - 'https://architect.io', - '${{ ingresses.frontend.url }}', - ], - }, + "allowed_return_urls": [ + "https://architect.io", + "${{ ingresses.frontend.url }}" + ] + } ], - 'type': 'object', - }, + "type": "object" + } }, - 'required': [ - 'component', + "required": [ + "component" ], - 'type': 'object', - }, - ], + "type": "object" + } + ] }, - 'description': 'A set of other components that this component depends on', - 'examples': [ + "description": "A set of other components that this component depends on", + "examples": [ { - 'auth': { - 'component': 'architect/auth-component', - 'variables': { - 'allowed_return_urls': [ - 'https://architect.io', - '${{ ingresses.frontend.url }}', - ], - }, + "auth": { + "component": "architect/auth-component", + "variables": { + "allowed_return_urls": [ + "https://architect.io", + "${{ ingresses.frontend.url }}" + ] + } }, - 'payments': 'architect/payments-component', - }, + "payments": "architect/payments-component" + } ], - 'type': 'object', + "type": "object" }, - 'deployments': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'autoscaling': { - 'additionalProperties': false, - 'description': 'Configuration settings for how to automatically scale the application up and down', - 'properties': { - 'cpu': { - 'description': 'Maximum number of CPUs to allocate to each replica', - 'examples': [ - '0.5', - '1', + "deployments": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "autoscaling": { + "additionalProperties": false, + "description": "Configuration settings for how to automatically scale the application up and down", + "properties": { + "cpu": { + "description": "Maximum number of CPUs to allocate to each replica", + "examples": [ + "0.5", + "1" ], - 'type': [ - 'number', - 'string', + "type": [ + "number", + "string" + ] + }, + "memory": { + "description": "Maximum memory usage per replica before scaling up", + "examples": [ + "200Mi", + "2Gi" ], - }, - 'memory': { - 'description': 'Maximum memory usage per replica before scaling up', - 'examples': [ - '200Mi', - '2Gi', - ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'description': 'Command to use when the container is booted up', - 'examples': [ + "description": "Command to use when the container is booted up", + "examples": [ [ - 'npm', - 'start', - ], - ], + "npm", + "start" + ] + ] }, - 'cpu': { - 'description': 'The amount of CPU to allocate to each instance of the deployment', - 'type': [ - 'number', - 'string', - ], + "cpu": { + "description": "The amount of CPU to allocate to each instance of the deployment", + "type": [ + "number", + "string" + ] }, - 'debug': { - 'additionalProperties': false, - 'description': 'Debugging options for the deployment', - 'properties': { - 'autoscaling': { - 'additionalProperties': false, - 'description': 'Configuration settings for how to automatically scale the application up and down', - 'properties': { - 'cpu': { - 'description': 'Maximum number of CPUs to allocate to each replica', - 'examples': [ - '0.5', - '1', + "debug": { + "additionalProperties": false, + "description": "Debugging options for the deployment", + "properties": { + "autoscaling": { + "additionalProperties": false, + "description": "Configuration settings for how to automatically scale the application up and down", + "properties": { + "cpu": { + "description": "Maximum number of CPUs to allocate to each replica", + "examples": [ + "0.5", + "1" ], - 'type': [ - 'number', - 'string', + "type": [ + "number", + "string" + ] + }, + "memory": { + "description": "Maximum memory usage per replica before scaling up", + "examples": [ + "200Mi", + "2Gi" ], - }, - 'memory': { - 'description': 'Maximum memory usage per replica before scaling up', - 'examples': [ - '200Mi', - '2Gi', - ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'command': { - 'anyOf': [ + "command": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'description': 'Command to use when the container is booted up', - 'examples': [ + "description": "Command to use when the container is booted up", + "examples": [ [ - 'npm', - 'start', - ], - ], - }, - 'cpu': { - 'description': 'The amount of CPU to allocate to each instance of the deployment', - 'type': [ - 'number', - 'string', + "npm", + "start" + ] + ] + }, + "cpu": { + "description": "The amount of CPU to allocate to each instance of the deployment", + "type": [ + "number", + "string" + ] + }, + "description": { + "description": "Human readable description of the deployment", + "examples": [ + "Runs the frontend web application" ], + "type": "string" }, - 'description': { - 'description': 'Human readable description of the deployment', - 'examples': [ - 'Runs the frontend web application', - ], - 'type': 'string', - }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'default': [ - '', + "default": [ + "" ], - 'description': 'The executable to run every time the container is booted up', + "description": "The executable to run every time the container is booted up" }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': 'Environment variables to pass to the service', - 'examples': [ + "description": "Environment variables to pass to the service", + "examples": [ { - 'NODE_ENV': 'production', + "NODE_ENV": "production" }, { - 'BACKEND_URL': '${{ ingresses.backend.url }}', - }, + "BACKEND_URL": "${{ ingresses.backend.url }}" + } ], - 'type': 'object', + "type": "object" }, - 'image': { - 'description': 'Docker image to use for the deployment', - 'examples': [ - '${{ builds.frontend.image }}', - 'my-registry.com/my-app:latest', + "image": { + "description": "Docker image to use for the deployment", + "examples": [ + "${{ builds.frontend.image }}", + "my-registry.com/my-app:latest" ], - 'type': 'string', + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'description': 'The labels to apply to the deployment', - 'type': 'object', + "description": "The labels to apply to the deployment", + "type": "object" }, - 'memory': { - 'description': 'The amount of memory to allocate to each instance of the deployment', - 'type': 'string', + "memory": { + "description": "The amount of memory to allocate to each instance of the deployment", + "type": "string" }, - 'platform': { - 'description': 'Set platform if server is multi-platform capable', - 'examples': [ - 'linux/amd64', + "platform": { + "description": "Set platform if server is multi-platform capable", + "examples": [ + "linux/amd64" ], - 'type': 'string', - }, - 'probes': { - 'additionalProperties': false, - 'description': 'Configuration details for probes that check each replicas status', - 'properties': { - 'liveness': { - 'anyOf': [ + "type": "string" + }, + "probes": { + "additionalProperties": false, + "description": "Configuration details for probes that check each replicas status", + "properties": { + "liveness": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'command': { - 'description': 'Command to run inside the container to determine if its healthy', - 'items': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "command": { + "description": "Command to run inside the container to determine if its healthy", + "items": { + "type": "string" }, - 'type': 'array', + "type": "array" }, - 'failure_threshold': { - 'default': 3, - 'description': - 'Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.', - 'minimum': 1, - 'type': 'number', + "failure_threshold": { + "default": 3, + "description": "Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.", + "minimum": 1, + "type": "number" }, - 'initial_delay': { - 'default': 0, - 'description': - 'Number of seconds after the container starts before the probe is initiated.', - 'minimum': 0, - 'type': 'number', + "initial_delay": { + "default": 0, + "description": "Number of seconds after the container starts before the probe is initiated.", + "minimum": 0, + "type": "number" }, - 'interval': { - 'default': 10, - 'description': 'How often (in seconds) to perform the probe.', - 'minimum': 1, - 'type': 'number', + "interval": { + "default": 10, + "description": "How often (in seconds) to perform the probe.", + "minimum": 1, + "type": "number" }, - 'success_threshold': { - 'default': 1, - 'description': - 'Minimum consecutive successes for the probe to be considered successful after having failed.', - 'minimum': 1, - 'type': 'number', + "success_threshold": { + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.", + "minimum": 1, + "type": "number" }, - 'timeout': { - 'default': 1, - 'description': 'Number of seconds after which the probe times out', - 'minimum': 1, - 'type': 'number', + "timeout": { + "default": 1, + "description": "Number of seconds after which the probe times out", + "minimum": 1, + "type": "number" }, - 'type': { - 'const': 'exec', - 'type': 'string', - }, - }, - 'required': [ - 'command', - 'type', + "type": { + "const": "exec", + "type": "string" + } + }, + "required": [ + "command", + "type" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'failure_threshold': { - 'default': 3, - 'description': - 'Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.', - 'minimum': 1, - 'type': 'number', + "additionalProperties": false, + "properties": { + "failure_threshold": { + "default": 3, + "description": "Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.", + "minimum": 1, + "type": "number" }, - 'headers': { - 'description': 'Custom headers to set in the request.', - 'items': { - 'additionalProperties': false, - 'properties': { - 'name': { - 'type': 'string', - }, - 'value': { - 'type': 'string', + "headers": { + "description": "Custom headers to set in the request.", + "items": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" }, + "value": { + "type": "string" + } }, - 'required': [ - 'name', - 'value', + "required": [ + "name", + "value" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'initial_delay': { - 'default': 0, - 'description': - 'Number of seconds after the container starts before the probe is initiated.', - 'minimum': 0, - 'type': 'number', + "initial_delay": { + "default": 0, + "description": "Number of seconds after the container starts before the probe is initiated.", + "minimum": 0, + "type": "number" }, - 'interval': { - 'default': 10, - 'description': 'How often (in seconds) to perform the probe.', - 'minimum': 1, - 'type': 'number', + "interval": { + "default": 10, + "description": "How often (in seconds) to perform the probe.", + "minimum": 1, + "type": "number" }, - 'path': { - 'default': '/', - 'description': 'Path to access on the http server', - 'type': 'string', + "path": { + "default": "/", + "description": "Path to access on the http server", + "type": "string" }, - 'port': { - 'description': 'Port to access on the container', - 'type': 'number', + "port": { + "description": "Port to access on the container", + "type": "number" }, - 'scheme': { - 'default': 'http', - 'description': 'Scheme to use for connecting to the host (http or https).', - 'type': 'string', + "scheme": { + "default": "http", + "description": "Scheme to use for connecting to the host (http or https).", + "type": "string" }, - 'success_threshold': { - 'default': 1, - 'description': - 'Minimum consecutive successes for the probe to be considered successful after having failed.', - 'minimum': 1, - 'type': 'number', + "success_threshold": { + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.", + "minimum": 1, + "type": "number" }, - 'timeout': { - 'default': 1, - 'description': 'Number of seconds after which the probe times out', - 'minimum': 1, - 'type': 'number', - }, - 'type': { - 'const': 'http', - 'type': 'string', + "timeout": { + "default": 1, + "description": "Number of seconds after which the probe times out", + "minimum": 1, + "type": "number" }, + "type": { + "const": "http", + "type": "string" + } }, - 'required': [ - 'type', + "required": [ + "type" ], - 'type': 'object', - }, + "type": "object" + } ], - 'description': - 'Configuration settings to determine if the deployment is ready to receive traffic', - }, + "description": "Configuration settings to determine if the deployment is ready to receive traffic" + } }, - 'type': 'object', - }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'Path on the host machine to sync with the volume', - 'examples': [ - '/Users/batman/app/src', + "type": "object" + }, + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "Path on the host machine to sync with the volume", + "examples": [ + "/Users/batman/app/src" ], - 'type': 'string', + "type": "string" }, - 'image': { - 'description': 'OCI image containing the contents to seed the volume with', - 'type': 'string', + "image": { + "description": "OCI image containing the contents to seed the volume with", + "type": "string" }, - 'mount_path': { - 'description': 'Path inside the container to mount the volume to', - 'examples': [ - '/app/src', + "mount_path": { + "description": "Path inside the container to mount the volume to", + "examples": [ + "/app/src" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'description': 'Volumes that should be created and attached to each replica', - 'type': 'object', - }, + "description": "Volumes that should be created and attached to each replica", + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'description': { - 'description': 'Human readable description of the deployment', - 'examples': [ - 'Runs the frontend web application', + "description": { + "description": "Human readable description of the deployment", + "examples": [ + "Runs the frontend web application" ], - 'type': 'string', + "type": "string" }, - 'entrypoint': { - 'anyOf': [ + "entrypoint": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, + "type": "array" + } ], - 'default': [ - '', + "default": [ + "" ], - 'description': 'The executable to run every time the container is booted up', + "description": "The executable to run every time the container is booted up" }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': 'Environment variables to pass to the service', - 'examples': [ + "description": "Environment variables to pass to the service", + "examples": [ { - 'NODE_ENV': 'production', + "NODE_ENV": "production" }, { - 'BACKEND_URL': '${{ ingresses.backend.url }}', - }, + "BACKEND_URL": "${{ ingresses.backend.url }}" + } ], - 'type': 'object', + "type": "object" }, - 'image': { - 'description': 'Docker image to use for the deployment', - 'examples': [ - '${{ builds.frontend.image }}', - 'my-registry.com/my-app:latest', + "image": { + "description": "Docker image to use for the deployment", + "examples": [ + "${{ builds.frontend.image }}", + "my-registry.com/my-app:latest" ], - 'type': 'string', + "type": "string" }, - 'labels': { - 'additionalProperties': { - 'type': 'string', + "labels": { + "additionalProperties": { + "type": "string" }, - 'description': 'The labels to apply to the deployment', - 'type': 'object', + "description": "The labels to apply to the deployment", + "type": "object" }, - 'memory': { - 'description': 'The amount of memory to allocate to each instance of the deployment', - 'type': 'string', + "memory": { + "description": "The amount of memory to allocate to each instance of the deployment", + "type": "string" }, - 'platform': { - 'description': 'Set platform if server is multi-platform capable', - 'examples': [ - 'linux/amd64', + "platform": { + "description": "Set platform if server is multi-platform capable", + "examples": [ + "linux/amd64" ], - 'type': 'string', + "type": "string" }, - 'probes': { - 'additionalProperties': false, - 'description': 'Configuration details for probes that check each replicas status', - 'properties': { - 'liveness': { - 'anyOf': [ + "probes": { + "additionalProperties": false, + "description": "Configuration details for probes that check each replicas status", + "properties": { + "liveness": { + "anyOf": [ { - 'additionalProperties': false, - 'properties': { - 'command': { - 'description': 'Command to run inside the container to determine if its healthy', - 'items': { - 'type': 'string', - }, - 'type': 'array', - }, - 'failure_threshold': { - 'default': 3, - 'description': - 'Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.', - 'minimum': 1, - 'type': 'number', - }, - 'initial_delay': { - 'default': 0, - 'description': - 'Number of seconds after the container starts before the probe is initiated.', - 'minimum': 0, - 'type': 'number', - }, - 'interval': { - 'default': 10, - 'description': 'How often (in seconds) to perform the probe.', - 'minimum': 1, - 'type': 'number', - }, - 'success_threshold': { - 'default': 1, - 'description': - 'Minimum consecutive successes for the probe to be considered successful after having failed.', - 'minimum': 1, - 'type': 'number', - }, - 'timeout': { - 'default': 1, - 'description': 'Number of seconds after which the probe times out', - 'minimum': 1, - 'type': 'number', - }, - 'type': { - 'const': 'exec', - 'type': 'string', - }, - }, - 'required': [ - 'command', - 'type', + "additionalProperties": false, + "properties": { + "command": { + "description": "Command to run inside the container to determine if its healthy", + "items": { + "type": "string" + }, + "type": "array" + }, + "failure_threshold": { + "default": 3, + "description": "Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.", + "minimum": 1, + "type": "number" + }, + "initial_delay": { + "default": 0, + "description": "Number of seconds after the container starts before the probe is initiated.", + "minimum": 0, + "type": "number" + }, + "interval": { + "default": 10, + "description": "How often (in seconds) to perform the probe.", + "minimum": 1, + "type": "number" + }, + "success_threshold": { + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.", + "minimum": 1, + "type": "number" + }, + "timeout": { + "default": 1, + "description": "Number of seconds after which the probe times out", + "minimum": 1, + "type": "number" + }, + "type": { + "const": "exec", + "type": "string" + } + }, + "required": [ + "command", + "type" ], - 'type': 'object', + "type": "object" }, { - 'additionalProperties': false, - 'properties': { - 'failure_threshold': { - 'default': 3, - 'description': - 'Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.', - 'minimum': 1, - 'type': 'number', - }, - 'headers': { - 'description': 'Custom headers to set in the request.', - 'items': { - 'additionalProperties': false, - 'properties': { - 'name': { - 'type': 'string', - }, - 'value': { - 'type': 'string', + "additionalProperties": false, + "properties": { + "failure_threshold": { + "default": 3, + "description": "Number of times the probe will tolerate failure before giving up. Giving up in the case of liveness probe means restarting the container.", + "minimum": 1, + "type": "number" + }, + "headers": { + "description": "Custom headers to set in the request.", + "items": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" }, + "value": { + "type": "string" + } }, - 'required': [ - 'name', - 'value', + "required": [ + "name", + "value" ], - 'type': 'object', - }, - 'type': 'array', - }, - 'initial_delay': { - 'default': 0, - 'description': - 'Number of seconds after the container starts before the probe is initiated.', - 'minimum': 0, - 'type': 'number', - }, - 'interval': { - 'default': 10, - 'description': 'How often (in seconds) to perform the probe.', - 'minimum': 1, - 'type': 'number', - }, - 'path': { - 'default': '/', - 'description': 'Path to access on the http server', - 'type': 'string', - }, - 'port': { - 'description': 'Port to access on the container', - 'type': 'number', - }, - 'scheme': { - 'default': 'http', - 'description': 'Scheme to use for connecting to the host (http or https).', - 'type': 'string', - }, - 'success_threshold': { - 'default': 1, - 'description': - 'Minimum consecutive successes for the probe to be considered successful after having failed.', - 'minimum': 1, - 'type': 'number', - }, - 'timeout': { - 'default': 1, - 'description': 'Number of seconds after which the probe times out', - 'minimum': 1, - 'type': 'number', - }, - 'type': { - 'const': 'http', - 'type': 'string', - }, - }, - 'required': [ - 'type', + "type": "object" + }, + "type": "array" + }, + "initial_delay": { + "default": 0, + "description": "Number of seconds after the container starts before the probe is initiated.", + "minimum": 0, + "type": "number" + }, + "interval": { + "default": 10, + "description": "How often (in seconds) to perform the probe.", + "minimum": 1, + "type": "number" + }, + "path": { + "default": "/", + "description": "Path to access on the http server", + "type": "string" + }, + "port": { + "description": "Port to access on the container", + "type": "number" + }, + "scheme": { + "default": "http", + "description": "Scheme to use for connecting to the host (http or https).", + "type": "string" + }, + "success_threshold": { + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.", + "minimum": 1, + "type": "number" + }, + "timeout": { + "default": 1, + "description": "Number of seconds after which the probe times out", + "minimum": 1, + "type": "number" + }, + "type": { + "const": "http", + "type": "string" + } + }, + "required": [ + "type" ], - 'type': 'object', - }, + "type": "object" + } ], - 'description': 'Configuration settings to determine if the deployment is ready to receive traffic', - }, + "description": "Configuration settings to determine if the deployment is ready to receive traffic" + } }, - 'type': 'object', + "type": "object" }, - 'volumes': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'Path on the host machine to sync with the volume', - 'examples': [ - '/Users/batman/app/src', + "volumes": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "Path on the host machine to sync with the volume", + "examples": [ + "/Users/batman/app/src" ], - 'type': 'string', + "type": "string" }, - 'image': { - 'description': 'OCI image containing the contents to seed the volume with', - 'type': 'string', + "image": { + "description": "OCI image containing the contents to seed the volume with", + "type": "string" }, - 'mount_path': { - 'description': 'Path inside the container to mount the volume to', - 'examples': [ - '/app/src', + "mount_path": { + "description": "Path inside the container to mount the volume to", + "examples": [ + "/app/src" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'mount_path', + "required": [ + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'description': 'Volumes that should be created and attached to each replica', - 'type': 'object', - }, + "description": "Volumes that should be created and attached to each replica", + "type": "object" + } }, - 'required': [ - 'image', + "required": [ + "image" ], - 'type': 'object', + "type": "object" }, - 'description': 'Workloads that should be deployed', - 'type': 'object', + "description": "Workloads that should be deployed", + "type": "object" }, - 'ingresses': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'headers': { - 'additionalProperties': { - 'type': 'string', + "ingresses": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "headers": { + "additionalProperties": { + "type": "string" }, - 'description': 'Additional headers to include in responses', - 'examples': [ + "description": "Additional headers to include in responses", + "examples": [ { - 'Access-Control-Allow-Credentials': 'true', - 'Access-Control-Allow-Origin': '${{ variables.allowed_return_urls }}', - }, + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "${{ variables.allowed_return_urls }}" + } ], - 'type': 'object', + "type": "object" }, - 'internal': { - 'default': false, - 'description': 'Whether or not the ingress rule should be attached to an internal gateway', - 'type': 'boolean', + "internal": { + "default": false, + "description": "Whether or not the ingress rule should be attached to an internal gateway", + "type": "boolean" }, - 'service': { - 'description': 'Service the ingress rule forwards traffic to', - 'examples': [ - 'backend', + "service": { + "description": "Service the ingress rule forwards traffic to", + "examples": [ + "backend" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'service', + "required": [ + "service" ], - 'type': 'object', + "type": "object" }, - 'description': 'Claims for external (e.g. client) access to a service', - 'type': 'object', + "description": "Claims for external (e.g. client) access to a service", + "type": "object" }, - 'services': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'deployment': { - 'description': 'Deployment the service sends requests to', - 'examples': [ - 'backend', + "services": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "deployment": { + "description": "Deployment the service sends requests to", + "examples": [ + "backend" ], - 'type': 'string', + "type": "string" }, - 'description': { - 'description': 'Description of the service', - 'examples': [ - 'Exposes the backend to other applications', + "description": { + "description": "Description of the service", + "examples": [ + "Exposes the backend to other applications" ], - 'type': 'string', + "type": "string" }, - 'password': { - 'description': 'Basic auth password', - 'type': 'string', + "password": { + "description": "Basic auth password", + "type": "string" }, - 'port': { - 'description': 'Port the service listens on', - 'examples': [ - 8080, + "port": { + "description": "Port the service listens on", + "examples": [ + 8080 ], - 'type': 'number', + "type": "number" }, - 'protocol': { - 'default': 'http', - 'description': 'Protocol the service listens on', - 'type': 'string', - }, - 'username': { - 'description': 'Basic auth username', - 'type': 'string', + "protocol": { + "default": "http", + "description": "Protocol the service listens on", + "type": "string" }, + "username": { + "description": "Basic auth username", + "type": "string" + } }, - 'required': [ - 'deployment', - 'port', + "required": [ + "deployment", + "port" ], - 'type': 'object', + "type": "object" }, - 'description': 'Services that can receive network traffic', - 'type': 'object', + "description": "Services that can receive network traffic", + "type": "object" }, - 'variables': { - 'additionalProperties': { - 'additionalProperties': false, - 'properties': { - 'default': { - 'anyOf': [ + "variables": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "default": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], - 'description': 'A default value to use if one isn\'t provided', - 'examples': [ - 'https://architect.io', + "type": "array" + } ], + "description": "A default value to use if one isn't provided", + "examples": [ + "https://architect.io" + ] }, - 'description': { - 'description': 'A human-readable description', - 'examples': [ - 'API key used to authenticate with Stripe', + "description": { + "description": "A human-readable description", + "examples": [ + "API key used to authenticate with Stripe" ], - 'type': 'string', + "type": "string" }, - 'merge': { - 'default': false, - 'description': - 'If true, upstream components can pass in values that will be merged together with each other and environment-provided values', - 'type': 'boolean', + "merge": { + "default": false, + "description": "If true, upstream components can pass in values that will be merged together with each other and environment-provided values", + "type": "boolean" }, - 'required': { - 'default': false, - 'description': 'If true, a value is required or the component won\'t run.', - 'type': 'boolean', - }, - 'sensitive': { - 'default': false, - 'description': 'Whether or not the data should be considered sensitive and stripped from logs', - 'type': 'boolean', + "required": { + "default": false, + "description": "If true, a value is required or the component won't run.", + "type": "boolean" }, + "sensitive": { + "default": false, + "description": "Whether or not the data should be considered sensitive and stripped from logs", + "type": "boolean" + } }, - 'type': 'object', + "type": "object" }, - 'description': 'A set of inputs the component expects to be provided', - 'type': 'object', - }, - 'version': { - 'const': 'v2', - 'type': 'string', + "description": "A set of inputs the component expects to be provided", + "type": "object" }, + "version": { + "const": "v2", + "type": "string" + } }, - 'required': [ - 'version', + "required": [ + "version" ], - 'type': 'object', - }, + "type": "object" + } ], - '$schema': 'https://json-schema.org/draft/2019-09/schema', - '$id': 'https://architect.io/.schemas/component.json', - 'type': 'object', - 'required': [ - 'version', + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://architect.io/.schemas/component.json", + "type": "object", + "required": [ + "version" ], - 'discriminator': { - 'propertyName': 'version', - }, -}; + "discriminator": { + "propertyName": "version" + } +} \ No newline at end of file diff --git a/src/components/schema.ts b/src/components/schema.ts index 44c54456a..a54e99350 100644 --- a/src/components/schema.ts +++ b/src/components/schema.ts @@ -7,7 +7,8 @@ export type ComponentSchema = } & component_v1) | ({ version: 'v2'; - } & component_v2); + } & component_v2) +; export const buildComponent = (data: ComponentSchema) => { switch (data.version) { diff --git a/src/components/v1/schema.json b/src/components/v1/schema.json index 82168752b..9dd080cc2 100644 --- a/src/components/v1/schema.json +++ b/src/components/v1/schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/ComponentV1", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "ComponentV1": { "additionalProperties": false, diff --git a/src/components/v2/schema.json b/src/components/v2/schema.json index 48158b4b9..f8a90f341 100644 --- a/src/components/v2/schema.json +++ b/src/components/v2/schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/ComponentV2", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "ComponentV2": { "additionalProperties": false, diff --git a/src/datacenter-modules/client.ts b/src/datacenter-modules/client.ts deleted file mode 100644 index ad4cd3804..000000000 --- a/src/datacenter-modules/client.ts +++ /dev/null @@ -1,72 +0,0 @@ -import * as path from 'std/path/mod.ts'; -import { Logger } from 'winston'; -import { ApplyOptions, ApplyRequest, ApplyResponse, BuildOptions, BuildRequest, BuildResponse } from './types.ts'; - -export class ModuleClient { - private socket: WebSocket; - private stateFilePath: string; - - constructor(port: number, stateFileDir: string) { - this.socket = new WebSocket(`ws://localhost:${port}/ws`); - this.stateFilePath = path.join(stateFileDir, 'statefile.txt'); - } - - private request( - command: string, - request: R, - logger?: Logger, - ): Promise { - return new Promise((resolve, reject) => { - this.socket.addEventListener('open', () => { - this.socket.send(JSON.stringify({ - command, - request, - })); - }); - - this.socket.addEventListener('message', (event) => { - try { - const evt = JSON.parse(event.data); - if (evt.verboseOutput) { - if (logger) { - logger.info(evt.verboseOutput); - } - } else if (evt.error) { - reject(evt.error); - } else if (evt.result) { - resolve(evt.result); - } - } catch (e) { - // Failed to parse message, invalid response - reject(e); - } - }); - - this.socket.addEventListener('error', async (event) => { - // Likely couldn't connect to plugin server - if (event instanceof ErrorEvent) { - return reject(event.message); - } - reject(event); - }); - }); - } - - public async build(buildRequest: BuildRequest, options?: BuildOptions): Promise { - return this.request('build', buildRequest, options?.logger) as Promise; - } - - public async apply(applyRequest: ApplyRequest, options?: ApplyOptions): Promise { - // Write the state to the mounted path - if (applyRequest.state) { - Deno.writeTextFileSync(this.stateFilePath, applyRequest.state); - // Replace state with the path to the state - applyRequest.state = this.stateFilePath; - } - return this.request('apply', applyRequest, options?.logger) as Promise; - } - - public close() { - this.socket.close(1000); - } -} diff --git a/src/datacenter-modules/index.ts b/src/datacenter-modules/index.ts deleted file mode 100644 index 9c17925ef..000000000 --- a/src/datacenter-modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './client.ts'; -export * from './server.ts'; -export * from './types.ts'; diff --git a/src/datacenter-modules/server.ts b/src/datacenter-modules/server.ts deleted file mode 100644 index 2584fac85..000000000 --- a/src/datacenter-modules/server.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { ModuleClient } from './client.ts'; -import { Plugin } from './types.ts'; - -export class ModuleServer { - private plugin: Plugin; - private proc?: Deno.ChildProcess; - private containerName?: string; - private dev_pulumi_plugin_port?: string; - private dev_opentofu_plugin_port?: string; - private dev_state_dir?: string; - - constructor(plugin: Plugin) { - this.plugin = plugin; - this.dev_pulumi_plugin_port = Deno.env.get('DEV_PULUMI_PLUGIN_PORT'); - this.dev_opentofu_plugin_port = Deno.env.get('DEV_OPENTOFU_PLUGIN_PORT'); - // This should be set to whatever directory is used in the first half of `-v /path/to/state/dir:/state` - // which should be set when running the plugin manually, e.g. DEV_STATE_DIR="/path/to/state/dir". - // The statefile for each pipeline step will be written to this directory. - this.dev_state_dir = Deno.env.get('DEV_STATE_DIR'); - } - - private async getPort(containerName: string): Promise { - const getPortCmd = new Deno.Command('docker', { args: ['port', containerName] }); - const { stdout: outputChunk } = await getPortCmd.output(); - const portOutput = new TextDecoder().decode(outputChunk); - const portMatches = portOutput.match(/-> 0\.0\.0\.0:([0-9]{1,5})/); - if (!portMatches) { - throw new Error('Failed to get port from container'); - } - - return parseInt(portMatches[1]); - } - - /** - * Start the websocket server for the plugin type and return the port its available on - */ - async start(directory?: string): Promise { - const pluginImage = `architectio/${this.plugin}-plugin`; - this.containerName = pluginImage.replace('/', '-') + '-' + Date.now(); - - if (this.dev_pulumi_plugin_port || this.dev_opentofu_plugin_port) { - if (!this.dev_state_dir) { - throw Error('DEV_STATE_DIR must be set when using dev mode.'); - } - if (this.dev_pulumi_plugin_port && this.plugin === 'pulumi') { - return new ModuleClient(parseInt(this.dev_pulumi_plugin_port), this.dev_state_dir); - } else if (this.dev_opentofu_plugin_port && this.plugin === 'opentofu') { - return new ModuleClient(parseInt(this.dev_opentofu_plugin_port), this.dev_state_dir); - } - } - - const stateFileDir = Deno.makeTempDirSync({ prefix: 'state' }); - const command = new Deno.Command('docker', { - args: [ - 'run', - '--name', - this.containerName, - '--rm', - '--pull', - 'missing', // TODO: version the plugins with the CLI to ensure that the right version of the plugin will be pulled if - '--quiet', // ignore the docker error 'unable to find image locally if the image needs to be downloaded - '-p', - '50051', - // Allows mounting from the host to the Docker-In-Docker container - '-v', - '/var/run/docker.sock:/var/run/docker.sock', - // Creates a volume that can be used to pass the input state or various other data to the DinD container - '-v', - `${stateFileDir}:/state`, - // Mounts the volume for the module itself - ...(directory ? ['-v', `${directory}:${directory}`] : []), - pluginImage, - ], - stdout: 'piped', - stderr: 'piped', - }); - const process = command.spawn(); - - return new Promise((resolve, reject) => { - // Resolve once we see the server has started in the container. - process.stdout.pipeTo( - new WritableStream({ - write: async (chunk) => { - const output = new TextDecoder().decode(chunk); - if (output.includes('Started server on port')) { - const port = await this.getPort(this.containerName!); - this.proc = process; - resolve(new ModuleClient(port, stateFileDir)); - } - }, - }), - ); - - process.stderr.pipeTo( - new WritableStream({ - write(chunk) { - const error = new TextDecoder().decode(chunk); - reject(error); - }, - }), - ); - }); - } - - async stop(): Promise { - if (this.dev_pulumi_plugin_port || this.dev_opentofu_plugin_port) { - return; // dev plugin server was run directly and shouldn't be stopped - } - const command = new Deno.Command('docker', { - args: [ - 'stop', - this.containerName!, - ], - stdout: 'piped', - stderr: 'piped', - }); - const stopProccess = command.spawn(); - await stopProccess.status; - await this.proc?.status; - } -} diff --git a/src/datacenter-modules/types.ts b/src/datacenter-modules/types.ts deleted file mode 100644 index ac926a68e..000000000 --- a/src/datacenter-modules/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Logger } from 'winston'; - -export const PluginArray = ['pulumi', 'opentofu'] as const; -export type Plugin = typeof PluginArray[number]; -export function isPlugin(x: unknown): x is Plugin { - return PluginArray.includes(x as any); -} - -export type BuildRequest = { - directory: string; - platform?: string; -}; - -export type BuildOptions = { - logger?: Logger; -}; - -export type BuildResponse = { - image: string; -}; - -export type ApplyRequest = { - datacenterid: string; - image: string; - inputs: Record; - environment?: Record; - volumes?: { - host_path: string; - mount_path: string; - }[]; - state?: string; - destroy?: boolean; -}; - -export type ApplyResponse = { - state: string; - outputs: Record; -}; - -export type ApplyOptions = { - logger?: Logger; -}; diff --git a/src/datacenters/datacenter-schema.ts b/src/datacenters/datacenter-schema.ts index c2357b9db..c7f4b357a 100644 --- a/src/datacenters/datacenter-schema.ts +++ b/src/datacenters/datacenter-schema.ts @@ -1,1977 +1,1814 @@ export default { - '$ref': '#/definitions/DatacenterSchema', - '$schema': 'http://json-schema.org/draft-07/schema#', - 'definitions': { - 'DatacenterSchema': { - 'additionalProperties': false, - 'properties': { - 'environment': { - 'description': - 'Rules dictating what resources should be created in each environment hosted by the datacenter', - 'items': { - 'additionalProperties': false, - 'properties': { - 'cronjob': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "$ref": "#/definitions/DatacenterSchema", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "definitions": { + "DatacenterSchema": { + "additionalProperties": false, + "properties": { + "environment": { + "description": "Rules dictating what resources should be created in each environment hosted by the datacenter", + "items": { + "additionalProperties": false, + "properties": { + "cronjob": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } + ], + "type": "object" + }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" ], - 'type': 'object', + "type": "string" }, - 'inputs': { - 'anyOf': [ + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': {}, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": {}, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'database': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "database": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } + ], + "type": "object" + }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" ], - 'type': 'object', + "type": "string" }, - 'inputs': { - 'anyOf': [ + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'certificate': { - 'description': 'SSL certificate used to authenticate with the database', - 'type': 'string', + "properties": { + "certificate": { + "description": "SSL certificate used to authenticate with the database", + "type": "string" }, - 'database': { - 'description': 'Name of the new database schema', - 'examples': [ - 'my-schema', + "database": { + "description": "Name of the new database schema", + "examples": [ + "my-schema" ], - 'type': 'string', + "type": "string" }, - 'host': { - 'description': 'Host address of the underlying database', - 'examples': [ - 'my-database.example.com', + "host": { + "description": "Host address of the underlying database", + "examples": [ + "my-database.example.com" ], - 'type': 'string', + "type": "string" }, - 'password': { - 'description': 'Passowrd used to authenticate with the schema', - 'examples': [ - 'pass', + "password": { + "description": "Passowrd used to authenticate with the schema", + "examples": [ + "pass" ], - 'type': 'string', + "type": "string" }, - 'port': { - 'description': 'Port the underlying database listens on', - 'examples': [ - 5432, - ], - 'type': [ - 'string', - 'number', + "port": { + "description": "Port the underlying database listens on", + "examples": [ + 5432 ], + "type": [ + "string", + "number" + ] }, - 'protocol': { - 'description': 'Protocol of the underlying database', - 'examples': [ - 'postgresql', + "protocol": { + "description": "Protocol of the underlying database", + "examples": [ + "postgresql" ], - 'type': 'string', + "type": "string" }, - 'url': { - 'description': 'Full connection string for the database', - 'examples': [ - 'postgresql://user:pass@my-database.example.com:5432/my-schema', + "url": { + "description": "Full connection string for the database", + "examples": [ + "postgresql://user:pass@my-database.example.com:5432/my-schema" ], - 'type': 'string', + "type": "string" }, - 'username': { - 'description': 'Username used to authenticate with the schema', - 'examples': [ - 'user', + "username": { + "description": "Username used to authenticate with the schema", + "examples": [ + "user" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'protocol', - 'host', - 'port', - 'database', - 'url', - 'username', - 'password', + "required": [ + "protocol", + "host", + "port", + "database", + "url", + "username", + "password" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'databaseUser': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "databaseUser": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } + ], + "type": "object" + }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" ], - 'type': 'object', + "type": "string" }, - 'inputs': { - 'anyOf': [ + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'certificate': { - 'description': 'The certificate used to connect to the database', - 'type': 'string', + "properties": { + "certificate": { + "description": "The certificate used to connect to the database", + "type": "string" }, - 'database': { - 'description': 'The name of the database to connect to', - 'examples': [ - 'database', + "database": { + "description": "The name of the database to connect to", + "examples": [ + "database" ], - 'type': 'string', + "type": "string" }, - 'host': { - 'description': 'The host the database listens on', - 'examples': [ - 'rds.amazonwebservices.com/abc123', + "host": { + "description": "The host the database listens on", + "examples": [ + "rds.amazonwebservices.com/abc123" ], - 'type': 'string', + "type": "string" }, - 'password': { - 'description': 'Password used to authenticate with the database', - 'examples': [ - 'password', + "password": { + "description": "Password used to authenticate with the database", + "examples": [ + "password" ], - 'type': 'string', + "type": "string" }, - 'port': { - 'description': 'The port the database listens on', - 'examples': [ - 5432, - ], - 'type': [ - 'number', - 'string', + "port": { + "description": "The port the database listens on", + "examples": [ + 5432 ], + "type": [ + "number", + "string" + ] }, - 'protocol': { - 'description': 'The protocol the database responds to', - 'examples': [ - 'postgresql', + "protocol": { + "description": "The protocol the database responds to", + "examples": [ + "postgresql" ], - 'type': 'string', + "type": "string" }, - 'url': { - 'description': 'Fully resolvable URL used to connect to the database', - 'examples': [ - 'postgresql://admin:password@rds.amazonwebservices.com:5432/database', + "url": { + "description": "Fully resolvable URL used to connect to the database", + "examples": [ + "postgresql://admin:password@rds.amazonwebservices.com:5432/database" ], - 'type': 'string', + "type": "string" }, - 'username': { - 'description': 'Username used to authenticate with the database', - 'examples': [ - 'admin', + "username": { + "description": "Username used to authenticate with the database", + "examples": [ + "admin" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'protocol', - 'host', - 'port', - 'database', - 'username', - 'password', - 'url', + "required": [ + "protocol", + "host", + "port", + "database", + "username", + "password", + "url" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'deployment': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "deployment": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } ], - 'type': 'object', + "type": "object" }, - 'inputs': { - 'anyOf': [ + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'labels': { - 'additionalProperties': { - 'type': 'string', + "properties": { + "labels": { + "additionalProperties": { + "type": "string" }, - 'description': 'A set of labels that were used to annotate the cloud resource', - 'examples': [ + "description": "A set of labels that were used to annotate the cloud resource", + "examples": [ { - 'app.kubernetes.io/name': 'my-app', - }, + "app.kubernetes.io/name": "my-app" + } ], - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'dockerBuild': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "dockerBuild": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } ], - 'type': 'object', + "type": "object" }, - 'inputs': { - 'anyOf': [ + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'image': { - 'description': 'The resulting image address of the built artifact', - 'examples': [ - 'registry.architect.io/my-component:latest', + "properties": { + "image": { + "description": "The resulting image address of the built artifact", + "examples": [ + "registry.architect.io/my-component:latest" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'image', + "required": [ + "image" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'ingress': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "ingress": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } ], - 'type': 'object', + "type": "object" }, - 'inputs': { - 'anyOf': [ + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'dns_zone': { - 'description': 'DNS zone the ingress rule responds to', - 'examples': [ - 'example.com', + "properties": { + "dns_zone": { + "description": "DNS zone the ingress rule responds to", + "examples": [ + "example.com" ], - 'type': 'string', + "type": "string" }, - 'host': { - 'description': 'Host the ingress rule responds to', - 'examples': [ - 'api.example.com', + "host": { + "description": "Host the ingress rule responds to", + "examples": [ + "api.example.com" ], - 'type': 'string', + "type": "string" }, - 'password': { - 'description': 'Password for basic auth', - 'examples': [ - 'password', + "password": { + "description": "Password for basic auth", + "examples": [ + "password" ], - 'type': 'string', + "type": "string" }, - 'path': { - 'description': 'Path the ingress rule responds to', - 'examples': [ - '/path', + "path": { + "description": "Path the ingress rule responds to", + "examples": [ + "/path" ], - 'type': 'string', + "type": "string" }, - 'port': { - 'description': 'Port the ingress rule responds to', - 'examples': [ - 80, - ], - 'type': [ - 'string', - 'number', + "port": { + "description": "Port the ingress rule responds to", + "examples": [ + 80 ], + "type": [ + "string", + "number" + ] }, - 'protocol': { - 'description': 'Protocol the ingress rule responds to', - 'examples': [ - 'http', + "protocol": { + "description": "Protocol the ingress rule responds to", + "examples": [ + "http" ], - 'type': 'string', + "type": "string" }, - 'subdomain': { - 'description': 'Subdomain the ingress rule responds to', - 'examples': [ - 'api', + "subdomain": { + "description": "Subdomain the ingress rule responds to", + "examples": [ + "api" ], - 'type': 'string', + "type": "string" }, - 'url': { - 'description': 'URL the ingress rule responds to', - 'examples': [ - 'http://admin:password@api.example.com/path', + "url": { + "description": "URL the ingress rule responds to", + "examples": [ + "http://admin:password@api.example.com/path" ], - 'type': 'string', + "type": "string" }, - 'username': { - 'description': 'Username for basic auth', - 'examples': [ - 'admin', + "username": { + "description": "Username for basic auth", + "examples": [ + "admin" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'protocol', - 'host', - 'port', - 'url', - 'path', - 'subdomain', - 'dns_zone', + "required": [ + "protocol", + "host", + "port", + "url", + "path", + "subdomain", + "dns_zone" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" ], - 'type': 'string', + "type": "string" }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } + ], + "type": "object" + }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" ], - 'type': 'object', + "type": "string" }, - 'inputs': { - 'anyOf': [ + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], + "image": "nginx:latest", + "port": 8080 + } + ] }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', + "type": "string" }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', - ], - 'type': 'string', - }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', - ], - 'type': 'string', - }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" + ], + "type": "string" + }, + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" + ], + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per environment', - 'type': 'object', + "description": "Modules that will be created once per environment", + "type": "object" }, - 'secret': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "secret": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } + ], + "type": "object" + }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" ], - 'type': 'object', + "type": "string" }, - 'inputs': { - 'anyOf': [ + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'data': { - 'description': 'The contents of the secret', - 'examples': [ - '...', + "properties": { + "data": { + "description": "The contents of the secret", + "examples": [ + "..." ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'data', + "required": [ + "data" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'service': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "service": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } ], - 'type': 'object', + "type": "object" }, - 'inputs': { - 'anyOf': [ + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'host': { - 'description': 'Host the service listens on', - 'examples': [ - 'my-service', + "properties": { + "host": { + "description": "Host the service listens on", + "examples": [ + "my-service" ], - 'type': 'string', + "type": "string" }, - 'name': { - 'description': 'Name of the service', - 'examples': [ - 'my-service', + "name": { + "description": "Name of the service", + "examples": [ + "my-service" ], - 'type': 'string', + "type": "string" }, - 'port': { - 'description': 'Port the service listens on', - 'examples': [ - 80, - ], - 'type': [ - 'number', - 'string', + "port": { + "description": "Port the service listens on", + "examples": [ + 80 ], + "type": [ + "number", + "string" + ] }, - 'protocol': { - 'description': 'Protocol the service listens on', - 'examples': [ - 'http', + "protocol": { + "description": "Protocol the service listens on", + "examples": [ + "http" ], - 'type': 'string', + "type": "string" }, - 'target_port': { - 'description': 'The port the service forwards traffic to', - 'examples': [ - 8080, - ], - 'type': [ - 'number', - 'string', + "target_port": { + "description": "The port the service forwards traffic to", + "examples": [ + 8080 ], + "type": [ + "number", + "string" + ] }, - 'url': { - 'description': 'Fully resolvable URL of the service', - 'examples': [ - 'http://my-service:80', + "url": { + "description": "Fully resolvable URL of the service", + "examples": [ + "http://my-service:80" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'name', - 'target_port', - 'protocol', - 'host', - 'port', - 'url', + "required": [ + "name", + "target_port", + "protocol", + "host", + "port", + "url" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'volume': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', - ], - 'type': 'string', - }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "volume": { + "items": { + "additionalProperties": false, + "properties": { + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" + ], + "type": "string" + }, + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': - 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } + ], + "type": "object" + }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" ], - 'type': 'object', + "type": "string" }, - 'inputs': { - 'anyOf': [ + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', - }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', - ], - 'type': 'string', - }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "image": "nginx:latest", + "port": 8080 + } + ] + }, + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" + ], + "type": "string" + }, + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per matching application resource', - 'type': 'object', + "description": "Modules that will be created once per matching application resource", + "type": "object" }, - 'outputs': { - 'additionalProperties': false, - 'description': 'A map of output values to be passed to upstream application resources', - 'examples': [ + "outputs": { + "additionalProperties": false, + "description": "A map of output values to be passed to upstream application resources", + "examples": [ { - 'host': '${module.database.host}', - 'id': '${module.database.id}', - 'password': '${module.database.password}', - 'port': '${module.database.port}', - 'username': '${module.database.username}', - }, + "host": "${module.database.host}", + "id": "${module.database.id}", + "password": "${module.database.password}", + "port": "${module.database.port}", + "username": "${module.database.username}" + } ], - 'properties': { - 'id': { - 'description': 'The unique ID of the volume', - 'examples': [ - 'my-volume', + "properties": { + "id": { + "description": "The unique ID of the volume", + "examples": [ + "my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'id', + "required": [ + "id" ], - 'type': 'object', + "type": "object" }, - 'when': { - 'description': - 'A condition that restricts when the hook should be active. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the hook should be active. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', - }, + "type": "array" + } }, - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'module': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'build': { - 'description': 'The path to a module that will be built during the build step.', - 'examples': [ - './my-module', + "module": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "build": { + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", + "examples": [ + "./my-module", + "./my-module/module.yml" ], - 'type': 'string', + "type": "string" }, - 'environment': { - 'additionalProperties': { - 'type': 'string', + "environment": { + "additionalProperties": { + "type": "string" }, - 'description': 'Environment variables that should be provided to the container executing the module', - 'examples': [ + "description": "Environment variables that should be provided to the container executing the module", + "examples": [ { - 'MY_ENV_VAR': 'my-value', - }, + "MY_ENV_VAR": "my-value" + } ], - 'type': 'object', + "type": "object" }, - 'inputs': { - 'anyOf': [ + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, + "inputs": { + "anyOf": [ { - 'additionalProperties': {}, - 'type': 'object', + "additionalProperties": {}, + "type": "object" }, { - 'type': 'string', - }, + "type": "string" + } ], - 'description': 'Input values for the module.', - 'examples': [ + "description": "Input values for the module.", + "examples": [ { - 'image': 'nginx:latest', - 'port': 8080, - }, - ], - }, - 'plugin': { - 'default': 'pulumi', - 'description': 'The plugin used to build the module. Defaults to pulumi.', - 'enum': [ - 'pulumi', - 'opentofu', - ], - 'examples': [ - 'opentofu', - ], - 'type': 'string', - }, - 'source': { - 'description': 'The image source of the module.', - 'examples': [ - 'my-registry.com/my-image:latest', - ], - 'type': 'string', + "image": "nginx:latest", + "port": 8080 + } + ] }, - 'ttl': { - 'description': - 'The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.', - 'examples': [ - '24*60*60', + "ttl": { + "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", + "examples": [ + "24*60*60" ], - 'type': 'string', + "type": "string" }, - 'volume': { - 'description': 'Volumes that should be mounted to the container executing the module', - 'items': { - 'additionalProperties': false, - 'properties': { - 'host_path': { - 'description': 'The path on the host machine to mount to the container', - 'examples': [ - '/Users/batman/my-volume', + "volume": { + "description": "Volumes that should be mounted to the container executing the module", + "items": { + "additionalProperties": false, + "properties": { + "host_path": { + "description": "The path on the host machine to mount to the container", + "examples": [ + "/Users/batman/my-volume" ], - 'type': 'string', + "type": "string" }, - 'mount_path': { - 'description': 'The path in the container to mount the volume to', - 'examples': [ - '/app/my-volume', + "mount_path": { + "description": "The path in the container to mount the volume to", + "examples": [ + "/app/my-volume" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'host_path', - 'mount_path', + "required": [ + "host_path", + "mount_path" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'when': { - 'description': - 'A condition that restricts when the module should be created. Must resolve to a boolean.', - 'examples': [ - 'node.type == \'database\' && node.inputs.databaseType == \'postgres\'', - 'contains(environment.nodes.*.inputs.databaseType, \'postgres\')', + "when": { + "description": "A condition that restricts when the module should be created. Must resolve to a boolean.", + "examples": [ + "node.type == 'database' && node.inputs.databaseType == 'postgres'", + "contains(environment.nodes.*.inputs.databaseType, 'postgres')" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'inputs', + "required": [ + "inputs" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Modules that will be created once per datacenter', - 'type': 'object', + "description": "Modules that will be created once per datacenter", + "type": "object" }, - 'variable': { - 'additionalProperties': { - 'items': { - 'additionalProperties': false, - 'properties': { - 'default': { - 'description': 'The default value of the variable', - 'examples': [ - 'my-value', + "variable": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "default": { + "description": "The default value of the variable", + "examples": [ + "my-value" ], - 'type': 'string', + "type": "string" }, - 'description': { - 'description': 'A human-readable description of the variable', - 'examples': [ - 'An example description', + "description": { + "description": "A human-readable description of the variable", + "examples": [ + "An example description" ], - 'type': 'string', + "type": "string" }, - 'type': { - 'description': 'The type of the variable', - 'enum': [ - 'string', - 'number', - 'boolean', + "type": { + "description": "The type of the variable", + "enum": [ + "string", + "number", + "boolean" ], - 'examples': [ - 'string', + "examples": [ + "string" ], - 'type': 'string', - }, + "type": "string" + } }, - 'required': [ - 'type', + "required": [ + "type" ], - 'type': 'object', + "type": "object" }, - 'type': 'array', + "type": "array" }, - 'description': 'Variables necessary for the datacenter to run', - 'type': 'object', - }, - 'version': { - 'const': 'v1', - 'type': 'string', + "description": "Variables necessary for the datacenter to run", + "type": "object" }, + "version": { + "const": "v1", + "type": "string" + } }, - 'required': [ - 'version', + "required": [ + "version" ], - 'type': 'object', - }, + "type": "object" + } }, - '$id': 'https://architect.io/.schemas/datacenter.json', -}; + "$id": "https://architect.io/.schemas/datacenter.json" +} \ No newline at end of file diff --git a/src/datacenters/datacenter.schema.json b/src/datacenters/datacenter.schema.json index 16257d472..47860aca1 100644 --- a/src/datacenters/datacenter.schema.json +++ b/src/datacenters/datacenter.schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DatacenterSchema", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DatacenterSchema": { "additionalProperties": false, @@ -20,9 +20,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -38,6 +39,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -56,25 +64,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -166,9 +155,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -184,6 +174,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -202,25 +199,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -379,9 +357,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -397,6 +376,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -415,25 +401,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -592,9 +559,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -610,6 +578,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -628,25 +603,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -752,9 +708,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -770,6 +727,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -788,25 +752,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -910,9 +855,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -928,6 +874,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -946,25 +899,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1129,9 +1063,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1147,6 +1082,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1165,25 +1107,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1248,9 +1171,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1266,6 +1190,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1284,25 +1215,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1406,9 +1318,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1424,6 +1337,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1442,25 +1362,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1610,9 +1511,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1628,6 +1530,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1646,25 +1555,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1769,9 +1659,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1787,6 +1678,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1805,25 +1703,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ diff --git a/src/datacenters/datacenter.ts b/src/datacenters/datacenter.ts index 895b272f8..1739b85c4 100644 --- a/src/datacenters/datacenter.ts +++ b/src/datacenters/datacenter.ts @@ -1,17 +1,16 @@ -import { Plugin } from '../datacenter-modules/index.ts'; import { AppGraph, InfraGraph } from '../graphs/index.ts'; import { DatacenterVariablesSchema } from './variables.ts'; -// Docker types -export type DockerBuildFn = (options: { +export type ModuleBuildFn = (options: { context: string; - plugin: Plugin; }) => Promise; -export type DockerTagFn = ( + +export type ModuleTagFn = ( sourceRef: string, targetName: string, ) => Promise; -export type DockerPushFn = (image: string) => Promise; + +export type ModulePushFn = (image: string) => Promise; export type GetGraphOptions = { /** @@ -34,7 +33,7 @@ export abstract class Datacenter { public abstract getGraph(appGraph: AppGraph, options: GetGraphOptions): InfraGraph; public abstract getVariablesSchema(): DatacenterVariablesSchema; - public abstract build(buildFn: DockerBuildFn): Promise; - public abstract tag(tagFn: DockerTagFn): Promise; - public abstract push(pushFn: DockerPushFn): Promise; + public abstract build(buildFn: ModuleBuildFn): Promise; + public abstract tag(tagFn: ModuleTagFn): Promise; + public abstract push(pushFn: ModulePushFn): Promise; } diff --git a/src/datacenters/parser.ts b/src/datacenters/parser.ts index 06b0ff324..46f23ac0c 100644 --- a/src/datacenters/parser.ts +++ b/src/datacenters/parser.ts @@ -1,5 +1,5 @@ import hclParser from 'hcl2-json-parser'; -import Ajv2019 from 'https://esm.sh/v124/ajv@8.12.0'; +import Ajv2019 from 'https://esm.sh/v124/ajv@8.12.0/dist/2019.js'; import yaml from 'js-yaml'; import { replaceExpressions } from '../hcl-parser/parser.ts'; import * as DatacenterSchemaContents from './datacenter-schema.ts'; diff --git a/src/datacenters/schema.ts b/src/datacenters/schema.ts index dd1c64d52..712adc4cd 100644 --- a/src/datacenters/schema.ts +++ b/src/datacenters/schema.ts @@ -1,9 +1,11 @@ import { Datacenter } from './datacenter.ts'; import datacenter_v1 from './v1/index.ts'; -export type DatacenterSchema = { - version: 'v1'; -} & datacenter_v1; +export type DatacenterSchema = + | ({ + version: 'v1'; + } & datacenter_v1) +; export const buildDatacenter = (data: DatacenterSchema): Datacenter => { switch (data.version) { @@ -11,4 +13,4 @@ export const buildDatacenter = (data: DatacenterSchema): Datacenter => { return new datacenter_v1(data); } } -}; +}; \ No newline at end of file diff --git a/src/datacenters/v1/__tests__/datacenter.test.ts b/src/datacenters/v1/__tests__/datacenter.test.ts index 5b44230cd..b37b9b802 100644 --- a/src/datacenters/v1/__tests__/datacenter.test.ts +++ b/src/datacenters/v1/__tests__/datacenter.test.ts @@ -16,7 +16,7 @@ describe('DatacenterV1', () => { it('should extract root modules', async () => { const rawDatacenterObj = await hclParser.parseToObject(` module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = "nyc1" @@ -33,7 +33,6 @@ describe('DatacenterV1', () => { region: 'nyc1', }, name: 'vpc', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedVpcNode]); @@ -42,7 +41,7 @@ describe('DatacenterV1', () => { it('should extract edges for related modules', async () => { const rawDatacenterObj = await hclParser.parseToObject(` module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = "nyc1" @@ -50,7 +49,7 @@ describe('DatacenterV1', () => { } module "cluster" { - source = "architect-io/digitalocean-kubernetes:latest" + image = "architect-io/digitalocean-kubernetes:latest" inputs = { name = "\${module.vpc.name}-cluster" vpc_id = module.vpc.id @@ -62,7 +61,6 @@ describe('DatacenterV1', () => { const expectedVpcNode = new InfraGraphNode({ name: 'vpc', - plugin: 'pulumi', image: 'architect-io/digitalocean-vpc:latest', inputs: { name: 'my-vpc', @@ -72,7 +70,6 @@ describe('DatacenterV1', () => { const expectedClusterNode = new InfraGraphNode({ name: 'cluster', - plugin: 'pulumi', image: 'architect-io/digitalocean-kubernetes:latest', inputs: { name: `\${${expectedVpcNode.getId()}.name}-cluster`, @@ -92,7 +89,7 @@ describe('DatacenterV1', () => { it('should fail when referencing invalid modules', async () => { const rawDatacenterObj = await hclParser.parseToObject(` module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = module.region.name region = "nyc1" @@ -113,7 +110,7 @@ describe('DatacenterV1', () => { const rawDatacenterObj = await hclParser.parseToObject(` environment { module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = "nyc1" @@ -132,7 +129,6 @@ describe('DatacenterV1', () => { }, name: 'vpc', environment: 'test', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedVpcNode]); @@ -142,7 +138,7 @@ describe('DatacenterV1', () => { const rawDatacenterObj = await hclParser.parseToObject(` environment { module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = "nyc1" @@ -159,7 +155,7 @@ describe('DatacenterV1', () => { it('should extract edges from environment modules to datacenter modules', async () => { const rawDatacenterObj = await hclParser.parseToObject(` module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = "nyc1" @@ -168,7 +164,7 @@ describe('DatacenterV1', () => { environment { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" vpc_id = module.vpc.id @@ -186,7 +182,6 @@ describe('DatacenterV1', () => { region: 'nyc1', }, name: 'vpc', - plugin: 'pulumi', }); const expectedDatabaseNode = new InfraGraphNode({ @@ -197,7 +192,6 @@ describe('DatacenterV1', () => { }, name: 'database', environment: 'test', - plugin: 'pulumi', }); const expectedEdge = new GraphEdge({ @@ -212,7 +206,7 @@ describe('DatacenterV1', () => { it('should fail when datacenter modules reference environment modules', async () => { const rawDatacenterObj = await hclParser.parseToObject(` module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = module.database.name region = "nyc1" @@ -221,7 +215,7 @@ describe('DatacenterV1', () => { environment { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" vpc_id = module.vpc.id @@ -244,7 +238,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" } @@ -289,7 +283,6 @@ describe('DatacenterV1', () => { type: 'postgres', }, name: 'database', - plugin: 'pulumi', }); assertEquals(infraGraph.nodes, [expectedDatabaseNode]); @@ -300,7 +293,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" } @@ -357,7 +350,6 @@ describe('DatacenterV1', () => { type: 'postgres', }, name: 'database', - plugin: 'pulumi', }); const expectedDb2Node = new InfraGraphNode({ @@ -369,7 +361,6 @@ describe('DatacenterV1', () => { type: 'postgres', }, name: 'database', - plugin: 'pulumi', }); assertEquals(infraGraph.nodes, [expectedDb1Node, expectedDb2Node]); @@ -380,7 +371,7 @@ describe('DatacenterV1', () => { environment { deployment { module "deployment" { - source = "architect-io/kubernetes-deployment:latest" + image = "architect-io/kubernetes-deployment:latest" inputs = { image = node.inputs.image environment = node.inputs.environment @@ -438,7 +429,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" } @@ -457,7 +448,7 @@ describe('DatacenterV1', () => { deployment { module "deployment" { - source = "architect-io/kubernetes-deployment:latest" + image = "architect-io/kubernetes-deployment:latest" inputs = { image = node.inputs.image environment = node.inputs.environment @@ -513,7 +504,6 @@ describe('DatacenterV1', () => { type: 'postgres', }, name: 'database', - plugin: 'pulumi', }); const expectedDeploymentNode = new InfraGraphNode({ @@ -528,7 +518,6 @@ describe('DatacenterV1', () => { }, }, name: 'deployment', - plugin: 'pulumi', }); assertEquals(infraGraph.nodes, [expectedDbNode, expectedDeploymentNode]); @@ -545,7 +534,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" } @@ -604,7 +593,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" } @@ -623,7 +612,7 @@ describe('DatacenterV1', () => { deployment { module "deployment" { - source = "architect-io/kubernetes-deployment:latest" + image = "architect-io/kubernetes-deployment:latest" inputs = { image = "nginx:latest" environment = { @@ -679,7 +668,7 @@ describe('DatacenterV1', () => { } module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = variable.region @@ -696,7 +685,6 @@ describe('DatacenterV1', () => { region: 'nyc1', }, name: 'vpc', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedVpcNode]); @@ -710,7 +698,7 @@ describe('DatacenterV1', () => { environment { module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = variable.region @@ -734,7 +722,6 @@ describe('DatacenterV1', () => { }, name: 'vpc', environment: 'test', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedVpcNode]); @@ -749,7 +736,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" region = variable.region @@ -798,7 +785,6 @@ describe('DatacenterV1', () => { region: 'nyc1', }, name: 'database', - plugin: 'pulumi', environment: 'test', component: databaseAppGraphNode.component, appNodeId: databaseAppGraphNode.getId(), @@ -814,7 +800,7 @@ describe('DatacenterV1', () => { } module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "my-vpc" region = var.region @@ -831,7 +817,6 @@ describe('DatacenterV1', () => { region: 'nyc1', }, name: 'vpc', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedVpcNode]); @@ -849,7 +834,7 @@ describe('DatacenterV1', () => { } module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = { name = "\${var.namePrefix}-vpc" region = "\${var.region}" @@ -866,7 +851,6 @@ describe('DatacenterV1', () => { region: 'nyc1', }, name: 'vpc', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedVpcNode]); @@ -879,7 +863,7 @@ describe('DatacenterV1', () => { when = node.inputs.databaseType == "postgres" module "database" { - source = "test:latest" + image = "test:latest" inputs = { type = "postgres" } @@ -929,7 +913,6 @@ describe('DatacenterV1', () => { environment: 'test', appNodeId: databaseNode.getId(), component: databaseNode.component, - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedDatabaseModule]); @@ -939,7 +922,7 @@ describe('DatacenterV1', () => { const rawDatacenterObj = await hclParser.parseToObject(` environment { module "database" { - source = "test:latest" + image = "test:latest" inputs = { type = "postgres" } @@ -994,7 +977,7 @@ describe('DatacenterV1', () => { environment { module "database" { when = contains(environment.nodes.*.inputs.databaseType, "postgres") - source = "architect-io/digitalocean-database:latest" + image = "architect-io/digitalocean-database:latest" inputs = { type = "postgres" } @@ -1002,7 +985,7 @@ describe('DatacenterV1', () => { module "redis" { when = contains(environment.nodes.*.inputs.databaseType, "redis") - source = "architect-io/digitalocean-cache:latest" + image = "architect-io/digitalocean-cache:latest" inputs = { type = "redis" } @@ -1039,7 +1022,6 @@ describe('DatacenterV1', () => { type: 'postgres', }, name: 'database', - plugin: 'pulumi', }); assertEquals(graph.nodes, [expectedDatabaseModule]); @@ -1050,7 +1032,7 @@ describe('DatacenterV1', () => { environment { deployment { module "deployment" { - source = "architect-io/kubernetes-deployment:latest" + image = "architect-io/kubernetes-deployment:latest" inputs = node.inputs } } @@ -1129,7 +1111,6 @@ describe('DatacenterV1', () => { }, }, name: 'deployment', - plugin: 'pulumi', }); assertEquals(infraGraph.nodes, [expectedDeploymentNode]); @@ -1140,7 +1121,7 @@ describe('DatacenterV1', () => { environment { database { module "database" { - source = "architect-io/postgres:latest" + image = "architect-io/postgres:latest" inputs = { name = node.inputs.name component = node.component @@ -1204,7 +1185,6 @@ describe('DatacenterV1', () => { environment: 'test', }, name: 'database', - plugin: 'pulumi', }); const expectedInfraNode2 = new InfraGraphNode({ @@ -1218,7 +1198,6 @@ describe('DatacenterV1', () => { environment: 'test', }, name: 'database', - plugin: 'pulumi', }); assertEquals(infraGraph.nodes, [expectedInfraNode1, expectedInfraNode2]); @@ -1229,7 +1208,7 @@ describe('DatacenterV1', () => { environment { deployment { module "deployment" { - source = "architect-io/kubernetes-deployment:latest" + image = "architect-io/kubernetes-deployment:latest" inputs = node.inputs } } @@ -1333,7 +1312,6 @@ describe('DatacenterV1', () => { }, }, name: 'deployment', - plugin: 'pulumi', }); assertEquals(infraGraph.nodes, [expectedDeploymentNode]); @@ -1348,7 +1326,7 @@ describe('DatacenterV1', () => { environment { ingress { module "ingressRule" { - source = "architect-io/kubernetes-ingress-rule:latest" + image = "architect-io/kubernetes-ingress-rule:latest" inputs = merge(node.inputs, { dns_zone = variable.dns_zone }) @@ -1403,7 +1381,6 @@ describe('DatacenterV1', () => { appNodeId: 'component/ingress/ingress', environment: 'test', name: 'ingressRule', - plugin: 'pulumi', inputs: { dns_zone: 'architect.io', internal: false, @@ -1426,13 +1403,13 @@ describe('DatacenterV1', () => { it('should extract ttl and return whether its expired', async () => { const rawDatacenterObj = await hclParser.parseToObject(` module "vpc" { - source = "architect-io/digitalocean-vpc:latest" + image = "architect-io/digitalocean-vpc:latest" inputs = {} ttl = 10 * 60 } module "cluster" { - source = "architect-io/digitalocean-kubernetes:latest" + image = "architect-io/digitalocean-kubernetes:latest" inputs = {} } `); @@ -1441,7 +1418,6 @@ describe('DatacenterV1', () => { const nodeWithTTL = new InfraGraphNode({ name: 'vpc', - plugin: 'pulumi', image: 'architect-io/digitalocean-vpc:latest', inputs: {}, ttl: 600, @@ -1449,7 +1425,6 @@ describe('DatacenterV1', () => { const nodeWithoutTTL = new InfraGraphNode({ name: 'cluster', - plugin: 'pulumi', image: 'architect-io/digitalocean-kubernetes:latest', inputs: {}, }); @@ -1457,7 +1432,6 @@ describe('DatacenterV1', () => { const finishedElevenMinsAgo = new InfraGraphNode({ // These dont matter name: 'cluster', - plugin: 'pulumi', image: 'architect-io/digitalocean-kubernetes:latest', inputs: {}, status: { @@ -1468,7 +1442,6 @@ describe('DatacenterV1', () => { const finishedOneMinuteAgo = new InfraGraphNode({ // These dont matter name: 'cluster', - plugin: 'pulumi', image: 'architect-io/digitalocean-kubernetes:latest', inputs: {}, status: { diff --git a/src/datacenters/v1/errors.ts b/src/datacenters/v1/errors.ts index 984c57bdf..757d97a5b 100644 --- a/src/datacenters/v1/errors.ts +++ b/src/datacenters/v1/errors.ts @@ -1,4 +1,4 @@ -import { ErrorObject } from 'https://esm.sh/v124/ajv@8.12.0'; +import { ErrorObject } from 'ajv'; import { ResourceType } from '../../@resources/index.ts'; export class DuplicateModuleNameError extends Error { diff --git a/src/datacenters/v1/index.ts b/src/datacenters/v1/index.ts index 61708baaa..1ff066a27 100644 --- a/src/datacenters/v1/index.ts +++ b/src/datacenters/v1/index.ts @@ -1,13 +1,12 @@ import * as path from 'std/path/mod.ts'; import { parseResourceOutputs, ResourceOutputs, ResourceType } from '../../@resources/index.ts'; -import { Plugin } from '../../datacenter-modules/index.ts'; import { AppGraph } from '../../graphs/app/graph.ts'; import { GraphEdge } from '../../graphs/edge.ts'; import { InfraGraphNode, MODULES_REGEX } from '../../graphs/index.ts'; import { InfraGraph } from '../../graphs/infra/graph.ts'; import { applyContext } from '../../hcl-parser/index.ts'; import { exec } from '../../utils/command.ts'; -import { Datacenter, DockerBuildFn, DockerPushFn, DockerTagFn, GetGraphOptions } from '../datacenter.ts'; +import { Datacenter, GetGraphOptions, ModuleBuildFn, ModulePushFn, ModuleTagFn } from '../datacenter.ts'; import { DatacenterVariablesSchema } from '../variables.ts'; import { DuplicateModuleNameError, @@ -17,8 +16,6 @@ import { ModuleReferencesNotAllowedInWhenClause, } from './errors.ts'; -const DEFAULT_PLUGIN: Plugin = 'pulumi'; - type Module = { /** * A condition that restricts when the module should be created. Must resolve to a boolean. @@ -33,25 +30,16 @@ type Module = { * * @example "my-registry.com/my-image:latest" */ - source?: string; + image?: string; /** - * The path to a module that will be built during the build step. + * The path to a directory containing the code for the module, or a module.yml file describing the module. * * @example "./my-module" + * @example "./my-module/module.yml" */ build?: string; - /** - * The plugin used to build the module. Defaults to pulumi. - * - * @default "pulumi" - * @example "opentofu" - * - * @enum "pulumi" "opentofu" - */ - plugin?: Plugin; - /** * Volumes that should be mounted to the container executing the module */ @@ -213,7 +201,7 @@ export default class DatacenterV1 extends Datacenter { return res; } - public async build(buildFn: DockerBuildFn): Promise { + public async build(buildFn: ModuleBuildFn): Promise { for (const mod of Object.values(this.getModules())) { // Modules with only a source are skipped, they point to images that already exist. if (mod.build) { @@ -230,30 +218,29 @@ export default class DatacenterV1 extends Datacenter { const digest = await buildFn({ context, - plugin: mod.plugin || DEFAULT_PLUGIN, }); - mod.source = digest; + mod.image = digest; } } return this; } - public async tag(tagFn: DockerTagFn): Promise { + public async tag(tagFn: ModuleTagFn): Promise { for (const [moduleName, mod] of Object.entries(this.getModules())) { - if (mod.build && mod.source) { - mod.source = await tagFn(mod.source, moduleName); + if (mod.build && mod.image) { + mod.image = await tagFn(mod.image, moduleName); } } return this; } - public async push(pushFn: DockerPushFn): Promise { + public async push(pushFn: ModulePushFn): Promise { for (const module of Object.values(this.getModules())) { // Only push modules that have a build field, otherwise the image already exists. - if (module.build && module.source) { - await pushFn(module.source); + if (module.build && module.image) { + await pushFn(module.image); } } @@ -332,17 +319,17 @@ export default class DatacenterV1 extends Datacenter { } else if (value[0].when && value[0].when === 'false') { // If it evaluates to false it should be skipped. return; - } else if (!module.source) { + } else if (!module.image) { + console.log(module); throw new Error(`Module ${name} must contain a build or source field.`); } scopedGraph.insertNodes( new InfraGraphNode({ - image: module.source, + image: module.image, inputs: module.inputs, component: options.component, appNodeId: options.appNodeId, - plugin: module.plugin || DEFAULT_PLUGIN, volumes: module.volume, environment_vars: module.environment, name: name, diff --git a/src/datacenters/v1/schema.json b/src/datacenters/v1/schema.json index 489e6ece1..2819fa8b8 100644 --- a/src/datacenters/v1/schema.json +++ b/src/datacenters/v1/schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/DatacenterV1", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "DatacenterV1": { "additionalProperties": false, @@ -20,9 +20,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -38,6 +39,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -56,25 +64,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -166,9 +155,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -184,6 +174,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -202,25 +199,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -379,9 +357,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -397,6 +376,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -415,25 +401,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -592,9 +559,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -610,6 +578,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -628,25 +603,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -752,9 +708,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -770,6 +727,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -788,25 +752,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -910,9 +855,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -928,6 +874,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -946,25 +899,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1129,9 +1063,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1147,6 +1082,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1165,25 +1107,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1248,9 +1171,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1266,6 +1190,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1284,25 +1215,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1406,9 +1318,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1424,6 +1337,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1442,25 +1362,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1610,9 +1511,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1628,6 +1530,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1646,25 +1555,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ @@ -1769,9 +1659,10 @@ "additionalProperties": false, "properties": { "build": { - "description": "The path to a module that will be built during the build step.", + "description": "The path to a directory containing the code for the module, or a module.yml file describing the module.", "examples": [ - "./my-module" + "./my-module", + "./my-module/module.yml" ], "type": "string" }, @@ -1787,6 +1678,13 @@ ], "type": "object" }, + "image": { + "description": "The image source of the module.", + "examples": [ + "my-registry.com/my-image:latest" + ], + "type": "string" + }, "inputs": { "anyOf": [ { @@ -1805,25 +1703,6 @@ } ] }, - "plugin": { - "default": "pulumi", - "description": "The plugin used to build the module. Defaults to pulumi.", - "enum": [ - "pulumi", - "opentofu" - ], - "examples": [ - "opentofu" - ], - "type": "string" - }, - "source": { - "description": "The image source of the module.", - "examples": [ - "my-registry.com/my-image:latest" - ], - "type": "string" - }, "ttl": { "description": "The Time to Live (in seconds) for a module. When the TTL for a module is expired, the next deploy will force an update of the module.", "examples": [ diff --git a/src/environments/environment-schema.ts b/src/environments/environment-schema.ts index a15d230f5..7885cb01c 100644 --- a/src/environments/environment-schema.ts +++ b/src/environments/environment-schema.ts @@ -1,230 +1,226 @@ export default { - '$ref': '#/definitions/EnvironmentSchema', - '$schema': 'https://json-schema.org/draft/2019-09/schema', - 'definitions': { - 'EnvironmentSchema': { - 'additionalProperties': false, - 'properties': { - 'components': { - 'additionalProperties': { - 'additionalProperties': false, - 'description': 'The name of the component that will be used to fulfill dependencies', - 'properties': { - 'deployments': { - 'additionalProperties': { - 'additionalProperties': false, - 'description': 'The name of the deployment to configure', - 'properties': { - 'autoscaling': { - 'additionalProperties': false, - 'description': 'Autoscaling rules for the deployment within the environment', - 'properties': { - 'max_replicas': { - 'default': 1, - 'description': 'Maximum number of replicas to maintain', - 'type': 'number', - }, - 'min_replicas': { - 'default': 1, - 'description': 'Minimum number of replicas to maintain', - 'type': 'number', + "$ref": "#/definitions/EnvironmentSchema", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "definitions": { + "EnvironmentSchema": { + "additionalProperties": false, + "properties": { + "components": { + "additionalProperties": { + "additionalProperties": false, + "description": "The name of the component that will be used to fulfill dependencies", + "properties": { + "deployments": { + "additionalProperties": { + "additionalProperties": false, + "description": "The name of the deployment to configure", + "properties": { + "autoscaling": { + "additionalProperties": false, + "description": "Autoscaling rules for the deployment within the environment", + "properties": { + "max_replicas": { + "default": 1, + "description": "Maximum number of replicas to maintain", + "type": "number" }, + "min_replicas": { + "default": 1, + "description": "Minimum number of replicas to maintain", + "type": "number" + } }, - 'type': 'object', + "type": "object" }, - 'enabled': { - 'default': true, - 'description': 'Set to false to make sure the deployment doesn\'t run in this environment', - 'type': 'boolean', + "enabled": { + "default": true, + "description": "Set to false to make sure the deployment doesn't run in this environment", + "type": "boolean" }, - 'environment': { - 'additionalProperties': { - 'anyOf': [ + "environment": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'type': 'number', + "type": "number" }, { - 'type': 'boolean', + "type": "boolean" }, { - 'type': 'null', + "type": "null" }, { - 'not': {}, - }, - ], + "not": {} + } + ] }, - 'description': 'Values for environment variables in the deployment', - 'examples': [ + "description": "Values for environment variables in the deployment", + "examples": [ { - 'STRIPE_API_KEY': 'sk_test_1234', - }, + "STRIPE_API_KEY": "sk_test_1234" + } ], - 'type': 'object', - }, - 'replicas': { - 'default': 1, - 'description': 'Number of replicas of the deployment to maintain', - 'type': 'number', + "type": "object" }, + "replicas": { + "default": 1, + "description": "Number of replicas of the deployment to maintain", + "type": "number" + } }, - 'type': 'object', + "type": "object" }, - 'description': 'Configuration for each deployment in the component', - 'type': 'object', + "description": "Configuration for each deployment in the component", + "type": "object" }, - 'ingresses': { - 'additionalProperties': { - 'additionalProperties': false, - 'description': 'The name of the ingress to configure', - 'properties': { - 'internal': { - 'description': - 'Set to true to make the ingress only available from a private gateway (no public IP)', - 'examples': [ - true, + "ingresses": { + "additionalProperties": { + "additionalProperties": false, + "description": "The name of the ingress to configure", + "properties": { + "internal": { + "description": "Set to true to make the ingress only available from a private gateway (no public IP)", + "examples": [ + true ], - 'type': 'boolean', + "type": "boolean" }, - 'path': { - 'description': 'A path that the ingress listens on', - 'examples': [ - '/api', + "path": { + "description": "A path that the ingress listens on", + "examples": [ + "/api" ], - 'type': 'string', + "type": "string" }, - 'subdomain': { - 'description': 'A subdomain that the ingress listens on', - 'examples': [ - 'api', + "subdomain": { + "description": "A subdomain that the ingress listens on", + "examples": [ + "api" ], - 'type': 'string', + "type": "string" }, - 'tls': { - 'additionalProperties': false, - 'description': 'Custom TLS configuration for the ingress rule', - 'properties': { - 'ca': { - 'description': 'The certificate authority', - 'type': 'string', - }, - 'crt': { - 'description': 'The certificate file contents', - 'type': 'string', + "tls": { + "additionalProperties": false, + "description": "Custom TLS configuration for the ingress rule", + "properties": { + "ca": { + "description": "The certificate authority", + "type": "string" }, - 'key': { - 'description': 'The key file contents', - 'type': 'string', + "crt": { + "description": "The certificate file contents", + "type": "string" }, + "key": { + "description": "The key file contents", + "type": "string" + } }, - 'required': [ - 'crt', - 'key', + "required": [ + "crt", + "key" ], - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'description': 'Configuration for each ingress in the component', - 'type': 'object', + "description": "Configuration for each ingress in the component", + "type": "object" }, - 'services': { - 'additionalProperties': { - 'additionalProperties': false, - 'description': 'The name of the service to configure', - 'properties': { - 'host': { - 'description': - 'Existing hostname that should act as the interface host instead of creating a new one', - 'examples': [ - 'example.com', + "services": { + "additionalProperties": { + "additionalProperties": false, + "description": "The name of the service to configure", + "properties": { + "host": { + "description": "Existing hostname that should act as the interface host instead of creating a new one", + "examples": [ + "example.com" ], - 'type': 'string', + "type": "string" }, - 'port': { - 'description': - 'Existing port that should act as the interface port instead of registering a new one', - 'examples': [ - 443, + "port": { + "description": "Existing port that should act as the interface port instead of registering a new one", + "examples": [ + 443 ], - 'type': 'number', + "type": "number" }, - 'url': { - 'description': 'Existing URL to point the service to instead of', - 'examples': [ - 'https://example.com', + "url": { + "description": "Existing URL to point the service to instead of", + "examples": [ + "https://example.com" ], - 'type': 'string', - }, + "type": "string" + } }, - 'type': 'object', + "type": "object" }, - 'description': 'Configuration for each service in the component', - 'type': 'object', + "description": "Configuration for each service in the component", + "type": "object" }, - 'source': { - 'description': - 'The source of the component to deploy. Can either be a docker registry repository or a reference to the local filesystem prefixed with `file:`', - 'examples': [ - 'architectio/kratos:v1', - 'file:/path/to/component', + "source": { + "description": "The source of the component to deploy. Can either be a docker registry repository or a reference to the local filesystem prefixed with `file:`", + "examples": [ + "architectio/kratos:v1", + "file:/path/to/component" ], - 'type': 'string', + "type": "string" }, - 'variables': { - 'additionalProperties': { - 'anyOf': [ + "variables": { + "additionalProperties": { + "anyOf": [ { - 'type': 'string', + "type": "string" }, { - 'items': { - 'type': 'string', + "items": { + "type": "string" }, - 'type': 'array', - }, - ], + "type": "array" + } + ] }, - 'description': 'Values for variables the component expects', - 'examples': [ + "description": "Values for variables the component expects", + "examples": [ { - 'log_level': 'debug', - }, + "log_level": "debug" + } ], - 'type': 'object', - }, + "type": "object" + } }, - 'type': 'object', + "type": "object" }, - 'description': 'Configuration settings for the components that may be deployed inside this environment', - 'type': 'object', + "description": "Configuration settings for the components that may be deployed inside this environment", + "type": "object" }, - 'locals': { - 'additionalProperties': { - 'type': 'string', + "locals": { + "additionalProperties": { + "type": "string" }, - 'description': 'Local variables that can be used to parameterize the environment', - 'examples': [ + "description": "Local variables that can be used to parameterize the environment", + "examples": [ { - 'log_level': 'debug', - }, + "log_level": "debug" + } ], - 'type': 'object', - }, - 'version': { - 'const': 'v1', - 'type': 'string', + "type": "object" }, + "version": { + "const": "v1", + "type": "string" + } }, - 'required': [ - 'version', + "required": [ + "version" ], - 'type': 'object', - }, + "type": "object" + } }, - '$id': 'https://architect.io/.schemas/environment.json', -}; + "$id": "https://architect.io/.schemas/environment.json" +} \ No newline at end of file diff --git a/src/environments/parser.ts b/src/environments/parser.ts index 272af06f2..ff24546bc 100644 --- a/src/environments/parser.ts +++ b/src/environments/parser.ts @@ -1,4 +1,4 @@ -import Ajv2019 from 'https://esm.sh/v124/ajv@8.11.0/dist/2019.js'; +import Ajv2019 from 'https://esm.sh/v124/ajv@8.12.0/dist/2019.js'; import yaml from 'js-yaml'; import * as EnvironmentSchemaContents from './environment-schema.ts'; import { Environment } from './environment.ts'; diff --git a/src/environments/schema.ts b/src/environments/schema.ts index c9b11117a..83ca27e77 100644 --- a/src/environments/schema.ts +++ b/src/environments/schema.ts @@ -4,9 +4,11 @@ import environment_v1 from './v1/index.ts'; /** * @discriminatorOpenApi version */ -export type EnvironmentSchema = { - version: 'v1'; -} & environment_v1; +export type EnvironmentSchema = + | ({ + version: 'v1'; + } & environment_v1) +; export const buildEnvironment = (data: EnvironmentSchema): Environment => { switch (data.version) { @@ -15,7 +17,9 @@ export const buildEnvironment = (data: EnvironmentSchema): Environment => { } default: { throw new Error( - `Invalid schema version: ${'version' in data ? data.version : 'none'}`, + `Invalid schema version: ${ + 'version' in data ? data.version : 'none' + }` ); } } diff --git a/src/environments/v1/schema.json b/src/environments/v1/schema.json index 0e7a60bcf..2bf7817f4 100644 --- a/src/environments/v1/schema.json +++ b/src/environments/v1/schema.json @@ -1,6 +1,6 @@ { "$ref": "#/definitions/EnvironmentV1", - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "EnvironmentV1": { "additionalProperties": false, diff --git a/src/graphs/infra/__tests__/graph.test.ts b/src/graphs/infra/__tests__/graph.test.ts index b84883f82..10286afb3 100644 --- a/src/graphs/infra/__tests__/graph.test.ts +++ b/src/graphs/infra/__tests__/graph.test.ts @@ -390,7 +390,6 @@ function createGraphNode( ): InfraGraphNode { return new InfraGraphNode({ image: 'test-module:test-tag', - plugin: 'pulumi', color: 'blue', name, action: options.action, diff --git a/src/graphs/infra/node.ts b/src/graphs/infra/node.ts index 7da01bb45..0d7ba75b3 100644 --- a/src/graphs/infra/node.ts +++ b/src/graphs/infra/node.ts @@ -1,7 +1,10 @@ import * as crypto from 'https://deno.land/std@0.177.0/node/crypto.ts'; import { Observable } from 'rxjs'; +import * as path from 'std/path/mod.ts'; import { Logger } from 'winston'; -import { ModuleServer, Plugin } from '../../datacenter-modules/index.ts'; +import { DatacenterModule } from '../../modules/index.ts'; +import { exec } from '../../utils/command.ts'; +import { getImageLabels } from '../../utils/docker.ts'; import { GraphNode, GraphNodeOptions } from '../node.ts'; export type NodeAction = 'no-op' | 'create' | 'update' | 'delete'; @@ -18,8 +21,7 @@ export type NodeStatus = { export type NodeStatusState = 'pending' | 'starting' | 'applying' | 'destroying' | 'complete' | 'unknown' | 'error'; -export type InfraGraphNodeOptions

= GraphNodeOptions | string> & { - plugin: P; +export type InfraGraphNodeOptions = GraphNodeOptions | string> & { action?: NodeAction; image: string; appNodeId?: string; @@ -44,8 +46,7 @@ function replaceSeparators(value: string): string { return value.replaceAll('/', '__').replaceAll('-', '__'); } -export class InfraGraphNode

extends GraphNode | string> { - plugin: P; +export class InfraGraphNode extends GraphNode | string> { action: NodeAction; color: NodeColor; status: NodeStatus; @@ -62,9 +63,8 @@ export class InfraGraphNode

extends GraphNode; ttl?: number; - constructor(options: InfraGraphNodeOptions

) { + constructor(options: InfraGraphNodeOptions) { super(options); - this.plugin = options.plugin; this.action = options.action || 'create'; this.color = options.color || 'blue'; this.component = options.component; @@ -80,9 +80,11 @@ export class InfraGraphNode

extends GraphNode { + // NOTE: This uses .RootFS as the string to compare because the ID field is not a reliable hash + // Try to find the image locally first const dockerImages = new Deno.Command('docker', { - args: ['image', 'inspect', '--format', '{{.ID}}', this.image], + args: ['image', 'inspect', '--format', '{{.RootFS}}', this.image], }); const { code: firstCode, stdout: firstStdout } = await dockerImages.output(); @@ -96,7 +98,7 @@ export class InfraGraphNode

extends GraphNode extends GraphNode extends GraphNode): boolean { + public equals(node: InfraGraphNode): boolean { return this.name === node.name && - this.plugin === node.plugin && this.color === node.color && this.appNodeId === node.appNodeId && this.component === node.component && @@ -151,7 +151,7 @@ export class InfraGraphNode

extends GraphNode> { + public apply(options?: { cwd?: string; logger?: Logger }): Observable { if (this.status.state !== 'pending') { throw new Error(`Cannot apply node ${this.getId()} in state: ${this.status.state}`); } @@ -168,43 +168,99 @@ export class InfraGraphNode

extends GraphNode { - try { - if (typeof this.inputs !== 'object') { - throw new Error(`Cannot apply node with inputs of type ${typeof this.inputs}`); + getImageLabels(this.image).then(async (labels) => { + const commands = DatacenterModule.fromLabels(labels); + + // Values will be joined with && to be run inside the docker image + const command_array: string[] = []; + + // Initialize the state file + const tmpDir = await Deno.makeTempDir({ prefix: this.getId() + '_' }); + const stateFile = path.join(tmpDir, 'state.json'); + const outputFile = path.join(tmpDir, 'output.json'); + await Deno.writeTextFile(stateFile, this.state); + await Deno.writeTextFile(outputFile, '{}'); + + // Run init + if (commands.init) { + command_array.push(commands.init.join(' ')); + } + + // Run import if necessary + if (commands.import && this.state) { + command_array.push(commands.import.join(' ')); + } + + if (this.action === 'delete') { + command_array.push(commands.destroy.join(' ')); + } else if (this.action === 'create' || this.action === 'update') { + command_array.push( + commands.apply.join(' '), + commands.outputs.join(' '), + ); + + if (commands.export) { + command_array.push(commands.export.join(' ')); } + } - const res = await client.apply({ - datacenterid: 'datacenter', - inputs: this.inputs, - environment: this.environment_vars, - volumes: this.volumes, - image: this.image, - state: this.state, - destroy: this.action === 'delete', - }, { logger: options?.logger }); - - this.state = this.action === 'delete' ? undefined : res.state; - this.outputs = res.outputs || {}; - this.status.state = 'complete'; - this.status.endTime = Date.now(); - this.status.lastUpdated = Date.now(); - client.close(); - subscriber.next(this); - await server.stop(); - subscriber.complete(); - } catch (err) { + const environment_vars = { ...(this.environment_vars ?? {}) }; + environment_vars.STATE_FILE = '/module/state.json'; + environment_vars.OUTPUT_FILE = '/module/output.json'; + environment_vars.INPUTS = JSON.stringify(this.inputs); + + const volume_mounts = [...(this.volumes ?? [])]; + volume_mounts.push({ + host_path: tmpDir, + mount_path: '/module', + }); + + const flags = ['run']; + Object.values(volume_mounts).forEach((value) => { + flags.push('-v', `${value.host_path}:${value.mount_path}`); + }); + Object.entries(environment_vars).forEach(([key, value]) => { + flags.push('-e', `${key}=${value}`); + }); + + const args = [ + ...flags, + this.image, + 'sh', + '-c', + command_array.join(' && '), + ]; + + const { code, stdout, stderr } = await exec('docker', { + args, + logger: options?.logger, + }); + + if (code !== 0) { + console.log(stdout); + throw new Error(stderr); + } + + const stateContents = await Deno.readTextFile(stateFile); + const outputContents = await Deno.readTextFile(outputFile); + + this.state = stateContents; + this.outputs = JSON.parse(outputContents); + this.status.state = 'complete'; + this.status.endTime = Date.now(); + this.status.lastUpdated = Date.now(); + subscriber.next(this); + subscriber.complete(); + }) + .catch((err) => { + console.error(err); this.status.state = 'error'; this.status.message = err.message; this.status.endTime = Date.now(); this.status.lastUpdated = Date.now(); - client.close(); subscriber.next(this); - await server.stop(); subscriber.error(err); - } - }); + }); }); } } diff --git a/src/modules/index.ts b/src/modules/index.ts new file mode 100644 index 000000000..58641dd5b --- /dev/null +++ b/src/modules/index.ts @@ -0,0 +1,2 @@ +export * from './module.ts'; +export * from './parser.ts'; diff --git a/src/modules/module-schema.ts b/src/modules/module-schema.ts new file mode 100644 index 000000000..38a86eef1 --- /dev/null +++ b/src/modules/module-schema.ts @@ -0,0 +1,147 @@ +export default { + "$ref": "#/definitions/DatacenterModuleSchema", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "definitions": { + "DatacenterModuleSchema": { + "additionalProperties": false, + "properties": { + "apply": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The command to run to create or update the resources the module controls", + "examples": [ + "pulumi up --stack module" + ] + }, + "destroy": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The command to run to destroy the module", + "examples": [ + "pulumi destroy --stack module" + ] + }, + "dockerfile": { + "default": "Dockerfile", + "description": "Dockerfile to use to build the resource", + "type": "string" + }, + "export": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to export the module state. The statefile address is in the environment variable, $STATE_FILE.", + "examples": [ + "pulumi stack export --stack module --file $STATE_FILE" + ] + }, + "import": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to import state into the module. The statefile address is in the environment variable, $STATE_FILE.", + "examples": [ + "pulumi stack import --stack module --file $STATE_FILE" + ] + }, + "init": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command that should be used to setup the module workspace", + "examples": [ + "pulumi login --local && pulumi stack init --stack module" + ] + }, + "outputs": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command to run to dump the module outputs as json. Contents should be written to $OUTPUT_FILE.", + "examples": [ + "pulumi stack output --stack module --show-secrets --json > $OUTPUT_FILE" + ] + }, + "plan": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to generate a preview of changes to apply", + "examples": [ + "pulumi preview --stack module" + ] + }, + "version": { + "const": "v1", + "type": "string" + } + }, + "required": [ + "apply", + "destroy", + "outputs", + "version" + ], + "type": "object" + } + }, + "$id": "https://architect.io/.schemas/module.json" +} \ No newline at end of file diff --git a/src/modules/module.schema.json b/src/modules/module.schema.json new file mode 100644 index 000000000..6b25185a1 --- /dev/null +++ b/src/modules/module.schema.json @@ -0,0 +1,147 @@ +{ + "$ref": "#/definitions/DatacenterModuleSchema", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "definitions": { + "DatacenterModuleSchema": { + "additionalProperties": false, + "properties": { + "apply": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The command to run to create or update the resources the module controls", + "examples": [ + "pulumi up --stack module" + ] + }, + "destroy": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The command to run to destroy the module", + "examples": [ + "pulumi destroy --stack module" + ] + }, + "dockerfile": { + "default": "Dockerfile", + "description": "Dockerfile to use to build the resource", + "type": "string" + }, + "export": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to export the module state. The statefile address is in the environment variable, $STATE_FILE.", + "examples": [ + "pulumi stack export --stack module --file $STATE_FILE" + ] + }, + "import": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to import state into the module. The statefile address is in the environment variable, $STATE_FILE.", + "examples": [ + "pulumi stack import --stack module --file $STATE_FILE" + ] + }, + "init": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command that should be used to setup the module workspace", + "examples": [ + "pulumi login --local && pulumi stack init --stack module" + ] + }, + "outputs": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command to run to dump the module outputs as json. Contents should be written to $OUTPUT_FILE.", + "examples": [ + "pulumi stack output --stack module --show-secrets --json > $OUTPUT_FILE" + ] + }, + "plan": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to generate a preview of changes to apply", + "examples": [ + "pulumi preview --stack module" + ] + }, + "version": { + "const": "v1", + "type": "string" + } + }, + "required": [ + "apply", + "destroy", + "outputs", + "version" + ], + "type": "object" + } + }, + "$id": "https://architect.io/.schemas/module.json" +} \ No newline at end of file diff --git a/src/modules/module.ts b/src/modules/module.ts new file mode 100644 index 000000000..622eb28df --- /dev/null +++ b/src/modules/module.ts @@ -0,0 +1,74 @@ +export type DatacenterModuleCommands = { + init?: string[]; + plan?: string[]; + import?: string[]; + export?: string[]; + apply: string[]; + destroy: string[]; + outputs: string[]; +}; + +export abstract class DatacenterModule { + abstract getDockerfile(): string | undefined; + abstract getInitCommand(): string[] | undefined; + abstract getPlanCommand(): string[] | undefined; + abstract getImportCommand(): string[] | undefined; + abstract getExportCommand(): string[] | undefined; + abstract getOutputsCommand(): string[]; + abstract getApplyCommand(): string[]; + abstract getDestroyCommand(): string[]; + + static fromLabels(labels: Record): DatacenterModuleCommands { + const commands: DatacenterModuleCommands = { + apply: [], + destroy: [], + outputs: [], + }; + + Object.entries(labels).forEach(([key, value]) => { + if (key === 'io.architect.module.init') { + commands.init = value.split(' '); + } else if (key === 'io.architect.module.plan') { + commands.plan = value.split(' '); + } else if (key === 'io.architect.module.import') { + commands.import = value.split(' '); + } else if (key === 'io.architect.module.export') { + commands.export = value.split(' '); + } else if (key === 'io.architect.module.apply') { + commands.apply = value.split(' '); + } else if (key === 'io.architect.module.destroy') { + commands.destroy = value.split(' '); + } else if (key === 'io.architect.module.outputs') { + commands.outputs = value.split(' '); + } + }); + + return commands; + } + + labels(): Record { + const labels: Record = { + 'io.architect.module.destroy': this.getDestroyCommand().join(' '), + 'io.architect.module.apply': this.getApplyCommand().join(' '), + 'io.architect.module.outputs': this.getOutputsCommand().join(' '), + }; + + if (this.getInitCommand()) { + labels['io.architect.module.init'] = this.getInitCommand()!.join(' '); + } + + if (this.getPlanCommand()) { + labels['io.architect.module.plan'] = this.getPlanCommand()!.join(' '); + } + + if (this.getImportCommand()) { + labels['io.architect.module.import'] = this.getImportCommand()!.join(' '); + } + + if (this.getExportCommand()) { + labels['io.architect.module.export'] = this.getExportCommand()!.join(' '); + } + + return labels; + } +} diff --git a/src/modules/parser.ts b/src/modules/parser.ts new file mode 100644 index 000000000..4d8c20ae3 --- /dev/null +++ b/src/modules/parser.ts @@ -0,0 +1,51 @@ +import Ajv2019 from 'https://esm.sh/v124/ajv@8.12.0/dist/2019.js'; +import yaml from 'js-yaml'; +import * as path from 'std/path/mod.ts'; +import * as ModuleSchemaContents from './module-schema.ts'; +import { DatacenterModule } from './module.ts'; +import { buildModule, DatacenterModuleSchema } from './schema.ts'; + +const DEFAULT_SCHEMA_VERSION = 'v1'; +const ajv = new Ajv2019({ strict: false, discriminator: true }); + +export const parseModule = async ( + input: Record | string, +): Promise => { + const module_validator = ajv.compile(ModuleSchemaContents.default); + + let raw_obj: any; + if (typeof input === 'string') { + let raw_contents: string; + if (input.startsWith('http://') || input.startsWith('https://')) { + const resp = await fetch(input); + raw_contents = await resp.text(); + } else { + let filename = input; + const lstat = await Deno.lstat(filename); + if (lstat.isDirectory) { + filename = path.join(filename, 'module.yml'); + } + + raw_contents = await Deno.readTextFile(filename); + } + + if (input.endsWith('.json')) { + raw_obj = JSON.parse(raw_contents); + } else { + raw_obj = yaml.load(raw_contents); + } + } else { + raw_obj = input; + } + + if (!('version' in raw_obj)) { + raw_obj.version = DEFAULT_SCHEMA_VERSION; + } + + if (!module_validator(raw_obj)) { + console.log(module_validator.errors); + throw new Error(module_validator.errors?.toString()); + } + + return buildModule(raw_obj); +}; diff --git a/src/modules/schema.ts b/src/modules/schema.ts new file mode 100644 index 000000000..07debe86f --- /dev/null +++ b/src/modules/schema.ts @@ -0,0 +1,26 @@ +import { DatacenterModule } from './module.ts'; +import module_v1 from './v1/index.ts'; + +/** + * @discriminatorOpenApi version + */ +export type DatacenterModuleSchema = + | ({ + version: 'v1'; + } & module_v1) +; + +export const buildModule = (data: DatacenterModuleSchema): DatacenterModule => { + switch (data.version) { + case 'v1': { + return new module_v1(data); + } + default: { + throw new Error( + `Invalid schema version: ${ + 'version' in data ? data.version : 'none' + }` + ); + } + } +}; diff --git a/src/modules/schema.ts.stache b/src/modules/schema.ts.stache new file mode 100644 index 000000000..3aa365f5d --- /dev/null +++ b/src/modules/schema.ts.stache @@ -0,0 +1,32 @@ +import { DatacenterModule } from './module.ts'; +{{#versions}} +import module_{{.}} from './{{.}}/index.ts'; +{{/versions}} + +/** + * @discriminatorOpenApi version + */ +export type DatacenterModuleSchema = +{{#versions}} + | ({ + version: '{{.}}'; + } & module_{{.}}) +{{/versions}} +; + +export const buildModule = (data: DatacenterModuleSchema): DatacenterModule => { + switch (data.version) { + {{#versions}} + case '{{.}}': { + return new module_{{.}}(data); + } + {{/versions}} + default: { + throw new Error( + `Invalid schema version: ${ + 'version' in data ? data.version : 'none' + }` + ); + } + } +}; diff --git a/src/modules/v1/index.ts b/src/modules/v1/index.ts new file mode 100644 index 000000000..0b7f5cfe7 --- /dev/null +++ b/src/modules/v1/index.ts @@ -0,0 +1,96 @@ +import { DatacenterModule } from '../module.ts'; + +export default class DatacenterModuleV1 extends DatacenterModule { + /** + * Dockerfile to use to build the resource + * + * @default "Dockerfile" + */ + dockerfile?: string; + + /** + * Command that should be used to setup the module workspace + * + * @example "pulumi login --local && pulumi stack init --stack module" + */ + init?: string | string[]; + + /** + * Command used to generate a preview of changes to apply + * + * @example "pulumi preview --stack module" + */ + plan?: string | string[]; + + /** + * Command used to import state into the module. The statefile address is in the environment variable, $STATE_FILE. + * + * @example "pulumi stack import --stack module --file $STATE_FILE" + */ + import?: string | string[]; + + /** + * Command used to export the module state. The statefile address is in the environment variable, $STATE_FILE. + * + * @example "pulumi stack export --stack module --file $STATE_FILE" + */ + export?: string | string[]; + + /** + * Command to run to dump the module outputs as json. Contents should be written to $OUTPUT_FILE. + * + * @example "pulumi stack output --stack module --show-secrets --json > $OUTPUT_FILE" + */ + outputs!: string | string[]; + + /** + * The command to run to create or update the resources the module controls + * + * @example "pulumi up --stack module" + */ + apply!: string | string[]; + + /** + * The command to run to destroy the module + * + * @example "pulumi destroy --stack module" + */ + destroy!: string | string[]; + + constructor(data: any) { + super(); + Object.assign(this, data); + } + + getDockerfile(): string | undefined { + return this.dockerfile; + } + + getInitCommand(): string[] | undefined { + return Array.isArray(this.init) ? this.init : this.init?.split(' '); + } + + getPlanCommand(): string[] | undefined { + return Array.isArray(this.plan) ? this.plan : this.plan?.split(' '); + } + + getImportCommand(): string[] | undefined { + return Array.isArray(this.import) ? this.import : this.import?.split(' '); + } + + getExportCommand(): string[] | undefined { + return Array.isArray(this.export) ? this.export : this.export?.split(' '); + } + + getOutputsCommand(): string[] { + return Array.isArray(this.outputs) ? this.outputs : this.outputs.split(' '); + } + + getApplyCommand(): string[] { + return Array.isArray(this.apply) ? this.apply : this.apply.split(' '); + } + + getDestroyCommand(): string[] { + return Array.isArray(this.destroy) ? this.destroy : this.destroy.split(' '); + } +} diff --git a/src/modules/v1/schema.json b/src/modules/v1/schema.json new file mode 100644 index 000000000..c5076efbd --- /dev/null +++ b/src/modules/v1/schema.json @@ -0,0 +1,141 @@ +{ + "$ref": "#/definitions/DatacenterModuleV1", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "definitions": { + "DatacenterModuleV1": { + "additionalProperties": false, + "properties": { + "apply": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The command to run to create or update the resources the module controls", + "examples": [ + "pulumi up --stack module" + ] + }, + "destroy": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "The command to run to destroy the module", + "examples": [ + "pulumi destroy --stack module" + ] + }, + "dockerfile": { + "default": "Dockerfile", + "description": "Dockerfile to use to build the resource", + "type": "string" + }, + "export": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to export the module state. The statefile address is in the environment variable, $STATE_FILE.", + "examples": [ + "pulumi stack export --stack module --file $STATE_FILE" + ] + }, + "import": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to import state into the module. The statefile address is in the environment variable, $STATE_FILE.", + "examples": [ + "pulumi stack import --stack module --file $STATE_FILE" + ] + }, + "init": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command that should be used to setup the module workspace", + "examples": [ + "pulumi login --local && pulumi stack init --stack module" + ] + }, + "outputs": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command to run to dump the module outputs as json. Contents should be written to $OUTPUT_FILE.", + "examples": [ + "pulumi stack output --stack module --show-secrets --json > $OUTPUT_FILE" + ] + }, + "plan": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "description": "Command used to generate a preview of changes to apply", + "examples": [ + "pulumi preview --stack module" + ] + } + }, + "required": [ + "outputs", + "apply", + "destroy" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/src/oci/image-manifest.ts b/src/oci/image-manifest.ts index 34b884837..ea66785f9 100644 --- a/src/oci/image-manifest.ts +++ b/src/oci/image-manifest.ts @@ -1,6 +1,7 @@ export type ImageManifest = { schemaVersion: number; mediaType: string; + artifactType?: string; config: { digest: string; mediaType: string; @@ -11,4 +12,5 @@ export type ImageManifest = { mediaType: string; size: number; }>; + annotations?: Record; }; diff --git a/src/oci/image-repository.ts b/src/oci/image-repository.ts index e2d8f8424..0fd461275 100644 --- a/src/oci/image-repository.ts +++ b/src/oci/image-repository.ts @@ -168,16 +168,17 @@ export class ImageRepository { } } - async getManifest(media_type: string): Promise { + async getManifest(media_type?: string): Promise { if (!this.manifest) { const manifest_id = this.tag || this.digest; const res = await this.fetch(`${this.getRegistryUrl()}/v2/${this.repository}/manifests/${manifest_id}`, { headers: { - Accept: media_type, + Accept: media_type ?? 'application/vnd.oci.image.index.v1+json', }, }); if (res.status >= 400) { + console.log(this); throw new Error(`Failed to fetch manifest: ${await res.text()}`); } diff --git a/src/utils/build.ts b/src/utils/build.ts new file mode 100644 index 000000000..3fc4b3093 --- /dev/null +++ b/src/utils/build.ts @@ -0,0 +1,62 @@ +import { Logger } from 'winston'; +import { DatacenterModule, parseModule } from '../modules/index.ts'; +import { exec } from './command.ts'; + +export type ModuleBuildOptions = { + logger?: Logger; + platform?: string; + tags?: string[]; + push?: boolean; +}; + +export const buildModuleFromDirectory = async (context: string, options?: ModuleBuildOptions): Promise => { + if (options?.push && (!options.tags || options.tags.length <= 0)) { + throw new Error('Cannot push images without at least one tag'); + } + + let module_config: DatacenterModule; + try { + module_config = await parseModule(context); + } catch (err) { + throw new Error(`Failed to parse module: ${err.message}`); + } + + const args = [ + 'build', + ]; + + if (module_config.getDockerfile()) { + args.push('--file', module_config.getDockerfile()!); + } + + Object.entries(module_config.labels()).forEach(([key, value]) => { + args.push('--label', `${key}=${value}`); + }); + + if (options?.push) { + args.push('--push'); + } + + options?.tags?.forEach((tag) => { + args.push('--tag', tag); + }); + + if (!options?.tags || options.tags.length <= 0) { + // We'll need to extract the digest if there are no tags + args.push('--quiet'); + } + + args.push(context); + + const { code, stdout } = await exec('docker', { args, logger: options?.logger }); + + if (code !== 0) { + throw new Error(`Failed to build the module at path: ${context}`); + } + + if (!options?.tags || options.tags.length <= 0) { + return stdout.trim(); + } else { + return options.tags[0]; + } +}; diff --git a/src/utils/command.ts b/src/utils/command.ts index 33e84724b..e5627e750 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -1,4 +1,5 @@ import { Buffer } from 'std/io/buffer.ts'; +import winston from 'winston'; export type ExecOutput = { code: number; @@ -6,20 +7,55 @@ export type ExecOutput = { stderr: string; }; -export async function exec(command: string, command_options: Deno.CommandOptions): Promise { +/** + * Execute the specified command + */ +export const exec = async ( + command: string, + options?: Deno.CommandOptions & { logger?: winston.Logger }, +): Promise => { const cmd = new Deno.Command(command, { - ...command_options, + ...options, stdout: 'piped', stderr: 'piped', }); - const { code, stdout, stderr } = await cmd.output(); + const proc = cmd.spawn(); + const stdout = new Buffer(); + const stderr = new Buffer(); + + proc.stdout.pipeTo( + new WritableStream({ + write(chunk) { + stdout.writeSync(chunk); + + if (options?.logger) { + options.logger.info(new TextDecoder().decode(chunk)); + } + }, + }), + ); + + proc.stderr.pipeTo( + new WritableStream({ + write(chunk) { + stderr.writeSync(chunk); + + if (options?.logger) { + options.logger.error(new TextDecoder().decode(chunk)); + } + }, + }), + ); + + const status = await proc.status; + return { - code, - stdout: new TextDecoder().decode(stdout), - stderr: new TextDecoder().decode(stderr), + code: status.code, + stdout: new TextDecoder().decode(stdout.bytes()), + stderr: new TextDecoder().decode(stderr.bytes()), }; -} +}; /** * Execs the command and pipes stdout and stderr to Deno.stdout and Deno.stderr diff --git a/src/utils/datacenter-store.ts b/src/utils/datacenter-store.ts deleted file mode 100644 index 26135aab3..000000000 --- a/src/utils/datacenter-store.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Datacenter } from '../datacenters/datacenter.ts'; -import { parseDatacenter } from '../datacenters/parser.ts'; -import * as path from 'std/path/mod.ts'; - -export type DatacenterRecord = { - name: string; - config: Datacenter; -}; - -export class DatacenterStore { - private _datacenters?: DatacenterRecord[]; - - constructor( - private config_dir: string = Deno.makeTempDirSync(), - private datacenter_filename: string = 'datacenters.json', - ) { - this.getDatacenters(); - } - - private get datacenters_config_file() { - return path.join(this.config_dir, this.datacenter_filename); - } - - async getDatacenter(name: string): Promise { - const datacenters = await this.getDatacenters(); - return datacenters.find((record) => record.name === name); - } - - async getDatacenters(): Promise { - if (this._datacenters) { - return this._datacenters; - } - - try { - const fileContents = Deno.readTextFileSync(this.datacenters_config_file); - const rawDatacenters = JSON.parse(fileContents); - - const datacenters: DatacenterRecord[] = []; - for (const raw of rawDatacenters) { - datacenters.push({ - name: raw.name, - config: await parseDatacenter(raw.config), - }); - } - - this._datacenters = datacenters; - } catch { - this._datacenters = []; - } - - return this._datacenters; - } - - async saveDatacenter(input: DatacenterRecord): Promise { - const allDatacenters = await this.getDatacenters(); - const foundIndex = allDatacenters.findIndex((d) => d.name === input.name); - if (foundIndex >= 0) { - allDatacenters[foundIndex] = input; - } else { - allDatacenters.push(input); - } - this.saveDatacenters(allDatacenters); - } - - async removeDatacenter(name: string): Promise { - const allDatacenters = await this.getDatacenters(); - const foundIndex = allDatacenters.findIndex((d) => d.name === name); - if (foundIndex < 0) { - throw new Error(`The ${name} datacenter was not found`); - } - - allDatacenters.splice(foundIndex, 1); - await this.saveDatacenters(allDatacenters); - } - - async saveDatacenters(datacenters: DatacenterRecord[]): Promise { - await Deno.mkdir(path.dirname(this.datacenters_config_file), { - recursive: true, - }); - await Deno.writeTextFile(this.datacenters_config_file, JSON.stringify(datacenters, null, 2)); - } -} diff --git a/src/utils/docker.ts b/src/utils/docker.ts new file mode 100644 index 000000000..b3c7a5065 --- /dev/null +++ b/src/utils/docker.ts @@ -0,0 +1,36 @@ +import { exec } from './command.ts'; + +export const getImageLabels = async (tag_or_digest: string): Promise> => { + const { code, stdout, stderr } = await exec('docker', { + args: ['inspect', '-f', 'json', tag_or_digest], + }); + + if (code !== 0) { + throw new Error(stderr); + } + + const results = JSON.parse(stdout); + if (results.length === 0) { + throw new Error(`No image found for ${tag_or_digest}`); + } + + return results[0].Config.Labels || {}; +}; + +/** + * Gets a sha256 hash of the exported filesystem contents from the specified image + */ +export const getHash = async (tag_or_digest: string): Promise => { + const { code, stdout, stderr } = await exec('sh', { + args: [ + '-c', + `docker create ${tag_or_digest} | { read cid; docker export $cid | tar Oxv 2>&1 | shasum -a 256; docker rm $cid > /dev/null; }`, + ], + }); + + if (code !== 0) { + throw new Error(stderr); + } + + return stdout; +}; diff --git a/src/utils/environment-store.ts b/src/utils/environment-store.ts deleted file mode 100644 index c25ad6563..000000000 --- a/src/utils/environment-store.ts +++ /dev/null @@ -1,102 +0,0 @@ -import * as path from 'std/path/mod.ts'; -import { Environment } from '../environments/environment.ts'; -import { parseEnvironment } from '../environments/parser.ts'; -import { InfraGraph, InfraGraphNode } from '../graphs/index.ts'; - -export type EnvironmentRecord = { - name: string; - datacenter: string; - graph: InfraGraph; - config?: Environment; -}; - -export class EnvironmentStore { - private _environments?: EnvironmentRecord[]; - - constructor( - private config_dir: string = Deno.makeTempDirSync(), - private environment_filename: string = 'environments.json', - ) { - this.getEnvironments(); - } - - private get environments_config_file() { - return path.join(this.config_dir, this.environment_filename); - } - - saveFile(name: string, content: string): string { - const file_path = path.join(this.config_dir, name); - Deno.mkdirSync(path.dirname(file_path), { recursive: true }); - Deno.writeTextFileSync(file_path, content); - return file_path; - } - - async getEnvironment(name: string): Promise { - const records = await this.getEnvironments(); - return records.find((r) => r.name === name); - } - - async getEnvironments(): Promise { - if (this._environments) { - return this._environments; - } - - try { - const fileContents = Deno.readTextFileSync(this.environments_config_file); - const rawEnvironmentRecords = JSON.parse(fileContents); - - const environments: EnvironmentRecord[] = []; - for (const raw of rawEnvironmentRecords) { - const record: EnvironmentRecord = { - name: raw.name, - graph: new InfraGraph({ - edges: raw.graph.edges, - nodes: raw.graph.nodes.map((n: any) => new InfraGraphNode(n)), - }), - datacenter: raw.datacenter, - }; - - if (raw.config) { - record.config = await parseEnvironment(raw.config); - } - - environments.push(record); - } - - this._environments = environments; - } catch { - this._environments = []; - } - - return this._environments; - } - - async saveEnvironment(input: EnvironmentRecord): Promise { - const allEnvironments = await this.getEnvironments(); - const foundIndex = allEnvironments.findIndex((e) => e.name === input.name); - if (foundIndex >= 0) { - allEnvironments[foundIndex] = input; - } else { - allEnvironments.push(input); - } - this.saveEnvironments(allEnvironments); - } - - async removeEnvironment(name: string): Promise { - const environments = await this.getEnvironments(); - const foundIndex = environments.findIndex((e) => e.name === name); - if (foundIndex < 0) { - throw new Error(`No environment named ${name}`); - } - - environments.splice(foundIndex, 1); - return this.saveEnvironments(environments); - } - - async saveEnvironments(records: EnvironmentRecord[]): Promise { - await Deno.mkdir(path.dirname(this.environments_config_file), { - recursive: true, - }); - await Deno.writeTextFile(this.environments_config_file, JSON.stringify(records, null, 2)); - } -} diff --git a/src/utils/jobs.ts b/src/utils/jobs.ts deleted file mode 100644 index c7a56d832..000000000 --- a/src/utils/jobs.ts +++ /dev/null @@ -1 +0,0 @@ -export const wait = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index 435a8d6a0..000000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,30 +0,0 @@ -import winston, { format, transports } from 'winston'; - -const createEmptyWriteStream = (): WritableStream => { - const writableStream = new WritableStream({ - write(_chunk: any) { - return new Promise((resolve) => { - resolve(); - }); - }, - }); - return writableStream; -}; - -const Logger = winston.createLogger({ - level: 'debug', - format: winston.format.json(), - transports: [new transports.Stream({ stream: createEmptyWriteStream() })], -}); - -export const getLogger = (name: string): winston.Logger => { - return Logger.child({ name }); -}; - -export const enableDebugLogger = (): void => { - Logger.add( - new transports.Console({ - format: format.combine(format.colorize(), format.simple()), - }), - ); -}; diff --git a/src/utils/string.ts b/src/utils/string.ts deleted file mode 100644 index 5170952d9..000000000 --- a/src/utils/string.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const replaceAsync = async ( - str: string, - regex: RegExp, - asyncFn: (...matchParts: string[]) => Promise, -): Promise => { - const promises: Promise[] = []; - str.replace(regex, (match, ...args) => { - const promise = asyncFn(match, ...args); - promises.push(promise); - return ''; - }); - const data = await Promise.all(promises); - return str.replace(regex, () => data.shift()!); -}; diff --git a/src/utils/task-manager.ts b/src/utils/task-manager.ts deleted file mode 100644 index b3a924aea..000000000 --- a/src/utils/task-manager.ts +++ /dev/null @@ -1,61 +0,0 @@ -import logUpdate from 'log-update'; -import { Observable } from 'rxjs'; - -export interface Task { - title: string; - action: () => Observable; - finished: boolean; -} - -// This class has a slight hack. It assumes that one of the initial promises -// will last for the entire duration of all processes added after the fact. -export default class TaskManager { - private readonly frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - private frameIndex = 0; - - constructor(private readonly tasks: Task[]) {} - - private render(): void { - const output: string[] = []; - for (const [index, task] of this.tasks.entries()) { - if (task.finished) continue; - const prefix = index === 0 ? ` ${this.frames[this.frameIndex++]} ` : ' '; - if (this.frameIndex >= this.frames.length) { - this.frameIndex = 0; - } - output.push( - `${prefix}${task.title}`.substring(0, Deno.consoleSize().columns), - ); - } - if (output.length > 0) { - logUpdate(output.join('\n')); - } - } - - private runTask(task: Task): Promise { - return new Promise((resolve, reject) => { - task.action().subscribe({ - complete: () => { - task.finished = true; - resolve(); - }, - error: reject, - }); - }); - } - - public async run(): Promise { - const promises = this.tasks.map((task) => { - return this.runTask(task); - }); - const intervalId = setInterval(this.render.bind(this), 80); - await Promise.all(promises); - clearInterval(intervalId); - logUpdate.clear(); - } - - public add(task: Task): void { - this.tasks.push(task); - this.runTask(task); - } -}