Skip to content

Commit

Permalink
Merge pull request #203 from boostcampwm-2024/v2/TerraformConvertor
Browse files Browse the repository at this point in the history
V2/terraform convertor
  • Loading branch information
p1n9d3v authored Jan 6, 2025
2 parents 3a58d96 + 76d160f commit 8c780e5
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 178 deletions.
25 changes: 25 additions & 0 deletions packages/terraform/parser/BaseResourceParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ResourceParsingStrategy } from '../util/interface/ResourceParsingStrategy';
import { NCloudModel } from '../interface/NCloudModel';

export abstract class BaseResourceParser implements ResourceParsingStrategy {
protected abstract resourceType: string[];

protected abstract createModel(properties: any): NCloudModel;

canParse(type: string): boolean {
return this.resourceType.includes(type.toLowerCase());
}

parse(properties: any): NCloudModel {
this.validateProperties(properties);
return this.createModel(properties);
}

protected validateProperties(properties: any): void {}

protected getNameOrDefault(properties: any, defaultPrefix: string): string {
return (
properties.name?.toLowerCase() || `${defaultPrefix}-${Date.now()}`
);
}
}
83 changes: 83 additions & 0 deletions packages/terraform/parser/MySQLParesr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { BaseResourceParser } from './BaseResourceParser';
import { ValidationError } from '../util/ValidationError';
import { NCloudModel } from '../interface/NCloudModel';
import { NCloudMySQL } from '../model/NCloudMySQL';

export class MySQLParesr extends BaseResourceParser {
protected resourceType = ['db-mysql', 'mysql'];
private validationRules: MySQLValidationRules[] = [
{
fieldName: 'serviceName',
minLength: 3,
maxLength: 30,
required: true,
},
{
fieldName: 'serverNamePrefix',
minLength: 3,
maxLength: 20,
required: true,
},
{ fieldName: 'userName', minLength: 4, maxLength: 16, required: true },
{
fieldName: 'userPassword',
minLength: 8,
maxLength: 20,
required: true,
},
{ fieldName: 'hostIp', required: true },
{
fieldName: 'databaseName',
minLength: 1,
maxLength: 30,
required: true,
},
];

protected validateProperties(properties: any) {
for (const rule of this.validationRules) {
this.validateField(properties, rule);
}
}

protected validateField(properties: any, rule: MySQLValidationRules): void {
const value = properties[rule.fieldName];

if (rule.required && !value) {
throw new ValidationError(
'MySQL',
rule.fieldName,
'필수 속성이 없습니다.',
);
}

if (value && rule.minLength && value.length < rule.minLength) {
throw new ValidationError(
'MySQL',
rule.fieldName,
'최소 길이보다 짧습니다.',
);
}

if (value && rule.maxLength && value.length > rule.maxLength) {
throw new ValidationError(
'MySQL',
rule.fieldName,
'최대 길이를 초과했습니다.',
);
}
}

protected createModel(properties: any): NCloudModel {
return new NCloudMySQL({
serviceName: properties.serviceName || 'mysql',
serverNamePrefix: properties.serverNamePrefix,
userName: properties.userName,
userPassword: properties.userPassword,
hostIp: properties.hostIp,
databaseName: properties.databaseName,
subnet: properties.subnet,
vpc: properties.vpc,
});
}
}
Empty file.
32 changes: 32 additions & 0 deletions packages/terraform/parser/ResourceParserFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ResourceParsingStrategy } from '../util/interface/ResourceParsingStrategy';
import { VPCParser } from './VPCParser';
import { NCloudModel } from '../interface/NCloudModel';
import { MySQLParesr } from './MySQLParesr';
import { ServerParser } from './ServerParser';
import { SubnetParser } from './SubnetParser';

export class ResourceParserFactory {
private static strategy: ResourceParsingStrategy[] = [
new VPCParser(),
new MySQLParesr(),
new ServerParser(),
new SubnetParser()
];

static getParser(type: string): ResourceParsingStrategy{
const parser = this.strategy.find(s => s.canParse(type));
if(!parser) {
throw new Error('올바르지 않은 리소스 타입입니다');
}
return parser;
}

static parseResource(type: string, properties: any): NCloudModel{
const parser = this.getParser(type);
return parser.parse(properties);
}

static registerParser(parser: ResourceParsingStrategy): void {
this.strategy.push(parser);
}
}
43 changes: 43 additions & 0 deletions packages/terraform/parser/ServerParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { BaseResourceParser } from './BaseResourceParser';
import { ValidationError } from '../util/ValidationError';
import { NCloudServer } from '../model/NCloudServer';

export class ServerParser extends BaseResourceParser {
protected resourceType = ['server'];

protected validateProperties(properties: any): void {
if (!properties.server_image_number) {
throw new ValidationError(
'Server',
'serverImageNumber',
'server image number 가 필수입니다.',
);
}
if (!properties.server_spec_code) {
throw new ValidationError(
'Server',
'serverSpecCode',
'server spec code 가 필수입니다.',
);
}
if (!properties.subnet) {
throw new ValidationError(
'Server',
'subnet',
'subnet 이 필수입니다.',
);
}
}

protected createModel(properties: any): NCloudServer {
return new NCloudServer({
name: this.getNameOrDefault(properties, 'server'),
serverImageNumber: properties.server_image_number,
serverSpecCode: properties.server_spec_code,
subnetName: properties.subnet,
loginKeyName: properties.loginKeyName,
nicName: properties.nicName,
acgName: properties.acgName,
});
}
}
56 changes: 56 additions & 0 deletions packages/terraform/parser/SubnetParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { BaseResourceParser } from './BaseResourceParser';
import { ValidationError } from '../util/ValidationError';
import { NCloudSubnet } from '../model/NCloudSubnet';

