Add apply subresource support to client-go's typed client

This commit is contained in:
Joe Betz 2021-03-05 08:09:32 -08:00
parent f137c47770
commit 29423501f0
6 changed files with 88 additions and 9 deletions

View File

@ -37,6 +37,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
appsv1apply "k8s.io/client-go/applyconfigurations/apps/v1"
appsv1autoscaling "k8s.io/client-go/applyconfigurations/autoscaling/v1"
coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes"
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
@ -266,6 +267,10 @@ func (c conversionClient) UpdateScale(ctx context.Context, name string, scale *a
return nil, errors.New("UpdateScale() is not implemented for conversionClient")
}
func (c conversionClient) ApplyScale(ctx context.Context, name string, scale *appsv1autoscaling.ScaleApplyConfiguration, opts metav1.ApplyOptions) (*autoscalingv1.Scale, error) {
return nil, errors.New("ApplyScale() is not implemented for conversionClient")
}
func convertSlice(rcList []*v1.ReplicationController) ([]*apps.ReplicaSet, error) {
rsList := make([]*apps.ReplicaSet, 0, len(rcList))
for _, rc := range rcList {

View File

@ -279,6 +279,12 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &applyconfigurationsautoscalingv1.HorizontalPodAutoscalerSpecApplyConfiguration{}
case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerStatus"):
return &applyconfigurationsautoscalingv1.HorizontalPodAutoscalerStatusApplyConfiguration{}
case autoscalingv1.SchemeGroupVersion.WithKind("Scale"):
return &applyconfigurationsautoscalingv1.ScaleApplyConfiguration{}
case autoscalingv1.SchemeGroupVersion.WithKind("ScaleSpec"):
return &applyconfigurationsautoscalingv1.ScaleSpecApplyConfiguration{}
case autoscalingv1.SchemeGroupVersion.WithKind("ScaleStatus"):
return &applyconfigurationsautoscalingv1.ScaleStatusApplyConfiguration{}
// Group=autoscaling, Version=v2beta1
case v2beta1.SchemeGroupVersion.WithKind("ContainerResourceMetricSource"):

View File

@ -63,11 +63,12 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath)
customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs)
initialTypes := customArgs.ExternalApplyConfigurations
refs := refGraphForReachableTypes(pkgTypes, initialTypes)
refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes)
typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes)
if err != nil {
klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err)
}
groupVersions := make(map[string]clientgentypes.GroupVersions)
groupGoNames := make(map[string]string)
applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig)

View File

