Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NCloud Terraform 변환 코드 구현 #133

Merged
merged 24 commits into from
Nov 22, 2024

Conversation

Gdm0714
Copy link
Collaborator

@Gdm0714 Gdm0714 commented Nov 21, 2024

연관 이슈

  • NCloud 인프라 구성을 위한 JSON을 Terraform 코드로 변환
  • 리소스 간의 참조 및 의존성 관리
  • 모델-컨트롤러 구조의 설계

주요 작업

  1. 초기 구조 설계
  • 인터페이스 정의
interface NCloudModel {
    name: string;
    serviceType: string;
    priority: ResourcePriority;
    getProperties(): { [key: string]: any };
}

interface CloudCanvasNode {
    id: string;
    type: string;
    name: string;
    properties: { [key: string]: any };
}
  • 리소스 우선순위 정의
enum ResourcePriority {
    VPC = 1,
    NETWORK_ACL = 2,
    SUBNET = 3,
    ACG = 4,
    ACG_RULE = 5,
    LOGIN_KEY = 6,
    NETWORK_INTERFACE = 7,
    SERVER = 8,
    PUBLIC_IP = 9
}
  1. 리소스 참조 처리 개선
  • 초기 문제
// 단순 문자열 체크
if (value.includes('ncloud_') && value.includes('.') && value.includes('_'))
  • 정규표현식 도입
const ncloudRefPattern = /^ncloud_[a-zA-Z_]+\.[a-zA-Z_-]+\.[a-zA-Z_]+$/;
const varRefPattern = /^var\.[a-zA-Z_]+$/;
  1. 모델-컨트롤러 관심사 분리
  • 초기 문제
// 모델에서 직접 참조
getProperties() {
    return {
        vpc_no: `ncloud_vpc.vpc.id`  // 하드코딩된 참조
    };
}
  • 플레이스홀더 패턴 도입
// 모델
getProperties() {
    return {
        vpc_no: "VPC_ID_PLACEHOLDER"
    };
}

// 컨트롤러에서 치환
private replaceReferences(properties: { [key: string]: any }): { [key: string]: any }
  1. 배열 참조 처리
  • 초기 문제
# 잘못된 형식
access_control_groups = ncloud_access_control_group.my-acg.id
  • 해결
private replaceReferences(properties: { [key: string]: any }): { [key: string]: any } {
    const result = { ...properties };

    for (const [key, value] of Object.entries(result)) {
        if (typeof value === 'string') {
            switch(value) {
                case 'ACG_ID_PLACEHOLDER':
                    const acgName = this.resourceNameMap.get('ncloud_access_control_group');
                    result[key] = `ncloud_access_control_group.${acgName}.id`;
                    break;
                // ... 다른 케이스들
            }
        } else if (Array.isArray(value)) {
            result[key] = value.map(item => {
                if (typeof item === 'string') {
                    const replacedValue = this.replaceReferences({ temp: item }).temp;
                    return replacedValue;
                }
                return this.replaceReferences(item);
            });
        }
    }
    return result;
}

구현 결과

  1. JSON 입력
{
    "id": "vpc1",
    "type": "VPC",
    "name": "my-vpc",
    "properties": {
        "cidrBlock": "172.16.0.0/16"
    }
}
  1. 생성된 Terraform 코드
    hclCopyresource "ncloud_vpc" "my-vpc" {
    name = "my-vpc"
    ipv4_cidr_block = "172.16.0.0/16"
    }

비고

  1. 해결한 문제들
  • 참조값 처리

문제: 하드코딩된 참조값
해결: 플레이스홀더 패턴과 ResourceNameMap 도입

  • 배열 참조

문제: 배열이 단일 값으로 처리
해결: 모델에서 배열 구조 유지

  • 코드 구조

문제: 모델-컨트롤러 책임 불명확
해결: 관심사 분리 및 인터페이스 기반 설계

  1. 고민했던 부분
  • 의존성 관리

