@liongard/inspector-dev-tools
Advanced tools
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const core_1 = require("@oclif/core"); | ||
| const deployInspectors_1 = require("../usecases/deployInspectors"); | ||
| class DeployInspectors extends core_1.Command { | ||
| static description = 'Updates inspector metadata for one or more Liongard instances.'; | ||
| static examples = [ | ||
| `$ idt deploy-inspectors --inspectors 'aws-inspector,office365-inspector' --version master`, | ||
| ]; | ||
| static flags = { | ||
| help: core_1.Flags.help({ char: 'h' }), | ||
| inspectors: core_1.Flags.string({ | ||
| description: 'The name of one or more inspector(s) to deploy. e.g. `aws-inspector,office365-inspector`', | ||
| required: true, | ||
| }), | ||
| allInspectors: core_1.Flags.boolean({ | ||
| description: 'Set this flag to deploy to all inspectors.', | ||
| default: false, | ||
| exclusive: ['inspectors'], | ||
| }), | ||
| version: core_1.Flags.string({ | ||
| description: 'git commit or branch to deploy.', | ||
| required: true, | ||
| }), | ||
| }; | ||
| async run() { | ||
| const { flags } = await this.parse(DeployInspectors); | ||
| await (0, deployInspectors_1.deployInspectors)(flags); | ||
| process.exit(0); | ||
| } | ||
| } | ||
| exports.default = DeployInspectors; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.deployInspectors = exports.cloneInspectorRespository = void 0; | ||
| const tmp_1 = __importDefault(require("tmp")); | ||
| const child_process_1 = require("child_process"); | ||
| const db_helpers_1 = require("../utils/db-helpers"); | ||
| const seedMetadata_1 = require("./seedMetadata"); | ||
| const cloneInspectorRespository = (repo, targetPath, commit) => { | ||
| (0, child_process_1.execSync)(`GIT_SSH_COMMAND="ssh -i /home/ubuntu/.ssh/inspector_metadata.pem -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git clone git@bitbucket.org:liongardteam/${repo}.git ${targetPath}`); | ||
| (0, child_process_1.execSync)(`git checkout ${commit}`, { cwd: targetPath }); | ||
| }; | ||
| exports.cloneInspectorRespository = cloneInspectorRespository; | ||
| const deployInspectors = async (flags) => { | ||
| const db = await (0, db_helpers_1.createDatabaseConnection)(); | ||
| const metadataSeedingService = new seedMetadata_1.MetadataSeeder(db); | ||
| const inspectors = flags.inspectors.split(','); | ||
| tmp_1.default.setGracefulCleanup(); | ||
| const tempDirectory = tmp_1.default.dirSync().name; | ||
| console.log(`Inspectors to deploy: ${inspectors}`); | ||
| console.log(`Cloning inspectors to ${tempDirectory}`); | ||
| await Promise.all(inspectors.map((repositoryName) => { | ||
| console.log(`Cloning ${repositoryName} to ${tempDirectory}/${repositoryName}`); | ||
| return (0, exports.cloneInspectorRespository)(repositoryName, `${tempDirectory}/${repositoryName}`, flags.version); | ||
| })); | ||
| console.log(`Successfully cloned inspectors from ${tempDirectory}`); | ||
| await Promise.all(inspectors.map(async (repositoryName) => { | ||
| await metadataSeedingService.seed(`${tempDirectory}/${repositoryName}`, flags.version); | ||
| })); | ||
| console.log(`Successfully seeded inspectors from ${tempDirectory}`); | ||
| metadataSeedingService.printMissingBundleMetadataInS3(); | ||
| await db.destroy(); | ||
| }; | ||
| exports.deployInspectors = deployInspectors; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createDatabaseConnection = void 0; | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const knex_1 = require("knex"); | ||
| const client_ssm_1 = require("@aws-sdk/client-ssm"); | ||
| const createDatabaseConnection = async () => { | ||
| const params = {}; | ||
| const client = new client_ssm_1.SSMClient({ | ||
| region: process.env.AWS_REGION, | ||
| }); | ||
| const command = new client_ssm_1.GetParametersCommand({ | ||
| Names: ['PG_USERNAME', 'PG_PASSWORD', 'PG_DATABASE', 'PG_HOSTNAME', 'PG_PORT'], | ||
| WithDecryption: true, | ||
| }); | ||
| let response; | ||
| try { | ||
| response = await client.send(command); | ||
| } | ||
| catch (err) { | ||
| console.log(`Connecting to localhost pgSql.`); | ||
| } | ||
| if (!response?.Parameters) { | ||
| if (response?.InvalidParameters) | ||
| console.log(`Invalid or no params returned from SSM in when trying to establish DB connection. ${response.InvalidParameters}. Connecting to localhost pgSql instead.`); | ||
| let connection = { | ||
| host: 'localhost', | ||
| port: 5432, | ||
| user: 'roaradmin', | ||
| password: 'password', | ||
| database: 'roar', | ||
| }; | ||
| if (fs_1.default.existsSync('./localDatabaseCreds.json')) | ||
| connection = JSON.parse(fs_1.default.readFileSync('./localDatabaseCreds.json').toString()); // allow local override | ||
| return (0, knex_1.knex)({ | ||
| client: 'pg', | ||
| connection, | ||
| }); | ||
| } | ||
| else { | ||
| console.log(`Found AWS credentials. Attempting to connect to RDS postgresql. If you're doing local development and are trying to seed your localhost pgSql, this is not what you want and you should not be doing this in a terminal instance that has AWS credentials in its env.`); | ||
| response.Parameters.forEach(function (p) { | ||
| params[p.Name] = p.Value; | ||
| }); | ||
| return (0, knex_1.knex)({ | ||
| client: 'pg', | ||
| connection: { | ||
| host: params['PG_HOSTNAME'], | ||
| user: params['PG_USERNAME'], | ||
| password: params['PG_PASSWORD'], | ||
| database: params['PG_DATABASE'], | ||
| port: params['PG_PORT'], | ||
| }, | ||
| }); | ||
| } | ||
| }; | ||
| exports.createDatabaseConnection = createDatabaseConnection; |
@@ -23,6 +23,11 @@ "use strict"; | ||
| const { flags } = await this.parse(Publish); | ||
| await (0, publish_1.publish)(!flags.bundleOnly, flags.skipGitCheck); | ||
| process.exit(0); | ||
| try { | ||
| const result = await (0, publish_1.publish)(!flags.bundleOnly, flags.skipGitCheck); | ||
| return result; | ||
| } | ||
| catch (error) { | ||
| throw error; | ||
| } | ||
| } | ||
| } | ||
| exports.default = Publish; |
@@ -5,2 +5,3 @@ "use strict"; | ||
| const seedMetadata_1 = require("../usecases/seedMetadata"); | ||
| const db_helpers_1 = require("../utils/db-helpers"); | ||
| class SeedMetadata extends core_1.Command { | ||
@@ -35,3 +36,4 @@ static description = 'Seeds metadata from inspectors into the DB. You can provide a localDatabaseCreds.json in the working directory to tell this command how to connect to the database. You can seed one inspector or many at once.'; | ||
| const { args, flags } = await this.parse(SeedMetadata); | ||
| await (0, seedMetadata_1.seed)(args.path, flags.branch, flags.recurse); | ||
| const metadataSeeder = new seedMetadata_1.MetadataSeeder(await (0, db_helpers_1.createDatabaseConnection)()); | ||
| await metadataSeeder.seed(args.path, flags.branch, flags.recurse); | ||
| process.exit(0); | ||
@@ -38,0 +40,0 @@ } |
@@ -18,9 +18,19 @@ "use strict"; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| var __importStar = (this && this.__importStar) || (function () { | ||
| var ownKeys = function(o) { | ||
| ownKeys = Object.getOwnPropertyNames || function (o) { | ||
| var ar = []; | ||
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; | ||
| return ar; | ||
| }; | ||
| return ownKeys(o); | ||
| }; | ||
| return function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| })(); | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -27,0 +37,0 @@ exports.generateBuildspec = void 0; |
@@ -6,3 +6,3 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.prepareBundleForDeployment = exports.buildAndPublishBundle = exports.postBundleMetadataToS3 = exports.fetchBundleMetadataFromS3 = exports.getInspectorNameFromMetadataDefinition = void 0; | ||
| exports.prepareBundleForDeployment = exports.buildAndPublishBundle = exports.postBundleMetadataToS3 = exports.fetchBundleMetadataFromS3 = exports.getRepositoryName = exports.isMissingBundleMetadataError = exports.MissingBundleMetadataError = void 0; | ||
| const fs_1 = __importDefault(require("fs")); | ||
@@ -16,2 +16,12 @@ const child_process_1 = require("child_process"); | ||
| const validator = new inspector_metadata_1.Validator(); | ||
| class MissingBundleMetadataError extends Error { | ||
| isMissingBundleMetadataError = true; | ||
| constructor(message) { | ||
| super(message); | ||
| Error.captureStackTrace(this, this.constructor); | ||
| } | ||
| } | ||
| exports.MissingBundleMetadataError = MissingBundleMetadataError; | ||
| const isMissingBundleMetadataError = (error) => error.isMissingBundleMetadataError === true; | ||
| exports.isMissingBundleMetadataError = isMissingBundleMetadataError; | ||
| const getLatestCommitOnCheckedOutBranch = () => { | ||
@@ -26,22 +36,23 @@ const HEAD = fs_1.default.readFileSync('.git/HEAD').toString().trim(); | ||
| }; | ||
| const getInspectorNameFromMetadataDefinition = () => JSON.parse(fs_1.default.readFileSync('./metadata/definition.json').toString())?.Name; | ||
| exports.getInspectorNameFromMetadataDefinition = getInspectorNameFromMetadataDefinition; | ||
| const getRepositoryName = () => (0, child_process_1.execSync)('git rev-parse --show-toplevel').toString().trim().split('/').pop(); | ||
| exports.getRepositoryName = getRepositoryName; | ||
| const fetchBundleMetadataFromS3 = async (fetchBundleMetadataFromS3Params) => { | ||
| try { | ||
| const { name, branch, location } = fetchBundleMetadataFromS3Params; | ||
| // n.b. We prepend this `git ls-remote` call with GIT_SSH_COMMAND w/ some additional options in order to use the inspector_metadata.pem | ||
| // SSH key that's copied to the targetted application server (e.g. Nemean) | ||
| const latestCommitOnBranch = (0, child_process_1.execSync)(`${location === 'application-server' | ||
| const { repositoryName, branch, executionEnvironment } = fetchBundleMetadataFromS3Params; | ||
| console.log(`[fetchBundleMetadataFromS3] Starting fetch for repository: ${repositoryName}, branch: ${branch}, env: ${executionEnvironment}`); | ||
| // For S3, we use autodiscovery-inspector if the repo is network-discovery-inspector | ||
| const s3BundleName = repositoryName; | ||
| console.log(`[fetchBundleMetadataFromS3] Using S3 bundle name: ${s3BundleName} (original repo: ${repositoryName})`); | ||
| console.log(`[fetchBundleMetadataFromS3] Getting latest commit on ${branch} for ${repositoryName}`); | ||
| const latestCommitOnBranch = (0, child_process_1.execSync)(`${executionEnvironment === 'application-server' | ||
| ? 'GIT_SSH_COMMAND="ssh -i /home/ubuntu/.ssh/inspector_metadata.pem -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" ' | ||
| : ''} git ls-remote bitbucket.org:liongardteam/${name}.git refs/heads/${branch} | awk '{ print $1 }'`) | ||
| : ''} git ls-remote bitbucket.org:liongardteam/${repositoryName}.git refs/heads/${branch} | awk '{ print $1 }'`) | ||
| .toString() | ||
| .trim(); | ||
| console.log(`Fetching bundle metadata from S3 for ${name} at ${latestCommitOnBranch} on branch '${branch}'.`); | ||
| // If this function is being called from an application server, we use these devops-specific AWS credentils that are copied to each | ||
| // application server at deployment-time. If this function is being called from the deployment server, we use the EC2 instance metadata | ||
| // service credentials (which permit the same level of access to the replicated 'inspector-bundles-x' bucket(s)). | ||
| console.log(`[fetchBundleMetadataFromS3] Latest commit found: ${latestCommitOnBranch} for ${repositoryName} on branch '${branch}'`); | ||
| console.log(`[fetchBundleMetadataFromS3] Will fetch bundle metadata from S3 path: ${constants_1.BUNDLE_METADATA_DIRECTORY}/${s3BundleName}/${latestCommitOnBranch}`); | ||
| const s3 = new client_s3_1.S3Client({ | ||
| ...(0, constants_1.getS3ClientConfig)(), | ||
| ...{ | ||
| credentials: location === 'application-server' && | ||
| credentials: executionEnvironment === 'application-server' && | ||
| process.env.DEVOPS_AWS_ACCESS_KEY_ID && | ||
@@ -56,20 +67,28 @@ process.env.DEVOPS_AWS_SECRET_ACCESS_KEY | ||
| }); | ||
| console.log(`[fetchBundleMetadataFromS3] Initialized S3 client with ${executionEnvironment} credentials`); | ||
| const object = await s3.send(new client_s3_1.GetObjectCommand({ | ||
| Bucket: constants_1.INSPECTOR_BUNDLES_REPLICATED_BUCKET, | ||
| Key: `${constants_1.BUNDLE_METADATA_DIRECTORY}/${name}/${latestCommitOnBranch}`, | ||
| Key: `${constants_1.BUNDLE_METADATA_DIRECTORY}/${s3BundleName}/${latestCommitOnBranch}`, | ||
| })); | ||
| console.log(`[fetchBundleMetadataFromS3] Successfully retrieved object from S3`); | ||
| const bodyString = await object.Body?.transformToString('utf-8'); | ||
| if (!bodyString) { | ||
| throw new Error(`No bundle metadata found in S3 for ${name} at ${latestCommitOnBranch} on branch '${branch}'.`); | ||
| console.log(`[fetchBundleMetadataFromS3] ERROR: Retrieved empty body from S3`); | ||
| throw new Error(`No bundle metadata found in S3 for ${s3BundleName} at ${latestCommitOnBranch} on branch '${branch}'.`); | ||
| } | ||
| const bundleMetadata = JSON.parse(bodyString); | ||
| if (!validator.isBundleMetadataValid(bundleMetadata)) { | ||
| throw new Error(`Found bundle metadata in S3 for ${name} at ${latestCommitOnBranch} on branch '${branch}', but received metadata is malformed: ${bodyString}`); | ||
| console.log(`[fetchBundleMetadataFromS3] ERROR: Retrieved invalid bundle metadata: ${bodyString}`); | ||
| throw new Error(`Found bundle metadata in S3 for ${s3BundleName} at ${latestCommitOnBranch} on branch '${branch}', but received metadata is malformed: ${bodyString}`); | ||
| } | ||
| console.log(`Successfully fetched bundle metadata from S3 for ${name} at ${latestCommitOnBranch}:\n${JSON.stringify(bundleMetadata, null, 2)}`); | ||
| console.log(`Bundle metadata last modified on ${object.LastModified}.`); | ||
| console.log(`[fetchBundleMetadataFromS3] Successfully validated bundle metadata for ${s3BundleName}:\n${JSON.stringify(bundleMetadata, null, 2)}`); | ||
| console.log(`[fetchBundleMetadataFromS3] Bundle metadata last modified on ${object.LastModified}`); | ||
| return bundleMetadata; | ||
| } | ||
| catch (error) { | ||
| throw new Error(`Error fetching bundle metadata for ${fetchBundleMetadataFromS3Params.name} on branch '${fetchBundleMetadataFromS3Params.branch}' from S3`, { cause: error }); | ||
| const s3BundleName = fetchBundleMetadataFromS3Params.repositoryName === 'network-discovery-inspector' | ||
| ? 'autodiscovery-inspector' | ||
| : fetchBundleMetadataFromS3Params.repositoryName; | ||
| console.log(`[fetchBundleMetadataFromS3] ERROR: Failed to fetch bundle metadata:`, error); | ||
| throw new MissingBundleMetadataError(`Error fetching bundle metadata for ${s3BundleName} on branch '${fetchBundleMetadataFromS3Params.branch}' from S3. ${error?.stack}`); | ||
| } | ||
@@ -81,9 +100,17 @@ }; | ||
| const { version, checksum, policyIntegrity } = postBundleMetadataToS3Params; | ||
| const name = (0, exports.getInspectorNameFromMetadataDefinition)(); | ||
| const repositoryName = (0, exports.getRepositoryName)(); | ||
| console.log(`[postBundleMetadataToS3] Starting upload for repository: ${repositoryName}`); | ||
| console.log(`[postBundleMetadataToS3] Version: ${version}, Checksum: ${checksum}`); | ||
| const commit = getLatestCommitOnCheckedOutBranch(); | ||
| console.log(`Uploading bundle metadata to S3 for ${name} at ${commit}.`); | ||
| console.log(`[postBundleMetadataToS3] Got latest commit: ${commit}`); | ||
| // Handle the name override for S3 | ||
| const s3BundleName = repositoryName; | ||
| console.log(`[postBundleMetadataToS3] Using S3 bundle name: ${s3BundleName} (original repo: ${repositoryName})`); | ||
| const s3 = new client_s3_1.S3Client((0, constants_1.getS3ClientConfig)()); | ||
| console.log(`[postBundleMetadataToS3] Initialized S3 client`); | ||
| const s3Path = `${constants_1.BUNDLE_METADATA_DIRECTORY}/${s3BundleName}/${commit}`; | ||
| console.log(`[postBundleMetadataToS3] Will upload to S3 path: ${s3Path}`); | ||
| await s3.send(new client_s3_1.PutObjectCommand({ | ||
| Bucket: constants_1.INSPECTOR_BUNDLES_REPLICATED_BUCKET, | ||
| Key: `${constants_1.BUNDLE_METADATA_DIRECTORY}/${name}/${commit}`, | ||
| Key: s3Path, | ||
| Body: JSON.stringify({ | ||
@@ -95,5 +122,6 @@ Version: version, | ||
| })); | ||
| console.log(`Successfully uploaded bundle metadata to S3 for ${name} at ${commit}.`); | ||
| console.log(`[postBundleMetadataToS3] Successfully uploaded bundle metadata to S3 for ${s3BundleName} at ${commit}`); | ||
| } | ||
| catch (error) { | ||
| console.log(`[postBundleMetadataToS3] ERROR: Failed to upload bundle metadata:`, error); | ||
| throw new Error(`Error uploading bundle metadata to S3`, { cause: error }); | ||
@@ -142,5 +170,5 @@ } | ||
| exports.buildAndPublishBundle = buildAndPublishBundle; | ||
| const prepareBundleForDeployment = async (repositoryPath, name, branch) => { | ||
| const prepareBundleForDeployment = async (repositoryPath, repositoryName, branch) => { | ||
| // Override name if it's network-discovery-inspector | ||
| const effectiveName = name === 'network-discovery-inspector' ? 'autodiscovery-inspector' : name; | ||
| const effectiveName = repositoryName === 'network-discovery-inspector' ? 'autodiscovery-inspector' : repositoryName; | ||
| console.log(`Checking that ${effectiveName} bundle metadata exists in S3 for latest commit on ${branch}.`); | ||
@@ -150,5 +178,5 @@ let bundleMetadata; | ||
| bundleMetadata = await (0, exports.fetchBundleMetadataFromS3)({ | ||
| name: effectiveName, | ||
| repositoryName: effectiveName, | ||
| branch, | ||
| location: 'deployment-server', | ||
| executionEnvironment: 'deployment-server', | ||
| }); | ||
@@ -155,0 +183,0 @@ } |
@@ -67,3 +67,3 @@ "use strict"; | ||
| } | ||
| console.log(`Zipping up contents of '${exports.BUILD_SPEC_ENFORCED_BUNDLE_FOLDER_NAME}', 'node_modules', ${buildSpec.includePowershell === 'true' ? "'powershell', " : ''}and 'package.json'`); | ||
| console.log(`Zipping up contents of '${exports.BUILD_SPEC_ENFORCED_BUNDLE_FOLDER_NAME}', 'node_modules', ${buildSpec.includePowershell === 'true' ? "'powershell', " : ''}'metadata', and 'package.json'`); | ||
| const fileNames = [exports.BUILD_SPEC_ENFORCED_BUNDLE_FOLDER_NAME, 'node_modules', 'package.json']; | ||
@@ -73,5 +73,12 @@ if (buildSpec.includePowershell === 'true') { | ||
| } | ||
| // Include metadata directory in bundle if it exists | ||
| if (fs_1.default.existsSync(`${process.cwd()}/metadata`)) { | ||
| fileNames.push('metadata'); | ||
| } | ||
| const manifestHash = await (0, generateManifest_1.generateManifest)(fileNames); | ||
| const zipCommand = (0, exports.getZipCommand)(bundleName, buildSpec.folder, generateManifest_1.manifestFile, buildSpec); | ||
| // create the zipped bundle | ||
| (0, child_process_1.execSync)((0, exports.getZipCommand)(bundleName, buildSpec.folder, generateManifest_1.manifestFile, buildSpec)); | ||
| (0, child_process_1.execSync)(zipCommand); | ||
| // Skip manifest recreation to prevent Node.js policy loading issues | ||
| // The manifest.json remains inside the ZIP bundle for proper policy loading | ||
| const bundleStats = getBundleStats(bundleName); | ||
@@ -103,2 +110,14 @@ const bundleMessage = `Successfully zipped bundle '${bundleName}' of size ${Math.round((bundleStats.size / 1024 / 1024) * 100) / 100} MB`; | ||
| }); | ||
| if ((0, prepareBundleForDeployment_1.getRepositoryName)() === 'windows-workstation-inspector') { | ||
| console.log('Creating a copy of the bundle for the windows server'); | ||
| const date = (0, dayjs_1.default)().format('YYYYMMDD'); | ||
| const gitHash = (0, child_process_1.execSync)('git log -n 1 --pretty=format:"%h"', { | ||
| encoding: 'utf8', | ||
| }); | ||
| const windowsServerBundleName = `windows-server-inspector.${buildSpec.version}-${date}-${gitHash}.zip`; | ||
| fs_1.default.copyFileSync(`${process.cwd()}/${bundleName}`, `${process.cwd()}/${windowsServerBundleName}`); | ||
| await uploadZip(windowsServerBundleName, constants_1.INSPECTOR_BUNDLES_REPLICATED_BUCKET); | ||
| slackMessage.push(bundleMessage, `\`\`\`inspector_version: ${inspector_version}`, `checksum: ${digest}`, `policy_integrity: ${manifestHash}\`\`\``); | ||
| await postSlackMessage(slackMessage); | ||
| } | ||
| return { | ||
@@ -135,9 +154,7 @@ name: bundleName, | ||
| try { | ||
| console.log(`Attempting to read ${constants_1.BUILD_SPEC_CONFIG_FILE_NAME}`); | ||
| fileContents = fs_1.default.readFileSync(`${process.cwd()}/${constants_1.BUILD_SPEC_CONFIG_FILE_NAME}`, 'utf8'); | ||
| } | ||
| catch { | ||
| catch (error) { | ||
| throw new Error(`Unable to read ${constants_1.BUILD_SPEC_CONFIG_FILE_NAME}. You can run 'idt generate-buildspec' to create one.`); | ||
| } | ||
| console.log(`Attempting to parse the contents of ${constants_1.BUILD_SPEC_CONFIG_FILE_NAME}`); | ||
| const potentialBuildSpec = constructBuildSpecCandidate(fileContents); | ||
@@ -219,4 +236,13 @@ validatePotentialBuildSpecObject(potentialBuildSpec); | ||
| const getZipCommand = (bundleName, folder, manifestFile, buildSpec = {}) => { | ||
| // Check for metadata directory | ||
| const metadataExists = fs_1.default.existsSync(`${process.cwd()}/metadata`); | ||
| if (['darwin', 'linux'].includes(process.platform)) { | ||
| return `zip --quiet -r ${bundleName} ${folder} ${manifestFile} node_modules package.json`; | ||
| let command = `zip --quiet -r ${bundleName} ${folder} ${manifestFile} node_modules package.json`; | ||
| if (metadataExists) { | ||
| command += ' metadata'; | ||
| } | ||
| if (buildSpec.includePowershell === 'true') { | ||
| command += ' powershell'; | ||
| } | ||
| return command; | ||
| } | ||
@@ -228,2 +254,5 @@ else if (process.platform === 'win32') { | ||
| } | ||
| if (metadataExists) { | ||
| paths.push('metadata'); | ||
| } | ||
| return `powershell -command "Compress-Archive -Path '${paths.join("','")}' -DestinationPath ${bundleName}"`; | ||
@@ -230,0 +259,0 @@ } |
+430
-459
@@ -6,198 +6,211 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.seed = exports.seedInspectorDefinition = void 0; | ||
| exports.MetadataSeeder = void 0; | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const client_ssm_1 = require("@aws-sdk/client-ssm"); | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const knex_1 = require("knex"); | ||
| const inspector_metadata_1 = require("@liongard/inspector-metadata"); | ||
| const spawnChildProcess_1 = require("./spawnChildProcess"); | ||
| const prepareBundleForDeployment_1 = require("./prepareBundleForDeployment"); | ||
| const validator = new inspector_metadata_1.Validator(); | ||
| const seedInspectorDefinition = async (txn, { INSPECTOR_DEFINITION, VIEWS, TEMPLATE, QUICKVIEWS }) => { | ||
| console.log(`Seeding metadata for ${INSPECTOR_DEFINITION.Name}`); | ||
| if (!INSPECTOR_DEFINITION.InspectorID) | ||
| throw new Error(`${INSPECTOR_DEFINITION.Name} must provide InspectorID in metadata/definition.json`); | ||
| if (!INSPECTOR_DEFINITION.InspectorVersionID) | ||
| throw new Error(`${INSPECTOR_DEFINITION.Name} must provide InspectorVersionID in metadata/definition.json`); | ||
| const inspectorDefinitionForDB = { | ||
| ID: INSPECTOR_DEFINITION.InspectorID, | ||
| Name: INSPECTOR_DEFINITION.Name, | ||
| Alias: INSPECTOR_DEFINITION.Alias, | ||
| Published: INSPECTOR_DEFINITION.Published, | ||
| Author: INSPECTOR_DEFINITION.Author, | ||
| ContactEmail: INSPECTOR_DEFINITION.ContactEmail, | ||
| Logo: INSPECTOR_DEFINITION.Logo, | ||
| Description: INSPECTOR_DEFINITION.Description, | ||
| DefaultFrequency: INSPECTOR_DEFINITION.DefaultFrequency, | ||
| Constraints: INSPECTOR_DEFINITION.Constraints, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| HelpLink: INSPECTOR_DEFINITION.HelpLink, | ||
| PublishedStatus: INSPECTOR_DEFINITION.PublishedStatus, | ||
| InspectorCategory: INSPECTOR_DEFINITION.InspectorCategory, | ||
| EndpointBilling: INSPECTOR_DEFINITION.EndpointBilling, | ||
| Icon: INSPECTOR_DEFINITION.Icon, | ||
| DedupeOnEnvironmentId: INSPECTOR_DEFINITION.DedupeOnEnvironmentId, | ||
| }; | ||
| const updatedInspectorRowCount = await txn('Inspector') | ||
| .update(inspectorDefinitionForDB) | ||
| .where({ ID: INSPECTOR_DEFINITION.InspectorID }); | ||
| if (!updatedInspectorRowCount) { | ||
| inspectorDefinitionForDB.CreatedOn = new Date().toUTCString(); | ||
| await txn('Inspector').insert(inspectorDefinitionForDB); | ||
| class MetadataSeeder { | ||
| dbClient; | ||
| validator; | ||
| missingBundleMetadataInS3 = []; | ||
| constructor(dbClient) { | ||
| this.dbClient = dbClient; | ||
| this.validator = new inspector_metadata_1.Validator(); | ||
| } | ||
| /* upsert the InspectorVersion. We no longer support more than one production version of an inspector and could get rid of this table | ||
| by combining it with Inspector */ | ||
| const inspectorDefinitionVersionForDB = { | ||
| ID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| Name: INSPECTOR_DEFINITION.Version, | ||
| WorkingDir: INSPECTOR_DEFINITION.WorkingDir, | ||
| RunCommand: INSPECTOR_DEFINITION.RunCommand, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| Discovers: INSPECTOR_DEFINITION.Discovers, | ||
| Checksum: INSPECTOR_DEFINITION.Checksum, | ||
| PolicyManifest: INSPECTOR_DEFINITION.PolicyManifest, | ||
| PolicyIntegrity: INSPECTOR_DEFINITION.PolicyIntegrity, | ||
| OnDemandEnabled: INSPECTOR_DEFINITION.OnDemandEnabled, | ||
| }; | ||
| const updatedInspectorVersionRowCount = await txn('InspectorVersion') | ||
| .update(inspectorDefinitionVersionForDB) | ||
| .where({ ID: INSPECTOR_DEFINITION.InspectorVersionID }); | ||
| if (!updatedInspectorVersionRowCount) { | ||
| inspectorDefinitionVersionForDB.CreatedOn = INSPECTOR_DEFINITION.InspectorVersionID === 128 | ||
| ? '2018-03-03 16:23:40+00' | ||
| : new Date().toUTCString(); | ||
| await txn('InspectorVersion').insert(inspectorDefinitionVersionForDB); | ||
| } | ||
| // upsert the UI views. There is only one row for each inspector version. This could also move over to the Inspector table now that we support only one version. There is no need for a separate table. | ||
| const viewDefinition = { Views: VIEWS }; // the database has these wrapped in a key called Views for no real reason. | ||
| const inspectorViewForDB = { | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Name: 'Default', | ||
| Description: 'Default', | ||
| ViewDefinition: viewDefinition, | ||
| Default: true, | ||
| Enabled: true, | ||
| Visible: true, //seems like these could be dropped. they're all the same all the time for all inspectors. | ||
| UpdatedOn: new Date().toUTCString(), | ||
| }; | ||
| const viewExists = await txn('InspectorView').select('ID').where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }); | ||
| if (viewExists && viewExists.length) { | ||
| const inspectorViewID = Number.parseInt(viewExists[0].ID); | ||
| await txn('InspectorView').update(inspectorViewForDB).where({ ID: inspectorViewID }); | ||
| if (viewExists.length > 1) { | ||
| // There should only be one row. There are some garbagey old views to clean away. | ||
| await txn('InspectorView') | ||
| .delete() | ||
| .where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }) | ||
| .andWhereNot({ ID: inspectorViewID }); | ||
| printMissingBundleMetadataInS3() { | ||
| if (!this.missingBundleMetadataInS3.length) { | ||
| return; | ||
| } | ||
| console.log('The following bundles are missing from the S3 bucket, please provide the metadata and run again:'); | ||
| console.log(this.missingBundleMetadataInS3.join('\n')); | ||
| } | ||
| else { | ||
| inspectorViewForDB.CreatedOn = new Date().toUTCString(); | ||
| await txn('InspectorView').insert(inspectorViewForDB); | ||
| } | ||
| // upsert the inspector template. | ||
| const inspectortemplateForDB = { | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Variables: { Variables: TEMPLATE }, | ||
| UpdatedOn: null, //this has to be null or else the config page breaks for some reason :) | ||
| }; | ||
| const templateExists = await txn('Template').select('ID').where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }); | ||
| if (templateExists && templateExists.length) { | ||
| const inspectorTemplateID = Number.parseInt(templateExists[0].ID); | ||
| await txn('Template').update(inspectortemplateForDB).where({ ID: inspectorTemplateID }); | ||
| if (templateExists.length > 1) { | ||
| // There should only be one row. There are some garbagey old views to clean away. | ||
| await txn('Template') | ||
| .delete() | ||
| .where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }) | ||
| .andWhereNot({ ID: inspectorTemplateID }); | ||
| seedInspectorDefinition = async (txn, { INSPECTOR_DEFINITION, VIEWS, TEMPLATE, QUICKVIEWS }) => { | ||
| console.log(`Seeding metadata for ${INSPECTOR_DEFINITION.Name}`); | ||
| if (!INSPECTOR_DEFINITION.InspectorID) | ||
| throw new Error(`${INSPECTOR_DEFINITION.Name} must provide InspectorID in metadata/definition.json`); | ||
| if (!INSPECTOR_DEFINITION.InspectorVersionID) | ||
| throw new Error(`${INSPECTOR_DEFINITION.Name} must provide InspectorVersionID in metadata/definition.json`); | ||
| const inspectorDefinitionForDB = { | ||
| ID: INSPECTOR_DEFINITION.InspectorID, | ||
| Name: INSPECTOR_DEFINITION.Name, | ||
| Alias: INSPECTOR_DEFINITION.Alias, | ||
| Published: INSPECTOR_DEFINITION.Published, | ||
| Author: INSPECTOR_DEFINITION.Author, | ||
| ContactEmail: INSPECTOR_DEFINITION.ContactEmail, | ||
| Logo: INSPECTOR_DEFINITION.Logo, | ||
| Description: INSPECTOR_DEFINITION.Description, | ||
| DefaultFrequency: INSPECTOR_DEFINITION.DefaultFrequency, | ||
| Constraints: INSPECTOR_DEFINITION.Constraints, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| HelpLink: INSPECTOR_DEFINITION.HelpLink, | ||
| PublishedStatus: INSPECTOR_DEFINITION.PublishedStatus, | ||
| InspectorCategory: INSPECTOR_DEFINITION.InspectorCategory, | ||
| EndpointBilling: INSPECTOR_DEFINITION.EndpointBilling, | ||
| Icon: INSPECTOR_DEFINITION.Icon, | ||
| DedupeOnEnvironmentId: INSPECTOR_DEFINITION.DedupeOnEnvironmentId, | ||
| }; | ||
| const updatedInspectorRowCount = await txn('Inspector') | ||
| .update(inspectorDefinitionForDB) | ||
| .where({ ID: INSPECTOR_DEFINITION.InspectorID }); | ||
| if (!updatedInspectorRowCount) { | ||
| inspectorDefinitionForDB.CreatedOn = new Date().toUTCString(); | ||
| await txn('Inspector').insert(inspectorDefinitionForDB); | ||
| } | ||
| } | ||
| else { | ||
| inspectortemplateForDB.CreatedOn = new Date().toUTCString(); | ||
| await txn('Template').insert(inspectortemplateForDB); | ||
| } | ||
| // upsert the quickviews | ||
| if (QUICKVIEWS.length) { | ||
| const quickViewsForDB = QUICKVIEWS.map((aQuickView) => { | ||
| const aQuickViewForDB = { | ||
| InspectorVersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| ...aQuickView, | ||
| }; | ||
| aQuickViewForDB.Filters = JSON.stringify(aQuickView.Filters); //json arrays don't map automatically. have to stringify them. | ||
| aQuickViewForDB.Sorting = JSON.stringify(aQuickView.Sorting); | ||
| return aQuickViewForDB; | ||
| /* upsert the InspectorVersion. We no longer support more than one production version of an inspector and could get rid of this table | ||
| by combining it with Inspector */ | ||
| const inspectorDefinitionVersionForDB = { | ||
| ID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| Name: INSPECTOR_DEFINITION.Version, | ||
| WorkingDir: INSPECTOR_DEFINITION.WorkingDir, | ||
| RunCommand: INSPECTOR_DEFINITION.RunCommand, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| Discovers: INSPECTOR_DEFINITION.Discovers, | ||
| Checksum: INSPECTOR_DEFINITION.Checksum, | ||
| PolicyManifest: INSPECTOR_DEFINITION.PolicyManifest, | ||
| PolicyIntegrity: INSPECTOR_DEFINITION.PolicyIntegrity, | ||
| OnDemandEnabled: INSPECTOR_DEFINITION.OnDemandEnabled, | ||
| }; | ||
| const updatedInspectorVersionRowCount = await txn('InspectorVersion') | ||
| .update(inspectorDefinitionVersionForDB) | ||
| .where({ ID: INSPECTOR_DEFINITION.InspectorVersionID }); | ||
| if (!updatedInspectorVersionRowCount) { | ||
| inspectorDefinitionVersionForDB.CreatedOn = | ||
| INSPECTOR_DEFINITION.InspectorVersionID === 128 | ||
| ? '2018-03-03 16:23:40+00' | ||
| : new Date().toUTCString(); | ||
| await txn('InspectorVersion').insert(inspectorDefinitionVersionForDB); | ||
| } | ||
| // upsert the UI views. There is only one row for each inspector version. This could also move over to the Inspector table now that we support only one version. There is no need for a separate table. | ||
| const viewDefinition = { Views: VIEWS }; // the database has these wrapped in a key called Views for no real reason. | ||
| const inspectorViewForDB = { | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Name: 'Default', | ||
| Description: 'Default', | ||
| ViewDefinition: viewDefinition, | ||
| Default: true, | ||
| Enabled: true, | ||
| Visible: true, //seems like these could be dropped. they're all the same all the time for all inspectors. | ||
| UpdatedOn: new Date().toUTCString(), | ||
| }; | ||
| const viewExists = await txn('InspectorView').select('ID').where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }); | ||
| //UCK is a unique constraint which allows us to can do simple onConflict merge. All UCKs should be like this! | ||
| await txn('QuickViewTemplate').insert(quickViewsForDB).onConflict('UCK').merge(); | ||
| // delete any existing quickviews from the database that are no longer in the metadata. | ||
| // The whereNotNull UCK is not actually necessary because the whereNotIn already excludes nulls because anything compared to null does not match. | ||
| await txn('QuickViewTemplate') | ||
| .delete() | ||
| .where({ InspectorVersionID: INSPECTOR_DEFINITION.InspectorVersionID }) | ||
| .whereNotNull('UCK') | ||
| .whereNotIn('UCK', QUICKVIEWS.map((aQuickView) => aQuickView.UCK)); | ||
| } | ||
| }; | ||
| exports.seedInspectorDefinition = seedInspectorDefinition; | ||
| const seedMetrics = async (txn, { INSPECTOR_DEFINITION, METRICS }) => { | ||
| if (METRICS.length) { | ||
| const metricsToUpsert = METRICS.map((aMetric) => { | ||
| const aMetricToUpsert = lodash_1.default.omit(aMetric, 'Query', 'EvaluateFor'); // omit these for now because they are not yet on the Metric table | ||
| aMetricToUpsert.InspectorID = INSPECTOR_DEFINITION.InspectorID; | ||
| aMetricToUpsert.UpdatedOn = new Date().toUTCString(); | ||
| delete aMetricToUpsert.ChangesEnabled; | ||
| return aMetricToUpsert; | ||
| if (viewExists && viewExists.length) { | ||
| const inspectorViewID = Number.parseInt(viewExists[0].ID); | ||
| await txn('InspectorView').update(inspectorViewForDB).where({ ID: inspectorViewID }); | ||
| if (viewExists.length > 1) { | ||
| // There should only be one row. There are some garbagey old views to clean away. | ||
| await txn('InspectorView') | ||
| .delete() | ||
| .where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }) | ||
| .andWhereNot({ ID: inspectorViewID }); | ||
| } | ||
| } | ||
| else { | ||
| inspectorViewForDB.CreatedOn = new Date().toUTCString(); | ||
| await txn('InspectorView').insert(inspectorViewForDB); | ||
| } | ||
| // upsert the inspector template. | ||
| const inspectortemplateForDB = { | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Variables: { Variables: TEMPLATE }, | ||
| UpdatedOn: null, //this has to be null or else the config page breaks for some reason :) | ||
| }; | ||
| const templateExists = await txn('Template').select('ID').where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }); | ||
| await txn('Metric').insert(metricsToUpsert).onConflict('UUID').merge(); // would be better if the unique constraint were on UCK and this field was removed | ||
| // get back the metric IDs so that we can save the queries on the MetricVersion table. | ||
| // Someday we should move the Query to the Metric table after we've gotten rid of versions for all inspectors and this complexity could go away. | ||
| const metricID_UCK_assoc = await txn('Metric') | ||
| .select('ID', 'UCK') | ||
| .where({ InspectorID: INSPECTOR_DEFINITION.InspectorID }) | ||
| .whereNotNull('UCK'); | ||
| for (const anAssociation of metricID_UCK_assoc) { | ||
| const aMetric = lodash_1.default.find(METRICS, (aMetric) => anAssociation.UCK === aMetric.UCK); | ||
| if (aMetric) { | ||
| // if there was a composite unique constraint on MetricID and InspectorVersionID (as it seems there should be), then we could have done an onConflict merge here instead of this: | ||
| const updateResult = await txn('MetricVersion').update({ Query: aMetric.Query }).where({ | ||
| MetricID: anAssociation.ID, | ||
| if (templateExists && templateExists.length) { | ||
| const inspectorTemplateID = Number.parseInt(templateExists[0].ID); | ||
| await txn('Template').update(inspectortemplateForDB).where({ ID: inspectorTemplateID }); | ||
| if (templateExists.length > 1) { | ||
| // There should only be one row. There are some garbagey old views to clean away. | ||
| await txn('Template') | ||
| .delete() | ||
| .where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }) | ||
| .andWhereNot({ ID: inspectorTemplateID }); | ||
| } | ||
| } | ||
| else { | ||
| inspectortemplateForDB.CreatedOn = new Date().toUTCString(); | ||
| await txn('Template').insert(inspectortemplateForDB); | ||
| } | ||
| // upsert the quickviews | ||
| if (QUICKVIEWS.length) { | ||
| const quickViewsForDB = QUICKVIEWS.map((aQuickView) => { | ||
| const aQuickViewForDB = { | ||
| InspectorVersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| }); | ||
| if (updateResult !== 1) { | ||
| // no row was found to update, so insert instead. | ||
| await txn('MetricVersion').insert({ | ||
| UpdatedOn: new Date().toUTCString(), | ||
| ...aQuickView, | ||
| }; | ||
| aQuickViewForDB.Filters = JSON.stringify(aQuickView.Filters); //json arrays don't map automatically. have to stringify them. | ||
| aQuickViewForDB.Sorting = JSON.stringify(aQuickView.Sorting); | ||
| return aQuickViewForDB; | ||
| }); | ||
| //UCK is a unique constraint which allows us to can do simple onConflict merge. All UCKs should be like this! | ||
| await txn('QuickViewTemplate').insert(quickViewsForDB).onConflict('UCK').merge(); | ||
| // delete any existing quickviews from the database that are no longer in the metadata. | ||
| // The whereNotNull UCK is not actually necessary because the whereNotIn already excludes nulls because anything compared to null does not match. | ||
| await txn('QuickViewTemplate') | ||
| .delete() | ||
| .where({ InspectorVersionID: INSPECTOR_DEFINITION.InspectorVersionID }) | ||
| .whereNotNull('UCK') | ||
| .whereNotIn('UCK', QUICKVIEWS.map((aQuickView) => aQuickView.UCK)); | ||
| } | ||
| }; | ||
| seedMetrics = async (txn, { INSPECTOR_DEFINITION, METRICS }) => { | ||
| if (METRICS.length) { | ||
| const metricsToUpsert = METRICS.map((aMetric) => { | ||
| const aMetricToUpsert = lodash_1.default.omit(aMetric, 'Query', 'EvaluateFor'); // omit these for now because they are not yet on the Metric table | ||
| aMetricToUpsert.InspectorID = INSPECTOR_DEFINITION.InspectorID; | ||
| aMetricToUpsert.UpdatedOn = new Date().toUTCString(); | ||
| delete aMetricToUpsert.ChangesEnabled; | ||
| return aMetricToUpsert; | ||
| }); | ||
| await txn('Metric').insert(metricsToUpsert).onConflict('UUID').merge(); // would be better if the unique constraint were on UCK and this field was removed | ||
| // get back the metric IDs so that we can save the queries on the MetricVersion table. | ||
| // Someday we should move the Query to the Metric table after we've gotten rid of versions for all inspectors and this complexity could go away. | ||
| const metricID_UCK_assoc = await txn('Metric') | ||
| .select('ID', 'UCK') | ||
| .where({ InspectorID: INSPECTOR_DEFINITION.InspectorID }) | ||
| .whereNotNull('UCK'); | ||
| for (const anAssociation of metricID_UCK_assoc) { | ||
| const aMetric = lodash_1.default.find(METRICS, (aMetric) => anAssociation.UCK === aMetric.UCK); | ||
| if (aMetric) { | ||
| // if there was a composite unique constraint on MetricID and InspectorVersionID (as it seems there should be), then we could have done an onConflict merge here instead of this: | ||
| const updateResult = await txn('MetricVersion').update({ Query: aMetric.Query }).where({ | ||
| MetricID: anAssociation.ID, | ||
| InspectorVersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Query: aMetric.Query, | ||
| }); | ||
| const serviceProviders = await txn('ServiceProvider').select('ID'); | ||
| if (serviceProviders.length) { | ||
| const configurations = serviceProviders.map((sp) => ({ | ||
| if (updateResult !== 1) { | ||
| // no row was found to update, so insert instead. | ||
| await txn('MetricVersion').insert({ | ||
| MetricID: anAssociation.ID, | ||
| ServiceProviderID: sp.ID, | ||
| Display: true, | ||
| ChangesEnabled: !!aMetric.ChangesEnabled, | ||
| })); | ||
| await txn('MetricConfiguration') | ||
| .insert(configurations) | ||
| .onConflict(['MetricID', 'ServiceProviderID']) | ||
| .merge(); | ||
| InspectorVersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Query: aMetric.Query, | ||
| }); | ||
| const serviceProviders = await txn('ServiceProvider').select('ID'); | ||
| if (serviceProviders.length) { | ||
| const configurations = serviceProviders.map((sp) => ({ | ||
| MetricID: anAssociation.ID, | ||
| ServiceProviderID: sp.ID, | ||
| Display: true, | ||
| ChangesEnabled: !!aMetric.ChangesEnabled, | ||
| })); | ||
| await txn('MetricConfiguration') | ||
| .insert(configurations) | ||
| .onConflict(['MetricID', 'ServiceProviderID']) | ||
| .merge(); | ||
| } | ||
| } | ||
@@ -207,309 +220,267 @@ } | ||
| } | ||
| } | ||
| }; | ||
| const seedRulesAndAlerts = async (txn, { INSPECTOR_DEFINITION, RULES }) => { | ||
| if (RULES.length) { | ||
| //These are the alert rules built on top of metrics | ||
| for (const aRule of RULES) { | ||
| let ruleID = 0; | ||
| const ruleForDB = { | ||
| Name: aRule.Name, | ||
| Title: aRule.Title, | ||
| Tags: aRule.Tags, | ||
| Enabled: aRule.Enabled, | ||
| Visible: aRule.Visible, | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| Body: aRule.Body, | ||
| DynamicContent: aRule.DynamicContent, | ||
| UCK: aRule.UCK, | ||
| }; | ||
| const ruleExists = await txn('Rule').select('ID').where({ UCK: aRule.UCK }); | ||
| if (ruleExists && ruleExists.length) { | ||
| ruleID = Number.parseInt(ruleExists[0].ID); | ||
| await txn('Rule').update(ruleForDB).where({ ID: ruleID }); | ||
| } | ||
| else { | ||
| ruleForDB.CreatedOn = new Date().toUTCString(); | ||
| const insertResult = await txn('Rule').returning('ID').insert(ruleForDB); //I verified that the sequence is working properly here and will return the right value | ||
| ruleID = Number.parseInt(insertResult[0].ID); | ||
| } | ||
| for (const aConditionGroup of aRule.ConditionGroups) { | ||
| let conditionGroupID = 0; | ||
| const conditionGroupForDB = { | ||
| RuleID: ruleID, | ||
| Priority: aConditionGroup.Priority, | ||
| GroupCondition: aConditionGroup.GroupCondition, | ||
| UCK: aConditionGroup.UCK, | ||
| }; | ||
| seedRulesAndAlerts = async (txn, { INSPECTOR_DEFINITION, RULES }) => { | ||
| if (RULES.length) { | ||
| //These are the alert rules built on top of metrics | ||
| for (const aRule of RULES) { | ||
| let ruleID = 0; | ||
| const ruleForDB = { | ||
| Name: aRule.Name, | ||
| Title: aRule.Title, | ||
| Tags: aRule.Tags, | ||
| Enabled: aRule.Enabled, | ||
| Visible: aRule.Visible, | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| UpdatedOn: new Date().toUTCString(), | ||
| Body: aRule.Body, | ||
| DynamicContent: aRule.DynamicContent, | ||
| UCK: aRule.UCK, | ||
| }; | ||
| const conditionGroupExists = await txn('AlertStatementGroup') | ||
| .select('ID') | ||
| .where({ UCK: aConditionGroup.UCK }); | ||
| if (conditionGroupExists && conditionGroupExists.length) { | ||
| conditionGroupID = Number.parseInt(conditionGroupExists[0].ID); | ||
| await txn('AlertStatementGroup') | ||
| .update(conditionGroupForDB) | ||
| .where({ ID: conditionGroupID }); | ||
| const ruleExists = await txn('Rule').select('ID').where({ UCK: aRule.UCK }); | ||
| if (ruleExists && ruleExists.length) { | ||
| ruleID = Number.parseInt(ruleExists[0].ID); | ||
| await txn('Rule').update(ruleForDB).where({ ID: ruleID }); | ||
| } | ||
| else { | ||
| const insertResult = await txn('AlertStatementGroup') | ||
| .returning('ID') | ||
| .insert(conditionGroupForDB); // I verified that this seq is in proper user and will return the right value | ||
| conditionGroupID = Number.parseInt(insertResult[0].ID); | ||
| ruleForDB.CreatedOn = new Date().toUTCString(); | ||
| const insertResult = await txn('Rule').returning('ID').insert(ruleForDB); //I verified that the sequence is working properly here and will return the right value | ||
| ruleID = Number.parseInt(insertResult[0].ID); | ||
| } | ||
| for (const aCondition of aConditionGroup.Conditions) { | ||
| const metricExists = await txn('Metric') | ||
| for (const aConditionGroup of aRule.ConditionGroups) { | ||
| let conditionGroupID = 0; | ||
| const conditionGroupForDB = { | ||
| RuleID: ruleID, | ||
| Priority: aConditionGroup.Priority, | ||
| GroupCondition: aConditionGroup.GroupCondition, | ||
| UCK: aConditionGroup.UCK, | ||
| }; | ||
| const conditionGroupExists = await txn('AlertStatementGroup') | ||
| .select('ID') | ||
| .where({ UCK: aCondition.MetricUCK }); | ||
| if (metricExists && metricExists.length) { | ||
| const metricID = Number.parseInt(metricExists[0].ID); | ||
| const conditionForDB = { | ||
| AlertStatementGroupID: conditionGroupID, | ||
| MetricID: metricID, | ||
| UCK: aCondition.UCK, | ||
| Operation: aCondition.Operator, | ||
| Value: aCondition.Value, | ||
| }; | ||
| const conditionExists = await txn('AlertStatement') | ||
| .where({ UCK: aConditionGroup.UCK }); | ||
| if (conditionGroupExists && conditionGroupExists.length) { | ||
| conditionGroupID = Number.parseInt(conditionGroupExists[0].ID); | ||
| await txn('AlertStatementGroup') | ||
| .update(conditionGroupForDB) | ||
| .where({ ID: conditionGroupID }); | ||
| } | ||
| else { | ||
| const insertResult = await txn('AlertStatementGroup') | ||
| .returning('ID') | ||
| .insert(conditionGroupForDB); // I verified that this seq is in proper user and will return the right value | ||
| conditionGroupID = Number.parseInt(insertResult[0].ID); | ||
| } | ||
| for (const aCondition of aConditionGroup.Conditions) { | ||
| const metricExists = await txn('Metric') | ||
| .select('ID') | ||
| .where({ UCK: aCondition.UCK }); | ||
| if (conditionExists && conditionExists.length) { | ||
| let conditionID = Number.parseInt(conditionExists[0].ID); | ||
| await txn('AlertStatement').update(conditionForDB).where({ ID: conditionID }); | ||
| .where({ UCK: aCondition.MetricUCK }); | ||
| if (metricExists && metricExists.length) { | ||
| const metricID = Number.parseInt(metricExists[0].ID); | ||
| const conditionForDB = { | ||
| AlertStatementGroupID: conditionGroupID, | ||
| MetricID: metricID, | ||
| UCK: aCondition.UCK, | ||
| Operation: aCondition.Operator, | ||
| Value: aCondition.Value, | ||
| }; | ||
| const conditionExists = await txn('AlertStatement') | ||
| .select('ID') | ||
| .where({ UCK: aCondition.UCK }); | ||
| if (conditionExists && conditionExists.length) { | ||
| let conditionID = Number.parseInt(conditionExists[0].ID); | ||
| await txn('AlertStatement').update(conditionForDB).where({ ID: conditionID }); | ||
| } | ||
| else { | ||
| await txn('AlertStatement').insert(conditionForDB); | ||
| } | ||
| } | ||
| else { | ||
| await txn('AlertStatement').insert(conditionForDB); | ||
| throw new Error(`Cannot insert AlertStatement with UCK ${aCondition.UCK} because metric with UCK ${aCondition.MetricUCK} does not exist.`); | ||
| } | ||
| } | ||
| else { | ||
| throw new Error(`Cannot insert AlertStatement with UCK ${aCondition.UCK} because metric with UCK ${aCondition.MetricUCK} does not exist.`); | ||
| } | ||
| } | ||
| } | ||
| // delete any rules for this inspectorID that are in the database but no longer in the metadata | ||
| await txn('Rule') | ||
| .delete() | ||
| .where({ InspectorID: INSPECTOR_DEFINITION.InspectorID }) | ||
| .whereNotNull('UCK') | ||
| .whereNotIn('UCK', RULES.map((aRule) => aRule.UCK)); | ||
| } | ||
| // delete any rules for this inspectorID that are in the database but no longer in the metadata | ||
| await txn('Rule') | ||
| .delete() | ||
| .where({ InspectorID: INSPECTOR_DEFINITION.InspectorID }) | ||
| .whereNotNull('UCK') | ||
| .whereNotIn('UCK', RULES.map((aRule) => aRule.UCK)); | ||
| } | ||
| }; | ||
| const seedAssetMappings = async (txn, { INSPECTOR_DEFINITION, ASSET_MAPPINGS }) => { | ||
| if (ASSET_MAPPINGS.length) { | ||
| //Verify that there is just one row in the AssetTypeMapping table for this inspector or create one if needed. | ||
| //I'm not sure this table should exist because there's just one row per inspector and no actual mapping is happening. There are null cols for service provider and env. | ||
| const AssetTypeID = inspector_metadata_1.AssetType[INSPECTOR_DEFINITION.AssetType]; | ||
| const assetTypeMappingExists = await txn('AssetTypeMapping') | ||
| .select('ID') | ||
| .where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| AssetTypeID, | ||
| }) | ||
| .whereNull('ServiceProviderID') | ||
| .whereNull('EnvironmentID'); | ||
| let assetTypeMappingID = 0; | ||
| if (assetTypeMappingExists && assetTypeMappingExists.length) { | ||
| assetTypeMappingID = assetTypeMappingExists[0].ID; | ||
| } | ||
| else { | ||
| const assetTypeMappingForDB = { | ||
| }; | ||
| seedAssetMappings = async (txn, { INSPECTOR_DEFINITION, ASSET_MAPPINGS }) => { | ||
| if (ASSET_MAPPINGS.length) { | ||
| //Verify that there is just one row in the AssetTypeMapping table for this inspector or create one if needed. | ||
| //I'm not sure this table should exist because there's just one row per inspector and no actual mapping is happening. There are null cols for service provider and env. | ||
| const AssetTypeID = inspector_metadata_1.AssetType[INSPECTOR_DEFINITION.AssetType]; | ||
| const assetTypeMappingExists = await txn('AssetTypeMapping') | ||
| .select('ID') | ||
| .where({ | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Locked: true, | ||
| AssetTypeID, | ||
| Headers: INSPECTOR_DEFINITION.ConnectwiseFlexibleAssetHeaders, | ||
| }; | ||
| const insertResult = await txn('AssetTypeMapping') | ||
| .returning('ID') | ||
| .insert(assetTypeMappingForDB); | ||
| assetTypeMappingID = insertResult[0].ID; | ||
| } | ||
| const fieldIDs = []; | ||
| for (const aQuery of ASSET_MAPPINGS) { | ||
| // lookup the field in AssetTypeField, which is reference data populated by roar-api or roar-metadata | ||
| const anAssetTypeFieldExists = await txn('AssetTypeField') | ||
| .select('ID') | ||
| .where({ AssetTypeID, Label: aQuery.Label }); | ||
| if (anAssetTypeFieldExists && anAssetTypeFieldExists.length) { | ||
| const assetTypeFieldID = anAssetTypeFieldExists[0].ID; | ||
| fieldIDs.push(assetTypeFieldID); | ||
| const aQueryExists = await txn('AssetTypeFieldMapping') | ||
| }) | ||
| .whereNull('ServiceProviderID') | ||
| .whereNull('EnvironmentID'); | ||
| let assetTypeMappingID = 0; | ||
| if (assetTypeMappingExists && assetTypeMappingExists.length) { | ||
| assetTypeMappingID = assetTypeMappingExists[0].ID; | ||
| } | ||
| else { | ||
| const assetTypeMappingForDB = { | ||
| InspectorID: INSPECTOR_DEFINITION.InspectorID, | ||
| VersionID: INSPECTOR_DEFINITION.InspectorVersionID, | ||
| Locked: true, | ||
| AssetTypeID, | ||
| Headers: INSPECTOR_DEFINITION.ConnectwiseFlexibleAssetHeaders, | ||
| }; | ||
| const insertResult = await txn('AssetTypeMapping') | ||
| .returning('ID') | ||
| .insert(assetTypeMappingForDB); | ||
| assetTypeMappingID = insertResult[0].ID; | ||
| } | ||
| const fieldIDs = []; | ||
| for (const aQuery of ASSET_MAPPINGS) { | ||
| // lookup the field in AssetTypeField, which is reference data populated by roar-api or roar-metadata | ||
| const anAssetTypeFieldExists = await txn('AssetTypeField') | ||
| .select('ID') | ||
| .where({ AssetTypeMappingID: assetTypeMappingID, AssetTypeFieldID: assetTypeFieldID }); | ||
| if (aQueryExists && aQueryExists.length) { | ||
| const assetTypeFieldMappingID = aQueryExists[0].ID; | ||
| await txn('AssetTypeFieldMapping') | ||
| .update({ JMES: aQuery.Query }) | ||
| .where({ ID: assetTypeFieldMappingID }); | ||
| .where({ AssetTypeID, Label: aQuery.Label }); | ||
| if (anAssetTypeFieldExists && anAssetTypeFieldExists.length) { | ||
| const assetTypeFieldID = anAssetTypeFieldExists[0].ID; | ||
| fieldIDs.push(assetTypeFieldID); | ||
| const aQueryExists = await txn('AssetTypeFieldMapping') | ||
| .select('ID') | ||
| .where({ AssetTypeMappingID: assetTypeMappingID, AssetTypeFieldID: assetTypeFieldID }); | ||
| if (aQueryExists && aQueryExists.length) { | ||
| const assetTypeFieldMappingID = aQueryExists[0].ID; | ||
| await txn('AssetTypeFieldMapping') | ||
| .update({ JMES: aQuery.Query }) | ||
| .where({ ID: assetTypeFieldMappingID }); | ||
| } | ||
| else { | ||
| const assetTypeFieldMapping = { | ||
| AssetTypeMappingID: assetTypeMappingID, | ||
| AssetTypeFieldID: assetTypeFieldID, | ||
| JMES: aQuery.Query, | ||
| }; | ||
| await txn('AssetTypeFieldMapping').insert(assetTypeFieldMapping); | ||
| } | ||
| } | ||
| else { | ||
| const assetTypeFieldMapping = { | ||
| AssetTypeMappingID: assetTypeMappingID, | ||
| AssetTypeFieldID: assetTypeFieldID, | ||
| JMES: aQuery.Query, | ||
| }; | ||
| await txn('AssetTypeFieldMapping').insert(assetTypeFieldMapping); | ||
| // field does not exist. | ||
| throw new Error(`No AssetTypeField row exists for AssetTypeID: ${AssetTypeID}, Label: ${aQuery.Label}`); | ||
| } | ||
| } | ||
| else { | ||
| // field does not exist. | ||
| throw new Error(`No AssetTypeField row exists for AssetTypeID: ${AssetTypeID}, Label: ${aQuery.Label}`); | ||
| } | ||
| // delete any mappings that aren't in our current set. | ||
| await txn('AssetTypeFieldMapping') | ||
| .delete() | ||
| .where({ AssetTypeMappingID: assetTypeMappingID }) | ||
| .whereNotIn('AssetTypeFieldID', fieldIDs); | ||
| } | ||
| // delete any mappings that aren't in our current set. | ||
| await txn('AssetTypeFieldMapping') | ||
| .delete() | ||
| .where({ AssetTypeMappingID: assetTypeMappingID }) | ||
| .whereNotIn('AssetTypeFieldID', fieldIDs); | ||
| } | ||
| }; | ||
| let db; | ||
| const dbConnection = async () => { | ||
| const params = {}; | ||
| const client = new client_ssm_1.SSMClient({ | ||
| region: process.env.AWS_REGION, | ||
| }); | ||
| const command = new client_ssm_1.GetParametersCommand({ | ||
| Names: ['PG_USERNAME', 'PG_PASSWORD', 'PG_DATABASE', 'PG_HOSTNAME', 'PG_PORT'], | ||
| WithDecryption: true, | ||
| }); | ||
| let response; | ||
| try { | ||
| response = await client.send(command); | ||
| } | ||
| catch (err) { | ||
| console.log(`Connecting to localhost pgSql.`); | ||
| } | ||
| if (!response?.Parameters) { | ||
| if (response?.InvalidParameters) | ||
| console.log(`Invalid or no params returned from SSM in when trying to establish DB connection. ${response.InvalidParameters}. Connecting to localhost pgSql instead.`); | ||
| let connection = { | ||
| host: 'localhost', | ||
| port: 5432, | ||
| user: 'roaradmin', | ||
| password: 'password', | ||
| database: 'roar', | ||
| }; | ||
| if (fs_1.default.existsSync('./localDatabaseCreds.json')) | ||
| connection = JSON.parse(fs_1.default.readFileSync('./localDatabaseCreds.json').toString()); // allow local override | ||
| return (0, knex_1.knex)({ | ||
| client: 'pg', | ||
| connection, | ||
| }; | ||
| validateMetadata = ({ INSPECTOR_DEFINITION, METRICS, QUICKVIEWS, RULES, ASSET_MAPPINGS, TEMPLATE, VIEWS, }) => this.validator.isDefinitionValid(INSPECTOR_DEFINITION) && | ||
| this.validator.isUIConfigTemplateValid(TEMPLATE) && | ||
| this.validator.areViewsValid(VIEWS) && | ||
| this.validator.areQuickViewsValid(QUICKVIEWS) && | ||
| this.validator.areMetricsValid(METRICS) && | ||
| this.validator.areRulesValid(RULES) && | ||
| this.validator.areAssetMappingsValid(ASSET_MAPPINGS); | ||
| seedOneInspector = async (METADATA) => { | ||
| this.validateMetadata(METADATA); | ||
| await this.dbClient.transaction(async (txn) => { | ||
| await this.seedInspectorDefinition(txn, METADATA); | ||
| await this.seedMetrics(txn, METADATA); | ||
| await this.seedRulesAndAlerts(txn, METADATA); | ||
| await this.seedAssetMappings(txn, METADATA); | ||
| }); | ||
| } | ||
| else { | ||
| console.log(`Found AWS credentials. Attempting to connect to RDS postgresql. If you're doing local development and are trying to seed your localhost pgSql, this is not what you want and you should not be doing this in a terminal instance that has AWS credentials in its env.`); | ||
| response.Parameters.forEach(function (p) { | ||
| params[p.Name] = p.Value; | ||
| }); | ||
| return (0, knex_1.knex)({ | ||
| client: 'pg', | ||
| connection: { | ||
| host: params['PG_HOSTNAME'], | ||
| user: params['PG_USERNAME'], | ||
| password: params['PG_PASSWORD'], | ||
| database: params['PG_DATABASE'], | ||
| port: params['PG_PORT'], | ||
| }, | ||
| }); | ||
| } | ||
| }; | ||
| const validateMetadata = ({ INSPECTOR_DEFINITION, METRICS, QUICKVIEWS, RULES, ASSET_MAPPINGS, TEMPLATE, VIEWS, }) => validator.isDefinitionValid(INSPECTOR_DEFINITION) && | ||
| validator.isUIConfigTemplateValid(TEMPLATE) && | ||
| validator.areViewsValid(VIEWS) && | ||
| validator.areQuickViewsValid(QUICKVIEWS) && | ||
| validator.areMetricsValid(METRICS) && | ||
| validator.areRulesValid(RULES) && | ||
| validator.areAssetMappingsValid(ASSET_MAPPINGS); | ||
| const seedOneInspector = async (METADATA) => { | ||
| validateMetadata(METADATA); | ||
| await db.transaction(async (txn) => { | ||
| await (0, exports.seedInspectorDefinition)(txn, METADATA); | ||
| await seedMetrics(txn, METADATA); | ||
| await seedRulesAndAlerts(txn, METADATA); | ||
| await seedAssetMappings(txn, METADATA); | ||
| }); | ||
| }; | ||
| const loadAndValidateMetadataFromPath = async (PATH, INSPECTOR_VERSION, CHECKSUM, POLICY_INTEGRITY) => { | ||
| let metadataPath = PATH; | ||
| if (!metadataPath.endsWith('/')) | ||
| metadataPath += '/'; | ||
| if (!metadataPath.endsWith('metadata')) | ||
| metadataPath += 'metadata'; | ||
| if (!fs_1.default.existsSync(metadataPath)) { | ||
| console.log(`${metadataPath} does not exist. Skipping.`); | ||
| return false; | ||
| } | ||
| if (!CHECKSUM) { | ||
| // in the local dev use case, make sure we have the latest code. deployment does this by itself. | ||
| const gitResult = await (0, spawnChildProcess_1.spawnChildProcess)('git', ['pull'], PATH); | ||
| if (gitResult.exitCode !== 0) | ||
| console.log(`Git pull failed for ${PATH}.`); | ||
| } | ||
| const loadMetadataJSON = (metadataFilename) => JSON.parse(fs_1.default.readFileSync(`${metadataPath}/${metadataFilename}.json`).toString()); | ||
| try { | ||
| const INSPECTOR_DEFINITION = { | ||
| Version: INSPECTOR_VERSION || '', | ||
| Checksum: CHECKSUM || '', | ||
| PolicyIntegrity: POLICY_INTEGRITY || '', | ||
| ...loadMetadataJSON(`definition`), | ||
| }; | ||
| if (!INSPECTOR_DEFINITION.Published) { | ||
| console.log(`${INSPECTOR_DEFINITION.Name} is set to Published = false. Skipping.`); | ||
| return true; | ||
| }; | ||
| loadAndValidateMetadataFromPath = async (PATH, INSPECTOR_VERSION, CHECKSUM, POLICY_INTEGRITY) => { | ||
| let metadataPath = PATH; | ||
| if (!metadataPath.endsWith('/')) | ||
| metadataPath += '/'; | ||
| if (!metadataPath.endsWith('metadata')) | ||
| metadataPath += 'metadata'; | ||
| if (!fs_1.default.existsSync(metadataPath)) { | ||
| console.log(`${metadataPath} does not exist. Skipping.`); | ||
| return false; | ||
| } | ||
| const TEMPLATE = loadMetadataJSON(`uiConfigTemplate`); | ||
| const VIEWS = loadMetadataJSON(`views`); | ||
| const QUICKVIEWS = loadMetadataJSON(`quickViews`); | ||
| const METRICS = loadMetadataJSON(`metrics`); | ||
| const RULES = loadMetadataJSON(`rules`); | ||
| const ASSET_MAPPINGS = loadMetadataJSON(`assetMappings`); | ||
| await seedOneInspector({ | ||
| INSPECTOR_DEFINITION, | ||
| TEMPLATE, | ||
| VIEWS, | ||
| QUICKVIEWS, | ||
| METRICS, | ||
| RULES, | ||
| ASSET_MAPPINGS, | ||
| }); | ||
| console.log(`Finished metadata seed of ${INSPECTOR_DEFINITION.Name}`); | ||
| return true; | ||
| } | ||
| catch (error) { | ||
| console.error(`Error seeding from ${metadataPath}. ${error.message}`); | ||
| return false; | ||
| } | ||
| }; | ||
| const seed = async (path, branch, recurse) => { | ||
| db = await dbConnection(); | ||
| if (recurse) { | ||
| // for the local development use case, seed all metadata from inspectors found under the given path. | ||
| await Promise.all(fs_1.default | ||
| .readdirSync(path, { withFileTypes: true }) | ||
| .filter((dirent) => dirent.isDirectory()) | ||
| .map((anInspectorDirEntry) => loadAndValidateMetadataFromPath(path + '/' + anInspectorDirEntry.name))); | ||
| process.exit(0); | ||
| } | ||
| else { | ||
| if (!CHECKSUM) { | ||
| // in the local dev use case, make sure we have the latest code. deployment does this by itself. | ||
| const gitResult = await (0, spawnChildProcess_1.spawnChildProcess)('git', ['pull'], PATH); | ||
| if (gitResult.exitCode !== 0) | ||
| console.log(`Git pull failed for ${PATH}.`); | ||
| } | ||
| const loadMetadataJSON = (metadataFilename) => JSON.parse(fs_1.default.readFileSync(`${metadataPath}/${metadataFilename}.json`).toString()); | ||
| try { | ||
| const name = (0, prepareBundleForDeployment_1.getInspectorNameFromMetadataDefinition)(); | ||
| const { Version: version, Checksum: checksum, PolicyIntegrity: policyIntegrity, } = await (0, prepareBundleForDeployment_1.fetchBundleMetadataFromS3)({ | ||
| name, | ||
| branch, | ||
| location: 'application-server', | ||
| const INSPECTOR_DEFINITION = { | ||
| Version: INSPECTOR_VERSION || '', | ||
| Checksum: CHECKSUM || '', | ||
| PolicyIntegrity: POLICY_INTEGRITY || '', | ||
| ...loadMetadataJSON(`definition`), | ||
| }; | ||
| if (!INSPECTOR_DEFINITION.Published) { | ||
| console.log(`${INSPECTOR_DEFINITION.Name} is set to Published = false. Skipping.`); | ||
| return true; | ||
| } | ||
| const TEMPLATE = loadMetadataJSON(`uiConfigTemplate`); | ||
| const VIEWS = loadMetadataJSON(`views`); | ||
| const QUICKVIEWS = loadMetadataJSON(`quickViews`); | ||
| const METRICS = loadMetadataJSON(`metrics`); | ||
| const RULES = loadMetadataJSON(`rules`); | ||
| const ASSET_MAPPINGS = loadMetadataJSON(`assetMappings`); | ||
| await this.seedOneInspector({ | ||
| INSPECTOR_DEFINITION, | ||
| TEMPLATE, | ||
| VIEWS, | ||
| QUICKVIEWS, | ||
| METRICS, | ||
| RULES, | ||
| ASSET_MAPPINGS, | ||
| }); | ||
| const success = await loadAndValidateMetadataFromPath(path, version, checksum, policyIntegrity); | ||
| if (!success) { | ||
| process.exit(1); // let the deployment caller know there was problem. | ||
| } | ||
| console.log(`Finished metadata seed of ${INSPECTOR_DEFINITION.Name}`); | ||
| return true; | ||
| } | ||
| catch (error) { | ||
| console.error(`Error seeding from ${path} on '${branch}' branch. ${error?.stack}`); | ||
| process.exit(1); | ||
| console.error(`Error seeding from ${metadataPath}. ${error.message}`); | ||
| return false; | ||
| } | ||
| } | ||
| }; | ||
| exports.seed = seed; | ||
| }; | ||
| seed = async (path, branch, recurse) => { | ||
| if (recurse) { | ||
| // for the local development use case, seed all metadata from inspectors found under the given path. | ||
| await Promise.all(fs_1.default | ||
| .readdirSync(path, { withFileTypes: true }) | ||
| .filter((dirent) => dirent.isDirectory()) | ||
| .map((anInspectorDirEntry) => this.loadAndValidateMetadataFromPath(path + '/' + anInspectorDirEntry.name))); | ||
| process.exit(0); | ||
| } | ||
| else { | ||
| try { | ||
| const repositoryName = path.split('/').pop(); | ||
| const originalPath = process.cwd(); | ||
| process.chdir(path); | ||
| const { Version: version, Checksum: checksum, PolicyIntegrity: policyIntegrity, } = await (0, prepareBundleForDeployment_1.fetchBundleMetadataFromS3)({ | ||
| repositoryName: repositoryName === 'windows-inspector' | ||
| ? 'windows-workstation-inspector' | ||
| : repositoryName, | ||
| branch, | ||
| executionEnvironment: 'application-server', | ||
| }); | ||
| const success = await this.loadAndValidateMetadataFromPath(path, version, checksum, policyIntegrity); | ||
| if (!success) { | ||
| process.exit(1); | ||
| } | ||
| process.chdir(originalPath); | ||
| } | ||
| catch (error) { | ||
| if ((0, prepareBundleForDeployment_1.isMissingBundleMetadataError)(error)) { | ||
| this.missingBundleMetadataInS3.push(`Error seeding from ${path} on '${branch}' branch. ${error?.stack}`); | ||
| } | ||
| else { | ||
| console.error(`Error seeding from ${path} on '${branch}' branch. ${error?.stack}`); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| exports.MetadataSeeder = MetadataSeeder; |
@@ -6,3 +6,3 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.downloadInspectorRepos = exports.setup = void 0; | ||
| exports.cloneInspectorRepo = exports.setup = void 0; | ||
| const fs_1 = __importDefault(require("fs")); | ||
@@ -19,8 +19,8 @@ const yaml_1 = __importDefault(require("yaml")); | ||
| console.log('Cloning inspector repos'); | ||
| return await Promise.all([...insp.inspector_repos, ...insp.inspector_repos_built_by_pipeline].map((anInspectorRepo) => { | ||
| return (0, exports.downloadInspectorRepos)(anInspectorRepo, branch); | ||
| return await Promise.all(insp.inspector_repos.map((anInspectorRepo) => { | ||
| return (0, exports.cloneInspectorRepo)(anInspectorRepo, branch); | ||
| })); | ||
| }; | ||
| exports.setup = setup; | ||
| const downloadInspectorRepos = async (repoName, branch = DEFAULT_BRANCH) => { | ||
| const cloneInspectorRepo = async (repoName, branch = DEFAULT_BRANCH) => { | ||
| try { | ||
@@ -45,2 +45,2 @@ console.log(`Cloning ${repoName} ${branch}`); | ||
| }; | ||
| exports.downloadInspectorRepos = downloadInspectorRepos; | ||
| exports.cloneInspectorRepo = cloneInspectorRepo; |
+3
-1
| { | ||
| "name": "@liongard/inspector-dev-tools", | ||
| "version": "5.0.3", | ||
| "version": "5.0.4", | ||
| "description": "A command line utility to assist in Liongard inspector development", | ||
@@ -29,2 +29,3 @@ "scripts": { | ||
| "@types/prettier": "^2.0.0", | ||
| "@types/tmp": "^0.2.6", | ||
| "@types/uuid": "^10.0.0", | ||
@@ -61,2 +62,3 @@ "@types/xml2js": "^0.4.14", | ||
| "terser": "^5.33.0", | ||
| "tmp": "^0.2.3", | ||
| "typescript": "^5.6.2", | ||
@@ -63,0 +65,0 @@ "uuid": "^10.0.0", |
Sorry, the diff of this file is not supported yet
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
116337
10.18%40
8.11%2342
7.98%26
4%15
7.14%24
9.09%5
25%+ Added
+ Added