Skip to content

Commit 2d48795

Browse files
committed
Introduce ack-generate webhooks subcommand
This patch brings in a new sub command that can be used to generate webhook functions. For now it can only generate conversion webhook functions, but it can be used to generate defaulting and validating functions. This patch also introduces a new function to load existing ack-generate metadata for a given api version.
1 parent 3ef26f4 commit 2d48795

File tree

4 files changed

+331
-0
lines changed

4 files changed

+331
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package command
15+
16+
import (
17+
"fmt"
18+
"io/ioutil"
19+
"path/filepath"
20+
"strings"
21+
22+
"github.com/spf13/cobra"
23+
24+
"github.com/aws-controllers-k8s/code-generator/pkg/generate/ack"
25+
ackgenerate "github.com/aws-controllers-k8s/code-generator/pkg/generate/ack"
26+
"github.com/aws-controllers-k8s/code-generator/pkg/model/multiversion"
27+
)
28+
29+
var (
30+
optHubVersion string
31+
optDeprecatedVersions []string
32+
optEnableConversionWebhook bool
33+
)
34+
35+
var webhooksCmd = &cobra.Command{
36+
Use: "webhooks <service>",
37+
Short: "Generates Go files containing ",
38+
RunE: generateWebhooks,
39+
}
40+
41+
func init() {
42+
webhooksCmd.PersistentFlags().StringVar(
43+
&optHubVersion, "hub-version", "", "the hub version for conversion webhooks",
44+
)
45+
webhooksCmd.PersistentFlags().BoolVar(
46+
&optEnableConversionWebhook, "enable-conversion-webhooks", false, "enable conversion webhooks generation",
47+
)
48+
rootCmd.AddCommand(webhooksCmd)
49+
}
50+
51+
// generateWebhooks generates the Go files for conversion, defaulting and validating webhooks.
52+
func generateWebhooks(cmd *cobra.Command, args []string) error {
53+
if len(args) != 1 {
54+
return fmt.Errorf("please specify the service alias for the AWS service API to generate")
55+
}
56+
svcAlias := strings.ToLower(args[0])
57+
if optOutputPath == "" {
58+
optOutputPath = filepath.Join(optServicesDir, svcAlias)
59+
}
60+
61+
apisVersionPath = filepath.Join(optOutputPath, "apis")
62+
files, err := ioutil.ReadDir(apisVersionPath)
63+
if err != nil {
64+
return err
65+
}
66+
67+
apisInfos := map[string]multiversion.APIInfo{}
68+
for _, f := range files {
69+
metadata, err := ack.LoadGenerationMetadata(apisVersionPath, f.Name())
70+
if err != nil {
71+
return err
72+
}
73+
apisInfos[f.Name()] = multiversion.APIInfo{
74+
// Setting the status to unknown for now.
75+
// TODO(a-hilaly) read status from ack-generate-metadata.yaml
76+
Status: multiversion.APIStatusUnknown,
77+
AWSSDKVersion: metadata.AWSSDKGoVersion,
78+
GeneratorConfigPath: filepath.Join(apisVersionPath, f.Name(), metadata.GeneratorConfigInfo.OriginalFileName),
79+
}
80+
}
81+
82+
if optHubVersion == "" {
83+
latestAPIVersion, err := getLatestAPIVersion()
84+
if err != nil {
85+
return err
86+
}
87+
optHubVersion = latestAPIVersion
88+
}
89+
90+
mgr, err := multiversion.NewAPIVersionManager(
91+
optCacheDir,
92+
svcAlias,
93+
optHubVersion,
94+
apisInfos,
95+
ack.DefaultConfig,
96+
)
97+
if err != nil {
98+
return err
99+
}
100+
101+
if optEnableConversionWebhook {
102+
ts, err := ackgenerate.ConversionWebhooks(mgr, optTemplateDirs)
103+
if err != nil {
104+
return err
105+
}
106+
107+
if err = ts.Execute(); err != nil {
108+
return err
109+
}
110+
111+
for path, contents := range ts.Executed() {
112+
if optDryRun {
113+
fmt.Printf("============================= %s ======================================\n", path)
114+
fmt.Println(strings.TrimSpace(contents.String()))
115+
continue
116+
}
117+
outPath := filepath.Join(optOutputPath, path)
118+
outDir := filepath.Dir(outPath)
119+
if _, err := ensureDir(outDir); err != nil {
120+
return err
121+
}
122+
if err = ioutil.WriteFile(outPath, contents.Bytes(), 0666); err != nil {
123+
return err
124+
}
125+
}
126+
}
127+
return nil
128+
}

pkg/generate/ack/output.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,23 @@ func CreateGenerationMetadata(
145145
return nil
146146
}
147147

148+
// LoadGenerationMetadata read the generation metadata for a given api version and
149+
// apis path.
150+
func LoadGenerationMetadata(apisPath, apiVersion string) (*GenerationMetadata, error) {
151+
filePath := filepath.Join(apisPath, apiVersion, outputFileName)
152+
b, err := ioutil.ReadFile(filePath)
153+
if err != nil {
154+
return nil, err
155+
}
156+
157+
var generationMetadata GenerationMetadata
158+
err = yaml.Unmarshal(b, &generationMetadata)
159+
if err != nil {
160+
return nil, err
161+
}
162+
return &generationMetadata, nil
163+
}
164+
148165
// hashDirectoryContent returns the sha1 checksum of a given directory. It will walk
149166
// the file tree of a directory and combine and the file contents before hashing it.
150167
func hashDirectoryContent(directory string) (string, error) {

pkg/generate/ack/webhook.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package ack
15+
16+
import (
17+
"fmt"
18+
ttpl "text/template"
19+
20+
"github.com/aws-controllers-k8s/code-generator/pkg/generate/code"
21+
"github.com/aws-controllers-k8s/code-generator/pkg/generate/templateset"
22+
ackmodel "github.com/aws-controllers-k8s/code-generator/pkg/model"
23+
"github.com/aws-controllers-k8s/code-generator/pkg/model/multiversion"
24+
)
25+
26+
var (
27+
webhooksIncludePaths = []string{
28+
"boilerplate.go.tpl",
29+
"apis/webhooks/conversion.go.tpl",
30+
}
31+
webhookCopyPaths = []string{}
32+
webhooksFuncMap = ttpl.FuncMap{
33+
"GoCodeConvert": func(
34+
src *ackmodel.CRD,
35+
dst *ackmodel.CRD,
36+
convertingToHub bool,
37+
hubImportPath string,
38+
sourceVarName string,
39+
targetVarName string,
40+
indentLevel int,
41+
) string {
42+
return code.Convert(src, dst, convertingToHub, hubImportPath, sourceVarName, targetVarName, indentLevel)
43+
},
44+
}
45+
)
46+
47+
// ConversionWebhooks returns a pointer to a TemplateSet containing all the templates
48+
// for generating conversion webhooks.
49+
func ConversionWebhooks(
50+
mgr *multiversion.APIVersionManager,
51+
templateBasePaths []string,
52+
) (*templateset.TemplateSet, error) {
53+
ts := templateset.New(
54+
templateBasePaths,
55+
webhooksIncludePaths,
56+
webhookCopyPaths,
57+
webhooksFuncMap,
58+
)
59+
hubVersion := mgr.GetHubVersion()
60+
hubModel, err := mgr.GetModel(hubVersion)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
hubMetaVars := hubModel.MetaVars()
66+
hubCRDs, err := hubModel.GetCRDs()
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
for _, crd := range hubCRDs {
72+
convertVars := conversionVars{
73+
MetaVars: hubMetaVars,
74+
SourceCRD: crd,
75+
IsHub: true,
76+
}
77+
// Add the hub version template
78+
target := fmt.Sprintf("apis/%s/%s_conversion.go", hubVersion, crd.Names.Snake)
79+
if err = ts.Add(target, "apis/webhooks/conversion.go.tpl", convertVars); err != nil {
80+
return nil, err
81+
}
82+
}
83+
84+
// Add spoke version templates
85+
for _, spokeVersion := range mgr.GetSpokeVersions() {
86+
model, err := mgr.GetModel(spokeVersion)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
metaVars := model.MetaVars()
92+
crds, err := model.GetCRDs()
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
for i, crd := range crds {
98+
convertVars := conversionVars{
99+
MetaVars: metaVars,
100+
SourceCRD: crd,
101+
DestCRD: hubCRDs[i],
102+
IsHub: false,
103+
HubVersion: hubVersion,
104+
}
105+
106+
target := fmt.Sprintf("apis/%s/%s_conversion.go", spokeVersion, crd.Names.Snake)
107+
if err = ts.Add(target, "apis/webhooks/conversion.go.tpl", convertVars); err != nil {
108+
return nil, err
109+
}
110+
}
111+
}
112+
113+
return ts, nil
114+
}
115+
116+
// conversionVars contains template variables for templates that output
117+
// Go conversion functions.
118+
type conversionVars struct {
119+
templateset.MetaVars
120+
SourceCRD *ackmodel.CRD
121+
DestCRD *ackmodel.CRD
122+
HubVersion string
123+
IsHub bool
124+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{{- template "boilerplate" }}
2+
3+
{{ $hubImportAlias := .HubVersion }}
4+
package {{ .APIVersion }}
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
10+
ctrlrtconversion "sigs.k8s.io/controller-runtime/pkg/conversion"
11+
{{- if not .IsHub }}
12+
ctrlrt "sigs.k8s.io/controller-runtime"
13+
ackrtwh "github.com/aws-controllers-k8s/runtime/pkg/webhook"
14+
15+
{{ $hubImportAlias }} "github.com/aws-controllers-k8s/{{ .ServiceIDClean }}-controller/apis/{{ .HubVersion }}"
16+
{{- end }}
17+
)
18+
19+
var (
20+
_ = fmt.Printf
21+
_ = json.Marshal
22+
)
23+
24+
{{- if .IsHub }}
25+
// Assert hub interface implementation {{ .SourceCRD.Names.Camel }}
26+
var _ ctrlrtconversion.Hub = &{{ .SourceCRD.Names.Camel }}{}
27+
28+
// Hub marks this type as conversion hub.
29+
func (*{{ .SourceCRD.Kind }}) Hub() {}
30+
{{ else }}
31+
32+
func init() {
33+
webhook := ackrtwh.New(
34+
"conversion",
35+
"{{ .SourceCRD.Names.Camel }}",
36+
"{{ .APIVersion }}",
37+
func(mgr ctrlrt.Manager) error {
38+
return ctrlrt.NewWebhookManagedBy(mgr).
39+
For(&{{ .SourceCRD.Names.Camel }}{}).
40+
Complete()
41+
},
42+
)
43+
if err := ackrtwh.RegisterWebhook(webhook); err != nil {
44+
msg := fmt.Sprintf("cannot register webhook: %v", err)
45+
panic(msg)
46+
}
47+
}
48+
49+
// Assert convertible interface implementation {{ .SourceCRD.Names.Camel }}
50+
var _ ctrlrtconversion.Convertible = &{{ .SourceCRD.Names.Camel }}{}
51+
52+
// ConvertTo converts this {{ .SourceCRD.Kind }} to the Hub version ({{ .HubVersion }}).
53+
func (src *{{ .SourceCRD.Kind }}) ConvertTo(dstRaw ctrlrtconversion.Hub) error {
54+
{{- GoCodeConvert .SourceCRD .DestCRD true $hubImportAlias "src" "dstRaw" 1}}
55+
}
56+
57+
// ConvertFrom converts the Hub version ({{ .HubVersion }}) to this {{ .SourceCRD.Kind }}.
58+
func (dst *{{ .SourceCRD.Kind }}) ConvertFrom(srcRaw ctrlrtconversion.Hub) error {
59+
{{- GoCodeConvert .SourceCRD .DestCRD false $hubImportAlias "dst" "srcRaw" 1}}
60+
}
61+
62+
{{- end }}

0 commit comments

Comments
 (0)