Learning About Lambda

Amazon Web Service’s serverless compute has always fascinated me. A lot of my pursuits of learning the DevOps methodology involves scripting but not full blown programming. When it comes to automation I can accomplish my goals with scripting. Now I just found out that you can run PowerShell in AWS Lambda but I think that is pretty recent. So before I knew that I decided I wanted to re-engage my brain centers for learning a programming language. I see a lot of hype and support for Google’s Go language. I started going through the tutorials and creating things. An idea came to me for a creation which I will talk about in a later post. For now I wanted to share my understanding of Lambda.

Lamba uses requests and events. The request contains the data it needs to function in kind of a HTTP post, at least that’s what it reminds me of since you can call it through a HTTP request and the Lambda library pulls out headers and the body. Then it returns in an HTTP fashion as well. Here’s a sample piece of code that leverages AWS Simple Email Service to take a name out of a HTTP body.

package main

import (
	"errors"
	"fmt"
	"log"

	"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/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ses"
)

const (
	// Replace sender@example.com with your "From" address.
	// This address must be verified with Amazon SES.
	Sender = "lambda@serverhobbyist.net"

	// Replace recipient@example.com with a "To" address. If your account
	// is still in the sandbox, this address must be verified.
	Recipient    = "Recipient@Recipient.com"

	// Specify a configuration set. To use a configuration
	// set, comment the next line and line 92.
	//ConfigurationSet = "ConfigSet"

	// The subject line for the email.
	Subject = "Lambda Function Go!"

	// The character encoding for the email.
	CharSet = "UTF-8"
)

var (
	// ErrNameNotProvided is thrown when a name is not provided
	ErrNameNotProvided = errors.New("no name was provided in the HTTP body")
)

// Handler is your Lambda function handler
// It uses Amazon API Gateway request/responses provided by the aws-lambda-go/events package,
// However you could use other event sources (S3, Kinesis etc), or JSON-decoded primitive types such as 'string'.
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	var TextBody = "Hello " + request.Body
	var HtmlBody = "Hello " + request.Body
	sess, err := session.NewSession(&aws.Config{
		Region:      aws.String("us-east-1"),
		Credentials: credentials.NewStaticCredentials("Removed", "Removed", ""),
	})

	// Create an SES session.
	svc := ses.New(sess)

	// Assemble the email.
	input := &ses.SendEmailInput{
		Destination: &ses.Destination{
			CcAddresses: []*string{},
			ToAddresses: []*string{
				aws.String(Recipient),
			},
			BccAddresses: []*string{
				aws.String(BCCRecipient),
			},
		},
		Message: &ses.Message{
			Body: &ses.Body{
				Html: &ses.Content{
					Charset: aws.String(CharSet),
					Data:    aws.String(HtmlBody),
				},
				Text: &ses.Content{
					Charset: aws.String(CharSet),
					Data:    aws.String(TextBody),
				},
			},
			Subject: &ses.Content{
				Charset: aws.String(CharSet),
				Data:    aws.String(Subject),
			},
		},
		Source: aws.String(Sender),
		// Uncomment to use a configuration set
		//ConfigurationSetName: aws.String(ConfigurationSet),
	}

	// Attempt to send the email.
	result, err := svc.SendEmail(input)
	log.Println(result)

	// Display error messages if they occur.
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok {
			switch aerr.Code() {
			case ses.ErrCodeMessageRejected:
				fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
			case ses.ErrCodeMailFromDomainNotVerifiedException:
				fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
			case ses.ErrCodeConfigurationSetDoesNotExistException:
				fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
			default:
				fmt.Println(aerr.Error())
			}
		} else {
			// Print the error, cast err to awserr.Error to get the Code and
			// Message from an error.
			fmt.Println(err.Error())
		}

	}

	// stdout and stderr are sent to AWS CloudWatch Logs
	log.Printf("Processing Lambda request %s\n", request.RequestContext.RequestID)

	// If no name is provided in the HTTP request body, throw an error
	if len(request.Body) < 1 {
		return events.APIGatewayProxyResponse{}, ErrNameNotProvided
	}
	log.Println(request.PathParameters)
	return events.APIGatewayProxyResponse{
		Body:       "Hello " + request.Body,
		StatusCode: 200,
	}, nil

}

