glambda

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2024 License: MIT Imports: 25 Imported by: 1

README

Glambda Deployment Tool

Glambda is a simple tool for bundling and deploying AL2023 compatible Lambda functions written in Go. It provides an easy way to create, update and delete AWS Lambdas quickly from the command line using a compact set of commands.

Get started with Glambda by running the following command:

glambda deploy <lambdaName> <path/to/handler.go> 

The intent is to maximise ease of use, at the expense of infinite customisability, and doesn't really play in the same space as SAM, CDK or Terraform.

If you'd prefer to use these more mature tools, consider using the package sub-command which will just write out a well formatted zip file ready to upload to AWS.

Why though?

AWS pivoted from a Go managed runtime to an OS only runtime. I'd argue that relative to the managed runtime, the OS only runtime has a much higher barrier to entry. Hence this libary!

You can learn more about it at https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html

Prerequisites

To use Glambda, you will need an AWS account, and an AWS Access Key ID and Secret Access Key with the appropriate permissions to create and manage Lambda functions, as well as IAM roles.

Glambda will also assume the following AWS environment variables are set up:

export AWS_ACCESS_KEY_ID=<your-access-key-id>
export AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
export AWS_DEFAULT_REGION=<your-region>

Installation

To install Glambda, run:

go install github.com/mr-joshcrane/glambda/cmd/glambda@latest

Usage

Package a lambda, ready for deployment

If you've already got a deployment tool you'd prefer to use, no problem. You can build the lambda zip file with the package sub-command.

## Default output path is "./bootstrap" which is what AWS will be expecting
glambda deploy package <path/to/handler.go>
## Alternatively you can provide the output path explicitly
glambda deploy package <path/to/handler.go> --output /my/custom/filepath/artifact.zip

From here you'll have the ability to take this zip file and do what needs doing in your tool of choice.

Create new lambdas directly

Run the following command to deploy a Lambda function with an associated execution role:

glambda deploy <lambdaName> <path/to/handler.go> 

Replace <lambdaName> with the desired name for your Lambda function and <path/to/handler.go> with the path to your Lambda function's handler file.

The source file should have a main function that calls lambda.Start(handler). See https://pkg.go.dev/github.com/aws/aws-lambda-go/lambda#Start for more details.


Update existing lambdas

What's that? You've updated your code and need to deploy a new version of your Lambda function? No problem! Just run the same command as before, and Glambda will update the function code for you without recreating the lambda or the role.

In fact, assuming the path to your handler didnt change, we only need to run the same command!

glambda deploy <lambdaName> <path/to/handler.go>

Execution Role and Lambda Resource Permissions

OK, that's nice, but sometimes your role actually has to DO things. Like access S3 buckets or DynamoDB tables. No problem! Glambda can attach managed policies, inline policies, and resource policies to your Lambda function's execution role.

## Attach a managed policy by name or ARN to the Lambda function's execution roles
managedPolicies=S3FullAccess,arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess

## Attach an inline policy (as a JSON literal) to the Lambda function's execution roles
inlinePolicies='{"Effect": "Deny", "Action": "s3:GetObject", "Resource": "*"}'

## Attach a resource policy (as a JSON literal) to the Lambda function
resourcePolicies='{
            "Sid": "YourLambdaResourcePolicy",
            "Effect": "Allow",
            "Principal": {
              "Service": "events.amazonaws.com"
            },
            "Action": "lambda:InvokeFunction",
            "Resource":  "arn:aws:lambda:us-east-2:123456789012:function:my-function",
            "Condition": {
              "StringEquals": {
                "AWS:SourceAccount": "123456789012"
              }
        }'

glambda deploy <lambdaName> <path/to/handler.go> \
    --managed-policies ${managedPolicies} \
    --inline-policy ${inlinePolicies} \
    --resource-policy ${resourcePolicies}
Deleting lambdas and associated roles

Deleting your Lambda function and associated role is also easy, performed with the following command:

glambda delete <lambdaName>

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultAssumeRolePolicy     = `` /* 130-byte string literal not displayed */
	AWSLambdaBasicExecutionRole = `arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole`
	ThisAWSAccountCondition     = `"Condition":{"StringEquals":{"aws:PrincipalAccount": "${aws:accountId}"}}"`
)
View Source
var AWSAccountID = GetAWSAccountID
View Source
var DefaultRetryWaitingPeriod = func() {
	time.Sleep(3 * time.Second)
}

