본문 바로가기
개발/Terraform on NaverCloud

MSSQL 개발하기 (1)

by 컴쏘 2023. 9. 9.
728x90

우선, cloud_mssql.go를 먼저 작성하기로 하였다. 

2개의 API 문서를 참고하였다. 

 

[ResourceNcloudMssql]

 

Schema 작성하기 - 우선 required parameter만 작성하기로 하였다. 

Schema: map[string]*schema.Schema{
			
		},

 

required 인 것

 

1) vpc_no

vpcNo

"vpc_no": {
				Type:        schema.TypeString,
				Required:    true,
				Description: "VPC Number of Cloud DB for MSSQL instances",
			},

 

2) subnet_no

subnetNo

"subnet_no": {
				Type:        schema.TypeString,
				Required:    true,
				Description: "Subnet Number of Cloud DB for MSSQL instance",
			},

 

 

3) service_name

여기는 제약 사항이 있어서 문서를 참고했다.

https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2@v2.26.1/helper/validation

 

cloudMssqlServiceName

"service_name": {
				Type:     schema.TypeString,
				Required: true,
				ValidateDiagFunc: ToDiagFunc(validation.All(
					validation.StringLenBetween(3, 15),
					validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9\\-가-힣]+$`), "Composed of alphabets, numbers, korean, hyphen (-)."),
				)),
				Description: "Name of Cloud DB for MSSQL instance",
			},

 

4) is_ha

isHa

"is_ha": {
				Type:        schema.TypeBool,
				Required:    true,
				Description: "Choice of High Availability",
			},

 

5) user_name

cloudMssqlUserName

"user_name": {
				Type:     schema.TypeString,
				Required: true,
				ValidateDiagFunc: ToDiagFunc(validation.All(
					validation.StringLenBetween(4, 16),
					validation.StringMatch(regexp.MustCompile(`^[a-zA-Z]+.+`), "starts with an alphabets."),
					validation.StringMatch(regexp.MustCompile(`^[a-zA-Z]+[a-zA-Z0-9-\\\\_]+$`), "Composed of alphabets, numbers, hyphen (-), (\\\\), (_)."),
				)),
				Description: "Access username, which will be used for DB admin",
			},

 

6) user_password

cloudMssqlUserPassword

"user_password": {
				Type:     schema.TypeString,
				Required: true,
				ValidateDiagFunc: ToDiagFunc(validation.All(
					validation.StringLenBetween(8, 20),
					validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9~!@#$%^*()\\-_=\\[\\]\\{\\};:,.<>?]{8,20}$`), "Must Combine at least one each of alphabets, numbers, special characters except ` & + \\\\ \\" ' / and white space."),
					validation.StringMatch(regexp.MustCompile(`.+[a-zA-Z]{1,}.+|.+[a-zA-Z]{1,}|[a-zA-Z]{1,}.+`), "Must have at least 1 alphabet."),
					validation.StringMatch(regexp.MustCompile(`.+[0-9]{1,}.+|.+[0-9]{1,}|[0-9]{1,}.+`), "Must have at least 1 Number."),
					validation.StringMatch(regexp.MustCompile(`.+[~!@#$%^*()\\-_=\\[\\]\\{\\};:,.<>?].+|.+[~!@#$%^*()\\-_=\\[\\]\\{\\};:,.<>?]|[~!@#$%^*()\\-_=\\[\\]\\{\\};:,.<>?].+`), "Must have at least 1 special characters except ` & + \\\\ \\" ' / and white space."),
				)),
				Description: "Access password for user, which will be used for DB admin",
			},

 

optional 인 것

 

1) region_code

regionCode

"region_code": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Region code to determine the region of Cloud DB for MSSQL instance",
			},

 

2) mirror_subnet_no

mirrorSubnetNo

"mirror_subnet_no": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Subnet number of Mirror Server. Required when isMultiZone is true.",
			},

 

3) config_group_no

configGroupNo

"config_group_no": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Config Group number of Cloud DB for MSSQL instance.",
				Default:     0,
			},

 

4) image_product_code

cloudMssqlImageProductCode

"image_product_code": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Image Product Code of Cloud DB for MSSQL instance.",
			},

 

5) product_code

cloudMssqlProductCode

"product_code": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Product Code of Cloud DB for MSSQL instance.",
			},

 

6) data_storage_type_code

dataStorageTypeCode

"data_storage_type_code": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Data Storage Type Code.",
				Default:     "SSD",
			},

 

7) is_multi_zone

isMultiZone

"is_multi_zone": {
				Type:        schema.TypeBool,
				Optional:    true,
				Description: "Multi Zone option. Required when isHa is true.",
				Default:     false,
			},

 

8) backup_file_retention_period

backupFileRetentionPeriod

"backup_file_retention_period": {
				Type:             schema.TypeInt,
				Optional:         true,
				ValidateDiagFunc: ToDiagFunc(validation.IntBetween(1, 30)),
				Description:      "Retention period of back-up files.",
				Default:          1,
			},

 

9) backup_time

backupTime