ResourcePriority enum으로 순서 보장
리소스 간 참조 관리 방법

  • 참조값 처리 방식

정규표현식 vs 단순 문자열 체크
플레이스홀더 패턴의 도입

  • 모델 설계

인터페이스 기반 vs 추상 클래스
Properties 구조 설계

  1. 개선된 점
  • 코드 구조

명확한 책임 분리
재사용 가능한 컴포넌트

  • 유지보수성

일관된 코드 패턴
확장 가능한 구조

  • 타입 안정성

인터페이스 기반 설계
TypeScript 타입 시스템 활용

@Gdm0714 Gdm0714 self-assigned this Nov 21, 2024
Copy link
Collaborator

@paulcjy paulcjy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!

테라폼 부분은 서버나 클라이언트에서 import해서 사용하게 될텐데 apps 말고 packages로 옮기면 좋을 것 같아요.

Comment on lines +4 to +23
accessKey: string;
secretKey: string;
region: string;
site: string;
name: string;
serviceType: string;
requiredVersion: string;
source: string;

constructor(json: any) {
this.serviceType = 'provider';
this.name = 'ncloud';
this.accessKey = json.accessKey;
this.secretKey = json.secretKey;
this.region = json.region;
this.site = json.site || 'public';
this.requiredVersion = '>= 0.13';
this.source = 'NaverCloudPlatform/ncloud';
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 클래스 생성자가 다른 클래스도 전부 json: any로 되어 있는데, 생성자 파라미터는 구조분해할당으로 받고, 파라미터 없이 정의할 수 있는 부분은 위에서 하는건 어떨까요?
이렇게 하면 json.site처럼 값을 확인하지 않고 파라미터에서 기본값을 설정할 수도 있습니다.
타입 활용이랑 가독성이 개선될 것 같아요.

this.resourceNameMap = new Map();
}

addResourceFromJson(jsonData: { nodes?: CloudCanvasNode[] }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 jsonData를 객체로 설정하신 이유가 있나요? 바로 CloudCanvasNode[] 배열을 받는건 어떤가요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseToNCloudModel() 함수는 ncloud에 종속적인 부분이라 ncloud provider의 메서드로 넣으면 어떨까 하는 생각이 들었습니다.
아니면 지금처럼 임시로 TerraformConvertor에 두거나요.

Comment on lines +19 to +22
return new NCloudVPC({
name: name || 'vpc',
ipv4CidrBlock: properties.cidrBlock
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|| 부분을 생성자 파라미터의 기본값으로 설정하면 어떨까요?

+NCloudModel 관련 클래스에서 속성이 있어도 지금처럼 생성자에 입력되지 않는 속성들이 있는데 ?를 사용해서 optional을 표시하면 좋을 것 같습니다.

Comment on lines 124 to 147
const terraformBlock = `
terraform {
required_providers {
ncloud = {
source = "${providerProperties.terraform.required_providers.ncloud.source}"
}
}
required_version = "${providerProperties.terraform.required_version}"
}`;

const providerBlock = `
provider "${this.provider.name}" {
${this.formatProperties(providerProperties.provider)}
}`;

const resourceBlocks = this.resources
.sort((a, b) => a.priority - b.priority)
.map(resource => {
const properties = this.replaceReferences(resource.getProperties());
return `
resource "${resource.serviceType}" "${resource.name}" {
${this.formatProperties(properties)}
}`;
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문자열 템플릿 부분을 utils로 분리하는건 어떨까요

Copy link
Collaborator Author

@Gdm0714 Gdm0714 Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포크된 레포에는 이미 반영된 상태이고 머지 이후에 다시 pr올릴려고 했는데 그냥 올리겠습니다

Copy link
Collaborator

@SeoGeonhyuk SeoGeonhyuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DFS를 써서 참조의존성으로 그래프를 만드는 방식으로 다시 리팩토링 하는 것도 좋아보입니다.
고생하셨습니다!

@Gdm0714 Gdm0714 merged commit f38b48f into boostcampwm-2024:development Nov 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants