unify tag syntax for genclient tags and add onlyVerbs and skipVerbs

This commit is contained in:
Michal Fojtik 2017-07-18 13:48:21 +02:00
parent 84db0a9487
commit d7e3029d7a
No known key found for this signature in database
GPG Key ID: 172B61E538AAC0EE
22 changed files with 529 additions and 239 deletions

View File

@ -27,6 +27,7 @@ go_library(
"//vendor/k8s.io/kube-gen/cmd/client-gen/args:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/fake:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/scheme:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/util:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/path:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/types:go_default_library",
],

View File

@ -29,6 +29,7 @@ import (
clientgenargs "k8s.io/kube-gen/cmd/client-gen/args"
"k8s.io/kube-gen/cmd/client-gen/generators/fake"
"k8s.io/kube-gen/cmd/client-gen/generators/scheme"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
"k8s.io/kube-gen/cmd/client-gen/path"
clientgentypes "k8s.io/kube-gen/cmd/client-gen/types"
@ -169,7 +170,7 @@ func packageForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, cli
return generators
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
return extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == true
return util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient
},
}
}
@ -342,7 +343,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
} else {
// User has not specified any override for this group version.
// filter out types which dont have genclient=true.
if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false {
if tags := util.MustParseClientGenTags(t.SecondClosestCommentLines); !tags.GenerateClient {
continue
}
}

View File

@ -17,12 +17,12 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/gengo/generator:go_default_library",
"//vendor/k8s.io/gengo/namer:go_default_library",
"//vendor/k8s.io/gengo/types:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/args:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/scheme:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/util:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/path:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/types:go_default_library",
],

View File

