diff --git a/golang_auto_start_stop_ec2/ec2sched/.DS_Store b/golang_auto_start_stop_ec2/ec2sched/.DS_Store
new file mode 100644
index 0000000..7adcd08
Binary files /dev/null and b/golang_auto_start_stop_ec2/ec2sched/.DS_Store differ
diff --git a/golang_auto_start_stop_ec2/ec2sched/AutoStart/main.go b/golang_auto_start_stop_ec2/ec2sched/AutoStart/main.go
new file mode 100644
index 0000000..5b894fc
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/AutoStart/main.go
@@ -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)
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/AutoStart/main_test.go b/golang_auto_start_stop_ec2/ec2sched/AutoStart/main_test.go
new file mode 100644
index 0000000..8ac09ee
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/AutoStart/main_test.go
@@ -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)
+
+ })
+ }
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/AutoStop/main.go b/golang_auto_start_stop_ec2/ec2sched/AutoStop/main.go
new file mode 100644
index 0000000..2f0ea17
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/AutoStop/main.go
@@ -0,0 +1,152 @@
+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 stoptInst interface {
+ stopInst(instId string) (string, error)
+}
+
+type instInfo interface {
+ instInfo(tagName string) (*ec2.DescribeInstancesOutput, error)
+}
+
+type ec2Api struct {
+ Client ec2iface.EC2API
+}
+
+// Fetch instances with tag "AutoStop", 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 stop.
+func evalInst(inst *ec2.Instance, s stoptInst) (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.stopInst(*inst.InstanceId)
+ if err != nil {
+ return "", err
+ }
+ return "Stopping Instance: " + result, nil
+}
+
+func (e ec2Api) stopInst(instId string) (string, error) {
+
+ input := &ec2.StopInstancesInput{
+ InstanceIds: []*string{
+ aws.String(instId),
+ },
+ }
+
+ res, si_err := e.Client.StopInstances(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.StoppingInstances[0].InstanceId, nil
+}
+
+func HandleLambdaEvent() {
+
+ //Tag too look for on the instances
+ schedule_tag := "autostop"
+ 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 != "stopped" {
+ st, err := evalInst(inst, e)
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(st)
+
+ } else {
+ fmt.Printf("Instance: %s is already stopped\n", *inst.InstanceId)
+ }
+ }
+ fmt.Println("-----")
+ }
+ fmt.Printf("Instance count evaluated with %s tag: %d", schedule_tag, instCount)
+ return
+}
+
+func main() {
+ lambda.Start(HandleLambdaEvent)
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/AutoStop/main_test.go b/golang_auto_start_stop_ec2/ec2sched/AutoStop/main_test.go
new file mode 100644
index 0000000..5358960
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/AutoStop/main_test.go
@@ -0,0 +1,197 @@
+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
+ StopInstancesOutputValue ec2.StopInstancesOutput
+}
+
+type mockStopInst struct{}
+
+func (m *MockEC2Client) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
+ return &m.DescribeInstancesOutputValue, nil
+}
+
+func (m *MockEC2Client) StopInstances(input *ec2.StopInstancesInput) (*ec2.StopInstancesOutput, error) {
+ return &m.StopInstancesOutputValue, nil
+}
+
+func (m mockStopInst) stopInst(instId string) (string, error) {
+ return "i-0c938b5e573fb0f26", nil
+}
+
+// Output for mocked DescribeInstancesOutput from DesribeInstances EC2 api call
+var outputZero = ec2.DescribeInstancesOutput{}
+var outputWithAutostop = 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("autostop"),
+ 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: "AutostopPresent",
+ Resp: outputWithAutostop,
+ Expected: outputWithAutostop,
+ },
+ {
+ Name: "NoInstances",
+ 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: outputWithAutostop.Reservations[0].Instances[1],
+ Expected: "Skipping - i-0c938b5e573fb0f27 is part of autoscaling group test-asg",
+ },
+ {
+ Name: "Autoscaling group not present",
+ InstanceInput: outputWithAutostop.Reservations[0].Instances[0],
+ Expected: "Stopping 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 := mockStopInst{}
+ 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.StopInstancesOutput
+ Expected string
+ }{
+ {
+ Name: "StopInstancesOutput",
+ Resp: ec2.StopInstancesOutput{StoppingInstances: []*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,
+ StopInstancesOutputValue: c.Resp,
+ },
+ }
+ inst, err := e.stopInst("i-0c938b5e573fb0f26")
+
+ if err != nil {
+ fmt.Println("Unexpected Error - ", err.Error())
+ }
+
+ assert.Equal(t, c.Expected, inst)
+
+ })
+ }
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/README.md b/golang_auto_start_stop_ec2/ec2sched/README.md
new file mode 100644
index 0000000..b3b2dc5
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/README.md
@@ -0,0 +1,66 @@
+# ec2sched
+
+Containing lambda code for AutoStart/AutoStop, StartWeekDay/StopWeekDay
+
+Asgsched allows for customizable Start Stop schedules to fit a variaty of use cases. The code evaluates tags on the autoscaling groups determining actions.
+
+Code Info
++ Code base language [go1.20.1](https://go.dev/doc/)
+
+
+
+## Schedule Functions
+
+The sheduling lambda functions operate by in large the same way. Collects the autoscaling groups, views the corresponding tag, and takes actions based on the tag value.
+> [!NOTE]
+> Tag names and values are case sensitive.
+
+If action is needed the autoscaling group actions are suspended (Launch, Terminate, Alarm Notification, etc.) then the underlying instances are stopped, the inverse happens when autoscaling groups need to start: underlying autoscaling group instance are started, then autoscaling actions are resumed. Fuction specifics outlined below
+
+
+
+
+### AutoStart/AutoStop
+
+AutoStart/AutoStop functions look for the AutoSart/AutoStop tag on the autoscaling group. If the tag (AutoStart/AutoStop) exists and value is `true` the function takes action.
+
+The deployed lambda for AutoStart and AutoStop is typically triggered via eventbridge rules running at the desired times
+
+Deploy Specifics
++ Tag Names
+ - AutoStart
+ - AutoStop
++ Tag Values
+ - `true`
+ - If that tag value is blank or a value other than `true` no action will take place.
+
+
+
+
+### Start/Stop and WeekDay/WeekEnd
+
+Start/Stop WeekDay/WeekEnd functions look for time values via tags on the autoscaling group. If that tag is present and the value (time) is plus or minus 5 minutes of the function running, currently a weekday is (Monday-Friday according to TZ tag), the start stop action will take place.
+
+The deployed lambda for Start/Stop WeekDay/WeekEnd is intended to triggered via eventbridge rule which runs every 5min.
+
+Deploy Specifics
++ Tag Names
+ - startweekday
+ - stopweekday
+ - startweekend
+ - stopweekend
++ Tag Values
+ - 24 hour values i.e. 14:00 for 2pm
++ Environment Variables
+ - TZ: Timezone the specified time value will be assessed i.e. US/Pacific, US/Eastern, Europe/London **Required**
+
+---
+
+
+# Build Binaries
+
+There are different options for OS and architecture (GOOS & GOARCH). To build on linux for arm64
+`env GOOS=linux GOARCH=arm64 go build -o bootstrap main.go`
+> [!NOTE]
+> Be sure to set the proper lambda properties i.e. Runtime, Architectures, and Handler that work with your GOOS and GOARCH values.
+
diff --git a/golang_auto_start_stop_ec2/ec2sched/go.mod b/golang_auto_start_stop_ec2/ec2sched/go.mod
new file mode 100644
index 0000000..e63e4c8
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/go.mod
@@ -0,0 +1,17 @@
+module go/ec2sched
+
+go 1.20
+
+require (
+ github.com/aws/aws-sdk-go v1.44.298
+ github.com/stretchr/testify v1.8.4
+)
+
+require (
+ github.com/aws/aws-lambda-go v1.41.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/jmespath/go-jmespath v0.4.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/stretchr/objx v0.5.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/golang_auto_start_stop_ec2/ec2sched/go.sum b/golang_auto_start_stop_ec2/ec2sched/go.sum
new file mode 100644
index 0000000..c4c5ad2
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/go.sum
@@ -0,0 +1,61 @@
+github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y=
+github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM=
+github.com/aws/aws-sdk-go v1.44.277 h1:YHmyzBPARTJ7LLYV1fxbfEbQOaUh3kh52hb7nBvX3BQ=
+github.com/aws/aws-sdk-go v1.44.277/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g=
+github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/golang_auto_start_stop_ec2/ec2sched/pkg/sess/sess.go b/golang_auto_start_stop_ec2/ec2sched/pkg/sess/sess.go
new file mode 100644
index 0000000..3e48206
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/pkg/sess/sess.go
@@ -0,0 +1,22 @@
+package sess
+
+import (
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/awserr"
+ "github.com/aws/aws-sdk-go/aws/session"
+)
+
+func EstablishSession(region string) (*session.Session, error) {
+ sess, err := session.NewSession(&aws.Config{
+ Region: aws.String(region),
+ })
+
+ if err != nil {
+ if awsErr, ok := err.(awserr.Error); ok {
+ return nil, awsErr
+ }
+ return nil, err
+ }
+
+ return sess, nil
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/pkg/settz/settz.go b/golang_auto_start_stop_ec2/ec2sched/pkg/settz/settz.go
new file mode 100644
index 0000000..3673347
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/pkg/settz/settz.go
@@ -0,0 +1,20 @@
+package settz
+
+import (
+ "os"
+ "time"
+)
+
+func SetRegion(tz string) (string, error) {
+ if tz == "" {
+ tz = "UTC"
+ }
+
+ loc, err := time.LoadLocation(tz)
+ if err != nil {
+ return "", err
+ }
+
+ os.Setenv("TZ", loc.String())
+ return loc.String(), nil
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/pkg/settz/settz_test.go b/golang_auto_start_stop_ec2/ec2sched/pkg/settz/settz_test.go
new file mode 100644
index 0000000..8192d18
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/pkg/settz/settz_test.go
@@ -0,0 +1,35 @@
+package settz
+
+import (
+ "testing"
+)
+
+func TestSetRegion(t *testing.T) {
+
+ t.Run("Usecase 1", func(t *testing.T) {
+ result, _ := SetRegion("")
+ expected_res := "UTC"
+
+ if result != expected_res {
+ t.Errorf("SetRegion(\"\") = %s; expected %s", result, expected_res)
+ }
+ })
+
+ t.Run("Usecase 2", func(t *testing.T) {
+ result, _ := SetRegion("America/Los_Angeles")
+ expected_res := "America/Los_Angeles"
+
+ if result != expected_res {
+ t.Errorf("SetRegion(\"America/Los_Angeles\") = %s; expected %s", result, expected_res)
+ }
+ })
+
+ t.Run("Usecase 3", func(t *testing.T) {
+ _, err := SetRegion("America/Los_Angelos")
+ expected_err := "unknown time zone America/Los_Angelos"
+
+ if err.Error() != expected_err {
+ t.Errorf("SetRegion(\"America/Los_Angelos\") = %s; expected %s", err.Error(), expected_err)
+ }
+ })
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/startWeekDay/.DS_Store b/golang_auto_start_stop_ec2/ec2sched/startWeekDay/.DS_Store
new file mode 100644
index 0000000..f93ad88
Binary files /dev/null and b/golang_auto_start_stop_ec2/ec2sched/startWeekDay/.DS_Store differ
diff --git a/golang_auto_start_stop_ec2/ec2sched/startWeekDay/main.go b/golang_auto_start_stop_ec2/ec2sched/startWeekDay/main.go
new file mode 100644
index 0000000..007f015
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/startWeekDay/main.go
@@ -0,0 +1,195 @@
+package main
+
+import (
+ "fmt"
+ "go/ec2sched/pkg/sess"
+ "go/ec2sched/pkg/settz"
+ "os"
+ "time"
+
+ "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, region string) (string, error)
+}
+
+type instInfo interface {
+ instInfo(tagName string, region string) (*ec2.DescribeInstancesOutput, error)
+}
+
+type ec2Api struct {
+ Client ec2iface.EC2API
+}
+
+func (e ec2Api) instInfo(tagName string, region 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
+
+}
+
+func (e ec2Api) startInst(instId string, region 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
+ }
+ }
+ //fmt.Println(res)
+ return *res.StartingInstances[0].InstanceId, nil
+}
+
+func evalInst(inst *ec2.Instance, region string, curTime time.Time, s startInst) (string, error) {
+ dayOfWeek := curTime.Weekday()
+ modTime := curTime.Format(("15:04"))
+
+ //startTime is the desired start time for the ec2 instance
+ startTime := ""
+
+ fmt.Println(*inst.InstanceId)
+
+ //Checks to see if weekend. If so skip out
+ if int(dayOfWeek) >= 6 && int(dayOfWeek) <= 7 {
+ return fmt.Sprintf("Current day of week %s. StartWeekDay requires non weekend values", dayOfWeek), nil
+ }
+
+ for _, tag := range inst.Tags {
+ if *tag.Key == "StartWeekDay" {
+ //fmt.Println("StartWeekDay tag found. Value: ", *tag.Value)
+ startTime = *tag.Value
+ }
+ //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
+ }
+ }
+
+ //Point here is to get the times on the same day to compare time of day, not just the date. Ensuring Apples to Apples
+ cur_tod, _ := time.Parse("15:04", modTime)
+ start_tod, start_tod_err := time.Parse("15:04", startTime)
+
+ if start_tod_err != nil {
+ return "", start_tod_err
+ }
+
+ cur_minus := cur_tod.Add(-time.Minute * 5)
+ cur_plus := cur_tod.Add(time.Minute * 5)
+
+ if start_tod.After(cur_minus) && start_tod.Before(cur_plus) {
+ result, err := s.startInst(*inst.InstanceId, region)
+ if err != nil {
+ return "", err
+ }
+ return "Starting Instance: " + result, nil
+ } else {
+ strVal := string(*inst.InstanceId)
+ return "StartWeekDay schedule not matched for: " + strVal, nil
+ }
+}
+
+// Handler doing bulk of work
+// Environment variables set at the lambda configuration level
+func HandleLambdaEvent() {
+
+ //Tag to look for on the instance
+ schedule_tag := "startweekday"
+ tz, err := settz.SetRegion(os.Getenv("TZ"))
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(tz)
+ fmt.Println(time.Now())
+
+ region := os.Getenv("REGION_TZ")
+
+ sess, _ := sess.EstablishSession(region)
+ e := ec2Api{
+ Client: ec2.New(sess),
+ }
+
+ instDesc, err := e.instInfo(schedule_tag, region)
+ if err != nil {
+ fmt.Println(err.Error())
+ 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, region, time.Now(), 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
+}
+
+// Go must call main function first, so we call the handler from the main.
+func main() {
+ lambda.Start(HandleLambdaEvent)
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/startWeekDay/main_test.go b/golang_auto_start_stop_ec2/ec2sched/startWeekDay/main_test.go
new file mode 100644
index 0000000..ca01f48
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/startWeekDay/main_test.go
@@ -0,0 +1,308 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "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"
+)
+
+// Create a mock structure implementing the ec2iface interface. Makes the
+// call to aws ec2 api
+type MockEC2Client struct {
+ ec2iface.EC2API
+ DescribeInstancesOutputValue ec2.DescribeInstancesOutput
+ StartInstancesOutputValue ec2.StartInstancesOutput
+}
+
+// Implements the interface
+type mockStartInst struct{}
+
+// Implement the mocked DescribeInstances function
+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, region string) (string, error) {
+ return "i-0c938b5e573fb0f26", nil
+}
+
+func TestInstInfo(t *testing.T) {
+ //Becauase we use the filter for included in the DescribeInstances we're testing to see
+ //we get a valid data type back
+
+ //Response from ec2 aws api and the expected result for function instInfo
+ descInstancesWithStartWeekDay := 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("startweekday"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("stopweekday"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Resp ec2.DescribeInstancesOutput
+ Expected ec2.DescribeInstancesOutput
+ }{
+ {
+ Name: "StartWeekDayPresent",
+ Resp: descInstancesWithStartWeekDay,
+ Expected: descInstancesWithStartWeekDay,
+ },
+ }
+
+ //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("startweekday", "us-east-1")
+
+ if err != nil {
+ fmt.Println("Unexpected Error - ", err.Error())
+ }
+
+ assert.Equal(t, "*ec2.DescribeInstancesOutput", fmt.Sprintf("%T", inst))
+
+ })
+ }
+}
+
+func TestStartInst(t *testing.T) {
+ //Do we start the instance? What do we get back from the startInst function
+
+ //The outputs we're mocking for the ec2 aws api for StartInstances
+ var (
+ //Output when starting Instance
+ startInstancesOutput = ec2.StartInstancesOutput{
+ StartingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("stopped"),
+ },
+ },
+ },
+ }
+
+ //Output when instance is stopped
+ startInstancesOutputStopped = ec2.StartInstancesOutput{
+ StartingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("running"),
+ },
+ },
+ },
+ }
+ )
+
+ cases := []struct {
+ Name string
+ Resp ec2.StartInstancesOutput
+ Expected string
+ }{
+ {
+ Name: "ProperInstanceId",
+ Resp: startInstancesOutput,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ {
+ Name: "InstanceRunning",
+ Resp: startInstancesOutputStopped,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ }
+
+ 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,
+ },
+ }
+ startInst, err := e.startInst("i-0c61fb5e573fb0c55", "us-east-1")
+
+ if err != nil {
+ t.Errorf("startInst(\"i-0c61fb5e573fb0c55\", \"us-east-1\") received %s; Expected %s", err.Error(), c.Expected)
+ }
+
+ assert.Equal(t, startInst, c.Expected)
+
+ })
+ }
+}
+
+func TestEvalInst(t *testing.T) {
+ //Figure out what hapens with time, and day of the week, and if the instnace starts
+
+ //Input to be used for eval inst. Perhaps pair it down to just the Instances section
+ goodTime := 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("startweekday"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("stopweekday"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ badTime := 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("startweekday"),
+ Value: aws.String("155540:0"),
+ },
+ {
+ Key: aws.String("stopweekday"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ //badTag meaning autoscaling group to be found
+ badTag := 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("startweekday"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("stopweekday"),
+ Value: aws.String("17:00"),
+ },
+ {
+ Key: aws.String("aws:autoscaling:groupName"),
+ Value: aws.String("testing-autoscaling-group"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Expected string
+ Time time.Time
+ Input *ec2.Instance
+ }{
+ {
+ Name: "StartInstance",
+ Expected: "Starting Instance: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 17, 15, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Start time mismatch",
+ Expected: "StartWeekDay schedule not matched for: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 17, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Weekend not Weekday",
+ Expected: "Current day of week Saturday. StartWeekDay requires non weekend values",
+ Time: time.Date(2000, 11, 18, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Improper tag time format",
+ Expected: "", //Error will throw instead
+ Time: time.Date(2000, 11, 17, 15, 02, 00, 0, time.UTC),
+ Input: badTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Autoscaling group present",
+ Expected: "Skipping - i-0c938b5e573fb0f26 is part of autoscaling group testing-autoscaling-group",
+ Time: time.Date(2000, 11, 17, 15, 02, 00, 0, time.UTC),
+ Input: badTag.Reservations[0].Instances[0],
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ //Put in the rest of the code to pass the mocked Start Inst
+ e := mockStartInst{}
+ result, err := evalInst(c.Input, "us-east-1", c.Time, e)
+ if err != nil {
+ assert.Equal(t, "parsing time \"155540:0\" as \"15:04\": cannot parse \"5540:0\" as \":\"", err.Error())
+ }
+ assert.Equal(t, c.Expected, result)
+ })
+ }
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/startWeekEnd/main.go b/golang_auto_start_stop_ec2/ec2sched/startWeekEnd/main.go
new file mode 100644
index 0000000..cd99220
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/startWeekEnd/main.go
@@ -0,0 +1,193 @@
+package main
+
+import (
+ "fmt"
+ "go/ec2sched/pkg/sess"
+ "go/ec2sched/pkg/settz"
+ "os"
+ "time"
+
+ "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, region string) (string, error)
+}
+
+type instInfo interface {
+ instInfo(tagName string, region string) (*ec2.DescribeInstancesOutput, error)
+}
+
+type ec2Api struct {
+ Client ec2iface.EC2API
+}
+
+func (e ec2Api) instInfo(tagName string, region 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
+}
+
+func (e ec2Api) startInst(instId string, region 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
+ }
+ }
+ //fmt.Println(res)
+ return *res.StartingInstances[0].InstanceId, nil
+}
+
+func evalInst(inst *ec2.Instance, region string, curTime time.Time, s startInst) (string, error) {
+ dayOfWeek := curTime.Weekday()
+ modTime := curTime.Format(("15:04"))
+
+ //startTime is the desired start time for the ec2 instance
+ startTime := ""
+
+ fmt.Println(*inst.InstanceId)
+ //Checks to see if weekday. If so skip out
+ if int(dayOfWeek) < 6 || int(dayOfWeek) > 7 {
+ return fmt.Sprintf("Current day of week %s. StartWeekEnd requires non weekday values", dayOfWeek), nil
+ }
+
+ for _, tag := range inst.Tags {
+ if *tag.Key == "StartWeekEnd" {
+ //fmt.Println("StartWeekEnd tag found. Value: ", *tag.Value)
+ startTime = *tag.Value
+ }
+ //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
+ }
+ }
+
+ //Point here is to get the times on the same day to compare time of day, not just the date. Ensuring Apples to Apples
+ cur_tod, _ := time.Parse("15:04", modTime)
+ start_tod, start_tod_err := time.Parse("15:04", startTime)
+
+ if start_tod_err != nil {
+ return "", start_tod_err
+ }
+
+ cur_minus := cur_tod.Add(-time.Minute * 5)
+ cur_plus := cur_tod.Add(time.Minute * 5)
+
+ if start_tod.After(cur_minus) && start_tod.Before(cur_plus) {
+ result, err := s.startInst(*inst.InstanceId, region)
+ if err != nil {
+ return "", err
+ }
+ return "Starting Instance: " + result, nil
+ } else {
+ strVal := string(*inst.InstanceId)
+ return "StartWeekEnd schedule not matched for: " + strVal, nil
+ }
+}
+
+// Handler doing bulk of work
+// Environment variables set at the lambda configuration level
+func HandleLambdaEvent() {
+
+ //Tag to look for on the instance
+ schedule_tag := "startweekend"
+ tz, err := settz.SetRegion(os.Getenv("TZ"))
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(tz)
+ fmt.Println(time.Now())
+
+ region := os.Getenv("REGION_TZ")
+
+ sess, err := sess.EstablishSession(region)
+ e := ec2Api{
+ Client: ec2.New(sess),
+ }
+
+ instDesc, err := e.instInfo(schedule_tag, region)
+ if err != nil {
+ fmt.Println(err.Error())
+ 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, region, time.Now(), 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
+}
+
+// Go must call main function first, so we call the handler from the main.
+func main() {
+ lambda.Start(HandleLambdaEvent)
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/startWeekEnd/main_test.go b/golang_auto_start_stop_ec2/ec2sched/startWeekEnd/main_test.go
new file mode 100644
index 0000000..ff55158
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/startWeekEnd/main_test.go
@@ -0,0 +1,308 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "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"
+)
+
+// Create a mock structure implementing the ec2iface interface. Makes the
+// call to aws ec2 api
+type MockEC2Client struct {
+ ec2iface.EC2API
+ DescribeInstancesOutputValue ec2.DescribeInstancesOutput
+ StartInstancesOutputValue ec2.StartInstancesOutput
+}
+
+// Implements the interface
+type mockStartInst struct{}
+
+// Implement the mocked DescribeInstances function
+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, region string) (string, error) {
+ return "i-0c938b5e573fb0f26", nil
+}
+
+func TestInstInfo(t *testing.T) {
+ //Becauase we use the filter for included in the DescribeInstances we're testing to see
+ //we get a valid data type back
+
+ //Response from ec2 aws api and the expected result for function instInfo
+ descInstancesWithStartWeekEnd := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Resp ec2.DescribeInstancesOutput
+ Expected ec2.DescribeInstancesOutput
+ }{
+ {
+ Name: "StartWeekEndPresent",
+ Resp: descInstancesWithStartWeekEnd,
+ Expected: descInstancesWithStartWeekEnd,
+ },
+ }
+
+ //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("StartWeekEnd", "us-east-1")
+
+ if err != nil {
+ fmt.Println("Unexpected Error - ", err.Error())
+ }
+
+ assert.Equal(t, "*ec2.DescribeInstancesOutput", fmt.Sprintf("%T", inst))
+
+ })
+ }
+}
+
+func TestStartInst(t *testing.T) {
+ //Do we start the instance? What do we get back from the startInst function
+
+ //The outputs we're mocking for the ec2 aws api for StartInstances
+ var (
+ //Output when starting Instance
+ startInstancesOutput = ec2.StartInstancesOutput{
+ StartingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("stopped"),
+ },
+ },
+ },
+ }
+
+ //Output when instance is stopped
+ startInstancesOutputStopped = ec2.StartInstancesOutput{
+ StartingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("running"),
+ },
+ },
+ },
+ }
+ )
+
+ cases := []struct {
+ Name string
+ Resp ec2.StartInstancesOutput
+ Expected string
+ }{
+ {
+ Name: "ProperInstanceId",
+ Resp: startInstancesOutput,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ {
+ Name: "InstanceRunning",
+ Resp: startInstancesOutputStopped,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ }
+
+ 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,
+ },
+ }
+ startInst, err := e.startInst("i-0c61fb5e573fb0c55", "us-east-1")
+
+ if err != nil {
+ t.Errorf("startInst(\"i-0c61fb5e573fb0c55\", \"us-east-1\") received %s; Expected %s", err.Error(), c.Expected)
+ }
+
+ assert.Equal(t, startInst, c.Expected)
+
+ })
+ }
+}
+
+func TestEvalInst(t *testing.T) {
+ //Figure out what hapens with time, and day of the week, and if the instnace starts
+
+ //Input to be used for eval inst. Perhaps pair it down to just the Instances section
+ goodTime := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ badTime := 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("StartWeekEnd"),
+ Value: aws.String("155540:0"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ //badTag meaning autoscaling group to be found
+ badTag := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ {
+ Key: aws.String("aws:autoscaling:groupName"),
+ Value: aws.String("testing-autoscaling-group"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Expected string
+ Time time.Time
+ Input *ec2.Instance
+ }{
+ {
+ Name: "StartInstance",
+ Expected: "Starting Instance: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 18, 15, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Start time mismatch",
+ Expected: "StartWeekEnd schedule not matched for: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 18, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Weekday not Weekend",
+ Expected: "Current day of week Friday. StartWeekEnd requires non weekday values",
+ Time: time.Date(2000, 11, 17, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Improper tag time format",
+ Expected: "", //Error will throw instead
+ Time: time.Date(2000, 11, 18, 15, 02, 00, 0, time.UTC),
+ Input: badTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Autoscaling group present",
+ Expected: "Skipping - i-0c938b5e573fb0f26 is part of autoscaling group testing-autoscaling-group",
+ Time: time.Date(2000, 11, 18, 15, 02, 00, 0, time.UTC),
+ Input: badTag.Reservations[0].Instances[0],
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ //Put in the rest of the code to pass the mocked Start Inst
+ e := mockStartInst{}
+ result, err := evalInst(c.Input, "us-east-1", c.Time, e)
+ if err != nil {
+ assert.Equal(t, "parsing time \"155540:0\" as \"15:04\": cannot parse \"5540:0\" as \":\"", err.Error())
+ }
+ assert.Equal(t, c.Expected, result)
+ })
+ }
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/stopWeekDay/main.go b/golang_auto_start_stop_ec2/ec2sched/stopWeekDay/main.go
new file mode 100644
index 0000000..b6633fb
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/stopWeekDay/main.go
@@ -0,0 +1,194 @@
+package main
+
+import (
+ "fmt"
+ "go/ec2sched/pkg/sess"
+ "go/ec2sched/pkg/settz"
+ "os"
+ "time"
+
+ "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 stoptInst interface {
+ stopInst(instId string, region string) (string, error)
+}
+
+type instInfo interface {
+ instInfo(tagName string, region string) (*ec2.DescribeInstancesOutput, error)
+}
+
+type ec2Api struct {
+ Client ec2iface.EC2API
+}
+
+func (e ec2Api) instInfo(tagName string, region 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
+}
+
+func (e ec2Api) stopInst(instId string, region string) (string, error) {
+
+ input := &ec2.StopInstancesInput{
+ InstanceIds: []*string{
+ aws.String(instId),
+ },
+ }
+
+ res, si_err := e.Client.StopInstances(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
+ }
+ }
+ //fmt.Println(res)
+ return *res.StoppingInstances[0].InstanceId, nil
+}
+
+func evalInst(inst *ec2.Instance, region string, curTime time.Time, s stoptInst) (string, error) {
+ dayOfWeek := curTime.Weekday()
+ modTime := curTime.Format(("15:04"))
+
+ //stopTime is the desired stop time for the ec2 instance
+ stopTime := ""
+
+ fmt.Println(*inst.InstanceId)
+
+ //Check to see if weekend
+ if int(dayOfWeek) >= 6 && int(dayOfWeek) <= 7 {
+ return fmt.Sprintf("Current day of week %s. StopWeekDay requires non weekend values", dayOfWeek), nil
+ }
+
+ for _, tag := range inst.Tags {
+ if *tag.Key == "StopWeekDay" {
+ //fmt.Println("StopWeekDay tag found. Value: ", *tag.Value)
+ stopTime = *tag.Value
+ }
+ //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
+ }
+ }
+
+ //Point here is to get the times on the same day to compare time of day, not just the date. Ensuring Apples to Apples
+ cur_tod, _ := time.Parse("15:04", modTime)
+ stop_tod, stop_tod_err := time.Parse("15:04", stopTime)
+
+ if stop_tod_err != nil {
+ return "", stop_tod_err
+ }
+
+ cur_minus := cur_tod.Add(-time.Minute * 5)
+ cur_plus := cur_tod.Add(time.Minute * 5)
+
+ if stop_tod.After(cur_minus) && stop_tod.Before(cur_plus) {
+ result, err := s.stopInst(*inst.InstanceId, region)
+ if err != nil {
+ return "", err
+ }
+ return "Stopping Instance: " + result, nil
+ } else {
+ strVal := string(*inst.InstanceId)
+ return "StopWeekDay schedule not matched for: " + strVal, nil
+ }
+}
+
+// Handler doing bulk of work
+// Environment variables set at the lambda configuration level
+func HandleLambdaEvent() {
+
+ //Tag to look for on the instances
+ schedule_tag := "stopweekday"
+ tz, err := settz.SetRegion(os.Getenv("TZ"))
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(tz)
+ fmt.Println(time.Now())
+
+ region := os.Getenv("REGION_TZ")
+
+ sess, err := sess.EstablishSession(region)
+ e := ec2Api{
+ Client: ec2.New(sess),
+ }
+
+ instDesc, err := e.instInfo(schedule_tag, region)
+ if err != nil {
+ fmt.Println(err.Error())
+ 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 != "stopped" {
+ st, err := evalInst(inst, region, time.Now(), e)
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(st)
+
+ } else {
+ fmt.Printf("Instance: %s is already stopped\n", *inst.InstanceId)
+ }
+ }
+ fmt.Println("-----")
+ }
+ fmt.Printf("Instance count evaluated with %s tag: %d", schedule_tag, instCount)
+ return
+}
+
+// Go must call main function first, so we call the handler from the main.
+func main() {
+ lambda.Start(HandleLambdaEvent)
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/stopWeekDay/main_test.go b/golang_auto_start_stop_ec2/ec2sched/stopWeekDay/main_test.go
new file mode 100644
index 0000000..73ccb90
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/stopWeekDay/main_test.go
@@ -0,0 +1,307 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "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"
+)
+
+// Create a mock structure implementing the ec2iface interface. Makes the
+// call to aws ec2 api
+type MockEC2Client struct {
+ ec2iface.EC2API
+ DescribeInstancesOutputValue ec2.DescribeInstancesOutput
+ StopInstancesOutputValue ec2.StopInstancesOutput
+}
+
+// Implements the interface
+type mockStopInst struct{}
+
+// Implement the mocked DescribeInstances function
+func (m *MockEC2Client) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
+ return &m.DescribeInstancesOutputValue, nil
+}
+
+func (m *MockEC2Client) StopInstances(input *ec2.StopInstancesInput) (*ec2.StopInstancesOutput, error) {
+ return &m.StopInstancesOutputValue, nil
+}
+
+func (m mockStopInst) stopInst(instId string, region string) (string, error) {
+ return "i-0c938b5e573fb0f26", nil
+}
+
+func TestInstInfo(t *testing.T) {
+ //Becauase we use the filter for included in the DescribeInstances we're testing to see
+ //we get a valid data type back
+
+ //Response from ec2 aws api and the expected result for function instInfo
+ descInstancesWithStopWeekDay := 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("StartWeekDay"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekDay"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Resp ec2.DescribeInstancesOutput
+ Expected ec2.DescribeInstancesOutput
+ }{
+ {
+ Name: "StopWeekDayPresent",
+ Resp: descInstancesWithStopWeekDay,
+ Expected: descInstancesWithStopWeekDay,
+ },
+ }
+
+ //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("StopWeekDay", "us-east-1")
+
+ if err != nil {
+ fmt.Println("Unexpected Error - ", err.Error())
+ }
+
+ assert.Equal(t, "*ec2.DescribeInstancesOutput", fmt.Sprintf("%T", inst))
+
+ })
+ }
+}
+
+func TestStopInst(t *testing.T) {
+ //Do we stop the instance? What do we get back from the startInst function
+
+ //The outputs we're mocking for the ec2 aws api for StartInstances
+ var (
+ //Output when stopping Instance
+ stopInstancesOutput = ec2.StopInstancesOutput{
+ StoppingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("running"),
+ },
+ },
+ },
+ }
+
+ //Output when instance is already stopped
+ stopInstancesOutputStopped = ec2.StopInstancesOutput{
+ StoppingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("stopped"),
+ },
+ },
+ },
+ }
+ )
+
+ cases := []struct {
+ Name string
+ Resp ec2.StopInstancesOutput
+ Expected string
+ }{
+ {
+ Name: "ProperInstanceId",
+ Resp: stopInstancesOutput,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ {
+ Name: "InstanceRunning",
+ Resp: stopInstancesOutputStopped,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ }
+
+ for _, c := range cases {
+ //Sub test for each case
+ t.Run(c.Name, func(t *testing.T) {
+ e := ec2Api{
+ Client: &MockEC2Client{
+ EC2API: nil,
+ StopInstancesOutputValue: c.Resp,
+ },
+ }
+ stopInst, err := e.stopInst("i-0c61fb5e573fb0c55", "us-east-1")
+
+ if err != nil {
+ t.Errorf("stopInst(\"i-0c61fb5e573fb0c55\", \"us-east-1\") received %s; Expected %s", err.Error(), c.Expected)
+ }
+
+ assert.Equal(t, stopInst, c.Expected)
+
+ })
+ }
+}
+
+func TestEvalInst(t *testing.T) {
+ //Figure out what hapens with time, and day of the week, and if the instnace starts
+
+ //Input to be used for eval inst. Perhaps pair it down to just the Instances section
+ goodTime := 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("StartWeekDay"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekDay"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ badTime := 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("StartWeekDay"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekDay"),
+ Value: aws.String("155540:0"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ badTag := 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("StartWeekDay"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekDay"),
+ Value: aws.String("17:00"),
+ },
+ {
+ Key: aws.String("aws:autoscaling:groupName"),
+ Value: aws.String("testing-autoscaling-group"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Expected string
+ Time time.Time
+ Input *ec2.Instance
+ }{
+ {
+ Name: "StopInstance",
+ Expected: "Stopping Instance: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 17, 17, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Stop time mismatch",
+ Expected: "StopWeekDay schedule not matched for: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 17, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Weekend not Weekday",
+ Expected: "Current day of week Saturday. StopWeekDay requires non weekend values",
+ Time: time.Date(2000, 11, 18, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Improper tag time format",
+ Expected: "", //Error will throw instead
+ Time: time.Date(2000, 11, 17, 15, 02, 00, 0, time.UTC),
+ Input: badTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Autoscaling group present",
+ Expected: "Skipping - i-0c938b5e573fb0f26 is part of autoscaling group testing-autoscaling-group",
+ Time: time.Date(2000, 11, 17, 17, 02, 00, 0, time.UTC),
+ Input: badTag.Reservations[0].Instances[0],
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ //Put in the rest of the code to pass the mocked Start Inst
+ e := mockStopInst{}
+ result, err := evalInst(c.Input, "us-east-1", c.Time, e)
+ if err != nil {
+ assert.Equal(t, "parsing time \"155540:0\" as \"15:04\": cannot parse \"5540:0\" as \":\"", err.Error())
+ }
+ assert.Equal(t, c.Expected, result)
+ })
+ }
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/stopWeekEnd/main.go b/golang_auto_start_stop_ec2/ec2sched/stopWeekEnd/main.go
new file mode 100644
index 0000000..61cc14a
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/stopWeekEnd/main.go
@@ -0,0 +1,195 @@
+package main
+
+import (
+ "fmt"
+ "go/ec2sched/pkg/sess"
+ "go/ec2sched/pkg/settz"
+ "os"
+ "time"
+
+ "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 stoptInst interface {
+ stopInst(instId string, region string) (string, error)
+}
+
+type instInfo interface {
+ instInfo(tagName string, region string) (*ec2.DescribeInstancesOutput, error)
+}
+
+type ec2Api struct {
+ Client ec2iface.EC2API
+}
+
+func (e ec2Api) instInfo(tagName string, region 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
+}
+
+func (e ec2Api) stopInst(instId string, region string) (string, error) {
+
+ input := &ec2.StopInstancesInput{
+ InstanceIds: []*string{
+ aws.String(instId),
+ },
+ }
+
+ res, si_err := e.Client.StopInstances(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
+ }
+ }
+ //fmt.Println(res)
+ return *res.StoppingInstances[0].InstanceId, nil
+}
+
+func evalInst(inst *ec2.Instance, region string, curTime time.Time, s stoptInst) (string, error) {
+ dayOfWeek := curTime.Weekday()
+ modTime := curTime.Format(("15:04"))
+
+ //stopTime is the desired stop time for the ec2 instance
+ stopTime := ""
+
+ fmt.Println(*inst.InstanceId)
+
+ //Check to see if weekend
+ if int(dayOfWeek) < 6 || int(dayOfWeek) > 7 {
+ return fmt.Sprintf("Current day of week %s. StopWeekEnd requires non weekday values", dayOfWeek), nil
+ }
+
+ for _, tag := range inst.Tags {
+ if *tag.Key == "StopWeekEnd" {
+ //fmt.Println("StopWeekEnd tag found. Value: ", *tag.Value)
+ stopTime = *tag.Value
+ }
+ //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
+ }
+ }
+
+ //Point here is to get the times on the same day to compare time of day, not just the date. Ensuring Apples to Apples
+ cur_tod, _ := time.Parse("15:04", modTime)
+ stop_tod, stop_tod_err := time.Parse("15:04", stopTime)
+
+ if stop_tod_err != nil {
+ return "", stop_tod_err
+ }
+
+ cur_minus := cur_tod.Add(-time.Minute * 5)
+ cur_plus := cur_tod.Add(time.Minute * 5)
+
+ if stop_tod.After(cur_minus) && stop_tod.Before(cur_plus) {
+ result, err := s.stopInst(*inst.InstanceId, region)
+ if err != nil {
+ return "", err
+ }
+ return "Stopping Instance: " + result, nil
+ } else {
+ strVal := string(*inst.InstanceId)
+ return "StopWeekEnd schedule not matched for: " + strVal, nil
+ }
+}
+
+// Handler doing bulk of work
+// Environment variables set at the lambda configuration level
+func HandleLambdaEvent() {
+
+ //Tag to look for on the instances
+ schedule_tag := "stopweekend"
+ tz, err := settz.SetRegion(os.Getenv("TZ"))
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(tz)
+ fmt.Println(time.Now())
+
+ region := os.Getenv("REIGON_TZ")
+
+ sess, err := sess.EstablishSession(region)
+ e := ec2Api{
+ Client: ec2.New(sess),
+ }
+
+ instDesc, err := e.instInfo(schedule_tag, region)
+ if err != nil {
+ fmt.Println(err.Error())
+ 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 != "stopped" {
+ st, err := evalInst(inst, region, time.Now(), e)
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ fmt.Println(st)
+
+ } else {
+ fmt.Printf("Instance: %s is already stopped\n", *inst.InstanceId)
+ }
+ }
+ fmt.Println("-----")
+ }
+ fmt.Printf("Instance count evaluated with %s tag: %d", schedule_tag, instCount)
+ return
+}
+
+// Go must call main function first, so we call the handler from the main.
+func main() {
+ lambda.Start(HandleLambdaEvent)
+}
diff --git a/golang_auto_start_stop_ec2/ec2sched/stopWeekEnd/main_test.go b/golang_auto_start_stop_ec2/ec2sched/stopWeekEnd/main_test.go
new file mode 100644
index 0000000..e8b88f4
--- /dev/null
+++ b/golang_auto_start_stop_ec2/ec2sched/stopWeekEnd/main_test.go
@@ -0,0 +1,307 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "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"
+)
+
+// Create a mock structure implementing the ec2iface interface. Makes the
+// call to aws ec2 api
+type MockEC2Client struct {
+ ec2iface.EC2API
+ DescribeInstancesOutputValue ec2.DescribeInstancesOutput
+ StopInstancesOutputValue ec2.StopInstancesOutput
+}
+
+// Implements the interface
+type mockStopInst struct{}
+
+// Implement the mocked DescribeInstances function
+func (m *MockEC2Client) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
+ return &m.DescribeInstancesOutputValue, nil
+}
+
+func (m *MockEC2Client) StopInstances(input *ec2.StopInstancesInput) (*ec2.StopInstancesOutput, error) {
+ return &m.StopInstancesOutputValue, nil
+}
+
+func (m mockStopInst) stopInst(instId string, region string) (string, error) {
+ return "i-0c938b5e573fb0f26", nil
+}
+
+func TestInstInfo(t *testing.T) {
+ //Becauase we use the filter for included in the DescribeInstances we're testing to see
+ //we get a valid data type back
+
+ //Response from ec2 aws api and the expected result for function instInfo
+ descInstancesWithStopWeekEnd := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Resp ec2.DescribeInstancesOutput
+ Expected ec2.DescribeInstancesOutput
+ }{
+ {
+ Name: "StopWeekEndPresent",
+ Resp: descInstancesWithStopWeekEnd,
+ Expected: descInstancesWithStopWeekEnd,
+ },
+ }
+
+ //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("StopWeekEnd", "us-east-1")
+
+ if err != nil {
+ fmt.Println("Unexpected Error - ", err.Error())
+ }
+
+ assert.Equal(t, "*ec2.DescribeInstancesOutput", fmt.Sprintf("%T", inst))
+
+ })
+ }
+}
+
+func TestStopInst(t *testing.T) {
+ //Do we stop the instance? What do we get back from the startInst function
+
+ //The outputs we're mocking for the ec2 aws api for StartInstances
+ var (
+ //Output when stopping Instance
+ stopInstancesOutput = ec2.StopInstancesOutput{
+ StoppingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("running"),
+ },
+ },
+ },
+ }
+
+ //Output when instance is already stopped
+ stopInstancesOutputStopped = ec2.StopInstancesOutput{
+ StoppingInstances: []*ec2.InstanceStateChange{
+ {
+ InstanceId: aws.String("i-0c61fb5e573fb0c55"),
+ PreviousState: &ec2.InstanceState{
+ Name: aws.String("stopped"),
+ },
+ },
+ },
+ }
+ )
+
+ cases := []struct {
+ Name string
+ Resp ec2.StopInstancesOutput
+ Expected string
+ }{
+ {
+ Name: "ProperInstanceId",
+ Resp: stopInstancesOutput,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ {
+ Name: "InstanceRunning",
+ Resp: stopInstancesOutputStopped,
+ Expected: "i-0c61fb5e573fb0c55",
+ },
+ }
+
+ for _, c := range cases {
+ //Sub test for each case
+ t.Run(c.Name, func(t *testing.T) {
+ e := ec2Api{
+ Client: &MockEC2Client{
+ EC2API: nil,
+ StopInstancesOutputValue: c.Resp,
+ },
+ }
+ stopInst, err := e.stopInst("i-0c61fb5e573fb0c55", "us-east-1")
+
+ if err != nil {
+ t.Errorf("stopInst(\"i-0c61fb5e573fb0c55\", \"us-east-1\") received %s; Expected %s", err.Error(), c.Expected)
+ }
+
+ assert.Equal(t, stopInst, c.Expected)
+
+ })
+ }
+}
+
+func TestEvalInst(t *testing.T) {
+ //Figure out what hapens with time, and day of the week, and if the instnace starts
+
+ //Input to be used for eval inst. Perhaps pair it down to just the Instances section
+ goodTime := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ badTime := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("155540:0"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ badTag := 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("StartWeekEnd"),
+ Value: aws.String("15:00"),
+ },
+ {
+ Key: aws.String("StopWeekEnd"),
+ Value: aws.String("17:00"),
+ },
+ {
+ Key: aws.String("aws:autoscaling:groupName"),
+ Value: aws.String("testing-autoscaling-group"),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cases := []struct {
+ Name string
+ Expected string
+ Time time.Time
+ Input *ec2.Instance
+ }{
+ {
+ Name: "StopInstance",
+ Expected: "Stopping Instance: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 18, 17, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Stop time mismatch",
+ Expected: "StopWeekEnd schedule not matched for: i-0c938b5e573fb0f26",
+ Time: time.Date(2000, 11, 18, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Weekday not Weekend",
+ Expected: "Current day of week Friday. StopWeekEnd requires non weekday values",
+ Time: time.Date(2000, 11, 17, 22, 02, 00, 0, time.UTC),
+ Input: goodTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Improper tag time format",
+ Expected: "", //Error will throw instead
+ Time: time.Date(2000, 11, 18, 15, 02, 00, 0, time.UTC),
+ Input: badTime.Reservations[0].Instances[0],
+ },
+ {
+ Name: "Autoscaling group present",
+ Expected: "Skipping - i-0c938b5e573fb0f26 is part of autoscaling group testing-autoscaling-group",
+ Time: time.Date(2000, 11, 18, 17, 02, 00, 0, time.UTC),
+ Input: badTag.Reservations[0].Instances[0],
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Name, func(t *testing.T) {
+ //Put in the rest of the code to pass the mocked Start Inst
+ e := mockStopInst{}
+ result, err := evalInst(c.Input, "us-east-1", c.Time, e)
+ if err != nil {
+ assert.Equal(t, "parsing time \"155540:0\" as \"15:04\": cannot parse \"5540:0\" as \":\"", err.Error())
+ }
+ assert.Equal(t, c.Expected, result)
+ })
+ }
+}