Skip to content

Commit

Permalink
Merge branch 'develop' into document_validate
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulMaddox authored Feb 23, 2018
2 parents a83c187 + 5fc9da3 commit 4d22ec4
Show file tree
Hide file tree
Showing 140 changed files with 64,921 additions and 84 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ sam --version

If you get a permission error when using npm (such as `EACCES: permission denied`), please see the instructions on this page of the NPM documentation: [https://docs.npmjs.com/getting-started/fixing-npm-permissions](https://docs.npmjs.com/getting-started/fixing-npm-permissions).

#### Upgrading via npm

To update **`sam`** once installed via npm:

```bash
npm update -g aws-sam-local
```

### Binary release

We also release the CLI as binaries that you can download and instantly use. You can find them under [Releases] in this repo. In case you cannot find the version or architecture you're looking for you can refer to [Build From Source](#build-from-source) section for build details.
Expand Down Expand Up @@ -457,7 +465,7 @@ sam local invoke --docker-volume-basedir /c/Users/shlee322/projects/test "Rating
- [ ] `dotnetcore1.0`
* [x] AWS credential support
* [x] Debugging support
* [ ] Inline Swagger support within SAM templates
* [x] Inline Swagger support within SAM templates

## Contributing

Expand Down
Empty file modified distribution/npm/npm-release.sh
100644 → 100755
Empty file.
18 changes: 9 additions & 9 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (

// Event represents an event passed to an AWS Lambda function by the runtime
type Event struct {
HTTPMethod string `json:"httpMethod,omitempty"`
Body string `json:"body,omitempty"`
Resource string `json:"resource,omitempty"`
RequestContext RequestContext `json:"requestContext,omitempty"`
QueryStringParams map[string]string `json:"queryStringParameters,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
PathParameters map[string]string `json:"pathParameters,omitempty"`
StageVariables map[string]string `json:"stageVariables,omitempty"`
Path string `json:"path,omitempty"`
HTTPMethod string `json:"httpMethod"`
Body string `json:"body"`
Resource string `json:"resource"`
RequestContext RequestContext `json:"requestContext"`
QueryStringParams map[string]string `json:"queryStringParameters"`
Headers map[string]string `json:"headers"`
PathParameters map[string]string `json:"pathParameters"`
StageVariables map[string]string `json:"stageVariables"`
Path string `json:"path"`
}

// RequestContext represents the context object that gets passed to an AWS Lambda function
Expand Down
169 changes: 163 additions & 6 deletions router/api.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
package router

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

"strings"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/awslabs/goformation/cloudformation"
"github.com/go-openapi/spec"
)

const apiGatewayIntegrationExtension = "x-amazon-apigateway-integration"
const apiGatewayAnyMethodExtension = "x-amazon-apigateway-any-method"

// temporary object. This is just used to marshal and unmarshal the any method
// API Gateway swagger extension
type ApiGatewayAnyMethod struct {
IntegrationSettings interface{} `json:"x-amazon-apigateway-integration"`
}

// AWSServerlessApi wraps GoFormation's AWS::Serverless::Api definition
// and adds some convenience methods for extracting the ServerlessRouterMount's
// from the swagger defintion etc.
Expand All @@ -16,7 +33,122 @@ 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{}

for path, pathItem := range swagger.Paths.Paths {
// temporary tracking of mounted methods for the current path. Used to
// mount all non-existing methods for the any extension. This is because
// the err from JSONLookup did not work as expected
mappedMethods := map[string]bool{}

for _, method := range HttpMethods {

if operationIface, err := pathItem.JSONLookup(strings.ToLower(method)); err == nil {
operation := spec.Operation{}
operationJSON, err := json.Marshal(operationIface)
if err != nil {
return nil, fmt.Errorf("Could not parse %s operation: %s", method, err.Error())
}
operation.UnmarshalJSON(operationJSON)

// the JSON will always contain the method because it's a property in the Swagger model
// If we don't have an integration defined then we skip it.
if operation.Extensions[apiGatewayIntegrationExtension] == nil {
continue
}

integration, _ := operation.Extensions[apiGatewayIntegrationExtension]
mounts = append(mounts, api.createMount(
path,
strings.ToLower(method),
api.parseIntegrationSettings(integration)))
mappedMethods[method] = true
}
}

anyMethod, available := pathItem.Extensions[apiGatewayAnyMethodExtension]
if available {
// any method to json then unmarshal to temporary object
anyMethodJSON, err := json.Marshal(anyMethod)
if err != nil {
return nil, fmt.Errorf("Could not marshal any method object to json")
}

anyMethodObject := ApiGatewayAnyMethod{}
err = json.Unmarshal(anyMethodJSON, &anyMethodObject)

if err != nil {
return nil, fmt.Errorf("Could not unmarshal any method josn to object model")
}

for _, method := range HttpMethods {
if _, ok := mappedMethods[method]; !ok {
mounts = append(mounts, api.createMount(
path,
strings.ToLower(method),
api.parseIntegrationSettings(anyMethodObject.IntegrationSettings)))
}
}
}
}

return mounts, nil
}

// parses a byte[] for the API Gateway inetegration extension from a method and return
// the object representation
func (api *AWSServerlessApi) parseIntegrationSettings(integrationData interface{}) *ApiGatewayIntegration {
integrationJSON, err := json.Marshal(integrationData)
if err != nil {
log.Printf("Could not parse integration data to json")
return nil
}

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

if err != nil {
log.Printf("Could not unmarshal integration data to ApiGatewayIntegration model")
return nil
}

return &integration
}

func (api *AWSServerlessApi) createMount(path string, verb string, integration *ApiGatewayIntegration) *(ServerlessRouterMount) {
newMount := &ServerlessRouterMount{
Name: path,
Path: path,
Method: verb,
}

if integration == nil {
log.Printf("No integration defined for method")
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 +191,42 @@ 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) getSwaggerFromS3Location(loc cloudformation.AWSServerlessApi_S3Location) ([]byte, error) {
sess := session.Must(session.NewSession())
client := s3.New(sess)

objectVersion := string(loc.Version)
s3Input := s3.GetObjectInput{
Bucket: &loc.Bucket,
Key: &loc.Key,
VersionId: &objectVersion,
}

object, err := client.GetObject(&s3Input)

if err != nil {
return nil, fmt.Errorf("Error while fetching Swagger template from S3: %s\n%s", loc.Bucket+loc.Key, err.Error())
}

body, err := ioutil.ReadAll(object.Body)

if err != nil {
return nil, fmt.Errorf("Cannot read s3 Swagger boject body: %s", err.Error())
}
return body, 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)
}
74 changes: 74 additions & 0 deletions router/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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_DefinitionUri{
String: templateUri,
},
},
}
return apiResource
}

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

Context("Load local Swagger definitions", func() {
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"))
}
}
})

It("Loads the proxy template", func() {
apiResource := getApiResourceFromTemplate("../test/templates/open-api/pet-store-proxy.json")

mounts, err := apiResource.Mounts()

Expect(err).Should(BeNil())
Expect(mounts).ShouldNot(BeNil())
// we expect 9 here because the any method should generate all 7
Expect(len(mounts)).To(BeIdenticalTo(9))

for _, mount := range mounts {
if mount.Method == "post" && mount.Path == "/pets/{proxy+}" {
Expect(mount.IntegrationArn.Arn).Should(ContainSubstring("AnyMethod"))
}
if mount.Method == "delete" && mount.Path == "/pets/{proxy+}" {
Expect(mount.IntegrationArn.Arn).Should(ContainSubstring("Calc"))
}
}
})

})
})
4 changes: 0 additions & 4 deletions router/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package router

import (
"net/http"
"regexp"

"github.com/awslabs/goformation/cloudformation"
)
Expand All @@ -15,8 +14,6 @@ type AWSServerlessFunction struct {
handler http.HandlerFunc
}

var catchAllPathVar = regexp.MustCompile(`\{([^}/]+)\+\}$`)

// Mounts fetches an array of the ServerlessRouterMount's for this API.
// These contain the path, method and handler function for each mount point.
func (f *AWSServerlessFunction) Mounts() ([]*ServerlessRouterMount, error) {
Expand All @@ -32,7 +29,6 @@ func (f *AWSServerlessFunction) Mounts() ([]*ServerlessRouterMount, error) {
Method: event.Properties.ApiEvent.Method,
Handler: f.handler,
Function: f,
UsePrefix: catchAllPathVar.MatchString(event.Properties.ApiEvent.Path),
})
}
}
Expand Down
Loading

0 comments on commit 4d22ec4

Please sign in to comment.