@ -20,12 +20,12 @@ import (
"path/filepath"
"strings"
"github.com/golang/glog"
"k8s.io/gengo/generator"
"k8s.io/gengo/types"
clientgenargs "k8s.io/kube-gen/cmd/client-gen/args"
scheme "k8s.io/kube-gen/cmd/client-gen/generators/scheme"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
clientgentypes "k8s.io/kube-gen/cmd/client-gen/types"
)
@ -78,19 +78,11 @@ func PackageForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, cli
return generators
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
return extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == true
return util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient
},
}
}
func extractBoolTagOrDie(key string, lines []string) bool {
val, err := types.ExtractSingleBoolCommentTag("+", key, false, lines)
if err != nil {
glog.Fatalf(err.Error())
}
return val
}
func PackageForClientset(customArgs clientgenargs.Args, fakeClientsetPackage string, boilerplate []byte, generatedBy string) generator.Package {
return &generator.DefaultPackage{
// TODO: we'll generate fake clientset for different release in the future.

View File

@ -25,6 +25,8 @@ import (
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
)
// genFakeForGroup produces a file for a group client, e.g. ExtensionsClient for the extension group.
@ -70,18 +72,20 @@ func (g *genFakeForGroup) GenerateType(c *generator.Context, t *types.Type, w io
sw.Do(groupClientTemplate, m)
for _, t := range g.types {
tags, err := util.ParseClientGenTags(t.SecondClosestCommentLines)
if err != nil {
return err
}
wrapper := map[string]interface{}{
"type": t,
"GroupVersion": namer.IC(g.group) + namer.IC(g.version),
"realClientPackage": strings.ToLower(filepath.Base(g.realClientPackage)),
}
namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines)
if namespaced {
sw.Do(getterImplNamespaced, wrapper)
} else {
if tags.NonNamespaced {
sw.Do(getterImplNonNamespaced, wrapper)
continue
}
sw.Do(getterImplNamespaced, wrapper)
}
sw.Do(getRESTClient, m)
return sw.Error()

View File

@ -17,13 +17,14 @@ limitations under the License.
package fake
import (
"fmt"
"io"
"path/filepath"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
"k8s.io/kube-gen/cmd/client-gen/path"
)
@ -66,12 +67,8 @@ func genStatus(t *types.Type) bool {
}
}
// Allow overriding via a comment on the type
genStatus, err := types.ExtractSingleBoolCommentTag("+", "genclientstatus", hasStatus, t.SecondClosestCommentLines)
if err != nil {
fmt.Printf("error looking up +genclientstatus: %v\n", err)
}
return genStatus
tags := util.MustParseClientGenTags(t.SecondClosestCommentLines)
return hasStatus && !tags.NoStatus
}
// hasObjectMeta returns true if the type has a ObjectMeta field.
@ -88,7 +85,10 @@ func hasObjectMeta(t *types.Type) bool {
func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
pkg := filepath.Base(t.Name.Package)
namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines)
tags, err := util.ParseClientGenTags(t.SecondClosestCommentLines)
if err != nil {
return err
}
canonicalGroup := g.group
if canonicalGroup == "core" {
canonicalGroup = ""
@ -110,7 +110,7 @@ func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io.
"type": t,
"package": pkg,
"Package": namer.IC(pkg),
"namespaced": namespaced,
"namespaced": !tags.NonNamespaced,
"Group": namer.IC(g.group),
"GroupVersion": namer.IC(g.group) + namer.IC(g.version),
"group": canonicalGroup,
@ -148,43 +148,48 @@ func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io.
"ExtractFromListOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "ExtractFromListOptions"}),
}
noMethods := extractBoolTagOrDie("noMethods", t.SecondClosestCommentLines) == true
readonly := extractBoolTagOrDie("readonly", t.SecondClosestCommentLines) == true
if namespaced {
sw.Do(structNamespaced, m)
} else {
if tags.NonNamespaced {
sw.Do(structNonNamespaced, m)
} else {
sw.Do(structNamespaced, m)
}
if !noMethods {
sw.Do(resource, m)
sw.Do(kind, m)
if tags.NoVerbs {
return sw.Error()
}
sw.Do(resource, m)
sw.Do(kind, m)
if !noMethods && !readonly {
sw.Do(createTemplate, m)
sw.Do(updateTemplate, m)
// Generate the UpdateStatus method if the type has a status
if genStatus(t) {
sw.Do(updateStatusTemplate, m)
}
sw.Do(deleteTemplate, m)
sw.Do(deleteCollectionTemplate, m)
}
if !noMethods {
if tags.HasVerb("get") {
sw.Do(getTemplate, m)
}
if tags.HasVerb("list") {
if hasObjectMeta(t) {
sw.Do(listUsingOptionsTemplate, m)
} else {
sw.Do(listTemplate, m)
}
}
if tags.HasVerb("watch") {
sw.Do(watchTemplate, m)
}
if !noMethods && !readonly {
if tags.HasVerb("create") {
sw.Do(createTemplate, m)
}
if tags.HasVerb("update") {
sw.Do(updateTemplate, m)
}
if tags.HasVerb("updateStatus") && genStatus(t) {
sw.Do(updateStatusTemplate, m)
}
if tags.HasVerb("delete") {
sw.Do(deleteTemplate, m)
}
if tags.HasVerb("deleteCollection") {
sw.Do(deleteCollectionTemplate, m)
}
if tags.HasVerb("patch") {
sw.Do(patchTemplate, m)
}
@ -217,6 +222,7 @@ var $.type|allLowercasePlural$Kind = $.GroupVersionKind|raw${Group: "$.groupName
`
var listTemplate = `
// List takes label and field selectors, and returns the list of $.type|publicPlural$ that match those selectors.
func (c *Fake$.type|publicPlural$) List(opts $.ListOptions|raw$) (result *$.type|raw$List, err error) {
obj, err := c.Fake.
$if .namespaced$Invokes($.NewListAction|raw$($.type|allLowercasePlural$Resource, $.type|allLowercasePlural$Kind, c.ns, opts), &$.type|raw$List{})
@ -229,6 +235,7 @@ func (c *Fake$.type|publicPlural$) List(opts $.ListOptions|raw$) (result *$.type
`
var listUsingOptionsTemplate = `
// List takes label and field selectors, and returns the list of $.type|publicPlural$ that match those selectors.
func (c *Fake$.type|publicPlural$) List(opts $.ListOptions|raw$) (result *$.type|raw$List, err error) {
obj, err := c.Fake.
$if .namespaced$Invokes($.NewListAction|raw$($.type|allLowercasePlural$Resource, $.type|allLowercasePlural$Kind, c.ns, opts), &$.type|raw$List{})
@ -252,6 +259,7 @@ func (c *Fake$.type|publicPlural$) List(opts $.ListOptions|raw$) (result *$.type
`
var getTemplate = `
// Get takes name of the $.type|private$, and returns the corresponding $.type|private$ object, and an error if there is any.
func (c *Fake$.type|publicPlural$) Get(name string, options $.GetOptions|raw$) (result *$.type|raw$, err error) {
obj, err := c.Fake.
$if .namespaced$Invokes($.NewGetAction|raw$($.type|allLowercasePlural$Resource, c.ns, name), &$.type|raw${})
@ -264,6 +272,7 @@ func (c *Fake$.type|publicPlural$) Get(name string, options $.GetOptions|raw$) (
`
var deleteTemplate = `
// Delete takes name of the $.type|private$ and deletes it. Returns an error if one occurs.
func (c *Fake$.type|publicPlural$) Delete(name string, options *$.DeleteOptions|raw$) error {
_, err := c.Fake.
$if .namespaced$Invokes($.NewDeleteAction|raw$($.type|allLowercasePlural$Resource, c.ns, name), &$.type|raw${})
@ -273,6 +282,7 @@ func (c *Fake$.type|publicPlural$) Delete(name string, options *$.DeleteOptions|
`
var deleteCollectionTemplate = `
// DeleteCollection deletes a collection of objects.
func (c *Fake$.type|publicPlural$) DeleteCollection(options *$.DeleteOptions|raw$, listOptions $.ListOptions|raw$) error {
$if .namespaced$action := $.NewDeleteCollectionAction|raw$($.type|allLowercasePlural$Resource, c.ns, listOptions)
$else$action := $.NewRootDeleteCollectionAction|raw$($.type|allLowercasePlural$Resource, listOptions)
@ -283,6 +293,7 @@ func (c *Fake$.type|publicPlural$) DeleteCollection(options *$.DeleteOptions|raw
`
var createTemplate = `
// Create takes the representation of a $.type|private$ and creates it. Returns the server's representation of the $.type|private$, and an error, if there is any.
func (c *Fake$.type|publicPlural$) Create($.type|private$ *$.type|raw$) (result *$.type|raw$, err error) {
obj, err := c.Fake.
$if .namespaced$Invokes($.NewCreateAction|raw$($.type|allLowercasePlural$Resource, c.ns, $.type|private$), &$.type|raw${})
@ -295,6 +306,7 @@ func (c *Fake$.type|publicPlural$) Create($.type|private$ *$.type|raw$) (result
`
var updateTemplate = `
// Update takes the representation of a $.type|private$ and updates it. Returns the server's representation of the $.type|private$, and an error, if there is any.
func (c *Fake$.type|publicPlural$) Update($.type|private$ *$.type|raw$) (result *$.type|raw$, err error) {
obj, err := c.Fake.
$if .namespaced$Invokes($.NewUpdateAction|raw$($.type|allLowercasePlural$Resource, c.ns, $.type|private$), &$.type|raw${})
@ -307,6 +319,8 @@ func (c *Fake$.type|publicPlural$) Update($.type|private$ *$.type|raw$) (result
`
var updateStatusTemplate = `
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *Fake$.type|publicPlural$) UpdateStatus($.type|private$ *$.type|raw$) (*$.type|raw$, error) {
obj, err := c.Fake.
$if .namespaced$Invokes($.NewUpdateSubresourceAction|raw$($.type|allLowercasePlural$Resource, "status", c.ns, $.type|private$), &$.type|raw${})

View File

@ -23,6 +23,8 @@ import (
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
"k8s.io/kube-gen/cmd/client-gen/path"
)
@ -101,15 +103,18 @@ func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer
sw.Do(groupInterfaceTemplate, m)
sw.Do(groupClientTemplate, m)
for _, t := range g.types {
tags, err := util.ParseClientGenTags(t.SecondClosestCommentLines)
if err != nil {
return err
}
wrapper := map[string]interface{}{
"type": t,
"GroupVersion": namer.IC(g.group) + namer.IC(g.version),
}
namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines)
if namespaced {
sw.Do(getterImplNamespaced, wrapper)
} else {
if tags.NonNamespaced {
sw.Do(getterImplNonNamespaced, wrapper)
} else {
sw.Do(getterImplNamespaced, wrapper)
}
}
sw.Do(newClientForConfigTemplate, m)

View File

@ -17,13 +17,15 @@ limitations under the License.
package generators
import (
"fmt"
"io"
"path/filepath"
"strings"
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
)
// genClientForType produces a file for each top-level type.
@ -64,25 +66,22 @@ func genStatus(t *types.Type) bool {
break
}
}
// Allow overriding via a comment on the type
genStatus, err := types.ExtractSingleBoolCommentTag("+", "genclientstatus", hasStatus, t.SecondClosestCommentLines)
if err != nil {
fmt.Printf("error looking up +genclientstatus: %v\n", err)
}
return genStatus
return hasStatus && !util.MustParseClientGenTags(t.SecondClosestCommentLines).NoStatus
}
// GenerateType makes the body of a file implementing the individual typed client for type t.
func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
pkg := filepath.Base(t.Name.Package)
namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines)
tags, err := util.ParseClientGenTags(t.SecondClosestCommentLines)
if err != nil {
return err
}
m := map[string]interface{}{
"type": t,
"package": pkg,
"Package": namer.IC(pkg),
"namespaced": namespaced,
"namespaced": !tags.NonNamespaced,
"Group": namer.IC(g.group),
"GroupVersion": namer.IC(g.group) + namer.IC(g.version),
"DeleteOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}),
@ -95,74 +94,100 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i
}
sw.Do(getterComment, m)
if namespaced {
sw.Do(getterNamesapced, m)
if tags.NonNamespaced {
sw.Do(getterNonNamespaced, m)
} else {
sw.Do(getterNonNamesapced, m)
sw.Do(getterNamespaced, m)
}
noMethods := extractBoolTagOrDie("noMethods", t.SecondClosestCommentLines) == true
readonly := extractBoolTagOrDie("readonly", t.SecondClosestCommentLines) == true
sw.Do(interfaceTemplate1, m)
if !noMethods {
if readonly {
sw.Do(interfaceTemplateReadonly, m)
} else {
sw.Do(interfaceTemplate2, m)
// Include the UpdateStatus method if the type has a status
if genStatus(t) {
sw.Do(interfaceUpdateStatusTemplate, m)
}
sw.Do(interfaceTemplate3, m)
if !tags.NoVerbs {
if !genStatus(t) {
tags.SkipVerbs = append(tags.SkipVerbs, "updateStatus")
}
sw.Do(generateInterface(tags), m)
}
sw.Do(interfaceTemplate4, m)
if namespaced {
sw.Do(structNamespaced, m)
sw.Do(newStructNamespaced, m)
} else {
if tags.NonNamespaced {
sw.Do(structNonNamespaced, m)
sw.Do(newStructNonNamespaced, m)
} else {
sw.Do(structNamespaced, m)
sw.Do(newStructNamespaced, m)
}
if !noMethods && !readonly {
sw.Do(createTemplate, m)
sw.Do(updateTemplate, m)
// Generate the UpdateStatus method if the type has a status
if genStatus(t) {
sw.Do(updateStatusTemplate, m)
}
sw.Do(deleteTemplate, m)
sw.Do(deleteCollectionTemplate, m)
if tags.NoVerbs {
return sw.Error()
}
if !noMethods {
if tags.HasVerb("get") {
sw.Do(getTemplate, m)
}
if tags.HasVerb("list") {
sw.Do(listTemplate, m)
}
if tags.HasVerb("watch") {
sw.Do(watchTemplate, m)
}
if !noMethods && !readonly {
if tags.HasVerb("create") {
sw.Do(createTemplate, m)
}
if tags.HasVerb("update") {
sw.Do(updateTemplate, m)
}
if tags.HasVerb("updateStatus") {
sw.Do(updateStatusTemplate, m)
}
if tags.HasVerb("delete") {
sw.Do(deleteTemplate, m)
}
if tags.HasVerb("deleteCollection") {
sw.Do(deleteCollectionTemplate, m)
}
if tags.HasVerb("patch") {
sw.Do(patchTemplate, m)
}
return sw.Error()
}
func generateInterface(tags util.Tags) string {
// need an ordered list here to guarantee order of generated methods.
out := []string{}
for _, m := range util.SupportedVerbs {
if tags.HasVerb(m) {
out = append(out, defaultVerbTemplates[m])
}
}
return strings.Join(out, "\n")
}
var defaultVerbTemplates = map[string]string{
"create": `Create(*$.type|raw$) (*$.type|raw$, error)`,
"update": `Update(*$.type|raw$) (*$.type|raw$, error)`,
"updateStatus": `UpdateStatus(*$.type|raw$) (*$.type|raw$, error)`,
"delete": `Delete(name string, options *$.DeleteOptions|raw$) error`,
"deleteCollection": `DeleteCollection(options *$.DeleteOptions|raw$, listOptions $.ListOptions|raw$) error`,
"get": `Get(name string, options $.GetOptions|raw$) (*$.type|raw$, error)`,
"list": `List(opts $.ListOptions|raw$) (*$.type|raw$List, error)`,
"watch": `Watch(opts $.ListOptions|raw$) ($.watchInterface|raw$, error)`,
"patch": `Patch(name string, pt $.PatchType|raw$, data []byte, subresources ...string) (result *$.type|raw$, err error)`,
}
// group client will implement this interface.
var getterComment = `
// $.type|publicPlural$Getter has a method to return a $.type|public$Interface.
// A group's client should implement this interface.`
var getterNamesapced = `
var getterNamespaced = `
type $.type|publicPlural$Getter interface {
$.type|publicPlural$(namespace string) $.type|public$Interface
}
`
var getterNonNamesapced = `
var getterNonNamespaced = `
type $.type|publicPlural$Getter interface {
$.type|publicPlural$() $.type|public$Interface
}
@ -173,27 +198,6 @@ var interfaceTemplate1 = `
// $.type|public$Interface has methods to work with $.type|public$ resources.
type $.type|public$Interface interface {`
var interfaceTemplate2 = `
Create(*$.type|raw$) (*$.type|raw$, error)
Update(*$.type|raw$) (*$.type|raw$, error)`
var interfaceUpdateStatusTemplate = `
UpdateStatus(*$.type|raw$) (*$.type|raw$, error)`
// template for the Interface
var interfaceTemplate3 = `
Delete(name string, options *$.DeleteOptions|raw$) error
DeleteCollection(options *$.DeleteOptions|raw$, listOptions $.ListOptions|raw$) error
Get(name string, options $.GetOptions|raw$) (*$.type|raw$, error)
List(opts $.ListOptions|raw$) (*$.type|raw$List, error)
Watch(opts $.ListOptions|raw$) ($.watchInterface|raw$, error)
Patch(name string, pt $.PatchType|raw$, data []byte, subresources ...string) (result *$.type|raw$, err error)`
var interfaceTemplateReadonly = `
Get(name string, options $.GetOptions|raw$) (*$.type|raw$, error)
List(opts $.ListOptions|raw$) (*$.type|raw$List, error)
Watch(opts $.ListOptions|raw$) ($.watchInterface|raw$, error)`
var interfaceTemplate4 = `
$.type|public$Expansion
}
@ -320,7 +324,7 @@ func (c *$.type|privatePlural$) Update($.type|private$ *$.type|raw$) (result *$.
var updateStatusTemplate = `
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclientstatus=false comment above the type to avoid generating UpdateStatus().
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *$.type|privatePlural$) UpdateStatus($.type|private$ *$.type|raw$) (result *$.type|raw$, err error) {
result = &$.type|raw${}

View File

@ -17,21 +17,9 @@ limitations under the License.
package generators
import (
"github.com/golang/glog"
"k8s.io/gengo/types"
)
// extractBoolTagOrDie gets the comment-tags for the key and asserts that, if
// it exists, the value is boolean. If the tag did not exist, it returns
// false.
func extractBoolTagOrDie(key string, lines []string) bool {
val, err := types.ExtractSingleBoolCommentTag("+", key, false, lines)
if err != nil {
glog.Fatalf(err.Error())
}
return val
}
// extractTag gets the comment-tags for the key. If the tag did not exist, it
// returns the empty string.
func extractTag(key string, lines []string) string {

View File

@ -0,0 +1,23 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["tags_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["tags.go"],
tags = ["automanaged"],
deps = ["//vendor/k8s.io/gengo/types:go_default_library"],
)

View File

@ -0,0 +1,163 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"errors"
"fmt"
"strings"
"k8s.io/gengo/types"
)
var supportedTags = []string{
"genclient",
"genclient:nonNamespaced",
"genclient:noVerbs",
"genclient:onlyVerbs",
"genclient:skipVerbs",
"genclient:noStatus",
"genclient:readonly",
}
// SupportedVerbs is a list of supported verbs for +onlyVerbs and +skipVerbs.
var SupportedVerbs = []string{
"create",
"update",
"updateStatus",
"delete",
"deleteCollection",
"get",
"list",
"watch",
"patch",
}
// ReadonlyVerbs represents a list of read-only verbs.
var ReadonlyVerbs = []string{
"get",
"list",
"watch",
}
// Tags represents a genclient configuration for a single type.
type Tags struct {
// +genclient
GenerateClient bool
// +genclient:nonNamespaced
NonNamespaced bool
// +genclient:noStatus
NoStatus bool
// +genclient:noVerbs
NoVerbs bool
// +genclient:skipVerbs=get,update
// +genclient:onlyVerbs=create,delete
SkipVerbs []string
}
// HasVerb returns true if we should include the given verb in final client interface and
// generate the function for it.
func (t Tags) HasVerb(verb string) bool {
if len(t.SkipVerbs) == 0 {
return true
}
for _, s := range t.SkipVerbs {
if verb == s {
return false
}
}
return true
}
// MustParseClientGenTags calls ParseClientGenTags but instead of returning error it panics.
func MustParseClientGenTags(lines []string) Tags {
tags, err := ParseClientGenTags(lines)
if err != nil {
panic(err.Error())
}
return tags
}
// ParseClientGenTags parse the provided genclient tags and validates that no unknown
// tags are provided.
func ParseClientGenTags(lines []string) (Tags, error) {
ret := Tags{}
values := types.ExtractCommentTags("+", lines)
value := []string{}
value, ret.GenerateClient = values["genclient"]
// Check the old format and error when used to avoid generating client when //+genclient=false
if len(value) > 0 && len(value[0]) > 0 {
return ret, fmt.Errorf("+genclient=%s is invalid, use //+genclient if you want to generate client or omit it when you want to disable generation", value)
}
_, ret.NonNamespaced = values["genclient:nonNamespaced"]
// Check the old format and error when used
if value := values["nonNamespaced"]; len(value) > 0 && len(value[0]) > 0 {
return ret, fmt.Errorf("+nonNamespaced=%s is invalid, use //+genclient:nonNamespaced instead", value[0])
}
_, ret.NoVerbs = values["genclient:noVerbs"]
_, ret.NoStatus = values["genclient:noStatus"]
onlyVerbs := []string{}
if _, isReadonly := values["genclient:readonly"]; isReadonly {
onlyVerbs = ReadonlyVerbs
}
// Check the old format and error when used
if value := values["readonly"]; len(value) > 0 && len(value[0]) > 0 {
return ret, fmt.Errorf("+readonly=%s is invalid, use //+genclient:readonly instead", value[0])
}
if v, exists := values["genclient:skipVerbs"]; exists {
ret.SkipVerbs = strings.Split(v[0], ",")
}
if v, exists := values["genclient:onlyVerbs"]; exists || len(onlyVerbs) > 0 {
if len(v) > 0 {
onlyVerbs = append(onlyVerbs, strings.Split(v[0], ",")...)
}
skipVerbs := []string{}
for _, m := range SupportedVerbs {
skip := true
for _, o := range onlyVerbs {
if o == m {
skip = false
break
}
}
// Check for conflicts
for _, v := range skipVerbs {
if v == m {
return ret, fmt.Errorf("verb %q used both in genclient:skipVerbs and genclient:onlyVerbs", v)
}
}
if skip {
skipVerbs = append(skipVerbs, m)
}
}
ret.SkipVerbs = skipVerbs
}
return ret, validateClientGenTags(values)
}
// validateTags validates that only supported genclient tags were provided.
func validateClientGenTags(values map[string][]string) error {
for _, k := range supportedTags {
delete(values, k)
}
for key := range values {
if strings.HasPrefix(key, "genclient") {
return errors.New("unknown tag detected: " + key)
}
}
return nil
}

View File

@ -0,0 +1,84 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"reflect"
"testing"
)
func TestParseTags(t *testing.T) {
testCases := map[string]struct {
lines []string
expectTags Tags
expectError bool
}{
"genclient": {
lines: []string{`+genclient`},
expectTags: Tags{GenerateClient: true},
},
"genclient=true": {
lines: []string{`+genclient=true`},
expectError: true,
},
"nonNamespaced=true": {
lines: []string{`+genclient=true`, `+nonNamespaced=true`},
expectError: true,
},
"readonly=true": {
lines: []string{`+genclient=true`, `+readonly=true`},
expectError: true,
},
"genclient:nonNamespaced": {
lines: []string{`+genclient`, `+genclient:nonNamespaced`},
expectTags: Tags{GenerateClient: true, NonNamespaced: true},
},
"genclient:noVerbs": {
lines: []string{`+genclient`, `+genclient:noVerbs`},
expectTags: Tags{GenerateClient: true, NoVerbs: true},
},
"genclient:noStatus": {
lines: []string{`+genclient`, `+genclient:noStatus`},
expectTags: Tags{GenerateClient: true, NoStatus: true},
},
"genclient:onlyVerbs": {
lines: []string{`+genclient`, `+genclient:onlyVerbs=create,delete`},
expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"update", "updateStatus", "deleteCollection", "get", "list", "watch", "patch"}},
},
"genclient:readonly": {
lines: []string{`+genclient`, `+genclient:readonly`},
expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"create", "update", "updateStatus", "delete", "deleteCollection", "patch"}},
},
"genclient:conflict": {
lines: []string{`+genclient`, `+genclient:onlyVerbs=create`, `+genclient:skipVerbs=create`},
expectError: true,
},
"genclient:invalid": {
lines: []string{`+genclient`, `+genclient:invalid`},
expectError: true,
},
}
for key, c := range testCases {
result, err := ParseClientGenTags(c.lines)
if err != nil && !c.expectError {
t.Fatalf("unexpected error: %v", err)
}
if !c.expectError && !reflect.DeepEqual(result, c.expectTags) {
t.Errorf("[%s] expected %#v to be %#v", key, result, c.expectTags)
}
}
}

View File

@ -18,7 +18,7 @@ package testgroup
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +genclient=true
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TestType struct {

View File

@ -18,7 +18,7 @@ package v1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +genclient=true
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TestType struct {

View File

@ -36,6 +36,44 @@ var testtypesResource = schema.GroupVersionResource{Group: "testgroup.k8s.io", V
var testtypesKind = schema.GroupVersionKind{Group: "testgroup.k8s.io", Version: "", Kind: "TestType"}
func (c *FakeTestTypes) Get(name string, options v1.GetOptions) (result *testgroup.TestType, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(testtypesResource, c.ns, name), &testgroup.TestType{})
if obj == nil {
return nil, err
}
return obj.(*testgroup.TestType), err
}
func (c *FakeTestTypes) List(opts v1.ListOptions) (result *testgroup.TestTypeList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(testtypesResource, testtypesKind, c.ns, opts), &testgroup.TestTypeList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &testgroup.TestTypeList{}
for _, item := range obj.(*testgroup.TestTypeList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested testTypes.
func (c *FakeTestTypes) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(testtypesResource, c.ns, opts))
}
func (c *FakeTestTypes) Create(testType *testgroup.TestType) (result *testgroup.TestType, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(testtypesResource, c.ns, testType), &testgroup.TestType{})
@ -80,44 +118,6 @@ func (c *FakeTestTypes) DeleteCollection(options *v1.DeleteOptions, listOptions
return err
}
func (c *FakeTestTypes) Get(name string, options v1.GetOptions) (result *testgroup.TestType, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(testtypesResource, c.ns, name), &testgroup.TestType{})
if obj == nil {
return nil, err
}
return obj.(*testgroup.TestType), err
}
func (c *FakeTestTypes) List(opts v1.ListOptions) (result *testgroup.TestTypeList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(testtypesResource, testtypesKind, c.ns, opts), &testgroup.TestTypeList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &testgroup.TestTypeList{}
for _, item := range obj.(*testgroup.TestTypeList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested testTypes.
func (c *FakeTestTypes) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(testtypesResource, c.ns, opts))
}
// Patch applies the patch and returns the patched testType.
func (c *FakeTestTypes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *testgroup.TestType, err error) {
obj, err := c.Fake.

View File

@ -59,6 +59,41 @@ func newTestTypes(c *TestgroupClient, namespace string) *testTypes {
}
}
// Get takes name of the testType, and returns the corresponding testType object, and an error if there is any.
func (c *testTypes) Get(name string, options v1.GetOptions) (result *testgroup.TestType, err error) {
result = &testgroup.TestType{}
err = c.client.Get().
Namespace(c.ns).
Resource("testtypes").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of TestTypes that match those selectors.
func (c *testTypes) List(opts v1.ListOptions) (result *testgroup.TestTypeList, err error) {
result = &testgroup.TestTypeList{}
err = c.client.Get().
Namespace(c.ns).
Resource("testtypes").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested testTypes.
func (c *testTypes) Watch(opts v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("testtypes").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Create takes the representation of a testType and creates it. Returns the server's representation of the testType, and an error, if there is any.
func (c *testTypes) Create(testType *testgroup.TestType) (result *testgroup.TestType, err error) {
result = &testgroup.TestType{}
@ -85,7 +120,7 @@ func (c *testTypes) Update(testType *testgroup.TestType) (result *testgroup.Test
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclientstatus=false comment above the type to avoid generating UpdateStatus().
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *testTypes) UpdateStatus(testType *testgroup.TestType) (result *testgroup.TestType, err error) {
result = &testgroup.TestType{}
@ -122,41 +157,6 @@ func (c *testTypes) DeleteCollection(options *v1.DeleteOptions, listOptions v1.L
Error()
}
// Get takes name of the testType, and returns the corresponding testType object, and an error if there is any.
func (c *testTypes) Get(name string, options v1.GetOptions) (result *testgroup.TestType, err error) {
result = &testgroup.TestType{}
err = c.client.Get().
Namespace(c.ns).
Resource("testtypes").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of TestTypes that match those selectors.
func (c *testTypes) List(opts v1.ListOptions) (result *testgroup.TestTypeList, err error) {
result = &testgroup.TestTypeList{}
err = c.client.Get().
Namespace(c.ns).
Resource("testtypes").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested testTypes.
func (c *testTypes) Watch(opts v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("testtypes").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Patch applies the patch and returns the patched testType.
func (c *testTypes) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *testgroup.TestType, err error) {
result = &testgroup.TestType{}

View File

@ -29,6 +29,7 @@ go_library(
"//vendor/k8s.io/gengo/generator:go_default_library",
"//vendor/k8s.io/gengo/namer:go_default_library",
"//vendor/k8s.io/gengo/types:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/util:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/types:go_default_library",
],
)

View File

@ -24,6 +24,8 @@ import (
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
clientgentypes "k8s.io/kube-gen/cmd/client-gen/types"
"github.com/golang/glog"
@ -69,6 +71,11 @@ func (g *informerGenerator) GenerateType(c *generator.Context, t *types.Type, w
clientSetInterface := c.Universe.Type(types.Name{Package: g.clientSetPackage, Name: "Interface"})
informerFor := "InformerFor"
tags, err := util.ParseClientGenTags(t.SecondClosestCommentLines)
if err != nil {
return err
}
m := map[string]interface{}{
"apiScheme": c.Universe.Type(apiScheme),
"cacheIndexers": c.Universe.Type(cacheIndexers),
@ -84,7 +91,7 @@ func (g *informerGenerator) GenerateType(c *generator.Context, t *types.Type, w
"listOptions": c.Universe.Type(listOptions),
"lister": c.Universe.Type(types.Name{Package: listerPackage, Name: t.Name.Name + "Lister"}),
"namespaceAll": c.Universe.Type(metav1NamespaceAll),
"namespaced": !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines),
"namespaced": !tags.NonNamespaced,
"newLister": c.Universe.Function(types.Name{Package: listerPackage, Name: "New" + t.Name.Name + "Lister"}),
"runtimeObject": c.Universe.Type(runtimeObject),
"timeDuration": c.Universe.Type(timeDuration),

View File

@ -25,6 +25,8 @@ import (
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
clientgentypes "k8s.io/kube-gen/cmd/client-gen/types"
"github.com/golang/glog"
@ -69,8 +71,7 @@ func generatedBy() string {
func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) {
generatingForPackage := false
for _, t := range p.Types {
// filter out types which dont have genclient=true.
if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false {
if !util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient {
continue
}
generatingForPackage = true
@ -170,12 +171,8 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
var typesToGenerate []*types.Type
for _, t := range p.Types {
// filter out types which dont have genclient=true.
if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false {
continue
}
// filter out types which have noMethods
if extractBoolTagOrDie("noMethods", t.SecondClosestCommentLines) == true {
tags := util.MustParseClientGenTags(t.SecondClosestCommentLines)
if !tags.GenerateClient || tags.NoVerbs {
continue
}
@ -307,8 +304,7 @@ func groupPackage(basePackage string, groupVersions clientgentypes.GroupVersions
return generators
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
// piggy-back on types that are tagged for client-gen
return extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == true
return util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient
},
}
}
@ -348,8 +344,7 @@ func versionPackage(basePackage string, gv clientgentypes.GroupVersion, boilerpl
return generators
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
// piggy-back on types that are tagged for client-gen
return extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == true
return util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient
},
}
}

View File

@ -21,6 +21,7 @@ go_library(
"//vendor/k8s.io/gengo/generator:go_default_library",
"//vendor/k8s.io/gengo/namer:go_default_library",
"//vendor/k8s.io/gengo/types:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/generators/util:go_default_library",
"//vendor/k8s.io/kube-gen/cmd/client-gen/types:go_default_library",
],
)

View File

@ -24,6 +24,8 @@ import (
"k8s.io/gengo/generator"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
)
// expansionGenerator produces a file for a expansion interfaces.
@ -41,10 +43,10 @@ func (g *expansionGenerator) Filter(c *generator.Context, t *types.Type) bool {
func (g *expansionGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
for _, t := range g.types {
tags := util.MustParseClientGenTags(t.SecondClosestCommentLines)
if _, err := os.Stat(filepath.Join(g.packagePath, strings.ToLower(t.Name.Name+"_expansion.go"))); os.IsNotExist(err) {
sw.Do(expansionInterfaceTemplate, t)
namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines)
if namespaced {
if !tags.NonNamespaced {
sw.Do(namespacedExpansionInterfaceTemplate, t)
}
}

View File

@ -26,6 +26,8 @@ import (
"k8s.io/gengo/generator"
"k8s.io/gengo/namer"
"k8s.io/gengo/types"
"k8s.io/kube-gen/cmd/client-gen/generators/util"
clientgentypes "k8s.io/kube-gen/cmd/client-gen/types"
"github.com/golang/glog"
@ -115,8 +117,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
var typesToGenerate []*types.Type
for _, t := range p.Types {
// filter out types which dont have genclient=true.
if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false {
if !util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient {
continue
}
typesToGenerate = append(typesToGenerate, t)
@ -154,8 +155,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
return generators
},
FilterFunc: func(c *generator.Context, t *types.Type) bool {
// piggy-back on types that are tagged for client-gen
return extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == true
return util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient
},
})
}
@ -168,7 +168,7 @@ func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) {
generatingForPackage := false
for _, t := range p.Types {
// filter out types which dont have genclient=true.
if extractBoolTagOrDie("genclient", t.SecondClosestCommentLines) == false {
if !util.MustParseClientGenTags(t.SecondClosestCommentLines).GenerateClient {
continue
}
generatingForPackage = true
@ -232,27 +232,32 @@ func (g *listerGenerator) GenerateType(c *generator.Context, t *types.Type, w io
"objectMeta": g.objectMeta,
}
namespaced := !extractBoolTagOrDie("nonNamespaced", t.SecondClosestCommentLines)
if namespaced {
sw.Do(typeListerInterface, m)
} else {
tags, err := util.ParseClientGenTags(t.SecondClosestCommentLines)
if err != nil {
return err
}
if tags.NonNamespaced {
sw.Do(typeListerInterface_NonNamespaced, m)
} else {
sw.Do(typeListerInterface, m)
}
sw.Do(typeListerStruct, m)
sw.Do(typeListerConstructor, m)
sw.Do(typeLister_List, m)
if namespaced {
sw.Do(typeLister_NamespaceLister, m)
sw.Do(namespaceListerInterface, m)
sw.Do(namespaceListerStruct, m)
sw.Do(namespaceLister_List, m)
sw.Do(namespaceLister_Get, m)
} else {
if tags.NonNamespaced {
sw.Do(typeLister_NonNamespacedGet, m)
return sw.Error()
}
sw.Do(typeLister_NamespaceLister, m)
sw.Do(namespaceListerInterface, m)
sw.Do(namespaceListerStruct, m)
sw.Do(namespaceLister_List, m)
sw.Do(namespaceLister_Get, m)
return sw.Error()
}