From 29423501f068d98d3e9addb9d56dd8fcf6ce4075 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 5 Mar 2021 08:09:32 -0800 Subject: [PATCH] Add apply subresource support to client-go's typed client --- pkg/controller/replication/conversion.go | 5 ++ .../client-go/applyconfigurations/utils.go | 6 ++ .../generators/packages.go | 3 +- .../generators/refgraph.go | 16 ++++- .../generators/generator_for_type.go | 65 +++++++++++++++++-- .../cmd/client-gen/generators/util/tags.go | 2 + 6 files changed, 88 insertions(+), 9 deletions(-) diff --git a/pkg/controller/replication/conversion.go b/pkg/controller/replication/conversion.go index 161923aff1b..653b41c0985 100644 --- a/pkg/controller/replication/conversion.go +++ b/pkg/controller/replication/conversion.go @@ -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 { diff --git a/staging/src/k8s.io/client-go/applyconfigurations/utils.go b/staging/src/k8s.io/client-go/applyconfigurations/utils.go index b116024fa13..9c55fbc0c0e 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/utils.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/utils.go @@ -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"): diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go index 409419431bf..53bea62fa56 100644 --- a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/packages.go @@ -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) diff --git a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go index cae4fce7dbf..2dc004f1df4 100644 --- a/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go +++ b/staging/src/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go @@ -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 { diff --git a/staging/src/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go b/staging/src/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go index 37dc4a36557..bd9bd299a8e 100644 --- a/staging/src/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go +++ b/staging/src/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go @@ -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 +} +` diff --git a/staging/src/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go b/staging/src/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go index 71346061f0c..b5736c40db5 100644 --- a/staging/src/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go +++ b/staging/src/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go @@ -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