From 1e4beabcb2b0f540653a4f44574f7874ee091d77 Mon Sep 17 00:00:00 2001 From: Marek Magdziak Date: Wed, 28 Apr 2021 11:49:01 +0200 Subject: [PATCH] Feat: support for pipeline resolvers (#9) --- appSyncClient.go | 215 +++++++++++++++++++++++++++++++++++++++++------ go.mod | 2 +- go.sum | 19 +++++ out/out.go | 22 +++-- resolvers.yml | 40 ++++++++- schema.graphql | 1 + 6 files changed, 265 insertions(+), 34 deletions(-) diff --git a/appSyncClient.go b/appSyncClient.go index eec5b9d..ee8dc83 100644 --- a/appSyncClient.go +++ b/appSyncClient.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os" - "strconv" "time" "github.com/aws/aws-sdk-go/aws" @@ -21,20 +20,36 @@ var env = os.Getenv("ENV") type ( Resolvers struct { Resolvers []Resolver `yaml:"resolvers"` + Functions []Function `yaml:"functions"` } Resolver struct { + DataSourceName string `yaml:"dataSource"` + FieldName string `yaml:"fieldName"` + RequestMappingTemplate string `yaml:"requestMappingTemplate"` + ResponseMappingTemplate string `yaml:"responseMappingTemplate"` + Functions []string `yaml:"functions"` + TypeName string `yaml:"typeName"` + } + + Function struct { DataSourceName string `yaml:"dataSource"` - FieldName string `yaml:"fieldName"` + Name string `yaml:"name"` RequestMappingTemplate string `yaml:"requestMappingTemplate"` ResponseMappingTemplate string `yaml:"responseMappingTemplate"` - TypeName string `yaml:"typeName"` + } + + Statistics struct { + Created int + Updated int + FailedToCreate int + FailedToUpdate int } ) // AppSync interface type AppSync interface { - CreateOrUpdateResolvers(apiID string, resolversFile []byte, logger *log.Logger) (string, string, string, string, error) + CreateOrUpdateResolvers(apiID string, resolversFile []byte, logger *log.Logger) (Statistics, Statistics, error) StartSchemaCreationOrUpdate(apiID string, schema []byte) error GetSchemaCreationStatus(apiID string) (string, string, error) } @@ -46,14 +61,19 @@ type appSyncClient struct { func NewAppSyncClient( awsConfig *aws.Config, -) AppSync { - session := session.New(awsConfig) +) (AppSync, error) { + session, err := session.NewSession(awsConfig) + + if err != nil { + return nil, err + } + client := appsync.New(session, awsConfig) return &appSyncClient{ appSyncClient: client, session: session, - } + }, nil } func NewAwsConfig( @@ -89,20 +109,63 @@ func NewAwsConfig( return awsConfig } -func (client *appSyncClient) CreateOrUpdateResolvers(apiID string, resolversFile []byte, logger *log.Logger) (string, string, string, string, error) { - // number of resolvers successfully created - var nResolversSuccessfullyCreated = 0 - // number of resolver successfully updated - var nResolversSuccessfullyUpdated = 0 - // number of resolver fail to create - var nResolversfailCreated = 0 - // number of resolver fail to update - var nResolversfailUpdate = 0 +func (client *appSyncClient) CreateOrUpdateResolvers(apiID string, resolversFile []byte, logger *log.Logger) (Statistics, Statistics, error) { + resolverStatistics := Statistics{} + functionStatistics := Statistics{} var resolvers Resolvers err := yaml.Unmarshal(resolversFile, &resolvers) if err != nil { - return strconv.Itoa(nResolversSuccessfullyCreated), strconv.Itoa(nResolversfailCreated), strconv.Itoa(nResolversSuccessfullyUpdated), strconv.Itoa(nResolversfailUpdate), err + return resolverStatistics, functionStatistics, err + } + + functions, err := client.getFunctions(apiID) + + if err != nil { + return resolverStatistics, functionStatistics, err + } + + for _, function := range resolvers.Functions { + functionName := function.Name + + existingFunction := getFunctionByName(functionName, functions) + + if existingFunction != nil { + _, err := client.updateFunction(&appsync.UpdateFunctionInput{ + ApiId: aws.String(apiID), + DataSourceName: aws.String(function.DataSourceName), + RequestMappingTemplate: aws.String(function.RequestMappingTemplate), + ResponseMappingTemplate: aws.String(function.ResponseMappingTemplate), + FunctionId: existingFunction.FunctionId, + Description: aws.String(function.Name), + Name: aws.String(function.Name), + FunctionVersion: aws.String("2018-05-29"), + }) + if err != nil { + fmt.Printf("Function %s failed to update: %s", function.Name, err) + functionStatistics.FailedToUpdate++ + } else { + functionStatistics.Updated++ + } + } else { + function, err := client.createFunction(&appsync.CreateFunctionInput{ + ApiId: aws.String(apiID), + DataSourceName: aws.String(function.DataSourceName), + RequestMappingTemplate: aws.String(function.RequestMappingTemplate), + ResponseMappingTemplate: aws.String(function.ResponseMappingTemplate), + Description: aws.String(function.Name), + Name: aws.String(function.Name), + FunctionVersion: aws.String("2018-05-29"), + }) + + if err != nil { + fmt.Printf("Function %s failed to create: %s", *function.Name, err) + functionStatistics.FailedToCreate++ + } else { + functions = append(functions, function) + functionStatistics.Created++ + } + } } for _, resolver := range resolvers.Resolvers { @@ -118,38 +181,86 @@ func (client *appSyncClient) CreateOrUpdateResolvers(apiID string, resolversFile resolver := fmt.Sprintf("Resolver, FieldName:%s, TypeName: %s, Error: %s", resolverFieldName, resolverTypeName, err) logger.Println("faild to fetch", resolver) } + + dataSourceName := aws.String(resolver.DataSourceName) + var pipelineConfig *appsync.PipelineConfig + resolverKind := appsync.ResolverKindUnit + + shouldContinue := true + if len(resolver.Functions) > 0 { + resolverKind = appsync.ResolverKindPipeline + pipelineConfig = &appsync.PipelineConfig{} + + for i := range resolver.Functions { + existingFunction := getFunctionByName(resolver.Functions[i], functions) + + if existingFunction == nil { + logger.Printf("Failed to find function: %s, I will not continue with updating resolver type: %s, field: %s\n", + resolver.Functions[i], resolver.TypeName, resolver.FieldName) + shouldContinue = false + break + } + + pipelineConfig.Functions = append(pipelineConfig.Functions, existingFunction.FunctionId) + } + + dataSourceName = nil + } + + if !shouldContinue { + continue + } + if resolverResp != nil { var params = &appsync.UpdateResolverInput{ ApiId: aws.String(apiID), - DataSourceName: aws.String(resolver.DataSourceName), + DataSourceName: dataSourceName, FieldName: aws.String(resolver.FieldName), - RequestMappingTemplate: aws.String(fmt.Sprintf("%s", resolver.RequestMappingTemplate)), + RequestMappingTemplate: aws.String(resolver.RequestMappingTemplate), ResponseMappingTemplate: aws.String(resolver.ResponseMappingTemplate), TypeName: aws.String(resolver.TypeName), + Kind: &resolverKind, + PipelineConfig: pipelineConfig, } _, err := client.updateResolver(params) if err != nil { - nResolversfailUpdate++ + fmt.Printf("Resolver on type %s and field %s failed to update: %s", resolver.TypeName, resolver.FieldName, err) + resolverStatistics.FailedToUpdate++ + } else { + resolverStatistics.Updated++ } - nResolversSuccessfullyUpdated++ } else { var params = &appsync.CreateResolverInput{ ApiId: aws.String(apiID), - DataSourceName: aws.String(resolver.DataSourceName), + DataSourceName: dataSourceName, FieldName: aws.String(resolver.FieldName), - RequestMappingTemplate: aws.String(fmt.Sprintf("%s", resolver.RequestMappingTemplate)), + RequestMappingTemplate: aws.String(resolver.RequestMappingTemplate), ResponseMappingTemplate: aws.String(resolver.ResponseMappingTemplate), TypeName: aws.String(resolver.TypeName), + Kind: &resolverKind, + PipelineConfig: pipelineConfig, } _, err := client.createResolver(params) if err != nil { - nResolversfailCreated++ + fmt.Printf("Resolver on type %s and field %s failed to create: %s", resolver.TypeName, resolver.FieldName, err) + resolverStatistics.FailedToCreate++ + } else { + resolverStatistics.Created++ } + } + } - nResolversSuccessfullyCreated++ + return resolverStatistics, functionStatistics, nil +} + +func getFunctionByName(name string, functions []*appsync.FunctionConfiguration) *appsync.FunctionConfiguration { + for _, function := range functions { + if *function.Name == name { + return function } } - return strconv.Itoa(nResolversSuccessfullyCreated), strconv.Itoa(nResolversfailCreated), strconv.Itoa(nResolversSuccessfullyUpdated), strconv.Itoa(nResolversfailUpdate), nil + + return nil } func (client *appSyncClient) getResolver(params *appsync.GetResolverInput) (*appsync.Resolver, error) { @@ -164,6 +275,36 @@ func (client *appSyncClient) getResolver(params *appsync.GetResolverInput) (*app } +func (client *appSyncClient) getFunctions(apiID string) ([]*appsync.FunctionConfiguration, error) { + var out []*appsync.FunctionConfiguration + + params := appsync.ListFunctionsInput{ + ApiId: aws.String(apiID), + MaxResults: aws.Int64(25), + NextToken: nil, + } + + for { + req, resp := client.appSyncClient.ListFunctionsRequest(¶ms) + + err := req.Send() + if err != nil { + return nil, err + } + + out = append(out, resp.Functions...) + + if resp.NextToken == nil { + break + } + + params.NextToken = resp.NextToken + } + + return out, nil + +} + func (client *appSyncClient) updateResolver(params *appsync.UpdateResolverInput) (*appsync.Resolver, error) { req, resp := client.appSyncClient.UpdateResolverRequest(params) @@ -175,6 +316,17 @@ func (client *appSyncClient) updateResolver(params *appsync.UpdateResolverInput) return resp.Resolver, nil } +func (client *appSyncClient) updateFunction(params *appsync.UpdateFunctionInput) (*appsync.FunctionConfiguration, error) { + req, resp := client.appSyncClient.UpdateFunctionRequest(params) + + err := req.Send() + if err != nil { + return nil, err + } + + return resp.FunctionConfiguration, nil +} + func (client *appSyncClient) createResolver(params *appsync.CreateResolverInput) (*appsync.Resolver, error) { req, resp := client.appSyncClient.CreateResolverRequest(params) @@ -186,6 +338,17 @@ func (client *appSyncClient) createResolver(params *appsync.CreateResolverInput) return resp.Resolver, nil } +func (client *appSyncClient) createFunction(params *appsync.CreateFunctionInput) (*appsync.FunctionConfiguration, error) { + req, resp := client.appSyncClient.CreateFunctionRequest(params) + + err := req.Send() + if err != nil { + return nil, err + } + + return resp.FunctionConfiguration, nil +} + func (client *appSyncClient) StartSchemaCreationOrUpdate(apiID string, schema []byte) error { schemaCreateParams := &appsync.StartSchemaCreationInput{ diff --git a/go.mod b/go.mod index ab7b2d8..d7e2d50 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/telia-oss/appsync-resource go 1.13 require ( - github.com/aws/aws-sdk-go v1.15.68 + github.com/aws/aws-sdk-go v1.38.25 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 2aea1f5..d87d04e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,26 @@ github.com/aws/aws-sdk-go v1.15.68 h1:2+CJhKkxU/ldztVaJ7V5adR1/ZnYl+oc7ufq2Bl18BQ= github.com/aws/aws-sdk-go v1.15.68/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.38.25 h1:aNjeh7+MON05cZPtZ6do+KxVT67jPOSQXANA46gOQao= +github.com/aws/aws-sdk-go v1.38.25/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/out/out.go b/out/out.go index d91c4e8..4c1f2a9 100644 --- a/out/out.go +++ b/out/out.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "log" "os" + "strconv" resource "github.com/telia-oss/appsync-resource" ) @@ -54,7 +55,11 @@ func Command(input InputJSON, logger *log.Logger) (outOutputJSON, error) { regionName, ) - client := resource.NewAppSyncClient(awsConfig) + client, err := resource.NewAppSyncClient(awsConfig) + + if err != nil { + return outOutputJSON{}, err + } if schemaFile == "" && resolversFile == "" { return outOutputJSON{}, errors.New("resolversFile and schemaFile both are not set") @@ -104,15 +109,20 @@ func Command(input InputJSON, logger *log.Logger) (outOutputJSON, error) { logger.Fatalf("can't read the resolvers file: %s", rerr) } - nResolversSuccessfullyCreated, nResolversfailCreated, nResolversSuccessfullyUpdated, nResolversfailUpdate, err := client.CreateOrUpdateResolvers(apiID, resolversFile, logger) + resolvers, functions, err := client.CreateOrUpdateResolvers(apiID, resolversFile, logger) if err != nil { logger.Println("failed to create/update", err) } resolverOutput = []metadata{ - {Name: "number of resolvers successfully created", Value: nResolversSuccessfullyCreated}, - {Name: "number of resolver successfully updated", Value: nResolversSuccessfullyUpdated}, - {Name: "number of resolver fail to create", Value: nResolversfailCreated}, - {Name: "number of resolver fail to update", Value: nResolversfailUpdate}, + {Name: "number of resolvers successfully created", Value: strconv.Itoa(resolvers.Created)}, + {Name: "number of resolver successfully updated", Value: strconv.Itoa(resolvers.Updated)}, + {Name: "number of resolver fail to create", Value: strconv.Itoa(resolvers.FailedToCreate)}, + {Name: "number of resolver fail to update", Value: strconv.Itoa(resolvers.FailedToUpdate)}, + + {Name: "number of functions successfully created", Value: strconv.Itoa(functions.Created)}, + {Name: "number of functions successfully updated", Value: strconv.Itoa(functions.Updated)}, + {Name: "number of functions fail to create", Value: strconv.Itoa(functions.FailedToCreate)}, + {Name: "number of functions fail to update", Value: strconv.Itoa(functions.FailedToUpdate)}, } } output = outOutputJSON{ diff --git a/resolvers.yml b/resolvers.yml index 27c763f..4254d99 100644 --- a/resolvers.yml +++ b/resolvers.yml @@ -1,5 +1,36 @@ +functions: + - dataSource: none_datasource + name: todo_one + requestMappingTemplate: ' + #set( $event = { + "id": "test", + "name": "Some todo" + } ) + + { + "version" : "2018-05-29", + "payload": $util.toJson($event) + } + ' + responseMappingTemplate: $util.toJson($context.result) + - dataSource: none_datasource + name: todo_two + requestMappingTemplate: ' + #set( $event = { + "id": $context.prev.result.id, + "name": $context.prev.result.name, + "description": 42, + "priority": 123 + } ) + + { + "version" : "2018-05-29", + "payload": $util.toJson($event) + } + ' + responseMappingTemplate: $util.toJson($context.result) resolvers: - - dataSource: test + - dataSource: none_datasource typeName: Query fieldName: getTodos requestMappingTemplate: > @@ -12,4 +43,11 @@ resolvers: "operation": "Invoke", "payload": $util.toJson($payload) } + responseMappingTemplate: $util.toJson($context.result) + - typeName: Query + fieldName: todo + functions: + - todo_one + - todo_two + requestMappingTemplate: '{}' responseMappingTemplate: $util.toJson($context.result) \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index a76acc0..c88c7a1 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,5 +1,6 @@ type Query { getTodos: [Todo] + todo: Todo } type Todo {