@ -28,7 +28,7 @@ type refGraph map[types.Name]string
// refGraphForReachableTypes returns a refGraph that contains all reachable types from
// the root clientgen types of the provided packages.
func refGraphForReachableTypes(pkgTypes map[string]*types.Package, initialTypes map[types.Name]string) refGraph {
func refGraphForReachableTypes(universe types.Universe, pkgTypes map[string]*types.Package, initialTypes map[types.Name]string) refGraph {
var refs refGraph = initialTypes
// Include only types that are reachable from the root clientgen types.
@ -42,6 +42,20 @@ func refGraphForReachableTypes(pkgTypes map[string]*types.Package, initialTypes
if tags.GenerateClient && hasApply {
findReachableTypes(t, reachableTypes)
}
// If any apply extensions have custom inputs, add them.
for _, extension := range tags.Extensions {
if extension.HasVerb("apply") {
if len(extension.InputTypeOverride) > 0 {
inputType := *t
if name, pkg := extension.Input(); len(pkg) > 0 {
inputType = *(universe.Type(types.Name{Package: pkg, Name: name}))
} else {
inputType.Name.Name = extension.InputTypeOverride
}
findReachableTypes(&inputType, reachableTypes)
}
}
}
}
}
for pkg, p := range pkgTypes {

View File

@ -90,14 +90,20 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
template string
args map[string]interface{}
}
_, typeGVString := util.ParsePathGroupVersion(g.inputPackage)
extendedMethods := []extendedInterfaceMethod{}
for _, e := range tags.Extensions {
if e.HasVerb("apply") && !generateApply {
continue
}
inputType := *t
resultType := *t
inputGVString := typeGVString
// TODO: Extract this to some helper method as this code is copied into
// 2 other places.
if len(e.InputTypeOverride) > 0 {
if name, pkg := e.Input(); len(pkg) > 0 {
_, inputGVString = util.ParsePathGroupVersion(pkg)
newType := c.Universe.Type(types.Name{Package: pkg, Name: name})
inputType = *newType
} else {
@ -118,7 +124,7 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
} else {
updatedVerbtemplate = e.VerbName + "(" + strings.TrimPrefix(defaultVerbTemplates[e.VerbType], strings.Title(e.VerbType)+"(")
}
extendedMethods = append(extendedMethods, extendedInterfaceMethod{
extendedMethod := extendedInterfaceMethod{
template: updatedVerbtemplate,
args: map[string]interface{}{
"type": t,
@ -128,9 +134,15 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
"GetOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}),
"ListOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}),
"UpdateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "UpdateOptions"}),
"ApplyOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ApplyOptions"}),
"PatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}),
"jsonMarshal": c.Universe.Type(types.Name{Package: "encoding/json", Name: "Marshal"}),
},
})
}
if e.HasVerb("apply") {
extendedMethod.args["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, inputGVString), inputType.Name.Name+"ApplyConfiguration")
}
extendedMethods = append(extendedMethods, extendedMethod)
}
m := map[string]interface{}{
"type": t,
@ -237,14 +249,18 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
if tags.HasVerb("applyStatus") && generateApply {
sw.Do(applyStatusTemplate, m)
}
// TODO: Add subresource support once apply subresources are supported on the server side
// generate expansion methods
for _, e := range tags.Extensions {
if e.HasVerb("apply") && !generateApply {
continue
}
inputType := *t
resultType := *t
inputGVString := typeGVString
if len(e.InputTypeOverride) > 0 {
if name, pkg := e.Input(); len(pkg) > 0 {
_, inputGVString = util.ParsePathGroupVersion(pkg)
newType := c.Universe.Type(types.Name{Package: pkg, Name: name})
inputType = *newType
} else {
@ -262,6 +278,9 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
m["inputType"] = &inputType
m["resultType"] = &resultType
m["subresourcePath"] = e.SubResourcePath
if e.HasVerb("apply") {
m["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, inputGVString), inputType.Name.Name+"ApplyConfiguration")
}
if e.HasVerb("get") {
if e.IsSubresource() {
@ -310,9 +329,12 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
sw.Do(adjustTemplate(e.VerbName, e.VerbType, patchTemplate), m)
}
if e.HasVerb("apply") && generateApply {
// TODO: Support apply on arbitrary subresource once it is supported by the api-server.
sw.Do(adjustTemplate(e.VerbName, e.VerbType, applyTemplate), m)
if e.HasVerb("apply") {
if e.IsSubresource() {
sw.Do(adjustTemplate(e.VerbName, e.VerbType, applySubresourceTemplate), m)
} else {
sw.Do(adjustTemplate(e.VerbName, e.VerbType, applyTemplate), m)
}
}
}
@ -344,7 +366,9 @@ func buildSubresourceDefaultVerbTemplates(generateApply bool) map[string]string
"update": `Update(ctx context.Context, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (*$.resultType|raw$, error)`,
"get": `Get(ctx context.Context, $.type|private$Name string, options $.GetOptions|raw$) (*$.resultType|raw$, error)`,
}
// TODO: Support apply on arbitrary subresource once it is supported by the api-server.
if generateApply {
m["apply"] = `Apply(ctx context.Context, $.type|private$Name string, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (*$.resultType|raw$, error)`
}
return m
}
@ -707,3 +731,30 @@ func (c *$.type|privatePlural$) ApplyStatus(ctx context.Context, $.inputType|pri
return
}
`
var applySubresourceTemplate = `
// Apply takes top resource name and the apply declarative configuration for $.subresourcePath$,
// applies it and returns the applied $.resultType|private$, and an error, if there is any.
func (c *$.type|privatePlural$) Apply(ctx context.Context, $.type|private$Name string, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error) {
if $.inputType|private$ == nil {
return nil, fmt.Errorf("$.inputType|private$ provided to Apply must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := $.jsonMarshal|raw$($.inputType|private$)
if err != nil {
return nil, err
}
result = &$.resultType|raw${}
err = c.client.Patch($.ApplyPatchType|raw$).
$if .namespaced$Namespace(c.ns).$end$
Resource("$.type|resource$").
Name($.type|private$Name).
SubResource("$.subresourcePath$").
VersionedParams(&patchOpts, $.schemeParameterCodec|raw$).
Body(data).
Do(ctx).
Into(result)
return
}
`

View File

@ -74,6 +74,7 @@ var unsupportedExtensionVerbs = []string{
var inputTypeSupportedVerbs = []string{
"create",
"update",
"apply",
}
// resultTypeSupportedVerbs is a list of verb types that supports overriding the
@ -84,6 +85,7 @@ var resultTypeSupportedVerbs = []string{
"get",
"list",
"patch",
"apply",
}
// Extensions allows to extend the default set of client verbs