Skip to content

Commit

Permalink
feat: add support for multi-regions
Browse files Browse the repository at this point in the history
  • Loading branch information
ohoareau committed Sep 1, 2024
1 parent 4f7e77c commit 96e5441
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 53 deletions.
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export type config = {
export type layer_config = {
only_on_envs?: string[];
not_on_envs?: string[];
envs?: Record<string, unknown>;
};

export type enriched_layer_config = {
defaultRegion: string;
regions?: string[];
};

export type loggable = {
Expand Down Expand Up @@ -61,3 +67,11 @@ export type fetch_layer = (
env: string,
name: string,
) => Promise<[Record<string, string>, string]>;

export type fetched_layer = {
name: string;
file: string;
filePath: string;
};

export type fetch_config = (configFile: string) => Promise<config>;
8 changes: 8 additions & 0 deletions src/utils/fetchConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {resolve} from 'path';
import {fetch_config} from '../types';

export const fetchConfig: fetch_config = async (configFile: string) => {
return import(resolve(configFile));
};

export default fetchConfig;
16 changes: 16 additions & 0 deletions src/utils/fetchLayers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {readdirSync} from 'fs';
import {fetched_layer} from '../types';

export const fetchLayers = async (
sourceDir: string,
): Promise<fetched_layer[]> => {
return readdirSync(sourceDir, {withFileTypes: true})
.filter(e => !e.isDirectory() && /.tmpl.tf$/.test(e.name))
.map(e => ({
name: e.name.replace(/\.tmpl\.tf$/, ''),
file: e.name,
filePath: `${sourceDir}/${e.name}`,
}));
};

export default fetchLayers;
46 changes: 36 additions & 10 deletions src/utils/generateEnvLayerFromFile.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,47 @@
import {dirname} from 'path';
import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';
import replaceVars from './replaceVars';
import {enriched_layer_config} from '../types';

export const generateEnvLayerFromFile = async (
sourceFile: string,
targetFile: string,
targetDir: string,
vars: Record<string, unknown>,
layerConfig: enriched_layer_config,
) => {
const parentDir = dirname(targetFile);
existsSync(parentDir) || mkdirSync(parentDir, {recursive: true});
writeFileSync(
targetFile,
replaceVars(
readFileSync(sourceFile, 'utf8') as string,
vars,
) as unknown as string,
existsSync(targetDir) || mkdirSync(targetDir, {recursive: true});

const defaultRegion = layerConfig.defaultRegion;
const regions: string[] = layerConfig.regions || [];

const mappedRegions = (regions || [defaultRegion]).map(
r =>
[
r,
`${targetDir}/main${r === defaultRegion ? '' : `_${r.replace(/-/g, '_')}`}.tf`,
] as [string, string],
);

const reports = await Promise.allSettled(
mappedRegions.map(async ([r, targetFile]: [string, string]) => {
writeFileSync(
targetFile,
replaceVars(readFileSync(sourceFile, 'utf8') as string, {
...vars,
region: r,
rsuffix: r === defaultRegion ? '' : `-${r}`,
}) as unknown as string,
);
}),
);

const errors: {reason: Error}[] = reports.filter(
x => x.status !== 'fulfilled',
) as unknown as {reason: Error}[];

if (errors.length)
throw new Error(
`Unable to generate all env layer files for ${sourceFile}: ${errors.map((x: {reason: Error}) => x.reason.message).join('\n')}`,
);
};

export default generateEnvLayerFromFile;
51 changes: 51 additions & 0 deletions src/utils/generateEnvLayers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import mergeEnvConfig from './mergeEnvConfig';
import generateEnvLayerFromFile from './generateEnvLayerFromFile';
import generateLayerVars from './generateLayerVars';
import {fetched_layer, config, enriched_layer_config} from '../types';

const defaultRegion = 'eu-west-3';

export const generateEnvLayers = async (
layers: fetched_layer[],
config: config,
targetDir: string,
defaultLayerConfig: Record<string, unknown> = {},
) => {
return Promise.all(
Object.keys(config.environments || {}).map(async (name: string) => {
const env = mergeEnvConfig(config, name);
await Promise.all(
layers.map(async ({name: layer, file, filePath}) => {
const layerEnv = {...env, env: name, layer: layer};
const layerConfig =
(config && config.layers && config.layers[layer]) || {};
if (layerConfig) {
if (
layerConfig.only_on_envs &&
!layerConfig.only_on_envs.includes(name)
)
return;
if (
layerConfig.not_on_envs &&
layerConfig.not_on_envs.includes(name)
)
return;
}
await generateEnvLayerFromFile(
filePath,
`${targetDir}/${name}/${file.replace(/\.tmpl\.tf$/, '')}`,
generateLayerVars(layerEnv, layerEnv),
{
defaultRegion,
...defaultLayerConfig,
...(layerConfig?.['envs']?.['common'] || {}),
...(layerConfig?.['envs']?.[name] || {}),
} as enriched_layer_config,
);
}),
);
}),
);
};

export default generateEnvLayers;
51 changes: 9 additions & 42 deletions src/utils/tfgen.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,18 @@
import {readdirSync} from 'fs';
import {resolve} from 'path';
import mergeEnvConfig from './mergeEnvConfig';
import generateEnvLayerFromFile from './generateEnvLayerFromFile';
import generateLayerVars from './generateLayerVars';
import {config} from '../types';
import fetchLayers from './fetchLayers';
import fetchConfig from './fetchConfig';
import generateEnvLayers from './generateEnvLayers';

export const tfgen = async (
configFile: string,
sourceDir: string,
targetDir: string,
defaultLayerConfig?: Record<string, unknown>,
) => {
const layers = readdirSync(sourceDir, {withFileTypes: true})
.filter(e => !e.isDirectory() && /.tmpl.tf$/.test(e.name))
.map(e => ({
name: e.name.replace(/\.tmpl\.tf$/, ''),
file: e.name,
filePath: `${sourceDir}/${e.name}`,
}));
const config: config = await import(resolve(configFile));
await Promise.all(
Object.keys(config.environments || {}).map(async (name: string) => {
const env = mergeEnvConfig(config, name);
await Promise.all(
layers.map(async ({name: layer, file, filePath}) => {
const layerEnv = {...env, env: name, layer: layer};
const layerConfig =
(config && config.layers && config.layers[layer]) || {};
if (layerConfig) {
if (
layerConfig.only_on_envs &&
!layerConfig.only_on_envs.includes(name)
)
return;
if (
layerConfig.not_on_envs &&
layerConfig.not_on_envs.includes(name)
)
return;
}
await generateEnvLayerFromFile(
filePath,
`${targetDir}/${name}/${file.replace(/\.tmpl\.tf$/, '')}/main.tf`,
generateLayerVars(layerEnv, layerEnv),
);
}),
);
}),
await generateEnvLayers(
await fetchLayers(sourceDir),
await fetchConfig(configFile),
targetDir,
defaultLayerConfig,
);
};

Expand Down
2 changes: 1 addition & 1 deletion test/tflayer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import getDirectories from '../src/utils/getDirectories';
beforeEach(() => {
jest.resetAllMocks();
})
describe('tflayer', () => {
describe('list-layers', () => {
it('no directories outputs empty string', async () => {
const logger = jest.fn();
(getDirectories as jest.Mock<unknown>).mockImplementation(() => []);
Expand Down

0 comments on commit 96e5441

Please sign in to comment.