Skip to content
This repository has been archived by the owner on Apr 13, 2020. It is now read-only.

Added support for remote backend in spk infra generate #159

Merged
merged 8 commits into from
Dec 4, 2019
15 changes: 13 additions & 2 deletions src/commands/infra/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
logger
} from "../../logger";
import {
generateSpkTfvars,
generateTfvars,
parseDefinitionJson,
readDefinitionJson,
validateDefinition,
Expand Down Expand Up @@ -85,7 +85,18 @@ describe("Validate spk.tfvars file", () => {
test("Validating that a spk.tfvars is generated and has appropriate format", async () => {
const mockProjectPath = "src/commands/infra/mocks";
const definitionJSON = await readDefinitionJson(mockProjectPath);
const spkTfvarsObject = await generateSpkTfvars(definitionJSON);
const spkTfvarsObject = await generateTfvars(definitionJSON.variables);
expect(spkTfvarsObject).toContain('gitops_poll_interval = "5m"');
});
});

describe("Validate backend.tfvars file", () => {
test("Validating that a backend.tfvars is generated and has appropriate format", async () => {
const mockProjectPath = "src/commands/infra/mocks";
const definitionJSON = await readDefinitionJson(mockProjectPath);
const baclendTfvarsObject = await generateTfvars(definitionJSON.backend);
expect(baclendTfvarsObject).toContain(
'storage_account_name = "storage-account-name"'
);
});
});
229 changes: 159 additions & 70 deletions src/commands/infra/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import mkdirp from "mkdirp";
import * as os from "os";
import path from "path";
import simpleGit from "simple-git/promise";
import {
getCurrentBranch,
getOriginUrl,
safeGitUrlForLogging
} from "../../lib/gitutils";
import { safeGitUrlForLogging } from "../../lib/gitutils";
import { logger } from "../../logger";
import * as infraCommon from "./infra_common";
import { copyTfTemplate } from "./scaffold";
Expand Down Expand Up @@ -191,75 +187,151 @@ export const generateConfig = async (projectPath: string): Promise<void> => {
// First, search for definition.json in current working directory
const templatePath = await parseDefinitionJson(projectPath);
const cwdPath = process.cwd();
if (fs.existsSync(path.join(cwdPath, "definition.json"))) {
// If there exists a definition.json, then read file
logger.info(`A definition.json was found in the parent directory.`);
const parentDefinitionJSON = await readDefinitionJson(cwdPath);
const leafDefinitionJSON = await readDefinitionJson(projectPath);
/* Iterate through parent and leaf JSON objects to find matches
If there is a match, then replace parent key-value
If there is no match between the parent and leaf,
then append leaf key-value parent key-value JSON */
for (const parentKey in parentDefinitionJSON.variables) {
if (parentKey) {
for (const leafKey in leafDefinitionJSON.variables) {
if (parentKey === leafKey) {
let parentVal = parentDefinitionJSON.variables[parentKey];
parentVal = leafDefinitionJSON.variables[leafKey];
} else {
// Append to parent variables block
const leafVal = leafDefinitionJSON.variables[leafKey];
parentDefinitionJSON.variables[leafKey] = leafVal;
}
}
}

// If there exists a definition.json, then read file
const parentDefinitionJSON = await readDefinitionJson(cwdPath);
const leafDefinitionJSON = await readDefinitionJson(projectPath);
/* Iterate through parent and leaf JSON objects to find matches
If there is a match, then replace parent key-value
If there is no match between the parent and leaf,
then append leaf key-value parent key-value JSON */
// Create a generated parent directory
const parentDirectory = cwdPath + "-generated";
const childDirectory = path.join(parentDirectory, projectPath);
if (projectPath === cwdPath) {
await createGenerated(parentDirectory);
if (parentDefinitionJSON.variables) {
const spkTfvarsObject = await generateTfvars(
parentDefinitionJSON.variables
);
await checkTfvars(parentDirectory, "spk.tfvars");
await writeTfvarsFile(spkTfvarsObject, parentDirectory, "spk.tfvars");
await copyTfTemplate(templatePath, parentDirectory, true);
} else {
logger.warning(`Variables are not defined in the definition.json`);
}
if (parentDefinitionJSON.backend) {
const backendTfvarsObject = await generateTfvars(
parentDefinitionJSON.backend
);
await checkTfvars(childDirectory, "backend.tfvars");
await writeTfvarsFile(
backendTfvarsObject,
parentDirectory,
"backend.tfvars"
);
} else {
logger.warning(
`A remote backend configuration is not defined in the definition.json`
);
}
// Create a generated parent directory
const parentDirectory = await createGenerated(cwdPath + "-generated");
} else {
await createGenerated(parentDirectory);
// Then, create generated child directory
const childDirectory = await createGenerated(
path.join(parentDirectory, projectPath)
);
// Generate Terraform files in generated directory
const spkTfvarsObject = await generateSpkTfvars(
parentDefinitionJSON.variables
);
await checkSpkTfvars(childDirectory);
await writeToSpkTfvarsFile(spkTfvarsObject, childDirectory);
// const templatePath = await parseDefinitionJson(projectPath);
await copyTfTemplate(templatePath, childDirectory);
await createGenerated(childDirectory);
}
if (typeof parentDefinitionJSON !== "undefined") {
if (leafDefinitionJSON) {
const finalDefinition = await dirIteration(
parentDefinitionJSON.variables,
leafDefinitionJSON.variables
);
// Generate Terraform files in generated directory
const combinedSpkTfvarsObject = await generateTfvars(finalDefinition);
// Write variables to `spk.tfvars` file
await checkTfvars(childDirectory, "spk.tfvars");
await writeTfvarsFile(
combinedSpkTfvarsObject,
childDirectory,
"spk.tfvars"
);

// Create a backend.tfvars for remote backend configuration
if (parentDefinitionJSON.backend && leafDefinitionJSON.backend) {
const finalBackendDefinition = await dirIteration(
parentDefinitionJSON.backend,
leafDefinitionJSON.backend
);
const backendTfvarsObject = await generateTfvars(
finalBackendDefinition
);
await checkTfvars(childDirectory, "backend.tfvars");
await writeTfvarsFile(
backendTfvarsObject,
childDirectory,
"backend.tfvars"
);
}
}
} else {
// If there is not a definition.json in current working directory,
// then proceed with reading definition.json in project path
// await createGenerated(projectPath)
// logger.info(`A definition.json was not found in the parent directory.`)
const definitionJSON = await readDefinitionJson(projectPath);
// Create a generated directory
const generatedDirectory = await createGenerated(
projectPath + "-generated"
);
// Generate Terraform files in generated directory
const spkTfvarsObject = await generateSpkTfvars(definitionJSON.variables);
await checkSpkTfvars(generatedDirectory);
await writeToSpkTfvarsFile(spkTfvarsObject, generatedDirectory);
await copyTfTemplate(templatePath, generatedDirectory);
if (leafDefinitionJSON.variables) {
// If there is not a variables block in the parent or root definition.json
// Then assume the variables are taken from leaf definitions
const spkTfvarsObject = await generateTfvars(
leafDefinitionJSON.variables
);
// Write variables to `spk.tfvars` file
await checkTfvars(childDirectory, "spk.tfvars");
await writeTfvarsFile(spkTfvarsObject, childDirectory, "spk.tfvars");
await copyTfTemplate(templatePath, childDirectory, true);
} else {
logger.warning(`Variables are not defined in the definition.json`);
}
// If there is no backend block specified in the parent definition.json,
// then create a backend based on the leaf definition.json
if (leafDefinitionJSON.backend) {
const backendTfvarsObject = await generateTfvars(
leafDefinitionJSON.backend
);
await checkTfvars(childDirectory, "backend.tfvars");
await writeTfvarsFile(
backendTfvarsObject,
childDirectory,
"backend.tfvars"
);
} else {
logger.warning(
`A remote backend configuration is not defined in the definition.json`
);
}
}
await copyTfTemplate(templatePath, childDirectory, true);
} catch (err) {
return err;
}
};

export const dirIteration = async (
parentObject: string[],
leafObject: string[]
): Promise<string[]> => {
for (const parentKey in parentObject) {
if (parentKey) {
for (const leafKey in leafObject) {
if (parentKey === leafKey) {
let parentVal = parentObject[parentKey];
parentVal = leafObject[leafKey];
} else {
// Append to parent variables block
const leafVal = leafObject[leafKey];
parentObject[leafKey] = leafVal;
}
}
}
}
return parentObject;
};

/**
* Creates "generated" directory if it does not already exists
*
* @param projectPath Path to the definition.json file
*/
export const createGenerated = async (projectPath: string): Promise<string> => {
try {
const newGeneratedPath = projectPath;
mkdirp.sync(newGeneratedPath);
logger.info(`Created generated directory: ${newGeneratedPath}`);
return newGeneratedPath;
mkdirp.sync(projectPath);
logger.info(`Created generated directory: ${projectPath}`);
return projectPath;
} catch (err) {
logger.error(`There was a problem creating the generated directory`);
return err;
Expand All @@ -285,21 +357,33 @@ export const parseDefinitionJson = async (projectPath: string) => {
};

/**
* Checks if an spk.tfvars
* Checks if an spk.tfvars already exists
*
* @param projectPath Path to the spk.tfvars file
*/
export const checkSpkTfvars = async (generatedPath: string): Promise<void> => {
export const checkTfvars = async (
generatedPath: string,
tfvarsFilename: string
): Promise<void> => {
try {
// Remove existing spk.tfvars if it already exists
if (fs.existsSync(path.join(generatedPath, "spk.tfvars"))) {
fs.unlinkSync(path.join(generatedPath, "spk.tfvars"));
if (fs.existsSync(path.join(generatedPath, tfvarsFilename))) {
fs.unlinkSync(path.join(generatedPath, tfvarsFilename));
}
} catch (err) {
return err;
}
};

export const generateBackendTfvars = async (definitionJSON: string[]) => {
try {
const backendTfvars: string[] = [];
const backendConfig = definitionJSON;
} catch (err) {
logger.error(err);
}
};

/**
*
* Takes in the "variables" block from definition.json file and returns
Expand All @@ -314,7 +398,7 @@ export const checkSpkTfvars = async (generatedPath: string): Promise<void> => {
* key = "value"
*
*/
export const generateSpkTfvars = async (definitionJSON: string[]) => {
export const generateTfvars = async (definitionJSON: string[]) => {
try {
const tfVars: string[] = [];
// Parse definition.json "variables" block
Expand Down Expand Up @@ -348,12 +432,13 @@ export const generateSpkTfvars = async (definitionJSON: string[]) => {
* @param spkTfVars spk tfvars object in an array
* @param generatedPath Path to write the spk.tfvars file to
*/
export const writeToSpkTfvarsFile = async (
export const writeTfvarsFile = async (
spkTfVars: string[],
generatedPath: string
generatedPath: string,
tfvarsFilename: string
) => {
spkTfVars.forEach(tfvar => {
fs.appendFileSync(path.join(generatedPath, "spk.tfvars"), tfvar + "\n");
fs.appendFileSync(path.join(generatedPath, tfvarsFilename), tfvar + "\n");
});
};

Expand All @@ -363,8 +448,12 @@ export const writeToSpkTfvarsFile = async (
* @param projectPath Path to the definition.json file
*/
export const readDefinitionJson = async (projectPath: string) => {
const rootDef = path.join(projectPath, "definition.json");
const data: string = fs.readFileSync(rootDef, "utf8");
const definitionJSON = JSON.parse(data);
return definitionJSON;
try {
const rootDef = path.join(projectPath, "definition.json");
const data: string = fs.readFileSync(rootDef, "utf8");
const definitionJSON = JSON.parse(data);
return definitionJSON;
} catch (err) {
logger.warn(`No definition.json file was found in ${projectPath}`);
}
};
41 changes: 33 additions & 8 deletions src/commands/infra/scaffold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ export const scaffoldCommandDecorator = (command: commander.Command): void => {
sourceFolder
);
await validateRemoteSource(scaffoldDefinition);
await copyTfTemplate(path.join(sourcePath, opts.template), opts.name);
await copyTfTemplate(
path.join(sourcePath, opts.template),
opts.name,
false
);
await validateVariablesTf(
path.join(sourcePath, opts.template, "variables.tf")
);
Expand Down Expand Up @@ -153,14 +157,33 @@ export const renameTfvars = async (dir: string): Promise<void> => {
*/
export const copyTfTemplate = async (
templatePath: string,
envName: string
envName: string,
generation: boolean
): Promise<boolean> => {
try {
await fsextra.copy(templatePath, envName, {
filter: file => {
return !(file.indexOf("terraform.tfvars") > -1);
}
});
if (generation === true) {
await fsextra.copy(templatePath, envName, {
filter: file => {
if (
file.indexOf("terraform.tfvars") !== -1 ||
file.indexOf("backend.tfvars") !== -1
) {
return false;
}
return true;
}
});
} else {
await fsextra.copy(templatePath, envName, {
filter: file => {
// return !(file.indexOf("terraform.tfvars") > -1);
if (file.indexOf("terraform.tfvars") !== -1) {
return false;
}
return true;
}
});
}
logger.info(`Terraform template files copied from ${templatePath}`);
} catch (err) {
logger.error(
Expand Down Expand Up @@ -254,7 +277,9 @@ export const parseBackendTfvars = (backendData: string) => {
block.forEach(b => {
const elt = b.split(":");
if (elt[0].length > 0) {
backend[elt[0]] = elt[1].replace(/\"/g, "");
backend[elt[0]] = elt[1]
.replace(/\"/g, "")
.replace(/(?:\\[rn]|[\r\n]+)+/g, "");
}
});
return backend;
Expand Down