Skip to content

Commit 56ffd73

Browse files
authored
Merge pull request #1 from oauth2-proxy/reference-gen
Add reference generator
2 parents d7654d0 + dca6ebf commit 56ffd73

File tree

7 files changed

+775
-0
lines changed

7 files changed

+775
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"github.com/oauth2-proxy/tools/reference-gen/pkg/generator"
5+
flag "github.com/spf13/pflag"
6+
"k8s.io/klog/v2"
7+
)
8+
9+
var (
10+
packageName = flag.String("package", "", "api directory (or import path), for the package for which references should be generated")
11+
requiredTypes = flag.StringSlice("types", []string{}, "types from the package for which references should be generated")
12+
templateDir = flag.String("template-dir", "", "path to output templates dir, if unset uses default templates")
13+
headerFile = flag.String("header-file", "", "file including header text to prepend to generated data")
14+
outputFile = flag.String("out-file", "", "path to output file to save the result")
15+
)
16+
17+
func main() {
18+
klog.InitFlags(nil)
19+
flag.Set("logtostderr", "true")
20+
flag.Parse()
21+
22+
gen, err := generator.NewGenerator(*packageName, *requiredTypes, *headerFile, *outputFile, *templateDir)
23+
if err != nil {
24+
klog.Fatalf("error constructing generator: %v", err)
25+
}
26+
27+
klog.Infof("Running generator on package %q", *packageName)
28+
if err := gen.Run(); err != nil {
29+
klog.Fatalf("error running generator: %v", err)
30+
}
31+
}

reference-gen/go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/oauth2-proxy/tools/reference-gen
2+
3+
go 1.15
4+
5+
require (
6+
github.com/spf13/pflag v1.0.5
7+
k8s.io/gengo v0.0.0-20201113003025-83324d819ded
8+
k8s.io/klog/v2 v2.4.0
9+
)

