-
Notifications
You must be signed in to change notification settings - Fork 190
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for generic callback hooks
This patch adds support for a generic callback system into the generator config that allows controller implementors to specify some code that should be injected at specific named points in a template. I expect that eventually this generic hook system will be more useful, flexible and extensible than the hodge-podge of custom callback methods and overrides currently in the generator config. The `pkg/generate/config.ResourceConfig` struct now has a `Hooks` field of type `map[string]*HookConfig`, with the map keys being named hook points, e.g. "sdk_update_pre_build_request". There are two ways to inject code at hook points: inline and via a template path. The inline method uses the `HookConfig.Code` field which should contain the Go code that gets injected at a named hook point. The `HookConfig.TemplatePath` field is used to refer to a template file at a specific path. The template file is searched for in any of the TemplateSet's base template paths. Here's an example of a generator config snippet that uses the inline code injection method (`HookConfig.Code`) to add a piece of custom code to be executed in the sdk_update.go.tpl right before the code in the resource manager's `sdkUpdate` method calls the `newUpdateRequestPayload()` function: ```yaml resources: Broker: hooks: sdk_update_pre_build_request: code: if err := rm.requeueIfNotRunning(latest); err != nil { return nil, err } ``` Here is the snippet from the templates/pkg/resource/sdk_update.go.tpl file that shows how we can add these generic named hooks into our templates at various places: ``` {{- if $hookCode := .CRD.HookCode sdk_update_pre_build_request }} {{ $hookCode }} {{ end -}} ``` The controller implementor need only implement the little `requeueIfNotRunning` function, with the function signature described in the generator config code block. We no longer need to have function signatures match for custom callback code since the function signature for callback code is up to the dev writing the generator.yaml config file. Here is an example of the `HookConfig.TemplatePath` being used to refer to a template file containing some code that is injected at a named hook point: ```yaml resources: Broker: hooks: sdk_update_pre_build_request: template_path: sdk_update_pre_build_request.go.tpl ``` A controller implementor would simply need to populate a `sdk_update_pre_build_request.go.tpl` file with the code to be included at the hook point. This patch introduces the following hook points in the ACK controller resource manager code paths: * sdk_read_one_pre_build_request * sdk_create_pre_build_request * sdk_update_pre_build_request * sdk_delete_pre_build_request * sdk_read_one_post_request * sdk_create_post_request * sdk_update_post_request * sdk_delete_post_request * sdk_read_one_pre_set_output * sdk_create_pre_set_output * sdk_update_pre_set_output The "pre_build_request" hooks are called BEFORE the call to construct the Input shape that is used in the API operation and therefore BEFORE any call to validate that Input shape. The "post_request" hooks are called IMMEDIATELY AFTER the API operation aws-sdk-go client call. These hooks will have access to a Go variable named `resp` that refers to the aws-sdk-go client response and a Go variable named `respErr` that refers to any error returned from the aws-sdk-go client call. The "pre_set_output" hooks are called BEFORE the code that processes the Outputshape (the pkg/generate/code.SetOutput function). These hooks will have access to a Go variable named `ko` that represents the concrete Kubernetes CR object that will be returned from the main method (sdkFind, sdkCreate, etc). This `ko` variable will have been defined immediately before the "pre_set_output" hooks as a copy of the resource that is supplied to the main method, like so: ```go // Merge in the information we read from the API call above to the copy of // the original Kubernetes object we passed to the function ko := r.ko.DeepCopy() ```
- Loading branch information
Showing
13 changed files
with
3,727 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package ack | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"path/filepath" | ||
ttpl "text/template" | ||
|
||
ackmodel "github.com/aws-controllers-k8s/code-generator/pkg/model" | ||
ackutil "github.com/aws-controllers-k8s/code-generator/pkg/util" | ||
) | ||
|
||
/* | ||
The following hook points are supported in the ACK controller resource manager | ||
code paths: | ||
* sdk_read_one_pre_build_request | ||
* sdk_create_pre_build_request | ||
* sdk_update_pre_build_request | ||
* sdk_delete_pre_build_request | ||
* sdk_read_one_post_request | ||
* sdk_create_post_request | ||
* sdk_update_post_request | ||
* sdk_delete_post_request | ||
* sdk_read_one_pre_set_output | ||
* sdk_create_pre_set_output | ||
* sdk_update_pre_set_output | ||
The "pre_build_request" hooks are called BEFORE the call to construct | ||
the Input shape that is used in the API operation and therefore BEFORE | ||
any call to validate that Input shape. | ||
The "post_request" hooks are called IMMEDIATELY AFTER the API operation | ||
aws-sdk-go client call. These hooks will have access to a Go variable | ||
named `resp` that refers to the aws-sdk-go client response and a Go | ||
variable named `respErr` that refers to any error returned from the | ||
aws-sdk-go client call. | ||
The "pre_set_output" hooks are called BEFORE the code that processes the | ||
Outputshape (the pkg/generate/code.SetOutput function). These hooks will | ||
have access to a Go variable named `ko` that represents the concrete | ||
Kubernetes CR object that will be returned from the main method | ||
(sdkFind, sdkCreate, etc). This `ko` variable will have been defined | ||
immediately before the "pre_set_output" hooks as a copy of the resource | ||
that is supplied to the main method, like so: | ||
```go | ||
// Merge in the information we read from the API call above to the copy of | ||
// the original Kubernetes object we passed to the function | ||
ko := r.ko.DeepCopy() | ||
``` | ||
*/ | ||
|
||
// ResourceHookCode returns a string with custom callback code for a resource | ||
// and hook identifier | ||
func ResourceHookCode( | ||
templateBasePaths []string, | ||
r *ackmodel.CRD, | ||
hookID string, | ||
) (string, error) { | ||
resourceName := r.Names.Original | ||
if resourceName == "" || hookID == "" { | ||
return "", nil | ||
} | ||
c := r.Config() | ||
if c == nil { | ||
return "", nil | ||
} | ||
rConfig, ok := c.Resources[resourceName] | ||
if !ok { | ||
return "", nil | ||
} | ||
hook, ok := rConfig.Hooks[hookID] | ||
if !ok { | ||
return "", nil | ||
} | ||
if hook.Code != nil { | ||
return *hook.Code, nil | ||
} | ||
if hook.TemplatePath == nil { | ||
err := fmt.Errorf( | ||
"resource %s hook config for %s is invalid. Need either code or template_path", | ||
resourceName, hookID, | ||
) | ||
return "", err | ||
} | ||
for _, basePath := range templateBasePaths { | ||
tplPath := filepath.Join(basePath, *hook.TemplatePath) | ||
if !ackutil.FileExists(tplPath) { | ||
continue | ||
} | ||
tplContents, err := ioutil.ReadFile(tplPath) | ||
if err != nil { | ||
err := fmt.Errorf( | ||
"resource %s hook config for %s is invalid: error reading %s: %s", | ||
resourceName, hookID, tplPath, err, | ||
) | ||
return "", err | ||
} | ||
t := ttpl.New(tplPath) | ||
if t, err = t.Parse(string(tplContents)); err != nil { | ||
err := fmt.Errorf( | ||
"resource %s hook config for %s is invalid: error parsing %s: %s", | ||
resourceName, hookID, tplPath, err, | ||
) | ||
return "", err | ||
} | ||
var b bytes.Buffer | ||
// TODO(jaypipes): Instead of nil for template vars here, maybe pass in | ||
// a struct of variables? | ||
if err := t.Execute(&b, nil); err != nil { | ||
err := fmt.Errorf( | ||
"resource %s hook config for %s is invalid: error executing %s: %s", | ||
resourceName, hookID, tplPath, err, | ||
) | ||
return "", err | ||
} | ||
return b.String(), nil | ||
} | ||
err := fmt.Errorf( | ||
"resource %s hook config for %s is invalid: template_path %s not found", | ||
resourceName, hookID, *hook.TemplatePath, | ||
) | ||
return "", err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package ack_test | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/aws-controllers-k8s/code-generator/pkg/generate/ack" | ||
"github.com/aws-controllers-k8s/code-generator/pkg/testutil" | ||
) | ||
|
||
func TestResourceHookCodeInline(t *testing.T) { | ||
assert := assert.New(t) | ||
require := require.New(t) | ||
basePaths := []string{} | ||
hookID := "sdk_update_pre_build_request" | ||
|
||
g := testutil.NewGeneratorForService(t, "mq") | ||
|
||
crd := testutil.GetCRDByName(t, g, "Broker") | ||
require.NotNil(crd) | ||
|
||
// The Broker's update operation has a special hook callback configured | ||
expected := `if err := rm.requeueIfNotRunning(latest); err != nil { return nil, err }` | ||
got, err := ack.ResourceHookCode(basePaths, crd, hookID) | ||
assert.Nil(err) | ||
assert.Equal(expected, got) | ||
} | ||
|
||
func TestResourceHookCodeTemplatePath(t *testing.T) { | ||
assert := assert.New(t) | ||
require := require.New(t) | ||
wd, _ := os.Getwd() | ||
basePaths := []string{ | ||
filepath.Join(wd, "testdata", "templates"), | ||
} | ||
hookID := "sdk_delete_pre_build_request" | ||
|
||
g := testutil.NewGeneratorForService(t, "mq") | ||
|
||
crd := testutil.GetCRDByName(t, g, "Broker") | ||
require.NotNil(crd) | ||
|
||
// The Broker's delete operation has a special hook configured to point to a template. | ||
expected := "// this is my template.\n" | ||
got, err := ack.ResourceHookCode(basePaths, crd, hookID) | ||
assert.Nil(err) | ||
assert.Equal(expected, got) | ||
} |
1 change: 1 addition & 0 deletions
1
pkg/generate/ack/testdata/templates/sdk_delete_pre_build_request.go.tpl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
// this is my template. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.