From d09c9d52d9f8547d33edebfef4b5f697cd13cbcf Mon Sep 17 00:00:00 2001 From: Dennis Seah Date: Mon, 30 Mar 2020 16:54:52 -0700 Subject: [PATCH 1/2] [HOUSEKEEPING] resolve no-use-before-define eslint in generate infra cmd --- src/commands/infra/generate.ts | 473 ++++++++++++++++----------------- 1 file changed, 236 insertions(+), 237 deletions(-) diff --git a/src/commands/infra/generate.ts b/src/commands/infra/generate.ts index f1939310c..4dcf1c1f0 100644 --- a/src/commands/infra/generate.ts +++ b/src/commands/infra/generate.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ import commander from "commander"; import fs from "fs"; import fsExtra from "fs-extra"; @@ -40,51 +39,70 @@ export enum DefinitionYAMLExistence { PARENT_ONLY, } -export const execute = async ( - opts: CommandOptions, - exitFn: (status: number) => Promise +/** + * Creates "generated" directory if it does not already exists + * + * @param projectPath path to the project directory + */ +export const createGenerated = (projectPath: string): void => { + mkdirp.sync(projectPath); + logger.info(`Created generated directory: ${projectPath}`); +}; + +export const checkRemoteGitExist = async ( + sourcePath: string, + source: string, + safeLoggingUrl: string ): Promise => { - const parentPath = process.cwd(); - // if the "--project" argument is not specified, then it is assumed - // that the current working directory is the project path. - const projectPath = opts.project || parentPath; - const outputPath = opts.output || ""; - try { - const definitionConfig = validateDefinition(parentPath, projectPath); - const sourceConfig = validateTemplateSources( - definitionConfig, - parentPath, - projectPath - ); - // validateTemplateSources makes sure that - // sourceConfig has values for source, template and version - await validateRemoteSource(sourceConfig); - await generateConfig( - parentPath, - projectPath, - definitionConfig, - sourceConfig, - outputPath - ); - await exitFn(0); - } catch (err) { - logger.error("Error occurred while generating project deployment files"); - logger.error(err); - await exitFn(1); + // Checking for git remote + if (!fs.existsSync(sourcePath)) { + throw buildError(errorStatusCode.GIT_OPS_ERR, { + errorKey: "infra-git-source-no-exist", + values: [sourcePath], + }); + } + + const result = await simpleGit(sourcePath).listRemote([source]); + if (!result) { + logger.error(result); + throw buildError(errorStatusCode.GIT_OPS_ERR, "infra-err-git-clone-failed"); } + + logger.info(`Remote source repo: ${safeLoggingUrl} exists.`); +}; + +export const gitFetchPull = async ( + sourcePath: string, + safeLoggingUrl: string +): Promise => { + // Make sure we have the latest version of all releases cached locally + await simpleGit(sourcePath).fetch("all"); + await simpleGit(sourcePath).pull("origin", "master"); + logger.info(`${safeLoggingUrl} already cloned. Performing 'git pull'...`); +}; + +export const gitCheckout = async ( + sourcePath: string, + version: string +): Promise => { + // Checkout tagged version + logger.info(`Checking out template version: ${version}`); + await simpleGit(sourcePath).checkout(version); }; /** - * Adds the init command to the commander command object + * Performs a 'git clone...' * - * @param command Commander command object to decorate + * @param source git url to clone + * @param sourcePath location to clone repo to */ -export const commandDecorator = (command: commander.Command): void => { - buildCmd(command, decorator).action(async (opts: CommandOptions) => { - await execute(opts, async (status: number) => { - await exitCmd(logger, process.exit, status); - }); - }); +export const gitClone = async ( + git: simpleGit.SimpleGit, + source: string, + sourcePath: string +): Promise => { + await git.clone(source, `${sourcePath}`); + logger.info(`Cloning source repo to .spk/templates was successful.`); }; /** @@ -182,45 +200,30 @@ Template: ${source.template} source: ${source.source} version: ${source.version} ); }; -export const checkRemoteGitExist = async ( - sourcePath: string, - source: string, - safeLoggingUrl: string -): Promise => { - // Checking for git remote - if (!fs.existsSync(sourcePath)) { - throw buildError(errorStatusCode.GIT_OPS_ERR, { - errorKey: "infra-git-source-no-exist", - values: [sourcePath], - }); - } - - const result = await simpleGit(sourcePath).listRemote([source]); - if (!result) { - logger.error(result); - throw buildError(errorStatusCode.GIT_OPS_ERR, "infra-err-git-clone-failed"); - } - - logger.info(`Remote source repo: ${safeLoggingUrl} exists.`); -}; - -export const gitFetchPull = async ( - sourcePath: string, - safeLoggingUrl: string -): Promise => { - // Make sure we have the latest version of all releases cached locally - await simpleGit(sourcePath).fetch("all"); - await simpleGit(sourcePath).pull("origin", "master"); - logger.info(`${safeLoggingUrl} already cloned. Performing 'git pull'...`); -}; +/** + * Creates "generated" directory if it does not already exists + * + * @param source remote URL for cloning to cache + * @param sourcePath Path to the template folder cache + * @param safeLoggingUrl URL with redacted authentication + * @param version version of terraform template + */ -export const gitCheckout = async ( +export const retryRemoteValidate = async ( + source: string, sourcePath: string, + safeLoggingUrl: string, version: string ): Promise => { - // Checkout tagged version + // SPK can assume that there is a remote that it has access to since it was able to compare commit histories. Delete cache and reset on provided remote + fsExtra.removeSync(sourcePath); + createGenerated(sourcePath); + const git = simpleGit(); + await gitClone(git, source, sourcePath); + await gitFetchPull(sourcePath, safeLoggingUrl); logger.info(`Checking out template version: ${version}`); - await simpleGit(sourcePath).checkout(version); + await gitCheckout(sourcePath, version); + logger.info(`Successfully re-cloned repo`); }; /** @@ -315,21 +318,6 @@ export const validateRemoteSource = async ( } }; -/** - * Performs a 'git clone...' - * - * @param source git url to clone - * @param sourcePath location to clone repo to - */ -export const gitClone = async ( - git: simpleGit.SimpleGit, - source: string, - sourcePath: string -): Promise => { - await git.clone(source, `${sourcePath}`); - logger.info(`Cloning source repo to .spk/templates was successful.`); -}; - export const getParentGeneratedFolder = ( parentPath: string, outputPath: string @@ -341,6 +329,59 @@ export const getParentGeneratedFolder = ( return parentPath + "-generated"; }; +/** + * Returns an array of formatted string for a given definition object. + * e.g. + * { + * keyA: "Value1", + * keyB: "\"Value2" + * } + * results in ["keyA = "Value1"", "keyB = "\"Value2""] + * + * @param definition a dictionary of key, value + */ +export const generateTfvars = ( + definition: { [key: string]: string } | undefined +): string[] => { + if (!definition) { + return []; + } + return Object.keys(definition).map((k) => `${k} = "${definition[k]}"`); +}; + +/** + * Checks if an spk.tfvars already exists + * + * @param generatedPath Path to the spk.tfvars file + * @param tfvarsFilename Name of .tfvars file + */ +export const checkTfvars = ( + generatedPath: string, + tfvarsFilename: string +): void => { + // Remove existing spk.tfvars if it already exists + if (fs.existsSync(path.join(generatedPath, tfvarsFilename))) { + fs.unlinkSync(path.join(generatedPath, tfvarsFilename)); + } +}; + +/** + * Reads in a tfVars object and returns a spk.tfvars file + * + * @param spkTfVars spk tfvars object in an array + * @param generatedPath Path to write the spk.tfvars file to + * @param tfvarsFileName Name of the .tfvaras file + */ +export const writeTfvarsFile = ( + spkTfVars: string[], + generatedPath: string, + tfvarsFilename: string +): void => { + spkTfVars.forEach((tfvar) => { + fs.appendFileSync(path.join(generatedPath, tfvarsFilename), tfvar + "\n"); + }); +}; + export const generateConfigWithParentEqProjectPath = async ( parentDirectory: string, templatePath: string, @@ -366,6 +407,76 @@ export const generateConfigWithParentEqProjectPath = async ( } }; +/** + * Replaces values from leaf definition in parent definition + * + * @param parentObject parent definition object + * @param leafObject leaf definition object + */ +export const dirIteration = ( + parentObject: { [key: string]: string } | undefined, + leafObject: { [key: string]: string } | undefined +): { [key: string]: string } => { + if (!parentObject) { + return !leafObject ? {} : deepClone(leafObject); + } + if (!leafObject) { + return parentObject; + } + + // parent take leaf's value + Object.keys(leafObject).forEach((k) => { + if (leafObject[k]) { + parentObject[k] = leafObject[k]; + } + }); + + return parentObject; +}; + +const combineVariable = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + parentVars: { [key: string]: any }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + leafVars: { [key: string]: any }, + childDirectory: string, + fileName: string +): void => { + const merged = dirIteration(parentVars, leafVars); + // Generate Terraform files in generated directory + const combined = generateTfvars(merged); + // Write variables to `spk.tfvars` file + checkTfvars(childDirectory, fileName); + writeTfvarsFile(combined, childDirectory, fileName); +}; + +/** + * Creates "generated" directory with generated Terraform files + * + * @param infraConfig definition object + * @param parentDirectory Path to the parent definition.yaml file + * @param childDirectory Path to the leaf definition.yaml file + * @param templatePath Path to the versioned Terraform template + */ +export const singleDefinitionGeneration = async ( + infraConfig: InfraConfigYaml, + parentDirectory: string, + childDirectory: string, + templatePath: string +): Promise => { + createGenerated(parentDirectory); + createGenerated(childDirectory); + const spkTfvarsObject = generateTfvars(infraConfig.variables); + checkTfvars(childDirectory, SPK_TFVARS); + writeTfvarsFile(spkTfvarsObject, childDirectory, SPK_TFVARS); + if (infraConfig.backend) { + const backendTfvarsObject = generateTfvars(infraConfig.backend); + checkTfvars(childDirectory, BACKEND_TFVARS); + writeTfvarsFile(backendTfvarsObject, childDirectory, BACKEND_TFVARS); + } + await copyTfTemplate(templatePath, childDirectory, true); +}; + /** * Creates "generated" directory if it does not already exists * @@ -443,161 +554,49 @@ export const generateConfig = async ( } }; -const combineVariable = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parentVars: { [key: string]: any }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - leafVars: { [key: string]: any }, - childDirectory: string, - fileName: string -): void => { - const merged = dirIteration(parentVars, leafVars); - // Generate Terraform files in generated directory - const combined = generateTfvars(merged); - // Write variables to `spk.tfvars` file - checkTfvars(childDirectory, fileName); - writeTfvarsFile(combined, childDirectory, fileName); -}; - -/** - * Creates "generated" directory with generated Terraform files - * - * @param infraConfig definition object - * @param parentDirectory Path to the parent definition.yaml file - * @param childDirectory Path to the leaf definition.yaml file - * @param templatePath Path to the versioned Terraform template - */ -export const singleDefinitionGeneration = async ( - infraConfig: InfraConfigYaml, - parentDirectory: string, - childDirectory: string, - templatePath: string -): Promise => { - createGenerated(parentDirectory); - createGenerated(childDirectory); - const spkTfvarsObject = generateTfvars(infraConfig.variables); - checkTfvars(childDirectory, SPK_TFVARS); - writeTfvarsFile(spkTfvarsObject, childDirectory, SPK_TFVARS); - if (infraConfig.backend) { - const backendTfvarsObject = generateTfvars(infraConfig.backend); - checkTfvars(childDirectory, BACKEND_TFVARS); - writeTfvarsFile(backendTfvarsObject, childDirectory, BACKEND_TFVARS); - } - await copyTfTemplate(templatePath, childDirectory, true); -}; - -/** - * Replaces values from leaf definition in parent definition - * - * @param parentObject parent definition object - * @param leafObject leaf definition object - */ -export const dirIteration = ( - parentObject: { [key: string]: string } | undefined, - leafObject: { [key: string]: string } | undefined -): { [key: string]: string } => { - if (!parentObject) { - return !leafObject ? {} : deepClone(leafObject); - } - if (!leafObject) { - return parentObject; - } - - // parent take leaf's value - Object.keys(leafObject).forEach((k) => { - if (leafObject[k]) { - parentObject[k] = leafObject[k]; - } - }); - - return parentObject; -}; - -/** - * Creates "generated" directory if it does not already exists - * - * @param projectPath path to the project directory - */ -export const createGenerated = (projectPath: string): void => { - mkdirp.sync(projectPath); - logger.info(`Created generated directory: ${projectPath}`); -}; - -/** - * Creates "generated" directory if it does not already exists - * - * @param source remote URL for cloning to cache - * @param sourcePath Path to the template folder cache - * @param safeLoggingUrl URL with redacted authentication - * @param version version of terraform template - */ - -export const retryRemoteValidate = async ( - source: string, - sourcePath: string, - safeLoggingUrl: string, - version: string +export const execute = async ( + opts: CommandOptions, + exitFn: (status: number) => Promise ): Promise => { - // SPK can assume that there is a remote that it has access to since it was able to compare commit histories. Delete cache and reset on provided remote - fsExtra.removeSync(sourcePath); - createGenerated(sourcePath); - const git = simpleGit(); - await gitClone(git, source, sourcePath); - await gitFetchPull(sourcePath, safeLoggingUrl); - logger.info(`Checking out template version: ${version}`); - await gitCheckout(sourcePath, version); - logger.info(`Successfully re-cloned repo`); -}; - -/** - * Checks if an spk.tfvars already exists - * - * @param generatedPath Path to the spk.tfvars file - * @param tfvarsFilename Name of .tfvars file - */ -export const checkTfvars = ( - generatedPath: string, - tfvarsFilename: string -): void => { - // Remove existing spk.tfvars if it already exists - if (fs.existsSync(path.join(generatedPath, tfvarsFilename))) { - fs.unlinkSync(path.join(generatedPath, tfvarsFilename)); - } -}; - -/** - * Returns an array of formatted string for a given definition object. - * e.g. - * { - * keyA: "Value1", - * keyB: "\"Value2" - * } - * results in ["keyA = "Value1"", "keyB = "\"Value2""] - * - * @param definition a dictionary of key, value - */ -export const generateTfvars = ( - definition: { [key: string]: string } | undefined -): string[] => { - if (!definition) { - return []; + const parentPath = process.cwd(); + // if the "--project" argument is not specified, then it is assumed + // that the current working directory is the project path. + const projectPath = opts.project || parentPath; + const outputPath = opts.output || ""; + try { + const definitionConfig = validateDefinition(parentPath, projectPath); + const sourceConfig = validateTemplateSources( + definitionConfig, + parentPath, + projectPath + ); + // validateTemplateSources makes sure that + // sourceConfig has values for source, template and version + await validateRemoteSource(sourceConfig); + await generateConfig( + parentPath, + projectPath, + definitionConfig, + sourceConfig, + outputPath + ); + await exitFn(0); + } catch (err) { + logger.error("Error occurred while generating project deployment files"); + logger.error(err); + await exitFn(1); } - return Object.keys(definition).map((k) => `${k} = "${definition[k]}"`); }; /** - * Reads in a tfVars object and returns a spk.tfvars file + * Adds the init command to the commander command object * - * @param spkTfVars spk tfvars object in an array - * @param generatedPath Path to write the spk.tfvars file to - * @param tfvarsFileName Name of the .tfvaras file + * @param command Commander command object to decorate */ -export const writeTfvarsFile = ( - spkTfVars: string[], - generatedPath: string, - tfvarsFilename: string -): void => { - spkTfVars.forEach((tfvar) => { - fs.appendFileSync(path.join(generatedPath, tfvarsFilename), tfvar + "\n"); +export const commandDecorator = (command: commander.Command): void => { + buildCmd(command, decorator).action(async (opts: CommandOptions) => { + await execute(opts, async (status: number) => { + await exitCmd(logger, process.exit, status); + }); }); }; From aa2dd6c7a378f4e8e892a9244c521320ca20ca62 Mon Sep 17 00:00:00 2001 From: Dennis Seah Date: Mon, 30 Mar 2020 17:09:04 -0700 Subject: [PATCH 2/2] Merge branch 'master' into eslintInfraGenerate