"backup_time": {
				Type:     schema.TypeString,
				Optional: true,
				ValidateDiagFunc: ToDiagFunc(validation.All(
					validation.StringMatch(regexp.MustCompile(`^(0[0-9]|1[0-9]|2[0-3])([0-5][0-9])$`), "Must be in the format HHMM."),
					validation.StringMatch(regexp.MustCompile(`^(0[0-9]|1[0-9]|2[0-3])([0-5][0-9])(00|15|30|45)$`), "Must be in 15-minute intervals."),
				)),
				Description: "Back-up time. Required when isAutomaticBackup is false.",
			},

 

10) is_automatic_backup

isAutomaticBackup

"is_automatic_backup": {
				Type:        schema.TypeBool,
				Optional:    true,
				Description: "Automatic backup time.",
			},

 

11) port

 

cloudMssqlPort

"port": {
				Type:     schema.TypeInt,
				Optional: true,
				ValidateDiagFunc: ToDiagFunc(validation.Any(
					//validation.
					validation.IntBetween(10000, 20000),
				)),
				Description: "Port of Cloud DB for MSSQL instance.",
				Default:     1433,
			},

 

12) character_set_name

characterSetName

"character_set_name": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "DB character set.",
				Default:     "Korean_Wansung_CI_AS",
			},

 

13) response_format_type

responseFormatType

"response_format_type": {
				Type:        schema.TypeString,
				Optional:    true,
				Description: "Response format type.",
				Default:     "xml",
			},

 

 

resourceNcloudMssqlCreate, resourceNcloudMssqlRead, resourceNcloudMssqlDelete는 기존에 개발되었던 코드들을 참고하면서 작성해보았다. 주로 classicLoadBalancer, lb 2개를 많이 참고하였다. 

 

[resourceNcloudMssqlCreate]

func resourceNcloudMssqlCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*conn.ProviderConfig).Client

	reqParams, err := buildCreateCloudMssqlInstanceParams(client, d)
	if err != nil {
		return err
	}
	LogCommonRequest("CreateCloudMssqlInstance", reqParams)
	resp, err := client.Vmssql.V2Api.CreateCloudMssqlInstance(reqParams)
	if err != nil {
		LogErrorResponse("CreateCloudMssqlInstance", err, reqParams)
		return err
	}
	LogCommonResponse("CreateCloudMssqlInstance", GetCommonResponse(resp))

	cloudMssqlInstance := resp.CloudMssqlInstanceList.CloudMssqlInstanceList[0]
	d.SetId(*cloudMssqlInstance.CloudMssqlInstanceNo)

	stateConf := &resource.StateChangeConf{
		Pending: []string{"INIT", "USE"},
		Target:  []string{"USED"},
		Refresh: func() (interface{}, string, error) {
			instance, err := GetCloudMssqlInstance(client, ncloud.StringValue(cloudMssqlInstance.CloudMssqlInstanceNo))
			if err != nil {
				return 0, "", err
			}

			if ncloud.StringValue(instance.CloudMssqlInstanceOperation.Code) == "NULL" {
				return instance, ncloud.StringValue(instance.CloudMssqlInstanceOperation.Code), nil
			}

			return instance, ncloud.StringValue(instance.CloudMssqlInstanceOperation.Code), nil
		},
		Timeout:    conn.DefaultCreateTimeout,
		Delay:      2 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error waiting for CloudMssqlInstanceStatus state to be \\"USED\\": %s", err)
	}

	return resourceNcloudMssqlRead(d, meta)
}

 

[resourceNcloudMssqlRead]

func resourceNcloudMssqlRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*conn.ProviderConfig).Client

	ms, err := GetCloudMssqlInstance(client, d.Id())
	if err != nil {
		return err
	}

	if ms != nil {
		d.Set("service_name", ms.CloudMssqlServiceName)
		d.Set("instance_status_name", ms.CloudMssqlInstanceStatusName)
		d.Set("image_product_code", ms.CloudMssqlImageProductCode)
		d.Set("is_ha", ms.IsHa)
		d.Set("port", ms.CloudMssqlPort)
		d.Set("backup_file_retention_period", ms.BackupFileRetentionPeriod)
		d.Set("backup_time", ms.BackupTime)
		d.Set("config_group_no", ms.ConfigGroupNo)
		d.Set("engine_version", ms.EngineVersion)
		d.Set("create_date", ms.CreateDate)
		d.Set("db_collation", ms.DbCollation)
		d.Set("access_control_group_no_list", ms.AccessControlGroupNoList)

		if instanceStatus := FlattenCommonCode(ms.CloudMssqlInstanceStatus); instanceStatus["code"] != nil {
			d.Set("instance_status", instanceStatus["code"])
		}

		if instanceOperation := FlattenCommonCode(ms.CloudMssqlInstanceOperation); instanceOperation["code"] != nil {
			d.Set("instance_operation", instanceOperation["code"])
		}

		if licenseCode := FlattenCommonCode(ms.LicenseCode); licenseCode["code"] != nil {
			d.Set("license_code", licenseCode["code"])
		}

		if len(ms.CloudMssqlServerInstanceList) != 0 {
			if err := d.Set("server_instance_list", flattenCloudMssqlServerInstanceList(ms.CloudMssqlServerInstanceList)); err != nil {
				return err
			}
		} else {
			d.Set("server_instance_list", nil)
		}
	} else {
		log.Printf("unable to find resource: %s", d.Id())
		d.SetId("") // resource not found
	}

	return nil
}

 

