diff --git a/builder/ncloud/builder.go b/builder/ncloud/builder.go index 644da6edf..5ac783a64 100644 --- a/builder/ncloud/builder.go +++ b/builder/ncloud/builder.go @@ -50,6 +50,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) NewStepValidateTemplate(conn, ui, &b.config), NewStepCreateLoginKey(conn, ui, &b.config), multistep.If(b.config.SupportVPC, NewStepCreateInitScript(conn, ui, &b.config)), + multistep.If(b.config.SupportVPC, NewStepCreateAccessControlGroup(conn, ui, &b.config)), NewStepCreateServerInstance(conn, ui, &b.config), NewStepCreateBlockStorage(conn, ui, &b.config), NewStepGetRootPassword(conn, ui, &b.config), diff --git a/builder/ncloud/config.go b/builder/ncloud/config.go index b30f0449a..fe6207083 100644 --- a/builder/ncloud/config.go +++ b/builder/ncloud/config.go @@ -54,16 +54,19 @@ type Config struct { // (default: Korea) Region string `mapstructure:"region" required:"false"` RegionCode string `mapstructure:"region_code" required:"false"` + // Deprecated + AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no" required:"false"` // This is used to allow // winrm access when you create a Windows server. An ACG that specifies an // access source (0.0.0.0/0) and allowed port (5985) must be created in - // advance. - AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no" required:"false"` - // Whether to use VPC. By default, the value is false on "public" site. If you want to use VPC environment. Please set this value true. + // advance if you use CLASSIC env. If this field is left blank, + // Packer will create temporary ACG for automatically in VPC environment. + AccessControlGroupNo string `mapstructure:"access_control_group_no" required:"false"` SupportVPC bool `mapstructure:"support_vpc" required:"false"` - // The ID of the associated Subnet + // The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`. SubnetNo string `mapstructure:"subnet_no" required:"false"` - // The ID of the VPC where you want to place the Server Instance + // The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`. + // (You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`) VpcNo string `mapstructure:"vpc_no" required:"false"` Comm communicator.Config `mapstructure:",squash"` @@ -141,14 +144,18 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { errs = packersdk.MultiErrorAppend(errs, errors.New("if `user_data` field is set, length of UserData should be max 21847")) } - if c.Comm.Type == "winrm" && c.AccessControlGroupConfigurationNo == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("if Communicator is winrm, `access_control_group_configuration_no` (allow 5986 port) is required")) + if c.AccessControlGroupConfigurationNo != "" { + errs = packersdk.MultiErrorAppend(errs, errors.New("`access_control_group_configuration_no` is deprecated, please use `access_control_group_no` instead")) } if c.VpcNo != "" || c.SubnetNo != "" { c.SupportVPC = true } + if c.Comm.Type == "winrm" && c.AccessControlGroupNo == "" && !c.SupportVPC { + errs = packersdk.MultiErrorAppend(errs, errors.New("if Communicator is winrm, `access_control_group_no` (allow 5986 port) is required in `CLASSIC` environment")) + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } diff --git a/builder/ncloud/config.hcl2spec.go b/builder/ncloud/config.hcl2spec.go index 45c904e26..e5e90f53f 100644 --- a/builder/ncloud/config.hcl2spec.go +++ b/builder/ncloud/config.hcl2spec.go @@ -31,6 +31,7 @@ type FlatConfig struct { Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"` RegionCode *string `mapstructure:"region_code" required:"false" cty:"region_code" hcl:"region_code"` AccessControlGroupConfigurationNo *string `mapstructure:"access_control_group_configuration_no" required:"false" cty:"access_control_group_configuration_no" hcl:"access_control_group_configuration_no"` + AccessControlGroupNo *string `mapstructure:"access_control_group_no" required:"false" cty:"access_control_group_no" hcl:"access_control_group_no"` SupportVPC *bool `mapstructure:"support_vpc" required:"false" cty:"support_vpc" hcl:"support_vpc"` SubnetNo *string `mapstructure:"subnet_no" required:"false" cty:"subnet_no" hcl:"subnet_no"` VpcNo *string `mapstructure:"vpc_no" required:"false" cty:"vpc_no" hcl:"vpc_no"` @@ -118,6 +119,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, "region_code": &hcldec.AttrSpec{Name: "region_code", Type: cty.String, Required: false}, "access_control_group_configuration_no": &hcldec.AttrSpec{Name: "access_control_group_configuration_no", Type: cty.String, Required: false}, + "access_control_group_no": &hcldec.AttrSpec{Name: "access_control_group_no", Type: cty.String, Required: false}, "support_vpc": &hcldec.AttrSpec{Name: "support_vpc", Type: cty.Bool, Required: false}, "subnet_no": &hcldec.AttrSpec{Name: "subnet_no", Type: cty.String, Required: false}, "vpc_no": &hcldec.AttrSpec{Name: "vpc_no", Type: cty.String, Required: false}, diff --git a/builder/ncloud/step_create_access_control_group.go b/builder/ncloud/step_create_access_control_group.go new file mode 100644 index 000000000..90a4f5a3a --- /dev/null +++ b/builder/ncloud/step_create_access_control_group.go @@ -0,0 +1,197 @@ +package ncloud + +import ( + "context" + "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "log" +) + +type StepCreateAccessControlGroup struct { + Conn *NcloudAPIClient + GetAccessControlGroup func(acgNo string) (*vserver.AccessControlGroup, error) + CreateAccessControlGroup func() (string, error) + AddAccessControlGroupRule func(acgNo string) error + Say func(message string) + Error func(e error) + Config *Config + createdAcgNo string +} + +func NewStepCreateAccessControlGroup(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateAccessControlGroup { + var step = &StepCreateAccessControlGroup{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + if config.SupportVPC { + step.GetAccessControlGroup = step.getVpcAccessControlGroup + step.CreateAccessControlGroup = step.createVpcAccessControlGroup + step.AddAccessControlGroupRule = step.addVpcAccessControlGroupRule + } + + return step +} + +func (s *StepCreateAccessControlGroup) createVpcAccessControlGroup() (string, error) { + reqParam := &vserver.CreateAccessControlGroupRequest{ + RegionCode: &s.Config.RegionCode, + AccessControlGroupName: &s.Config.ServerImageName, + AccessControlGroupDescription: ncloud.String("Temporary ACG for packer"), + VpcNo: &s.Config.VpcNo, + } + + resp, err := s.Conn.vserver.V2Api.CreateAccessControlGroup(reqParam) + if err != nil { + return "", err + } + + if resp != nil && *resp.TotalRows > 0 { + return *resp.AccessControlGroupList[0].AccessControlGroupNo, nil + } + + return "", nil +} + +func (s *StepCreateAccessControlGroup) addVpcAccessControlGroupRule(acgNo string) error { + _, err := s.Conn.vserver.V2Api.AddAccessControlGroupInboundRule(&vserver.AddAccessControlGroupInboundRuleRequest{ + RegionCode: &s.Config.RegionCode, + AccessControlGroupNo: &acgNo, + VpcNo: &s.Config.VpcNo, + AccessControlGroupRuleList: []*vserver.AddAccessControlGroupRuleParameter{ + { + IpBlock: ncloud.String("0.0.0.0/0"), + PortRange: ncloud.String("22"), + ProtocolTypeCode: ncloud.String("TCP"), + }, + { + IpBlock: ncloud.String("0.0.0.0/0"), + PortRange: ncloud.String("3389"), + ProtocolTypeCode: ncloud.String("TCP"), + }, + { + IpBlock: ncloud.String("0.0.0.0/0"), + PortRange: ncloud.String("5985"), + ProtocolTypeCode: ncloud.String("TCP"), + }, + }, + }) + if err != nil { + return err + } + + _, err = s.Conn.vserver.V2Api.AddAccessControlGroupOutboundRule(&vserver.AddAccessControlGroupOutboundRuleRequest{ + RegionCode: &s.Config.RegionCode, + AccessControlGroupNo: &acgNo, + VpcNo: &s.Config.VpcNo, + AccessControlGroupRuleList: []*vserver.AddAccessControlGroupRuleParameter{ + { + IpBlock: ncloud.String("0.0.0.0/0"), + ProtocolTypeCode: ncloud.String("ICMP"), + }, + { + IpBlock: ncloud.String("0.0.0.0/0"), + PortRange: ncloud.String("1-65535"), + ProtocolTypeCode: ncloud.String("TCP"), + }, + { + IpBlock: ncloud.String("0.0.0.0/0"), + PortRange: ncloud.String("1-65535"), + ProtocolTypeCode: ncloud.String("UDP"), + }, + }, + }) + if err != nil { + return err + } + + return nil +} + +func (s *StepCreateAccessControlGroup) deleteVpcAccessControlGroup(id string) error { + reqParam := &vserver.DeleteAccessControlGroupRequest{ + RegionCode: &s.Config.RegionCode, + VpcNo: &s.Config.VpcNo, + AccessControlGroupNo: ncloud.String(id), + } + + _, err := s.Conn.vserver.V2Api.DeleteAccessControlGroup(reqParam) + if err != nil { + return err + } + + return nil +} + +func (s *StepCreateAccessControlGroup) getVpcAccessControlGroup(id string) (*vserver.AccessControlGroup, error) { + reqParam := &vserver.GetAccessControlGroupDetailRequest{ + RegionCode: &s.Config.RegionCode, + AccessControlGroupNo: ncloud.String(id), + } + + resp, err := s.Conn.vserver.V2Api.GetAccessControlGroupDetail(reqParam) + if err != nil { + return nil, err + } + + if resp != nil && *resp.TotalRows > 0 { + return resp.AccessControlGroupList[0], nil + } + + return nil, nil +} + +func (s *StepCreateAccessControlGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + s.Say("Create temporary ACG") + if len(s.Config.AccessControlGroupNo) > 0 { + acg, err := s.GetAccessControlGroup(s.Config.AccessControlGroupNo) + if err != nil || acg == nil { + err := fmt.Errorf("couldn't find specified ACG: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + log.Printf("Using specified ACG: %v", s.Config.AccessControlGroupNo) + return multistep.ActionContinue + } + + acgNo, err := s.CreateAccessControlGroup() + s.Say(fmt.Sprintf("Creating temporary ACG [%s]", acgNo)) + if err != nil || len(acgNo) == 0 { + err := fmt.Errorf("couldn't create ACG for VPC: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + s.createdAcgNo = acgNo + + s.Say(fmt.Sprintf("Creating temporary rules ACG [%s]", acgNo)) + err = s.AddAccessControlGroupRule(acgNo) + if err != nil { + err := fmt.Errorf("couldn't create ACG rules for SSH or winrm: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + state.Put("access_control_group_no", acgNo) + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreateAccessControlGroup) Cleanup(state multistep.StateBag) { + if s.createdAcgNo == "" { + return + } + + err := s.deleteVpcAccessControlGroup(s.createdAcgNo) + if err != nil { + s.Error(fmt.Errorf("error cleaning up ACG. Please delete the ACG manually: err: %s; ACG No: %s", err, s.createdAcgNo)) + } + + s.Say("Clean up temporary ACG") +} diff --git a/builder/ncloud/step_create_access_control_group_test.go b/builder/ncloud/step_create_access_control_group_test.go new file mode 100644 index 000000000..4c94e183e --- /dev/null +++ b/builder/ncloud/step_create_access_control_group_test.go @@ -0,0 +1,66 @@ +package ncloud + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +func TestStepCreateAccessControlGroupShouldFailIfOperationCreateAccessControlGroupFails(t *testing.T) { + var testSubject = &StepCreateAccessControlGroup{ + CreateAccessControlGroup: func() (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: &Config{ + Region: "Korea", + SupportVPC: true, + }, + } + + stateBag := createTestStateBagStepCreateAccessControlGroup() + + var result = testSubject.Run(context.Background(), stateBag) + + if result != multistep.ActionHalt { + t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) + } + + if _, ok := stateBag.GetOk("error"); ok == false { + t.Fatal("Expected the step to set stateBag['Error'], but it was not.") + } +} + +func TestStepCreateAccessControlGroupShouldPassIfOperationCreateAccessControlGroupPasses(t *testing.T) { + var testSubject = &StepCreateAccessControlGroup{ + CreateAccessControlGroup: func() (string, error) { return "123", nil }, + AddAccessControlGroupRule: func(acgNo string) error { + return nil + }, + Say: func(message string) {}, + Error: func(error) {}, + Config: &Config{ + Region: "Korea", + SupportVPC: true, + }, + } + + stateBag := createTestStateBagStepCreateAccessControlGroup() + + var result = testSubject.Run(context.Background(), stateBag) + + if result != multistep.ActionContinue { + t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) + } + + if _, ok := stateBag.GetOk("error"); ok == true { + t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") + } +} + +func createTestStateBagStepCreateAccessControlGroup() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + return stateBag +} diff --git a/builder/ncloud/step_create_init_script.go b/builder/ncloud/step_create_init_script.go index f56a1635e..6e523336d 100644 --- a/builder/ncloud/step_create_init_script.go +++ b/builder/ncloud/step_create_init_script.go @@ -92,8 +92,10 @@ func (s *StepCreateInitScript) deleteVpcInitScript(initScriptNo string) error { } func (s *StepCreateInitScript) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + if len(s.Config.UserData) == 0 && len(s.Config.UserDataFile) == 0 { + return multistep.ActionContinue + } s.Say("Create Init script") - initScriptNo, err := s.CreateInitScript() if err == nil && initScriptNo != "" { state.Put("init_script_no", initScriptNo) diff --git a/builder/ncloud/step_create_init_script_test.go b/builder/ncloud/step_create_init_script_test.go index 100031442..a722d40a1 100644 --- a/builder/ncloud/step_create_init_script_test.go +++ b/builder/ncloud/step_create_init_script_test.go @@ -14,7 +14,7 @@ func TestStepCreateInitScriptShouldFailIfOperationCreateInitScriptFails(t *testi Say: func(message string) {}, Error: func(e error) {}, Config: &Config{ - Region: "KR", + Region: "Korea", SupportVPC: true, }, } @@ -38,7 +38,7 @@ func TestStepCreateInitScriptShouldPassIfOperationCreateInitScriptPasses(t *test Say: func(message string) {}, Error: func(e error) {}, Config: &Config{ - Region: "KR", + Region: "Korea", SupportVPC: true, }, } diff --git a/builder/ncloud/step_create_server_instance.go b/builder/ncloud/step_create_server_instance.go index c1f74bd34..f55f75bae 100644 --- a/builder/ncloud/step_create_server_instance.go +++ b/builder/ncloud/step_create_server_instance.go @@ -80,8 +80,8 @@ func (s *StepCreateServerInstance) createClassicServerInstance(loginKeyName stri reqParams.UserData = ncloud.String(string(contents)) } - if s.Config.AccessControlGroupConfigurationNo != "" { - reqParams.AccessControlGroupConfigurationNoList = []*string{&s.Config.AccessControlGroupConfigurationNo} + if s.Config.AccessControlGroupNo != "" { + reqParams.AccessControlGroupConfigurationNoList = []*string{&s.Config.AccessControlGroupNo} } serverInstanceList, err := s.Conn.server.V2Api.CreateServerInstances(reqParams) @@ -111,14 +111,10 @@ func (s *StepCreateServerInstance) createVpcServerInstance(loginKeyName string, initScriptNo = v.(string) } - if s.Config.AccessControlGroupConfigurationNo != "" { - acgNo = s.Config.AccessControlGroupConfigurationNo + if s.Config.AccessControlGroupNo != "" { + acgNo = s.Config.AccessControlGroupNo } else { - acgNo, err = s.getDefaultAccessControlGroup(s.Config.VpcNo) - } - - if err != nil { - return "", err + acgNo = state.Get("access_control_group_no").(string) } reqParams := &vserver.CreateServerInstancesRequest{ @@ -158,31 +154,6 @@ func (s *StepCreateServerInstance) createVpcServerInstance(loginKeyName string, return s.serverInstanceNo, nil } -func (s *StepCreateServerInstance) getDefaultAccessControlGroup(id string) (string, error) { - reqParams := &vserver.GetAccessControlGroupListRequest{ - RegionCode: &s.Config.RegionCode, - VpcNo: ncloud.String(id), - } - - resp, err := s.Conn.vserver.V2Api.GetAccessControlGroupList(reqParams) - - if err != nil { - return "", err - } - - if resp == nil || len(resp.AccessControlGroupList) == 0 { - return "", fmt.Errorf("no matching Access Control Group found") - } - - for _, i := range resp.AccessControlGroupList { - if *i.IsDefault { - return *i.AccessControlGroupNo, nil - } - } - - return "", fmt.Errorf("no matching default Access Control Group found") -} - func (s *StepCreateServerInstance) getClassicServerInstance() (string, string, error) { reqParams := &server.GetServerInstanceListRequest{ ServerInstanceNoList: []*string{&s.serverInstanceNo}, @@ -219,7 +190,7 @@ func (s *StepCreateServerInstance) getVpcServerInstance() (string, string, error func (s *StepCreateServerInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Create Server Instance") - var loginKey = state.Get("login_key").(*LoginKey) + loginKey := state.Get("login_key").(*LoginKey) feeSystemTypeCode := "MTRAT" if _, ok := state.GetOk("fee_system_type_code"); ok { diff --git a/website/content/docs/builders/ncloud.mdx b/website/content/docs/builders/ncloud.mdx index 8add8a251..f1bdafb00 100644 --- a/website/content/docs/builders/ncloud.mdx +++ b/website/content/docs/builders/ncloud.mdx @@ -31,9 +31,8 @@ Platform](https://www.ncloud.com/). - `server_image_product_code` (string) - Product code of an image to create. (member_server_image_no is required if not specified) -- `server_product_code` (string) - Product (spec) code to create. - ### Optional: +- `server_product_code` (string) - Product (spec) code to create. - `vpc_no` (string) - The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`. (You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`) @@ -51,10 +50,10 @@ Platform](https://www.ncloud.com/). - `block_storage_size` (number) - You can add block storage ranging from 10 GB to 2000 GB, in increments of 10 GB. -- `access_control_group_configuration_no` (string) - This is used to allow +- `access_control_group_no` (string) - This is used to allow winrm access when you create a Windows server. An ACG that specifies an access source (`0.0.0.0/0`) and allowed port (5985) must be created in - advance. + advance if you use CLASSIC env. If this field is left blank, Packer will create temporary ACG for automatically in VPC environment. - `user_data` (string) - User data to apply when launching the instance. Note that you need to be careful about escaping characters due to the templates @@ -104,6 +103,7 @@ source "ncloud" "example-windows" { subnet_no = "{{YOUR_SUBNET_ID}}" // Remove this if you use CLASSIC environment. communicator = "winrm" winrm_username = "Administrator" + # access_control_group_no = "{{YOUR_ACG_ID}}" // Specific ACG ID allowed port (5985) if you use CLASSIC environment. } build { @@ -111,7 +111,7 @@ build { provisioner "powershell" { inline = [ - "$Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet /mode:vm \"/unattend:C:\\Program Files (x86)\\example\\nserver64.xml\" " + "Write-Output 1,2,3 | Measure-Object" ] } } @@ -144,7 +144,7 @@ build { { "type": "powershell", "inline": [ - "$Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet /mode:vm \"/unattend:C:\\Program Files (x86)\\NBP\\nserver64.xml\" " + "Write-Output 1,2,3 | Measure-Object" ] } ] @@ -195,7 +195,7 @@ build { provisioner "shell" { inline = [ - "sleep 30", "yum install redis.x86_64 -y" + "echo Connected via SSM at '${build.User}@${build.Host}:${build.Port}'" ] } } @@ -226,7 +226,7 @@ build { { "type": "shell", "inline": [ - "sleep 30", "yum install redis.x86_64 -y" + "echo Connected via SSM at '${build.User}@${build.Host}:${build.Port}'" ] } ] diff --git a/website/content/partials/builder/ncloud/Config-not-required.mdx b/website/content/partials/builder/ncloud/Config-not-required.mdx index 9fe57b65f..1e06b84f7 100644 --- a/website/content/partials/builder/ncloud/Config-not-required.mdx +++ b/website/content/partials/builder/ncloud/Config-not-required.mdx @@ -29,12 +29,15 @@ - `region_code` (string) - Region Code -- `access_control_group_configuration_no` (string) - This is used to allow +- `access_control_group_configuration_no` (string) - Deprecated + +- `access_control_group_no` (string) - This is used to allow winrm access when you create a Windows server. An ACG that specifies an access source (0.0.0.0/0) and allowed port (5985) must be created in - advance. + advance if you use CLASSIC env. If this field is left blank, + Packer will create temporary ACG for automatically in VPC environment. + +- `subnet_no` (string) - The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`. - `vpc_no` (string) - The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`. (You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`) - -- `subnet_no` (string) - The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`.