2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2020

03/07/2018: Environment Variables Available In AWS Lambda Go

The short version of this story is simple. Below is a sorted list of the environment variables available to an AWS function written in Go. Note that the secret information is available. I’ve used asterisks for their value.

AWS_ACCESS_KEY: ********
AWS_ACCESS_KEY_ID: ********
AWS_DEFAULT_REGION: us-east-1
AWS_LAMBDA_FUNCTION_MEMORY_SIZ: 1024
AWS_LAMBDA_FUNCTION_NAME: odol-sango-dev-logenvironment
AWS_LAMBDA_FUNCTION_VERSION: $LATEST
AWS_LAMBDA_LOG_GROUP_NAME: /aws/lambda/odol-sango-dev-logenvironment
AWS_LAMBDA_LOG_STREAM_NAME: 2018/03/07/[$LATEST]2b67007c25944c85a09e4b06ae683867
AWS_REGION: us-east-1
AWS_SECRET_ACCESS_KEY: ********
AWS_SECRET_KEY: ********
AWS_SECURITY_TOKEN: ********
AWS_SESSION_TOKEN: ********
AWS_XRAY_CONTEXT_MISSING: LOG_ERROR
AWS_XRAY_DAEMON_ADDRESS: 169.254.79.2:2000
LAMBDA_RUNTIME_DIR: /var/runtime
LAMBDA_TASK_ROOT: /var/task
LANG: en_US.UTF-8
LD_LIBRARY_PATH: /lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib
PATH: /usr/local/bin:/usr/bin/:/bin
0TZ: :UTC
_AWS_XRAY_DAEMON_ADDRESS: 169.254.79.2
_AWS_XRAY_DAEMON_PORT: 2000
_HANDLER: bin/logenvironment
_LAMBDA_CONSOLE_SOCKET: 35
_LAMBDA_CONTROL_SOCKET: 28
_LAMBDA_LOG_FD: 45
_LAMBDA_RUNTIME_LOAD_TIME: 4653494624711
_LAMBDA_SB_ID: 16
_LAMBDA_SERVER_PORT: 61375
_LAMBDA_SHARED_MEM_FD: 12
_X_AMZN_TRACE_ID: Parent

The long version is more involved. I used the Serverless framework for this short project. After installing the Serverless framework I created a project using the Go template as follows:

cd $GOPATH/src
serverless create -t aws-go-dep -p odol-sango
cd odol-sango
make
serverless deploy
serverless invoke -f hello

Now that the basic project is working. Create a file called functions/logenvironment/logenvironment.go:

package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	var restricted [6]string
	restricted[0] = "AWS_ACCESS_KEY"
	restricted[1] = "AWS_ACCESS_KEY_ID"
	restricted[2] = "AWS_SECRET_ACCESS_KEY"
	restricted[3] = "AWS_SECRET_KEY"
	restricted[4] = "AWS_SECURITY_TOKEN"
	restricted[5] = "AWS_SESSION_TOKEN"

	envs := make(map[string]string)
	// Make a hash map of the environment variables.
	for _, e := range os.Environ() {
		pair := strings.Split(e, "=")
		var key_is_restricted = false
		for _, a := range restricted {
			if a == pair[0] {
				key_is_restricted = true
			}
		}
		if key_is_restricted {
			envs[pair[0]] = "********"
		} else {
			envs[pair[0]] = pair[1]
		}
	}

	// Sort the keys
	keys := make([]string, 0, len(envs))
	for key := range envs {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	// Print the environment variables
	for _, k := range keys {
		fmt.Printf("%03.30s: %s\n", k, envs[k])
	}

	return events.APIGatewayProxyResponse{Body: "Environment Logged\n", StatusCode: 200}, nil
}

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

Update the Makefile:

build:
	dep ensure
	env GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
	env GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go
	env GOOS=linux go build -ldflags="-s -w" -o bin/logenvironment functions/logenvironment/logenvironment.go

Update the serverless.yml file:

logenvironment:
  handler: bin/logenvironment
  events:
    - http:
        path: logenvironment
        method: get

Compile and deploy:

make && sls deploy

In one bash window, run the following command to watch the Lambda log messages:

serverless logs -f logenvironment -t

In another window, call the function using something like:

curl https://olkj0m5hag.execute-api.us-east-1.amazonaws.com/dev/logenvironment

Good Luck!

02/15/2018: python wtforms - handling dynamic fields

I wanted to have a web form with a variable number of fields - one for each day in a month. Since that number varies (i.e., Feb usually has 28, while Jan Jan has 31), I wanted my form to also have a variable number of input fields.

Naturally, I created a hack. The form has a constant 31 fields, but when displayed in the jinja2 template, only daysInMonth fields are used.

Here is the form definition:

from flask_wtf import FlaskForm
from wtforms import FieldList, HiddenField, IntegerField, StringField, SubmitField, validators
from wtforms.validators import DataRequired, UUID

class MonthForm(FlaskForm):
    handle = HiddenField('handle', validators=[DataRequired(), UUID()])
    month = HiddenField('month', validators=[DataRequired()])
    year = HiddenField('year', validators=[DataRequired()])
    daysInMonth = HiddenField('daysInMonth', validators=[DataRequired()])
    for n in range(1, 32):
        locals()[''.join("morning"+str(n))] = StringField('', [ validators.Length(min=0, max=4)])
        locals()[''.join("evening"+str(n))] = StringField('', [ validators.Length(min=0, max=4)])
    submit = SubmitField('Save')
    export = SubmitField('Export')

    def getField(self, fieldName):
        for f in self:
            if f.name == fieldName:
                return f
        return None

Note that I use a range to avoid the tedious, error-prone duplication of 64 fields. The locals() method is terrific.

The getField() method is needed in order to find the right field to display in the template.


{% for n in range(1, daysInMonth+1) %}
  <tr>
    <td>{{n}}</td>
    <td>{{ form.getField('morning' + n|string)(size="5") }}</td>
    <td>{{ form.getField('evening' + n|string)(size="5") }}</td>
  </tr>
{% endfor %}

In a world in which python supports static class members, I’d use a hash to cache the fields. This would avoid the for loop every time a field is needed. On the other hand, the cache might be premature optimization. For my little, tiny application with less than 40 fields running the for loop is not a problem.

Footnote: Use raw and endraw inside {% %} tags to display Jinja2 code inside GitHub Markdown.