Functions

func AttachInLinePolicyCommand

func AttachInLinePolicyCommand(roleName string, policyName string, inlinePolicy string) iam.PutRolePolicyInput

AttachInLinePolicyCommand is a paperwork reducer that translates parameters into the smithy autogenerated AWS IAM SDKv2 format of iam.PutRolePolicyInput

func AttachManagedPolicyCommand

func AttachManagedPolicyCommand(roleName string, policyARN string) iam.AttachRolePolicyInput

GetRoleCommand is a paperwork reducer that translates parameters into the smithy autogenerated AWS IAM SDKv2 format of iam.GetRoleInput

func CreateLambdaCommand

func CreateLambdaCommand(name, roleARN string, pkg []byte) *lambda.CreateFunctionInput

CreateLambdaCommand is a paperwork reducer that translates parameters into the smithy autogenerated AWS Lambda SDKv2 format of lambda.CreateFunctionInput

func CreateRoleCommand

func CreateRoleCommand(roleName string, assumePolicyDocument string) *iam.CreateRoleInput

CreateRoleCommand is a paperwork reducer that translates parameters into the smithy autogenerated AWS IAM SDKv2 format of iam.CreateRoleInput

func Delete

func Delete(name string) error

Delete is a convenience function that will delete a lambda function and the associated IAM Role. Deletion is actually more complex than it might seem at first glance and requires a specific unwinding of various resources.

It should be noted that it is A) a destructive operation and B) allows for the possibility of deleting resources that are not managed by this library. The usual care and due dillgence should be taken before deleting.

It will also detach any managed policies that were attached to the role. It is a high level abstraction that should represent the majority of use cases for this library.

func Deploy

func Deploy(name, source string, opts ...DeployOptions) error

Deploy is a convenience function that will handle the paperwork that would otherwise fall to the user to manage. It will create a new Lambda struct and attempt to deploy it to AWS. It will also test the lambda function after deployment. It is a high level abstraction that should represent the majority of use cases for this library.

func GenerateUUID

func GenerateUUID() string

func GetAWSAccountID

func GetAWSAccountID(client STSClient) (string, error)

GetAWSAccountID calls the AWS STS API to get the user credentials that the user is using to make the API call. This response contains the AWS Account ID of the IAM Principal

func PackageTo

func PackageTo(path string, output io.Writer) error

PackageTo takes a path to a file, attempts to build it for the ARM64 architecture and massages it into the format expected by AWS Lambda.

The result is a zip file containing the executable binary within the context of a file system.

func ParseInlinePolicy

func ParseInlinePolicy(policy string) (string, error)

ParseInlinePolicy takes a string representation of an inline policy. It validates that the string contains a valid JSON representation. It also removes whitespace characters. Critically it does not ensure that a policy itself is valid and will only catch more obvious errors.

func ParseManagedPolicy

func ParseManagedPolicy(policy string) []string

ParseManagedPolicy takes a string representation of a list of managed policies. Each element in the list should be a string that represents an ARN or a name of a managed policy. Given the AWS Managed Policy 'AWSLambdaBasicExecutionRole' as an example, it may be specified in the following ways:

1. Full ARN: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 2. PolicyName: "AWSLambdaBasicExecutionRole"

func PutRolePolicyCommand

func PutRolePolicyCommand(role ExecutionRole) []iam.PutRolePolicyInput

PutRolePolicyCommand is a paperwork reducer that takes the definition of an execution role and creates an appropriate iam.PutRolePolicyInput payload. This payload is sent to the AWS API to attach an inline policy to a given AWS IAM Role. Useful for when you need to give fine grained permissions to your Lambda Execution Role

func UpdateLambdaCommand

func UpdateLambdaCommand(name string, pkg []byte) *lambda.UpdateFunctionCodeInput

UpdateLambdaCommand is a paperwork reducer that translates parameters into the smithy autogenerated AWS Lambda SDKv2 format of lambda.UpdateFunctionCodeInput

func Validate

func Validate(path string) error

Validate takes a path to a Go source file. A valid Go source file for the purposes of AWS Lambda will...

