2014-07-08 20:06:39 +02:00
package aws
import (
2014-08-21 07:24:13 +02:00
"bytes"
2014-07-08 20:06:39 +02:00
"fmt"
"log"
2014-08-22 17:46:03 +02:00
"sort"
2014-07-17 20:14:51 +02:00
"time"
2014-07-08 20:06:39 +02:00
2015-04-09 16:10:04 +02:00
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/ec2"
2014-08-21 07:24:13 +02:00
"github.com/hashicorp/terraform/helper/hashcode"
2014-07-17 20:14:51 +02:00
"github.com/hashicorp/terraform/helper/resource"
2014-08-20 19:40:18 +02:00
"github.com/hashicorp/terraform/helper/schema"
2014-07-08 20:06:39 +02:00
)
2014-08-20 19:40:18 +02:00
func resourceAwsSecurityGroup ( ) * schema . Resource {
return & schema . Resource {
Create : resourceAwsSecurityGroupCreate ,
Read : resourceAwsSecurityGroupRead ,
2014-08-20 19:54:43 +02:00
Update : resourceAwsSecurityGroupUpdate ,
2014-08-20 19:40:18 +02:00
Delete : resourceAwsSecurityGroupDelete ,
Schema : map [ string ] * schema . Schema {
"name" : & schema . Schema {
Type : schema . TypeString ,
2015-04-22 19:56:06 +02:00
Optional : true ,
Computed : true ,
2014-08-20 19:40:18 +02:00
ForceNew : true ,
} ,
"description" : & schema . Schema {
Type : schema . TypeString ,
2015-04-22 20:27:20 +02:00
Optional : true ,
Default : "Managed by Terraform" ,
2014-08-20 19:40:18 +02:00
} ,
"vpc_id" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
ForceNew : true ,
2014-10-11 02:12:03 +02:00
Computed : true ,
2014-08-20 19:40:18 +02:00
} ,
"ingress" : & schema . Schema {
2014-08-21 07:24:13 +02:00
Type : schema . TypeSet ,
2014-12-08 08:52:04 +01:00
Optional : true ,
2014-08-20 19:40:18 +02:00
Elem : & schema . Resource {
Schema : map [ string ] * schema . Schema {
"from_port" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
"to_port" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
"protocol" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
} ,
"cidr_blocks" : & schema . Schema {
Type : schema . TypeList ,
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
} ,
"security_groups" : & schema . Schema {
2014-10-21 19:49:27 +02:00
Type : schema . TypeSet ,
2014-08-20 19:40:18 +02:00
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
2014-10-21 19:49:27 +02:00
Set : func ( v interface { } ) int {
return hashcode . String ( v . ( string ) )
} ,
2014-08-20 19:40:18 +02:00
} ,
2014-09-30 23:19:16 +02:00
"self" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
2014-10-21 19:49:27 +02:00
Default : false ,
2014-09-30 23:19:16 +02:00
} ,
2014-08-20 19:40:18 +02:00
} ,
} ,
2015-01-23 15:46:20 +01:00
Set : resourceAwsSecurityGroupRuleHash ,
} ,
"egress" : & schema . Schema {
Type : schema . TypeSet ,
Optional : true ,
2015-02-18 18:27:55 +01:00
Computed : true ,
2015-01-23 15:46:20 +01:00
Elem : & schema . Resource {
Schema : map [ string ] * schema . Schema {
"from_port" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
"to_port" : & schema . Schema {
Type : schema . TypeInt ,
Required : true ,
} ,
"protocol" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
} ,
"cidr_blocks" : & schema . Schema {
Type : schema . TypeList ,
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
} ,
"security_groups" : & schema . Schema {
Type : schema . TypeSet ,
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
Set : func ( v interface { } ) int {
return hashcode . String ( v . ( string ) )
} ,
} ,
"self" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
Default : false ,
} ,
} ,
} ,
Set : resourceAwsSecurityGroupRuleHash ,
2014-08-20 19:40:18 +02:00
} ,
"owner_id" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2014-10-14 23:07:01 +02:00
"tags" : tagsSchema ( ) ,
2014-08-20 19:40:18 +02:00
} ,
}
}
func resourceAwsSecurityGroupCreate ( d * schema . ResourceData , meta interface { } ) error {
2015-04-16 22:05:55 +02:00
conn := meta . ( * AWSClient ) . ec2conn
2014-07-08 20:06:39 +02:00
2015-04-22 19:56:06 +02:00
securityGroupOpts := & ec2 . CreateSecurityGroupInput { }
2014-07-17 02:13:16 +02:00
2015-05-01 17:07:46 +02:00
if v := d . Get ( "vpc_id" ) ; v != nil {
2015-04-30 23:59:32 +02:00
if len ( d . Get ( "egress" ) . ( * schema . Set ) . List ( ) ) == 0 {
2015-05-01 17:07:46 +02:00
return fmt . Errorf ( "Error creating Security Group: Security groups inside a VPC require an egress rule. See http://localhost:4567/docs/providers/aws/r/security_group.html for more information." )
2015-04-30 23:59:32 +02:00
}
2015-05-01 17:07:46 +02:00
securityGroupOpts . VPCID = aws . String ( v . ( string ) )
2014-07-17 02:13:16 +02:00
}
2014-08-20 19:40:18 +02:00
if v := d . Get ( "description" ) ; v != nil {
2015-03-09 16:02:27 +01:00
securityGroupOpts . Description = aws . String ( v . ( string ) )
2014-07-08 20:06:39 +02:00
}
2015-04-22 19:56:06 +02:00
var groupName string
if v , ok := d . GetOk ( "name" ) ; ok {
groupName = v . ( string )
} else {
groupName = resource . UniqueId ( )
}
securityGroupOpts . GroupName = aws . String ( groupName )
2014-08-20 19:40:18 +02:00
log . Printf (
"[DEBUG] Security Group create configuration: %#v" , securityGroupOpts )
2015-04-09 16:10:04 +02:00
createResp , err := conn . CreateSecurityGroup ( securityGroupOpts )
2014-07-08 20:06:39 +02:00
if err != nil {
2014-08-20 19:40:18 +02:00
return fmt . Errorf ( "Error creating Security Group: %s" , err )
2014-07-08 20:06:39 +02:00
}
2015-03-09 16:02:27 +01:00
d . SetId ( * createResp . GroupID )
2014-07-08 20:06:39 +02:00
2014-08-20 19:40:18 +02:00
log . Printf ( "[INFO] Security Group ID: %s" , d . Id ( ) )
2014-07-17 20:14:51 +02:00
// Wait for the security group to truly exist
log . Printf (
2014-07-29 18:45:57 +02:00
"[DEBUG] Waiting for Security Group (%s) to exist" ,
2014-08-20 19:40:18 +02:00
d . Id ( ) )
2014-07-17 20:14:51 +02:00
stateConf := & resource . StateChangeConf {
Pending : [ ] string { "" } ,
Target : "exists" ,
2015-04-09 16:10:04 +02:00
Refresh : SGStateRefreshFunc ( conn , d . Id ( ) ) ,
2014-07-17 20:14:51 +02:00
Timeout : 1 * time . Minute ,
}
if _ , err := stateConf . WaitForState ( ) ; err != nil {
2014-08-20 19:40:18 +02:00
return fmt . Errorf (
2014-07-29 18:45:57 +02:00
"Error waiting for Security Group (%s) to become available: %s" ,
2014-08-20 19:40:18 +02:00
d . Id ( ) , err )
2014-07-17 20:14:51 +02:00
}
2014-07-08 20:06:39 +02:00
2014-09-30 23:19:16 +02:00
return resourceAwsSecurityGroupUpdate ( d , meta )
2014-07-08 20:06:39 +02:00
}
2015-01-23 15:46:20 +01:00
func resourceAwsSecurityGroupRead ( d * schema . ResourceData , meta interface { } ) error {
2015-04-16 22:05:55 +02:00
conn := meta . ( * AWSClient ) . ec2conn
2014-08-20 20:18:00 +02:00
2015-04-09 16:10:04 +02:00
sgRaw , _ , err := SGStateRefreshFunc ( conn , d . Id ( ) ) ( )
2014-08-20 20:18:00 +02:00
if err != nil {
return err
}
if sgRaw == nil {
d . SetId ( "" )
return nil
}
2015-04-09 16:10:04 +02:00
sg := sgRaw . ( * ec2 . SecurityGroup )
2015-01-23 15:46:20 +01:00
2015-03-09 16:02:27 +01:00
ingressRules := resourceAwsSecurityGroupIPPermGather ( d , sg . IPPermissions )
egressRules := resourceAwsSecurityGroupIPPermGather ( d , sg . IPPermissionsEgress )
2015-01-23 15:46:20 +01:00
d . Set ( "description" , sg . Description )
2015-03-09 16:02:27 +01:00
d . Set ( "name" , sg . GroupName )
d . Set ( "vpc_id" , sg . VPCID )
d . Set ( "owner_id" , sg . OwnerID )
2015-01-23 15:46:20 +01:00
d . Set ( "ingress" , ingressRules )
d . Set ( "egress" , egressRules )
2015-04-09 16:10:04 +02:00
d . Set ( "tags" , tagsToMapSDK ( sg . Tags ) )
2015-01-23 15:46:20 +01:00
return nil
}
func resourceAwsSecurityGroupUpdate ( d * schema . ResourceData , meta interface { } ) error {
2015-04-16 22:05:55 +02:00
conn := meta . ( * AWSClient ) . ec2conn
2015-01-23 15:46:20 +01:00
2015-04-09 16:10:04 +02:00
sgRaw , _ , err := SGStateRefreshFunc ( conn , d . Id ( ) ) ( )
2015-01-23 15:46:20 +01:00
if err != nil {
return err
}
if sgRaw == nil {
d . SetId ( "" )
return nil
}
2015-03-09 16:02:27 +01:00
2015-04-09 16:10:04 +02:00
group := sgRaw . ( * ec2 . SecurityGroup )
2015-01-23 15:46:20 +01:00
2015-02-18 01:39:25 +01:00
err = resourceAwsSecurityGroupUpdateRules ( d , "ingress" , meta , group )
2015-01-23 15:46:20 +01:00
if err != nil {
return err
}
if d . Get ( "vpc_id" ) != nil {
2015-02-18 01:39:25 +01:00
err = resourceAwsSecurityGroupUpdateRules ( d , "egress" , meta , group )
2015-01-23 15:46:20 +01:00
if err != nil {
return err
}
}
2015-04-09 16:10:04 +02:00
if err := setTagsSDK ( conn , d ) ; err != nil {
2014-10-14 23:07:01 +02:00
return err
}
2015-01-23 15:46:20 +01:00
d . SetPartial ( "tags" )
2014-11-24 21:22:18 +01:00
return resourceAwsSecurityGroupRead ( d , meta )
2014-08-20 19:54:43 +02:00
}
2014-08-20 19:40:18 +02:00
func resourceAwsSecurityGroupDelete ( d * schema . ResourceData , meta interface { } ) error {
2015-04-16 22:05:55 +02:00
conn := meta . ( * AWSClient ) . ec2conn
2014-07-08 20:06:39 +02:00
2014-08-20 19:40:18 +02:00
log . Printf ( "[DEBUG] Security Group destroy: %v" , d . Id ( ) )
2014-07-08 20:06:39 +02:00
2015-05-05 18:07:16 +02:00
return resource . Retry ( 2 * time . Minute , func ( ) error {
2015-04-09 16:10:04 +02:00
_ , err := conn . DeleteSecurityGroup ( & ec2 . DeleteSecurityGroupInput {
2015-03-09 16:02:27 +01:00
GroupID : aws . String ( d . Id ( ) ) ,
} )
2014-10-18 03:21:10 +02:00
if err != nil {
2015-03-09 16:02:27 +01:00
ec2err , ok := err . ( aws . APIError )
2014-10-18 03:29:48 +02:00
if ! ok {
return err
}
switch ec2err . Code {
case "InvalidGroup.NotFound" :
2014-10-18 03:21:10 +02:00
return nil
2014-10-18 03:29:48 +02:00
case "DependencyViolation" :
// If it is a dependency violation, we want to retry
return err
default :
// Any other error, we want to quit the retry loop immediately
2015-02-18 19:26:59 +01:00
return resource . RetryError { Err : err }
2014-10-18 03:21:10 +02:00
}
2014-07-08 20:06:39 +02:00
}
2014-10-18 03:29:48 +02:00
return nil
2014-10-18 03:21:10 +02:00
} )
2014-07-08 20:06:39 +02:00
}
2015-01-23 15:46:20 +01:00
func resourceAwsSecurityGroupRuleHash ( v interface { } ) int {
2014-11-21 17:58:34 +01:00
var buf bytes . Buffer
m := v . ( map [ string ] interface { } )
buf . WriteString ( fmt . Sprintf ( "%d-" , m [ "from_port" ] . ( int ) ) )
buf . WriteString ( fmt . Sprintf ( "%d-" , m [ "to_port" ] . ( int ) ) )
buf . WriteString ( fmt . Sprintf ( "%s-" , m [ "protocol" ] . ( string ) ) )
2015-03-17 21:48:10 +01:00
buf . WriteString ( fmt . Sprintf ( "%t-" , m [ "self" ] . ( bool ) ) )
2014-10-21 19:49:27 +02:00
2014-11-21 17:58:34 +01:00
// We need to make sure to sort the strings below so that we always
// generate the same hash code no matter what is in the set.
if v , ok := m [ "cidr_blocks" ] ; ok {
vs := v . ( [ ] interface { } )
s := make ( [ ] string , len ( vs ) )
for i , raw := range vs {
s [ i ] = raw . ( string )
2014-07-29 18:06:28 +02:00
}
2014-11-21 17:58:34 +01:00
sort . Strings ( s )
2014-07-29 18:06:28 +02:00
2014-11-21 17:58:34 +01:00
for _ , v := range s {
buf . WriteString ( fmt . Sprintf ( "%s-" , v ) )
2014-09-30 23:26:01 +02:00
}
2014-11-21 17:58:34 +01:00
}
if v , ok := m [ "security_groups" ] ; ok {
vs := v . ( * schema . Set ) . List ( )
s := make ( [ ] string , len ( vs ) )
for i , raw := range vs {
s [ i ] = raw . ( string )
2014-09-30 23:26:01 +02:00
}
2014-11-21 17:58:34 +01:00
sort . Strings ( s )
2014-10-21 19:49:27 +02:00
2014-11-21 17:58:34 +01:00
for _ , v := range s {
buf . WriteString ( fmt . Sprintf ( "%s-" , v ) )
2014-10-21 19:49:27 +02:00
}
}
2014-07-08 20:06:39 +02:00
2014-11-21 17:58:34 +01:00
return hashcode . String ( buf . String ( ) )
2014-07-15 18:40:20 +02:00
}
2014-07-17 20:14:51 +02:00
2015-04-09 16:10:04 +02:00
func resourceAwsSecurityGroupIPPermGather ( d * schema . ResourceData , permissions [ ] * ec2 . IPPermission ) [ ] map [ string ] interface { } {
2015-02-18 18:07:46 +01:00
ruleMap := make ( map [ string ] map [ string ] interface { } )
for _ , perm := range permissions {
2015-04-20 17:27:58 +02:00
var fromPort , toPort int64
2015-03-11 14:30:43 +01:00
if v := perm . FromPort ; v != nil {
2015-04-20 17:27:58 +02:00
fromPort = * v
2015-03-09 16:02:27 +01:00
}
2015-03-11 14:30:43 +01:00
if v := perm . ToPort ; v != nil {
2015-04-20 17:27:58 +02:00
toPort = * v
2015-03-09 16:02:27 +01:00
}
k := fmt . Sprintf ( "%s-%d-%d" , * perm . IPProtocol , fromPort , toPort )
2015-02-18 18:07:46 +01:00
m , ok := ruleMap [ k ]
if ! ok {
m = make ( map [ string ] interface { } )
ruleMap [ k ] = m
}
2015-03-09 16:02:27 +01:00
m [ "from_port" ] = fromPort
m [ "to_port" ] = toPort
m [ "protocol" ] = * perm . IPProtocol
2015-02-18 18:07:46 +01:00
2015-03-09 16:02:27 +01:00
if len ( perm . IPRanges ) > 0 {
2015-02-18 18:07:46 +01:00
raw , ok := m [ "cidr_blocks" ]
if ! ok {
2015-03-09 16:02:27 +01:00
raw = make ( [ ] string , 0 , len ( perm . IPRanges ) )
2015-02-18 18:07:46 +01:00
}
list := raw . ( [ ] string )
2015-03-09 16:02:27 +01:00
for _ , ip := range perm . IPRanges {
list = append ( list , * ip . CIDRIP )
}
2015-02-18 18:07:46 +01:00
m [ "cidr_blocks" ] = list
}
var groups [ ] string
2015-03-09 16:02:27 +01:00
if len ( perm . UserIDGroupPairs ) > 0 {
2015-04-16 22:28:18 +02:00
groups = flattenSecurityGroups ( perm . UserIDGroupPairs )
2015-02-18 18:07:46 +01:00
}
for i , id := range groups {
if id == d . Id ( ) {
groups [ i ] , groups = groups [ len ( groups ) - 1 ] , groups [ : len ( groups ) - 1 ]
m [ "self" ] = true
}
}
if len ( groups ) > 0 {
raw , ok := m [ "security_groups" ]
if ! ok {
raw = make ( [ ] string , 0 , len ( groups ) )
}
list := raw . ( [ ] string )
list = append ( list , groups ... )
m [ "security_groups" ] = list
}
}
rules := make ( [ ] map [ string ] interface { } , 0 , len ( ruleMap ) )
for _ , m := range ruleMap {
rules = append ( rules , m )
}
return rules
}
func resourceAwsSecurityGroupUpdateRules (
d * schema . ResourceData , ruleset string ,
2015-04-09 16:10:04 +02:00
meta interface { } , group * ec2 . SecurityGroup ) error {
2015-02-18 18:07:46 +01:00
if d . HasChange ( ruleset ) {
o , n := d . GetChange ( ruleset )
if o == nil {
o = new ( schema . Set )
}
if n == nil {
n = new ( schema . Set )
}
os := o . ( * schema . Set )
ns := n . ( * schema . Set )
2015-04-16 22:28:18 +02:00
remove := expandIPPerms ( group , os . Difference ( ns ) . List ( ) )
2015-04-30 23:59:32 +02:00
if len ( remove ) == 0 && len ( os . List ( ) ) == 0 && ruleset == "egress" {
// For new Security groups, a default Egress rule is created. Here we add
// the equivelent IPPermission struct for removal, so that only the
// supplied egress rules exist in the security group.
remove = append ( remove , & ec2 . IPPermission {
FromPort : aws . Long ( int64 ( 0 ) ) ,
ToPort : aws . Long ( int64 ( 0 ) ) ,
IPRanges : [ ] * ec2 . IPRange {
& ec2 . IPRange {
CIDRIP : aws . String ( "0.0.0.0/0" ) ,
} ,
} ,
IPProtocol : aws . String ( "-1" ) ,
} )
}
2015-04-16 22:28:18 +02:00
add := expandIPPerms ( group , ns . Difference ( os ) . List ( ) )
2015-02-18 18:07:46 +01:00
// TODO: We need to handle partial state better in the in-between
// in this update.
// TODO: It'd be nicer to authorize before removing, but then we have
// to deal with complicated unrolling to get individual CIDR blocks
// to avoid authorizing already authorized sources. Removing before
// adding is easier here, and Terraform should be fast enough to
// not have service issues.
if len ( remove ) > 0 || len ( add ) > 0 {
2015-04-16 22:05:55 +02:00
conn := meta . ( * AWSClient ) . ec2conn
2015-02-18 18:07:46 +01:00
2015-03-09 16:02:27 +01:00
var err error
2015-02-18 18:07:46 +01:00
if len ( remove ) > 0 {
2015-03-10 21:55:49 +01:00
log . Printf ( "[DEBUG] Revoking security group %#v %s rule: %#v" ,
2015-03-09 16:02:27 +01:00
group , ruleset , remove )
2015-02-18 18:07:46 +01:00
if ruleset == "egress" {
2015-04-09 16:10:04 +02:00
req := & ec2 . RevokeSecurityGroupEgressInput {
2015-03-09 16:02:27 +01:00
GroupID : group . GroupID ,
IPPermissions : remove ,
}
2015-04-09 16:10:04 +02:00
_ , err = conn . RevokeSecurityGroupEgress ( req )
2015-03-09 16:02:27 +01:00
} else {
2015-04-09 16:10:04 +02:00
req := & ec2 . RevokeSecurityGroupIngressInput {
2015-03-09 16:02:27 +01:00
GroupID : group . GroupID ,
IPPermissions : remove ,
}
2015-04-09 16:10:04 +02:00
_ , err = conn . RevokeSecurityGroupIngress ( req )
2015-02-18 18:07:46 +01:00
}
2015-02-18 18:27:55 +01:00
2015-03-09 16:02:27 +01:00
if err != nil {
2015-02-18 18:07:46 +01:00
return fmt . Errorf (
2015-03-09 16:02:27 +01:00
"Error authorizing security group %s rules: %s" ,
2015-02-18 18:07:46 +01:00
ruleset , err )
}
}
if len ( add ) > 0 {
2015-03-10 21:55:49 +01:00
log . Printf ( "[DEBUG] Authorizing security group %#v %s rule: %#v" ,
2015-03-09 16:02:27 +01:00
group , ruleset , add )
2015-02-18 18:07:46 +01:00
// Authorize the new rules
if ruleset == "egress" {
2015-04-09 16:10:04 +02:00
req := & ec2 . AuthorizeSecurityGroupEgressInput {
2015-03-09 16:02:27 +01:00
GroupID : group . GroupID ,
IPPermissions : add ,
}
2015-04-09 16:10:04 +02:00
_ , err = conn . AuthorizeSecurityGroupEgress ( req )
2015-03-09 16:02:27 +01:00
} else {
2015-04-09 16:10:04 +02:00
req := & ec2 . AuthorizeSecurityGroupIngressInput {
2015-03-09 16:02:27 +01:00
GroupID : group . GroupID ,
IPPermissions : add ,
}
2015-03-18 14:47:59 +01:00
if group . VPCID == nil || * group . VPCID == "" {
req . GroupID = nil
req . GroupName = group . GroupName
}
2015-04-09 16:10:04 +02:00
_ , err = conn . AuthorizeSecurityGroupIngress ( req )
2015-02-18 18:07:46 +01:00
}
2015-02-18 18:27:55 +01:00
2015-03-09 16:02:27 +01:00
if err != nil {
2015-02-18 18:07:46 +01:00
return fmt . Errorf (
"Error authorizing security group %s rules: %s" ,
ruleset , err )
}
}
}
}
return nil
}
2014-07-17 20:14:51 +02:00
// SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// a security group.
func SGStateRefreshFunc ( conn * ec2 . EC2 , id string ) resource . StateRefreshFunc {
return func ( ) ( interface { } , string , error ) {
2015-04-09 16:10:04 +02:00
req := & ec2 . DescribeSecurityGroupsInput {
GroupIDs : [ ] * string { aws . String ( id ) } ,
2015-03-09 16:02:27 +01:00
}
resp , err := conn . DescribeSecurityGroups ( req )
2014-07-17 20:14:51 +02:00
if err != nil {
2015-03-09 16:02:27 +01:00
if ec2err , ok := err . ( aws . APIError ) ; ok {
2014-07-17 20:28:40 +02:00
if ec2err . Code == "InvalidSecurityGroupID.NotFound" ||
ec2err . Code == "InvalidGroup.NotFound" {
resp = nil
err = nil
}
}
if err != nil {
2014-07-17 20:14:51 +02:00
log . Printf ( "Error on SGStateRefresh: %s" , err )
return nil , "" , err
}
}
if resp == nil {
return nil , "" , nil
}
2015-03-09 16:02:27 +01:00
group := resp . SecurityGroups [ 0 ]
2014-07-17 20:14:51 +02:00
return group , "exists" , nil
}
}