reference-gen/go.sum

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
3+
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
4+
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
5+
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
6+
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
7+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
8+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
9+
github.com/oauth2-proxy/tools v0.0.0-20201128195737-d7654d0fc8f3 h1:OxDB4o8RLdDjBerJ0wvzjUPfkyUTTmcUaDDpV98KY1A=
10+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
11+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
12+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
13+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
14+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
15+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
16+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
17+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
18+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
19+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
20+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
21+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
22+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
23+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
24+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
25+
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
26+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
27+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
28+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
29+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
30+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
31+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
32+
k8s.io/gengo v0.0.0-20201113003025-83324d819ded h1:JApXBKYyB7l9xx+DK7/+mFjC7A9Bt5A93FPvFD0HIFE=
33+
k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
34+
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
35+
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
36+
k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
37+
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
38+
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package generator
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"io/ioutil"
8+
"os"
9+
"path/filepath"
10+
"text/template"
11+
12+
"k8s.io/gengo/types"
13+
"k8s.io/klog/v2"
14+
)
15+
16+
const (
17+
generatedTextWarning = "<!--- THIS FILE IS AUTOGENERATED!!! DO NOT EDIT!!! -->\n"
18+
)
19+
20+
type Generator interface {
21+
Run() error
22+
}
23+
24+
func NewGenerator(packageName string, requestedTypesList []string, headerTextFile string, outputFileName string, templateDirectory string) (Generator, error) {
25+
if packageName == "" {
26+
return nil, errors.New("a package name must be specified")
27+
}
28+
29+
headerText, err := loadHeaderText(headerTextFile)
30+
if err != nil {
31+
return nil, fmt.Errorf("error loading header text: %v", err)
32+
}
33+
34+
if err := checkTemplateDir(templateDirectory); err != nil {
35+
return nil, fmt.Errorf("invalid template directory: %v", err)
36+
}
37+
38+
return &generator{
39+
packageName: packageName,
40+
requestedTypes: newStringSet(requestedTypesList),
41+
headerText: headerText,
42+
outputFileName: outputFileName,
43+
templateDirectory: templateDirectory,
44+
}, nil
45+
}
46+
47+
// checkTemplateDir checks whether the template directory given exists and can be read
48+
func checkTemplateDir(dir string) error {
49+
if dir == "" {
50+
return nil
51+
}
52+
path, err := filepath.Abs(dir)
53+
if err != nil {
54+
return err
55+
}
56+
57+
if fi, err := os.Stat(path); err != nil {
58+
return fmt.Errorf("cannot read directory %q: %v", path, err)
59+
} else if !fi.IsDir() {
60+
return fmt.Errorf("path %q is not a directory", path)
61+
}
62+
return nil
63+
}
64+
65+
// loadHeaderText loads the header text from the file if a filename was given
66+
func loadHeaderText(fileName string) ([]byte, error) {
67+
if fileName == "" {
68+
return []byte{}, nil
69+
}
70+
71+
headerText, err := ioutil.ReadFile(fileName)
72+
if err != nil {
73+
return nil, fmt.Errorf("error reading file: %v", err)
74+
}
75+
return headerText, nil
76+
}
77+
78+
type generator struct {
79+
packageName string
80+
requestedTypes stringSet
81+
headerText []byte
82+
outputFileName string
83+
templateDirectory string
84+
}
85+
86+
// Run runs the generation logic for the generator
87+
func (g *generator) Run() error {
88+
typesToRender, err := g.loadTypesAndReferences()
89+
if err != nil {
90+
return fmt.Errorf("unable to load types: %v", err)
91+
}
92+
93+
for typ := range typesToRender {
94+
klog.Infof("Rendering reference for type: %s", typ.Name.Name)
95+
}
96+
97+
if err := g.renderOutput(typesToRender); err != nil {
98+
return fmt.Errorf("error rendering output: %v", err)
99+
}
100+
101+
return nil
102+
}
103+
104+
// loadTypes loads the package in the generator and returns a map
105+
// of types and the types that reference them.
106+
func (g *generator) loadTypesAndReferences() (map[*types.Type][]*types.Type, error) {
107+
pkg, err := loadPackage(g.packageName)
108+
if err != nil {
109+
return nil, fmt.Errorf("could not load package: %v", err)
110+
}
111+
112+
typeReferences := findTypeReferences(pkg.Types)
113+
pkgTypeSet := newTypeSetFromStringMap(pkg.Types)
114+
115+
if !g.requestedTypes.isEmpty() {
116+
typeReferences = filterToRequestedTypes(typeReferences, g.requestedTypes)
117+
}
118+
119+
return filterToPackageTypes(typeReferences, pkgTypeSet), nil
120+
}
121+
122+
func (g *generator) renderOutput(typesToRender map[*types.Type][]*types.Type) error {
123+
typeList := createTypeList(typesToRender)
124+
125+
t, err := g.buildTemplate(typesToRender, typeList)
126+
if err != nil {
127+
return fmt.Errorf("error building template: %v", err)
128+
}
129+
130+
// Create a buffer and render everything into that before writing out
131+
b := bytes.NewBuffer(append(g.headerText, []byte(generatedTextWarning)...))
132+
if err := t.ExecuteTemplate(b, "package", map[string]interface{}{
133+
"types": typeList,
134+
}); err != nil {
135+
return fmt.Errorf("error executing template: %v", err)
136+
}
137+
138+
if err := g.writeOutput(b.Bytes()); err != nil {
139+
return fmt.Errorf("error writing output: %v", err)
140+
}
141+
return nil
142+
}
143+
144+
func (g *generator) buildTemplate(typesToRender map[*types.Type][]*types.Type, typeList []*types.Type) (*template.Template, error) {
145+
knownTypes := newTypeSetFromList(typeList)
146+
t := template.New("").Funcs(map[string]interface{}{
147+
"aliasDisplayName": aliasDisplayNameFunc(knownTypes),
148+
"backtick": backtick,
149+
"dereference": tryDereference,
150+
"fieldEmbedded": fieldEmbedded,
151+
"fieldName": fieldName,
152+
"isOptionalMember": isOptionalMember,
153+
"linkForType": linkForTypeFunc(knownTypes),
154+
"renderCommentsBR": renderCommentsBR,
155+
"renderCommentsLF": renderCommentsLF,
156+
"sortedTypes": sortTypes,
157+
"typeDisplayName": typeDisplayNameFunc(knownTypes),
158+
"typeReferences": typeReferencesFunc(typesToRender, knownTypes),
159+
"visibleTypes": visibleTypes,
160+
})
161+
162+
var err error
163+
t, err = loadTemplatesInto(t, g.templateDirectory)
164+
if err != nil {
165+
return nil, fmt.Errorf("error loading templates: %v", err)
166+
}
167+
168+
return t, nil
169+
}
170+
171+
func (g *generator) writeOutput(content []byte) error {
172+
if g.outputFileName == "" {
173+
_, err := os.Stdout.Write(content)
174+
if err != nil {
175+
return fmt.Errorf("error writing output to stdout: %v", err)
176+
}
177+
klog.Infof("Rendered output written to stdout")
178+
return nil
179+
}
180+
181+
if err := ioutil.WriteFile(g.outputFileName, content, 0600); err != nil {
182+
return fmt.Errorf("could not write file %q: %v", g.outputFileName, err)
183+
}
184+
klog.Infof("Rendered output written to %q", g.outputFileName)
185+
186+
return nil
187+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package generator
2+
3+
import (
4+
"fmt"
5+
6+
"k8s.io/gengo/parser"
7+
"k8s.io/gengo/types"
8+
)
9+
10+
// loadPackage loads and parses the given package.
11+
func loadPackage(packageName string) (*types.Package, error) {
12+
b := parser.New()
13+
// the following may silently fail (turn on -v=4 to see logs)
14+
if err := b.AddDir(packageName); err != nil {
15+
return nil, err
16+
}
17+
18+
universe, err := b.FindTypes()
19+
if err != nil {
20+
return nil, fmt.Errorf("failed to find types for package: %v", err)
21+
}
22+
23+
pkg := universe.Package(packageName)
24+
if pkg == nil {
25+
return nil, fmt.Errorf("package %q was not found by parser", packageName)
26+
}
27+
28+
return pkg, nil
29+
}
30+
31+
// isReferenceRequired determines if the type needs a reference generated.
32+
func isReferenceRequired(t *types.Type, requiredTypes stringSet, allReferences map[*types.Type][]*types.Type) bool {
33+
if requiredTypes.has(t.Name.Name) {
34+
return true
35+
}
36+
for _, reference := range allReferences[t] {
37+
if isReferenceRequired(reference, requiredTypes, allReferences) {
38+
return true
39+
}
40+
}
41+
return false
42+
}
43+
44+
// filterToRequestedTypes filters the type references given to only those that
45+
// are requested or referenced by a requested type.
46+
func filterToRequestedTypes(allTypes map[*types.Type][]*types.Type, requestedTypes stringSet) map[*types.Type][]*types.Type {
47+
filteredTypes := make(map[*types.Type][]*types.Type)
48+
for typ, ref := range allTypes {
49+
if isReferenceRequired(typ, requestedTypes, allTypes) {
50+
filteredTypes[typ] = ref
51+
}
52+
}
53+
54+
return filteredTypes
55+
}
56+
57+
// filterToPackageTypes filters the given references to only those present in the typeSet.
58+
func filterToPackageTypes(allReferences map[*types.Type][]*types.Type, pkgTypes typeSet) map[*types.Type][]*types.Type {
59+
filteredTypes := make(map[*types.Type][]*types.Type)
60+
for typ, refs := range allReferences {
61+
if pkgTypes.has(typ) {
62+
filteredTypes[typ] = refs
63+
}
64+
}
65+
return filteredTypes
66+
}
67+
68+
// findTypeReferences converts a list of types to a map of types and types that
69+
// reference that type.
70+
func findTypeReferences(allTypes map[string]*types.Type) map[*types.Type][]*types.Type {
71+
m := make(map[*types.Type]typeSet)
72+
for _, typ := range allTypes {
73+
// Ensure every type is initialised, if not already
74+
if _, ok := m[typ]; !ok {
75+
m[typ] = make(typeSet)
76+
}
77+
78+
// add this type to other types that it references
79+
for _, member := range typ.Members {
80+
t := member.Type
81+
t = tryDereference(t)
82+
if _, ok := m[t]; !ok {
83+
m[t] = make(typeSet)
84+
}
85+
m[t].add(typ)
86+
}
87+
88+
// Cater for aliases rather than structs
89+
if typ.Underlying != nil {
90+
t := tryDereference(typ.Underlying)
91+
if _, ok := m[t]; !ok {
92+
m[t] = make(typeSet)
93+
}
94+
m[t].add(typ)
95+
}
96+
}
97+
98+
out := make(map[*types.Type][]*types.Type)
99+
for t, s := range m {
100+
out[t] = s.toList()
101+
}
102+
return out
103+
}

0 commit comments

Comments
 (0)