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" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
appsv1apply "k8s.io/client-go/applyconfigurations/apps/v1" appsv1apply "k8s.io/client-go/applyconfigurations/apps/v1"
appsv1autoscaling "k8s.io/client-go/applyconfigurations/autoscaling/v1"
coreinformers "k8s.io/client-go/informers/core/v1" coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1" 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") 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) { func convertSlice(rcList []*v1.ReplicationController) ([]*apps.ReplicaSet, error) {
rsList := make([]*apps.ReplicaSet, 0, len(rcList)) rsList := make([]*apps.ReplicaSet, 0, len(rcList))
for _, rc := range rcList { for _, rc := range rcList {

View File

@ -279,6 +279,12 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &applyconfigurationsautoscalingv1.HorizontalPodAutoscalerSpecApplyConfiguration{} return &applyconfigurationsautoscalingv1.HorizontalPodAutoscalerSpecApplyConfiguration{}
case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerStatus"): case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerStatus"):
return &applyconfigurationsautoscalingv1.HorizontalPodAutoscalerStatusApplyConfiguration{} 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 // Group=autoscaling, Version=v2beta1
case v2beta1.SchemeGroupVersion.WithKind("ContainerResourceMetricSource"): 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) pkgTypes := packageTypesForInputDirs(context, arguments.InputDirs, arguments.OutputPackagePath)
customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs) customArgs := arguments.CustomArgs.(*applygenargs.CustomArgs)
initialTypes := customArgs.ExternalApplyConfigurations initialTypes := customArgs.ExternalApplyConfigurations
refs := refGraphForReachableTypes(pkgTypes, initialTypes) refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes)
typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes) typeModels, err := newTypeModels(customArgs.OpenAPISchemaFilePath, pkgTypes)
if err != nil { if err != nil {
klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err) klog.Fatalf("Failed build type models from typeModels %s: %v", customArgs.OpenAPISchemaFilePath, err)
} }
groupVersions := make(map[string]clientgentypes.GroupVersions) groupVersions := make(map[string]clientgentypes.GroupVersions)
groupGoNames := make(map[string]string) groupGoNames := make(map[string]string)
applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig) 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 // refGraphForReachableTypes returns a refGraph that contains all reachable types from
// the root clientgen types of the provided packages. // 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 var refs refGraph = initialTypes
// Include only types that are reachable from the root clientgen types. // 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 { if tags.GenerateClient && hasApply {
findReachableTypes(t, reachableTypes) 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 { 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 template string
args map[string]interface{} args map[string]interface{}
} }
_, typeGVString := util.ParsePathGroupVersion(g.inputPackage)
extendedMethods := []extendedInterfaceMethod{} extendedMethods := []extendedInterfaceMethod{}
for _, e := range tags.Extensions { for _, e := range tags.Extensions {
if e.HasVerb("apply") && !generateApply {
continue
}
inputType := *t inputType := *t
resultType := *t resultType := *t
inputGVString := typeGVString
// TODO: Extract this to some helper method as this code is copied into // TODO: Extract this to some helper method as this code is copied into
// 2 other places. // 2 other places.
if len(e.InputTypeOverride) > 0 { if len(e.InputTypeOverride) > 0 {
if name, pkg := e.Input(); len(pkg) > 0 { if name, pkg := e.Input(); len(pkg) > 0 {
_, inputGVString = util.ParsePathGroupVersion(pkg)
newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) newType := c.Universe.Type(types.Name{Package: pkg, Name: name})
inputType = *newType inputType = *newType
} else { } else {
@ -118,7 +124,7 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
} else { } else {
updatedVerbtemplate = e.VerbName + "(" + strings.TrimPrefix(defaultVerbTemplates[e.VerbType], strings.Title(e.VerbType)+"(") updatedVerbtemplate = e.VerbName + "(" + strings.TrimPrefix(defaultVerbTemplates[e.VerbType], strings.Title(e.VerbType)+"(")
} }
extendedMethods = append(extendedMethods, extendedInterfaceMethod{ extendedMethod := extendedInterfaceMethod{
template: updatedVerbtemplate, template: updatedVerbtemplate,
args: map[string]interface{}{ args: map[string]interface{}{
"type": t, "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"}), "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"}), "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"}), "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"}), "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{}{ m := map[string]interface{}{
"type": t, "type": t,
@ -237,14 +249,18 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
if tags.HasVerb("applyStatus") && generateApply { if tags.HasVerb("applyStatus") && generateApply {
sw.Do(applyStatusTemplate, m) sw.Do(applyStatusTemplate, m)
} }
// TODO: Add subresource support once apply subresources are supported on the server side
// generate expansion methods // generate expansion methods
for _, e := range tags.Extensions { for _, e := range tags.Extensions {
if e.HasVerb("apply") && !generateApply {
continue
}
inputType := *t inputType := *t
resultType := *t resultType := *t
inputGVString := typeGVString
if len(e.InputTypeOverride) > 0 { if len(e.InputTypeOverride) > 0 {
if name, pkg := e.Input(); len(pkg) > 0 { if name, pkg := e.Input(); len(pkg) > 0 {
_, inputGVString = util.ParsePathGroupVersion(pkg)
newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) newType := c.Universe.Type(types.Name{Package: pkg, Name: name})
inputType = *newType inputType = *newType
} else { } else {
@ -262,6 +278,9 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
m["inputType"] = &inputType m["inputType"] = &inputType
m["resultType"] = &resultType m["resultType"] = &resultType
m["subresourcePath"] = e.SubResourcePath 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.HasVerb("get") {
if e.IsSubresource() { 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) sw.Do(adjustTemplate(e.VerbName, e.VerbType, patchTemplate), m)
} }
if e.HasVerb("apply") && generateApply { if e.HasVerb("apply") {
// TODO: Support apply on arbitrary subresource once it is supported by the api-server. if e.IsSubresource() {
sw.Do(adjustTemplate(e.VerbName, e.VerbType, applyTemplate), m) 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)`, "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)`, "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 return m
} }
@ -707,3 +731,30 @@ func (c *$.type|privatePlural$) ApplyStatus(ctx context.Context, $.inputType|pri
return 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{ var inputTypeSupportedVerbs = []string{
"create", "create",
"update", "update",
"apply",
} }
// resultTypeSupportedVerbs is a list of verb types that supports overriding the // resultTypeSupportedVerbs is a list of verb types that supports overriding the
@ -84,6 +85,7 @@ var resultTypeSupportedVerbs = []string{
"get", "get",
"list", "list",
"patch", "patch",
"apply",
} }
// Extensions allows to extend the default set of client verbs // Extensions allows to extend the default set of client verbs