create temporary ACG for VPC

This commit is contained in:
sangkyu.kim 2021-03-31 15:15:57 +09:00
parent 3a11352dfa
commit 74434b3c3e
10 changed files with 306 additions and 57 deletions

View File

@ -50,6 +50,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
NewStepValidateTemplate(conn, ui, &b.config),
NewStepCreateLoginKey(conn, ui, &b.config),
multistep.If(b.config.SupportVPC, NewStepCreateInitScript(conn, ui, &b.config)),
multistep.If(b.config.SupportVPC, NewStepCreateAccessControlGroup(conn, ui, &b.config)),
NewStepCreateServerInstance(conn, ui, &b.config),
NewStepCreateBlockStorage(conn, ui, &b.config),
NewStepGetRootPassword(conn, ui, &b.config),

View File

@ -54,16 +54,19 @@ type Config struct {
// (default: Korea)
Region string `mapstructure:"region" required:"false"`
RegionCode string `mapstructure:"region_code" required:"false"`
// Deprecated
AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no" required:"false"`
// This is used to allow
// winrm access when you create a Windows server. An ACG that specifies an
// access source (0.0.0.0/0) and allowed port (5985) must be created in
// advance.
AccessControlGroupConfigurationNo string `mapstructure:"access_control_group_configuration_no" required:"false"`
// Whether to use VPC. By default, the value is false on "public" site. If you want to use VPC environment. Please set this value true.
// advance if you use CLASSIC env. If this field is left blank,
// Packer will create temporary ACG for automatically in VPC environment.
AccessControlGroupNo string `mapstructure:"access_control_group_no" required:"false"`
SupportVPC bool `mapstructure:"support_vpc" required:"false"`
// The ID of the associated Subnet
// The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`.
SubnetNo string `mapstructure:"subnet_no" required:"false"`
// The ID of the VPC where you want to place the Server Instance
// The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`.
// (You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`)
VpcNo string `mapstructure:"vpc_no" required:"false"`
Comm communicator.Config `mapstructure:",squash"`
@ -141,14 +144,18 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packersdk.MultiErrorAppend(errs, errors.New("if `user_data` field is set, length of UserData should be max 21847"))
}
if c.Comm.Type == "winrm" && c.AccessControlGroupConfigurationNo == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("if Communicator is winrm, `access_control_group_configuration_no` (allow 5986 port) is required"))
if c.AccessControlGroupConfigurationNo != "" {
errs = packersdk.MultiErrorAppend(errs, errors.New("`access_control_group_configuration_no` is deprecated, please use `access_control_group_no` instead"))
}
if c.VpcNo != "" || c.SubnetNo != "" {
c.SupportVPC = true
}
if c.Comm.Type == "winrm" && c.AccessControlGroupNo == "" && !c.SupportVPC {
errs = packersdk.MultiErrorAppend(errs, errors.New("if Communicator is winrm, `access_control_group_no` (allow 5986 port) is required in `CLASSIC` environment"))
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}

View File

