diff --git a/builtin/providers/cloudstack/provider.go b/builtin/providers/cloudstack/provider.go index 4f5dc49a3..34addd605 100644 --- a/builtin/providers/cloudstack/provider.go +++ b/builtin/providers/cloudstack/provider.go @@ -54,6 +54,7 @@ func Provider() terraform.ResourceProvider { "cloudstack_nic": resourceCloudStackNIC(), "cloudstack_port_forward": resourceCloudStackPortForward(), "cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(), + "cloudstack_security_group": resourceCloudStackSecurityGroup(), "cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(), "cloudstack_static_nat": resourceCloudStackStaticNAT(), "cloudstack_template": resourceCloudStackTemplate(), diff --git a/builtin/providers/cloudstack/resource_cloudstack_security_group.go b/builtin/providers/cloudstack/resource_cloudstack_security_group.go new file mode 100644 index 000000000..e1619ab98 --- /dev/null +++ b/builtin/providers/cloudstack/resource_cloudstack_security_group.go @@ -0,0 +1,310 @@ +package cloudstack + +import ( + "fmt" + "log" + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/xanzy/go-cloudstack/cloudstack" +) + +func resourceCloudStackSecurityGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackSecurityGroupCreate, + Read: resourceCloudStackSecurityGroupRead, + Delete: resourceCloudStackSecurityGroupDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "project": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "rules": &schema.Schema{ + Type: schema.TypeList, + ForceNew: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidr_list": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "security_group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ports": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "traffic_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "ingress", + }, + }, + }, + }, + }, + } +} + +func resourceCloudStackSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + name := d.Get("name").(string) + + // Create a new parameter struct + p := cs.SecurityGroup.NewCreateSecurityGroupParams(name) + + // Set the description + if description, ok := d.GetOk("description"); ok { + p.SetDescription(description.(string)) + } else { + p.SetDescription(name) + } + + // If there is a project supplied, we retrieve and set the project id + if err := setProjectid(p, cs, d); err != nil { + return err + } + + log.Printf("[DEBUG] Creating security group %s", name) + r, err := cs.SecurityGroup.CreateSecurityGroup(p) + if err != nil { + return err + } + + log.Printf("[DEBUG] Security group %s successfully created with ID: %s", name, r.Id) + d.SetId(r.Id) + + // Create Rules + rules := d.Get("rules").([]interface{}) + for _, r := range rules { + + rule := r.(map[string]interface{}) + + m := splitPorts.FindStringSubmatch(rule["ports"].(string)) + startPort, err := strconv.Atoi(m[1]) + if err != nil { + return err + } + + endPort := startPort + if m[2] != "" { + endPort, err = strconv.Atoi(m[2]) + if err != nil { + return err + } + } + + traffic := rule["traffic_type"].(string) + if traffic == "ingress" { + param := cs.SecurityGroup.NewAuthorizeSecurityGroupIngressParams() + + param.SetSecuritygroupid(d.Id()) + if cidrlist := rule["cidr_list"]; cidrlist != "" { + param.SetCidrlist([]string{cidrlist.(string)}) + log.Printf("[DEBUG] cidr = %v", cidrlist) + } + if securitygroup := rule["security_group"]; securitygroup != "" { + // Get the security group details + ag, count, err := cs.SecurityGroup.GetSecurityGroupByName( + securitygroup.(string), + cloudstack.WithProject(d.Get("project").(string)), + ) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Security group %s does not longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + + log.Printf("[DEBUG] Found %v groups matching", count) + + return err + } + log.Printf("[DEBUG] ag = %v", ag) + m := make(map[string]string) + m[ag.Account] = ag.Name + log.Printf("[DEBUG] m = %v", m) + param.SetUsersecuritygrouplist(m) + } + param.SetStartport(startPort) + param.SetEndport(endPort) + param.SetProtocol(rule["protocol"].(string)) + + log.Printf("[DEBUG] Authorizing Ingress Rule %#v", param) + _, err := cs.SecurityGroup.AuthorizeSecurityGroupIngress(param) + if err != nil { + return err + } + } else if traffic == "egress" { + param := cs.SecurityGroup.NewAuthorizeSecurityGroupEgressParams() + + param.SetSecuritygroupid(d.Id()) + if cidrlist := rule["cidr_list"]; cidrlist != "" { + param.SetCidrlist([]string{cidrlist.(string)}) + log.Printf("[DEBUG] cidr = %v", cidrlist) + } + if securitygroup := rule["security_group"]; securitygroup != "" { + // Get the security group details + ag, count, err := cs.SecurityGroup.GetSecurityGroupByName( + securitygroup.(string), + cloudstack.WithProject(d.Get("project").(string)), + ) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Security group %s does not longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + + log.Printf("[DEBUG] Found %v groups matching", count) + + return err + } + log.Printf("[DEBUG] ag = %v", ag) + m := make(map[string]string) + m[ag.Account] = ag.Name + log.Printf("[DEBUG] m = %v", m) + param.SetUsersecuritygrouplist(m) + } + param.SetStartport(startPort) + param.SetEndport(endPort) + param.SetProtocol(rule["protocol"].(string)) + + log.Printf("[DEBUG] Authorizing Egress Rule %#v", param) + _, err := cs.SecurityGroup.AuthorizeSecurityGroupEgress(param) + if err != nil { + return err + } + } else { + return fmt.Errorf( + "Parameter traffic_type only accepts 'ingress' or 'egress' as values") + } + } + + return resourceCloudStackSecurityGroupRead(d, meta) +} + +func resourceCloudStackSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + log.Printf("[DEBUG] Retrieving security group %s (ID=%s)", d.Get("name").(string), d.Id()) + + // Get the security group details + ag, count, err := cs.SecurityGroup.GetSecurityGroupByID( + d.Id(), + cloudstack.WithProject(d.Get("project").(string)), + ) + if err != nil { + if count == 0 { + log.Printf("[DEBUG] Security group %s does not longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } + + log.Printf("[DEBUG] Found %v groups matching", count) + + return err + } + + // Update the config + d.Set("name", ag.Name) + d.Set("description", ag.Description) + + var rules []interface{} + for _, r := range ag.Ingressrule { + var ports string + if r.Startport == r.Endport { + ports = strconv.Itoa(r.Startport) + } else { + ports = fmt.Sprintf("%v-%v", r.Startport, r.Endport) + } + rule := map[string]interface{}{ + "cidr_list": r.Cidr, + "ports": ports, + "protocol": r.Protocol, + "traffic_type": "ingress", + "security_group": r.Securitygroupname, + } + rules = append(rules, rule) + } + for _, r := range ag.Egressrule { + var ports string + if r.Startport == r.Endport { + ports = strconv.Itoa(r.Startport) + } else { + ports = fmt.Sprintf("%s-%s", r.Startport, r.Endport) + } + rule := map[string]interface{}{ + "cidr_list": r.Cidr, + "ports": ports, + "protocol": r.Protocol, + "traffic_type": "egress", + "security_group": r.Securitygroupname, + } + rules = append(rules, rule) + } + d.Set("rules", rules) + + return nil +} + +func resourceCloudStackSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Create a new parameter struct + p := cs.SecurityGroup.NewDeleteSecurityGroupParams() + p.SetId(d.Id()) + + // If there is a project supplied, we retrieve and set the project id + if err := setProjectid(p, cs, d); err != nil { + return err + } + + // Delete the security group + _, err := cs.SecurityGroup.DeleteSecurityGroup(p) + if err != nil { + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error deleting security group: %s", err) + } + + return nil +} diff --git a/vendor/github.com/xanzy/go-cloudstack/cloudstack/SecurityGroupService.go b/vendor/github.com/xanzy/go-cloudstack/cloudstack/SecurityGroupService.go index 6eec7916a..803442498 100644 --- a/vendor/github.com/xanzy/go-cloudstack/cloudstack/SecurityGroupService.go +++ b/vendor/github.com/xanzy/go-cloudstack/cloudstack/SecurityGroupService.go @@ -107,6 +107,10 @@ func (s *SecurityGroupService) CreateSecurityGroup(p *CreateSecurityGroupParams) return nil, err } + if resp, err = getRawValue(resp); err != nil { + return nil, err + } + var r CreateSecurityGroupResponse if err := json.Unmarshal(resp, &r); err != nil { return nil, err @@ -329,8 +333,8 @@ func (p *AuthorizeSecurityGroupIngressParams) toURLValues() url.Values { if v, found := p.p["usersecuritygrouplist"]; found { i := 0 for k, vv := range v.(map[string]string) { - u.Set(fmt.Sprintf("usersecuritygrouplist[%d].key", i), k) - u.Set(fmt.Sprintf("usersecuritygrouplist[%d].value", i), vv) + u.Set(fmt.Sprintf("usersecuritygrouplist[%d].account", i), k) + u.Set(fmt.Sprintf("usersecuritygrouplist[%d].group", i), vv) i++ } } @@ -617,8 +621,8 @@ func (p *AuthorizeSecurityGroupEgressParams) toURLValues() url.Values { if v, found := p.p["usersecuritygrouplist"]; found { i := 0 for k, vv := range v.(map[string]string) { - u.Set(fmt.Sprintf("usersecuritygrouplist[%d].key", i), k) - u.Set(fmt.Sprintf("usersecuritygrouplist[%d].value", i), vv) + u.Set(fmt.Sprintf("usersecuritygrouplist[%d].account", i), k) + u.Set(fmt.Sprintf("usersecuritygrouplist[%d].group", i), vv) i++ } } diff --git a/vendor/vendor.json b/vendor/vendor.json index b68e8f414..9b830ec39 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2137,11 +2137,11 @@ "revision": "9051bd6b44125d9472e0c148b5965692ab283d4a" }, { - "checksumSHA1": "CT9vuv0kaEnwEDcNywB/wD6eA54=", + "checksumSHA1": "s6bz9U3gi638R0Ln7v14h5+e2Wg=", "comment": "v2.1.3", "path": "github.com/xanzy/go-cloudstack/cloudstack", - "revision": "b33e8849d135b99cb8236237524d10b41f597cb3", - "revisionTime": "2016-08-31T14:38:59Z" + "revision": "ff444b1afdd1961f8118bad30db9fa27cb989ced", + "revisionTime": "2016-10-24T10:36:39Z" }, { "path": "github.com/xanzy/ssh-agent", diff --git a/website/source/docs/providers/cloudstack/r/security_group.html.markdown b/website/source/docs/providers/cloudstack/r/security_group.html.markdown new file mode 100644 index 000000000..418906f0b --- /dev/null +++ b/website/source/docs/providers/cloudstack/r/security_group.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_security_group" +sidebar_current: "docs-cloudstack-resource-security_group" +description: |- + Creates security group. +--- + +# cloudstack\_security\_group + +Creates security group. + +## Example Usage + +``` +resource "cloudstack_security_group" "default" { + name = "allow_web" + description = "Allow access to HTTP and HTTPS" + + rules = [ + { + cidr_list = "0.0.0.0/0" + protocol = "tcp" + ports = "80" + traffic_type = "ingress" + }, + { + cidr_list = "0.0.0.0/0" + protocol = "tcp" + ports = "443" + traffic_type = "ingress" + }, + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The Name of the Security Group to create. Changing this + forces a new resource to be created. + +* `description` - (Optional) The description of the Security Group to create. + Changing this forces a new resource to be created. + +* `rules` - (Optional) List of rule blocks, supported fields documented below. + +The `rule` block supports: + +* `cidr_list` - (Optional) A CIDR list to allow access to the given ports. + +* `security_group` - (Optional) A Security Group to apply the rules to. + +* `protocol` - (Required) The name of the protocol to allow. Valid options are: + `tcp`, `udp` and `icmp`. + +* `ports` - (Optional) List of ports and/or port ranges to allow. This can only + be specified if the protocol is TCP or UDP. + +* `traffic_type` - (Optional) Weither Ingress or Egress. (Default: Ingress). + +## Attributes Reference + +The following attributes are exported: + +* `name` - The name of the Security Group. +