Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added golang_auto_start_stop_ec2/ec2sched/.DS_Store
Binary file not shown.
154 changes: 154 additions & 0 deletions golang_auto_start_stop_ec2/ec2sched/AutoStart/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package main

import (
"fmt"
"go/ec2sched/pkg/sess"
"os"

"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
)

type startInst interface {
startInst(instId string) (string, error)
}

type instInfo interface {
instInfo(tagName string) (*ec2.DescribeInstancesOutput, error)
}

type ec2Api struct {
Client ec2iface.EC2API
}

// Fetch instances with tag "AutoSart", which is passed as input parameter
func (e ec2Api) instInfo(tagName string) (*ec2.DescribeInstancesOutput, error) {

var maxOutput int = 75
m := int64(maxOutput)
var resp *ec2.DescribeInstancesOutput

input := &ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag-key"),
Values: []*string{
aws.String(tagName),
},
},
},
MaxResults: &m,
}

//Cycle through paginated results for describe instances (incase we have more than 75 instances)
for {
instOutput, err := e.Client.DescribeInstances(input)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
return nil, awsErr
}
return nil, err
}

if resp == nil {
resp = instOutput
} else {
resp.Reservations = append(resp.Reservations, instOutput.Reservations...)
}

if instOutput.NextToken == nil {
break
}

input.NextToken = instOutput.NextToken
}
//fmt.Println(resp)
return resp, nil

}

// Evaluate instances to see if we can start.
func evalInst(inst *ec2.Instance, s startInst) (string, error) {

fmt.Println(*inst.InstanceId)

for _, tag := range inst.Tags {
//ASGs not supported
if *tag.Key == "aws:autoscaling:groupName" {
return fmt.Sprintf("Skipping - %s is part of autoscaling group %s", string(*inst.InstanceId), *tag.Value), nil
}
}

result, err := s.startInst(*inst.InstanceId)
if err != nil {
return "", err
}
return "Starting Instance: " + result, nil
}

func (e ec2Api) startInst(instId string) (string, error) {

input := &ec2.StartInstancesInput{
InstanceIds: []*string{
aws.String(instId),
},
}

res, si_err := e.Client.StartInstances(input)
if si_err != nil {
if awsErr, ok := si_err.(awserr.Error); ok {
fmt.Println(awsErr.Error())
return "", awsErr
} else {
fmt.Println(si_err.Error())
return "", si_err
}
}
return *res.StartingInstances[0].InstanceId, nil
}

func HandleLambdaEvent() {

//Tag to look for on the instances
schedule_tag := "autostart"
region := os.Getenv("REGION_TZ")
sess, err := sess.EstablishSession(region)
e := ec2Api{
Client: ec2.New(sess),
}

instDesc, err := e.instInfo(schedule_tag)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

instCount := 0

for idx, res := range instDesc.Reservations {
instCount += len(res.Instances)

for _, inst := range instDesc.Reservations[idx].Instances {
if *inst.State.Name != "running" {
st, err := evalInst(inst, e)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(st)

} else {
fmt.Printf("Instance: %s is already running\n", *inst.InstanceId)
}
}
fmt.Println("-----")
}
fmt.Printf("Instance count evaluated with %s tag: %d", schedule_tag, instCount)
return
}

func main() {
lambda.Start(HandleLambdaEvent)
}
196 changes: 196 additions & 0 deletions golang_auto_start_stop_ec2/ec2sched/AutoStart/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package main

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/stretchr/testify/assert"
)

type MockEC2Client struct {
ec2iface.EC2API
DescribeInstancesOutputValue ec2.DescribeInstancesOutput
StartInstancesOutputValue ec2.StartInstancesOutput
}

type mockStartInst struct{}

func (m *MockEC2Client) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
return &m.DescribeInstancesOutputValue, nil
}

func (m *MockEC2Client) StartInstances(input *ec2.StartInstancesInput) (*ec2.StartInstancesOutput, error) {
return &m.StartInstancesOutputValue, nil
}

