Skip to content

Commit c447c38

Browse files
committed
Generate type fake clients that may opt in to the use of managed fields
1 parent 75d6f02 commit c447c38

File tree

9 files changed

+170
-97
lines changed

9 files changed

+170
-97
lines changed

staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,16 @@ import (
2323
"strings"
2424

2525
openapiv2 "github.com/google/gnostic-models/openapiv2"
26+
27+
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
2628
"k8s.io/gengo/v2/types"
2729
utilproto "k8s.io/kube-openapi/pkg/util/proto"
2830
"k8s.io/kube-openapi/pkg/validation/spec"
2931
)
3032

3133
type typeModels struct {
3234
models utilproto.Models
33-
gvkToOpenAPIType map[gvk]string
34-
}
35-
36-
type gvk struct {
37-
group, version, kind string
35+
gvkToOpenAPIType map[clientgentypes.GroupVersionKind]string
3836
}
3937

4038
func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Package) (*typeModels, error) {
@@ -56,7 +54,7 @@ func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Pack
5654

5755
// Build a mapping from openAPI type name to GVK.
5856
// Find the root types needed by by client-go for apply.
59-
gvkToOpenAPIType := map[gvk]string{}
57+
gvkToOpenAPIType := map[clientgentypes.GroupVersionKind]string{}
6058
rootDefs := map[string]spec.Schema{}
6159
for _, p := range pkgTypes {
6260
gv := groupVersion(p)
@@ -65,11 +63,7 @@ func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Pack
6563
hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus")
6664
if tags.GenerateClient && hasApply {
6765
openAPIType := friendlyName(typeName(t))
68-
gvk := gvk{
69-
group: gv.Group.String(),
70-
version: gv.Version.String(),
71-
kind: t.Name.Name,
72-
}
66+
gvk := gv.WithKind(clientgentypes.Kind(t.Name.Name))
7367
rootDefs[openAPIType] = openAPISchema.Definitions[openAPIType]
7468
gvkToOpenAPIType[gvk] = openAPIType
7569
}
@@ -94,7 +88,7 @@ func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Pack
9488

9589
var emptyModels = &typeModels{
9690
models: &utilproto.Definitions{},
97-
gvkToOpenAPIType: map[gvk]string{},
91+
gvkToOpenAPIType: map[clientgentypes.GroupVersionKind]string{},
9892
}
9993

10094
func toValidatedModels(openAPISchema *spec.Swagger) (utilproto.Models, error) {

staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/targets.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,14 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
8686
klog.V(5).Infof("skipping type %v because does not have ObjectMeta", t)
8787
continue
8888
}
89+
gvk := gv.WithKind(clientgentypes.Kind(t.Name.Name))
90+
openAPIName := typeModels.gvkToOpenAPIType[gvk]
91+
8992
if typePkg, ok := refs[t.Name]; ok {
9093
toGenerate = append(toGenerate, applyConfig{
9194
Type: t,
9295
ApplyConfiguration: types.Ref(typePkg, t.Name.Name+ApplyConfigurationTypeSuffix),
96+
OpenAPIName: openAPIName,
9397
})
9498
}
9599
}
@@ -132,7 +136,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
132136
// generate ForKind() utility function
133137
targetList = append(targetList,
134138
targetForUtils(args.OutputDir, args.OutputPkg,
135-
boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames))
139+
boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames, typeModels))
136140
// generate internal embedded schema, required for generated Extract functions
137141
targetList = append(targetList,
138142
targetForInternal(args.OutputDir, args.OutputPkg,
@@ -171,11 +175,7 @@ func targetForApplyConfigurationsPackage(outputDirBase, outputPkgBase, pkgSubdir
171175
GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) {
172176
for _, toGenerate := range typesToGenerate {
173177
var openAPIType *string
174-
gvk := gvk{
175-
group: gv.Group.String(),
176-
version: gv.Version.String(),
177-
kind: toGenerate.Type.Name.Name,
178-
}
178+
gvk := gv.WithKind(clientgentypes.Kind(toGenerate.Type.Name.Name))
179179
if v, ok := models.gvkToOpenAPIType[gvk]; ok {
180180
openAPIType = &v
181181
}
@@ -198,7 +198,8 @@ func targetForApplyConfigurationsPackage(outputDirBase, outputPkgBase, pkgSubdir
198198
}
199199
}
200200

201-
func targetForUtils(outputDirBase, outputPkgBase string, boilerplate []byte, groupVersions map[string]clientgentypes.GroupVersions, applyConfigsForGroupVersion map[clientgentypes.GroupVersion][]applyConfig, groupGoNames map[string]string) generator.Target {
201+
func targetForUtils(outputDirBase, outputPkgBase string, boilerplate []byte, groupVersions map[string]clientgentypes.GroupVersions,
202+
applyConfigsForGroupVersion map[clientgentypes.GroupVersion][]applyConfig, groupGoNames map[string]string, models *typeModels) generator.Target {
202203
return &generator.SimpleTarget{
203204
PkgName: path.Base(outputPkgBase),
204205
PkgPath: outputPkgBase,
@@ -214,6 +215,7 @@ func targetForUtils(outputDirBase, outputPkgBase string, boilerplate []byte, gro
214215
groupVersions: groupVersions,
215216
typesForGroupVersion: applyConfigsForGroupVersion,
216217
groupGoNames: groupGoNames,
218+
typeModels: models,
217219
})
218220
return generators
219221
},

staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ package generators
1919
import "k8s.io/gengo/v2/types"
2020

2121
var (
22-
applyConfiguration = types.Ref("k8s.io/apimachinery/pkg/runtime", "ApplyConfiguration")
23-
groupVersionKind = types.Ref("k8s.io/apimachinery/pkg/runtime/schema", "GroupVersionKind")
24-
typeMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "TypeMeta")
25-
objectMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "ObjectMeta")
26-
rawExtension = types.Ref("k8s.io/apimachinery/pkg/runtime", "RawExtension")
27-
unknown = types.Ref("k8s.io/apimachinery/pkg/runtime", "Unknown")
28-
extractInto = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "ExtractInto")
29-
smdNewParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "NewParser")
30-
smdParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "Parser")
31-
yamlObject = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "YAMLObject")
32-
yamlUnmarshal = types.Ref("gopkg.in/yaml.v2", "Unmarshal")
22+
applyConfiguration = types.Ref("k8s.io/apimachinery/pkg/runtime", "ApplyConfiguration")
23+
groupVersionKind = types.Ref("k8s.io/apimachinery/pkg/runtime/schema", "GroupVersionKind")
24+
typeMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "TypeMeta")
25+
objectMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "ObjectMeta")
26+
rawExtension = types.Ref("k8s.io/apimachinery/pkg/runtime", "RawExtension")
27+
unknown = types.Ref("k8s.io/apimachinery/pkg/runtime", "Unknown")
28+
extractInto = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "ExtractInto")
29+
runtimeScheme = types.Ref("k8s.io/apimachinery/pkg/runtime", "Scheme")
30+
smdNewParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "NewParser")
31+
smdParser = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "Parser")
32+
testingTypeConverter = types.Ref("k8s.io/client-go/testing", "TypeConverter")
33+
yamlObject = types.Ref("sigs.k8s.io/structured-merge-diff/v4/typed", "YAMLObject")
34+
yamlUnmarshal = types.Ref("gopkg.in/yaml.v2", "Unmarshal")
3335
)

staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/util.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package generators
1818

1919
import (
2020
"io"
21+
"path"
2122
"sort"
2223
"strings"
2324

@@ -37,6 +38,7 @@ type utilGenerator struct {
3738
groupGoNames map[string]string
3839
typesForGroupVersion map[clientgentypes.GroupVersion][]applyConfig
3940
filtered bool
41+
typeModels *typeModels
4042
}
4143

4244
var _ generator.Generator = &utilGenerator{}
@@ -92,6 +94,7 @@ func (v versionSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
9294
type applyConfig struct {
9395
Type *types.Type
9496
ApplyConfiguration *types.Type
97+
OpenAPIName string
9598
}
9699

97100
type applyConfigSort []applyConfig
@@ -133,16 +136,26 @@ func (g *utilGenerator) GenerateType(c *generator.Context, _ *types.Type, w io.W
133136
sort.Sort(groupSort(groups))
134137

135138
m := map[string]interface{}{
139+
"applyConfiguration": applyConfiguration,
136140
"groups": groups,
141+
"internalParser": types.Ref(path.Join(g.outputPackage, "internal"), "Parser"),
142+
"runtimeScheme": runtimeScheme,
137143
"schemeGVs": schemeGVs,
138144
"schemaGroupVersionKind": groupVersionKind,
139-
"applyConfiguration": applyConfiguration,
145+
"testingTypeConverter": testingTypeConverter,
140146
}
141147
sw.Do(forKindFunc, m)
148+
sw.Do(typeConverter, m)
142149

143150
return sw.Error()
144151
}
145152

153+
var typeConverter = `
154+
func NewTypeConverter(scheme *{{.runtimeScheme|raw}}) *{{.testingTypeConverter|raw}} {
155+
return &{{.testingTypeConverter|raw}}{Scheme: scheme, TypeResolver: {{.internalParser|raw}}()}
156+
}
157+
`
158+
146159
var forKindFunc = `
147160
// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no
148161
// apply configuration type exists for the given GroupVersionKind.

staging/src/k8s.io/code-generator/cmd/client-gen/generators/client_generator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
405405
targetForScheme(args, clientsetDir, clientsetPkg, groupGoNames, boilerplate))
406406
if args.FakeClient {
407407
targetList = append(targetList,
408-
fake.TargetForClientset(args, clientsetDir, clientsetPkg, groupGoNames, boilerplate))
408+
fake.TargetForClientset(args, clientsetDir, clientsetPkg, args.ApplyConfigurationPackage, groupGoNames, boilerplate))
409409
}
410410

411411
// If --clientset-only=true, we don't regenerate the individual typed clients.

staging/src/k8s.io/code-generator/cmd/client-gen/generators/fake/fake_client_generator.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TargetForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, clie
8888
}
8989
}
9090

91-
func TargetForClientset(args *args.Args, clientsetDir, clientsetPkg string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Target {
91+
func TargetForClientset(args *args.Args, clientsetDir, clientsetPkg string, applyConfigurationPkg string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Target {
9292
return &generator.SimpleTarget{
9393
// TODO: we'll generate fake clientset for different release in the future.
9494
// Package name and path are hard coded for now.
@@ -108,11 +108,12 @@ func TargetForClientset(args *args.Args, clientsetDir, clientsetPkg string, grou
108108
GoGenerator: generator.GoGenerator{
109109
OutputFilename: "clientset_generated.go",
110110
},
111-
groups: args.Groups,
112-
groupGoNames: groupGoNames,
113-
fakeClientsetPackage: clientsetPkg,
114-
imports: generator.NewImportTracker(),
115-
realClientsetPackage: clientsetPkg,
111+
groups: args.Groups,
112+
groupGoNames: groupGoNames,
113+
fakeClientsetPackage: clientsetPkg,
114+
imports: generator.NewImportTracker(),
115+
realClientsetPackage: clientsetPkg,
116+
applyConfigurationPackage: applyConfigurationPkg,
116117
},
117118
&scheme.GenScheme{
118119
GoGenerator: generator.GoGenerator{

staging/src/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ type genClientset struct {
3737
imports namer.ImportTracker
3838
clientsetGenerated bool
3939
// the import path of the generated real clientset.
40-
realClientsetPackage string // must be a Go import-path
40+
realClientsetPackage string // must be a Go import-path
41+
applyConfigurationPackage string
4142
}
4243

4344
var _ generator.Generator = &genClientset{}
@@ -76,19 +77,29 @@ func (g *genClientset) Imports(c *generator.Context) (imports []string) {
7677
"fakediscovery \"k8s.io/client-go/discovery/fake\"",
7778
"k8s.io/apimachinery/pkg/runtime",
7879
"k8s.io/apimachinery/pkg/watch",
80+
"k8s.io/apimachinery/pkg/api/meta/testrestmapper",
7981
)
8082

8183
return
8284
}
8385

8486
func (g *genClientset) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
87+
generateApply := len(g.applyConfigurationPackage) > 0
88+
8589
// TODO: We actually don't need any type information to generate the clientset,
8690
// perhaps we can adapt the go2ild framework to this kind of usage.
8791
sw := generator.NewSnippetWriter(w, c, "$", "$")
8892

8993
allGroups := clientgentypes.ToGroupVersionInfo(g.groups, g.groupGoNames)
9094

9195
sw.Do(common, nil)
96+
97+
if generateApply {
98+
sw.Do(managedFieldsClientset, map[string]any{
99+
"newTypeConverter": types.Ref(g.applyConfigurationPackage, "NewTypeConverter"),
100+
})
101+
}
102+
92103
sw.Do(checkImpl, nil)
93104

94105
for _, group := range allGroups {
@@ -107,11 +118,50 @@ func (g *genClientset) GenerateType(c *generator.Context, t *types.Type, w io.Wr
107118
}
108119

109120
// This part of code is version-independent, unchanging.
121+
122+
var managedFieldsClientset = `
123+
// NewClientset returns a clientset that will respond with the provided objects.
124+
// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
125+
// without applying any validations and/or defaults. It shouldn't be considered a replacement
126+
// for a real clientset and is mostly useful in simple unit tests.
127+
func NewClientset(objects ...runtime.Object) *Clientset {
128+
o := testing.NewFieldManagedObjectTracker(
129+
scheme,
130+
codecs.UniversalDecoder(),
131+
$.newTypeConverter|raw$(scheme),
132+
)
133+
for _, obj := range objects {
134+
if err := o.Add(obj); err != nil {
135+
panic(err)
136+
}
137+
}
138+
139+
cs := &Clientset{tracker: o}
140+
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
141+
cs.AddReactor("*", "*", testing.ObjectReaction(o))
142+
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
143+
gvr := action.GetResource()
144+
ns := action.GetNamespace()
145+
watch, err := o.Watch(gvr, ns)
146+
if err != nil {
147+
return false, nil, err
148+
}
149+
return true, watch, nil
150+
})
151+
152+
return cs
153+
}
154+
`
155+
110156
var common = `
111157
// NewSimpleClientset returns a clientset that will respond with the provided objects.
112158
// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
113-
// without applying any validations and/or defaults. It shouldn't be considered a replacement
159+
// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement
114160
// for a real clientset and is mostly useful in simple unit tests.
161+
//
162+
// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves
163+
// server side apply testing. NewClientset is only available when apply configurations are generated (e.g.
164+
// via --with-applyconfig).
115165
func NewSimpleClientset(objects ...runtime.Object) *Clientset {
116166
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
117167
for _, obj := range objects {

0 commit comments

Comments
 (0)