diff --git a/Scripts/TerraformProvider/GenerateProvider.ts b/Scripts/TerraformProvider/GenerateProvider.ts index e69de29bb2..88baf9a0de 100644 --- a/Scripts/TerraformProvider/GenerateProvider.ts +++ b/Scripts/TerraformProvider/GenerateProvider.ts @@ -0,0 +1,24 @@ +import { generateOpenAPISpec } from '../OpenAPI/GenerateSpec'; +import GeneratorConfig from './GeneratorConfig'; +import path from 'path'; + +async function main() { + // 1. Generate OpenAPI spec + const openApiSpecPath = path.resolve(__dirname, '../../openapi.json'); + await generateOpenAPISpec(openApiSpecPath); + + // 2. Generate Terraform provider generator config + GeneratorConfig.generateGeneratorConfigAndWriteToFile({ + openApiSpecInJsonFilePath: openApiSpecPath, + outputPath: path.resolve(__dirname, '../../'), + outputFileName: 'terraform-provider-generator-config.yaml', + providerName: 'oneuptime', // Change as needed + }); + + console.log('OpenAPI spec and Terraform provider generator config generated successfully.'); +} + +main().catch((err) => { + console.error('Error generating provider:', err); + process.exit(1); +}); diff --git a/Scripts/TerraformProvider/GeneratorConfig.ts b/Scripts/TerraformProvider/GeneratorConfig.ts new file mode 100644 index 0000000000..470eb2f53c --- /dev/null +++ b/Scripts/TerraformProvider/GeneratorConfig.ts @@ -0,0 +1,83 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as yaml from 'js-yaml'; + +export default class GeneratorConfig { + + /** + * Generates a generator config for the Terraform provider and writes it to a file. + * @param data - The data required to generate the config. + * @param data.openApiSpecInJsonFilePath - The OpenAPI specification in JSON format. + * @param data.outputPath - The path where the output file will be written. + * @param data.outputFileName - The name of the output file. + * @param data.providerName - The name of the Terraform provider. + * + * This implementation generates a minimal valid generator config for the OpenAPI provider spec generator. + * You can extend this to add resources, data_sources, and schema options as needed. + */ + + public static generateGeneratorConfigAndWriteToFile(data: { + openApiSpecInJsonFilePath: string, + outputPath: string, + outputFileName: string, + providerName: string, + }): void { + // Read the OpenAPI spec JSON file + const openApiSpec = JSON.parse(fs.readFileSync(data.openApiSpecInJsonFilePath, 'utf-8')); + const config: any = { + provider: { + name: data.providerName + }, + resources: {}, + data_sources: {} + }; + + // Parse OpenAPI paths to generate resources and data sources + if (openApiSpec.paths) { + for (const [pathKey, pathObj] of Object.entries(openApiSpec.paths)) { + for (const [method, opRaw] of Object.entries(pathObj as any)) { + const op = opRaw as any; + if (!op || typeof op !== 'object' || typeof op.operationId !== 'string') continue; + // Heuristic: POST/PUT = resource, GET = data source + if (method.toLowerCase() === 'post' || method.toLowerCase() === 'put') { + // Use operationId or path as resource name + const resourceName = (op.operationId.replace(/^(create|put|add)/i, '').toLowerCase() || pathKey.replace(/[\/{\}]/g, '').replace(/\//g, '_')).replace(/^_+|_+$/g, ''); + if (!config.resources[resourceName]) config.resources[resourceName] = {}; + config.resources[resourceName][method.toLowerCase()] = { path: pathKey, method: method.toUpperCase() }; + } else if (method.toLowerCase() === 'get') { + const dsName = (op.operationId.replace(/^get/i, '').toLowerCase() || pathKey.replace(/[\/{\}]/g, '').replace(/\//g, '_')).replace(/^_+|_+$/g, ''); + if (!config.data_sources[dsName]) config.data_sources[dsName] = {}; + config.data_sources[dsName]['read'] = { path: pathKey, method: 'GET' }; + } else if (method.toLowerCase() === 'delete') { + // Attach delete to resource if exists + for (const resName in config.resources) { + if (pathKey.includes(resName)) { + config.resources[resName]['delete'] = { path: pathKey, method: 'DELETE' }; + } + } + } + } + } + } + + // Remove empty objects + if (Object.keys(config.resources).length === 0) delete config.resources; + if (Object.keys(config.data_sources).length === 0) delete config.data_sources; + + // Convert the config object to YAML + const yamlStr = yaml.dump(config, { noRefs: true, lineWidth: 120 }); + + // Ensure output directory exists + if (!fs.existsSync(data.outputPath)) { + fs.mkdirSync(data.outputPath, { recursive: true }); + } + + // Write the YAML string to the output file + const outputFile = path.join(data.outputPath, data.outputFileName); + fs.writeFileSync(outputFile, yamlStr, 'utf-8'); + } +} + + + + diff --git a/Scripts/TerraformProvider/Index.ts b/Scripts/TerraformProvider/Index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Scripts/package-lock.json b/Scripts/package-lock.json index 7c4eaf3fb9..adc9dc441b 100644 --- a/Scripts/package-lock.json +++ b/Scripts/package-lock.json @@ -10,8 +10,10 @@ "license": "Apache-2.0", "dependencies": { "@readme/openapi-parser": "^4.1.0", + "@types/js-yaml": "^4.0.9", "Common": "file:../Common", "ejs": "^3.1.10", + "js-yaml": "^4.1.0", "ts-node": "^10.9.2" }, "devDependencies": { @@ -67,6 +69,7 @@ "crypto-js": "^4.2.0", "dotenv": "^16.4.4", "ejs": "^3.1.10", + "esbuild": "^0.25.5", "express": "^4.21.1", "formik": "^2.4.6", "history": "^5.3.0", @@ -147,6 +150,7 @@ "jest-environment-jsdom": "^29.7.0", "jest-mock-extended": "^3.0.5", "react-test-renderer": "^18.2.0", + "sass": "^1.89.2", "ts-jest": "^28.0.5" } }, @@ -248,24 +252,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -797,6 +783,30 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1271,6 +1281,12 @@ "pretty-format": "^27.0.0" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1456,13 +1472,10 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/async": { "version": "3.2.4", @@ -3251,13 +3264,12 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -3999,7 +4011,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", @@ -4680,21 +4693,6 @@ "requires": { "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - } } }, "@babel/code-frame": { @@ -5089,6 +5087,27 @@ "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } } }, "@istanbuljs/schema": { @@ -5483,6 +5502,11 @@ "pretty-format": "^27.0.0" } }, + "@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" + }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5620,13 +5644,9 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "async": { "version": "3.2.4", @@ -5929,6 +5949,7 @@ "crypto-js": "^4.2.0", "dotenv": "^16.4.4", "ejs": "^3.1.10", + "esbuild": "^0.25.5", "express": "^4.21.1", "formik": "^2.4.6", "history": "^5.3.0", @@ -5972,6 +5993,7 @@ "redis-semaphore": "^5.5.1", "reflect-metadata": "^0.2.2", "remark-gfm": "^3.0.1", + "sass": "^1.89.2", "slackify-markdown": "^4.4.0", "slugify": "^1.6.5", "socket.io": "^4.7.4", @@ -7104,13 +7126,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsdom": { diff --git a/Scripts/package.json b/Scripts/package.json index 876443b877..0f2155fe54 100644 --- a/Scripts/package.json +++ b/Scripts/package.json @@ -7,8 +7,10 @@ }, "dependencies": { "@readme/openapi-parser": "^4.1.0", + "@types/js-yaml": "^4.0.9", "Common": "file:../Common", "ejs": "^3.1.10", + "js-yaml": "^4.1.0", "ts-node": "^10.9.2" }, "devDependencies": { diff --git a/package.json b/package.json index a0f3d0bce1..142356bda4 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,8 @@ "template-deploy-test": "kubectl config use-context oneuptime-test && helm template oneuptime ./HelmChart/Public/oneuptime -f ./HelmChart/Public/oneuptime/values.yaml -f ./HelmChart/Values/test.values.yaml --debug", "deploy-prod": "kubectl config use-context oneuptime-prod && helm upgrade oneuptime ./HelmChart/Public/oneuptime -f ./HelmChart/Public/oneuptime/values.yaml -f ./HelmChart/Values/prod.values.yaml", "generate-postgres-migration": "export $(grep -v '^#' config.env | xargs) && node --require ts-node/register ./node_modules/typeorm/cli.js migration:generate ./Common/Server/Infrastructure/Postgres/SchemaMigrations/MigrationName -d ./Common/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts", - "generate-openapi-spec": "node --require ts-node/register ./Scripts/OpenAPI/GenerateSpec.ts", - "generate-terraform-provider": "node --require ts-node/register ./Scripts/TerraformProvider/GenerateProvider.ts" + "generate-openapi-spec": "export $(grep -v '^#' config.env | xargs) && node --require ts-node/register ./Scripts/OpenAPI/GenerateSpec.ts", + "generate-terraform-provider": "export $(grep -v '^#' config.env | xargs) && node --require ts-node/register ./Scripts/TerraformProvider/GenerateProvider.ts" }, "repository": { "type": "git",