1. Contain a main function.

2. Call one of the lambda Start... functions as seen here https://pkg.go.dev/github.com/aws/aws-lambda-go/lambda#Start

func WaitForConsistency

func WaitForConsistency(c LambdaClient, name string) (string, error)

WaitForConsistence deals with the fact that lambda functions are eventually consistent. Deploying a lambda function and then immediately invoking it can result in an invocation of the previous version of the lambda function, which could mask deployment failures. This function waits for the lambda function to become consistent by publishing a new version which seems to wait on the backend until the lambda function is consistent.

Types

type Action

type Action interface {
	Do() error
}

Actions are at a high level a way to organise a set of operations that need to be performed with the AWS SDK and in which order. Operations might depend on the result of a previous operation.

type DeployOptions

type DeployOptions func(*Lambda) error

DeployOptions is any function that can be used to configure a Lambda struct before it is deployed. It is a functional option pattern.

func WithAWSConfig

func WithAWSConfig(cfg aws.Config) DeployOptions

WithAWSConfig is a deploy option that allows the user to provide a custom AWS Config to the Lambda struct. This is useful when you need more fine grained control over the AWS SDK configuration.

func WithInlinePolicy

func WithInlinePolicy(policy string) DeployOptions

WithInlinePolicy is a deploy option that allows the user to attach an inline policy to the Lambda struct. The inline policy is expected to be a JSON string. For parsing rules see ParseInlinePolicy.

func WithManagedPolicies

func WithManagedPolicies(policies string) DeployOptions

WithManagedPolicies is a deploy option that allows the user to attach one or more managed policies to the Lambda struct. The managed policies are expected to be a comma separated string of ARNs. For parsing rules see ParseManagedPolicy.

func WithResourcePolicy

func WithResourcePolicy(policy string) DeployOptions

WithResourcePolicy is a deploy option that allows the user to attach a resource policy to the Lambda struct. The resource policy is expected to be a JSON string. For parsing rules see ParseResourcePolicy.

type ExecutionRole

type ExecutionRole struct {
	RoleName                 string
	RoleARN                  string
	AssumeRolePolicyDocument string
	ManagedPolicies          []string
	InLinePolicy             string
}

ExecutionRole is a struct that attempts to encapsulate all the information required to create an AWS IAM Role that the lambda function will assume and operate on behalf of. It ties together several IAM concepts that would otherwise require separate API calls to create.

type IAMClient

type IAMClient interface {
	CreateRole(ctx context.Context, params *iam.CreateRoleInput, optFns ...func(*iam.Options)) (*iam.CreateRoleOutput, error)
	GetRole(ctx context.Context, params *iam.GetRoleInput, optFns ...func(*iam.Options)) (*iam.GetRoleOutput, error)
	AttachRolePolicy(ctx context.Context, params *iam.AttachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.AttachRolePolicyOutput, error)
	PutRolePolicy(ctx context.Context, params *iam.PutRolePolicyInput, optFns ...func(*iam.Options)) (*iam.PutRolePolicyOutput, error)
}

IAMClient represents the interface that an iam client should implement.

The most obvious implementation is the iam.Client from the aws-sdk-go-v2 However we also use it for mock clients in tests

type Lambda

type Lambda struct {
	Name           string
	HandlerPath    string
	ExecutionRole  ExecutionRole
	AWSAccountID   string
	ResourcePolicy ResourcePolicy
	// contains filtered or unexported fields
}

Lambda is a struct that attempts to encapsulate the neccessary information required to deploy a lambda function to AWS. It doesn't map 1:1 with the AWS Lambda API, or any of the concrete AWS artifacts, and should be thought of as a higher level abstraction of convenience.

func NewLambda

func NewLambda(name, handlerPath string) (*Lambda, error)

NewLambda is a constructor function that creates a new Lambda struct. It requires a friendly name for the lambda function to be created, and the path to the handler code that will be executed when the lambda function is invoked. It assumes the environment is configured with the necessary AWS credentials can be found in the enviroment. It also assumes that a default AWS region is set. Finally it assumes that the current AWS credentials can perform an sts:GetCallerIdentity identity call in order to determine the AWS account ID.

func (Lambda) CreateLambdaResourcePolicy

