diff --git a/resources/aws_ec2.go b/resources/aws_ec2.go index 09cb7b51..6dd9adf2 100644 --- a/resources/aws_ec2.go +++ b/resources/aws_ec2.go @@ -33,6 +33,8 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/sns" + multierr "github.com/hashicorp/go-multierror" errwrap "github.com/pkg/errors" ) @@ -44,6 +46,12 @@ const ( // AwsPrefix is a const which gets prepended onto object names. We can only use // alphanumeric chars, underscores and hyphens for sns topics and cloud watch rules. AwsPrefix = "_mgmt-" + // Ec2Prefix is added to the names of sns and cloudwatch objects. + Ec2Prefix = AwsPrefix + "ec2-" + // SnsPrefix gets prepended onto the sns topic. + SnsPrefix = Ec2Prefix + "sns-" + // SnsTopicName is the name of the sns topic created by snsMakeTopic. + SnsTopicName = SnsPrefix + "events" // waitTimeout is the duration in seconds of the timeout context in CheckApply. waitTimeout = 400 ) @@ -88,6 +96,11 @@ type AwsEc2Res struct { client *ec2.EC2 // client session for AWS API calls + snsClient *sns.SNS // client for AWS SNS API calls + // snsTopicArn requires looping through every topic to get, + // so we save it here when we create the topic instead. + snsTopicArn string + awsChan chan *chanStruct // channel used to send events and errors to Watch() closeChan chan struct{} // channel used to cancel context when it's time to shut down wg *sync.WaitGroup // waitgroup for goroutines in Watch() @@ -175,6 +188,25 @@ func (obj *AwsEc2Res) Init() error { obj.closeChan = make(chan struct{}) obj.wg = &sync.WaitGroup{} + // if we are using sns watch + if obj.WatchListenAddr != "" { + // make sns client + snsSess, err := session.NewSession(&aws.Config{ + Region: aws.String(obj.Region), + }) + if err != nil { + return errwrap.Wrapf(err, "error creating sns session") + } + obj.snsClient = sns.New(snsSess) + // make the sns topic + snsTopicArn, err := obj.snsMakeTopic() + if err != nil { + return errwrap.Wrapf(err, "error making sns topic") + } + // save the topicArn for later use + obj.snsTopicArn = snsTopicArn + } + return obj.BaseRes.Init() // call base init, b/c we're overriding } @@ -656,6 +688,9 @@ func (obj *AwsEc2Res) Compare(r Res) bool { if obj.UserData != res.UserData { return false } + if obj.WatchListenAddr != res.WatchListenAddr { + return false + } return true } @@ -708,3 +743,50 @@ func (obj *AwsEc2Res) snsPostHandler(w http.ResponseWriter, req *http.Request) { log.Printf("%s: Post: %s", obj, string(post)) } } + +// snsMakeTopic creates a topic on aws sns. +func (obj *AwsEc2Res) snsMakeTopic() (string, error) { + // make topic + topicInput := &sns.CreateTopicInput{ + Name: aws.String(SnsTopicName), + } + topic, err := obj.snsClient.CreateTopic(topicInput) + if err != nil { + return "", err + } + log.Printf("%s: Created SNS Topic", obj) + if topic.TopicArn == nil { + return "", fmt.Errorf("TopicArn is nil") + } + return *topic.TopicArn, nil +} + +// snsDeleteTopic deletes the sns topic. +func (obj *AwsEc2Res) snsDeleteTopic(topicArn string) error { + // delete the topic + dtInput := &sns.DeleteTopicInput{ + TopicArn: aws.String(topicArn), + } + if _, err := obj.snsClient.DeleteTopic(dtInput); err != nil { + return err + } + log.Printf("%s: Deleted SNS Topic", obj) + return nil +} + +// Close cleans up when we're done. This is needed to delete some of the AWS +// objects created for the SNS endpoint. +func (obj *AwsEc2Res) Close() error { + var errList error + // clean up sns objects created by Init/snsWatch + if obj.snsClient != nil { + // delete the topic + if err := obj.snsDeleteTopic(obj.snsTopicArn); err != nil { + errList = multierr.Append(errList, err) + } + } + if err := obj.BaseRes.Close(); err != nil { + errList = multierr.Append(errList, err) // list of errors + } + return errList +}