export class SubnetParser extends BaseResourceParser {
protected resourceType = ['subnet'];

protected validateProperties(properties: any): void {
if (!properties.subnet) {
throw new ValidationError(
'Subnet',
'subnet',
'CIDR block 이 필수 속성입니다.',
);
}
if (!properties.zone) {
throw new ValidationError(
'Subnet',
'zone',
'zone 이 필수 속성입니다.',
);
}
if (
!properties.subnetType ||
!['PUBLIC', 'PRIVATE'].includes(properties.subnetType)
) {
throw new ValidationError(
'Subnet',
'subnetType',
'PUBLIC 또는 PRIVATE 이어야 합니다',
);
}
if (
properties.usageType &&
!['GEN', 'LOADB', 'BM', 'NATGW'].includes(properties.usageType)
) {
throw new ValidationError(
'Subnet',
'usageType',
'GEN, LOADB, BM, NATGW 중 하나여야 합니다',
);
}
}

protected createModel(properties: any): NCloudSubnet {
return new NCloudSubnet({
name: this.getNameOrDefault(properties, 'subnet'),
subnet: properties.subnet,
zone: properties.zone,
subnetType: properties.subnetType,
usageType: properties.usageType || 'GEN',
vpcName: properties.vpcName,
networkAclNo: properties.networkAclNo,
});
}
}
24 changes: 24 additions & 0 deletions packages/terraform/parser/VPCParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BaseResourceParser } from './BaseResourceParser';
import { ValidationError } from '../util/ValidationError';
import { NCloudVPC } from '../model/NCloudVPC';

export class VPCParser extends BaseResourceParser {
protected resourceType = ['vpc'];

protected validateProperties(properties: any) {
if (!properties.cidrBlock) {
throw new ValidationError(
'VPC',
'ipv4CidrBlock',
'필수 속성이 없습니다.',
);
}
}

protected createModel(properties: any): NCloudVPC {
return new NCloudVPC({
name: this.getNameOrDefault(properties, 'vpc'),
ipv4CidrBlock: properties.cidrBlock,
});
}
}
6 changes: 6 additions & 0 deletions packages/terraform/parser/interface/MySQLValidationRules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface MySQLValidationRules {
fieldName: string,
minLength?: number,
maxLength?: number,
required: boolean;
}
Empty file.
87 changes: 87 additions & 0 deletions packages/terraform/util/ReferenceReplacer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ReferenceMetrics } from './ReferenceMetrics';

type ReferenceMap = Map<string, string>;

export class ReferenceReplacer {
private referenceCache = new Map<string, string>();

constructor(private resourceNameMap: ReferenceMap) {}

replaceReferences(properties: { [key: string]: any }): {
[key: string]: any;
} {
const result = { ...properties };

for (const [key, value] of Object.entries(result)) {
result[key] = this.transformValue(value);
}

return result;
}

private transformValue(value: any): any {
if (typeof value === 'string') {
return this.getCachedReference(value);
}

if (Array.isArray(value)) {
return value.map((item) => this.transformValue(item));
}

if (typeof value === 'object' && value !== null) {
const transformed = { ...value };
for (const [k, v] of Object.entries(transformed)) {
transformed[k] = this.transformValue(v);
}
return transformed;
}

return value;
}

private getCachedReference(value: string): string {
const startTime = performance.now();

if (this.referenceCache.has(value)) {
const result = this.referenceCache.get(value)!;
return result;
}

const resolvedReference = this.resolveReference(value);
this.referenceCache.set(value, resolvedReference);

return resolvedReference;
}

private resolveReference(value: string): string {
if (value.startsWith('ncloud_')) {
return value;
}

const resourceTypeMap = new Map([
['vpc', 'ncloud_vpc'],
['subnet', 'ncloud_subnet'],
['acg', 'ncloud_access_control_group'],
['nic', 'ncloud_network_interface'],
['server', 'ncloud_server'],
['loginkey', 'ncloud_login_key'],
['nacl', 'ncloud_network_acl'],
['publicip', 'ncloud_public_ip'],
]);

if (value.endsWith('.default_network_acl_no')) {
const resourceName = value.split('.')[0];
return `${resourceTypeMap.get('vpc')}.${resourceName}.default_network_acl_no`;
}

if (value.endsWith('.id')) {
const resourceName = value.split('.')[0];
const resourceType = resourceTypeMap.get(
resourceName.split('-')[0],
);
return `${resourceType}.${resourceName}.id`;
}

return value;
}
}
11 changes: 11 additions & 0 deletions packages/terraform/util/ValidationError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class ValidationError extends Error{
constructor(
public readonly resource: string,
public readonly field: string,
message: string
) {
super(`${resource} 에서 ${field}속성이 필요합니다`);
this.name = `ValidationError`;
this.message = message;
}
}
6 changes: 6 additions & 0 deletions packages/terraform/util/interface/ResourceParsingStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NCloudModel } from '../../interface/NCloudModel';

export interface ResourceParsingStrategy{
parse(properties: any): NCloudModel;
canParse(type: string): boolean;
}
Loading

0 comments on commit 8c780e5

Please sign in to comment.