Created
December 3, 2019 03:53
-
-
Save bearmini/99e99ead08ed0e8aff39c6379f5aeeae to your computer and use it in GitHub Desktop.
AWS Lambda code for SORACOM LTE-M Button powered by AWS - Start/stop instances
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"strings" | |
"fmt" | |
"os" | |
"github.com/aws/aws-lambda-go/lambda" | |
"github.com/aws/aws-sdk-go/aws" | |
"github.com/aws/aws-sdk-go/aws/session" | |
"github.com/aws/aws-sdk-go/service/ec2" | |
"github.com/pkg/errors" | |
"github.com/soracom/common-utils-go/slack" | |
) | |
type ButtonEvent struct { | |
DeviceEvent DeviceEvent `json:"deviceEvent"` | |
DeviceInfo DeviceInfo `json:"deviceInfo"` | |
PlacementInfo PlacementInfo `json:"placementInfo"` | |
} | |
type DeviceEvent struct { | |
ButtonClicked ButtonClicked `json:"buttonClicked"` | |
} | |
type ButtonClicked struct { | |
ClickType string `json:"clickType"` | |
ReportedTime string `json:"reportedTime"` | |
} | |
type DeviceInfo struct { | |
Attributes DeviceAttributes `json:"attributes"` | |
DeviceID string `json:"deviceId"` | |
RemainingLife float32 `json:"remainingLife"` | |
Type string `json:"type"` | |
} | |
type DeviceAttributes struct { | |
DeviceTemplateName string `json:"deviceTemplateName"` | |
PlacementName string `json:"placementName"` | |
ProjectName string `json:"projectName"` | |
ProjectRegion string `json:"projectRegion"` | |
} | |
type PlacementInfo struct { | |
Attributes map[string]string `json:"attributes"` | |
Devices map[string]string `json:"devices"` | |
PlacementName string `json:"placementName"` | |
ProjectName string `json:"projectName"` | |
} | |
func main() { | |
log("starting process") | |
lambda.Start(handleFunc) | |
log("exiting process ...") | |
} | |
//func handleFunc(ibe *events.IoTButtonEvent) error { | |
func handleFunc(be ButtonEvent) error { | |
log("event: %+v", toJSON(be)) | |
var err error | |
switch strings.ToLower(be.DeviceEvent.ButtonClicked.ClickType) { | |
case "single": | |
err = onSingleClick(be) | |
case "double": | |
err = onDoubleClick(be) | |
case "long": | |
err = onLongClick(be) | |
default: | |
err = errors.Errorf("unknown click type: %s", be.DeviceEvent.ButtonClicked.ClickType) | |
} | |
if err != nil { | |
return reportError(be, err) | |
} | |
return nil | |
} | |
func onSingleClick(be ButtonEvent) error { | |
state, err := getInstanceState(be) | |
if err != nil { | |
return err | |
} | |
return reportInstanceStatus(be, state) | |
} | |
func onDoubleClick(be ButtonEvent) error { | |
state, err := startInstance(be) | |
if err != nil { | |
return err | |
} | |
return reportInstanceStatus(be, state) | |
} | |
func onLongClick(be ButtonEvent) error { | |
state, err := stopInstance(be) | |
if err != nil { | |
return err | |
} | |
return reportInstanceStatus(be, state) | |
} | |
func startInstance(be ButtonEvent) (InstanceState, error) { | |
instanceID := be.PlacementInfo.Attributes["instance-id"] | |
region := be.PlacementInfo.Attributes["instance-region"] | |
rc := ec2.New(session.New(&aws.Config{ | |
Region: aws.String(region), | |
})) | |
in := &ec2.StartInstancesInput{ | |
InstanceIds: []*string{aws.String(instanceID)}, | |
} | |
out, err := rc.StartInstances(in) | |
if err != nil { | |
return 0, err | |
} | |
for _, si := range out.StartingInstances { | |
if *si.InstanceId == instanceID { | |
return InstanceState(*si.CurrentState.Code), nil | |
} | |
} | |
return 0, errors.New("starting instance not found") | |
} | |
func stopInstance(be ButtonEvent) (InstanceState, error) { | |
instanceID := be.PlacementInfo.Attributes["instance-id"] | |
region := be.PlacementInfo.Attributes["instance-region"] | |
rc := ec2.New(session.New(&aws.Config{ | |
Region: aws.String(region), | |
})) | |
in := &ec2.StopInstancesInput{ | |
InstanceIds: []*string{aws.String(instanceID)}, | |
} | |
out, err := rc.StopInstances(in) | |
if err != nil { | |
return 0, err | |
} | |
for _, si := range out.StoppingInstances { | |
if *si.InstanceId == instanceID { | |
return InstanceState(*si.CurrentState.Code), nil | |
} | |
} | |
return 0, errors.New("stopping instance not found") | |
} | |
func reportInstanceStatus(be ButtonEvent, state InstanceState) error { | |
url := be.PlacementInfo.Attributes["slack-webhook-url"] | |
return slack.PostTo(url, slack.Message{ | |
Attachments: []slack.Attachment{ | |
{ | |
Color: "good", | |
Fields: []slack.AttachmentField{ | |
{ | |
Title: "Current state", | |
Value: fmt.Sprintf("%s", instanceStateToString(state)), | |
}, | |
}, | |
}, | |
}, | |
}) | |
} | |
type InstanceState uint8 | |
const ( | |
InstanceStatePending InstanceState = 0 | |
InstanceStateRunning = 16 | |
InstanceStateShuttingDown = 32 | |
InstanceStateTerminated = 48 | |
InstanceStateStopping = 64 | |
InstanceStateStopped = 80 | |
) | |
func instanceStateToString(state InstanceState) string { | |
switch state { | |
case InstanceStatePending: return "pending" | |
case InstanceStateRunning: return "running" | |
case InstanceStateShuttingDown: return "shutting down" | |
case InstanceStateTerminated: return "terminated" | |
case InstanceStateStopping: return "stopping" | |
case InstanceStateStopped: return "stopped" | |
default: return "(unknown)" | |
} | |
} | |
func getInstanceState(be ButtonEvent) (InstanceState, error) { | |
instanceID := be.PlacementInfo.Attributes["instance-id"] | |
region := be.PlacementInfo.Attributes["instance-region"] | |
rc := ec2.New(session.New(&aws.Config{ | |
Region: aws.String(region), | |
})) | |
in := &ec2.DescribeInstanceStatusInput{ | |
IncludeAllInstances: aws.Bool(true), | |
InstanceIds: []*string{aws.String(instanceID)}, | |
} | |
out, err := rc.DescribeInstanceStatus(in) | |
if err != nil { | |
log("ERROR: ec2.DescribeInstanceStatus() - %+v", err) | |
return 0, err | |
} | |
for _, is := range out.InstanceStatuses { | |
if *is.InstanceId == instanceID { | |
return InstanceState(uint8(*is.InstanceState.Code)), nil | |
} | |
} | |
return 0, errors.New("unable to get instance state") | |
} | |
func reportError(be ButtonEvent, err error) error { | |
url := be.PlacementInfo.Attributes["slack-webhook-url"] | |
return slack.PostTo(url, slack.Message{ | |
Attachments: []slack.Attachment{ | |
{ | |
Color: "danger", | |
Fields: []slack.AttachmentField{ | |
{ | |
Title: "Error", | |
Value: fmt.Sprintf("%+v", err), | |
}, | |
}, | |
}, | |
}, | |
}) | |
} | |
func toJSON(x interface{}) string { | |
b, err := json.Marshal(x) | |
if err != nil { | |
return fmt.Sprintf("error: %+v", err) | |
} | |
return string(b) | |
} | |
func log(format string, v ...interface{}) { | |
fmt.Fprintf(os.Stderr, format+"\n", v...) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment