From cd370aaaaddc624a67495464adba9dfba8290314 Mon Sep 17 00:00:00 2001 From: "sangkyu.kim" Date: Tue, 23 Mar 2021 16:35:36 +0900 Subject: [PATCH] implement vpc environment --- builder/ncloud/builder.go | 89 ++--- builder/ncloud/config.go | 55 ++-- builder/ncloud/config.hcl2spec.go | 8 + builder/ncloud/step.go | 2 +- .../step_create_block_storage_instance.go | 118 +++++-- ...step_create_block_storage_instance_test.go | 26 +- builder/ncloud/step_create_init_script.go | 114 +++++++ .../ncloud/step_create_init_script_test.go | 63 ++++ builder/ncloud/step_create_login_key.go | 66 +++- builder/ncloud/step_create_login_key_test.go | 4 +- .../ncloud/step_create_public_ip_instance.go | 309 ++++++++++++++---- .../step_create_public_ip_instance_test.go | 14 +- builder/ncloud/step_create_server_image.go | 72 +++- .../ncloud/step_create_server_image_test.go | 6 +- builder/ncloud/step_create_server_instance.go | 213 +++++++++--- .../step_create_server_instance_test.go | 18 +- .../step_delete_block_storage_instance.go | 92 ++++-- ...step_delete_block_storage_instance_test.go | 26 +- builder/ncloud/step_get_rootpassword.go | 37 ++- builder/ncloud/step_get_rootpassword_test.go | 8 +- builder/ncloud/step_stop_server_instance.go | 63 +++- .../ncloud/step_stop_server_instance_test.go | 6 +- .../ncloud/step_terminate_server_instance.go | 80 ++++- .../step_terminate_server_instance_test.go | 6 +- builder/ncloud/step_validate_template.go | 306 ++++++++++++++--- builder/ncloud/step_validate_template_test.go | 4 +- .../ncloud/waiter_block_storage_instance.go | 87 ++++- builder/ncloud/waiter_server_image_status.go | 43 ++- .../ncloud/waiter_server_instance_status.go | 38 ++- go.mod | 2 +- go.sum | 2 + .../builder/ncloud/Config-not-required.mdx | 8 + 32 files changed, 1603 insertions(+), 382 deletions(-) create mode 100644 builder/ncloud/step_create_init_script.go create mode 100644 builder/ncloud/step_create_init_script_test.go diff --git a/builder/ncloud/builder.go b/builder/ncloud/builder.go index d21efd465..644da6edf 100644 --- a/builder/ncloud/builder.go +++ b/builder/ncloud/builder.go @@ -32,7 +32,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { } func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) { - ui.Message("Creating Naver Cloud Platform Connection ...") + ui.Message("Creating NAVER CLOUD PLATFORM Connection ...") config := Config{ AccessKey: b.config.AccessKey, SecretKey: b.config.SecretKey, @@ -46,60 +46,41 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) b.stateBag.Put("hook", hook) b.stateBag.Put("ui", ui) - var steps []multistep.Step - - steps = []multistep.Step{} - - if b.config.Comm.Type == "ssh" { - steps = []multistep.Step{ - NewStepValidateTemplate(conn, ui, &b.config), - NewStepCreateLoginKey(conn, ui), - NewStepCreateServerInstance(conn, ui, &b.config), - NewStepCreateBlockStorageInstance(conn, ui, &b.config), - NewStepGetRootPassword(conn, ui, &b.config), - NewStepCreatePublicIPInstance(conn, ui, &b.config), - &communicator.StepConnectSSH{ - Config: &b.config.Comm, - Host: func(stateBag multistep.StateBag) (string, error) { - return stateBag.Get("PublicIP").(string), nil - }, - SSHConfig: b.config.Comm.SSHConfigFunc(), + steps := []multistep.Step{ + NewStepValidateTemplate(conn, ui, &b.config), + NewStepCreateLoginKey(conn, ui, &b.config), + multistep.If(b.config.SupportVPC, NewStepCreateInitScript(conn, ui, &b.config)), + NewStepCreateServerInstance(conn, ui, &b.config), + NewStepCreateBlockStorage(conn, ui, &b.config), + NewStepGetRootPassword(conn, ui, &b.config), + NewStepCreatePublicIP(conn, ui, &b.config), + multistep.If(b.config.Comm.Type == "ssh", &communicator.StepConnectSSH{ + Config: &b.config.Comm, + Host: func(stateBag multistep.StateBag) (string, error) { + return stateBag.Get("public_ip").(string), nil }, - &commonsteps.StepProvision{}, - &commonsteps.StepCleanupTempKeys{ - Comm: &b.config.Comm, + SSHConfig: b.config.Comm.SSHConfigFunc(), + }), + multistep.If(b.config.Comm.Type == "winrm", &communicator.StepConnectWinRM{ + Config: &b.config.Comm, + Host: func(stateBag multistep.StateBag) (string, error) { + return stateBag.Get("public_ip").(string), nil }, - NewStepStopServerInstance(conn, ui), - NewStepCreateServerImage(conn, ui, &b.config), - NewStepDeleteBlockStorageInstance(conn, ui, &b.config), - NewStepTerminateServerInstance(conn, ui), - } - } else if b.config.Comm.Type == "winrm" { - steps = []multistep.Step{ - NewStepValidateTemplate(conn, ui, &b.config), - NewStepCreateLoginKey(conn, ui), - NewStepCreateServerInstance(conn, ui, &b.config), - NewStepCreateBlockStorageInstance(conn, ui, &b.config), - NewStepGetRootPassword(conn, ui, &b.config), - NewStepCreatePublicIPInstance(conn, ui, &b.config), - &communicator.StepConnectWinRM{ - Config: &b.config.Comm, - Host: func(stateBag multistep.StateBag) (string, error) { - return stateBag.Get("PublicIP").(string), nil - }, - WinRMConfig: func(state multistep.StateBag) (*communicator.WinRMConfig, error) { - return &communicator.WinRMConfig{ - Username: b.config.Comm.WinRMUser, - Password: b.config.Comm.WinRMPassword, - }, nil - }, + WinRMConfig: func(state multistep.StateBag) (*communicator.WinRMConfig, error) { + return &communicator.WinRMConfig{ + Username: b.config.Comm.WinRMUser, + Password: b.config.Comm.WinRMPassword, + }, nil }, - &commonsteps.StepProvision{}, - NewStepStopServerInstance(conn, ui), - NewStepCreateServerImage(conn, ui, &b.config), - NewStepDeleteBlockStorageInstance(conn, ui, &b.config), - NewStepTerminateServerInstance(conn, ui), - } + }), + &commonsteps.StepProvision{}, + multistep.If(b.config.Comm.Type == "ssh", &commonsteps.StepCleanupTempKeys{ + Comm: &b.config.Comm, + }), + NewStepStopServerInstance(conn, ui, &b.config), + NewStepCreateServerImage(conn, ui, &b.config), + NewStepDeleteBlockStorage(conn, ui, &b.config), + NewStepTerminateServerInstance(conn, ui, &b.config), } // Run! @@ -107,7 +88,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) b.runner.Run(ctx, b.stateBag) // If there was an error, return that - if rawErr, ok := b.stateBag.GetOk("Error"); ok { + if rawErr, ok := b.stateBag.GetOk("error"); ok { return nil, rawErr.(error) } @@ -116,7 +97,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) StateData: map[string]interface{}{"generated_data": b.stateBag.Get("generated_data")}, } - if serverImage, ok := b.stateBag.GetOk("memberServerImage"); ok { + if serverImage, ok := b.stateBag.GetOk("member_server_image"); ok { artifact.MemberServerImage = serverImage.(*server.MemberServerImage) } diff --git a/builder/ncloud/config.go b/builder/ncloud/config.go index fbb7d5af2..b30f0449a 100644 --- a/builder/ncloud/config.go +++ b/builder/ncloud/config.go @@ -6,6 +6,8 @@ package ncloud import ( "errors" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vpc" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "os" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud" @@ -50,12 +52,19 @@ type Config struct { BlockStorageSize int `mapstructure:"block_storage_size" required:"false"` // Name of the region where you want to create an image. // (default: Korea) - Region string `mapstructure:"region" required:"false"` + Region string `mapstructure:"region" required:"false"` + RegionCode string `mapstructure:"region_code" 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. + SupportVPC bool `mapstructure:"support_vpc" required:"false"` + // The ID of the associated Subnet + SubnetNo string `mapstructure:"subnet_no" required:"false"` + // The ID of the VPC where you want to place the Server Instance + VpcNo string `mapstructure:"vpc_no" required:"false"` Comm communicator.Config `mapstructure:",squash"` ctx *interpolate.Context @@ -63,7 +72,7 @@ type Config struct { // NewConfig checks parameters func (c *Config) Prepare(raws ...interface{}) ([]string, error) { - warnings := []string{} + var warnings []string err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, @@ -81,59 +90,63 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { } if c.AccessKey == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("access_key is required")) + errs = packersdk.MultiErrorAppend(errs, errors.New("`access_key` is required")) } if c.SecretKey == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("secret_key is required")) + errs = packersdk.MultiErrorAppend(errs, errors.New("`secret_key` is required")) } if c.MemberServerImageNo == "" && c.ServerImageProductCode == "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("server_image_product_code or member_server_image_no is required")) + errs = packersdk.MultiErrorAppend(errs, errors.New("`server_image_product_code` or `member_server_image_no` is required")) } if c.MemberServerImageNo != "" && c.ServerImageProductCode != "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("Only one of server_image_product_code and member_server_image_no can be set")) + errs = packersdk.MultiErrorAppend(errs, errors.New("only one of `server_image_product_code` and `member_server_image_no` can be set")) } - if c.ServerImageProductCode != "" && len(c.ServerImageProductCode) > 20 { - errs = packersdk.MultiErrorAppend(errs, errors.New("If server_image_product_code field is set, length of server_image_product_code should be max 20")) + if c.ServerImageProductCode != "" && len(c.ServerImageProductCode) > 50 { + errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_image_product_code` field is set, length of `server_image_product_code` should be max 20")) } - if c.ServerProductCode != "" && len(c.ServerProductCode) > 20 { - errs = packersdk.MultiErrorAppend(errs, errors.New("If server_product_code field is set, length of server_product_code should be max 20")) + if c.ServerProductCode != "" && len(c.ServerProductCode) > 50 { + errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_product_code` field is set, length of `server_product_code` should be max 20")) } if c.ServerImageName != "" && (len(c.ServerImageName) < 3 || len(c.ServerImageName) > 30) { - errs = packersdk.MultiErrorAppend(errs, errors.New("If server_image_name field is set, length of server_image_name should be min 3 and max 20")) + errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_image_name` field is set, length of `server_image_name` should be min 3 and max 20")) } if c.ServerImageDescription != "" && len(c.ServerImageDescription) > 1000 { - errs = packersdk.MultiErrorAppend(errs, errors.New("If server_image_description field is set, length of server_image_description should be max 1000")) + errs = packersdk.MultiErrorAppend(errs, errors.New("if `server_image_description` field is set, length of `server_image_description` should be max 1000")) } if c.BlockStorageSize != 0 { if c.BlockStorageSize < 10 || c.BlockStorageSize > 2000 { - errs = packersdk.MultiErrorAppend(errs, errors.New("The size of BlockStorageSize is at least 10 GB and up to 2000GB")) + errs = packersdk.MultiErrorAppend(errs, errors.New("the size of `block_storage_size` is at least 10 GB and up to 2000GB")) } else if int(c.BlockStorageSize/10)*10 != c.BlockStorageSize { return nil, errors.New("BlockStorageSize must be a multiple of 10 GB") } } if c.UserData != "" && c.UserDataFile != "" { - errs = packersdk.MultiErrorAppend(errs, errors.New("Only one of user_data or user_data_file can be specified.")) + errs = packersdk.MultiErrorAppend(errs, errors.New("only one of user_data or `user_data`_file can be specified.")) } else if c.UserDataFile != "" { if _, err := os.Stat(c.UserDataFile); err != nil { - errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile)) + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`user_data_file` not found: %s", c.UserDataFile)) } } if c.UserData != "" && len(c.UserData) > 21847 { - errs = packersdk.MultiErrorAppend(errs, errors.New("If user_data field is set, length of UserData should be max 21847")) + 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 is required")) + errs = packersdk.MultiErrorAppend(errs, errors.New("if Communicator is winrm, `access_control_group_configuration_no` (allow 5986 port) is required")) + } + + if c.VpcNo != "" || c.SubnetNo != "" { + c.SupportVPC = true } if errs != nil && len(errs.Errors) > 0 { @@ -144,7 +157,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { } type NcloudAPIClient struct { - server *server.APIClient + server *server.APIClient + vserver *vserver.APIClient + vpc *vpc.APIClient } func (c *Config) Client() (*NcloudAPIClient, error) { @@ -153,6 +168,8 @@ func (c *Config) Client() (*NcloudAPIClient, error) { SecretKey: c.SecretKey, } return &NcloudAPIClient{ - server: server.NewAPIClient(server.NewConfiguration(apiKey)), + server: server.NewAPIClient(server.NewConfiguration(apiKey)), + vserver: vserver.NewAPIClient(vserver.NewConfiguration(apiKey)), + vpc: vpc.NewAPIClient(vpc.NewConfiguration(apiKey)), }, nil } diff --git a/builder/ncloud/config.hcl2spec.go b/builder/ncloud/config.hcl2spec.go index 8335d7639..45c904e26 100644 --- a/builder/ncloud/config.hcl2spec.go +++ b/builder/ncloud/config.hcl2spec.go @@ -29,7 +29,11 @@ type FlatConfig struct { UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file" hcl:"user_data_file"` BlockStorageSize *int `mapstructure:"block_storage_size" required:"false" cty:"block_storage_size" hcl:"block_storage_size"` 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"` + 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"` Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` @@ -112,7 +116,11 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false}, "block_storage_size": &hcldec.AttrSpec{Name: "block_storage_size", Type: cty.Number, Required: false}, "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}, + "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}, "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, diff --git a/builder/ncloud/step.go b/builder/ncloud/step.go index 57599e745..1272641d8 100644 --- a/builder/ncloud/step.go +++ b/builder/ncloud/step.go @@ -6,7 +6,7 @@ import ( func processStepResult(err error, sayError func(error), state multistep.StateBag) multistep.StepAction { if err != nil { - state.Put("Error", err) + state.Put("error", err) sayError(err) return multistep.ActionHalt diff --git a/builder/ncloud/step_create_block_storage_instance.go b/builder/ncloud/step_create_block_storage_instance.go index c3c1d591c..878303637 100644 --- a/builder/ncloud/step_create_block_storage_instance.go +++ b/builder/ncloud/step_create_block_storage_instance.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "log" "time" @@ -13,34 +14,49 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) +const ( + BlockStorageStatusAttached = "ATTAC" + BlockStorageStatusDetached = "DETAC" +) + // StepCreateBlockStorageInstance struct is for making extra block storage -type StepCreateBlockStorageInstance struct { - Conn *NcloudAPIClient - CreateBlockStorageInstance func(serverInstanceNo string) (*string, error) - Say func(message string) - Error func(e error) - Config *Config +type StepCreateBlockStorage struct { + Conn *NcloudAPIClient + CreateBlockStorage func(serverInstanceNo string) (*string, error) + DeleteBlockStorage func(blockStorageInstanceNo string) error + WaiterBlockStorageStatus func(conn *NcloudAPIClient, blockStorageInstanceNo *string, status string, timeout time.Duration) error + Say func(message string) + Error func(e error) + Config *Config } // NewStepCreateBlockStorageInstance make StepCreateBlockStorage struct to make extra block storage -func NewStepCreateBlockStorageInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateBlockStorageInstance { - var step = &StepCreateBlockStorageInstance{ +func NewStepCreateBlockStorage(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateBlockStorage { + var step = &StepCreateBlockStorage{ Conn: conn, Say: func(message string) { ui.Say(message) }, Error: func(e error) { ui.Error(e.Error()) }, Config: config, } - step.CreateBlockStorageInstance = step.createBlockStorageInstance + if config.SupportVPC { + step.CreateBlockStorage = step.createVpcBlockStorage + step.DeleteBlockStorage = step.deleteVpcBlockStorage + step.WaiterBlockStorageStatus = waiterVpcBlockStorageStatus + } else { + step.CreateBlockStorage = step.createClassicBlockStorage + step.DeleteBlockStorage = step.deleteClassicBlockStorage + step.WaiterBlockStorageStatus = waiterClassicBlockStorageStatus + } return step } -func (s *StepCreateBlockStorageInstance) createBlockStorageInstance(serverInstanceNo string) (*string, error) { - - reqParams := new(server.CreateBlockStorageInstanceRequest) - reqParams.BlockStorageSize = ncloud.Int64(int64(s.Config.BlockStorageSize)) - reqParams.ServerInstanceNo = &serverInstanceNo +func (s *StepCreateBlockStorage) createClassicBlockStorage(serverInstanceNo string) (*string, error) { + reqParams := &server.CreateBlockStorageInstanceRequest{ + BlockStorageSize: ncloud.Int64(int64(s.Config.BlockStorageSize)), + ServerInstanceNo: &serverInstanceNo, + } resp, err := s.Conn.server.V2Api.CreateBlockStorageInstance(reqParams) if err != nil { @@ -50,31 +66,82 @@ func (s *StepCreateBlockStorageInstance) createBlockStorageInstance(serverInstan blockStorageInstance := resp.BlockStorageInstanceList[0] log.Println("Block Storage Instance information : ", blockStorageInstance.BlockStorageInstanceNo) - if err := waiterBlockStorageInstanceStatus(s.Conn, blockStorageInstance.BlockStorageInstanceNo, "ATTAC", 10*time.Minute); err != nil { + if err := waiterClassicBlockStorageStatus(s.Conn, blockStorageInstance.BlockStorageInstanceNo, BlockStorageStatusAttached, 10*time.Minute); err != nil { return nil, errors.New("TIMEOUT : Block Storage instance status is not attached") } return blockStorageInstance.BlockStorageInstanceNo, nil } -func (s *StepCreateBlockStorageInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepCreateBlockStorage) createVpcBlockStorage(serverInstanceNo string) (*string, error) { + reqParams := &vserver.CreateBlockStorageInstanceRequest{ + RegionCode: &s.Config.RegionCode, + BlockStorageSize: ncloud.Int32(int32(s.Config.BlockStorageSize)), + BlockStorageDescription: nil, + ServerInstanceNo: &serverInstanceNo, + BlockStorageSnapshotInstanceNo: nil, + ZoneCode: nil, + } + + resp, err := s.Conn.vserver.V2Api.CreateBlockStorageInstance(reqParams) + if err != nil { + return nil, err + } + + blockStorageInstance := resp.BlockStorageInstanceList[0] + log.Println("Block Storage Instance information : ", blockStorageInstance.BlockStorageInstanceNo) + + if err := s.WaiterBlockStorageStatus(s.Conn, blockStorageInstance.BlockStorageInstanceNo, BlockStorageStatusAttached, 10*time.Minute); err != nil { + return nil, errors.New("TIMEOUT : Block Storage instance status is not attached") + } + + return blockStorageInstance.BlockStorageInstanceNo, nil +} + +func (s *StepCreateBlockStorage) deleteClassicBlockStorage(blockStorageInstanceNo string) error { + reqParams := &server.DeleteBlockStorageInstancesRequest{ + BlockStorageInstanceNoList: []*string{&blockStorageInstanceNo}, + } + + _, err := s.Conn.server.V2Api.DeleteBlockStorageInstances(reqParams) + if err != nil { + return err + } + + return nil +} + +func (s *StepCreateBlockStorage) deleteVpcBlockStorage(blockStorageInstanceNo string) error { + reqParams := &vserver.DeleteBlockStorageInstancesRequest{ + BlockStorageInstanceNoList: []*string{&blockStorageInstanceNo}, + } + + _, err := s.Conn.vserver.V2Api.DeleteBlockStorageInstances(reqParams) + if err != nil { + return err + } + + return nil +} + +func (s *StepCreateBlockStorage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { if s.Config.BlockStorageSize == 0 { return processStepResult(nil, s.Error, state) } s.Say("Create extra block storage instance") - serverInstanceNo := state.Get("InstanceNo").(string) + serverInstanceNo := state.Get("instance_no").(string) - blockStorageInstanceNo, err := s.CreateBlockStorageInstance(serverInstanceNo) + blockStorageInstanceNo, err := s.CreateBlockStorage(serverInstanceNo) if err == nil { - state.Put("BlockStorageInstanceNo", *blockStorageInstanceNo) + state.Put("block_storage_instance_no", *blockStorageInstanceNo) } return processStepResult(err, s.Error, state) } -func (s *StepCreateBlockStorageInstance) Cleanup(state multistep.StateBag) { +func (s *StepCreateBlockStorage) Cleanup(state multistep.StateBag) { _, cancelled := state.GetOk(multistep.StateCancelled) _, halted := state.GetOk(multistep.StateHalted) @@ -86,20 +153,17 @@ func (s *StepCreateBlockStorageInstance) Cleanup(state multistep.StateBag) { return } - if blockStorageInstanceNo, ok := state.GetOk("BlockStorageInstanceNo"); ok { + if blockStorageInstanceNo, ok := state.GetOk("block_storage_instance_no"); ok { s.Say("Clean up Block Storage Instance") - reqParams := server.DeleteBlockStorageInstancesRequest{ - BlockStorageInstanceNoList: []*string{blockStorageInstanceNo.(*string)}, - } - blockStorageInstanceList, err := s.Conn.server.V2Api.DeleteBlockStorageInstances(&reqParams) + err := s.DeleteBlockStorage(blockStorageInstanceNo.(string)) if err != nil { + s.Error(err) return } s.Say(fmt.Sprintf("Block Storage Instance is deleted. Block Storage InstanceNo is %s", blockStorageInstanceNo.(string))) - log.Println("Block Storage Instance information : ", blockStorageInstanceList.BlockStorageInstanceList[0]) - if err := waiterBlockStorageInstanceStatus(s.Conn, blockStorageInstanceNo.(*string), "DETAC", time.Minute); err != nil { + if err := s.WaiterBlockStorageStatus(s.Conn, blockStorageInstanceNo.(*string), BlockStorageStatusDetached, time.Minute); err != nil { s.Say("TIMEOUT : Block Storage instance status is not deattached") } } diff --git a/builder/ncloud/step_create_block_storage_instance_test.go b/builder/ncloud/step_create_block_storage_instance_test.go index 0a79684eb..c4517146d 100644 --- a/builder/ncloud/step_create_block_storage_instance_test.go +++ b/builder/ncloud/step_create_block_storage_instance_test.go @@ -10,11 +10,11 @@ import ( func TestStepCreateBlockStorageInstanceShouldFailIfOperationCreateBlockStorageInstanceFails(t *testing.T) { - var testSubject = &StepCreateBlockStorageInstance{ - CreateBlockStorageInstance: func(serverInstanceNo string) (*string, error) { return nil, fmt.Errorf("!! Unit Test FAIL !!") }, - Say: func(message string) {}, - Error: func(e error) {}, - Config: new(Config), + var testSubject = &StepCreateBlockStorage{ + CreateBlockStorage: func(serverInstanceNo string) (*string, error) { return nil, fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: new(Config), } testSubject.Config.BlockStorageSize = 10 @@ -27,18 +27,18 @@ func TestStepCreateBlockStorageInstanceShouldFailIfOperationCreateBlockStorageIn t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } func TestStepCreateBlockStorageInstanceShouldPassIfOperationCreateBlockStorageInstancePasses(t *testing.T) { var instanceNo = "a" - var testSubject = &StepCreateBlockStorageInstance{ - CreateBlockStorageInstance: func(serverInstanceNo string) (*string, error) { return &instanceNo, nil }, - Say: func(message string) {}, - Error: func(e error) {}, - Config: new(Config), + var testSubject = &StepCreateBlockStorage{ + CreateBlockStorage: func(serverInstanceNo string) (*string, error) { return &instanceNo, nil }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: new(Config), } testSubject.Config.BlockStorageSize = 10 @@ -51,7 +51,7 @@ func TestStepCreateBlockStorageInstanceShouldPassIfOperationCreateBlockStorageIn t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -59,7 +59,7 @@ func TestStepCreateBlockStorageInstanceShouldPassIfOperationCreateBlockStorageIn func createTestStateBagStepCreateBlockStorageInstance() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("InstanceNo", "a") + stateBag.Put("instance_no", "a") return stateBag } diff --git a/builder/ncloud/step_create_init_script.go b/builder/ncloud/step_create_init_script.go new file mode 100644 index 000000000..f56a1635e --- /dev/null +++ b/builder/ncloud/step_create_init_script.go @@ -0,0 +1,114 @@ +package ncloud + +import ( + "context" + "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud" + "io/ioutil" + "time" + + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type InitScript struct { + KeyName string + PrivateKey string +} + +type StepCreateInitScript struct { + Conn *NcloudAPIClient + CreateInitScript func() (string, error) + DeleteInitScript func(initScriptNo string) error + Say func(message string) + Error func(e error) + Config *Config +} + +func NewStepCreateInitScript(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateInitScript { + var step = &StepCreateInitScript{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + if config.SupportVPC { + step.CreateInitScript = step.createVpcInitScript + step.DeleteInitScript = step.deleteVpcInitScript + } + + return step +} + +func (s *StepCreateInitScript) createVpcInitScript() (string, error) { + name := fmt.Sprintf("packer-%d", time.Now().Unix()) + reqParams := &vserver.CreateInitScriptRequest{ + RegionCode: &s.Config.RegionCode, + InitScriptName: &name, + } + + if s.Config.Comm.Type == "winrm" { + reqParams.OsTypeCode = ncloud.String("WND") + } + + if s.Config.UserData != "" { + reqParams.InitScriptContent = &s.Config.UserData + } + if s.Config.UserDataFile != "" { + contents, err := ioutil.ReadFile(s.Config.UserDataFile) + if err != nil { + return "", fmt.Errorf("Problem reading user data file: %s", err) + } + + reqParams.InitScriptContent = ncloud.String(string(contents)) + } + + if reqParams.InitScriptContent == nil { + return "", nil + } + + resp, err := s.Conn.vserver.V2Api.CreateInitScript(reqParams) + if err != nil { + return "", err + } + + return *resp.InitScriptList[0].InitScriptNo, nil +} + +func (s *StepCreateInitScript) deleteVpcInitScript(initScriptNo string) error { + reqParams := &vserver.DeleteInitScriptsRequest{ + RegionCode: &s.Config.RegionCode, + InitScriptNoList: []*string{&initScriptNo}, + } + _, err := s.Conn.vserver.V2Api.DeleteInitScripts(reqParams) + + if err != nil { + return err + } + + return nil +} + +func (s *StepCreateInitScript) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + s.Say("Create Init script") + + initScriptNo, err := s.CreateInitScript() + if err == nil && initScriptNo != "" { + state.Put("init_script_no", initScriptNo) + s.Say(fmt.Sprintf("Init script[%s] is created", initScriptNo)) + } + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreateInitScript) Cleanup(state multistep.StateBag) { + if initScriptNo, ok := state.GetOk("init_script_no"); ok { + s.Say("Cleanup Init script") + if err := s.DeleteInitScript(initScriptNo.(string)); err != nil { + s.Error(err) + return + } + } +} diff --git a/builder/ncloud/step_create_init_script_test.go b/builder/ncloud/step_create_init_script_test.go new file mode 100644 index 000000000..100031442 --- /dev/null +++ b/builder/ncloud/step_create_init_script_test.go @@ -0,0 +1,63 @@ +package ncloud + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +func TestStepCreateInitScriptShouldFailIfOperationCreateInitScriptFails(t *testing.T) { + var testSubject = &StepCreateInitScript{ + CreateInitScript: func() (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: &Config{ + Region: "KR", + SupportVPC: true, + }, + } + + stateBag := createTestStateBagStepCreateInitScript() + + 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 TestStepCreateInitScriptShouldPassIfOperationCreateInitScriptPasses(t *testing.T) { + var testSubject = &StepCreateInitScript{ + CreateInitScript: func() (string, error) { return "123", nil }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: &Config{ + Region: "KR", + SupportVPC: true, + }, + } + + stateBag := createTestStateBagStepCreateInitScript() + + 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 createTestStateBagStepCreateInitScript() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + return stateBag +} diff --git a/builder/ncloud/step_create_login_key.go b/builder/ncloud/step_create_login_key.go index 9283faa63..f9bf00833 100644 --- a/builder/ncloud/step_create_login_key.go +++ b/builder/ncloud/step_create_login_key.go @@ -6,6 +6,7 @@ import ( "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) @@ -18,23 +19,32 @@ type LoginKey struct { type StepCreateLoginKey struct { Conn *NcloudAPIClient CreateLoginKey func() (*LoginKey, error) + DeleteLoginKey func(loginKey string) error Say func(message string) Error func(e error) + Config *Config } -func NewStepCreateLoginKey(conn *NcloudAPIClient, ui packersdk.Ui) *StepCreateLoginKey { +func NewStepCreateLoginKey(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateLoginKey { var step = &StepCreateLoginKey{ - Conn: conn, - Say: func(message string) { ui.Say(message) }, - Error: func(e error) { ui.Error(e.Error()) }, + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, } - step.CreateLoginKey = step.createLoginKey + if config.SupportVPC { + step.CreateLoginKey = step.createVpcLoginKey + step.DeleteLoginKey = step.deleteVpcLoginKey + } else { + step.CreateLoginKey = step.createClassicLoginKey + step.DeleteLoginKey = step.deleteClassicLoginKey + } return step } -func (s *StepCreateLoginKey) createLoginKey() (*LoginKey, error) { +func (s *StepCreateLoginKey) createClassicLoginKey() (*LoginKey, error) { keyName := fmt.Sprintf("packer-%d", time.Now().Unix()) reqParams := &server.CreateLoginKeyRequest{KeyName: &keyName} @@ -46,12 +56,46 @@ func (s *StepCreateLoginKey) createLoginKey() (*LoginKey, error) { return &LoginKey{keyName, *privateKey.PrivateKey}, nil } +func (s *StepCreateLoginKey) createVpcLoginKey() (*LoginKey, error) { + keyName := fmt.Sprintf("packer-%d", time.Now().Unix()) + reqParams := &vserver.CreateLoginKeyRequest{KeyName: &keyName} + + privateKey, err := s.Conn.vserver.V2Api.CreateLoginKey(reqParams) + if err != nil { + return nil, err + } + + return &LoginKey{keyName, *privateKey.PrivateKey}, nil +} + +func (s *StepCreateLoginKey) deleteClassicLoginKey(keyName string) error { + reqParams := &server.DeleteLoginKeyRequest{KeyName: &keyName} + _, err := s.Conn.server.V2Api.DeleteLoginKey(reqParams) + + if err != nil { + return err + } + + return nil +} + +func (s *StepCreateLoginKey) deleteVpcLoginKey(keyName string) error { + reqParams := &vserver.DeleteLoginKeysRequest{KeyNameList: []*string{&keyName}} + _, err := s.Conn.vserver.V2Api.DeleteLoginKeys(reqParams) + + if err != nil { + return err + } + + return nil +} + func (s *StepCreateLoginKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Create Login Key") loginKey, err := s.CreateLoginKey() if err == nil { - state.Put("LoginKey", loginKey) + state.Put("login_key", loginKey) s.Say(fmt.Sprintf("Login Key[%s] is created", loginKey.KeyName)) } @@ -59,9 +103,11 @@ func (s *StepCreateLoginKey) Run(ctx context.Context, state multistep.StateBag) } func (s *StepCreateLoginKey) Cleanup(state multistep.StateBag) { - if loginKey, ok := state.GetOk("LoginKey"); ok { + if loginKey, ok := state.GetOk("login_key"); ok { s.Say("Clean up login key") - reqParams := &server.DeleteLoginKeyRequest{KeyName: &loginKey.(*LoginKey).KeyName} - s.Conn.server.V2Api.DeleteLoginKey(reqParams) + if err := s.DeleteLoginKey(loginKey.(*LoginKey).KeyName); err != nil { + s.Error(err) + return + } } } diff --git a/builder/ncloud/step_create_login_key_test.go b/builder/ncloud/step_create_login_key_test.go index 2e2003eb2..1e3e41a00 100644 --- a/builder/ncloud/step_create_login_key_test.go +++ b/builder/ncloud/step_create_login_key_test.go @@ -23,7 +23,7 @@ func TestStepCreateLoginKeyShouldFailIfOperationCreateLoginKeyFails(t *testing.T t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -43,7 +43,7 @@ func TestStepCreateLoginKeyShouldPassIfOperationCreateLoginKeyPasses(t *testing. t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } diff --git a/builder/ncloud/step_create_public_ip_instance.go b/builder/ncloud/step_create_public_ip_instance.go index cf6623d76..646f852cd 100644 --- a/builder/ncloud/step_create_public_ip_instance.go +++ b/builder/ncloud/step_create_public_ip_instance.go @@ -3,6 +3,7 @@ package ncloud import ( "context" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "log" "time" @@ -12,32 +13,103 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) -type StepCreatePublicIPInstance struct { +const ( + PublicIpStatusUsed = "USED" + PublicIpStatusCreated = "CREAT" + PublicIpStatusRunning = "RUN" +) + +type StepCreatePublicIP struct { Conn *NcloudAPIClient - CreatePublicIPInstance func(serverInstanceNo string) (*server.PublicIpInstance, error) + GetPublicIP func(publicIPNo string) (*server.PublicIpInstance, error) + CreatePublicIP func(serverInstanceNo string) (*server.PublicIpInstance, error) + DeletePublicIp func(publicIPNo string) error WaiterAssociatePublicIPToServerInstance func(serverInstanceNo string, publicIP string) error + DisassociatePublicIpFromServerInstance func(publicIPNo string) error Say func(message string) Error func(e error) Config *Config } -func NewStepCreatePublicIPInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreatePublicIPInstance { - var step = &StepCreatePublicIPInstance{ +func NewStepCreatePublicIP(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreatePublicIP { + var step = &StepCreatePublicIP{ Conn: conn, Say: func(message string) { ui.Say(message) }, Error: func(e error) { ui.Error(e.Error()) }, Config: config, } - step.CreatePublicIPInstance = step.createPublicIPInstance - step.WaiterAssociatePublicIPToServerInstance = step.waiterAssociatePublicIPToServerInstance + if config.SupportVPC { + step.GetPublicIP = step.getVpcPublicIP + step.CreatePublicIP = step.createVpcPublicIP + step.WaiterAssociatePublicIPToServerInstance = step.waiterAssociateVpcPublicIPToServerInstance + step.DisassociatePublicIpFromServerInstance = step.disassociateVpcPublicIpFromServerInstance + step.DeletePublicIp = step.deleteVpcPublicIp + } else { + step.GetPublicIP = step.getClassicPublicIP + step.CreatePublicIP = step.createClassicPublicIP + step.WaiterAssociatePublicIPToServerInstance = step.waiterAssociateClassicPublicIPToServerInstance + step.DisassociatePublicIpFromServerInstance = step.disassociateClassicPublicIpFromServerInstance + step.DeletePublicIp = step.deleteClassicPublicIp + } return step } -func (s *StepCreatePublicIPInstance) waiterAssociatePublicIPToServerInstance(serverInstanceNo string, publicIP string) error { - reqParams := new(server.GetServerInstanceListRequest) - reqParams.ServerInstanceNoList = []*string{&serverInstanceNo} +func (s *StepCreatePublicIP) getClassicPublicIP(publicIPNo string) (*server.PublicIpInstance, error) { + reqParams := &server.GetPublicIpInstanceListRequest{ + PublicIpInstanceNoList: []*string{&publicIPNo}, + } + + resp, err := s.Conn.server.V2Api.GetPublicIpInstanceList(reqParams) + if err != nil { + return nil, err + } + + if resp != nil && *resp.TotalRows > 0 { + return resp.PublicIpInstanceList[0], nil + } + + return nil, nil +} + +func (s *StepCreatePublicIP) getVpcPublicIP(publicIPNo string) (*server.PublicIpInstance, error) { + reqParams := &vserver.GetPublicIpInstanceDetailRequest{ + RegionCode: &s.Config.RegionCode, + PublicIpInstanceNo: &publicIPNo, + } + + resp, err := s.Conn.vserver.V2Api.GetPublicIpInstanceDetail(reqParams) + if err != nil { + return nil, err + } + + if resp != nil && *resp.TotalRows > 0 { + inst := resp.PublicIpInstanceList[0] + return &server.PublicIpInstance{ + PublicIpInstanceNo: inst.PublicIpInstanceNo, + PublicIp: inst.PublicIp, + PublicIpInstanceStatus: &server.CommonCode{ + Code: inst.PublicIpInstanceStatus.Code, + CodeName: inst.PublicIpInstanceStatus.CodeName, + }, + PublicIpInstanceOperation: &server.CommonCode{ + Code: inst.PublicIpInstanceOperation.Code, + CodeName: inst.PublicIpInstanceOperation.CodeName, + }, + ServerInstanceAssociatedWithPublicIp: &server.ServerInstance{ + ServerInstanceNo: inst.ServerInstanceNo, + }, + }, nil + } + + return nil, nil +} + +func (s *StepCreatePublicIP) waiterAssociateClassicPublicIPToServerInstance(serverInstanceNo string, publicIP string) error { + reqParams := &server.GetServerInstanceListRequest{ + ServerInstanceNoList: []*string{&serverInstanceNo}, + } c1 := make(chan error, 1) @@ -68,9 +140,45 @@ func (s *StepCreatePublicIPInstance) waiterAssociatePublicIPToServerInstance(ser } } -func (s *StepCreatePublicIPInstance) createPublicIPInstance(serverInstanceNo string) (*server.PublicIpInstance, error) { - reqParams := new(server.CreatePublicIpInstanceRequest) - reqParams.ServerInstanceNo = &serverInstanceNo +func (s *StepCreatePublicIP) waiterAssociateVpcPublicIPToServerInstance(serverInstanceNo string, publicIP string) error { + reqParams := &vserver.GetServerInstanceDetailRequest{ + RegionCode: &s.Config.RegionCode, + ServerInstanceNo: &serverInstanceNo, + } + + c1 := make(chan error, 1) + + go func() { + for { + serverInstanceList, err := s.Conn.vserver.V2Api.GetServerInstanceDetail(reqParams) + + if err != nil { + c1 <- err + return + } + + if publicIP == *serverInstanceList.ServerInstanceList[0].PublicIp { + c1 <- nil + return + } + + s.Say("Wait to associate public ip serverInstance") + time.Sleep(time.Second * 3) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(time.Second * 60): + return fmt.Errorf("TIMEOUT : association public ip[%s] to server instance[%s] Failed", publicIP, serverInstanceNo) + } +} + +func (s *StepCreatePublicIP) createClassicPublicIP(serverInstanceNo string) (*server.PublicIpInstance, error) { + reqParams := &server.CreatePublicIpInstanceRequest{ + ServerInstanceNo: &serverInstanceNo, + } publicIPInstanceList, err := s.Conn.server.V2Api.CreatePublicIpInstance(reqParams) if err != nil { @@ -81,7 +189,7 @@ func (s *StepCreatePublicIPInstance) createPublicIPInstance(serverInstanceNo str publicIP := publicIPInstance.PublicIp s.Say(fmt.Sprintf("Public IP Instance [%s:%s] is created", *publicIPInstance.PublicIpInstanceNo, *publicIP)) - err = s.waiterAssociatePublicIPToServerInstance(serverInstanceNo, *publicIP) + err = s.WaiterAssociatePublicIPToServerInstance(serverInstanceNo, *publicIP) if err != nil { return nil, err } @@ -89,69 +197,49 @@ func (s *StepCreatePublicIPInstance) createPublicIPInstance(serverInstanceNo str return publicIPInstance, nil } -func (s *StepCreatePublicIPInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - s.Say("Create Public IP Instance") - - serverInstanceNo := state.Get("InstanceNo").(string) - - publicIPInstance, err := s.CreatePublicIPInstance(serverInstanceNo) - if err == nil { - state.Put("PublicIP", *publicIPInstance.PublicIp) - state.Put("PublicIPInstance", publicIPInstance) - // instance_id is the generic term used so that users can have access to the - // instance id inside of the provisioners, used in step_provision. - state.Put("instance_id", *publicIPInstance) +func (s *StepCreatePublicIP) createVpcPublicIP(serverInstanceNo string) (*server.PublicIpInstance, error) { + reqParams := &vserver.CreatePublicIpInstanceRequest{ + RegionCode: &s.Config.RegionCode, + ServerInstanceNo: &serverInstanceNo, } - return processStepResult(err, s.Error, state) -} - -func (s *StepCreatePublicIPInstance) Cleanup(state multistep.StateBag) { - publicIPInstance, ok := state.GetOk("PublicIPInstance") - if !ok { - return + publicIPInstanceList, err := s.Conn.vserver.V2Api.CreatePublicIpInstance(reqParams) + if err != nil { + return nil, err } - s.Say("Clean up Public IP Instance") - publicIPInstanceNo := publicIPInstance.(*server.PublicIpInstance).PublicIpInstanceNo - s.waitPublicIPInstanceStatus(publicIPInstanceNo, "USED") - - log.Println("Disassociate Public IP Instance ", publicIPInstanceNo) - reqParams := &server.DisassociatePublicIpFromServerInstanceRequest{PublicIpInstanceNo: publicIPInstanceNo} - s.Conn.server.V2Api.DisassociatePublicIpFromServerInstance(reqParams) - - s.waitPublicIPInstanceStatus(publicIPInstanceNo, "CREAT") + publicIPInstance := publicIPInstanceList.PublicIpInstanceList[0] + publicIP := publicIPInstance.PublicIp + s.Say(fmt.Sprintf("Public IP Instance [%s:%s] is created", *publicIPInstance.PublicIpInstanceNo, *publicIP)) - reqDeleteParams := &server.DeletePublicIpInstancesRequest{ - PublicIpInstanceNoList: ncloud.StringList([]string{*publicIPInstanceNo}), + err = s.WaiterAssociatePublicIPToServerInstance(serverInstanceNo, *publicIP) + if err != nil { + return nil, err } - log.Println("Delete Public IP Instance ", publicIPInstanceNo) - s.Conn.server.V2Api.DeletePublicIpInstances(reqDeleteParams) + return &server.PublicIpInstance{ + PublicIpInstanceNo: publicIPInstance.PublicIpInstanceNo, + PublicIp: publicIP, + }, nil } -func (s *StepCreatePublicIPInstance) waitPublicIPInstanceStatus(publicIPInstanceNo *string, status string) { +func (s *StepCreatePublicIP) waitPublicIPStatus(publicIPInstanceNo string, status string) error { c1 := make(chan error, 1) go func() { - reqParams := new(server.GetPublicIpInstanceListRequest) - reqParams.PublicIpInstanceNoList = []*string{publicIPInstanceNo} - for { - resp, err := s.Conn.server.V2Api.GetPublicIpInstanceList(reqParams) + publicIp, err := s.GetPublicIP(publicIPInstanceNo) if err != nil { - log.Printf(err.Error()) c1 <- err return } - if *resp.TotalRows == 0 { + if publicIp == nil { c1 <- nil return } - instance := resp.PublicIpInstanceList[0] - if *instance.PublicIpInstanceStatus.Code == status && *instance.PublicIpInstanceOperation.Code == "NULL" { + if *publicIp.PublicIpInstanceStatus.Code == status && *publicIp.PublicIpInstanceOperation.Code == "NULL" { c1 <- nil return } @@ -162,8 +250,119 @@ func (s *StepCreatePublicIPInstance) waitPublicIPInstanceStatus(publicIPInstance select { case <-c1: - return + return nil case <-time.After(time.Second * 60): + return fmt.Errorf("TIMEOUT : wait for public ip[%s] to status[%s] Failed", publicIPInstanceNo, status) + } +} + +func (s *StepCreatePublicIP) disassociateClassicPublicIpFromServerInstance(publicIPInstanceNo string) error { + reqParams := &server.DisassociatePublicIpFromServerInstanceRequest{ + PublicIpInstanceNo: &publicIPInstanceNo, + } + + _, err := s.Conn.server.V2Api.DisassociatePublicIpFromServerInstance(reqParams) + + return err +} + +func (s *StepCreatePublicIP) disassociateVpcPublicIpFromServerInstance(publicIPInstanceNo string) error { + reqParams := &vserver.DisassociatePublicIpFromServerInstanceRequest{ + RegionCode: &s.Config.RegionCode, + PublicIpInstanceNo: &publicIPInstanceNo, + } + + _, err := s.Conn.vserver.V2Api.DisassociatePublicIpFromServerInstance(reqParams) + + return err +} + +func (s *StepCreatePublicIP) deleteClassicPublicIp(publicIPInstanceNo string) error { + reqParams := &server.DeletePublicIpInstancesRequest{ + PublicIpInstanceNoList: ncloud.StringList([]string{publicIPInstanceNo}), + } + + _, err := s.Conn.server.V2Api.DeletePublicIpInstances(reqParams) + + return err +} + +func (s *StepCreatePublicIP) deleteVpcPublicIp(publicIPInstanceNo string) error { + reqParams := &vserver.DeletePublicIpInstanceRequest{ + RegionCode: &s.Config.RegionCode, + PublicIpInstanceNo: &publicIPInstanceNo, + } + + _, err := s.Conn.vserver.V2Api.DeletePublicIpInstance(reqParams) + + return err +} + +func (s *StepCreatePublicIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + s.Say("Create Public IP Instance") + + serverInstanceNo := state.Get("instance_no").(string) + + publicIPInstance, err := s.CreatePublicIP(serverInstanceNo) + if err == nil { + state.Put("public_ip", *publicIPInstance.PublicIp) + state.Put("public_ip_instance", publicIPInstance) + // instance_id is the generic term used so that users can have access to the + // instance id inside of the provisioners, used in step_provision. + state.Put("instance_id", *publicIPInstance) + } + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreatePublicIP) Cleanup(state multistep.StateBag) { + publicIPInstance, ok := state.GetOk("public_ip_instance") + if !ok { + return + } + + s.Say("Clean up Public IP Instance") + publicIPInstanceNo := publicIPInstance.(*server.PublicIpInstance).PublicIpInstanceNo + + publicIp, err := s.GetPublicIP(*publicIPInstanceNo) + if err != nil { + s.Error(err) + } + + if publicIp == nil { + return + } + + publicIpUsedStatus := PublicIpStatusUsed + publicIpCreatedStatus := PublicIpStatusCreated + + if s.Config.SupportVPC { + publicIpUsedStatus = PublicIpStatusRunning + publicIpCreatedStatus = PublicIpStatusRunning + } + + if publicIp.ServerInstanceAssociatedWithPublicIp != nil && + publicIp.ServerInstanceAssociatedWithPublicIp.ServerInstanceNo != nil && + len(*publicIp.ServerInstanceAssociatedWithPublicIp.ServerInstanceNo) > 0 { + if err := s.waitPublicIPStatus(*publicIPInstanceNo, publicIpUsedStatus); err != nil { + s.Error(err) + } + + log.Println("Disassociate Public IP Instance ", publicIPInstanceNo) + if err := s.DisassociatePublicIpFromServerInstance(*publicIPInstanceNo); err != nil { + s.Error(err) + return + } + } + + if err := s.waitPublicIPStatus(*publicIPInstanceNo, publicIpCreatedStatus); err != nil { + s.Error(err) + return + } + + log.Println("Delete Public IP Instance ", publicIPInstanceNo) + if err := s.DeletePublicIp(*publicIPInstanceNo); err != nil { + s.Error(err) return } } diff --git a/builder/ncloud/step_create_public_ip_instance_test.go b/builder/ncloud/step_create_public_ip_instance_test.go index 14e4a62d6..c081409ea 100644 --- a/builder/ncloud/step_create_public_ip_instance_test.go +++ b/builder/ncloud/step_create_public_ip_instance_test.go @@ -12,8 +12,8 @@ import ( ) func TestStepCreatePublicIPInstanceShouldFailIfOperationCreatePublicIPInstanceFails(t *testing.T) { - var testSubject = &StepCreatePublicIPInstance{ - CreatePublicIPInstance: func(serverInstanceNo string) (*server.PublicIpInstance, error) { + var testSubject = &StepCreatePublicIP{ + CreatePublicIP: func(serverInstanceNo string) (*server.PublicIpInstance, error) { return nil, fmt.Errorf("!! Unit Test FAIL !!") }, Say: func(message string) {}, @@ -28,7 +28,7 @@ func TestStepCreatePublicIPInstanceShouldFailIfOperationCreatePublicIPInstanceFa t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -38,8 +38,8 @@ func TestStepCreatePublicIPInstanceShouldPassIfOperationCreatePublicIPInstancePa c.Comm.Prepare(nil) c.Comm.Type = "ssh" - var testSubject = &StepCreatePublicIPInstance{ - CreatePublicIPInstance: func(serverInstanceNo string) (*server.PublicIpInstance, error) { + var testSubject = &StepCreatePublicIP{ + CreatePublicIP: func(serverInstanceNo string) (*server.PublicIpInstance, error) { return &server.PublicIpInstance{PublicIpInstanceNo: ncloud.String("a"), PublicIp: ncloud.String("b")}, nil }, Say: func(message string) {}, @@ -55,7 +55,7 @@ func TestStepCreatePublicIPInstanceShouldPassIfOperationCreatePublicIPInstancePa t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -63,7 +63,7 @@ func TestStepCreatePublicIPInstanceShouldPassIfOperationCreatePublicIPInstancePa func createTestStateBagStepCreatePublicIPInstance() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("InstanceNo", "a") + stateBag.Put("instance_no", "a") return stateBag } diff --git a/builder/ncloud/step_create_server_image.go b/builder/ncloud/step_create_server_image.go index f0a2b4d7c..d3ad06d49 100644 --- a/builder/ncloud/step_create_server_image.go +++ b/builder/ncloud/step_create_server_image.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" @@ -11,6 +12,10 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) +const ( + ServerImageStatusCreated = "CREAT" +) + type StepCreateServerImage struct { Conn *NcloudAPIClient CreateServerImage func(serverInstanceNo string) (*server.MemberServerImage, error) @@ -27,21 +32,26 @@ func NewStepCreateServerImage(conn *NcloudAPIClient, ui packersdk.Ui, config *Co Config: config, } - step.CreateServerImage = step.createServerImage + if config.SupportVPC { + step.CreateServerImage = step.createVpcServerImage + } else { + step.CreateServerImage = step.createClassicServerImage + } return step } -func (s *StepCreateServerImage) createServerImage(serverInstanceNo string) (*server.MemberServerImage, error) { +func (s *StepCreateServerImage) createClassicServerImage(serverInstanceNo string) (*server.MemberServerImage, error) { // Can't create server image when status of server instance is stopping (not stopped) - if err := waiterServerInstanceStatus(s.Conn, serverInstanceNo, "NSTOP", 1*time.Minute); err != nil { + if err := waiterClassicServerInstanceStatus(s.Conn, serverInstanceNo, ServerInstanceStatusStopped, 1*time.Minute); err != nil { return nil, err } - reqParams := new(server.CreateMemberServerImageRequest) - reqParams.MemberServerImageName = &s.Config.ServerImageName - reqParams.MemberServerImageDescription = &s.Config.ServerImageDescription - reqParams.ServerInstanceNo = &serverInstanceNo + reqParams := &server.CreateMemberServerImageRequest{ + MemberServerImageName: &s.Config.ServerImageName, + MemberServerImageDescription: &s.Config.ServerImageDescription, + ServerInstanceNo: &serverInstanceNo, + } memberServerImageList, err := s.Conn.server.V2Api.CreateMemberServerImage(reqParams) if err != nil { @@ -52,7 +62,7 @@ func (s *StepCreateServerImage) createServerImage(serverInstanceNo string) (*ser s.Say(fmt.Sprintf("Server Image[%s:%s] is creating...", *serverImage.MemberServerImageName, *serverImage.MemberServerImageNo)) - if err := waiterMemberServerImageStatus(s.Conn, *serverImage.MemberServerImageNo, "CREAT", 6*time.Hour); err != nil { + if err := waiterClassicMemberServerImageStatus(s.Conn, *serverImage.MemberServerImageNo, ServerImageStatusCreated, 6*time.Hour); err != nil { return nil, errors.New("TIMEOUT : Server Image is not created") } @@ -61,14 +71,56 @@ func (s *StepCreateServerImage) createServerImage(serverInstanceNo string) (*ser return serverImage, nil } +func (s *StepCreateServerImage) createVpcServerImage(serverInstanceNo string) (*server.MemberServerImage, error) { + // Can't create server image when status of server instance is stopping (not stopped) + if err := waiterVpcServerInstanceStatus(s.Conn, serverInstanceNo, ServerInstanceStatusStopped, 1*time.Minute); err != nil { + return nil, err + } + + reqParams := &vserver.CreateMemberServerImageInstanceRequest{ + MemberServerImageName: &s.Config.ServerImageName, + MemberServerImageDescription: &s.Config.ServerImageDescription, + ServerInstanceNo: &serverInstanceNo, + } + + memberServerImageList, err := s.Conn.vserver.V2Api.CreateMemberServerImageInstance(reqParams) + if err != nil { + return nil, err + } + + serverImage := memberServerImageList.MemberServerImageInstanceList[0] + + s.Say(fmt.Sprintf("Server Image[%s:%s] is creating...", *serverImage.MemberServerImageName, *serverImage.MemberServerImageInstanceNo)) + + if err := waiterVpcMemberServerImageStatus(s.Conn, *serverImage.MemberServerImageInstanceNo, ServerImageStatusCreated, 6*time.Hour); err != nil { + return nil, errors.New("TIMEOUT : Server Image is not created") + } + + s.Say(fmt.Sprintf("Server Image[%s:%s] is created", *serverImage.MemberServerImageName, *serverImage.MemberServerImageInstanceNo)) + + result := &server.MemberServerImage{ + MemberServerImageNo: serverImage.MemberServerImageInstanceNo, + MemberServerImageName: serverImage.MemberServerImageName, + } + + if serverImage.MemberServerImageInstanceStatus != nil { + result.MemberServerImageStatus = &server.CommonCode{ + Code: serverImage.MemberServerImageInstanceStatus.Code, + CodeName: serverImage.MemberServerImageInstanceStatus.CodeName, + } + } + + return result, nil +} + func (s *StepCreateServerImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Create Server Image") - serverInstanceNo := state.Get("InstanceNo").(string) + serverInstanceNo := state.Get("instance_no").(string) serverImage, err := s.CreateServerImage(serverInstanceNo) if err == nil { - state.Put("memberServerImage", serverImage) + state.Put("member_server_image", serverImage) } return processStepResult(err, s.Error, state) diff --git a/builder/ncloud/step_create_server_image_test.go b/builder/ncloud/step_create_server_image_test.go index a6ff44aaa..4f807c634 100644 --- a/builder/ncloud/step_create_server_image_test.go +++ b/builder/ncloud/step_create_server_image_test.go @@ -27,7 +27,7 @@ func TestStepCreateServerImageShouldFailIfOperationCreateServerImageFails(t *tes t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -46,7 +46,7 @@ func TestStepCreateServerImageShouldPassIfOperationCreateServerImagePasses(t *te t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -54,7 +54,7 @@ func TestStepCreateServerImageShouldPassIfOperationCreateServerImagePasses(t *te func createTestStateBagStepCreateServerImage() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("InstanceNo", "a") + stateBag.Put("instance_no", "a") return stateBag } diff --git a/builder/ncloud/step_create_server_instance.go b/builder/ncloud/step_create_server_instance.go index 59df31ce4..c1f74bd34 100644 --- a/builder/ncloud/step_create_server_instance.go +++ b/builder/ncloud/step_create_server_instance.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "io/ioutil" "log" "time" @@ -14,14 +15,21 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) +const ( + ServerInstanceStatusStopped = "NSTOP" + ServerInstanceStatusTerminated = "TERMT" + ServerInstanceStatusRunning = "RUN" +) + type StepCreateServerInstance struct { - Conn *NcloudAPIClient - CreateServerInstance func(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) - CheckServerInstanceStatusIsRunning func(serverInstanceNo string) error - Say func(message string) - Error func(e error) - Config *Config - serverInstanceNo string + Conn *NcloudAPIClient + CreateServerInstance func(loginKeyName string, feeSystemTypeCode string, state multistep.StateBag) (string, error) + GetServerInstance func() (string, string, error) + WaiterServerInstanceStatus func(conn *NcloudAPIClient, serverInstanceNo string, status string, timeout time.Duration) error + Say func(message string) + Error func(e error) + Config *Config + serverInstanceNo string } func NewStepCreateServerInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateServerInstance { @@ -32,21 +40,32 @@ func NewStepCreateServerInstance(conn *NcloudAPIClient, ui packersdk.Ui, config Config: config, } - step.CreateServerInstance = step.createServerInstance + if config.SupportVPC { + step.CreateServerInstance = step.createVpcServerInstance + step.WaiterServerInstanceStatus = waiterVpcServerInstanceStatus + step.GetServerInstance = step.getVpcServerInstance + } else { + step.CreateServerInstance = step.createClassicServerInstance + step.WaiterServerInstanceStatus = waiterClassicServerInstanceStatus + step.GetServerInstance = step.getClassicServerInstance + } return step } -func (s *StepCreateServerInstance) createServerInstance(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) { - reqParams := new(server.CreateServerInstancesRequest) - reqParams.ServerProductCode = &s.Config.ServerProductCode - reqParams.MemberServerImageNo = &s.Config.MemberServerImageNo +func (s *StepCreateServerInstance) createClassicServerInstance(loginKeyName string, feeSystemTypeCode string, state multistep.StateBag) (string, error) { + var zoneNo = state.Get("zone_no").(string) + + reqParams := &server.CreateServerInstancesRequest{ + ServerProductCode: &s.Config.ServerProductCode, + MemberServerImageNo: &s.Config.MemberServerImageNo, + LoginKeyName: &loginKeyName, + ZoneNo: &zoneNo, + FeeSystemTypeCode: &feeSystemTypeCode, + } if s.Config.MemberServerImageNo == "" { reqParams.ServerImageProductCode = &s.Config.ServerImageProductCode } - reqParams.LoginKeyName = &loginKeyName - reqParams.ZoneNo = &zoneNo - reqParams.FeeSystemTypeCode = &feeSystemTypeCode if s.Config.UserData != "" { reqParams.UserData = &s.Config.UserData @@ -74,7 +93,63 @@ func (s *StepCreateServerInstance) createServerInstance(loginKeyName string, zon s.Say(fmt.Sprintf("Server Instance is creating. Server InstanceNo is %s", s.serverInstanceNo)) log.Println("Server Instance information : ", serverInstanceList.ServerInstanceList[0]) - if err := waiterServerInstanceStatus(s.Conn, s.serverInstanceNo, "RUN", 30*time.Minute); err != nil { + if err := s.WaiterServerInstanceStatus(s.Conn, s.serverInstanceNo, ServerInstanceStatusRunning, 30*time.Minute); err != nil { + return "", errors.New("TIMEOUT : server instance status is not running") + } + + s.Say(fmt.Sprintf("Server Instance is created. Server InstanceNo is %s", s.serverInstanceNo)) + + return s.serverInstanceNo, nil +} + +func (s *StepCreateServerInstance) createVpcServerInstance(loginKeyName string, feeSystemTypeCode string, state multistep.StateBag) (string, error) { + var initScriptNo string + var acgNo string + var err error + + if v, ok := state.GetOk("init_script_no"); ok { + initScriptNo = v.(string) + } + + if s.Config.AccessControlGroupConfigurationNo != "" { + acgNo = s.Config.AccessControlGroupConfigurationNo + } else { + acgNo, err = s.getDefaultAccessControlGroup(s.Config.VpcNo) + } + + if err != nil { + return "", err + } + + reqParams := &vserver.CreateServerInstancesRequest{ + RegionCode: &s.Config.RegionCode, + ServerProductCode: &s.Config.ServerProductCode, + MemberServerImageInstanceNo: &s.Config.MemberServerImageNo, + LoginKeyName: &loginKeyName, + FeeSystemTypeCode: &feeSystemTypeCode, + InitScriptNo: &initScriptNo, + VpcNo: &s.Config.VpcNo, + SubnetNo: &s.Config.SubnetNo, + NetworkInterfaceList: []*vserver.NetworkInterfaceParameter{{ + NetworkInterfaceOrder: ncloud.Int32(0), + AccessControlGroupNoList: []*string{ncloud.String(acgNo)}}, + }, + } + + if s.Config.MemberServerImageNo == "" { + reqParams.ServerImageProductCode = &s.Config.ServerImageProductCode + } + + serverInstanceList, err := s.Conn.vserver.V2Api.CreateServerInstances(reqParams) + if err != nil { + return "", err + } + + s.serverInstanceNo = *serverInstanceList.ServerInstanceList[0].ServerInstanceNo + s.Say(fmt.Sprintf("Server Instance is creating. Server InstanceNo is %s", s.serverInstanceNo)) + log.Println("Server Instance information : ", serverInstanceList.ServerInstanceList[0]) + + if err := s.WaiterServerInstanceStatus(s.Conn, s.serverInstanceNo, ServerInstanceStatusRunning, 30*time.Minute); err != nil { return "", errors.New("TIMEOUT : server instance status is not running") } @@ -83,20 +158,77 @@ func (s *StepCreateServerInstance) createServerInstance(loginKeyName string, zon 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}, + } + + resp, err := s.Conn.server.V2Api.GetServerInstanceList(reqParams) + if err != nil { + return "", "", err + } + + if *resp.TotalRows > 0 { + return *resp.ServerInstanceList[0].ServerInstanceNo, *resp.ServerInstanceList[0].ServerInstanceStatus.Code, nil + } else { + return "", "", nil + } +} + +func (s *StepCreateServerInstance) getVpcServerInstance() (string, string, error) { + reqParams := &vserver.GetServerInstanceDetailRequest{ + ServerInstanceNo: &s.serverInstanceNo, + } + + resp, err := s.Conn.vserver.V2Api.GetServerInstanceDetail(reqParams) + if err != nil { + return "", "", err + } + + if *resp.TotalRows > 0 { + return *resp.ServerInstanceList[0].ServerInstanceNo, *resp.ServerInstanceList[0].ServerInstanceStatus.Code, nil + } else { + return "", "", nil + } +} + func (s *StepCreateServerInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Create Server Instance") - - var loginKey = state.Get("LoginKey").(*LoginKey) - var zoneNo = state.Get("ZoneNo").(string) + var loginKey = state.Get("login_key").(*LoginKey) feeSystemTypeCode := "MTRAT" - if _, ok := state.GetOk("FeeSystemTypeCode"); ok { - feeSystemTypeCode = state.Get("FeeSystemTypeCode").(string) + if _, ok := state.GetOk("fee_system_type_code"); ok { + feeSystemTypeCode = state.Get("fee_system_type_code").(string) } - serverInstanceNo, err := s.CreateServerInstance(loginKey.KeyName, zoneNo, feeSystemTypeCode) + serverInstanceNo, err := s.CreateServerInstance(loginKey.KeyName, feeSystemTypeCode, state) if err == nil { - state.Put("InstanceNo", serverInstanceNo) + state.Put("instance_no", serverInstanceNo) // instance_id is the generic term used so that users can have access to the // instance id inside of the provisioners, used in step_provision. state.Put("instance_id", serverInstanceNo) @@ -108,6 +240,7 @@ func (s *StepCreateServerInstance) Run(ctx context.Context, state multistep.Stat func (s *StepCreateServerInstance) Cleanup(state multistep.StateBag) { _, cancelled := state.GetOk(multistep.StateCancelled) _, halted := state.GetOk(multistep.StateHalted) + ui := state.Get("ui").(packersdk.Ui) if !cancelled && !halted { return @@ -117,33 +250,31 @@ func (s *StepCreateServerInstance) Cleanup(state multistep.StateBag) { return } - reqParams := new(server.GetServerInstanceListRequest) - reqParams.ServerInstanceNoList = []*string{&s.serverInstanceNo} + _, status, err := s.GetServerInstance() - serverInstanceList, err := s.Conn.server.V2Api.GetServerInstanceList(reqParams) - if err != nil || *serverInstanceList.TotalRows == 0 { + if err != nil || len(status) == 0 { return } s.Say("Clean up Server Instance") - serverInstance := serverInstanceList.ServerInstanceList[0] // stop server instance - if *serverInstance.ServerInstanceStatus.Code != "NSTOP" && *serverInstance.ServerInstanceStatus.Code != "TERMT" { - reqParams := new(server.StopServerInstancesRequest) - reqParams.ServerInstanceNoList = []*string{&s.serverInstanceNo} - - log.Println("Stop Server Instance") - s.Conn.server.V2Api.StopServerInstances(reqParams) - waiterServerInstanceStatus(s.Conn, s.serverInstanceNo, "NSTOP", time.Minute) + if status != ServerInstanceStatusStopped && status != ServerInstanceStatusTerminated { + stepStopServerInstance := NewStepStopServerInstance(s.Conn, ui, s.Config) + err := stepStopServerInstance.StopServerInstance(s.serverInstanceNo) + if err != nil { + s.Error(err) + return + } } // terminate server instance - if *serverInstance.ServerInstanceStatus.Code != "TERMT" { - reqParams := new(server.TerminateServerInstancesRequest) - reqParams.ServerInstanceNoList = []*string{&s.serverInstanceNo} - - log.Println("Terminate Server Instance") - s.Conn.server.V2Api.TerminateServerInstances(reqParams) + if status != ServerInstanceStatusTerminated { + stepStopServerInstance := NewStepTerminateServerInstance(s.Conn, ui, s.Config) + err := stepStopServerInstance.TerminateServerInstance(s.serverInstanceNo) + if err != nil { + s.Error(err) + return + } } } diff --git a/builder/ncloud/step_create_server_instance_test.go b/builder/ncloud/step_create_server_instance_test.go index cc970dcad..b995d15f9 100644 --- a/builder/ncloud/step_create_server_instance_test.go +++ b/builder/ncloud/step_create_server_instance_test.go @@ -10,7 +10,7 @@ import ( func TestStepCreateServerInstanceShouldFailIfOperationCreateFails(t *testing.T) { var testSubject = &StepCreateServerInstance{ - CreateServerInstance: func(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) { + CreateServerInstance: func(loginKeyName string, feeSystemTypeCode string, state multistep.StateBag) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") }, Say: func(message string) {}, @@ -25,16 +25,18 @@ func TestStepCreateServerInstanceShouldFailIfOperationCreateFails(t *testing.T) t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } func TestStepCreateServerInstanceShouldPassIfOperationCreatePasses(t *testing.T) { var testSubject = &StepCreateServerInstance{ - CreateServerInstance: func(loginKeyName string, zoneNo string, feeSystemTypeCode string) (string, error) { return "", nil }, - Say: func(message string) {}, - Error: func(e error) {}, + CreateServerInstance: func(loginKeyName string, feeSystemTypeCode string, state multistep.StateBag) (string, error) { + return "", nil + }, + Say: func(message string) {}, + Error: func(e error) {}, } stateBag := createTestStateBagStepCreateServerInstance() @@ -45,7 +47,7 @@ func TestStepCreateServerInstanceShouldPassIfOperationCreatePasses(t *testing.T) t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -53,8 +55,8 @@ func TestStepCreateServerInstanceShouldPassIfOperationCreatePasses(t *testing.T) func createTestStateBagStepCreateServerInstance() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("LoginKey", &LoginKey{"a", "b"}) - stateBag.Put("ZoneNo", "1") + stateBag.Put("login_key", &LoginKey{"a", "b"}) + stateBag.Put("zone_no", "1") return stateBag } diff --git a/builder/ncloud/step_delete_block_storage_instance.go b/builder/ncloud/step_delete_block_storage_instance.go index 0134e1856..f64caa155 100644 --- a/builder/ncloud/step_delete_block_storage_instance.go +++ b/builder/ncloud/step_delete_block_storage_instance.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "log" "time" @@ -12,30 +13,35 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) -type StepDeleteBlockStorageInstance struct { - Conn *NcloudAPIClient - DeleteBlockStorageInstance func(blockStorageInstanceNo string) error - Say func(message string) - Error func(e error) - Config *Config +type StepDeleteBlockStorage struct { + Conn *NcloudAPIClient + DeleteBlockStorage func(blockStorageNo string) error + Say func(message string) + Error func(e error) + Config *Config } -func NewStepDeleteBlockStorageInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepDeleteBlockStorageInstance { - var step = &StepDeleteBlockStorageInstance{ +func NewStepDeleteBlockStorage(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepDeleteBlockStorage { + var step = &StepDeleteBlockStorage{ Conn: conn, Say: func(message string) { ui.Say(message) }, Error: func(e error) { ui.Error(e.Error()) }, Config: config, } - step.DeleteBlockStorageInstance = step.deleteBlockStorageInstance + if config.SupportVPC { + step.DeleteBlockStorage = step.deleteVpcBlockStorage + } else { + step.DeleteBlockStorage = step.deleteClassicBlockStorage + } return step } -func (s *StepDeleteBlockStorageInstance) getBlockInstanceList(serverInstanceNo string) []*string { - reqParams := new(server.GetBlockStorageInstanceListRequest) - reqParams.ServerInstanceNo = &serverInstanceNo +func (s *StepDeleteBlockStorage) getClassicBlockList(serverInstanceNo string) []*string { + reqParams := &server.GetBlockStorageInstanceListRequest{ + ServerInstanceNo: &serverInstanceNo, + } blockStorageInstanceList, err := s.Conn.server.V2Api.GetBlockStorageInstanceList(reqParams) if err != nil { @@ -58,8 +64,34 @@ func (s *StepDeleteBlockStorageInstance) getBlockInstanceList(serverInstanceNo s return instanceList } -func (s *StepDeleteBlockStorageInstance) deleteBlockStorageInstance(serverInstanceNo string) error { - blockStorageInstanceList := s.getBlockInstanceList(serverInstanceNo) +func (s *StepDeleteBlockStorage) getVpcBlockList(serverInstanceNo string) []*string { + reqParams := &vserver.GetBlockStorageInstanceListRequest{ + ServerInstanceNo: &serverInstanceNo, + } + + blockStorageInstanceList, err := s.Conn.vserver.V2Api.GetBlockStorageInstanceList(reqParams) + if err != nil { + return nil + } + + if *blockStorageInstanceList.TotalRows == 1 { + return nil + } + + var instanceList []*string + + for _, blockStorageInstance := range blockStorageInstanceList.BlockStorageInstanceList { + log.Println(blockStorageInstance) + if *blockStorageInstance.BlockStorageType.Code != "BASIC" { + instanceList = append(instanceList, blockStorageInstance.BlockStorageInstanceNo) + } + } + + return instanceList +} + +func (s *StepDeleteBlockStorage) deleteClassicBlockStorage(serverInstanceNo string) error { + blockStorageInstanceList := s.getClassicBlockList(serverInstanceNo) if blockStorageInstanceList == nil || len(blockStorageInstanceList) == 0 { return nil } @@ -73,26 +105,48 @@ func (s *StepDeleteBlockStorageInstance) deleteBlockStorageInstance(serverInstan s.Say(fmt.Sprintf("Block Storage Instance is deleted. Block Storage Instance List is %v", blockStorageInstanceList)) - if err := waiterDetachedBlockStorageInstance(s.Conn, serverInstanceNo, time.Minute); err != nil { + if err := waiterClassicDetachedBlockStorage(s.Conn, serverInstanceNo, time.Minute); err != nil { + return errors.New("TIMEOUT : Block Storage instance status is not deattached") + } + + return nil +} + +func (s *StepDeleteBlockStorage) deleteVpcBlockStorage(serverInstanceNo string) error { + blockStorageInstanceList := s.getVpcBlockList(serverInstanceNo) + if blockStorageInstanceList == nil || len(blockStorageInstanceList) == 0 { + return nil + } + reqParams := vserver.DeleteBlockStorageInstancesRequest{ + BlockStorageInstanceNoList: blockStorageInstanceList, + } + _, err := s.Conn.vserver.V2Api.DeleteBlockStorageInstances(&reqParams) + if err != nil { + return err + } + + s.Say(fmt.Sprintf("Block Storage Instance is deleted. Block Storage Instance List is %v", blockStorageInstanceList)) + + if err := waiterVpcDetachedBlockStorage(s.Conn, serverInstanceNo, time.Minute); err != nil { return errors.New("TIMEOUT : Block Storage instance status is not deattached") } return nil } -func (s *StepDeleteBlockStorageInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepDeleteBlockStorage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { if s.Config.BlockStorageSize == 0 { return processStepResult(nil, s.Error, state) } s.Say("Delete Block Storage Instance") - var serverInstanceNo = state.Get("InstanceNo").(string) + var serverInstanceNo = state.Get("instance_no").(string) - err := s.DeleteBlockStorageInstance(serverInstanceNo) + err := s.DeleteBlockStorage(serverInstanceNo) return processStepResult(err, s.Error, state) } -func (*StepDeleteBlockStorageInstance) Cleanup(multistep.StateBag) { +func (*StepDeleteBlockStorage) Cleanup(multistep.StateBag) { } diff --git a/builder/ncloud/step_delete_block_storage_instance_test.go b/builder/ncloud/step_delete_block_storage_instance_test.go index 6f8008343..01179280c 100644 --- a/builder/ncloud/step_delete_block_storage_instance_test.go +++ b/builder/ncloud/step_delete_block_storage_instance_test.go @@ -9,11 +9,11 @@ import ( ) func TestStepDeleteBlockStorageInstanceShouldFailIfOperationDeleteBlockStorageInstanceFails(t *testing.T) { - var testSubject = &StepDeleteBlockStorageInstance{ - DeleteBlockStorageInstance: func(blockStorageInstanceNo string) error { return fmt.Errorf("!! Unit Test FAIL !!") }, - Say: func(message string) {}, - Error: func(e error) {}, - Config: &Config{BlockStorageSize: 10}, + var testSubject = &StepDeleteBlockStorage{ + DeleteBlockStorage: func(blockStorageNo string) error { return fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: &Config{BlockStorageSize: 10}, } stateBag := createTestStateBagStepDeleteBlockStorageInstance() @@ -24,17 +24,17 @@ func TestStepDeleteBlockStorageInstanceShouldFailIfOperationDeleteBlockStorageIn t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } func TestStepDeleteBlockStorageInstanceShouldPassIfOperationDeleteBlockStorageInstancePasses(t *testing.T) { - var testSubject = &StepDeleteBlockStorageInstance{ - DeleteBlockStorageInstance: func(blockStorageInstanceNo string) error { return nil }, - Say: func(message string) {}, - Error: func(e error) {}, - Config: &Config{BlockStorageSize: 10}, + var testSubject = &StepDeleteBlockStorage{ + DeleteBlockStorage: func(blockStorageNo string) error { return nil }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: &Config{BlockStorageSize: 10}, } stateBag := createTestStateBagStepDeleteBlockStorageInstance() @@ -45,7 +45,7 @@ func TestStepDeleteBlockStorageInstanceShouldPassIfOperationDeleteBlockStorageIn t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -53,7 +53,7 @@ func TestStepDeleteBlockStorageInstanceShouldPassIfOperationDeleteBlockStorageIn func createTestStateBagStepDeleteBlockStorageInstance() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("InstanceNo", "1") + stateBag.Put("instance_no", "1") return stateBag } diff --git a/builder/ncloud/step_get_rootpassword.go b/builder/ncloud/step_get_rootpassword.go index 20da12f37..fb455be2a 100644 --- a/builder/ncloud/step_get_rootpassword.go +++ b/builder/ncloud/step_get_rootpassword.go @@ -3,6 +3,7 @@ package ncloud import ( "context" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" "github.com/hashicorp/packer-plugin-sdk/multistep" @@ -25,15 +26,20 @@ func NewStepGetRootPassword(conn *NcloudAPIClient, ui packersdk.Ui, config *Conf Config: config, } - step.GetRootPassword = step.getRootPassword + if config.SupportVPC { + step.GetRootPassword = step.getVpcRootPassword + } else { + step.GetRootPassword = step.getClassicRootPassword + } return step } -func (s *StepGetRootPassword) getRootPassword(serverInstanceNo string, privateKey string) (string, error) { - reqParams := new(server.GetRootPasswordRequest) - reqParams.ServerInstanceNo = &serverInstanceNo - reqParams.PrivateKey = &privateKey +func (s *StepGetRootPassword) getClassicRootPassword(serverInstanceNo string, privateKey string) (string, error) { + reqParams := &server.GetRootPasswordRequest{ + ServerInstanceNo: &serverInstanceNo, + PrivateKey: &privateKey, + } rootPassword, err := s.Conn.server.V2Api.GetRootPassword(reqParams) if err != nil { @@ -45,11 +51,28 @@ func (s *StepGetRootPassword) getRootPassword(serverInstanceNo string, privateKe return *rootPassword.RootPassword, nil } +func (s *StepGetRootPassword) getVpcRootPassword(serverInstanceNo string, privateKey string) (string, error) { + reqParams := &vserver.GetRootPasswordRequest{ + RegionCode: &s.Config.RegionCode, + ServerInstanceNo: &serverInstanceNo, + PrivateKey: &privateKey, + } + + rootPassword, err := s.Conn.vserver.V2Api.GetRootPassword(reqParams) + if err != nil { + return "", err + } + + s.Say(fmt.Sprintf("Root password is %s", *rootPassword.RootPassword)) + + return *rootPassword.RootPassword, nil +} + func (s *StepGetRootPassword) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Get Root Password") - serverInstanceNo := state.Get("InstanceNo").(string) - loginKey := state.Get("LoginKey").(*LoginKey) + serverInstanceNo := state.Get("instance_no").(string) + loginKey := state.Get("login_key").(*LoginKey) rootPassword, err := s.GetRootPassword(serverInstanceNo, loginKey.PrivateKey) diff --git a/builder/ncloud/step_get_rootpassword_test.go b/builder/ncloud/step_get_rootpassword_test.go index 1f147da66..571a4751f 100644 --- a/builder/ncloud/step_get_rootpassword_test.go +++ b/builder/ncloud/step_get_rootpassword_test.go @@ -24,7 +24,7 @@ func TestStepGetRootPasswordShouldFailIfOperationGetRootPasswordFails(t *testing t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -45,7 +45,7 @@ func TestStepGetRootPasswordShouldPassIfOperationGetRootPasswordPasses(t *testin t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -53,8 +53,8 @@ func TestStepGetRootPasswordShouldPassIfOperationGetRootPasswordPasses(t *testin func DeleteTestStateBagStepGetRootPassword() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("LoginKey", &LoginKey{"a", "b"}) - stateBag.Put("InstanceNo", "a") + stateBag.Put("login_key", &LoginKey{"a", "b"}) + stateBag.Put("instance_no", "a") return stateBag } diff --git a/builder/ncloud/step_stop_server_instance.go b/builder/ncloud/step_stop_server_instance.go index 11b315bc6..609234bf0 100644 --- a/builder/ncloud/step_stop_server_instance.go +++ b/builder/ncloud/step_stop_server_instance.go @@ -3,7 +3,7 @@ package ncloud import ( "context" "fmt" - "log" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" @@ -12,27 +12,37 @@ import ( ) type StepStopServerInstance struct { - Conn *NcloudAPIClient - StopServerInstance func(serverInstanceNo string) error - Say func(message string) - Error func(e error) + Conn *NcloudAPIClient + StopServerInstance func(serverInstanceNo string) error + WaiterServerInstanceStatus func(conn *NcloudAPIClient, serverInstanceNo string, status string, timeout time.Duration) error + Say func(message string) + Error func(e error) + Config *Config } -func NewStepStopServerInstance(conn *NcloudAPIClient, ui packersdk.Ui) *StepStopServerInstance { +func NewStepStopServerInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepStopServerInstance { var step = &StepStopServerInstance{ - Conn: conn, - Say: func(message string) { ui.Say(message) }, - Error: func(e error) { ui.Error(e.Error()) }, + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, } - step.StopServerInstance = step.stopServerInstance + if config.SupportVPC { + step.StopServerInstance = step.stopVpcServerInstance + step.WaiterServerInstanceStatus = waiterVpcServerInstanceStatus + } else { + step.StopServerInstance = step.stopClassicServerInstance + step.WaiterServerInstanceStatus = waiterClassicServerInstanceStatus + } return step } -func (s *StepStopServerInstance) stopServerInstance(serverInstanceNo string) error { - reqParams := new(server.StopServerInstancesRequest) - reqParams.ServerInstanceNoList = []*string{&serverInstanceNo} +func (s *StepStopServerInstance) stopClassicServerInstance(serverInstanceNo string) error { + reqParams := &server.StopServerInstancesRequest{ + ServerInstanceNoList: []*string{&serverInstanceNo}, + } serverInstanceList, err := s.Conn.server.V2Api.StopServerInstances(reqParams) if err != nil { @@ -40,9 +50,30 @@ func (s *StepStopServerInstance) stopServerInstance(serverInstanceNo string) err } s.Say(fmt.Sprintf("Server Instance is stopping. Server InstanceNo is %s", *serverInstanceList.ServerInstanceList[0].ServerInstanceNo)) - log.Println("Server Instance information : ", serverInstanceList.ServerInstanceList[0]) - if err := waiterServerInstanceStatus(s.Conn, serverInstanceNo, "NSTOP", 5*time.Minute); err != nil { + if err := s.WaiterServerInstanceStatus(s.Conn, serverInstanceNo, ServerInstanceStatusStopped, 5*time.Minute); err != nil { + return err + } + + s.Say(fmt.Sprintf("Server Instance stopped. Server InstanceNo is %s", *serverInstanceList.ServerInstanceList[0].ServerInstanceNo)) + + return nil +} + +func (s *StepStopServerInstance) stopVpcServerInstance(serverInstanceNo string) error { + reqParams := &vserver.StopServerInstancesRequest{ + RegionCode: &s.Config.RegionCode, + ServerInstanceNoList: []*string{&serverInstanceNo}, + } + + serverInstanceList, err := s.Conn.vserver.V2Api.StopServerInstances(reqParams) + if err != nil { + return err + } + + s.Say(fmt.Sprintf("Server Instance is stopping. Server InstanceNo is %s", *serverInstanceList.ServerInstanceList[0].ServerInstanceNo)) + + if err := s.WaiterServerInstanceStatus(s.Conn, serverInstanceNo, ServerInstanceStatusStopped, 5*time.Minute); err != nil { return err } @@ -54,7 +85,7 @@ func (s *StepStopServerInstance) stopServerInstance(serverInstanceNo string) err func (s *StepStopServerInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Stop Server Instance") - var serverInstanceNo = state.Get("InstanceNo").(string) + var serverInstanceNo = state.Get("instance_no").(string) err := s.StopServerInstance(serverInstanceNo) diff --git a/builder/ncloud/step_stop_server_instance_test.go b/builder/ncloud/step_stop_server_instance_test.go index 64db61f12..58d6bb3fe 100644 --- a/builder/ncloud/step_stop_server_instance_test.go +++ b/builder/ncloud/step_stop_server_instance_test.go @@ -23,7 +23,7 @@ func TestStepStopServerInstanceShouldFailIfOperationStopFails(t *testing.T) { t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -43,7 +43,7 @@ func TestStepStopServerInstanceShouldPassIfOperationStopPasses(t *testing.T) { t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -51,6 +51,6 @@ func TestStepStopServerInstanceShouldPassIfOperationStopPasses(t *testing.T) { func createTestStateBagStepStopServerInstance() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("InstanceNo", "a") + stateBag.Put("instance_no", "a") return stateBag } diff --git a/builder/ncloud/step_terminate_server_instance.go b/builder/ncloud/step_terminate_server_instance.go index 28666a420..ed1def333 100644 --- a/builder/ncloud/step_terminate_server_instance.go +++ b/builder/ncloud/step_terminate_server_instance.go @@ -3,6 +3,9 @@ package ncloud import ( "context" "errors" + "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" + "log" "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" @@ -15,37 +18,45 @@ type StepTerminateServerInstance struct { TerminateServerInstance func(serverInstanceNo string) error Say func(message string) Error func(e error) + Config *Config } -func NewStepTerminateServerInstance(conn *NcloudAPIClient, ui packersdk.Ui) *StepTerminateServerInstance { +func NewStepTerminateServerInstance(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepTerminateServerInstance { var step = &StepTerminateServerInstance{ - Conn: conn, - Say: func(message string) { ui.Say(message) }, - Error: func(e error) { ui.Error(e.Error()) }, + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, } - step.TerminateServerInstance = step.terminateServerInstance + if config.SupportVPC { + step.TerminateServerInstance = step.terminateVpcServerInstance + } else { + step.TerminateServerInstance = step.terminateClassicServerInstance + } return step } -func (s *StepTerminateServerInstance) terminateServerInstance(serverInstanceNo string) error { - reqParams := new(server.TerminateServerInstancesRequest) - reqParams.ServerInstanceNoList = []*string{&serverInstanceNo} +func (s *StepTerminateServerInstance) terminateClassicServerInstance(serverInstanceNo string) error { + reqParams := &server.TerminateServerInstancesRequest{ + ServerInstanceNoList: []*string{&serverInstanceNo}, + } _, err := s.Conn.server.V2Api.TerminateServerInstances(reqParams) if err != nil { return err } + s.Say(fmt.Sprintf("Server Instance is terminating. Server InstanceNo is %s", serverInstanceNo)) c1 := make(chan error, 1) go func() { - reqParams := new(server.GetServerInstanceListRequest) - reqParams.ServerInstanceNoList = []*string{&serverInstanceNo} + reqParams := &server.GetServerInstanceListRequest{ + ServerInstanceNoList: []*string{&serverInstanceNo}, + } for { - serverInstanceList, err := s.Conn.server.V2Api.GetServerInstanceList(reqParams) if err != nil { c1 <- err @@ -55,22 +66,67 @@ func (s *StepTerminateServerInstance) terminateServerInstance(serverInstanceNo s return } + log.Printf("Wating for terminating server instance [%s] is %s\n", serverInstanceNo, *serverInstanceList.ServerInstanceList[0].ServerInstanceStatus.Code) time.Sleep(time.Second * 3) } }() select { case res := <-c1: + s.Say(fmt.Sprintf("Server Instance terminated. Server InstanceNo is %s", serverInstanceNo)) return res case <-time.After(time.Second * 60): return errors.New("TIMEOUT : Can't terminate server instance") } } +func (s *StepTerminateServerInstance) terminateVpcServerInstance(serverInstanceNo string) error { + reqParams := &vserver.TerminateServerInstancesRequest{ + ServerInstanceNoList: []*string{&serverInstanceNo}, + } + + _, err := s.Conn.vserver.V2Api.TerminateServerInstances(reqParams) + if err != nil { + return err + } + s.Say(fmt.Sprintf("Server Instance is terminating. Server InstanceNo is %s", serverInstanceNo)) + + c1 := make(chan error, 1) + + go func() { + reqParams := &vserver.GetServerInstanceListRequest{ + RegionCode: &s.Config.RegionCode, + ServerInstanceNoList: []*string{&serverInstanceNo}, + } + + for { + serverInstanceList, err := s.Conn.vserver.V2Api.GetServerInstanceList(reqParams) + if err != nil { + c1 <- err + return + } else if *serverInstanceList.TotalRows == 0 { + c1 <- nil + return + } + + log.Printf("Wating for terminating server instance [%s] is %s\n", serverInstanceNo, *serverInstanceList.ServerInstanceList[0].ServerInstanceStatus.Code) + time.Sleep(time.Second * 3) + } + }() + + select { + case res := <-c1: + s.Say(fmt.Sprintf("Server Instance terminated. Server InstanceNo is %s", serverInstanceNo)) + return res + case <-time.After(time.Second * 120): + return errors.New("TIMEOUT : Can't terminate server instance") + } +} + func (s *StepTerminateServerInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { s.Say("Terminate Server Instance") - var serverInstanceNo = state.Get("InstanceNo").(string) + var serverInstanceNo = state.Get("instance_no").(string) err := s.TerminateServerInstance(serverInstanceNo) diff --git a/builder/ncloud/step_terminate_server_instance_test.go b/builder/ncloud/step_terminate_server_instance_test.go index e06a69b90..1c9b78b06 100644 --- a/builder/ncloud/step_terminate_server_instance_test.go +++ b/builder/ncloud/step_terminate_server_instance_test.go @@ -23,7 +23,7 @@ func TestStepTerminateServerInstanceShouldFailIfOperationTerminationFails(t *tes t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -43,7 +43,7 @@ func TestStepTerminateServerInstanceShouldPassIfOperationTerminationPasses(t *te t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } @@ -51,6 +51,6 @@ func TestStepTerminateServerInstanceShouldPassIfOperationTerminationPasses(t *te func createTestStateBagStepTerminateServerInstance() multistep.StateBag { stateBag := new(multistep.BasicStateBag) - stateBag.Put("InstanceNo", "a") + stateBag.Put("instance_no", "a") return stateBag } diff --git a/builder/ncloud/step_validate_template.go b/builder/ncloud/step_validate_template.go index 9dc85a95b..b28b01ba0 100644 --- a/builder/ncloud/step_validate_template.go +++ b/builder/ncloud/step_validate_template.go @@ -5,6 +5,8 @@ import ( "context" "errors" "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vpc" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "strings" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud" @@ -16,14 +18,20 @@ import ( //StepValidateTemplate : struct for Validation a template type StepValidateTemplate struct { - Conn *NcloudAPIClient - Validate func() error - Say func(message string) - Error func(e error) - Config *Config - zoneNo string - regionNo string - FeeSystemTypeCode string + Conn *NcloudAPIClient + Validate func() error + getZone func() error + getMemberServerImageList func() ([]*server.MemberServerImage, error) + getServerImageProductList func(blockStorageSize *int32) ([]*server.Product, error) + getServerProductList func(serverImageProductCode string) ([]*server.Product, error) + Say func(message string) + Error func(e error) + Config *Config + zoneNo string + zoneCode string + regionNo string + regionCode string + FeeSystemTypeCode string } // NewStepValidateTemplate : function for Validation a template @@ -35,6 +43,18 @@ func NewStepValidateTemplate(conn *NcloudAPIClient, ui packersdk.Ui, config *Con Config: config, } + if config.SupportVPC { + step.getZone = step.getZoneCode + step.getMemberServerImageList = step.getVpcMemberServerImageList + step.getServerImageProductList = step.getVpcServerImageProductList + step.getServerProductList = step.getVpcServerProductList + } else { + step.getZone = step.getZoneNo + step.getMemberServerImageList = step.getClassicMemberServerImageList + step.getServerImageProductList = step.getClassicServerImageProductList + step.getServerProductList = step.getClassicServerProductList + } + step.Validate = step.validateTemplate return step @@ -51,21 +71,19 @@ func (s *StepValidateTemplate) getZoneNo() error { return err } - var regionNo string for _, region := range regionList.RegionList { if strings.EqualFold(*region.RegionName, s.Config.Region) { - regionNo = *region.RegionNo + s.regionNo = *region.RegionNo + s.regionCode = *region.RegionCode } } - if regionNo == "" { + if s.regionNo == "" { return fmt.Errorf("region %s is invalid", s.Config.Region) } - s.regionNo = regionNo - // Get ZoneNo - resp, err := s.Conn.server.V2Api.GetZoneList(&server.GetZoneListRequest{RegionNo: ®ionNo}) + resp, err := s.Conn.server.V2Api.GetZoneList(&server.GetZoneListRequest{RegionNo: &s.regionNo}) if err != nil { return err } @@ -77,19 +95,51 @@ func (s *StepValidateTemplate) getZoneNo() error { return nil } -func (s *StepValidateTemplate) validateMemberServerImage() error { - var serverImageName = s.Config.ServerImageName +// getZoneCode : get zoneCode +func (s *StepValidateTemplate) getZoneCode() error { + if s.Config.Region == "" { + return nil + } + + regionList, err := s.Conn.vserver.V2Api.GetRegionList(&vserver.GetRegionListRequest{}) + if err != nil { + return err + } + + for _, region := range regionList.RegionList { + if strings.EqualFold(*region.RegionName, s.Config.Region) { + s.regionCode = *region.RegionCode + s.Config.RegionCode = *region.RegionCode + } + } - reqParams := new(server.GetMemberServerImageListRequest) - reqParams.RegionNo = &s.regionNo + if s.regionCode == "" { + return fmt.Errorf("region %s is invalid", s.Config.Region) + } + + // Get ZoneNo + resp, err := s.Conn.vserver.V2Api.GetZoneList(&vserver.GetZoneListRequest{RegionCode: &s.regionCode}) + if err != nil { + return err + } + + if len(resp.ZoneList) > 0 { + s.zoneCode = *resp.ZoneList[0].ZoneCode + } + + return nil +} + +func (s *StepValidateTemplate) validateMemberServerImage(fnGetServerImageList func() ([]*server.MemberServerImage, error)) error { + var serverImageName = s.Config.ServerImageName - memberServerImageList, err := s.Conn.server.V2Api.GetMemberServerImageList(reqParams) + memberServerImageList, err := fnGetServerImageList() if err != nil { return err } var isExistMemberServerImageNo = false - for _, image := range memberServerImageList.MemberServerImageList { + for _, image := range memberServerImageList { // Check duplicate server_image_name if *image.MemberServerImageName == serverImageName { return fmt.Errorf("server_image_name %s is exists", serverImageName) @@ -112,16 +162,86 @@ func (s *StepValidateTemplate) validateMemberServerImage() error { return nil } +func (s *StepValidateTemplate) getClassicMemberServerImageList() ([]*server.MemberServerImage, error) { + reqParams := &server.GetMemberServerImageListRequest{ + RegionNo: &s.regionNo, + } + + resp, err := s.Conn.server.V2Api.GetMemberServerImageList(reqParams) + if err != nil { + return nil, err + } + + return resp.MemberServerImageList, nil +} + +func (s *StepValidateTemplate) getVpcMemberServerImageList() ([]*server.MemberServerImage, error) { + reqParams := &vserver.GetMemberServerImageInstanceListRequest{ + RegionCode: &s.regionCode, + } + + memberServerImageList, err := s.Conn.vserver.V2Api.GetMemberServerImageInstanceList(reqParams) + if err != nil { + return nil, err + } + + var results []*server.MemberServerImage + for _, r := range memberServerImageList.MemberServerImageInstanceList { + results = append(results, &server.MemberServerImage{ + MemberServerImageNo: r.MemberServerImageInstanceNo, + MemberServerImageName: r.MemberServerImageName, + MemberServerImageDescription: r.MemberServerImageDescription, + OriginalServerInstanceNo: r.OriginalServerInstanceNo, + OriginalServerProductCode: r.OriginalServerImageProductCode, + }) + } + + return results, nil +} + +func (s *StepValidateTemplate) getClassicServerImageProductList(blockStorageSize *int32) ([]*server.Product, error) { + reqParams := &server.GetServerImageProductListRequest{ + RegionNo: &s.regionNo, + BlockStorageSize: blockStorageSize, + } + + resp, err := s.Conn.server.V2Api.GetServerImageProductList(reqParams) + if err != nil { + return nil, err + } + + return resp.ProductList, nil +} + +func (s *StepValidateTemplate) getVpcServerImageProductList(blockStorageSize *int32) ([]*server.Product, error) { + reqParams := &vserver.GetServerImageProductListRequest{ + RegionCode: &s.regionCode, + BlockStorageSize: blockStorageSize, + } + + resp, err := s.Conn.vserver.V2Api.GetServerImageProductList(reqParams) + if err != nil { + return nil, err + } + + var results []*server.Product + for _, r := range resp.ProductList { + results = append(results, &server.Product{ + ProductCode: r.ProductCode, + ProductName: r.ProductName, + }) + } + + return results, nil +} + func (s *StepValidateTemplate) validateServerImageProduct() error { var serverImageProductCode = s.Config.ServerImageProductCode if serverImageProductCode == "" { return nil } - reqParams := new(server.GetServerImageProductListRequest) - reqParams.RegionNo = &s.regionNo - - serverImageProductList, err := s.Conn.server.V2Api.GetServerImageProductList(reqParams) + serverImageProductList, err := s.getServerImageProductList(nil) if err != nil { return err } @@ -132,7 +252,7 @@ func (s *StepValidateTemplate) validateServerImageProduct() error { table := tablewriter.NewWriter(&buf) table.SetHeader([]string{"Name", "Code"}) - for _, product := range serverImageProductList.ProductList { + for _, product := range serverImageProductList { // Check exist server image product code if *product.ProductCode == serverImageProductCode { isExistServerImage = true @@ -144,14 +264,12 @@ func (s *StepValidateTemplate) validateServerImageProduct() error { } if !isExistServerImage { - reqParams.BlockStorageSize = ncloud.Int32(100) - - serverImageProductList, err := s.Conn.server.V2Api.GetServerImageProductList(reqParams) + serverImageProductList, err := s.getServerImageProductList(ncloud.Int32(100)) if err != nil { return err } - for _, product := range serverImageProductList.ProductList { + for _, product := range serverImageProductList { // Check exist server image product code if *product.ProductCode == serverImageProductCode { isExistServerImage = true @@ -177,21 +295,59 @@ func (s *StepValidateTemplate) validateServerImageProduct() error { return nil } +func (s *StepValidateTemplate) getClassicServerProductList(serverImageProductCode string) ([]*server.Product, error) { + reqParams := &server.GetServerProductListRequest{ + ServerImageProductCode: &serverImageProductCode, + RegionNo: &s.regionNo, + } + + resp, err := s.Conn.server.V2Api.GetServerProductList(reqParams) + + if err != nil { + return nil, err + } + + return resp.ProductList, nil +} + +func (s *StepValidateTemplate) getVpcServerProductList(serverImageProductCode string) ([]*server.Product, error) { + reqParams := &vserver.GetServerProductListRequest{ + ServerImageProductCode: &serverImageProductCode, + RegionCode: &s.regionCode, + } + + resp, err := s.Conn.vserver.V2Api.GetServerProductList(reqParams) + + if err != nil { + return nil, err + } + + var results []*server.Product + for _, r := range resp.ProductList { + results = append(results, &server.Product{ + ProductCode: r.ProductCode, + ProductName: r.ProductName, + ProductType: &server.CommonCode{ + Code: r.ProductType.Code, + CodeName: r.ProductType.CodeName, + }, + }) + } + + return results, nil +} + func (s *StepValidateTemplate) validateServerProductCode() error { var serverImageProductCode = s.Config.ServerImageProductCode var productCode = s.Config.ServerProductCode - reqParams := new(server.GetServerProductListRequest) - reqParams.ServerImageProductCode = &serverImageProductCode - reqParams.RegionNo = &s.regionNo - - resp, err := s.Conn.server.V2Api.GetServerProductList(reqParams) + productList, err := s.getServerProductList(serverImageProductCode) if err != nil { return err } var isExistProductCode = false - for _, product := range resp.ProductList { + for _, product := range productList { // Check exist server image product code if *product.ProductCode == productCode { isExistProductCode = true @@ -216,7 +372,7 @@ func (s *StepValidateTemplate) validateServerProductCode() error { var buf bytes.Buffer table := tablewriter.NewWriter(&buf) table.SetHeader([]string{"Name", "Code"}) - for _, product := range resp.ProductList { + for _, product := range productList { table.Append([]string{*product.ProductName, *product.ProductCode}) } table.Render() @@ -229,15 +385,80 @@ func (s *StepValidateTemplate) validateServerProductCode() error { return nil } +func (s *StepValidateTemplate) validateVpc() error { + if !s.Config.SupportVPC { + return nil + } + + if s.Config.VpcNo != "" { + reqParam := &vpc.GetVpcDetailRequest{ + RegionCode: &s.Config.RegionCode, + VpcNo: &s.Config.VpcNo, + } + + resp, err := s.Conn.vpc.V2Api.GetVpcDetail(reqParam) + if err != nil { + return err + } + + if resp == nil || *resp.TotalRows == 0 { + return fmt.Errorf("cloud not found VPC `vpc_no` [%s]", s.Config.VpcNo) + } + } + + if s.Config.SubnetNo != "" { + reqParam := &vpc.GetSubnetDetailRequest{ + RegionCode: &s.Config.RegionCode, + SubnetNo: &s.Config.SubnetNo, + } + + resp, err := s.Conn.vpc.V2Api.GetSubnetDetail(reqParam) + if err != nil { + return err + } + + if resp != nil && *resp.TotalRows > 0 && *resp.SubnetList[0].SubnetType.Code != "PUBLIC" { + if s.Config.VpcNo == "" { + s.Config.VpcNo = *resp.SubnetList[0].VpcNo + s.Say("Set `vpc_no` is " + s.Config.VpcNo) + } + } else { + return fmt.Errorf("cloud not found public subnet in `vpc_no` [%s]", s.Config.VpcNo) + } + } + + if s.Config.VpcNo != "" && s.Config.SubnetNo == "" { + reqParam := &vpc.GetSubnetListRequest{ + RegionCode: &s.Config.RegionCode, + VpcNo: &s.Config.VpcNo, + SubnetTypeCode: ncloud.String("PUBLIC"), + } + + resp, err := s.Conn.vpc.V2Api.GetSubnetList(reqParam) + if err != nil { + return err + } + + if resp != nil && *resp.TotalRows > 0 { + s.Config.SubnetNo = *resp.SubnetList[0].SubnetNo + s.Say("Set `subnet_no` is " + s.Config.SubnetNo) + } else { + return fmt.Errorf("cloud not found public subnet in `vpc_no` [%s]", s.Config.VpcNo) + } + } + + return nil +} + // Check ImageName / Product Code / Server Image Product Code / Server Product Code... func (s *StepValidateTemplate) validateTemplate() error { // Get RegionNo, ZoneNo - if err := s.getZoneNo(); err != nil { + if err := s.getZone(); err != nil { return err } // Validate member_server_image_no and member_server_image_no - if err := s.validateMemberServerImage(); err != nil { + if err := s.validateMemberServerImage(s.getMemberServerImageList); err != nil { return err } @@ -246,6 +467,11 @@ func (s *StepValidateTemplate) validateTemplate() error { return err } + // Validate VPC + if err := s.validateVpc(); err != nil { + return err + } + // Validate server_product_code return s.validateServerProductCode() } @@ -256,10 +482,10 @@ func (s *StepValidateTemplate) Run(ctx context.Context, state multistep.StateBag err := s.Validate() - state.Put("ZoneNo", s.zoneNo) + state.Put("zone_no", s.zoneNo) if s.FeeSystemTypeCode != "" { - state.Put("FeeSystemTypeCode", s.FeeSystemTypeCode) + state.Put("fee_system_type_code", s.FeeSystemTypeCode) } return processStepResult(err, s.Error, state) diff --git a/builder/ncloud/step_validate_template_test.go b/builder/ncloud/step_validate_template_test.go index 1641715e3..09ade7a71 100644 --- a/builder/ncloud/step_validate_template_test.go +++ b/builder/ncloud/step_validate_template_test.go @@ -23,7 +23,7 @@ func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) { t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == false { + if _, ok := stateBag.GetOk("error"); ok == false { t.Fatal("Expected the step to set stateBag['Error'], but it was not.") } } @@ -43,7 +43,7 @@ func TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) { t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result) } - if _, ok := stateBag.GetOk("Error"); ok == true { + if _, ok := stateBag.GetOk("error"); ok == true { t.Fatalf("Expected the step to not set stateBag['Error'], but it was.") } } diff --git a/builder/ncloud/waiter_block_storage_instance.go b/builder/ncloud/waiter_block_storage_instance.go index d13dbe3a6..6a0109dda 100644 --- a/builder/ncloud/waiter_block_storage_instance.go +++ b/builder/ncloud/waiter_block_storage_instance.go @@ -2,15 +2,17 @@ package ncloud import ( "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "log" "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" ) -func waiterBlockStorageInstanceStatus(conn *NcloudAPIClient, blockStorageInstanceNo *string, status string, timeout time.Duration) error { - reqParams := new(server.GetBlockStorageInstanceListRequest) - reqParams.BlockStorageInstanceNoList = []*string{blockStorageInstanceNo} +func waiterClassicBlockStorageStatus(conn *NcloudAPIClient, blockStorageInstanceNo *string, status string, timeout time.Duration) error { + reqParams := &server.GetBlockStorageInstanceListRequest{ + BlockStorageInstanceNoList: []*string{blockStorageInstanceNo}, + } c1 := make(chan error, 1) @@ -49,10 +51,52 @@ func waiterBlockStorageInstanceStatus(conn *NcloudAPIClient, blockStorageInstanc } } -func waiterDetachedBlockStorageInstance(conn *NcloudAPIClient, serverInstanceNo string, timeout time.Duration) error { - reqParams := new(server.GetBlockStorageInstanceListRequest) - reqParams.ServerInstanceNo = &serverInstanceNo +func waiterVpcBlockStorageStatus(conn *NcloudAPIClient, blockStorageInstanceNo *string, status string, timeout time.Duration) error { + reqParams := &vserver.GetBlockStorageInstanceListRequest{ + BlockStorageInstanceNoList: []*string{blockStorageInstanceNo}, + } + + c1 := make(chan error, 1) + + go func() { + for { + blockStorageInstanceList, err := conn.vserver.V2Api.GetBlockStorageInstanceList(reqParams) + if err != nil { + c1 <- err + return + } + + if status == "DETAC" && len(blockStorageInstanceList.BlockStorageInstanceList) == 0 { + c1 <- nil + return + } + + blockStorageInstance := blockStorageInstanceList.BlockStorageInstanceList[0] + code := blockStorageInstance.BlockStorageInstanceStatus.Code + operationCode := blockStorageInstance.BlockStorageInstanceOperation.Code + + if *code == status && *operationCode == "NULL" { + c1 <- nil + return + } + + log.Println(blockStorageInstance) + time.Sleep(time.Second * 5) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(timeout): + return fmt.Errorf("TIMEOUT : block storage instance status is not changed into status %s", status) + } +} +func waiterClassicDetachedBlockStorage(conn *NcloudAPIClient, serverInstanceNo string, timeout time.Duration) error { + reqParams := &server.GetBlockStorageInstanceListRequest{ + ServerInstanceNo: &serverInstanceNo, + } c1 := make(chan error, 1) go func() { @@ -79,3 +123,34 @@ func waiterDetachedBlockStorageInstance(conn *NcloudAPIClient, serverInstanceNo return fmt.Errorf("TIMEOUT : attached block storage instance is not detached") } } + +func waiterVpcDetachedBlockStorage(conn *NcloudAPIClient, serverInstanceNo string, timeout time.Duration) error { + reqParams := &vserver.GetBlockStorageInstanceListRequest{ + ServerInstanceNo: &serverInstanceNo, + } + c1 := make(chan error, 1) + + go func() { + for { + blockStorageInstanceList, err := conn.vserver.V2Api.GetBlockStorageInstanceList(reqParams) + if err != nil { + c1 <- err + return + } + + if *blockStorageInstanceList.TotalRows == 1 { + c1 <- nil + return + } + + time.Sleep(time.Second * 5) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(timeout): + return fmt.Errorf("TIMEOUT : attached block storage instance is not detached") + } +} diff --git a/builder/ncloud/waiter_server_image_status.go b/builder/ncloud/waiter_server_image_status.go index 76f6712d0..1a281012f 100644 --- a/builder/ncloud/waiter_server_image_status.go +++ b/builder/ncloud/waiter_server_image_status.go @@ -2,15 +2,17 @@ package ncloud import ( "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "log" "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" ) -func waiterMemberServerImageStatus(conn *NcloudAPIClient, memberServerImageNo string, status string, timeout time.Duration) error { - reqParams := new(server.GetMemberServerImageListRequest) - reqParams.MemberServerImageNoList = []*string{&memberServerImageNo} +func waiterClassicMemberServerImageStatus(conn *NcloudAPIClient, memberServerImageNo string, status string, timeout time.Duration) error { + reqParams := &server.GetMemberServerImageListRequest{ + MemberServerImageNoList: []*string{&memberServerImageNo}, + } c1 := make(chan error, 1) @@ -29,7 +31,40 @@ func waiterMemberServerImageStatus(conn *NcloudAPIClient, memberServerImageNo st } log.Printf("Status of member server image [%s] is %s\n", memberServerImageNo, *code) - log.Println(memberServerImageList.MemberServerImageList[0]) + time.Sleep(time.Second * 5) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(timeout): + return fmt.Errorf("TIMEOUT : member server image status is not changed into status %s", status) + } +} + +func waiterVpcMemberServerImageStatus(conn *NcloudAPIClient, memberServerImageNo string, status string, timeout time.Duration) error { + reqParams := &vserver.GetMemberServerImageInstanceDetailRequest{ + MemberServerImageInstanceNo: &memberServerImageNo, + } + + c1 := make(chan error, 1) + + go func() { + for { + memberServerImageList, err := conn.vserver.V2Api.GetMemberServerImageInstanceDetail(reqParams) + if err != nil { + c1 <- err + return + } + + code := memberServerImageList.MemberServerImageInstanceList[0].MemberServerImageInstanceStatus.Code + if *code == status { + c1 <- nil + return + } + + log.Printf("Status of member server image [%s] is %s\n", memberServerImageNo, *code) time.Sleep(time.Second * 5) } }() diff --git a/builder/ncloud/waiter_server_instance_status.go b/builder/ncloud/waiter_server_instance_status.go index 30a050892..8f846cf37 100644 --- a/builder/ncloud/waiter_server_instance_status.go +++ b/builder/ncloud/waiter_server_instance_status.go @@ -2,13 +2,14 @@ package ncloud import ( "fmt" + "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver" "log" "time" "github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server" ) -func waiterServerInstanceStatus(conn *NcloudAPIClient, serverInstanceNo string, status string, timeout time.Duration) error { +func waiterClassicServerInstanceStatus(conn *NcloudAPIClient, serverInstanceNo string, status string, timeout time.Duration) error { reqParams := new(server.GetServerInstanceListRequest) reqParams.ServerInstanceNoList = []*string{&serverInstanceNo} @@ -29,7 +30,40 @@ func waiterServerInstanceStatus(conn *NcloudAPIClient, serverInstanceNo string, } log.Printf("Status of serverInstanceNo [%s] is %s\n", serverInstanceNo, *code) - log.Println(serverInstanceList.ServerInstanceList[0]) + time.Sleep(time.Second * 5) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(timeout): + return fmt.Errorf("TIMEOUT : server instance status is not changed into status %s", status) + } +} + +func waiterVpcServerInstanceStatus(conn *NcloudAPIClient, serverInstanceNo string, status string, timeout time.Duration) error { + reqParams := &vserver.GetServerInstanceDetailRequest{ + ServerInstanceNo: &serverInstanceNo, + } + + c1 := make(chan error, 1) + + go func() { + for { + serverInstanceList, err := conn.vserver.V2Api.GetServerInstanceDetail(reqParams) + if err != nil { + c1 <- err + return + } + + code := serverInstanceList.ServerInstanceList[0].ServerInstanceStatus.Code + if *code == status { + c1 <- nil + return + } + + log.Printf("Status of serverInstanceNo [%s] is %s\n", serverInstanceNo, *code) time.Sleep(time.Second * 5) } }() diff --git a/go.mod b/go.mod index 8e63daf14..d1602e83c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.2.0 github.com/Azure/go-autorest/autorest/to v0.3.0 github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022 - github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0 + github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.7 github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f diff --git a/go.sum b/go.sum index 3f51c4032..c27efbbf9 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nu github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0 h1:0nxjOH7NurPGUWNG5BCrASWjB0uuhGbgJAKLqj2ZDTo= github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0/go.mod h1:P+3VS0ETiQPyWOx3vB/oeC8J3qd7jnVZLYAFwWgGRt8= +github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.7 h1:Kpnbe19WkzVPpLLdAus4LkpNJ+pzLpfAViOUuvPcCqA= +github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.7/go.mod h1:P+3VS0ETiQPyWOx3vB/oeC8J3qd7jnVZLYAFwWgGRt8= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887 h1:Q65o4V0g/KR1sSUZIMf4m1rShb7f1tVHuEt30hfnh2A= diff --git a/website/content/partials/builder/ncloud/Config-not-required.mdx b/website/content/partials/builder/ncloud/Config-not-required.mdx index d25039e3e..b8bc17536 100644 --- a/website/content/partials/builder/ncloud/Config-not-required.mdx +++ b/website/content/partials/builder/ncloud/Config-not-required.mdx @@ -27,7 +27,15 @@ - `region` (string) - Name of the region where you want to create an image. (default: Korea) +- `region_code` (string) - Region Code + - `access_control_group_configuration_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. + +- `support_vpc` (bool) - 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. + +- `subnet_no` (string) - The ID of the associated Subnet + +- `vpc_no` (string) - The ID of the VPC where you want to place the Server Instance