func (l Lambda) CreateLambdaResourcePolicy() *lambda.AddPermissionInput

CreateLambdaResourcePolicy is a paperwork reducer that takes the definition of a lambda and creates an appropriate lambda.AddPermissionInput payload. This payload is sent to the AWS API to allow the source defined in the lambda.ResourcePolicy to invoke this lambda. This is useful for example when you want to allow only a particular AWS Service or AWS Principal (ie. Account, Role, User) to invoke the lambda.

func (Lambda) Deploy

func (l Lambda) Deploy() error

Deploy is a method on the Lambda struct that will attempt to deploy the lambda function to AWS. It will attempt to prepare, then deploy the execution role, and if successful will repeat the process for the lambda function itself.

func (Lambda) Test

func (l Lambda) Test() error

Test is a method on the Lambda struct that will attempt to invoke the newly created lambda function in a dry run mode. This is useful for testing the lambda function after deployment. As per AWS documentation, the dry run mode should not execute the lambda function, but will rather 'validate parameter values and verify that the user or role has permission to invoke the function'.

type LambdaAction

type LambdaAction interface {
	Client() LambdaClient
	Action
}

LambdaActions are any set of operations that requires the AWS Lambda service. It includes a configured client in order to perform these operations. They are a subset of the Action interface.

func PrepareLambdaAction

func PrepareLambdaAction(l Lambda, c LambdaClient) (LambdaAction, error)

PrepareLambdaAction is a function that creates a new LambdaAction struct. It will create the deployment package, and then determine if the lambda function needs to be created. It will branch out into either a LambdaCreateAction or a LambdaUpdateAction depending on the current state in AWS.

type LambdaClient

type LambdaClient interface {
	CreateFunction(ctx context.Context, params *lambda.CreateFunctionInput, optFns ...func(*lambda.Options)) (*lambda.CreateFunctionOutput, error)
	UpdateFunctionCode(ctx context.Context, params *lambda.UpdateFunctionCodeInput, optFns ...func(*lambda.Options)) (*lambda.UpdateFunctionCodeOutput, error)
	GetFunction(ctx context.Context, params *lambda.GetFunctionInput, optFns ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error)
	PublishVersion(ctx context.Context, params *lambda.PublishVersionInput, optFns ...func(*lambda.Options)) (*lambda.PublishVersionOutput, error)
	Invoke(ctx context.Context, params *lambda.InvokeInput, optFns ...func(*lambda.Options)) (*lambda.InvokeOutput, error)
	AddPermission(ctx context.Context, params *lambda.AddPermissionInput, optFns ...func(*lambda.Options)) (*lambda.AddPermissionOutput, error)
	DeleteFunction(ctx context.Context, params *lambda.DeleteFunctionInput, optFns ...func(*lambda.Options)) (*lambda.DeleteFunctionOutput, error)
}

LambdaClient represents the interface that a lambda client should implement.

The most obvious implementation is the lambda.Client from the aws-sdk-go-v2 However we also use it for mock clients in tests

type LambdaCreateAction

type LambdaCreateAction struct {
	CreateLambdaCommand   *lambda.CreateFunctionInput
	ResourcePolicyCommand *lambda.AddPermissionInput
	// contains filtered or unexported fields
}

LambdaCreateAction is LambdaAction that will create a new lambda function, and potentially attach a resource policy to it.

func NewLambdaCreateAction

func NewLambdaCreateAction(client LambdaClient, l Lambda, pkg []byte) LambdaCreateAction

NewLambdaCreateAction is a constructor function that creates a new LambdaCreateAction.

func (LambdaCreateAction) Client

func (a LambdaCreateAction) Client() LambdaClient

Client returns the required client type. In this case LambdaClient.

func (LambdaCreateAction) Do

func (a LambdaCreateAction) Do() error

Do is the implementation of the Action interface. It will create the lambda function and attach the resource policy if it was provided, returning any error.

type LambdaUpdateAction

type LambdaUpdateAction struct {
	UpdateLambdaCommand   *lambda.UpdateFunctionCodeInput
	ResourcePolicyCommand *lambda.AddPermissionInput
	// contains filtered or unexported fields
}

LambdaUpdateAction is LambdaAction that will update an existing lambda function.

func NewLambdaUpdateAction

