Merge pull request #131 from rackspace/security-groups-api

Attempt the security group API.
diff --git a/interfaces.go b/interfaces.go
index 6340c1c..b038aa7 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -193,4 +193,25 @@
 
 	// ShowKeyPair will yield the named keypair.
 	ShowKeyPair(name string) (KeyPair, error)
+
+	// ListSecurityGroups provides a listing of security groups for the tenant.
+	// This method works only if the provider supports the os-security-groups extension.
+	ListSecurityGroups() ([]SecurityGroup, error)
+
+	// CreateSecurityGroup lets a tenant create a new security group.
+	// Only the SecurityGroup fields which are specified will be marshalled to the API.
+	// This method works only if the provider supports the os-security-groups extension.
+	CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error)
+
+	// ListSecurityGroupsByServerId provides a list of security groups which apply to the indicated server.
+	// This method works only if the provider supports the os-security-groups extension.
+	ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error)
+
+	// SecurityGroupById returns a security group corresponding to the provided ID number.
+	// This method works only if the provider supports the os-security-groups extension.
+	SecurityGroupById(id int) (*SecurityGroup, error)
+
+	// DeleteSecurityGroupById disposes of a security group corresponding to the provided ID number.
+	// This method works only if the provider supports the os-security-groups extension.
+	DeleteSecurityGroupById(id int) error
 }
diff --git a/servers.go b/servers.go
index 92bb370..a66e9f2 100644
--- a/servers.go
+++ b/servers.go
@@ -397,6 +397,114 @@
 	return locationArr[len(locationArr)-1], err
 }
 
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) ListSecurityGroups() ([]SecurityGroup, error) {
+	var sgs []SecurityGroup
+
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint)
+		return perigee.Get(ep, perigee.Options{
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			Results: &struct {
+				SecurityGroups *[]SecurityGroup `json:"security_groups"`
+			}{&sgs},
+		})
+	})
+	return sgs, err
+}
+
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error) {
+	var actual *SecurityGroup
+
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint)
+		return perigee.Post(ep, perigee.Options{
+			ReqBody: struct {
+				AddSecurityGroup SecurityGroup `json:"security_group"`
+			}{desired},
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			Results: &struct {
+				SecurityGroup **SecurityGroup `json:"security_group"`
+			}{&actual},
+		})
+	})
+	return actual, err
+}
+
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error) {
+	var sgs []SecurityGroup
+
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		ep := fmt.Sprintf("%s/servers/%s/os-security-groups", gsp.endpoint, id)
+		return perigee.Get(ep, perigee.Options{
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			Results: &struct {
+				SecurityGroups *[]SecurityGroup `json:"security_groups"`
+			}{&sgs},
+		})
+	})
+	return sgs, err
+}
+
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) SecurityGroupById(id int) (*SecurityGroup, error) {
+	var actual *SecurityGroup
+
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id)
+		return perigee.Get(ep, perigee.Options{
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			Results: &struct {
+				SecurityGroup **SecurityGroup `json:"security_group"`
+			}{&actual},
+		})
+	})
+	return actual, err
+}
+
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) DeleteSecurityGroupById(id int) error {
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id)
+		return perigee.Delete(ep, perigee.Options{
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			OkCodes: []int{202},
+		})
+	})
+	return err
+}
+
+// SecurityGroup provides a description of a security group, including all its rules.
+type SecurityGroup struct {
+	Description string   `json:"description,omitempty"`
+	Id          int      `json:"id,omitempty"`
+	Name        string   `json:"name,omitempty"`
+	Rules       []SGRule `json:"rules,omitempty"`
+	TenantId    string   `json:"tenant_id,omitempty"`
+}
+
+// SGRule encapsulates a single rule which applies to a security group.
+// This definition is just a guess, based on the documentation found in another extension here: http://docs.openstack.org/api/openstack-compute/2/content/GET_os-security-group-default-rules-v2_listSecGroupDefaultRules_v2__tenant_id__os-security-group-rules_ext-os-security-group-default-rules.html
+type SGRule struct {
+	FromPort   int                    `json:"from_port,omitempty"`
+	Id         int                    `json:"id,omitempty"`
+	IpProtocol string                 `json:"ip_protocol,omitempty"`
+	IpRange    map[string]interface{} `json:"ip_range,omitempty"`
+	ToPort     int                    `json:"to_port,omitempty"`
+}
+
 // RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
 type RaxBandwidth struct {
 	AuditPeriodEnd    string `json:"audit_period_end"`