func main() {
	lambda.Start(Handler)
}

Monitoring App

I recently started trying to create a setup that can monitor CPU and RAM usage on a remote server. After quite a bit of thinking I created a SQL database and shell script that saved outputted data into the DB. This wasn’t ideal for remote servers so I created a poor man’s API with Node & Express. It takes a web call from remote systems running a shell script on a schedule. It then saves it to a table and I’m using grafana to visualize the data. Its in a super early state now with no validation but it is functional. Here’s my code for Node:

 

var express = require('express');

var router = express.Router();

var bodyParser = require('body-parser');

var mysql = require('mysql');

var con = mysql.createConnection({

host:"localhost",

user:"perfmon",

password:"passwordhere",

database:"perfmon"

});

con.connect(function (err) {

if (err) throwerr;

console.log("Connected to SQL database");

});

/* GET home page. */

router.get('/', function (req, res, next) {

varserverName=req.query.serverName;

varCPU=req.query.CPU;

varRAM=req.query.RAM;

//sqlInsertPerfmon = "INSERT INTO serverbuilddata (ServerName, CPUCount, RAMCount) VALUES ('" + serverName + "'," + CPUCount + "," + serverMemory + ');'

sqlInsertPerfmon="INSERT INTO perfdata (serverName, CPU, RAM) VALUES ('"+serverName+"',"+CPU+","+RAM+");"

console.log('SQL Statement: '+sqlInsertPerfmon);

con.query(sqlInsertPerfmon, function (err, result) {

if (err) throw(err);

console.log("Performance Data Logged");

res.render('perfmon', { title:'Perfmon', serverName:serverName, CPU:CPU, RAM:RAM });

});




});

module.exports = router;

Learning about NodeJS, ExpressJS, and other components of server side javascript

Recently I’ve been learning about NodeJS and how to write server side JavaScript. This week I was creating a route that pulled variables from¬† POST, inserted them into a SQL database, and then retrieved the ID of the inserted record. Later on I wanted to take that ID and retrieve the record submitted. When the code ran I didn’t have errors but I had symptoms that didn’t make sense. As my debugging strategy I started logging variables to make sure they had values that I expected.

sqlInsertServer = "INSERT INTO serverbuilddata (ServerName, CPUCount, RAMCount) VALUES ('" + serverName + "'," + CPUCount + "," + serverMemory + ');'
console.log('SQL Statement: ' + sqlInsertServer);
var buildID;
con.query(sqlInsertServer, function (err, result) {
if (err) throw(err, null);
buildID = result.insertId;
console.log("Inside function. Server record inserted. Build ID: " + buildID);
);
console.log(buildID);

The first instance of build ID I logged inside the function returned the correct result, but the one outside of the function was undefined. I thought it was a scoping problem at first and tried to make sure the variable was declared globally. Then I noticed that the undefined variable outside the function was being logged before the one in the function. Then it kind of clicked about how Node is asynchronous. It doesn’t wait for other functions to run before the code gets executed even if they are in the correct order. After some research about my options I decided to go with call backs since they seemed to be the most recommended and best way.

sqlInsertServer = "INSERT INTO serverbuilddata (ServerName, CPUCount, RAMCount) VALUES ('" + serverName + "'," + CPUCount + "," + serverMemory + ');'
console.log('SQL Statement: ' + sqlInsertServer);

function serverInsert(callback) {
var buildID;
con.query(sqlInsertServer, function (err, result) {
if (err) callback(err, null);
buildID = result.insertId;
callback(null, buildID);
console.log("Inside function. Server record inserted. Build ID: " + buildID);
});
}

serverInsert(function (err, buildID) {
if (err) {
// error handling code goes here
console.log("ERROR : " + err);
} else {
// code to execute on data retrieval
console.log("result from callback is : " + buildID);
})

Callbacks wait for the function to provide it with a value. The problem is that value still can’t be used outside the function. And if that value in the callback needs to be used in another SQL function you need to nest another callback in the callback. Before you know it you have a mess. It does work but I’m still looking for better ways to do it. I’ve read about promises but I’m not ready for that yet.