Forward on JSON received from SNS to Lambda - GoLang - amazon-web-services

I am trying to achieve the following:
Cloudwatch alarm details are received as JSON to a Lambda
The Lambda looks at the JSON to determine if the 'NewStateValue' == "ALARM"
If it does == "ALARM" forward the whole JSON received from the SNS out via another SNS.
I am most of the way towards achieving this and I have the following code:
package main
import (
"context"
"fmt"
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"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/sns"
)
func handler(ctx context.Context, snsEvent events.SNSEvent) {
for _, record := range snsEvent.Records {
snsRecord := record.SNS
//To add in additional fields to publish to the logs add "snsRecord.'fieldname'"
fmt.Printf("Message = %s \n", snsRecord.Message)
var event CloudWatchAlarm
err := json.Unmarshal([]byte(snsRecord.Message), &event)
if err != nil {
fmt.Println("There is an error: " + err.Error())
}
fmt.Printf("Test Message = %s \n", event.NewStateValue)
if ( event.NewStateValue == "ALARM") {
svc := sns.New(session.New())
// params will be sent to the publish call included here is the bare minimum params to send a message.
params := &sns.PublishInput{
Message: Message: aws.String("message"), // This is the message itself (can be XML / JSON / Text - anything you want)
TopicArn: aws.String("my arn"), //Get this from the Topic in the AWS console.
}
resp, err := svc.Publish(params) //Call to puclish the message
if err != nil { //Check for errors
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
}
}
func main() {
lambda.Start(handler)
}
Currently this sends an email to the address set-up in the SNS linked to the ARN above. However I would like the email to include the full, ideally formatted, JSON received by the first SNS. I have the Cloudwatch JSON structure defined in another file, this is being called by var event CloudWatchAlarm

From AWS SDK for Go docs:
type PublishInput struct {
// The message you want to send.
//
// If you are publishing to a topic and you want to send the same message to
// all transport protocols, include the text of the message as a String value.
// If you want to send different messages for each transport protocol, set the
// value of the MessageStructure parameter to json and use a JSON object for
// the Message parameter.
So, params would be:
params := &sns.PublishInput{
Message: myjson, // json data you want to send
TopicArn: aws.String("my arn"),
MessageStructure: aws.String("json")
}
WARNING
Notice the help paragraph says "use a JSON object for the parameter message", such JSON object must have a keys that correspond to supported transport protocol That means
{
'default': json_message,
'email': json_message
}
It will send json_message using both the default and email transports.

Related

I want to send verification code SMS by using golang gin-gonic server

In my product, I have to verify the user's phone number. And to do that, I want to send verification code by using SMS
But, I can't find references to send verification code by using golang
this is my code
package main
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sns"
)
func main() {
fmt.Println("creating session")
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String("ap-northeast-1"),
}))
fmt.Println("session created")
svc := sns.New(sess)
fmt.Println("service created")
params := &sns.PublishInput{
Message: aws.String("testing 123"),
PhoneNumber: aws.String(<"myPhoneNumber">),
}
resp, err := svc.Publish(params)
if err != nil {
// Print the error, cast err to awserr.Error to get the Code and
// Message from an error.
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println(resp)
}
and i get this response
creating session
session created
service created
{
MessageId: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX"
}
But Message didn't arrived to me.
Also, i don't know how to check the result by using MessageId in response
Can I get some references for this? or am I need to know about other features first?
Please help me 🙏
I just searched many words like "aws SMS using golang", "aws verification code SMS golang"
But It didn't work for me
============================================================
This is my second try, by using JS! But it also didn't work🥹
let { SNSClient } = require("#aws-sdk/client-sns");
let { PublishCommand } = require("#aws-sdk/client-sns");
// Set the AWS Region.
const REGION = "ap-northeast-1"; //e.g. "us-east-1"
// Create SNS service object.
const snsClient = new SNSClient({ region: REGION });
// Set the parameters
const params = {
Message: "test please" /* required */,
PhoneNumber: "+821012345678", //PHONE_NUMBER, in the E.164 phone number structure
};
const run = async () => {
try {
const data = await snsClient.send(new PublishCommand(params));
console.log("Success.", data);
return data; // For unit tests.
} catch (err) {
console.log("Error", err.stack);
}
};
run();
And this is JS's response
Success. {
'$metadata': {
httpStatusCode: 200,
requestId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
MessageId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
SequenceNumber: undefined
}
Please check phone number first. You code seems correct.
AWS SNS SMS follows E.164 format.
The phone number that receives the message. This must be in E.164 format. For example, a United States phone number might be +12065550101
you can read more here
From above link:
When you send an SMS message, specify the phone number using the E.164 format. E.164 is a standard for the phone number structure used for international telecommunication. Phone numbers that follow this format can have a maximum of 15 digits, and they are prefixed with the plus character (+) and the country code. For example, a U.S. phone number in E.164 format would appear as +1XXX5550100.

Amazon S3 cannot call API because of no such host error

I am attempting to build an Amazon S3 client in GoLang but I am having trouble making API calls. I'm receiving an error that says "no such host" but I am positive the credentials I'm providing are correct.
Defining a struct to hold the client
// the Client struct holding the client itself as well as the bucket.
type S3Client struct {
S3clientObject s3.S3
bucket string
}
// Initialize the client
func CreateS3Client() S3Client{
S3clientCreate := S3Client{S3clientObject: Connect(), bucket: GetS3Bucket()}
if (!CheckBuckets(S3clientCreate)) {
exitErrorf("Bucket does not exist, try again.")
}
return S3clientCreate
}
Connecting to the bucket
func Connect() s3.S3{
// Initialize a session
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials("myCredentials", "myCreds", ""),
Endpoint: aws.String("myDomain"),
Region: aws.String("myRegion"),
},
)
if err != nil {
exitErrorf("Unable to use credentials, %v", err)
}
// Create S3 service client
svc := s3.New(sess)
return *svc
}
At this point, I am able to establish a connection and use the ListBuckets functionality to receive a list of all the buckets (like this: https://docs.aws.amazon.com/sdk-for-go/api/service/s3/#S3.ListBuckets)
When I try to call the GetObject API, it tells me it cannot find the host
// Gets an object from the bucket
func Get(client S3Client, key string) interface{} {
// golang does not support "default values" so I used a nil (same as null)
if (key == "") {
return nil
}
svc := client.S3clientObject
input := &s3.GetObjectInput{
Bucket: aws.String("myBucket"),
Key: aws.String("myPathKey"),
}
result, err := svc.GetObject(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case s3.ErrCodeNoSuchKey:
fmt.Println(s3.ErrCodeNoSuchKey, aerr.Error())
case s3.ErrCodeInvalidObjectState:
fmt.Println(s3.ErrCodeInvalidObjectState, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
fmt.Println(err.Error())
}
}
return result
}
This returns:
dial tcp: lookup "hostname": no such host
I cannot figure out why this is happening, because I am able to successfully make a connection to the bucket, and list them out using ListBuckets, but when using another API call, it fails to find the host. Is there something wrong with my code? Is there another configuration that I forgot about?
Any help or guidance is greatly appreciated as I'm somewhat new to using GoLang and S3.
Apparently the issue was with the bucket name. All I did to resolve this was put a "/" in front of the bucket name when creating it and it worked.

How to pass AWS Gateway parameter to AWS lambda function in Go

I deployed REST API into AWS lambda function. I am trying to use API Gateway GET method with lambda function to get a single value from MySQL database. I want to pass id in the URL. Here is my handler function
package main
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type User struct{
Id int `json:"id"`
Name string `json:"name"`
}
type Ids struct{
Id int `json:"id"`
}
//func handler(id Ids) (*User, error)
//id1 := id.Id
func handler( id events.APIGatewayProxyRequest) (*User, error) {
id1 := id.Body
user := &User{
Id: id1,
Name : "abc"
}
return user, nil
}
func main(){
lambda.Start(handler)
}
I am getting result while I create test event in lambda function with the test for handler(events.APIGatewayProxyRequest)
{
"body":123
}
or for handler(id Ids)
{
"id":123
}
When I use API Gateway to create GET method for the above lambda function, I am getting "internal server error" as it is giving null value to read database. I use the URL https://xxxx.xxxxx.amazonaws.com/test/user/123, but it is not reading the value of id. What am I doing wrong here? Should I need to pass the JSON for the handler function? If yes, how to pass JSON in the URL? or is there any other method where I can pass the value to handler?

AWS Golang CreateSecret() ResourceExistsException on new unique key name

Not sure what is going on, this code worked once yesterday. Now no matter what value I use, AWS is returning a error that it already exists, but that's impossible.
2020/04/17 19:10:30 error ResourceExistsException: The operation failed because the secret /gog1/RandomSiteName3 already exists.
_, err = PutParam("/gog1/RandomSiteName3", "test", true, EventGuid)
if err != nil {
log.Printf("error writing secret: %v ", err)
return
}
func PutParam(paramName string, paramValue string, encrypt bool, guid string) (output string, err error) {
svc := secretsmanager.New(AWSSession)
input := &secretsmanager.CreateSecretInput{
// ClientRequestToken: aws.String(guid),
// Description: aws.String("My test database secret created with the CLI"),
Name: aws.String(paramName),
SecretString: aws.String(paramValue),
}
fmt.Printf("putting secret key: %v", paramName)
_, err = svc.CreateSecret(input)
if err != nil {
return "", err
}
return
}
It was due to an s3 trigger firing in a loop:
NOTE: If writing to the bucket that triggers the notification, this
could cause an execution loop. For example, if the bucket triggers a
Lambda function each time an object is uploaded, and the function
uploads an object to the bucket, then the function indirectly triggers
itself. To avoid this, use two buckets, or configure the trigger to
only apply to a prefix used for incoming objects.

How to support more than one trigger in AWS Lambda in Golang?

I am building an AWS Lambda function in Golang that copy the content from n to m S3 buckets. There is a requirement to support for S3 trigger as well as fetching the data from an SQS where all source S3 bucket change is stored. The code can be found here: https://github.com/maknahar/s3copy
I tried following:
func main() {
lambda.Start(ProcessIncomingS3Events)
lambda.Start(ProcessIncomingEvents)
}
func ProcessIncomingS3Events(event events.S3Event) error {
...
log.Println("Got S3 Event")
return processS3Trigger(config, event)
}
func ProcessIncomingEvents() error {
...
log.Println("Defaulting to SQS")
return processSQSMessage(config)
}
In this case, the first event ProcessIncomingS3Events is triggered every time.
I tried following as well
func main() {
lambda.Start(ProcessIncomingEvents)
}
func ProcessIncomingEvents(event interface{}) error {
...
switch request := event.(type) {
case events.S3Event:
log.Println("Got S3 Event")
return processS3Trigger(config, request)
case types.Nil:
log.Println("Defaulting to SQS")
return processSQSMessage(config)
default:
log.Println("Could not find the event type")
}
return nil
}
In this case, Lambda could not detect the type and Could not find the event type is logged in every trigger.
Is there a way to support multiple triggers via AWS SDK at all for the function?
I achieved to listen to multiple events by implementing the aws Handler interface, it defines one method
Invoke(ctx context.Context, payload []byte) ([]byte, error)
I implemented a multievent Handler as follows
type Handler struct {
//add global variables or context information that your handler may need
}
func (h Handler) Invoke(ctx context.Context, data []byte) ([]byte, error) {
//for demonstration purposes, not the best way to handle
apiGatewayEvent := new(events.APIGatewayProxyRequest)
if err := json.Unmarshal(data, apiGatewayEvent); err != nil {
log.Println("Not a api gateway event")
}
snsEvent := new(events.SNSEvent)
if err := json.Unmarshal(data, snsEvent); err != nil {
log.Println("Not a sns event")
}
return nil, nil
}
func main() {
lambda.StartHandler(Handler{})
}
As you can see, you could get the raw bytes of any event and handle them as you need giving you the possibility to listen to any aws event with the same lambda. However, think carefully before using this aproach, because, as noted above, lambdas are best used handling just one type of event
Hope this helps.
You can configure multiple event sources to trigger one or more Lambda functions.
However, in Go the lambda.Start call is blocking so it's not very easy to write a single function that handles multiple event types. You are strongly encouraged to make a separate Lambda function for every event source.
The idiomatic Go solution is to have your function logic defined once in the main package, and write multiple programs that take the source event and call your function. So the project layout would be:
s3copy/main.go
s3copy/handlers/s3/main.go
s3copy/handlers/sqs/main.go
See my boilerplate Lambda and Go app for an example project layout and Makefile.
I'm not a GoLang guy. Just guessing on normal programming thinking.
In approach one, you are directly calling ProcessIncomingS3Events in first statement, so every time this is called.
Read this - Lambda Function Handler (Go)
In above link, the author is parsing event's name field. Similarly, you can check for any field which is always present in S3 event e.g. "eventSource":"aws:s3" (S3 event structure see here)
If present then S3 event else other. Or you can also check for SQS event's field.
HIH
You can use embedding in Go to solve this
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"reflect"
)
type Event struct {
events.SQSEvent
events.APIGatewayProxyRequest
//other event type
}
type Response struct {
events.SQSEventResponse `json:",omitempty"`
events.APIGatewayProxyResponse `json:",omitempty"`
//other response type
}
func main() {
lambda.Start(eventRouter)
}
func eventRouter(event Event) (Response, error) {
var response Response
switch {
case reflect.DeepEqual(event.APIGatewayProxyRequest, events.APIGatewayProxyRequest{}):
response.SQSEventResponse = sqsEventHandler(event.SQSEvent)
case reflect.DeepEqual(event.SQSEvent, events.SQSEvent{}):
response.APIGatewayProxyResponse = apiGatewayEventHandler(event.APIGatewayProxyRequest)
//another case for a event handler
}
return response, nil
}
func sqsEventHandler(sqsEvent events.SQSEvent) events.SQSEventResponse {
//do something with the SQS event
}
func apiGatewayEventHandler(apiEvent events.APIGatewayProxyRequest) events.APIGatewayProxyResponse {
//do something with the API Gateway event
}
Note: if the basic events have some same field names, you will need to look for another the compare method instance of DeepEqual.
Note2: sorry for my english, greetings from Argentina