func (m mockStartInst) startInst(instId string) (string, error) {
return "i-0c938b5e573fb0f26", nil
}

var outputZero = ec2.DescribeInstancesOutput{}
var outputWithAutostart = ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{
{
InstanceId: aws.String("i-0c938b5e573fb0f26"),
InstanceType: aws.String("m5.large"),
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNameStopped),
},
Tags: []*ec2.Tag{
{
Key: aws.String("autostart"),
Value: aws.String("true"),
},
{
Key: aws.String("autostop"),
Value: aws.String("true"),
},
},
},
{
InstanceId: aws.String("i-0c938b5e573fb0f27"),
InstanceType: aws.String("m5.large"),
State: &ec2.InstanceState{
Name: aws.String(ec2.InstanceStateNameRunning),
},
Tags: []*ec2.Tag{
{
Key: aws.String("autostart"),
Value: aws.String("true"),
},
{
Key: aws.String("aws:autoscaling:groupName"),
Value: aws.String("test-asg"),
},
},
},
},
},
},
}

func TestInstInfo(t *testing.T) {

//Resp is the result from the ec2 api
//Expected is the expected result from the function instInfo
cases := []struct {
Name string
Resp ec2.DescribeInstancesOutput
Expected ec2.DescribeInstancesOutput
}{
{
Name: "AutostartPresent",
Resp: outputWithAutostart,
Expected: outputWithAutostart,
},
{
Name: "Zero EC2s",
Resp: outputZero,
Expected: outputZero,
},
}

//time to iterate over the test cases
for _, c := range cases {

//Sub test for each case
t.Run(c.Name, func(t *testing.T) {
e := ec2Api{
Client: &MockEC2Client{
EC2API: nil,
DescribeInstancesOutputValue: c.Resp,
},
}
inst, err := e.instInfo("autostop")

if err != nil {
fmt.Println("Unexpected Error - ", err.Error())
}

assert.Equal(t, "*ec2.DescribeInstancesOutput", fmt.Sprintf("%T", inst))

})
}
}

func TestEvalInst(t *testing.T) {

//Resp is the result from the ec2 api StartInstances
cases := []struct {
Name string
InstanceInput *ec2.Instance
Expected string
}{
{
Name: "Autoscaling group present",
InstanceInput: outputWithAutostart.Reservations[0].Instances[1],
Expected: "Skipping - i-0c938b5e573fb0f27 is part of autoscaling group test-asg",
},
{
Name: "Autoscaling group not present",
InstanceInput: outputWithAutostart.Reservations[0].Instances[0],
Expected: "Starting Instance: i-0c938b5e573fb0f26",
},
}

//time to iterate over the test cases
for _, c := range cases {

//Sub test for each case
t.Run(c.Name, func(t *testing.T) {
s := mockStartInst{}
st, err := evalInst(c.InstanceInput, s)

if err != nil {
fmt.Println("Unexpected Error - ", err.Error())
}

assert.Equal(t, c.Expected, st)

})
}

}

func TestStartInst(t *testing.T) {

//Resp is the result from the ec2 api StartInstances
cases := []struct {
Name string
Resp ec2.StartInstancesOutput
Expected string
}{
{
Name: "StartInstancesOutput",
Resp: ec2.StartInstancesOutput{StartingInstances: []*ec2.InstanceStateChange{{InstanceId: aws.String("i-0c938b5e573fb0f26")}}},
Expected: "i-0c938b5e573fb0f26",
},
}

//time to iterate over the test cases
for _, c := range cases {

//Sub test for each case
t.Run(c.Name, func(t *testing.T) {
e := ec2Api{
Client: &MockEC2Client{
EC2API: nil,
StartInstancesOutputValue: c.Resp,
},
}
inst, err := e.startInst("i-0c938b5e573fb0f26")

if err != nil {
fmt.Println("Unexpected Error - ", err.Error())
}

assert.Equal(t, c.Expected, inst)

})
}
}
Loading