func NewLambdaUpdateAction(client LambdaClient, l Lambda, pkg []byte) LambdaUpdateAction

NewLambdaUpdateAction is a constructor function that creates a new LambdaUpdateAction.

func (LambdaUpdateAction) Client

func (a LambdaUpdateAction) Client() LambdaClient

Client returns the required client type. In this case LambdaClient.

func (LambdaUpdateAction) Do

func (a LambdaUpdateAction) Do() error

Do is the implementation of the Action interface. It will update the lambda Updating a lambda function in this context will mean updating the packaged zip file that contains the lambda function code. It may also optionally require updating the resource policy attached to the lambda function, if one was provided.

type ResourcePolicy

type ResourcePolicy struct {
	Principal               string
	SourceAccountCondition  *string
	SourceArnCondition      *string
	PrincipalOrgIdCondition *string
}

ResourcePolicy is a struct that represents the policy that will be attached to the lambda function. Unlike the Lambda struct, this struct is more directly aligned to an AWS artifact. Namely the result of a call to the [AddPermission] API.

func ParseResourcePolicy

func ParseResourcePolicy(policy string) (ResourcePolicy, error)

ParseResourcePolicy takes a string representation of a AWS Lambda resource policy and returns a ResourcePolicy struct.

Parsing is done by regex matching, but ideally this could be done with something more rigourous like unification with a CUE schema.

type RetryableErrors

type RetryableErrors struct{}

func (RetryableErrors) IsErrorRetryable

func (r RetryableErrors) IsErrorRetryable(err error) aws.Ternary

IsErrorRetryable is a custom retryer that tells the lambda client to retry on which errors.

type RoleAction

type RoleAction interface {
	Client() IAMClient
	Do() error
}

RoleAction is a high level interface that represents a set of operations that come from attempting to manage the AWS IAM Role that will be used as the Lambda's execution role.

func PrepareRoleAction

func PrepareRoleAction(role ExecutionRole, iamClient IAMClient) (RoleAction, error)

PrepareRoleAction is a function that creates a new RoleCreateOrUpdate struct. It will add the AWS managed policy "AWSLambdaBasicExecutionRole" to the role by default as a lambda without this role makes very little sense. It will also add any managed policies and inline policies that were provided in the ExecutionRole struct.

This function does make live API calls to AWS IAM to determine if the role already exists. If not, it will create a new CreateRoleCommand to be executed by the RoleCreateOrUpdate. The PutRolePolicyCommand and AttachManagedPolicyCommand created here for deferred execution.

type RoleCreateOrUpdate

type RoleCreateOrUpdate struct {
	CreateRole      *iam.CreateRoleInput
	ManagedPolicies []iam.AttachRolePolicyInput
	InlinePolicies  []iam.PutRolePolicyInput
	// contains filtered or unexported fields
}

RoleCreateOrUpdate is a struct that implements the RoleAction interface. It is a high level abstraction that encapsulates the operations required to either create or update an IAM Role. It includes the ability to attach managed policies and inline policies to the role.

The reason create and update are combined into a single struct is because from the users perspective, the goal is the same. They want to ensure that the role exists and has the correct policies attached to it.

func NewRoleCreateOrUpdateAction

func NewRoleCreateOrUpdateAction(client IAMClient) RoleCreateOrUpdate

NewRoleCreateOrUpdateAction is a constructor function that creates a new RoleCreateOrUpdate.

func (RoleCreateOrUpdate) Client

func (a RoleCreateOrUpdate) Client() IAMClient

Client returns the required client type. In this case IAMClient.

func (RoleCreateOrUpdate) Do

func (a RoleCreateOrUpdate) Do() error

Do is the implementation of the Action interface. It will create the role if it was determined that it didn't exist at Action construction time (see PrepareRoleAction). It will then execute the attach role policy and put role policy commands in that order as provided at Action construction time.

type STSClient

type STSClient interface {
	GetCallerIdentity(ctx context.Context, params *sts.GetCallerIdentityInput, optFns ...func(*sts.Options)) (*sts.GetCallerIdentityOutput, error)
}

STSClient represents the interface that an sts client should implement.

The most obvious implementation is the sts.Client from the aws-sdk-go-v2 However we also use it for mock clients in tests

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL