From 5189d65467d9fe857fe1f286c2649c1e1f0063ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=89=E1=85=A5=E1=86=BC=E1=84=83?= =?UTF-8?q?=E1=85=A5=E1=86=A8?= Date: Thu, 11 Jan 2018 18:57:53 +0900 Subject: [PATCH] Support Naver Cloud Platform --- builder/ncloud/artifact.go | 46 +++ builder/ncloud/builder.go | 117 ++++++++ builder/ncloud/config.go | 114 ++++++++ builder/ncloud/config_test.go | 149 ++++++++++ builder/ncloud/ssh.go | 30 ++ builder/ncloud/step.go | 16 ++ .../step_create_block_storage_instance.go | 101 +++++++ ...step_create_block_storage_instance_test.go | 62 +++++ builder/ncloud/step_create_login_key.go | 71 +++++ builder/ncloud/step_create_login_key_test.go | 53 ++++ .../ncloud/step_create_public_ip_instance.go | 167 +++++++++++ .../step_create_public_ip_instance_test.go | 62 +++++ builder/ncloud/step_create_server_image.go | 77 +++++ .../ncloud/step_create_server_image_test.go | 58 ++++ builder/ncloud/step_create_server_instance.go | 129 +++++++++ .../step_create_server_instance_test.go | 58 ++++ .../step_delete_block_storage_instance.go | 95 +++++++ ...step_delete_block_storage_instance_test.go | 57 ++++ builder/ncloud/step_delete_login_key.go | 51 ++++ builder/ncloud/step_delete_login_key_test.go | 55 ++++ .../ncloud/step_delete_public_ip_instance.go | 78 ++++++ .../step_delete_public_ip_instance_test.go | 57 ++++ builder/ncloud/step_get_rootpassword.go | 57 ++++ builder/ncloud/step_get_rootpassword_test.go | 56 ++++ builder/ncloud/step_stop_server_instance.go | 64 +++++ .../ncloud/step_stop_server_instance_test.go | 55 ++++ .../ncloud/step_terminate_server_instance.go | 81 ++++++ .../step_terminate_server_instance_test.go | 54 ++++ builder/ncloud/step_validate_template.go | 263 ++++++++++++++++++ builder/ncloud/step_validate_template_test.go | 54 ++++ .../ncloud/waiter_block_storage_instance.go | 80 ++++++ builder/ncloud/waiter_server_image_status.go | 43 +++ .../ncloud/waiter_server_instance_status.go | 43 +++ command/plugin.go | 2 + 34 files changed, 2555 insertions(+) create mode 100644 builder/ncloud/artifact.go create mode 100644 builder/ncloud/builder.go create mode 100644 builder/ncloud/config.go create mode 100644 builder/ncloud/config_test.go create mode 100644 builder/ncloud/ssh.go create mode 100644 builder/ncloud/step.go create mode 100644 builder/ncloud/step_create_block_storage_instance.go create mode 100644 builder/ncloud/step_create_block_storage_instance_test.go create mode 100644 builder/ncloud/step_create_login_key.go create mode 100644 builder/ncloud/step_create_login_key_test.go create mode 100644 builder/ncloud/step_create_public_ip_instance.go create mode 100644 builder/ncloud/step_create_public_ip_instance_test.go create mode 100644 builder/ncloud/step_create_server_image.go create mode 100644 builder/ncloud/step_create_server_image_test.go create mode 100644 builder/ncloud/step_create_server_instance.go create mode 100644 builder/ncloud/step_create_server_instance_test.go create mode 100644 builder/ncloud/step_delete_block_storage_instance.go create mode 100644 builder/ncloud/step_delete_block_storage_instance_test.go create mode 100644 builder/ncloud/step_delete_login_key.go create mode 100644 builder/ncloud/step_delete_login_key_test.go create mode 100644 builder/ncloud/step_delete_public_ip_instance.go create mode 100644 builder/ncloud/step_delete_public_ip_instance_test.go create mode 100644 builder/ncloud/step_get_rootpassword.go create mode 100644 builder/ncloud/step_get_rootpassword_test.go create mode 100644 builder/ncloud/step_stop_server_instance.go create mode 100644 builder/ncloud/step_stop_server_instance_test.go create mode 100644 builder/ncloud/step_terminate_server_instance.go create mode 100644 builder/ncloud/step_terminate_server_instance_test.go create mode 100644 builder/ncloud/step_validate_template.go create mode 100644 builder/ncloud/step_validate_template_test.go create mode 100644 builder/ncloud/waiter_block_storage_instance.go create mode 100644 builder/ncloud/waiter_server_image_status.go create mode 100644 builder/ncloud/waiter_server_instance_status.go diff --git a/builder/ncloud/artifact.go b/builder/ncloud/artifact.go new file mode 100644 index 000000000..52624e437 --- /dev/null +++ b/builder/ncloud/artifact.go @@ -0,0 +1,46 @@ +package ncloud + +import ( + "bytes" + "fmt" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" +) + +const BuilderID = "ncloud.server.image" + +type Artifact struct { + ServerImage *ncloud.ServerImage +} + +func (*Artifact) BuilderId() string { + return BuilderID +} + +func (a *Artifact) Files() []string { + /* no file */ + return nil +} + +func (a *Artifact) Id() string { + return a.ServerImage.MemberServerImageNo +} + +func (a *Artifact) String() string { + var buf bytes.Buffer + + // TODO : Logging artifact information + buf.WriteString(fmt.Sprintf("%s:\n\n", a.BuilderId())) + buf.WriteString(fmt.Sprintf("Member Server Image Name: %s\n", a.ServerImage.MemberServerImageName)) + buf.WriteString(fmt.Sprintf("Member Server Image No: %s\n", a.ServerImage.MemberServerImageNo)) + + return buf.String() +} + +func (a *Artifact) State(name string) interface{} { + return a.ServerImage.MemberServerImageStatus +} + +func (a *Artifact) Destroy() error { + return nil +} diff --git a/builder/ncloud/builder.go b/builder/ncloud/builder.go new file mode 100644 index 000000000..580514d53 --- /dev/null +++ b/builder/ncloud/builder.go @@ -0,0 +1,117 @@ +package ncloud + +import ( + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +const version = "1.0.0" + +// Builder assume this implements packer.Builder +type Builder struct { + config *Config + stateBag multistep.StateBag + runner multistep.Runner +} + +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + c, warnings, errs := NewConfig(raws...) + if errs != nil { + return warnings, errs + } + b.config = c + + b.stateBag = new(multistep.BasicStateBag) + + return warnings, nil +} + +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + ui.Say("Running builder for Naver Cloud Platform (version: " + version + ") ...") + + ui.Message("Creating Naver Cloud Platform Connection ...") + conn := ncloud.NewConnection(b.config.AccessKey, b.config.SecretKey) + + b.stateBag.Put("hook", hook) + b.stateBag.Put("ui", ui) + + var steps []multistep.Step + + steps = []multistep.Step{} + + if b.config.OSType == "Linux" { + steps = []multistep.Step{ + NewStepValidateTemplate(conn, ui, b.config), + NewStepCreateLoginKey(conn, ui), + NewStepCreateServerInstance(conn, ui, b.config), + NewStepCreateBlockStorageInstance(conn, ui, b.config), + NewStepGetRootPassword(conn, ui), + NewStepCreatePublicIPInstance(conn, ui, b.config), + &communicator.StepConnectSSH{ + Config: &b.config.Comm, + Host: SSHHost, + SSHConfig: SSHConfig(b.config.Comm.SSHUsername), + }, + &common.StepProvision{}, + NewStepStopServerInstance(conn, ui), + NewStepCreateServerImage(conn, ui, b.config), + NewStepDeleteBlockStorageInstance(conn, ui, b.config), + NewStepTerminateServerInstance(conn, ui), + NewStepDeleteLoginKey(conn, ui), + NewStepDeletePublicIPInstance(conn, ui), + } + } else if b.config.OSType == "Windows" { + steps = []multistep.Step{ + NewStepValidateTemplate(conn, ui, b.config), + NewStepCreateLoginKey(conn, ui), + NewStepCreateServerInstance(conn, ui, b.config), + NewStepCreateBlockStorageInstance(conn, ui, b.config), + NewStepGetRootPassword(conn, ui), + NewStepCreatePublicIPInstance(conn, ui, b.config), + &communicator.StepConnectWinRM{ + Config: &b.config.Comm, + Host: func(stateBag multistep.StateBag) (string, error) { + return stateBag.Get("WinRMHost").(string), nil + }, + WinRMConfig: func(state multistep.StateBag) (*communicator.WinRMConfig, error) { + return &communicator.WinRMConfig{ + Username: b.config.Comm.WinRMUser, + Password: state.Get("Password").(string), + }, nil + }, + }, + &common.StepProvision{}, + NewStepStopServerInstance(conn, ui), + NewStepCreateServerImage(conn, ui, b.config), + NewStepDeleteBlockStorageInstance(conn, ui, b.config), + NewStepTerminateServerInstance(conn, ui), + NewStepDeleteLoginKey(conn, ui), + NewStepDeletePublicIPInstance(conn, ui), + } + } + + // Run! + b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) + b.runner.Run(b.stateBag) + + // If there was an error, return that + if rawErr, ok := b.stateBag.GetOk("Error"); ok { + return nil, rawErr.(error) + } + + // Build the artifact and return it + artifact := &Artifact{} + + if serverImage, ok := b.stateBag.GetOk("memberServerImage"); ok { + artifact.ServerImage = serverImage.(*ncloud.ServerImage) + } + + return artifact, nil +} + +func (b *Builder) Cancel() { + b.runner.Cancel() +} diff --git a/builder/ncloud/config.go b/builder/ncloud/config.go new file mode 100644 index 000000000..e18ab822d --- /dev/null +++ b/builder/ncloud/config.go @@ -0,0 +1,114 @@ +package ncloud + +import ( + "errors" + + "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template/interpolate" +) + +// Config is structure to use packer builder plugin for Naver Cloud Platform +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + AccessKey string `mapstructure:"access_key"` + SecretKey string `mapstructure:"secret_key"` + OSType string `mapstructure:"os_type"` + ServerImageProductCode string `mapstructure:"server_image_product_code"` + ServerProductCode string `mapstructure:"server_product_code"` + MemberServerImageNo string `mapstructure:"member_server_image_no"` + ServerImageName string `mapstructure:"server_image_name"` + ServerImageDescription string `mapstructure:"server_image_description"` + UserData string `mapstructure:"user_data"` + BlockStorageSize int `mapstructure:"block_storage_size"` + Region string `mapstructure:"region"` + AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no"` + FeeSystemTypeCode string `mapstructure:"-"` + + Comm communicator.Config `mapstructure:",squash"` + ctx *interpolate.Context +} + +// NewConfig checks parameters +func NewConfig(raws ...interface{}) (*Config, []string, error) { + c := new(Config) + warnings := []string{} + + err := config.Decode(c, &config.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{}, + }, + }, raws...) + if err != nil { + return nil, warnings, err + } + + var errs *packer.MultiError + if es := c.Comm.Prepare(nil); len(es) > 0 { + errs = packer.MultiErrorAppend(errs, es...) + } + + if c.AccessKey == "" { + errs = packer.MultiErrorAppend(errs, errors.New("access_key is required")) + } + + if c.SecretKey == "" { + errs = packer.MultiErrorAppend(errs, errors.New("secret_key is required")) + } + + if c.OSType != "Linux" && c.OSType != "Windows" { + errs = packer.MultiErrorAppend(errs, errors.New("os_type is required. ('Linux' or 'Windows')")) + } + + if c.MemberServerImageNo == "" && c.ServerImageProductCode == "" { + errs = packer.MultiErrorAppend(errs, errors.New("server_image_product_code or member_server_image_no is required")) + } + + if c.MemberServerImageNo != "" && c.ServerImageProductCode != "" { + errs = packer.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 = packer.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 = packer.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 = packer.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 = packer.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 = packer.MultiErrorAppend(errs, errors.New("The size of BlockStorageSize is at least 10 GB and up to 2000GB")) + } else if int(c.BlockStorageSize/10)*10 != c.BlockStorageSize { + return nil, nil, errors.New("BlockStorageSize must be a multiple of 10 GB") + } + } + + if c.UserData != "" && len(c.UserData) > 21847 { + errs = packer.MultiErrorAppend(errs, errors.New("If user_data field is set, length of UserData should be max 21847")) + } + + if c.OSType == "Windows" && c.AccessControlGroupConfigurationNo == "" { + errs = packer.MultiErrorAppend(errs, errors.New("If os_type is Windows, access_control_group_configuration_no is required")) + } + + c.FeeSystemTypeCode = "MTRAT" + + if errs != nil && len(errs.Errors) > 0 { + return nil, warnings, errs + } + + return c, warnings, nil +} diff --git a/builder/ncloud/config_test.go b/builder/ncloud/config_test.go new file mode 100644 index 000000000..7907c7ee1 --- /dev/null +++ b/builder/ncloud/config_test.go @@ -0,0 +1,149 @@ +package ncloud + +import ( + "strings" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "access_key": "access_key", + "secret_key": "secret_key", + "os_type": "Windows", + "server_image_product_code": "SPSW0WINNT000016", + "server_product_code": "SPSVRSSD00000011", + "server_image_name": "packer-test {{timestamp}}", + "server_image_description": "server description", + "block_storage_size": 100, + "user_data": "#!/bin/sh\nyum install -y httpd\ntouch /var/www/html/index.html\nchkconfig --level 2345 httpd on", + "region": "Korea", + "access_control_group_configuration_no": "33", + "communicator": "ssh", + "ssh_username": "root", + } +} + +func testConfigForMemberServerImage() map[string]interface{} { + return map[string]interface{}{ + "access_key": "access_key", + "secret_key": "secret_key", + "os_type": "Windows", + "server_product_code": "SPSVRSSD00000011", + "member_server_image_no": "2440", + "server_image_name": "packer-test {{timestamp}}", + "server_image_description": "server description", + "block_storage_size": 100, + "user_data": "#!/bin/sh\nyum install -y httpd\ntouch /var/www/html/index.html\nchkconfig --level 2345 httpd on", + "region": "Korea", + "access_control_group_configuration_no": "33", + "communicator": "ssh", + "ssh_username": "root", + } +} + +func TestConfigWithServerImageProductCode(t *testing.T) { + raw := testConfig() + + c, _, _ := NewConfig(raw) + + if c.AccessKey != "access_key" { + t.Errorf("Expected 'access_key' to be set to '%s', but got '%s'.", raw["access_key"], c.AccessKey) + } + + if c.SecretKey != "secret_key" { + t.Errorf("Expected 'secret_key' to be set to '%s', but got '%s'.", raw["secret_key"], c.SecretKey) + } + + if c.ServerImageProductCode != "SPSW0WINNT000016" { + t.Errorf("Expected 'server_image_product_code' to be set to '%s', but got '%s'.", raw["server_image_product_code"], c.ServerImageProductCode) + } + + if c.ServerProductCode != "SPSVRSSD00000011" { + t.Errorf("Expected 'server_product_code' to be set to '%s', but got '%s'.", raw["server_product_code"], c.ServerProductCode) + } + + if c.BlockStorageSize != 100 { + t.Errorf("Expected 'block_storage_size' to be set to '%d', but got '%d'.", raw["block_storage_size"], c.BlockStorageSize) + } + + if c.ServerImageDescription != "server description" { + t.Errorf("Expected 'server_image_description_key' to be set to '%s', but got '%s'.", raw["server_image_description"], c.ServerImageDescription) + } + + if c.Region != "Korea" { + t.Errorf("Expected 'region' to be set to '%s', but got '%s'.", raw["server_image_description"], c.Region) + } +} + +func TestConfigWithMemberServerImageCode(t *testing.T) { + raw := testConfigForMemberServerImage() + + c, _, _ := NewConfig(raw) + + if c.AccessKey != "access_key" { + t.Errorf("Expected 'access_key' to be set to '%s', but got '%s'.", raw["access_key"], c.AccessKey) + } + + if c.SecretKey != "secret_key" { + t.Errorf("Expected 'secret_key' to be set to '%s', but got '%s'.", raw["secret_key"], c.SecretKey) + } + + if c.MemberServerImageNo != "2440" { + t.Errorf("Expected 'member_server_image_no' to be set to '%s', but got '%s'.", raw["member_server_image_no"], c.MemberServerImageNo) + } + + if c.ServerProductCode != "SPSVRSSD00000011" { + t.Errorf("Expected 'server_product_code' to be set to '%s', but got '%s'.", raw["server_product_code"], c.ServerProductCode) + } + + if c.BlockStorageSize != 100 { + t.Errorf("Expected 'block_storage_size' to be set to '%d', but got '%d'.", raw["block_storage_size"], c.BlockStorageSize) + } + + if c.ServerImageDescription != "server description" { + t.Errorf("Expected 'server_image_description_key' to be set to '%s', but got '%s'.", raw["server_image_description"], c.ServerImageDescription) + } + + if c.Region != "Korea" { + t.Errorf("Expected 'region' to be set to '%s', but got '%s'.", raw["server_image_description"], c.Region) + } +} + +func TestEmptyConfig(t *testing.T) { + raw := new(map[string]interface{}) + + _, _, err := NewConfig(raw) + + if err == nil { + t.Error("Expected Config to require 'access_key', 'secret_key' and some mendatory fields, but it did not") + } + + if !strings.Contains(err.Error(), "access_key is required") { + t.Error("Expected Config to require 'access_key', but it did not") + } + + if !strings.Contains(err.Error(), "secret_key is required") { + t.Error("Expected Config to require 'secret_key', but it did not") + } + + if !strings.Contains(err.Error(), "server_image_product_code or member_server_image_no is required") { + t.Error("Expected Config to require 'server_image_product_code' or 'member_server_image_no', but it did not") + } +} + +func TestExistsBothServerImageProductCodeAndMemberServerImageNoConfig(t *testing.T) { + raw := map[string]interface{}{ + "access_key": "access_key", + "secret_key": "secret_key", + "os_type": "Windows", + "server_image_product_code": "SPSW0WINNT000016", + "server_product_code": "SPSVRSSD00000011", + "member_server_image_no": "2440", + } + + _, _, err := NewConfig(raw) + + if !strings.Contains(err.Error(), "Only one of server_image_product_code and member_server_image_no can be set") { + t.Error("Expected Config to require Only one of 'server_image_product_code' and 'member_server_image_no' can be set, but it did not") + } +} diff --git a/builder/ncloud/ssh.go b/builder/ncloud/ssh.go new file mode 100644 index 000000000..ee7d99226 --- /dev/null +++ b/builder/ncloud/ssh.go @@ -0,0 +1,30 @@ +package ncloud + +import ( + packerssh "github.com/hashicorp/packer/communicator/ssh" + "github.com/mitchellh/multistep" + "golang.org/x/crypto/ssh" +) + +func SSHHost(state multistep.StateBag) (string, error) { + host := state.Get("SSHHost").(string) + return host, nil +} + +// SSHConfig returns a function that can be used for the SSH communicator +// config for connecting to the specified host via SSH +func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) { + return func(state multistep.StateBag) (*ssh.ClientConfig, error) { + password := state.Get("Password").(string) + + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + ssh.KeyboardInteractive( + packerssh.PasswordKeyboardInteractive(password)), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, nil + } +} diff --git a/builder/ncloud/step.go b/builder/ncloud/step.go new file mode 100644 index 000000000..ba71efbee --- /dev/null +++ b/builder/ncloud/step.go @@ -0,0 +1,16 @@ +package ncloud + +import ( + "github.com/mitchellh/multistep" +) + +func processStepResult(err error, sayError func(error), state multistep.StateBag) multistep.StepAction { + if err != nil { + state.Put("Error", err) + sayError(err) + + return multistep.ActionHalt + } + + return multistep.ActionContinue +} diff --git a/builder/ncloud/step_create_block_storage_instance.go b/builder/ncloud/step_create_block_storage_instance.go new file mode 100644 index 000000000..ef7306264 --- /dev/null +++ b/builder/ncloud/step_create_block_storage_instance.go @@ -0,0 +1,101 @@ +package ncloud + +import ( + "errors" + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +// StepCreateBlockStorageInstance struct is for making extra block storage +type StepCreateBlockStorageInstance struct { + Conn *ncloud.Conn + CreateBlockStorageInstance func(serverInstanceNo string) (string, error) + Say func(message string) + Error func(e error) + Config *Config +} + +// NewStepCreateBlockStorageInstance make StepCreateBlockStorage struct to make extra block storage +func NewStepCreateBlockStorageInstance(conn *ncloud.Conn, ui packer.Ui, config *Config) *StepCreateBlockStorageInstance { + var step = &StepCreateBlockStorageInstance{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + step.CreateBlockStorageInstance = step.createBlockStorageInstance + + return step +} + +func (s *StepCreateBlockStorageInstance) createBlockStorageInstance(serverInstanceNo string) (string, error) { + + reqParams := new(ncloud.RequestBlockStorageInstance) + reqParams.BlockStorageSize = s.Config.BlockStorageSize + reqParams.ServerInstanceNo = serverInstanceNo + + blockStorageInstanceList, err := s.Conn.CreateBlockStorageInstance(reqParams) + if err != nil { + return "", fmt.Errorf("error code: %d, error message: %s", blockStorageInstanceList.ReturnCode, blockStorageInstanceList.ReturnMessage) + } + + log.Println("Block Storage Instance information : ", blockStorageInstanceList.BlockStorageInstance[0]) + + if err := waiterBlockStorageInstanceStatus(s.Conn, blockStorageInstanceList.BlockStorageInstance[0].BlockStorageInstanceNo, "ATTAC", 10*time.Minute); err != nil { + return "", errors.New("TIMEOUT : Block Storage instance status is not attached") + } + + return blockStorageInstanceList.BlockStorageInstance[0].BlockStorageInstanceNo, nil +} + +func (s *StepCreateBlockStorageInstance) Run(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) + + blockStorageInstanceNo, err := s.CreateBlockStorageInstance(serverInstanceNo) + if err == nil { + state.Put("BlockStorageInstanceNo", blockStorageInstanceNo) + } + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreateBlockStorageInstance) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if !cancelled && !halted { + return + } + + if s.Config.BlockStorageSize == 0 { + return + } + + if blockStorageInstanceNo, ok := state.GetOk("BlockStorageInstanceNo"); ok { + s.Say("Clean up Block Storage Instance") + no := blockStorageInstanceNo.(string) + blockStorageInstanceList, err := s.Conn.DeleteBlockStorageInstances([]string{no}) + if err != nil { + return + } + + s.Say(fmt.Sprintf("Block Storage Instance is deleted. Block Storage InstanceNo is %s", no)) + log.Println("Block Storage Instance information : ", blockStorageInstanceList.BlockStorageInstance[0]) + + if err := waiterBlockStorageInstanceStatus(s.Conn, no, "DETAC", 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 new file mode 100644 index 000000000..101e94658 --- /dev/null +++ b/builder/ncloud/step_create_block_storage_instance_test.go @@ -0,0 +1,62 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepCreateBlockStorageInstanceShouldFailIfOperationCreateBlockStorageInstanceFails(t *testing.T) { + + var testSubject = &StepCreateBlockStorageInstance{ + CreateBlockStorageInstance: func(serverInstanceNo string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: new(Config), + } + + testSubject.Config.BlockStorageSize = 10 + + stateBag := createTestStateBagStepCreateBlockStorageInstance() + + var result = testSubject.Run(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 TestStepCreateBlockStorageInstanceShouldPassIfOperationCreateBlockStorageInstancePasses(t *testing.T) { + var testSubject = &StepCreateBlockStorageInstance{ + CreateBlockStorageInstance: func(serverInstanceNo string) (string, error) { return "a", nil }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: new(Config), + } + + testSubject.Config.BlockStorageSize = 10 + + stateBag := createTestStateBagStepCreateBlockStorageInstance() + + var result = testSubject.Run(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 createTestStateBagStepCreateBlockStorageInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("InstanceNo", "a") + + return stateBag +} diff --git a/builder/ncloud/step_create_login_key.go b/builder/ncloud/step_create_login_key.go new file mode 100644 index 000000000..7dddebd25 --- /dev/null +++ b/builder/ncloud/step_create_login_key.go @@ -0,0 +1,71 @@ +package ncloud + +import ( + "fmt" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type LoginKey struct { + KeyName string + PrivateKey string +} + +type StepCreateLoginKey struct { + Conn *ncloud.Conn + CreateLoginKey func() (*LoginKey, error) + Say func(message string) + Error func(e error) +} + +func NewStepCreateLoginKey(conn *ncloud.Conn, ui packer.Ui) *StepCreateLoginKey { + var step = &StepCreateLoginKey{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + } + + step.CreateLoginKey = step.createLoginKey + + return step +} + +func (s *StepCreateLoginKey) createLoginKey() (*LoginKey, error) { + KeyName := fmt.Sprintf("packer-%d", time.Now().Unix()) + + privateKey, err := s.Conn.CreateLoginKey(KeyName) + if err != nil { + return nil, fmt.Errorf("error code: %d , error message: %s", privateKey.ReturnCode, privateKey.ReturnMessage) + } + + return &LoginKey{KeyName, privateKey.PrivateKey}, nil +} + +func (s *StepCreateLoginKey) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Create Login Key") + + loginKey, err := s.CreateLoginKey() + if err == nil { + state.Put("LoginKey", loginKey) + s.Say(fmt.Sprintf("Login Key[%s] is created", loginKey.KeyName)) + } + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreateLoginKey) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if !cancelled && !halted { + return + } + + if loginKey, ok := state.GetOk("LoginKey"); ok { + s.Say("Clean up login key") + s.Conn.DeleteLoginKey(loginKey.(*LoginKey).KeyName) + } +} diff --git a/builder/ncloud/step_create_login_key_test.go b/builder/ncloud/step_create_login_key_test.go new file mode 100644 index 000000000..374d10330 --- /dev/null +++ b/builder/ncloud/step_create_login_key_test.go @@ -0,0 +1,53 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepCreateLoginKeyShouldFailIfOperationCreateLoginKeyFails(t *testing.T) { + var testSubject = &StepCreateLoginKey{ + CreateLoginKey: func() (*LoginKey, error) { return nil, fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateLoginKey() + + var result = testSubject.Run(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 TestStepCreateLoginKeyShouldPassIfOperationCreateLoginKeyPasses(t *testing.T) { + var testSubject = &StepCreateLoginKey{ + CreateLoginKey: func() (*LoginKey, error) { return &LoginKey{"a", "b"}, nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateLoginKey() + + var result = testSubject.Run(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 createTestStateBagStepCreateLoginKey() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + return stateBag +} diff --git a/builder/ncloud/step_create_public_ip_instance.go b/builder/ncloud/step_create_public_ip_instance.go new file mode 100644 index 000000000..88d98eb6c --- /dev/null +++ b/builder/ncloud/step_create_public_ip_instance.go @@ -0,0 +1,167 @@ +package ncloud + +import ( + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepCreatePublicIPInstance struct { + Conn *ncloud.Conn + CreatePublicIPInstance func(serverInstanceNo string) (*ncloud.PublicIPInstance, error) + WaiterAssociatePublicIPToServerInstance func(serverInstanceNo string, publicIP string) error + Say func(message string) + Error func(e error) + Config *Config +} + +func NewStepCreatePublicIPInstance(conn *ncloud.Conn, ui packer.Ui, config *Config) *StepCreatePublicIPInstance { + var step = &StepCreatePublicIPInstance{ + 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 + + return step +} + +func (s *StepCreatePublicIPInstance) waiterAssociatePublicIPToServerInstance(serverInstanceNo string, publicIP string) error { + reqParams := new(ncloud.RequestGetServerInstanceList) + reqParams.ServerInstanceNoList = []string{serverInstanceNo} + + c1 := make(chan error, 1) + + go func() { + for { + serverInstanceList, err := s.Conn.GetServerInstanceList(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 *StepCreatePublicIPInstance) createPublicIPInstance(serverInstanceNo string) (*ncloud.PublicIPInstance, error) { + reqParams := new(ncloud.RequestCreatePublicIPInstance) + reqParams.ServerInstanceNo = serverInstanceNo + + publicIPInstanceList, err := s.Conn.CreatePublicIPInstance(reqParams) + if err != nil { + return nil, fmt.Errorf("error code: %d, error message: %s", publicIPInstanceList.ReturnCode, publicIPInstanceList.ReturnMessage) + } + + publicIPInstance := publicIPInstanceList.PublicIPInstanceList[0] + publicIP := publicIPInstance.PublicIP + s.Say(fmt.Sprintf("Public IP Instance [%s:%s] is created", publicIPInstance.PublicIPInstanceNo, publicIP)) + + err = s.waiterAssociatePublicIPToServerInstance(serverInstanceNo, publicIP) + + return &publicIPInstance, nil +} + +func (s *StepCreatePublicIPInstance) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Create Public IP Instance") + + serverInstanceNo := state.Get("InstanceNo").(string) + + publicIPInstance, err := s.CreatePublicIPInstance(serverInstanceNo) + if err == nil { + switch s.Config.OSType { + case "Linux": + state.Put("SSHHost", publicIPInstance.PublicIP) + case "Windows": + state.Put("WinRMHost", publicIPInstance.PublicIP) + } + + state.Put("PublicIPInstance", publicIPInstance) + } + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreatePublicIPInstance) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if !cancelled && !halted { + return + } + + publicIPInstance, ok := state.GetOk("PublicIPInstance") + if !ok { + return + } + + s.Say("Clean up Public IP Instance") + publicIPInstanceNo := publicIPInstance.(*ncloud.PublicIPInstance).PublicIPInstanceNo + s.waitPublicIPInstanceStatus(publicIPInstanceNo, "USED") + + log.Println("Disassociate Public IP Instance ", publicIPInstanceNo) + s.Conn.DisassociatePublicIP(publicIPInstanceNo) + + s.waitPublicIPInstanceStatus(publicIPInstanceNo, "CREAT") + + reqParams := new(ncloud.RequestDeletePublicIPInstances) + reqParams.PublicIPInstanceNoList = []string{publicIPInstanceNo} + + log.Println("Delete Public IP Instance ", publicIPInstanceNo) + s.Conn.DeletePublicIPInstances(reqParams) +} + +func (s *StepCreatePublicIPInstance) waitPublicIPInstanceStatus(publicIPInstanceNo string, status string) { + c1 := make(chan error, 1) + + go func() { + reqParams := new(ncloud.RequestPublicIPInstanceList) + reqParams.PublicIPInstanceNoList = []string{publicIPInstanceNo} + + for { + resp, err := s.Conn.GetPublicIPInstanceList(reqParams) + if err != nil { + log.Printf("error code: %d, error message: %s", resp.ReturnCode, resp.ReturnMessage) + c1 <- err + return + } + + instance := resp.PublicIPInstanceList[0] + if instance.PublicIPInstanceStatus.Code == status && instance.PublicIPInstanceOperation.Code == "NULL" { + c1 <- nil + return + } + + time.Sleep(time.Second * 2) + } + }() + + select { + case <-c1: + return + case <-time.After(time.Second * 60): + return + } +} diff --git a/builder/ncloud/step_create_public_ip_instance_test.go b/builder/ncloud/step_create_public_ip_instance_test.go new file mode 100644 index 000000000..30eaca017 --- /dev/null +++ b/builder/ncloud/step_create_public_ip_instance_test.go @@ -0,0 +1,62 @@ +package ncloud + +import ( + "fmt" + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCreatePublicIPInstanceShouldFailIfOperationCreatePublicIPInstanceFails(t *testing.T) { + var testSubject = &StepCreatePublicIPInstance{ + CreatePublicIPInstance: func(serverInstanceNo string) (*ncloud.PublicIPInstance, error) { + return nil, fmt.Errorf("!! Unit Test FAIL !!") + }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateServerImage() + + var result = testSubject.Run(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 TestStepCreatePublicIPInstanceShouldPassIfOperationCreatePublicIPInstancePasses(t *testing.T) { + var testSubject = &StepCreatePublicIPInstance{ + CreatePublicIPInstance: func(serverInstanceNo string) (*ncloud.PublicIPInstance, error) { + return &ncloud.PublicIPInstance{PublicIPInstanceNo: "a", PublicIP: "b"}, nil + }, + Say: func(message string) {}, + Error: func(e error) {}, + Config: &Config{OSType: "Windows"}, + } + + stateBag := createTestStateBagStepCreatePublicIPInstance() + + var result = testSubject.Run(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 createTestStateBagStepCreatePublicIPInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("InstanceNo", "a") + + return stateBag +} diff --git a/builder/ncloud/step_create_server_image.go b/builder/ncloud/step_create_server_image.go new file mode 100644 index 000000000..4256664f4 --- /dev/null +++ b/builder/ncloud/step_create_server_image.go @@ -0,0 +1,77 @@ +package ncloud + +import ( + "errors" + "fmt" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepCreateServerImage struct { + Conn *ncloud.Conn + CreateServerImage func(serverInstanceNo string) (*ncloud.ServerImage, error) + Say func(message string) + Error func(e error) + Config *Config +} + +func NewStepCreateServerImage(conn *ncloud.Conn, ui packer.Ui, config *Config) *StepCreateServerImage { + var step = &StepCreateServerImage{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + step.CreateServerImage = step.createServerImage + + return step +} + +func (s *StepCreateServerImage) createServerImage(serverInstanceNo string) (*ncloud.ServerImage, error) { + // 서버 인스턴스 상태가 정지 중일 경우에는 서버 이미지 생성할 수 없음. + if err := waiterServerInstanceStatus(s.Conn, serverInstanceNo, "NSTOP", 1*time.Minute); err != nil { + return nil, err + } + + reqParams := new(ncloud.RequestCreateServerImage) + reqParams.MemberServerImageName = s.Config.ServerImageName + reqParams.MemberServerImageDescription = s.Config.ServerImageDescription + reqParams.ServerInstanceNo = serverInstanceNo + + memberServerImageList, err := s.Conn.CreateMemberServerImage(reqParams) + if err != nil { + return nil, fmt.Errorf("error code: %d , error message: %s", memberServerImageList.ReturnCode, memberServerImageList.ReturnMessage) + } + + serverImage := memberServerImageList.MemberServerImageList[0] + + 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 { + return nil, errors.New("TIMEOUT : Server Image is not created") + } + + s.Say(fmt.Sprintf("Server Image[%s:%s] is created", serverImage.MemberServerImageName, serverImage.MemberServerImageNo)) + + return &serverImage, nil +} + +func (s *StepCreateServerImage) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Create Server Image") + + serverInstanceNo := state.Get("InstanceNo").(string) + + serverImage, err := s.CreateServerImage(serverInstanceNo) + if err == nil { + state.Put("memberServerImage", serverImage) + } + + return processStepResult(err, s.Error, state) +} + +func (*StepCreateServerImage) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_create_server_image_test.go b/builder/ncloud/step_create_server_image_test.go new file mode 100644 index 000000000..41a40971a --- /dev/null +++ b/builder/ncloud/step_create_server_image_test.go @@ -0,0 +1,58 @@ +package ncloud + +import ( + "fmt" + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepCreateServerImageShouldFailIfOperationCreateServerImageFails(t *testing.T) { + var testSubject = &StepCreateServerImage{ + CreateServerImage: func(serverInstanceNo string) (*ncloud.ServerImage, error) { + return nil, fmt.Errorf("!! Unit Test FAIL !!") + }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateServerImage() + + var result = testSubject.Run(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 TestStepCreateServerImageShouldPassIfOperationCreateServerImagePasses(t *testing.T) { + var testSubject = &StepCreateServerImage{ + CreateServerImage: func(serverInstanceNo string) (*ncloud.ServerImage, error) { return nil, nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateServerImage() + + var result = testSubject.Run(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 createTestStateBagStepCreateServerImage() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("InstanceNo", "a") + + return stateBag +} diff --git a/builder/ncloud/step_create_server_instance.go b/builder/ncloud/step_create_server_instance.go new file mode 100644 index 000000000..040e72eb1 --- /dev/null +++ b/builder/ncloud/step_create_server_instance.go @@ -0,0 +1,129 @@ +package ncloud + +import ( + "errors" + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepCreateServerInstance struct { + Conn *ncloud.Conn + CreateServerInstance func(loginKeyName string, zoneNo string) (string, error) + CheckServerInstanceStatusIsRunning func(serverInstanceNo string) error + Say func(message string) + Error func(e error) + Config *Config + serverInstanceNo string +} + +func NewStepCreateServerInstance(conn *ncloud.Conn, ui packer.Ui, config *Config) *StepCreateServerInstance { + var step = &StepCreateServerInstance{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + step.CreateServerInstance = step.createServerInstance + + return step +} + +func (s *StepCreateServerInstance) createServerInstance(loginKeyName string, zoneNo string) (string, error) { + reqParams := new(ncloud.RequestCreateServerInstance) + reqParams.ServerProductCode = s.Config.ServerProductCode + reqParams.MemberServerImageNo = s.Config.MemberServerImageNo + if s.Config.MemberServerImageNo == "" { + reqParams.ServerImageProductCode = s.Config.ServerImageProductCode + } + reqParams.LoginKeyName = loginKeyName + reqParams.ZoneNo = zoneNo + reqParams.FeeSystemTypeCode = s.Config.FeeSystemTypeCode + + if s.Config.UserData != "" { + reqParams.UserData = s.Config.UserData + } + + if s.Config.AccessControlGroupConfigurationNo != "" { + reqParams.AccessControlGroupConfigurationNoList = []string{s.Config.AccessControlGroupConfigurationNo} + } + + serverInstanceList, err := s.Conn.CreateServerInstances(reqParams) + if err != nil { + return "", fmt.Errorf("error code: %d, error message: %s", serverInstanceList.ReturnCode, serverInstanceList.ReturnMessage) + } + + 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 := waiterServerInstanceStatus(s.Conn, s.serverInstanceNo, "RUN", 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) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Create Server Instance") + + var loginKey = state.Get("LoginKey").(*LoginKey) + var zoneNo = state.Get("ZoneNo").(string) + + serverInstanceNo, err := s.CreateServerInstance(loginKey.KeyName, zoneNo) + if err == nil { + state.Put("InstanceNo", serverInstanceNo) + } + + return processStepResult(err, s.Error, state) +} + +func (s *StepCreateServerInstance) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if !cancelled && !halted { + return + } + + if s.serverInstanceNo == "" { + return + } + + reqParams := new(ncloud.RequestGetServerInstanceList) + reqParams.ServerInstanceNoList = []string{s.serverInstanceNo} + + serverInstanceList, err := s.Conn.GetServerInstanceList(reqParams) + if err != nil || serverInstanceList.TotalRows == 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(ncloud.RequestStopServerInstances) + reqParams.ServerInstanceNoList = []string{s.serverInstanceNo} + + log.Println("Stop Server Instance") + s.Conn.StopServerInstances(reqParams) + waiterServerInstanceStatus(s.Conn, s.serverInstanceNo, "NSTOP", time.Minute) + } + + // terminate server instance + if serverInstance.ServerInstanceStatus.Code != "TERMT" { + reqParams := new(ncloud.RequestTerminateServerInstances) + reqParams.ServerInstanceNoList = []string{s.serverInstanceNo} + + log.Println("Terminate Server Instance") + s.Conn.TerminateServerInstances(reqParams) + } +} diff --git a/builder/ncloud/step_create_server_instance_test.go b/builder/ncloud/step_create_server_instance_test.go new file mode 100644 index 000000000..327228b75 --- /dev/null +++ b/builder/ncloud/step_create_server_instance_test.go @@ -0,0 +1,58 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepCreateServerInstanceShouldFailIfOperationCreateFails(t *testing.T) { + var testSubject = &StepCreateServerInstance{ + CreateServerInstance: func(loginKeyName string, zoneNo string) (string, error) { + return "", fmt.Errorf("!! Unit Test FAIL !!") + }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateServerInstance() + + var result = testSubject.Run(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 TestStepCreateServerInstanceShouldPassIfOperationCreatePasses(t *testing.T) { + var testSubject = &StepCreateServerInstance{ + CreateServerInstance: func(loginKeyName string, zoneNo string) (string, error) { return "", nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepCreateServerInstance() + + var result = testSubject.Run(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 createTestStateBagStepCreateServerInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("LoginKey", &LoginKey{"a", "b"}) + stateBag.Put("ZoneNo", "1") + + return stateBag +} diff --git a/builder/ncloud/step_delete_block_storage_instance.go b/builder/ncloud/step_delete_block_storage_instance.go new file mode 100644 index 000000000..3c6c44b82 --- /dev/null +++ b/builder/ncloud/step_delete_block_storage_instance.go @@ -0,0 +1,95 @@ +package ncloud + +import ( + "errors" + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepDeleteBlockStorageInstance struct { + Conn *ncloud.Conn + DeleteBlockStorageInstance func(blockStorageInstanceNo string) error + Say func(message string) + Error func(e error) + Config *Config +} + +func NewStepDeleteBlockStorageInstance(conn *ncloud.Conn, ui packer.Ui, config *Config) *StepDeleteBlockStorageInstance { + var step = &StepDeleteBlockStorageInstance{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + step.DeleteBlockStorageInstance = step.deleteBlockStorageInstance + + return step +} + +func (s *StepDeleteBlockStorageInstance) getBlockInstanceList(serverInstanceNo string) []string { + reqParams := new(ncloud.RequestBlockStorageInstanceList) + reqParams.ServerInstanceNo = serverInstanceNo + + blockStorageInstanceList, err := s.Conn.GetBlockStorageInstance(reqParams) + if err != nil { + return nil + } + + if blockStorageInstanceList.TotalRows == 1 { + return nil + } + + var instanceList []string + + for _, blockStorageInstance := range blockStorageInstanceList.BlockStorageInstance { + log.Println(blockStorageInstance) + if blockStorageInstance.BlockStorageType.Code != "BASIC" { + instanceList = append(instanceList, blockStorageInstance.BlockStorageInstanceNo) + } + } + + return instanceList +} + +func (s *StepDeleteBlockStorageInstance) deleteBlockStorageInstance(serverInstanceNo string) error { + blockStorageInstanceList := s.getBlockInstanceList(serverInstanceNo) + if blockStorageInstanceList == nil || len(blockStorageInstanceList) == 0 { + return nil + } + + result, err := s.Conn.DeleteBlockStorageInstances(blockStorageInstanceList) + if err != nil { + return fmt.Errorf("error code: %d , error message: %s", result.ReturnCode, result.ReturnMessage) + } + + s.Say(fmt.Sprintf("Block Storage Instance is deleted. Block Storage InstanceNo is %s", blockStorageInstanceList)) + + if err := waiterDetachedBlockStorageInstance(s.Conn, serverInstanceNo, time.Minute); err != nil { + return errors.New("TIMEOUT : Block Storage instance status is not deattached") + } + + return nil +} + +func (s *StepDeleteBlockStorageInstance) Run(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) + + err := s.DeleteBlockStorageInstance(serverInstanceNo) + + return processStepResult(err, s.Error, state) +} + +func (*StepDeleteBlockStorageInstance) 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 new file mode 100644 index 000000000..ce0f0cb09 --- /dev/null +++ b/builder/ncloud/step_delete_block_storage_instance_test.go @@ -0,0 +1,57 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +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}, + } + + stateBag := createTestStateBagStepDeleteBlockStorageInstance() + + var result = testSubject.Run(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 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}, + } + + stateBag := createTestStateBagStepDeleteBlockStorageInstance() + + var result = testSubject.Run(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 createTestStateBagStepDeleteBlockStorageInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("InstanceNo", "1") + + return stateBag +} diff --git a/builder/ncloud/step_delete_login_key.go b/builder/ncloud/step_delete_login_key.go new file mode 100644 index 000000000..b0cfbf7a1 --- /dev/null +++ b/builder/ncloud/step_delete_login_key.go @@ -0,0 +1,51 @@ +package ncloud + +import ( + "fmt" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepDeleteLoginKey struct { + Conn *ncloud.Conn + DeleteLoginKey func(keyName string) error + Say func(message string) + Error func(e error) +} + +func NewStepDeleteLoginKey(conn *ncloud.Conn, ui packer.Ui) *StepDeleteLoginKey { + var step = &StepDeleteLoginKey{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + } + + step.DeleteLoginKey = step.deleteLoginKey + + return step +} + +func (s *StepDeleteLoginKey) deleteLoginKey(keyName string) error { + resp, err := s.Conn.DeleteLoginKey(keyName) + if err != nil { + return fmt.Errorf("error code: %d , error message: %s", resp.ReturnCode, resp.ReturnMessage) + } + + return nil +} + +func (s *StepDeleteLoginKey) Run(state multistep.StateBag) multistep.StepAction { + var loginKey = state.Get("LoginKey").(*LoginKey) + + err := s.DeleteLoginKey(loginKey.KeyName) + if err == nil { + s.Say(fmt.Sprintf("Login Key[%s] is deleted", loginKey.KeyName)) + } + + return processStepResult(err, s.Error, state) +} + +func (*StepDeleteLoginKey) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_delete_login_key_test.go b/builder/ncloud/step_delete_login_key_test.go new file mode 100644 index 000000000..38ce1e22c --- /dev/null +++ b/builder/ncloud/step_delete_login_key_test.go @@ -0,0 +1,55 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepDeleteLoginKeyShouldFailIfOperationDeleteLoginKeyFails(t *testing.T) { + var testSubject = &StepDeleteLoginKey{ + DeleteLoginKey: func(keyName string) error { return fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := DeleteTestStateBagStepDeleteLoginKey() + + var result = testSubject.Run(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 TestStepDeleteLoginKeyShouldPassIfOperationDeleteLoginKeyPasses(t *testing.T) { + var testSubject = &StepDeleteLoginKey{ + DeleteLoginKey: func(keyName string) error { return nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := DeleteTestStateBagStepDeleteLoginKey() + + var result = testSubject.Run(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 DeleteTestStateBagStepDeleteLoginKey() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("LoginKey", &LoginKey{"a", "b"}) + + return stateBag +} diff --git a/builder/ncloud/step_delete_public_ip_instance.go b/builder/ncloud/step_delete_public_ip_instance.go new file mode 100644 index 000000000..4a1401984 --- /dev/null +++ b/builder/ncloud/step_delete_public_ip_instance.go @@ -0,0 +1,78 @@ +package ncloud + +import ( + "errors" + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepDeletePublicIPInstance struct { + Conn *ncloud.Conn + DeletePublicIPInstance func(publicIPInstanceNo string) error + Say func(message string) + Error func(e error) +} + +func NewStepDeletePublicIPInstance(conn *ncloud.Conn, ui packer.Ui) *StepDeletePublicIPInstance { + var step = &StepDeletePublicIPInstance{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + } + + step.DeletePublicIPInstance = step.deletePublicIPInstance + + return step +} + +func (s *StepDeletePublicIPInstance) deletePublicIPInstance(publicIPInstanceNo string) error { + reqParams := new(ncloud.RequestDeletePublicIPInstances) + reqParams.PublicIPInstanceNoList = []string{publicIPInstanceNo} + + c1 := make(chan error, 1) + + go func() { + for { + resp, err := s.Conn.DeletePublicIPInstances(reqParams) + if err != nil && (resp.ReturnCode == 24073 || resp.ReturnCode == 25032) { + // error code : 24073 : Unable to destroy the server since a public IP is associated with the server. First, please disassociate a public IP from the server. + // error code : 25032 : You may not delete sk since (other) user is changing the target official IP settings. + log.Println(resp.ReturnCode, resp.ReturnMessage) + } else if err != nil { + c1 <- fmt.Errorf("error code: %d, error message: %s", resp.ReturnCode, resp.ReturnMessage) + return + } else if err == nil { + s.Say(fmt.Sprintf("Public IP Instance [%s] is deleted.", publicIPInstanceNo)) + c1 <- nil + return + } + + time.Sleep(time.Second * 5) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(time.Second * 60): + return errors.New("TIMEOUT : Can't delete server instance") + } +} + +func (s *StepDeletePublicIPInstance) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Delete Public IP Instance") + + publicIPInstance := state.Get("PublicIPInstance").(*ncloud.PublicIPInstance) + + err := s.DeletePublicIPInstance(publicIPInstance.PublicIPInstanceNo) + + return processStepResult(err, s.Error, state) +} + +func (*StepDeletePublicIPInstance) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_delete_public_ip_instance_test.go b/builder/ncloud/step_delete_public_ip_instance_test.go new file mode 100644 index 000000000..b9fa04ec8 --- /dev/null +++ b/builder/ncloud/step_delete_public_ip_instance_test.go @@ -0,0 +1,57 @@ +package ncloud + +import ( + "fmt" + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepDeletePublicIPInstanceShouldFailIfOperationDeletePublicIPInstanceFails(t *testing.T) { + var testSubject = &StepDeletePublicIPInstance{ + DeletePublicIPInstance: func(publicIPInstanceNo string) error { return fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepDeletePublicIPInstance() + + var result = testSubject.Run(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 TestStepDeletePublicIPInstanceShouldPassIfOperationDeletePublicIPInstancePasses(t *testing.T) { + var testSubject = &StepDeletePublicIPInstance{ + DeletePublicIPInstance: func(publicIPInstanceNo string) error { return nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepDeletePublicIPInstance() + + var result = testSubject.Run(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 createTestStateBagStepDeletePublicIPInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("PublicIPInstance", &ncloud.PublicIPInstance{PublicIPInstanceNo: "22"}) + + return stateBag +} diff --git a/builder/ncloud/step_get_rootpassword.go b/builder/ncloud/step_get_rootpassword.go new file mode 100644 index 000000000..c5070d179 --- /dev/null +++ b/builder/ncloud/step_get_rootpassword.go @@ -0,0 +1,57 @@ +package ncloud + +import ( + "fmt" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepGetRootPassword struct { + Conn *ncloud.Conn + GetRootPassword func(serverInstanceNo string, privateKey string) (string, error) + Say func(message string) + Error func(e error) +} + +func NewStepGetRootPassword(conn *ncloud.Conn, ui packer.Ui) *StepGetRootPassword { + var step = &StepGetRootPassword{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + } + + step.GetRootPassword = step.getRootPassword + + return step +} + +func (s *StepGetRootPassword) getRootPassword(serverInstanceNo string, privateKey string) (string, error) { + reqParams := new(ncloud.RequestGetRootPassword) + reqParams.ServerInstanceNo = serverInstanceNo + reqParams.PrivateKey = privateKey + + rootPassword, err := s.Conn.GetRootPassword(reqParams) + if err != nil { + return "", fmt.Errorf("error code: %d, error message: %s", rootPassword.ReturnCode, rootPassword.ReturnMessage) + } + + return rootPassword.RootPassword, nil +} + +func (s *StepGetRootPassword) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Get Root Password") + + serverInstanceNo := state.Get("InstanceNo").(string) + loginKey := state.Get("LoginKey").(*LoginKey) + + rootPassword, err := s.GetRootPassword(serverInstanceNo, loginKey.PrivateKey) + + state.Put("Password", rootPassword) + + return processStepResult(err, s.Error, state) +} + +func (*StepGetRootPassword) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_get_rootpassword_test.go b/builder/ncloud/step_get_rootpassword_test.go new file mode 100644 index 000000000..b9ed6aa56 --- /dev/null +++ b/builder/ncloud/step_get_rootpassword_test.go @@ -0,0 +1,56 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepGetRootPasswordShouldFailIfOperationGetRootPasswordFails(t *testing.T) { + var testSubject = &StepGetRootPassword{ + GetRootPassword: func(string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := DeleteTestStateBagStepGetRootPassword() + + var result = testSubject.Run(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 TestStepGetRootPasswordShouldPassIfOperationGetRootPasswordPasses(t *testing.T) { + var testSubject = &StepGetRootPassword{ + GetRootPassword: func(string, string) (string, error) { return "a", nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := DeleteTestStateBagStepGetRootPassword() + + var result = testSubject.Run(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 DeleteTestStateBagStepGetRootPassword() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("LoginKey", &LoginKey{"a", "b"}) + stateBag.Put("InstanceNo", "a") + + return stateBag +} diff --git a/builder/ncloud/step_stop_server_instance.go b/builder/ncloud/step_stop_server_instance.go new file mode 100644 index 000000000..1c9252ed2 --- /dev/null +++ b/builder/ncloud/step_stop_server_instance.go @@ -0,0 +1,64 @@ +package ncloud + +import ( + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepStopServerInstance struct { + Conn *ncloud.Conn + StopServerInstance func(serverInstanceNo string) error + Say func(message string) + Error func(e error) +} + +func NewStepStopServerInstance(conn *ncloud.Conn, ui packer.Ui) *StepStopServerInstance { + var step = &StepStopServerInstance{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + } + + step.StopServerInstance = step.stopServerInstance + + return step +} + +func (s *StepStopServerInstance) stopServerInstance(serverInstanceNo string) error { + reqParams := new(ncloud.RequestStopServerInstances) + reqParams.ServerInstanceNoList = []string{serverInstanceNo} + + serverInstanceList, err := s.Conn.StopServerInstances(reqParams) + if err != nil { + return fmt.Errorf("error code: %d , error message: %s", serverInstanceList.ReturnCode, serverInstanceList.ReturnMessage) + } + + 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 { + return err + } + + s.Say(fmt.Sprintf("Server Instance stopped. Server InstanceNo is %s", serverInstanceList.ServerInstanceList[0].ServerInstanceNo)) + + return nil +} + +func (s *StepStopServerInstance) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Stop Server Instance") + + var serverInstanceNo = state.Get("InstanceNo").(string) + + err := s.StopServerInstance(serverInstanceNo) + + return processStepResult(err, s.Error, state) +} + +func (*StepStopServerInstance) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_stop_server_instance_test.go b/builder/ncloud/step_stop_server_instance_test.go new file mode 100644 index 000000000..d90bc6953 --- /dev/null +++ b/builder/ncloud/step_stop_server_instance_test.go @@ -0,0 +1,55 @@ +package ncloud + +import ( + "fmt" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepStopServerInstanceShouldFailIfOperationStopFails(t *testing.T) { + var testSubject = &StepStopServerInstance{ + StopServerInstance: func(serverInstanceNo string) error { return fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepStopServerInstance() + + var result = testSubject.Run(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 TestStepStopServerInstanceShouldPassIfOperationStopPasses(t *testing.T) { + var testSubject = &StepStopServerInstance{ + StopServerInstance: func(serverInstanceNo string) error { return nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepStopServerInstance() + + var result = testSubject.Run(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 createTestStateBagStepStopServerInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("InstanceNo", "a") + return stateBag +} diff --git a/builder/ncloud/step_terminate_server_instance.go b/builder/ncloud/step_terminate_server_instance.go new file mode 100644 index 000000000..5d60236f0 --- /dev/null +++ b/builder/ncloud/step_terminate_server_instance.go @@ -0,0 +1,81 @@ +package ncloud + +import ( + "errors" + "fmt" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" +) + +type StepTerminateServerInstance struct { + Conn *ncloud.Conn + TerminateServerInstance func(serverInstanceNo string) error + Say func(message string) + Error func(e error) +} + +func NewStepTerminateServerInstance(conn *ncloud.Conn, ui packer.Ui) *StepTerminateServerInstance { + var step = &StepTerminateServerInstance{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + } + + step.TerminateServerInstance = step.terminateServerInstance + + return step +} + +func (s *StepTerminateServerInstance) terminateServerInstance(serverInstanceNo string) error { + reqParams := new(ncloud.RequestTerminateServerInstances) + reqParams.ServerInstanceNoList = []string{serverInstanceNo} + + serverInstanceList, err := s.Conn.TerminateServerInstances(reqParams) + if err != nil { + return fmt.Errorf("error code: %d , error message: %s", serverInstanceList.ReturnCode, serverInstanceList.ReturnMessage) + } + + c1 := make(chan error, 1) + + go func() { + reqParams := new(ncloud.RequestGetServerInstanceList) + reqParams.ServerInstanceNoList = []string{serverInstanceNo} + + for { + + serverInstanceList, err := s.Conn.GetServerInstanceList(reqParams) + if err != nil { + c1 <- fmt.Errorf("error code: %d , error message: %s", serverInstanceList.ReturnCode, serverInstanceList.ReturnMessage) + return + } else if serverInstanceList.TotalRows == 0 { + c1 <- nil + return + } + + time.Sleep(time.Second * 3) + } + }() + + select { + case res := <-c1: + return res + case <-time.After(time.Second * 60): + return errors.New("TIMEOUT : Can't terminate server instance") + } +} + +func (s *StepTerminateServerInstance) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Terminate Server Instance") + + var serverInstanceNo = state.Get("InstanceNo").(string) + + err := s.TerminateServerInstance(serverInstanceNo) + + return processStepResult(err, s.Error, state) +} + +func (*StepTerminateServerInstance) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_terminate_server_instance_test.go b/builder/ncloud/step_terminate_server_instance_test.go new file mode 100644 index 000000000..b5967fc65 --- /dev/null +++ b/builder/ncloud/step_terminate_server_instance_test.go @@ -0,0 +1,54 @@ +package ncloud + +import ( + "fmt" + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepTerminateServerInstanceShouldFailIfOperationTerminationFails(t *testing.T) { + var testSubject = &StepTerminateServerInstance{ + TerminateServerInstance: func(serverInstanceNo string) error { return fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepTerminateServerInstance() + + var result = testSubject.Run(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 TestStepTerminateServerInstanceShouldPassIfOperationTerminationPasses(t *testing.T) { + var testSubject = &StepTerminateServerInstance{ + TerminateServerInstance: func(serverInstanceNo string) error { return nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepTerminateServerInstance() + + var result = testSubject.Run(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 createTestStateBagStepTerminateServerInstance() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + stateBag.Put("InstanceNo", "a") + return stateBag +} diff --git a/builder/ncloud/step_validate_template.go b/builder/ncloud/step_validate_template.go new file mode 100644 index 000000000..0007bfa40 --- /dev/null +++ b/builder/ncloud/step_validate_template.go @@ -0,0 +1,263 @@ +package ncloud + +import ( + "bytes" + "errors" + "fmt" + "strings" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" + "github.com/olekukonko/tablewriter" +) + +//StepValidateTemplate : struct for Validation a tempalte +type StepValidateTemplate struct { + Conn *ncloud.Conn + Validate func() error + Say func(message string) + Error func(e error) + Config *Config + zoneNo string + regionNo string +} + +// NewStepValidateTemplate : funciton for Validation a tempalte +func NewStepValidateTemplate(conn *ncloud.Conn, ui packer.Ui, config *Config) *StepValidateTemplate { + var step = &StepValidateTemplate{ + Conn: conn, + Say: func(message string) { ui.Say(message) }, + Error: func(e error) { ui.Error(e.Error()) }, + Config: config, + } + + step.Validate = step.validateTemplate + + return step +} + +// getZoneNo : get zoneNo +func (s *StepValidateTemplate) getZoneNo() error { + if s.Config.Region == "" { + return nil + } + + regionList, err := s.Conn.GetRegionList() + if err != nil { + return fmt.Errorf("error code: %d , error message: %s", regionList.ReturnCode, regionList.ReturnMessage) + } + + var regionNo string + for _, region := range regionList.RegionList { + if strings.EqualFold(region.RegionName, s.Config.Region) { + regionNo = region.RegionNo + } + } + + if regionNo == "" { + return fmt.Errorf("region %s is invalid", s.Config.Region) + } + + s.regionNo = regionNo + + // Get ZoneNo + ZoneList, err := s.Conn.GetZoneList(regionNo) + if err != nil { + return fmt.Errorf("error code: %d , error message: %s", ZoneList.ReturnCode, ZoneList.ReturnMessage) + } + + if len(ZoneList.Zone) > 0 { + s.zoneNo = ZoneList.Zone[0].ZoneNo + } + + return nil +} + +func (s *StepValidateTemplate) validateMemberServerImage() error { + var serverImageName = s.Config.ServerImageName + + reqParams := new(ncloud.RequestServerImageList) + reqParams.RegionNo = s.regionNo + + memberServerImageList, err := s.Conn.GetMemberServerImageList(reqParams) + if err != nil { + return err + } + + var isExistMemberServerImageNo = false + for _, image := range memberServerImageList.MemberServerImageList { + // Check duplicate server_image_name + if image.MemberServerImageName == serverImageName { + return fmt.Errorf("server_image_name %s is exists", serverImageName) + } + + if image.MemberServerImageNo == s.Config.MemberServerImageNo { + isExistMemberServerImageNo = true + if s.Config.ServerProductCode == "" { + s.Config.ServerProductCode = image.OriginalServerProductCode + s.Say("server_product_code for member server image '" + image.OriginalServerProductCode + "' is configured automatically") + } + s.Config.ServerImageProductCode = image.OriginalServerImageProductCode + } + } + + if s.Config.MemberServerImageNo != "" && !isExistMemberServerImageNo { + return fmt.Errorf("member_server_image_no %s does not exist", s.Config.MemberServerImageNo) + } + + return nil +} + +func (s *StepValidateTemplate) validateServerImageProduct() error { + var serverImageProductCode = s.Config.ServerImageProductCode + if serverImageProductCode == "" { + return nil + } + + reqParams := new(ncloud.RequestGetServerImageProductList) + reqParams.RegionNo = s.regionNo + + serverImageProductList, err := s.Conn.GetServerImageProductList(reqParams) + if err != nil { + return err + } + + var isExistServerImage = false + var buf bytes.Buffer + var productName string + table := tablewriter.NewWriter(&buf) + table.SetHeader([]string{"Name", "Code"}) + + for _, product := range serverImageProductList.Product { + // Check exist server image product code + if product.ProductCode == serverImageProductCode { + isExistServerImage = true + productName = product.ProductName + break + } + + table.Append([]string{product.ProductName, product.ProductCode}) + } + + if !isExistServerImage { + reqParams.BlockStorageSize = 100 + + serverImageProductList, err := s.Conn.GetServerImageProductList(reqParams) + if err != nil { + return err + } + + for _, product := range serverImageProductList.Product { + // Check exist server image product code + if product.ProductCode == serverImageProductCode { + isExistServerImage = true + productName = product.ProductName + break + } + + table.Append([]string{product.ProductName, product.ProductCode}) + } + } + + if !isExistServerImage { + table.Render() + s.Say(buf.String()) + + return fmt.Errorf("server_image_product_code %s does not exist", serverImageProductCode) + } + + if strings.Contains(productName, "mssql") { + s.Config.FeeSystemTypeCode = "FXSUM" + } + + return nil +} + +func (s *StepValidateTemplate) validateServerProductCode() error { + var serverImageProductCode = s.Config.ServerImageProductCode + var productCode = s.Config.ServerProductCode + + reqParams := new(ncloud.RequestGetServerProductList) + reqParams.ServerImageProductCode = serverImageProductCode + reqParams.RegionNo = s.regionNo + + productList, err := s.Conn.GetServerProductList(reqParams) + if err != nil { + return err + } + + var isExistProductCode = false + for _, product := range productList.Product { + // Check exist server image product code + if product.ProductCode == productCode { + isExistProductCode = true + if strings.Contains(product.ProductName, "mssql") { + s.Config.FeeSystemTypeCode = "FXSUM" + } + + if product.ProductType.Code == "VDS" { + return errors.New("You cannot create my server image for VDS servers") + } + + break + } else if productCode == "" && product.ProductType.Code == "STAND" { + isExistProductCode = true + s.Config.ServerProductCode = product.ProductCode + s.Say("server_product_code '" + product.ProductCode + "' is configured automatically") + break + } + } + + if !isExistProductCode { + var buf bytes.Buffer + table := tablewriter.NewWriter(&buf) + table.SetHeader([]string{"Name", "Code"}) + for _, product := range productList.Product { + table.Append([]string{product.ProductName, product.ProductCode}) + } + table.Render() + + s.Say(buf.String()) + + return fmt.Errorf("server_product_code %s does not exist", productCode) + } + + 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 { + return err + } + + // Validate member_server_image_no and member_server_image_no + if err := s.validateMemberServerImage(); err != nil { + return err + } + + // Validate server_image_product_code + if err := s.validateServerImageProduct(); err != nil { + return err + } + + // Validate server_product_code + return s.validateServerProductCode() +} + +// Run : main funciton for validation a template +func (s *StepValidateTemplate) Run(state multistep.StateBag) multistep.StepAction { + s.Say("Validating deployment template ...") + + err := s.Validate() + + state.Put("ZoneNo", s.zoneNo) + + return processStepResult(err, s.Error, state) +} + +// Cleanup : cleanup on error +func (s *StepValidateTemplate) Cleanup(multistep.StateBag) { +} diff --git a/builder/ncloud/step_validate_template_test.go b/builder/ncloud/step_validate_template_test.go new file mode 100644 index 000000000..98f45b4a0 --- /dev/null +++ b/builder/ncloud/step_validate_template_test.go @@ -0,0 +1,54 @@ +package ncloud + +import ( + "fmt" + "testing" + + "github.com/mitchellh/multistep" +) + +func TestStepValidateTemplateShouldFailIfValidateFails(t *testing.T) { + var testSubject = &StepValidateTemplate{ + Validate: func() error { return fmt.Errorf("!! Unit Test FAIL !!") }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepValidateTemplate() + + var result = testSubject.Run(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 TestStepValidateTemplateShouldPassIfValidatePasses(t *testing.T) { + var testSubject = &StepValidateTemplate{ + Validate: func() error { return nil }, + Say: func(message string) {}, + Error: func(e error) {}, + } + + stateBag := createTestStateBagStepValidateTemplate() + + var result = testSubject.Run(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 createTestStateBagStepValidateTemplate() multistep.StateBag { + stateBag := new(multistep.BasicStateBag) + + return stateBag +} diff --git a/builder/ncloud/waiter_block_storage_instance.go b/builder/ncloud/waiter_block_storage_instance.go new file mode 100644 index 000000000..9cfa5562a --- /dev/null +++ b/builder/ncloud/waiter_block_storage_instance.go @@ -0,0 +1,80 @@ +package ncloud + +import ( + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" +) + +func waiterBlockStorageInstanceStatus(conn *ncloud.Conn, blockStorageInstanceNo string, status string, timeout time.Duration) error { + reqParams := new(ncloud.RequestBlockStorageInstanceList) + reqParams.BlockStorageInstanceNoList = []string{blockStorageInstanceNo} + + c1 := make(chan error, 1) + + go func() { + for { + blockStorageInstanceList, err := conn.GetBlockStorageInstance(reqParams) + if err != nil { + c1 <- err + return + } + + if status == "DETAC" && len(blockStorageInstanceList.BlockStorageInstance) == 0 { + c1 <- nil + return + } + + code := blockStorageInstanceList.BlockStorageInstance[0].BlockStorageInstanceStatus.Code + operationCode := blockStorageInstanceList.BlockStorageInstance[0].BlockStorageInstanceOperation.Code + + if code == status && operationCode == "NULL" { + c1 <- nil + return + } + + log.Println(blockStorageInstanceList.BlockStorageInstance[0]) + 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 waiterDetachedBlockStorageInstance(conn *ncloud.Conn, serverInstanceNo string, timeout time.Duration) error { + reqParams := new(ncloud.RequestBlockStorageInstanceList) + reqParams.ServerInstanceNo = serverInstanceNo + + c1 := make(chan error, 1) + + go func() { + for { + blockStorageInstanceList, err := conn.GetBlockStorageInstance(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 new file mode 100644 index 000000000..5ba640874 --- /dev/null +++ b/builder/ncloud/waiter_server_image_status.go @@ -0,0 +1,43 @@ +package ncloud + +import ( + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" +) + +func waiterMemberServerImageStatus(conn *ncloud.Conn, memberServerImageNo string, status string, timeout time.Duration) error { + reqParams := new(ncloud.RequestServerImageList) + reqParams.MemberServerImageNoList = []string{memberServerImageNo} + + c1 := make(chan error, 1) + + go func() { + for { + memberServerImageList, err := conn.GetMemberServerImageList(reqParams) + if err != nil { + c1 <- err + return + } + + code := memberServerImageList.MemberServerImageList[0].MemberServerImageStatus.Code + if code == status { + c1 <- nil + return + } + + 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) + } +} diff --git a/builder/ncloud/waiter_server_instance_status.go b/builder/ncloud/waiter_server_instance_status.go new file mode 100644 index 000000000..176cde701 --- /dev/null +++ b/builder/ncloud/waiter_server_instance_status.go @@ -0,0 +1,43 @@ +package ncloud + +import ( + "fmt" + "log" + "time" + + ncloud "github.com/NaverCloudPlatform/ncloud-sdk-go/sdk" +) + +func waiterServerInstanceStatus(conn *ncloud.Conn, serverInstanceNo string, status string, timeout time.Duration) error { + reqParams := new(ncloud.RequestGetServerInstanceList) + reqParams.ServerInstanceNoList = []string{serverInstanceNo} + + c1 := make(chan error, 1) + + go func() { + for { + serverInstanceList, err := conn.GetServerInstanceList(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) + 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) + } +} diff --git a/command/plugin.go b/command/plugin.go index d9e7ea577..73a04a035 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -29,6 +29,7 @@ import ( hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx" lxcbuilder "github.com/hashicorp/packer/builder/lxc" lxdbuilder "github.com/hashicorp/packer/builder/lxd" + ncloudbuilder "github.com/hashicorp/packer/builder/ncloud" nullbuilder "github.com/hashicorp/packer/builder/null" oneandonebuilder "github.com/hashicorp/packer/builder/oneandone" openstackbuilder "github.com/hashicorp/packer/builder/openstack" @@ -96,6 +97,7 @@ var Builders = map[string]packer.Builder{ "hyperv-vmcx": new(hypervvmcxbuilder.Builder), "lxc": new(lxcbuilder.Builder), "lxd": new(lxdbuilder.Builder), + "ncloud": new(ncloudbuilder.Builder), "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder),