Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/commands/iascable-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {promises} from 'fs';
import {default as jsYaml} from 'js-yaml';
import {dirname, join} from 'path';

import {IascableInput} from './inputs/iascable.input';
import {IascableBuild} from './inputs/iascable.input';
import {CommandLineInput} from './inputs/command-line.input';
import {BillOfMaterialModel, isTileConfig, OutputFile, TerraformComponent, Tile} from '../models';
import {
Expand Down Expand Up @@ -79,7 +79,7 @@ export const builder = (yargs: Argv<any>) => {
});
};

export const handler = async (argv: Arguments<IascableInput & CommandLineInput>) => {
export const handler = async (argv: Arguments<IascableBuild & CommandLineInput>) => {
process.env.LOG_LEVEL = argv.debug ? 'debug' : 'info';

const cmd: IascableApi = Container.get(IascableApi);
Expand Down Expand Up @@ -107,7 +107,7 @@ export const handler = async (argv: Arguments<IascableInput & CommandLineInput>)
}
};

function buildCatalogBuilderOptions(input: IascableInput): IascableOptions {
function buildCatalogBuilderOptions(input: IascableBuild): IascableOptions {
const tileConfig = {
label: input.tileLabel,
name: input.name,
Expand Down
71 changes: 71 additions & 0 deletions src/commands/iascable-validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {Container} from 'typescript-ioc';
import {Arguments, Argv} from 'yargs';
import {promises} from 'fs';
import {default as jsYaml} from 'js-yaml';
import {dirname, join} from 'path';

import {IascableBuild, IascableValidate} from './inputs/iascable.input';
import {CommandLineInput} from './inputs/command-line.input';
import {BillOfMaterialModel, isTileConfig, OutputFile, TerraformComponent, Tile} from '../models';
import {
IascableApi,
IascableOptions,
IascableResult,
loadBillOfMaterialFromFile,
loadReferenceBom
} from '../services';
import {LoggerApi} from '../util/logger';
import {isUndefined} from '../util/object-util';

export const command = 'validate';
export const desc = 'Validate the provided bill of material against the catalog';
export const builder = (yargs: Argv<any>) => {
return yargs
.option('catalogUrl', {
alias: 'u',
description: 'The url of the module catalog. Can be https:// or file:/ protocol.',
default: 'https://cloud-native-toolkit.github.io/garage-terraform-modules/index.yaml'
})
.option('input', {
alias: 'i',
description: 'The path to the bill of materials to use as input',
conflicts: 'reference',
demandOption: false,
})
.option('reference', {
alias: 'r',
description: 'The reference BOM to use for the build',
conflicts: 'input',
demandOption: false,
})
.option('debug', {
type: 'boolean',
describe: 'Flag to turn on more detailed output message',
});
};

export const handler = async (argv: Arguments<IascableValidate & CommandLineInput>) => {
process.env.LOG_LEVEL = argv.debug ? 'debug' : 'info';

const cmd: IascableApi = Container.get(IascableApi);
const logger: LoggerApi = Container.get(LoggerApi).child('build');

const bom: BillOfMaterialModel | undefined = argv.reference
? await loadReferenceBom(argv.reference, '')
: await loadBillOfMaterialFromFile(argv.input, '');

if (isUndefined(bom)) {
console.log('No BOM found to validate!');
return;
}

const name = bom?.metadata?.name || 'component';
console.log('Validating:', name);

const results = await cmd.validate(argv.catalogUrl, bom);

console.log('Results:');
results.forEach(result => {
console.log(' Module: ', result);
})
};
5 changes: 4 additions & 1 deletion src/commands/inputs/iascable.input.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@

export interface IascableInput {
export interface IascableValidate {
catalogUrl: string;
input?: string;
reference?: string;
}

export interface IascableBuild extends IascableValidate{
ci: boolean;
prompt: boolean;
platform?: string;
Expand Down
1 change: 1 addition & 0 deletions src/services/iascable.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface IascableOptions {

export abstract class IascableApi {
abstract build(catalogUrl: string, input?: BillOfMaterialModel, options?: IascableOptions): Promise<IascableResult>;
abstract validate(catalogUrl: string, input: BillOfMaterialModel): Promise<Array<string | Error>>;
}
6 changes: 6 additions & 0 deletions src/services/iascable.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,10 @@ export class CatalogBuilder implements IascableApi {
tile,
};
}

async validate(catalogUrl: string, input: BillOfMaterialModel): Promise<Array<string | Error>> {
const catalog: Catalog = await this.loader.loadCatalog(catalogUrl);

return this.moduleSelector.validateBillOfMaterial(catalog, input);
}
}
12 changes: 9 additions & 3 deletions src/services/module-selector/module-selector.api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {CatalogModel} from '../catalog-loader';
import {BillOfMaterialModel} from '../../models/bill-of-material.model';
import {BillOfMaterialModel, BillOfMaterialModule} from '../../models/bill-of-material.model';
import {SingleModuleVersion} from '../../models/module.model';

export abstract class ModuleSelectorApi {
abstract buildBillOfMaterial(fullCatalog: CatalogModel, input?: BillOfMaterialModel, filter?: {platform?: string, provider?: string}): Promise<BillOfMaterialModel>;
abstract buildBillOfMaterial(fullCatalog: CatalogModel, input?: BillOfMaterialModel, filter?: { platform?: string, provider?: string }): Promise<BillOfMaterialModel>;

abstract resolveBillOfMaterial(fullCatalog: CatalogModel, input: BillOfMaterialModel): Promise<SingleModuleVersion[]>;
abstract validateBillOfMaterialModuleConfigYaml(fullCatalog: CatalogModel, moduleRef: string, yaml: string): Promise<void>;

abstract validateBillOfMaterialModuleConfigYaml(fullCatalog: CatalogModel, moduleRef: string, yaml: string): Promise<string>;

abstract validateBillOfMaterial(catalogModel: CatalogModel, bom: BillOfMaterialModel): Promise<Array<string | Error>>;

abstract validateBillOfMaterialModuleConfig(catalogModel: CatalogModel, moduleConfig: BillOfMaterialModule): Promise<string>;
}
27 changes: 25 additions & 2 deletions src/services/module-selector/module-selector.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {QuestionBuilderImpl} from '../../util/question-builder/question-builder.
import {LoggerApi} from '../../util/logger';
import {BillOfMaterialModuleConfigError, ModuleNotFound} from '../../errors';
import {of as arrayOf} from '../../util/array-util';
import {isDefinedAndNotNull, isUndefined} from '../../util/object-util';

export class ModuleSelector implements ModuleSelectorApi {
logger: LoggerApi;
Expand Down Expand Up @@ -133,9 +134,30 @@ export class ModuleSelector implements ModuleSelectorApi {
return new SelectedModules(fullCatalog).resolveModules(modules);
}

async validateBillOfMaterialModuleConfigYaml(catalogModel: CatalogModel, moduleRef: string, yaml: string) {
async validateBillOfMaterialModuleConfigYaml(catalogModel: CatalogModel, moduleRef: string, yaml: string): Promise<string> {
const moduleConfig: BillOfMaterialModule = jsYaml.load(yaml) as any;

return this.validateBillOfMaterialModuleConfig(catalogModel, moduleConfig);
}

async validateBillOfMaterial(catalogModel: CatalogModel, bom: BillOfMaterialModel): Promise<Array<string | Error>> {
const result = Promise
.all(BillOfMaterial.getModules(bom).map(m => {
return this.validateBillOfMaterialModuleConfig(catalogModel, m);
}))
.then((result: Array<string | Error>) => result.filter(isDefinedAndNotNull));

return result;
}

async validateBillOfMaterialModuleConfig(catalogModel: CatalogModel, moduleConfig: BillOfMaterialModule): Promise<string> {
const fullCatalog: Catalog = Catalog.fromModel(catalogModel);

const moduleRef: string | undefined = moduleConfig.name || moduleConfig.id;
if (isUndefined(moduleRef)) {
throw new ModuleNotFound('unknown');
}

const module: Module | undefined = fullCatalog.lookupModule({id: moduleRef, name: moduleRef});
if (!module) {
throw new ModuleNotFound(moduleRef);
Expand All @@ -148,7 +170,6 @@ export class ModuleSelector implements ModuleSelectorApi {
.map(v => v.id)
.asArray();

const moduleConfig: BillOfMaterialModule = jsYaml.load(yaml) as any;
const unmatchedVariableNames: string[] = arrayOf(moduleConfig.variables)
.filter(v => !availableVariableNames.includes(v.name))
.map(v => v.name)
Expand All @@ -161,6 +182,8 @@ export class ModuleSelector implements ModuleSelectorApi {
if (unmatchedVariableNames.length > 0 || unmatchedDependencyNames.length > 0) {
throw new BillOfMaterialModuleConfigError({unmatchedVariableNames, unmatchedDependencyNames, availableVariableNames, availableDependencyNames});
}

return moduleRef;
}
}

Expand Down