[resourceNcloudMssqlDelete]

func resourceNcloudMssqlDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*conn.ProviderConfig).Client
	if err := deleteCloudMssqlInstance(client, d.Id()); err != nil {
		return err
	}

	d.SetId("")
	return nil
}

 

 

이제 test 코드를 작성해보자. 역시나, 기존에 작성되었던 코드들을 참고하였고, classicLoadBalancer, lb 2가지를 많이 참고하였다. 

 

[cloud_mssql_test.go] 

package cloudmssql_test

import (
	"fmt"
	"testing"

	"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
	"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vmssql"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
	. "github.com/terraform-providers/terraform-provider-ncloud/internal/acctest"
	"github.com/terraform-providers/terraform-provider-ncloud/internal/conn"
	mssqlservice "github.com/terraform-providers/terraform-provider-ncloud/internal/service/cloudmssql"
)

func TestAccResourceNcloudMssql_vpc_basic(t *testing.T) {
	var mssqlInstance vmssql.CloudMssqlInstance
	testMssqlName := fmt.Sprintf("tf-mssql-%s", acctest.RandString(5))
	resourceName := "ncloud_mssql.mssql"

	resource.Test(t, resource.TestCase{
		PreCheck:  func() { TestAccPreCheck(t) },
		Providers: GetTestAccProviders(true),
		CheckDestroy: func(state *terraform.State) error {
			return testAccCheckCloudMssqlDestroy(state, GetTestProvider(true))
		},
		Steps: []resource.TestStep{
			{
				Config: testAccMssqlVpcConfig(testMssqlName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckCloudMssqlExists(resourceName, &mssqlInstance, GetTestProvider(true)),
					resource.TestCheckResourceAttr(resourceName, "service_name", testMssqlName),
					resource.TestCheckResourceAttr(resourceName, "user_name", "test"),
					resource.TestCheckResourceAttr(resourceName, "user_password", "qwer1234!"),
					resource.TestCheckResourceAttr(resourceName, "is_ha", "true"),
					resource.TestCheckResourceAttr(resourceName, "is_multi_zone", "false"),
					resource.TestCheckResourceAttr(resourceName, "backup_file_retention_period", "1"),
					resource.TestCheckResourceAttr(resourceName, "is_automatic_backup", "true"),
				),
			},
		},
	})
}

func testAccCheckCloudMssqlExists(n string, mssql *vmssql.CloudMssqlInstance, provider *schema.Provider) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		resource, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("not found %s", n)
		}

		if resource.Primary.ID == "" {
			return fmt.Errorf("no ID is set")
		}

		clinet := provider.Meta().(*conn.ProviderConfig).Client
		mssqlInstance, err := mssqlservice.GetCloudMssqlInstance(clinet, resource.Primary.ID)
		if err != nil {
			return err
		}

		if mssqlInstance == nil {
			return fmt.Errorf("Not found Mssql : %s", resource.Primary.ID)
		}

		mssql = mssqlInstance
		return nil
	}
}

func testAccCheckCloudMssqlDestroy(s *terraform.State, provider *schema.Provider) error {
	client := provider.Meta().(*conn.ProviderConfig).Client

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "ncloud_mssql" {
			continue
		}

		cloudMssql, err := mssqlservice.GetCloudMssqlInstance(client, rs.Primary.ID)
		if err != nil {
			return err
		}

		if cloudMssql != nil {
			return fmt.Errorf("CloudMssql(%s) still exists", ncloud.StringValue(cloudMssql.CloudMssqlInstanceNo))
		}
	}

	return nil
}

func testAccMssqlVpcConfig(testMssqlName string) string {
	return fmt.Sprintf(`
		resource "ncloud_vpc" "test_vpc" {
			name               = "%[1]s"
			ipv4_cidr_block    = "10.0.0.0/16"
		}
		resource "ncloud_subnet" "test_subnet" {
			vpc_no             = ncloud_vpc.test_vpc.vpc_no
			name               = "%[1]s"
			subnet             = "10.0.0.0/24"
			zone               = "KR-2"
			network_acl_no     = ncloud_vpc.test_vpc.default_network_acl_no
			subnet_type        = "PUBLIC"
		}
		resource "ncloud_mssql" "mssql" {
			vpc_no = ncloud_vpc.test_vpc.vpc_no
			subnet_no = ncloud_subnet.test_subnet.id
			service_name = "%[1]s"
			is_ha = true
			is_multi_zone = false
			user_name = "test"
			user_password = "qwer1234!"
		}
`, testMssqlName)
}

 

 

기본적인 코드는 작성하였으니, 이제 test를 해보자..!

728x90