Skip to content

Commit

Permalink
feat(router): aws lambda support (wundergraph#446)
Browse files Browse the repository at this point in the history
Co-authored-by: Suvij Surya <suvijsurya76@gmail.com>
  • Loading branch information
StarpTech and JivusAyrus authored Jan 22, 2024
1 parent a6a6322 commit 9c7d386
Show file tree
Hide file tree
Showing 94 changed files with 1,643 additions and 421 deletions.
2 changes: 2 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ controlplane:
- "controlplane/**/*"
graphqlmetrics:
- "graphqlmetrics/**/*"
aws-lambda-router:
- "aws-lambda-router/**/*"
63 changes: 63 additions & 0 deletions .github/workflows/aws-lambda-router-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: AWS Lambda Router CI
on:
pull_request:
paths:
- "aws-lambda-router/**/*"
- "router-tests/**/*"
- ".github/workflows/aws-lambda-router-ci.yaml"

concurrency:
group: ${{github.workflow}}-${{github.head_ref}}
cancel-in-progress: true

env:
CI: true

jobs:
build_test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
# The go install / version instructions are inside the Makefile, so we need to cache the Makefile.
key: ${{ runner.os }}-go-${{ hashFiles('aws-lambda-router/go.sum') }}-makefile-${{ hashFiles('Makefile') }}
restore-keys: |
${{ runner.os }}-go-
- uses: ./.github/actions/go
with:
cache-dependency-path: router/go.sum

- name: Install tools
run: make setup-build-tools

- name: Generate code
run: make generate-go

- name: Check if git is not dirty after generating files
run: git diff --no-ext-diff --exit-code

- name: Install dependencies
working-directory: ./aws-lambda-router
run: go mod download

- name: Run linters on router
uses: ./.github/actions/go-linter
with:
working-directory: ./aws-lambda-router

- name: Test
working-directory: ./aws-lambda-router
run: make test

- name: Build
working-directory: ./aws-lambda-router
run: make build
57 changes: 57 additions & 0 deletions .github/workflows/aws-router-binary-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Build and Release AWS Router Binaries
on:
release:
types: [published]
#workflow_dispatch:

permissions:
contents: write
packages: write

jobs:
releases-matrix:
if: startsWith(github.event.release.tag_name, 'aws-lambda-router@')
name: Build and Release AWS Router Binaries
runs-on: ubuntu-latest
timeout-minutes: 30

strategy:
matrix:
# build and publish in parallel: linux/386, linux/amd64, linux/arm64, darwin/amd64, darwin/arm64
goos: [linux, darwin]
goarch: ["386", amd64, arm64]
exclude:
- goarch: "386"
goos: darwin

steps:
- name: Checkout repository
uses: actions/checkout@v3

- uses: ./.github/actions/go
with:
cache-dependency-path: router/go.sum

- uses: winterjung/split@v2
id: split
with:
separator: "@"
msg: "${{ github.event.release.tag_name }}"

- uses: wangyoucao577/go-release-action@v1
name: Build and attach binaries to GitHub Release
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
# Where to run `go build .`
project_path: "aws-lambda-router/cmd"
# Convention from AWS Lambda
binary_name: "bootstrap"
pre_command: export CGO_ENABLED=0
build_flags: -trimpath
# -w = omits the DWARF symbol table, effectively removing debugging information. Reduces binary size by ~30%.
ldflags: -w -extldflags -static -X module github.com/wundergraph/cosmo/aws-lambda-router/internal.Version=${{ steps.split.outputs._1 }}
overwrite: true
extra_files: LICENSE
#release_tag: router@0.14.0
3 changes: 3 additions & 0 deletions aws-lambda-router/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.aws-sam
bootstrap
lambda.zip
30 changes: 30 additions & 0 deletions aws-lambda-router/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.PHONY: build

VERSION?=dev
build:
CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/aws-lambda-router/internal.Version=$(VERSION)" -a -o bootstrap cmd/main.go

build-sam:
rm -rf .aws-sam && sam build --parallel && cp router.json .aws-sam/build/Api/router.json

dev: build-sam
sam local start-api -p 3003 --shutdown

deploy: build-sam
sam deploy

lint:
cd adapter && go vet ./...
cd adapter && staticcheck ./...

test:
go test -v ./...

fetch-router-config:
wgc router fetch production -o router.json

sync:
sam sync --watch

create-lambda-zip: build fetch-router-config
zip -r lambda.zip bootstrap router.json
86 changes: 86 additions & 0 deletions aws-lambda-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# aws-lambda-router

<p align="center">
<img width="550" src="cover.png"/>
</p>

This is the [AWS Lambda](https://aws.amazon.com/lambda/) version of the WunderGraph Cosmo [Router](https://wundergraph.com/cosmo/features/router). Please [contact](https://wundergraph.com/contact/sales) us if you have any questions or production use case.
Why AWS lambda? Because it's cheap and scales automatically. You only pay for what you use. No need to manage servers or containers. It also integrates well with the rest of the AWS ecosystem.

Status: **Beta**

Demo: [https://zqadzbqwsi.execute-api.us-west-1.amazonaws.com/Prod/](https://zqadzbqwsi.execute-api.us-west-1.amazonaws.com/Prod/) (Playground)

## Features

- [X] GraphQL Queries
- [X] GraphQL Mutations
- [X] Telemetry Flushing after each request
- [X] Schema Usage Tracking after each request
- [ ] Subscription: Not implemented. Please [talk to us](https://wundergraph.com/contact/sales) if you need this.

## Requirements

* AWS CLI already configured with Administrator permission
* [Docker installed](https://www.docker.com/community-edition)
* [Golang](https://golang.org)
* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)

# Setup process

## Cosmo Cloud

First signup For Cosmo Cloud and follow the [onboarding](https://cosmo-docs.wundergraph.com/tutorial/cosmo-cloud-onboarding) process.

Run `make fetch-router-config` to fetch the latest router configuration from Cosmo Cloud. We assume that you have named your graph `production`.
The file is stored in `router.json` and copied to the Lambda build directory on each build.

## Local development

Build the router and start the API Gateway locally:

```bash
make dev
```

Open [http://127.0.0.1:3003/](http://127.0.0.1:3003/) in your browser and you should see the GraphQL Playground.

### Deploy on code change

This will upload the code to AWS without performing a CloudFormation deployment. This is useful for development.

```bash
make sync
```

## Deploying application

Ensure that the following environment variables are set in [template.yaml](template.yaml):

- `STAGE` - The name of the stage, which API Gateway uses as the first path segment in the invoke Uniform Resource Identifier (URI) e.g. `Prod` for `/Prod`.
- `GRAPH_API_TOKEN` - The API token for your graph. You can find this in the Cosmo Cloud dashboard.

*For production use cases, we recommend to use [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to store the `GRAPH_API_TOKEN`.*

```bash
make deploy
```

The command will package and deploy your application with the SAM CLI to AWS.
You can find your API Gateway Endpoint URL in the output values displayed after deployment.

# User Guide

You don't have to build the router yourself. You can download the latest release and follow the instructions below.

1. Download the Lambda Router binary from the official [Router Releases](https://github.com/wundergraph/cosmo/releases?q=aws-lambda-router&expanded=true) page.
2. Create a .zip archive with the binary and the `router.json` file. You can download the latest `router.json` with [`wgc federated-graph fetch`](https://cosmo-docs.wundergraph.com/cli/federated-graph/fetch).

The .zip archive should look like this:
```bash
.
└── myFunction.zip/
├── bootstrap # Extracted from the Router release archive
└── router.json # Downloaded with `wgc federated-graph fetch`
```
3. Deploy the .zip archive to AWS Lambda. You can use SAM CLI or the AWS console. Alternatively, you can use your IaC tool of choice.
99 changes: 99 additions & 0 deletions aws-lambda-router/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"context"
"errors"
"fmt"
"github.com/akrylysov/algnhsa"
"github.com/aws/aws-lambda-go/lambda"
"github.com/wundergraph/cosmo/aws-lambda-router/internal"
"github.com/wundergraph/cosmo/router/core"
"github.com/wundergraph/cosmo/router/pkg/config"
"github.com/wundergraph/cosmo/router/pkg/logging"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net/http"
"os"
"time"
)

const (
telemetryServiceName = "aws-lambda-router"
routerConfigPath = "router.json"
)

var (
defaultSampleRate = 0.2 // 20% of requests will be sampled
enableTelemetry = os.Getenv("DISABLE_TELEMETRY") != "true"
devMode = os.Getenv("DEV_MODE") == "true"
stage = os.Getenv("STAGE")
graphApiToken = os.Getenv("GRAPH_API_TOKEN")
httpPort = os.Getenv("HTTP_PORT")
)

func main() {
ctx := context.Background()

logger := logging.New(false, false, zapcore.InfoLevel)
logger = logger.With(
zap.String("service_version", internal.Version),
)
defer func() {
if err := logger.Sync(); err != nil {
fmt.Println("Could not sync logger", err)
}
}()

r := internal.NewRouter(
internal.WithGraphApiToken(graphApiToken),
internal.WithLogger(logger),
internal.WithRouterConfigPath(routerConfigPath),
internal.WithTelemetryServiceName(telemetryServiceName),
internal.WithStage(stage),
internal.WithTraceSampleRate(defaultSampleRate),
internal.WithEnableTelemetry(enableTelemetry),
internal.WithHttpPort(httpPort),
internal.WithRouterOpts(core.WithDevelopmentMode(devMode)),
internal.WithRouterOpts(
core.WithEngineExecutionConfig(config.EngineExecutionConfiguration{
EnableSingleFlight: true,
EnableRequestTracing: devMode,
EnableExecutionPlanCacheResponseHeader: devMode,
MaxConcurrentResolvers: 1024,
}),
),
)

svr, err := r.NewServer(ctx)
if err != nil {
logger.Fatal("Could not create server", zap.Error(err))
}

// Set the server to ready
svr.HealthChecks().SetReady(true)

// If HTTP_PORT is set, we assume we are running the router without lambda
if httpPort != "" {
if err := svr.HttpServer().ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Fatal("Could not start server", zap.Error(err))
}
return
}

lambdaHandler := algnhsa.New(svr.HttpServer().Handler, nil)
lambda.StartWithOptions(lambdaHandler,
lambda.WithContext(ctx),
// Registered an internal extensions which gives us 500ms to shutdown
// This mechanism does not replace telemetry flushing after a request
// https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-lifecycle-extensions-shutdown
lambda.WithEnableSIGTERM(func() {
logger.Debug("Server shutting down")
sCtx, cancel := context.WithTimeout(context.Background(), 400*time.Millisecond)
defer cancel()
if err := r.Shutdown(sCtx); err != nil {
panic(err)
}
logger.Debug("Server shutdown")
}),
)
}
Binary file added aws-lambda-router/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9c7d386

Please sign in to comment.