@ -31,6 +31,7 @@ type FlatConfig struct {
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
RegionCode *string `mapstructure:"region_code" required:"false" cty:"region_code" hcl:"region_code"`
AccessControlGroupConfigurationNo *string `mapstructure:"access_control_group_configuration_no" required:"false" cty:"access_control_group_configuration_no" hcl:"access_control_group_configuration_no"`
AccessControlGroupNo *string `mapstructure:"access_control_group_no" required:"false" cty:"access_control_group_no" hcl:"access_control_group_no"`
SupportVPC *bool `mapstructure:"support_vpc" required:"false" cty:"support_vpc" hcl:"support_vpc"`
SubnetNo *string `mapstructure:"subnet_no" required:"false" cty:"subnet_no" hcl:"subnet_no"`
VpcNo *string `mapstructure:"vpc_no" required:"false" cty:"vpc_no" hcl:"vpc_no"`
@ -118,6 +119,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"region_code": &hcldec.AttrSpec{Name: "region_code", Type: cty.String, Required: false},
"access_control_group_configuration_no": &hcldec.AttrSpec{Name: "access_control_group_configuration_no", Type: cty.String, Required: false},
"access_control_group_no": &hcldec.AttrSpec{Name: "access_control_group_no", Type: cty.String, Required: false},
"support_vpc": &hcldec.AttrSpec{Name: "support_vpc", Type: cty.Bool, Required: false},
"subnet_no": &hcldec.AttrSpec{Name: "subnet_no", Type: cty.String, Required: false},
"vpc_no": &hcldec.AttrSpec{Name: "vpc_no", Type: cty.String, Required: false},

View File

@ -0,0 +1,197 @@
package ncloud
import (
"context"
"fmt"
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/ncloud"
"github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/vserver"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"log"
)
type StepCreateAccessControlGroup struct {
Conn *NcloudAPIClient
GetAccessControlGroup func(acgNo string) (*vserver.AccessControlGroup, error)
CreateAccessControlGroup func() (string, error)
AddAccessControlGroupRule func(acgNo string) error
Say func(message string)
Error func(e error)
Config *Config
createdAcgNo string
}
func NewStepCreateAccessControlGroup(conn *NcloudAPIClient, ui packersdk.Ui, config *Config) *StepCreateAccessControlGroup {
var step = &StepCreateAccessControlGroup{
Conn: conn,
Say: func(message string) { ui.Say(message) },
Error: func(e error) { ui.Error(e.Error()) },
Config: config,
}
if config.SupportVPC {
step.GetAccessControlGroup = step.getVpcAccessControlGroup
step.CreateAccessControlGroup = step.createVpcAccessControlGroup
step.AddAccessControlGroupRule = step.addVpcAccessControlGroupRule
}
return step
}
func (s *StepCreateAccessControlGroup) createVpcAccessControlGroup() (string, error) {
reqParam := &vserver.CreateAccessControlGroupRequest{
RegionCode: &s.Config.RegionCode,
AccessControlGroupName: &s.Config.ServerImageName,
AccessControlGroupDescription: ncloud.String("Temporary ACG for packer"),
VpcNo: &s.Config.VpcNo,
}
resp, err := s.Conn.vserver.V2Api.CreateAccessControlGroup(reqParam)
if err != nil {
return "", err
}
if resp != nil && *resp.TotalRows > 0 {
return *resp.AccessControlGroupList[0].AccessControlGroupNo, nil
}
return "", nil
}
func (s *StepCreateAccessControlGroup) addVpcAccessControlGroupRule(acgNo string) error {
_, err := s.Conn.vserver.V2Api.AddAccessControlGroupInboundRule(&vserver.AddAccessControlGroupInboundRuleRequest{
RegionCode: &s.Config.RegionCode,
AccessControlGroupNo: &acgNo,
VpcNo: &s.Config.VpcNo,
AccessControlGroupRuleList: []*vserver.AddAccessControlGroupRuleParameter{
{
IpBlock: ncloud.String("0.0.0.0/0"),
PortRange: ncloud.String("22"),
ProtocolTypeCode: ncloud.String("TCP"),
},
{
IpBlock: ncloud.String("0.0.0.0/0"),
PortRange: ncloud.String("3389"),
ProtocolTypeCode: ncloud.String("TCP"),
},
{
IpBlock: ncloud.String("0.0.0.0/0"),
PortRange: ncloud.String("5985"),
ProtocolTypeCode: ncloud.String("TCP"),
},
},
})
if err != nil {
return err
}
_, err = s.Conn.vserver.V2Api.AddAccessControlGroupOutboundRule(&vserver.AddAccessControlGroupOutboundRuleRequest{
RegionCode: &s.Config.RegionCode,
AccessControlGroupNo: &acgNo,
VpcNo: &s.Config.VpcNo,
AccessControlGroupRuleList: []*vserver.AddAccessControlGroupRuleParameter{
{
IpBlock: ncloud.String("0.0.0.0/0"),
ProtocolTypeCode: ncloud.String("ICMP"),
},
{
IpBlock: ncloud.String("0.0.0.0/0"),
PortRange: ncloud.String("1-65535"),
ProtocolTypeCode: ncloud.String("TCP"),
},
{
IpBlock: ncloud.String("0.0.0.0/0"),
PortRange: ncloud.String("1-65535"),
ProtocolTypeCode: ncloud.String("UDP"),
},
},
})
if err != nil {
return err
}
return nil
}
func (s *StepCreateAccessControlGroup) deleteVpcAccessControlGroup(id string) error {
reqParam := &vserver.DeleteAccessControlGroupRequest{
RegionCode: &s.Config.RegionCode,
VpcNo: &s.Config.VpcNo,
AccessControlGroupNo: ncloud.String(id),
}
_, err := s.Conn.vserver.V2Api.DeleteAccessControlGroup(reqParam)
if err != nil {
return err
}
return nil
}
func (s *StepCreateAccessControlGroup) getVpcAccessControlGroup(id string) (*vserver.AccessControlGroup, error) {
reqParam := &vserver.GetAccessControlGroupDetailRequest{
RegionCode: &s.Config.RegionCode,
AccessControlGroupNo: ncloud.String(id),
}
resp, err := s.Conn.vserver.V2Api.GetAccessControlGroupDetail(reqParam)
if err != nil {
return nil, err
}
if resp != nil && *resp.TotalRows > 0 {
return resp.AccessControlGroupList[0], nil
}
return nil, nil
}
func (s *StepCreateAccessControlGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.Say("Create temporary ACG")
if len(s.Config.AccessControlGroupNo) > 0 {
acg, err := s.GetAccessControlGroup(s.Config.AccessControlGroupNo)
if err != nil || acg == nil {
err := fmt.Errorf("couldn't find specified ACG: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
log.Printf("Using specified ACG: %v", s.Config.AccessControlGroupNo)
return multistep.ActionContinue
}
acgNo, err := s.CreateAccessControlGroup()
s.Say(fmt.Sprintf("Creating temporary ACG [%s]", acgNo))
if err != nil || len(acgNo) == 0 {
err := fmt.Errorf("couldn't create ACG for VPC: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
s.createdAcgNo = acgNo
s.Say(fmt.Sprintf("Creating temporary rules ACG [%s]", acgNo))
err = s.AddAccessControlGroupRule(acgNo)
if err != nil {
err := fmt.Errorf("couldn't create ACG rules for SSH or winrm: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("access_control_group_no", acgNo)
return processStepResult(err, s.Error, state)
}
func (s *StepCreateAccessControlGroup) Cleanup(state multistep.StateBag) {
if s.createdAcgNo == "" {
return
}
err := s.deleteVpcAccessControlGroup(s.createdAcgNo)
if err != nil {
s.Error(fmt.Errorf("error cleaning up ACG. Please delete the ACG manually: err: %s; ACG No: %s", err, s.createdAcgNo))
}
s.Say("Clean up temporary ACG")
}

View File

@ -0,0 +1,66 @@
package ncloud
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepCreateAccessControlGroupShouldFailIfOperationCreateAccessControlGroupFails(t *testing.T) {
var testSubject = &StepCreateAccessControlGroup{
CreateAccessControlGroup: func() (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
Say: func(message string) {},
Error: func(e error) {},
Config: &Config{
Region: "Korea",
SupportVPC: true,
},
}
stateBag := createTestStateBagStepCreateAccessControlGroup()
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionHalt {
t.Fatalf("Expected the step to return 'ActionHalt', but got '%d'.", result)
}
if _, ok := stateBag.GetOk("error"); ok == false {
t.Fatal("Expected the step to set stateBag['Error'], but it was not.")
}
}
func TestStepCreateAccessControlGroupShouldPassIfOperationCreateAccessControlGroupPasses(t *testing.T) {
var testSubject = &StepCreateAccessControlGroup{
CreateAccessControlGroup: func() (string, error) { return "123", nil },
AddAccessControlGroupRule: func(acgNo string) error {
return nil
},
Say: func(message string) {},
Error: func(error) {},
Config: &Config{
Region: "Korea",
SupportVPC: true,
},
}
stateBag := createTestStateBagStepCreateAccessControlGroup()
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}
if _, ok := stateBag.GetOk("error"); ok == true {
t.Fatalf("Expected the step to not set stateBag['Error'], but it was.")
}
}
func createTestStateBagStepCreateAccessControlGroup() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)
return stateBag
}

View File

@ -92,8 +92,10 @@ func (s *StepCreateInitScript) deleteVpcInitScript(initScriptNo string) error {
}
func (s *StepCreateInitScript) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if len(s.Config.UserData) == 0 && len(s.Config.UserDataFile) == 0 {
return multistep.ActionContinue
}
s.Say("Create Init script")
initScriptNo, err := s.CreateInitScript()
if err == nil && initScriptNo != "" {
state.Put("init_script_no", initScriptNo)

View File

@ -14,7 +14,7 @@ func TestStepCreateInitScriptShouldFailIfOperationCreateInitScriptFails(t *testi
Say: func(message string) {},
Error: func(e error) {},
Config: &Config{
Region: "KR",
Region: "Korea",
SupportVPC: true,
},
}
@ -38,7 +38,7 @@ func TestStepCreateInitScriptShouldPassIfOperationCreateInitScriptPasses(t *test
Say: func(message string) {},
Error: func(e error) {},
Config: &Config{
Region: "KR",
Region: "Korea",
SupportVPC: true,
},
}

View File

@ -80,8 +80,8 @@ func (s *StepCreateServerInstance) createClassicServerInstance(loginKeyName stri
reqParams.UserData = ncloud.String(string(contents))
}
if s.Config.AccessControlGroupConfigurationNo != "" {
reqParams.AccessControlGroupConfigurationNoList = []*string{&s.Config.AccessControlGroupConfigurationNo}
if s.Config.AccessControlGroupNo != "" {
reqParams.AccessControlGroupConfigurationNoList = []*string{&s.Config.AccessControlGroupNo}
}
serverInstanceList, err := s.Conn.server.V2Api.CreateServerInstances(reqParams)
@ -111,14 +111,10 @@ func (s *StepCreateServerInstance) createVpcServerInstance(loginKeyName string,
initScriptNo = v.(string)
}
if s.Config.AccessControlGroupConfigurationNo != "" {
acgNo = s.Config.AccessControlGroupConfigurationNo
if s.Config.AccessControlGroupNo != "" {
acgNo = s.Config.AccessControlGroupNo
} else {
acgNo, err = s.getDefaultAccessControlGroup(s.Config.VpcNo)
}
if err != nil {
return "", err
acgNo = state.Get("access_control_group_no").(string)
}
reqParams := &vserver.CreateServerInstancesRequest{
@ -158,31 +154,6 @@ func (s *StepCreateServerInstance) createVpcServerInstance(loginKeyName string,
return s.serverInstanceNo, nil
}
func (s *StepCreateServerInstance) getDefaultAccessControlGroup(id string) (string, error) {
reqParams := &vserver.GetAccessControlGroupListRequest{
RegionCode: &s.Config.RegionCode,
VpcNo: ncloud.String(id),
}
resp, err := s.Conn.vserver.V2Api.GetAccessControlGroupList(reqParams)
if err != nil {
return "", err
}
if resp == nil || len(resp.AccessControlGroupList) == 0 {
return "", fmt.Errorf("no matching Access Control Group found")
}
for _, i := range resp.AccessControlGroupList {
if *i.IsDefault {
return *i.AccessControlGroupNo, nil
}
}
return "", fmt.Errorf("no matching default Access Control Group found")
}
func (s *StepCreateServerInstance) getClassicServerInstance() (string, string, error) {
reqParams := &server.GetServerInstanceListRequest{
ServerInstanceNoList: []*string{&s.serverInstanceNo},
@ -219,7 +190,7 @@ func (s *StepCreateServerInstance) getVpcServerInstance() (string, string, error
func (s *StepCreateServerInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.Say("Create Server Instance")
var loginKey = state.Get("login_key").(*LoginKey)
loginKey := state.Get("login_key").(*LoginKey)
feeSystemTypeCode := "MTRAT"
if _, ok := state.GetOk("fee_system_type_code"); ok {

View File

@ -31,9 +31,8 @@ Platform](https://www.ncloud.com/).
- `server_image_product_code` (string) - Product code of an image to create.
(member_server_image_no is required if not specified)
- `server_product_code` (string) - Product (spec) code to create.
### Optional:
- `server_product_code` (string) - Product (spec) code to create.
- `vpc_no` (string) - The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`.
(You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`)
@ -51,10 +50,10 @@ Platform](https://www.ncloud.com/).
- `block_storage_size` (number) - You can add block storage ranging from 10
GB to 2000 GB, in increments of 10 GB.
- `access_control_group_configuration_no` (string) - This is used to allow
- `access_control_group_no` (string) - This is used to allow
winrm access when you create a Windows server. An ACG that specifies an
access source (`0.0.0.0/0`) and allowed port (5985) must be created in
advance.
advance if you use CLASSIC env. If this field is left blank, Packer will create temporary ACG for automatically in VPC environment.
- `user_data` (string) - User data to apply when launching the instance. Note
that you need to be careful about escaping characters due to the templates
@ -104,6 +103,7 @@ source "ncloud" "example-windows" {
subnet_no = "{{YOUR_SUBNET_ID}}" // Remove this if you use CLASSIC environment.
communicator = "winrm"
winrm_username = "Administrator"
# access_control_group_no = "{{YOUR_ACG_ID}}" // Specific ACG ID allowed port (5985) if you use CLASSIC environment.
}
build {
@ -111,7 +111,7 @@ build {
provisioner "powershell" {
inline = [
"$Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet /mode:vm \"/unattend:C:\\Program Files (x86)\\example\\nserver64.xml\" "
"Write-Output 1,2,3 | Measure-Object"
]
}
}
@ -144,7 +144,7 @@ build {
{
"type": "powershell",
"inline": [
"$Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet /mode:vm \"/unattend:C:\\Program Files (x86)\\NBP\\nserver64.xml\" "
"Write-Output 1,2,3 | Measure-Object"
]
}
]
@ -195,7 +195,7 @@ build {
provisioner "shell" {
inline = [
"sleep 30", "yum install redis.x86_64 -y"
"echo Connected via SSM at '${build.User}@${build.Host}:${build.Port}'"
]
}
}
@ -226,7 +226,7 @@ build {
{
"type": "shell",
"inline": [
"sleep 30", "yum install redis.x86_64 -y"
"echo Connected via SSM at '${build.User}@${build.Host}:${build.Port}'"
]
}
]

View File

@ -29,12 +29,15 @@
- `region_code` (string) - Region Code
- `access_control_group_configuration_no` (string) - This is used to allow
- `access_control_group_configuration_no` (string) - Deprecated
- `access_control_group_no` (string) - This is used to allow
winrm access when you create a Windows server. An ACG that specifies an
access source (0.0.0.0/0) and allowed port (5985) must be created in
advance.
advance if you use CLASSIC env. If this field is left blank,
Packer will create temporary ACG for automatically in VPC environment.
- `subnet_no` (string) - The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`.
- `vpc_no` (string) - The ID of the VPC where you want to place the Server Instance. If this field is left blank, Packer will try to get the VPC ID from the `subnet_no`.
(You are required to least one between two parameters if u want using VPC environment: `vpc_no` or `subnet_no`)
- `subnet_no` (string) - The ID of the Subnet where you want to place the Server Instance. If this field is left blank, Packer will try to get the Public Subnet ID from the `vpc_no`.