From 1a1ab3863c9a7a2bd8b419524be7487892c79a9b Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Tue, 18 Jul 2017 09:08:11 +0200 Subject: [PATCH 1/2] cloudstack: Add support for Security Groups Adds two new options: - `create_security_group` which automatically creates a temporary SG. - `security_groups` which takes a list of SGs to attach to the instance. --- builder/cloudstack/builder.go | 1 + builder/cloudstack/config.go | 14 ++- builder/cloudstack/step_create_instance.go | 4 + .../cloudstack/step_create_security_group.go | 94 +++++++++++++++++++ builder/cloudstack/step_prepare_config.go | 12 +++ .../source/docs/builders/cloudstack.html.md | 8 ++ 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 builder/cloudstack/step_create_security_group.go diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index 8bd6dfed2..e46e56086 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -71,6 +71,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHAgentAuth: b.config.Comm.SSHAgentAuth, TemporaryKeyPairName: b.config.TemporaryKeypairName, }, + &stepCreateSecurityGroup{}, &stepCreateInstance{ Ctx: b.config.ctx, Debug: b.config.PackerDebug, diff --git a/builder/cloudstack/config.go b/builder/cloudstack/config.go index 513ad8eed..35026f6e3 100644 --- a/builder/cloudstack/config.go +++ b/builder/cloudstack/config.go @@ -28,22 +28,24 @@ type Config struct { SSLNoVerify bool `mapstructure:"ssl_no_verify"` CIDRList []string `mapstructure:"cidr_list"` + CreateSecurityGroup bool `mapstructure:"create_security_group"` DiskOffering string `mapstructure:"disk_offering"` DiskSize int64 `mapstructure:"disk_size"` Expunge bool `mapstructure:"expunge"` Hypervisor string `mapstructure:"hypervisor"` InstanceName string `mapstructure:"instance_name"` Keypair string `mapstructure:"keypair"` - TemporaryKeypairName string `mapstructure:"temporary_keypair_name"` Network string `mapstructure:"network"` Project string `mapstructure:"project"` PublicIPAddress string `mapstructure:"public_ip_address"` + SecurityGroups []string `mapstructure:"security_groups"` ServiceOffering string `mapstructure:"service_offering"` - SourceTemplate string `mapstructure:"source_template"` SourceISO string `mapstructure:"source_iso"` + SourceTemplate string `mapstructure:"source_template"` + TemporaryKeypairName string `mapstructure:"temporary_keypair_name"` + UseLocalIPAddress bool `mapstructure:"use_local_ip_address"` UserData string `mapstructure:"user_data"` UserDataFile string `mapstructure:"user_data_file"` - UseLocalIPAddress bool `mapstructure:"use_local_ip_address"` Zone string `mapstructure:"zone"` TemplateName string `mapstructure:"template_name"` @@ -99,7 +101,7 @@ func NewConfig(raws ...interface{}) (*Config, error) { c.AsyncTimeout = 30 * time.Minute } - if len(c.CIDRList) == 0 && !c.UseLocalIPAddress { + if len(c.CIDRList) == 0 { c.CIDRList = []string{"0.0.0.0/0"} } @@ -146,6 +148,10 @@ func NewConfig(raws ...interface{}) (*Config, error) { errs = packer.MultiErrorAppend(errs, errors.New("a network must be specified")) } + if c.CreateSecurityGroup && !c.Expunge { + errs = packer.MultiErrorAppend(errs, errors.New("auto creating a temporary security group requires expunge")) + } + if c.ServiceOffering == "" { errs = packer.MultiErrorAppend(errs, errors.New("a service_offering must be specified")) } diff --git a/builder/cloudstack/step_create_instance.go b/builder/cloudstack/step_create_instance.go index 894d0394e..538a062b9 100644 --- a/builder/cloudstack/step_create_instance.go +++ b/builder/cloudstack/step_create_instance.go @@ -49,6 +49,10 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction p.SetKeypair(keypair.(string)) } + if securitygroups, ok := state.GetOk("security_groups"); ok { + p.SetSecuritygroupids(securitygroups.([]string)) + } + // If we use an ISO, configure the disk offering. if config.SourceISO != "" { p.SetDiskofferingid(config.DiskOffering) diff --git a/builder/cloudstack/step_create_security_group.go b/builder/cloudstack/step_create_security_group.go new file mode 100644 index 000000000..4bab0d4c7 --- /dev/null +++ b/builder/cloudstack/step_create_security_group.go @@ -0,0 +1,94 @@ +package cloudstack + +import ( + "fmt" + + "github.com/hashicorp/packer/common/uuid" + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" + "github.com/xanzy/go-cloudstack/cloudstack" +) + +type stepCreateSecurityGroup struct { + tempSG string +} + +func (s *stepCreateSecurityGroup) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*cloudstack.CloudStackClient) + config := state.Get("config").(*Config) + ui := state.Get("ui").(packer.Ui) + + if len(config.SecurityGroups) > 0 { + state.Put("security_groups", config.SecurityGroups) + return multistep.ActionContinue + } + + if !config.CreateSecurityGroup { + return multistep.ActionContinue + } + + ui.Say("Creating temporary Security Group...") + + p := client.SecurityGroup.NewCreateSecurityGroupParams( + fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), + ) + p.SetDescription("Temporary SG created by Packer") + if config.Project != "" { + p.SetProjectid(config.Project) + } + + sg, err := client.SecurityGroup.CreateSecurityGroup(p) + if err != nil { + err := fmt.Errorf("Failed to create security group: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.tempSG = sg.Id + state.Put("security_groups", []string{sg.Id}) + + // Create Ingress rule + i := client.SecurityGroup.NewAuthorizeSecurityGroupIngressParams() + i.SetCidrlist(config.CIDRList) + if config.Project != "" { + i.SetProjectid(config.Project) + } + i.SetProtocol("TCP") + i.SetSecuritygroupid(sg.Id) + i.SetStartport(config.Comm.Port()) + i.SetEndport(config.Comm.Port()) + + _, err = client.SecurityGroup.AuthorizeSecurityGroupIngress(i) + if err != nil { + err := fmt.Errorf("Failed to authorize security group ingress rule: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +// Cleanup any resources that may have been created during the Run phase. +func (s *stepCreateSecurityGroup) Cleanup(state multistep.StateBag) { + client := state.Get("client").(*cloudstack.CloudStackClient) + config := state.Get("config").(*Config) + ui := state.Get("ui").(packer.Ui) + + if s.tempSG == "" { + return + } + + ui.Say(fmt.Sprintf("Cleanup temporary security group: %s ...", s.tempSG)) + p := client.SecurityGroup.NewDeleteSecurityGroupParams() + p.SetId(s.tempSG) + if config.Project != "" { + p.SetProjectid(config.Project) + } + + if _, err := client.SecurityGroup.DeleteSecurityGroup(p); err != nil { + ui.Error(err.Error()) + ui.Error(fmt.Sprintf("Error deleting security group: %s. Please destroy it manually.\n", s.tempSG)) + } +} diff --git a/builder/cloudstack/step_prepare_config.go b/builder/cloudstack/step_prepare_config.go index af28a76a8..6cfb5c478 100644 --- a/builder/cloudstack/step_prepare_config.go +++ b/builder/cloudstack/step_prepare_config.go @@ -83,6 +83,18 @@ func (s *stepPrepareConfig) Run(state multistep.StateBag) multistep.StepAction { } } + // Then try to get the SG's UUID's. + if len(config.SecurityGroups) > 0 { + for i := range config.SecurityGroups { + if !isUUID(config.SecurityGroups[i]) { + config.SecurityGroups[i], _, err = client.SecurityGroup.GetSecurityGroupID(config.SecurityGroups[i], cloudstack.WithProject(config.Project)) + if err != nil { + errs = packer.MultiErrorAppend(errs, &retrieveErr{"network", config.SecurityGroups[i], err}) + } + } + } + } + if !isUUID(config.ServiceOffering) { config.ServiceOffering, _, err = client.ServiceOffering.GetServiceOfferingID(config.ServiceOffering) if err != nil { diff --git a/website/source/docs/builders/cloudstack.html.md b/website/source/docs/builders/cloudstack.html.md index a3f091315..7e59fe718 100644 --- a/website/source/docs/builders/cloudstack.html.md +++ b/website/source/docs/builders/cloudstack.html.md @@ -74,6 +74,11 @@ builder. connect to the instance. Defaults to `[ "0.0.0.0/0" ]`. Only required when `use_local_ip_address` is `false`. +- `create_security_group` (boolean) - If `true` a temporary security group + will be created which allows traffic towards the instance from the + `cidr_list`. This option will be ignored if `security_groups` is also + defined. Requires `expunge` set to `true`. Defaults to `false`. + - `disk_offering` (string) - The name or ID of the disk offering used for the instance. This option is only available (and also required) when using `source_iso`. @@ -118,6 +123,9 @@ builder. connecting any provisioners to. If not provided, a temporary public IP address will be associated and released during the Packer run. +- `security_groups` (array of strings) - A list of security group IDs or names + to associate the instance with. + - `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to authenticate connections to the source instance. No temporary keypair will be created, and the values of `ssh_password` and `ssh_private_key_file` will From cad3978e6ae0a64ba052d1bbb96ab61111f60e67 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Wed, 26 Jul 2017 21:11:01 +0200 Subject: [PATCH 2/2] cloudstack: Updated after review --- builder/cloudstack/step_create_security_group.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/cloudstack/step_create_security_group.go b/builder/cloudstack/step_create_security_group.go index 4bab0d4c7..1bf23100b 100644 --- a/builder/cloudstack/step_create_security_group.go +++ b/builder/cloudstack/step_create_security_group.go @@ -51,13 +51,13 @@ func (s *stepCreateSecurityGroup) Run(state multistep.StateBag) multistep.StepAc // Create Ingress rule i := client.SecurityGroup.NewAuthorizeSecurityGroupIngressParams() i.SetCidrlist(config.CIDRList) - if config.Project != "" { - i.SetProjectid(config.Project) - } i.SetProtocol("TCP") i.SetSecuritygroupid(sg.Id) i.SetStartport(config.Comm.Port()) i.SetEndport(config.Comm.Port()) + if config.Project != "" { + i.SetProjectid(config.Project) + } _, err = client.SecurityGroup.AuthorizeSecurityGroupIngress(i) if err != nil {