Skip to content

Commit

Permalink
First import of Swagger support, lots more work to do
Browse files Browse the repository at this point in the history
  • Loading branch information
sapessi committed Oct 22, 2017
1 parent 69633c0 commit 22c305b
Show file tree
Hide file tree
Showing 106 changed files with 41,667 additions and 12 deletions.
92 changes: 88 additions & 4 deletions router/api.go
Expand Up @@ -2,8 +2,12 @@ package router

import (
"fmt"
"log"
"io/ioutil"
"encoding/json"

"github.com/awslabs/goformation/cloudformation"
"github.com/go-openapi/spec"
)

// AWSServerlessApi wraps GoFormation's AWS::Serverless::Api definition
Expand All @@ -16,7 +20,83 @@ type AWSServerlessApi struct {
// Mounts fetches an array of the ServerlessRouterMount's for this API.
// These contain the path, method and handler function for each mount point.
func (api *AWSServerlessApi) Mounts() ([]*ServerlessRouterMount, error) {
return nil, nil
jsonDefinition, err := api.Swagger();

if err != nil {
// this is our own error so we return it directly
return nil, err
}
swagger := spec.Swagger{}
err = swagger.UnmarshalJSON(jsonDefinition)

if err != nil {
return nil, fmt.Errorf("Cannot parse Swagger definition: %s", err.Error())
}

mounts := []*ServerlessRouterMount{}
log.Printf("Reading paths from Swagger")

for path, pathItem := range swagger.Paths.Paths {

if pathItem.Get != nil {
mounts = append(mounts, api.createMount(path, "get", pathItem.Get))
}
if pathItem.Post != nil {
mounts = append(mounts, api.createMount(path, "post", pathItem.Post))
}
if pathItem.Put != nil {
mounts = append(mounts, api.createMount(path, "put", pathItem.Put))
}
if pathItem.Patch != nil {
mounts = append(mounts, api.createMount(path, "patch", pathItem.Patch))
}
if pathItem.Delete != nil {
mounts = append(mounts, api.createMount(path, "delete", pathItem.Delete))
}
if pathItem.Options != nil {
mounts = append(mounts, api.createMount(path, "options", pathItem.Options))
}
}

return mounts, nil
}

func (api *AWSServerlessApi) createMount(path string, verb string, method *spec.Operation) *(ServerlessRouterMount) {
newMount := &ServerlessRouterMount{
Name: path,
Path: path,
Method: verb,
}

integrationData, available := method.Extensions["x-amazon-apigateway-integration"]
if !available {
log.Printf("No integration defined")
return newMount
}
integrationJson, err := json.Marshal(integrationData);
if err != nil {
log.Printf("Could not parse integration data to json")
return newMount
}

integration := ApiGatewayIntegration{}
err = json.Unmarshal(integrationJson, &integration)

// I'm not going to treat this as a fatal error. We can still pick up from the list of functions
// integration data may not be defined up at all.
if err != nil {
log.Printf("Could not Unmarshal integration: %s", err.Error())
return newMount
}

functionName, err := integration.GetFunctionArn()

if err != nil {
log.Printf("Could not extract Lambda function ARN: %s", err.Error())
}
newMount.IntegrationArn = functionName

return newMount;
}

// Swagger gets the swagger definition for the API.
Expand Down Expand Up @@ -59,17 +139,21 @@ func (api *AWSServerlessApi) Swagger() ([]byte, error) {
}

func (api *AWSServerlessApi) getSwaggerFromURI(uri string) ([]byte, error) {
return nil, nil
data, err := ioutil.ReadFile(uri)
if err != nil {
return nil, fmt.Errorf("Cannot read local Swagger definition (%s): %s", uri, err.Error())
}
return data, nil
}

func (api *AWSServerlessApi) getSwaggerFromS3Location(cloudformation.AWSServerlessApi_S3Location) ([]byte, error) {
return nil, nil
}

func (api *AWSServerlessApi) getSwaggerFromString(input string) ([]byte, error) {
return nil, nil
return []byte(input), nil
}

func (api *AWSServerlessApi) getSwaggerFromMap(input map[string]interface{}) ([]byte, error) {
return nil, nil
return json.Marshal(input)
}
61 changes: 61 additions & 0 deletions router/api_test.go
@@ -0,0 +1,61 @@
package router_test

import (
"github.com/awslabs/aws-sam-local/router"
"github.com/awslabs/goformation/cloudformation"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func getApiResourceFromTemplate(path string) (*router.AWSServerlessApi) {
templateUri := &path
apiResource := &router.AWSServerlessApi{
AWSServerlessApi: &cloudformation.AWSServerlessApi{
DefinitionUri: &cloudformation.AWSServerlessApi_StringOrS3Location{
String: templateUri,
},
},
}
return apiResource
}

var _ = Describe("Api", func() {

Context("Load local Swagger definitions", func() {
/*apiResource := router.AWSServerlessApi{}
templateUri := new(string)
*templateUri = "test/templates/open-api/pet-store-simple.json"
definitionUri := new(cloudformation.AWSServerlessApi_StringOrS3Location)
definitionUri.String = templateUri
apiResource.DefinitionUri = definitionUri*/
It("Succesfully loads the basic template", func() {
apiResource := getApiResourceFromTemplate("../test/templates/open-api/pet-store-simple.json")

mounts, err := apiResource.Mounts()

Expect(err).Should(BeNil())
Expect(mounts).ShouldNot(BeNil())

Expect(mounts).ShouldNot(BeEmpty())
Expect(len(mounts)).Should(BeIdenticalTo(4))
})

It("Succesfully reads integration definition", func() {
apiResource := getApiResourceFromTemplate("../test/templates/open-api/pet-store-simple.json")

mounts, err := apiResource.Mounts()

Expect(err).Should(BeNil())
Expect(mounts).ShouldNot(BeNil())

for _, mount := range mounts {
if mount.Method == "get" && mount.Path == "/pets" {
Expect(mount.IntegrationArn.Arn).Should(BeIdenticalTo("arn:aws:lambda:us-west-2:123456789012:function:Calc"))
}
}
})

})
})
48 changes: 48 additions & 0 deletions router/integration.go
@@ -0,0 +1,48 @@
package router

import (
"regexp"
"fmt"
)

type ApiGatewayIntegration struct {
Uri string `json:"uri"`
PassthroughBehavior string `json:"passthroughBehavior"`
Type string `json:"type"`
}

func (i *ApiGatewayIntegration) GetFunctionArn() (*LambdaFunctionArn, error) {
// arn:aws:apigateway:us-west-2:lambda:path//2015-03-31/functions/arn:aws:lambda:us-west-2:123456789012:function:Calc/invocations
firstMatch, err := getFirstMatch(`.*/functions/(.*)/invocations`, i.Uri)

if err != nil {
return nil, err
}

return &LambdaFunctionArn{ Arn: firstMatch }, nil
}

type LambdaFunctionArn struct {
Arn string
}

func (a *LambdaFunctionArn) GetFunctionName() (string, error) {
firstMatch, err := getFirstMatch(`.*:function:(.*)/invocations`, a.Arn)

if err != nil {
return "", err
}

return firstMatch, nil
}

func getFirstMatch(regex string, value string) (string, error) {
re := regexp.MustCompile(regex)
match := re.FindStringSubmatch(value)

if len(match) < 2 {
return "", fmt.Errorf("Could not find match in %s", value)
}

return match[1], nil
}
14 changes: 9 additions & 5 deletions router/mount.go
Expand Up @@ -8,11 +8,15 @@ import (
// ServerlessRouterMount represents a single mount point on the API
// Such as '/path', the HTTP method, and the function to resolve it
type ServerlessRouterMount struct {
Name string
Function *AWSServerlessFunction
Handler http.HandlerFunc
Path string
Method string
Name string
Function *AWSServerlessFunction
Handler http.HandlerFunc
Path string
Method string
// authorization settings
AuthType string
AuthFunction *AWSServerlessFunction
IntegrationArn *LambdaFunctionArn
}

// Methods gets an array of HTTP methods from a AWS::Serverless::Function
Expand Down
37 changes: 35 additions & 2 deletions router/router.go
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/awslabs/goformation/cloudformation"
"github.com/gorilla/mux"
//"github.com/docker/docker/api/types/mount"
)

// ErrNoEventsFound is thrown if a AWS::Serverless::Function is added to this
Expand Down Expand Up @@ -43,7 +44,12 @@ func (r *ServerlessRouter) AddFunction(f *cloudformation.AWSServerlessFunction,
return ErrNoEventsFound
}

r.mounts = append(r.mounts, mounts...)
//r.mounts = append(r.mounts, mounts...)
err = r.mergeMounts(mounts)
if err != nil {
return err
}

return nil

}
Expand All @@ -59,9 +65,36 @@ func (r *ServerlessRouter) AddAPI(a *cloudformation.AWSServerlessApi) error {
return err
}

r.mounts = append(r.mounts, mounts...)
//r.mounts = append(r.mounts, mounts...)
err = r.mergeMounts(mounts)
if err != nil {
return err
}

return nil
}

// merges the various mount paths. mounts could be coming from a function as well as API
// definition. Mounts defined by an API do not have a handler, only a function ARN.
func (r *ServerlessRouter) mergeMounts(newMounts []*ServerlessRouterMount) error {
for _, newMount := range newMounts {
newMountExists := false

for _, existingMount := range r.mounts {
if newMount.Path == existingMount.Path && newMount.Method == existingMount.Method {
newMountExists = true
// if the new mount has a valid handler I override the existing one anyway
if newMount.Handler != nil {
existingMount.Handler = newMount.Handler
}
}
}

if !newMountExists {
r.mounts = append(r.mounts, newMount)
}
}
return nil
}

// AddStaticDir mounts a static directory provided, at the mount point also provided
Expand Down
7 changes: 7 additions & 0 deletions start.go
Expand Up @@ -54,6 +54,13 @@ func start(c *cli.Context) {
// Create a new router
mux := router.NewServerlessRouter()

templateApis := template.GetAllAWSServerlessApiResources()

for name, api := range templateApis {
log.Printf("Adding new API: %s", name)
mux.AddAPI(&api)
}

functions := template.GetAllAWSServerlessFunctionResources()

for name, function := range functions {
Expand Down

0 comments on commit 22c305b

Please sign in to comment.