Use apiextension v1

- upgrade from apiextension v1beta1 to v1
 - generate yaml manifest for crd intead of applying it at runtime
  - users will have to apply the manifest with kubectl
 - kg and kgctl log an error if the crd is not present
 - now validation should actually work

Signed-off-by: leonnicolas <leonloechner@gmx.de>
This commit is contained in:
leonnicolas
2021-06-14 09:08:46 +02:00
parent e272d725a5
commit 36643b77b4
584 changed files with 50911 additions and 55838 deletions

View File

@@ -0,0 +1,422 @@
/*
Copyright 2019 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 markers
import (
"go/ast"
"go/token"
"strings"
"sync"
"sigs.k8s.io/controller-tools/pkg/loader"
)
// Collector collects and parses marker comments defined in the registry
// from package source code. If no registry is provided, an empty one will
// be initialized on the first call to MarkersInPackage.
type Collector struct {
*Registry
byPackage map[string]map[ast.Node]MarkerValues
mu sync.Mutex
}
// MarkerValues are all the values for some set of markers.
type MarkerValues map[string][]interface{}
// Get fetches the first value that for the given marker, returning
// nil if no values are available.
func (v MarkerValues) Get(name string) interface{} {
vals := v[name]
if len(vals) == 0 {
return nil
}
return vals[0]
}
func (c *Collector) init() {
if c.Registry == nil {
c.Registry = &Registry{}
}
if c.byPackage == nil {
c.byPackage = make(map[string]map[ast.Node]MarkerValues)
}
}
// MarkersInPackage computes the marker values by node for the given package. Results
// are cached by package ID, so this is safe to call repeatedly from different functions.
// Each file in the package is treated as a distinct node.
//
// We consider a marker to be associated with a given AST node if either of the following are true:
//
// - it's in the Godoc for that AST node
//
// - it's in the closest non-godoc comment group above that node,
// *and* that node is a type or field node, *and* [it's either
// registered as type-level *or* it's not registered as being
// package-level]
//
// - it's not in the Godoc of a node, doesn't meet the above criteria, and
// isn't in a struct definition (in which case it's package-level)
func (c *Collector) MarkersInPackage(pkg *loader.Package) (map[ast.Node]MarkerValues, error) {
c.mu.Lock()
c.init()
if markers, exist := c.byPackage[pkg.ID]; exist {
c.mu.Unlock()
return markers, nil
}
// unlock early, it's ok if we do a bit extra work rather than locking while we're working
c.mu.Unlock()
pkg.NeedSyntax()
nodeMarkersRaw := c.associatePkgMarkers(pkg)
markers, err := c.parseMarkersInPackage(nodeMarkersRaw)
if err != nil {
return nil, err
}
c.mu.Lock()
defer c.mu.Unlock()
c.byPackage[pkg.ID] = markers
return markers, nil
}
// parseMarkersInPackage parses the given raw marker comments into output values using the registry.
func (c *Collector) parseMarkersInPackage(nodeMarkersRaw map[ast.Node][]markerComment) (map[ast.Node]MarkerValues, error) {
var errors []error
nodeMarkerValues := make(map[ast.Node]MarkerValues)
for node, markersRaw := range nodeMarkersRaw {
var target TargetType
switch node.(type) {
case *ast.File:
target = DescribesPackage
case *ast.Field:
target = DescribesField
default:
target = DescribesType
}
markerVals := make(map[string][]interface{})
for _, markerRaw := range markersRaw {
markerText := markerRaw.Text()
def := c.Registry.Lookup(markerText, target)
if def == nil {
continue
}
val, err := def.Parse(markerText)
if err != nil {
errors = append(errors, loader.ErrFromNode(err, markerRaw))
continue
}
markerVals[def.Name] = append(markerVals[def.Name], val)
}
nodeMarkerValues[node] = markerVals
}
return nodeMarkerValues, loader.MaybeErrList(errors)
}
// associatePkgMarkers associates markers with AST nodes in the given package.
func (c *Collector) associatePkgMarkers(pkg *loader.Package) map[ast.Node][]markerComment {
nodeMarkers := make(map[ast.Node][]markerComment)
for _, file := range pkg.Syntax {
fileNodeMarkers := c.associateFileMarkers(file)
for node, markers := range fileNodeMarkers {
nodeMarkers[node] = append(nodeMarkers[node], markers...)
}
}
return nodeMarkers
}
// associateFileMarkers associates markers with AST nodes in the given file.
func (c *Collector) associateFileMarkers(file *ast.File) map[ast.Node][]markerComment {
// grab all the raw marker comments by node
visitor := markerSubVisitor{
collectPackageLevel: true,
markerVisitor: &markerVisitor{
nodeMarkers: make(map[ast.Node][]markerComment),
allComments: file.Comments,
},
}
ast.Walk(visitor, file)
// grab the last package-level comments at the end of the file (if any)
lastFileMarkers := visitor.markersBetween(false, visitor.commentInd, len(visitor.allComments))
visitor.pkgMarkers = append(visitor.pkgMarkers, lastFileMarkers...)
// figure out if any type-level markers are actually package-level markers
for node, markers := range visitor.nodeMarkers {
_, isType := node.(*ast.TypeSpec)
if !isType {
continue
}
endOfMarkers := 0
for _, marker := range markers {
if marker.fromGodoc {
// markers from godoc are never package level
markers[endOfMarkers] = marker
endOfMarkers++
continue
}
markerText := marker.Text()
typeDef := c.Registry.Lookup(markerText, DescribesType)
if typeDef != nil {
// prefer assuming type-level markers
markers[endOfMarkers] = marker
endOfMarkers++
continue
}
def := c.Registry.Lookup(markerText, DescribesPackage)
if def == nil {
// assume type-level unless proven otherwise
markers[endOfMarkers] = marker
endOfMarkers++
continue
}
// it's package-level, since a package-level definition exists
visitor.pkgMarkers = append(visitor.pkgMarkers, marker)
}
visitor.nodeMarkers[node] = markers[:endOfMarkers] // re-set after trimming the package markers
}
visitor.nodeMarkers[file] = visitor.pkgMarkers
return visitor.nodeMarkers
}
// markerComment is an AST comment that contains a marker.
// It may or may not be from a Godoc comment, which affects
// marker re-associated (from type-level to package-level)
type markerComment struct {
*ast.Comment
fromGodoc bool
}
// Text returns the text of the marker, stripped of the comment
// marker and leading spaces, as should be passed to Registry.Lookup
// and Registry.Parse.
func (c markerComment) Text() string {
return strings.TrimSpace(c.Comment.Text[2:])
}
// markerVisistor visits AST nodes, recording markers associated with each node.
type markerVisitor struct {
allComments []*ast.CommentGroup
commentInd int
declComments []markerComment
lastLineCommentGroup *ast.CommentGroup
pkgMarkers []markerComment
nodeMarkers map[ast.Node][]markerComment
}
// isMarkerComment checks that the given comment is a single-line (`//`)
// comment and it's first non-space content is `+`.
func isMarkerComment(comment string) bool {
if comment[0:2] != "//" {
return false
}
stripped := strings.TrimSpace(comment[2:])
if len(stripped) < 1 || stripped[0] != '+' {
return false
}
return true
}
// markersBetween grabs the markers between the given indicies in the list of all comments.
func (v *markerVisitor) markersBetween(fromGodoc bool, start, end int) []markerComment {
if start < 0 || end < 0 {
return nil
}
var res []markerComment
for i := start; i < end; i++ {
commentGroup := v.allComments[i]
for _, comment := range commentGroup.List {
if !isMarkerComment(comment.Text) {
continue
}
res = append(res, markerComment{Comment: comment, fromGodoc: fromGodoc})
}
}
return res
}
type markerSubVisitor struct {
*markerVisitor
node ast.Node
collectPackageLevel bool
}
// Visit collects markers for each node in the AST, optionally
// collecting unassociated markers as package-level.
func (v markerSubVisitor) Visit(node ast.Node) ast.Visitor {
if node == nil {
// end of the node, so we might need to advance comments beyond the end
// of the block if we don't want to collect package-level markers in
// this block.
if !v.collectPackageLevel {
if v.commentInd < len(v.allComments) {
lastCommentInd := v.commentInd
nextGroup := v.allComments[lastCommentInd]
for nextGroup.Pos() < v.node.End() {
lastCommentInd++
if lastCommentInd >= len(v.allComments) {
// after the increment so our decrement below still makes sense
break
}
nextGroup = v.allComments[lastCommentInd]
}
v.commentInd = lastCommentInd
}
}
return nil
}
// skip comments on the same line as the previous node
// making sure to double-check for the case where we've gone past the end of the comments
// but still have to finish up typespec-gendecl association (see below).
if v.lastLineCommentGroup != nil && v.commentInd < len(v.allComments) && v.lastLineCommentGroup.Pos() == v.allComments[v.commentInd].Pos() {
v.commentInd++
}
// stop visiting if there are no more comments in the file
// NB(directxman12): we can't just stop immediately, because we
// still need to check if there are typespecs associated with gendecls.
var markerCommentBlock []markerComment
var docCommentBlock []markerComment
lastCommentInd := v.commentInd
if v.commentInd < len(v.allComments) {
// figure out the first comment after the node in question...
nextGroup := v.allComments[lastCommentInd]
for nextGroup.Pos() < node.Pos() {
lastCommentInd++
if lastCommentInd >= len(v.allComments) {
// after the increment so our decrement below still makes sense
break
}
nextGroup = v.allComments[lastCommentInd]
}
lastCommentInd-- // ...then decrement to get the last comment before the node in question
// figure out the godoc comment so we can deal with it separately
var docGroup *ast.CommentGroup
docGroup, v.lastLineCommentGroup = associatedCommentsFor(node)
// find the last comment group that's not godoc
markerCommentInd := lastCommentInd
if docGroup != nil && v.allComments[markerCommentInd].Pos() == docGroup.Pos() {
markerCommentInd--
}
// check if we have freestanding package markers,
// and find the markers in our "closest non-godoc" comment block,
// plus our godoc comment block
if markerCommentInd >= v.commentInd {
if v.collectPackageLevel {
// assume anything between the comment ind and the marker ind (not including it)
// are package-level
v.pkgMarkers = append(v.pkgMarkers, v.markersBetween(false, v.commentInd, markerCommentInd)...)
}
markerCommentBlock = v.markersBetween(false, markerCommentInd, markerCommentInd+1)
docCommentBlock = v.markersBetween(true, markerCommentInd+1, lastCommentInd+1)
} else {
docCommentBlock = v.markersBetween(true, markerCommentInd+1, lastCommentInd+1)
}
}
resVisitor := markerSubVisitor{
collectPackageLevel: false, // don't collect package level by default
markerVisitor: v.markerVisitor,
node: node,
}
// associate those markers with a node
switch typedNode := node.(type) {
case *ast.GenDecl:
// save the comments associated with the gen-decl if it's a single-line type decl
if typedNode.Lparen != token.NoPos || typedNode.Tok != token.TYPE {
// not a single-line type spec, treat them as free comments
v.pkgMarkers = append(v.pkgMarkers, markerCommentBlock...)
break
}
// save these, we'll need them when we encounter the actual type spec
v.declComments = append(v.declComments, markerCommentBlock...)
v.declComments = append(v.declComments, docCommentBlock...)
case *ast.TypeSpec:
// add in comments attributed to the gen-decl, if any,
// as well as comments associated with the actual type
v.nodeMarkers[node] = append(v.nodeMarkers[node], v.declComments...)
v.nodeMarkers[node] = append(v.nodeMarkers[node], markerCommentBlock...)
v.nodeMarkers[node] = append(v.nodeMarkers[node], docCommentBlock...)
v.declComments = nil
v.collectPackageLevel = false // don't collect package-level inside type structs
case *ast.Field:
v.nodeMarkers[node] = append(v.nodeMarkers[node], markerCommentBlock...)
v.nodeMarkers[node] = append(v.nodeMarkers[node], docCommentBlock...)
case *ast.File:
v.pkgMarkers = append(v.pkgMarkers, markerCommentBlock...)
v.pkgMarkers = append(v.pkgMarkers, docCommentBlock...)
// collect markers in root file scope
resVisitor.collectPackageLevel = true
default:
// assume markers before anything else are package-level markers,
// *but* don't include any markers in godoc
if v.collectPackageLevel {
v.pkgMarkers = append(v.pkgMarkers, markerCommentBlock...)
}
}
// increment the comment ind so that we start at the right place for the next node
v.commentInd = lastCommentInd + 1
return resVisitor
}
// associatedCommentsFor returns the doc comment group (if relevant and present) and end-of-line comment
// (again if relevant and present) for the given AST node.
func associatedCommentsFor(node ast.Node) (docGroup *ast.CommentGroup, lastLineCommentGroup *ast.CommentGroup) {
switch typedNode := node.(type) {
case *ast.Field:
docGroup = typedNode.Doc
lastLineCommentGroup = typedNode.Comment
case *ast.File:
docGroup = typedNode.Doc
case *ast.FuncDecl:
docGroup = typedNode.Doc
case *ast.GenDecl:
docGroup = typedNode.Doc
case *ast.ImportSpec:
docGroup = typedNode.Doc
lastLineCommentGroup = typedNode.Comment
case *ast.TypeSpec:
docGroup = typedNode.Doc
lastLineCommentGroup = typedNode.Comment
case *ast.ValueSpec:
docGroup = typedNode.Doc
lastLineCommentGroup = typedNode.Comment
default:
lastLineCommentGroup = nil
}
return docGroup, lastLineCommentGroup
}

113
vendor/sigs.k8s.io/controller-tools/pkg/markers/doc.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
/*
Copyright 2019 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 markers contains utilities for defining and parsing "marker
// comments", also occasionally called tag comments (we use the term marker to
// avoid confusing with struct tags). Parsed result (output) values take the
// form of Go values, much like the "encoding/json" package.
//
// Definitions and Parsing
//
// Markers are defined as structured Definitions which can be used to
// consistently parse marker comments. A Definition contains an concrete
// output type for the marker, which can be a simple type (like string), a
// struct, or a wrapper type (useful for defining additional methods on marker
// types).
//
// Markers take the general form
//
// +path:to:marker=val
//
// +path:to:marker:arg1=val,arg2=val2
//
// +path:to:marker
//
// Arguments may be ints, bools, strings, and slices. Ints and bool take their
// standard form from Go. Strings may take any of their standard forms, or any
// sequence of unquoted characters up until a `,` or `;` is encountered. Lists
// take either of the following forms:
//
// val;val;val
//
// {val, val, val}
//
// Note that the first form will not properly parse nested slices, but is
// generally convenient and is the form used in many existing markers.
//
// Each of those argument types maps to the corresponding go type. Pointers
// mark optional fields (a struct tag, below, may also be used). The empty
// interface will match any type.
//
// Struct fields may optionally be annotated with the `marker` struct tag. The
// first argument is a name override. If it's left blank (or the tag isn't
// present), the camelCase version of the name will be used. The only
// additional argument defined is `optional`, which marks a field as optional
// without using a pointer.
//
// All parsed values are unmarshalled into the output type. If any
// non-optional fields aren't mentioned, an error will be raised unless
// `Strict` is set to false.
//
// Registries and Lookup
//
// Definitions can be added to registries to facilitate lookups. Each
// definition is marked as either describing a type, struct field, or package
// (unassociated). The same marker name may be registered multiple times, as
// long as each describes a different construct (type, field, or package).
// Definitions can then be looked up by passing unparsed markers.
//
// Collection and Extraction
//
// Markers can be collected from a loader.Package using a Collector. The
// Collector will read from a given Registry, collecting comments that look
// like markers and parsing them if they match some definition on the registry.
//
// Markers are considered associated with a particular field or type if they
// exist in the Godoc, or the closest non-godoc comment. Any other markers not
// inside a some other block (e.g. a struct definition, interface definition,
// etc) are considered package level. Markers in a "closest non-Go comment
// block" may also be considered package level if registered as such and no
// identical type-level definition exists.
//
// Like loader.Package, Collector's methods are idempotent and will not
// reperform work.
//
// Traversal
//
// EachType function iterates over each type in a Package, providing
// conveniently structured type and field information with marker values
// associated.
//
// PackageMarkers can be used to fetch just package-level markers.
//
// Help
//
// Help can be defined for each marker using the DefinitionHelp struct. It's
// mostly intended to be generated off of godocs using cmd/helpgen, which takes
// the first line as summary (removing the type/field name), and considers the
// rest as details. It looks for the
//
// +controllertools:generateHelp[:category=<string>]
//
// marker to start generation.
//
// If you can't use godoc-based generation for whatever reasons (e.g.
// primitive-typed markers), you can use the SimpleHelp and DeprecatedHelp
// helper functions to generate help structs.
//
// Help is then registered into a registry as associated with the actual
// definition, and can then be later retrieved from the registry.
package markers

View File

@@ -0,0 +1,81 @@
/*
Copyright 2019 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 markers
// You *probably* don't want to write these structs by hand
// -- use cmd/helpgen if you can write Godoc, and {Simple,Deprecated}Help
// otherwise.
// DetailedHelp contains brief help, as well as more details.
// For the "full" help, join the two together.
type DetailedHelp struct {
Summary string
Details string
}
// DefinitionHelp contains overall help for a marker Definition,
// as well as per-field help.
type DefinitionHelp struct {
// DetailedHelp contains the overall help for the marker.
DetailedHelp
// Category describes what kind of marker this is.
Category string
// DeprecatedInFavorOf marks the marker as deprecated.
// If non-nil & empty, it's assumed to just mean deprecated permanently.
// If non-empty, it's assumed to be a marker name.
DeprecatedInFavorOf *string
// NB(directxman12): we make FieldHelp be in terms of the Go struct field
// names so that we don't have to know the conversion or processing rules
// for struct fields at compile-time for help generation.
// FieldHelp defines the per-field help for this marker, *in terms of the
// go struct field names. Use the FieldsHelp method to map this to
// marker argument names.
FieldHelp map[string]DetailedHelp
}
// FieldsHelp maps per-field help to the actual marker argument names from the
// given definition.
func (d *DefinitionHelp) FieldsHelp(def *Definition) map[string]DetailedHelp {
fieldsHelp := make(map[string]DetailedHelp, len(def.FieldNames))
for fieldName, argName := range def.FieldNames {
fieldsHelp[fieldName] = d.FieldHelp[argName]
}
return fieldsHelp
}
// SimpleHelp returns help that just has marker-level summary information
// (e.g. for use with empty or primitive-typed markers, where Godoc-based
// generation isn't possible).
func SimpleHelp(category, summary string) *DefinitionHelp {
return &DefinitionHelp{
Category: category,
DetailedHelp: DetailedHelp{Summary: summary},
}
}
// DeprecatedHelp returns simple help (a la SimpleHelp), except marked as
// deprecated in favor of the given marker (or an empty string for just
// deprecated).
func DeprecatedHelp(inFavorOf, category, summary string) *DefinitionHelp {
return &DefinitionHelp{
Category: category,
DetailedHelp: DetailedHelp{Summary: summary},
DeprecatedInFavorOf: &inFavorOf,
}
}

View File

@@ -0,0 +1,923 @@
/*
Copyright 2019 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 markers
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
sc "text/scanner"
"unicode"
"sigs.k8s.io/controller-tools/pkg/loader"
)
// expect checks that the next token of the scanner is the given token, adding an error
// to the scanner if not. It returns whether the token was as expected.
func expect(scanner *sc.Scanner, expected rune, errDesc string) bool {
tok := scanner.Scan()
if tok != expected {
scanner.Error(scanner, fmt.Sprintf("expected %s, got %q", errDesc, scanner.TokenText()))
return false
}
return true
}
// peekNoSpace is equivalent to scanner.Peek, except that it will consume intervening whitespace.
func peekNoSpace(scanner *sc.Scanner) rune {
hint := scanner.Peek()
for ; hint <= rune(' ') && ((1<<uint64(hint))&scanner.Whitespace) != 0; hint = scanner.Peek() {
scanner.Next() // skip the whitespace
}
return hint
}
var (
// interfaceType is a pre-computed reflect.Type representing the empty interface.
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
rawArgsType = reflect.TypeOf((*RawArguments)(nil)).Elem()
)
// lowerCamelCase converts PascalCase string to
// a camelCase string (by lowering the first rune).
func lowerCamelCase(in string) string {
isFirst := true
return strings.Map(func(inRune rune) rune {
if isFirst {
isFirst = false
return unicode.ToLower(inRune)
}
return inRune
}, in)
}
// RawArguments is a special type that can be used for a marker
// to receive *all* raw, underparsed argument data for a marker.
// You probably want to use `interface{}` to match any type instead.
// Use *only* for legacy markers that don't follow Definition's normal
// parsing logic. It should *not* be used as a field in a marker struct.
type RawArguments []byte
// ArgumentType is the kind of a marker argument type.
// It's roughly analogous to a subset of reflect.Kind, with
// an extra "AnyType" to represent the empty interface.
type ArgumentType int
const (
// Invalid represents a type that can't be parsed, and should never be used.
InvalidType ArgumentType = iota
// IntType is an int
IntType
// StringType is a string
StringType
// BoolType is a bool
BoolType
// AnyType is the empty interface, and matches the rest of the content
AnyType
// SliceType is any slice constructed of the ArgumentTypes
SliceType
// MapType is any map constructed of string keys, and ArgumentType values.
// Keys are strings, and it's common to see AnyType (non-uniform) values.
MapType
// RawType represents content that gets passed directly to the marker
// without any parsing. It should *only* be used with anonymous markers.
RawType
)
// Argument is the type of a marker argument.
type Argument struct {
// Type is the type of this argument For non-scalar types (map and slice),
// further information is specified in ItemType.
Type ArgumentType
// Optional indicates if this argument is optional.
Optional bool
// Pointer indicates if this argument was a pointer (this is really only
// needed for deserialization, and should alway imply optional)
Pointer bool
// ItemType is the type of the slice item for slices, and the value type
// for maps.
ItemType *Argument
}
// typeString contains the internals of TypeString.
func (a Argument) typeString(out *strings.Builder) {
if a.Pointer {
out.WriteRune('*')
}
switch a.Type {
case InvalidType:
out.WriteString("<invalid>")
case IntType:
out.WriteString("int")
case StringType:
out.WriteString("string")
case BoolType:
out.WriteString("bool")
case AnyType:
out.WriteString("<any>")
case SliceType:
out.WriteString("[]")
// arguments can't be non-pointer optional, so just call into typeString again.
a.ItemType.typeString(out)
case MapType:
out.WriteString("map[string]")
a.ItemType.typeString(out)
case RawType:
out.WriteString("<raw>")
}
}
// TypeString returns a string roughly equivalent
// (but not identical) to the underlying Go type that
// this argument would parse to. It's mainly useful
// for user-friendly formatting of this argument (e.g.
// help strings).
func (a Argument) TypeString() string {
out := &strings.Builder{}
a.typeString(out)
return out.String()
}
func (a Argument) String() string {
if a.Optional {
return fmt.Sprintf("<optional arg %s>", a.TypeString())
}
return fmt.Sprintf("<arg %s>", a.TypeString())
}
// castAndSet casts val to out's type if needed,
// then sets out to val.
func castAndSet(out, val reflect.Value) {
outType := out.Type()
if outType != val.Type() {
val = val.Convert(outType)
}
out.Set(val)
}
// makeSliceType makes a reflect.Type for a slice of the given type.
// Useful for constructing the out value for when AnyType's guess returns a slice.
func makeSliceType(itemType Argument) (reflect.Type, error) {
var itemReflectedType reflect.Type
switch itemType.Type {
case IntType:
itemReflectedType = reflect.TypeOf(int(0))
case StringType:
itemReflectedType = reflect.TypeOf("")
case BoolType:
itemReflectedType = reflect.TypeOf(false)
case SliceType:
subItemType, err := makeSliceType(*itemType.ItemType)
if err != nil {
return nil, err
}
itemReflectedType = subItemType
case MapType:
subItemType, err := makeMapType(*itemType.ItemType)
if err != nil {
return nil, err
}
itemReflectedType = subItemType
// TODO(directxman12): support non-uniform slices? (probably not)
default:
return nil, fmt.Errorf("invalid type when constructing guessed slice out: %v", itemType.Type)
}
if itemType.Pointer {
itemReflectedType = reflect.PtrTo(itemReflectedType)
}
return reflect.SliceOf(itemReflectedType), nil
}
// makeMapType makes a reflect.Type for a map of the given item type.
// Useful for constructing the out value for when AnyType's guess returns a map.
func makeMapType(itemType Argument) (reflect.Type, error) {
var itemReflectedType reflect.Type
switch itemType.Type {
case IntType:
itemReflectedType = reflect.TypeOf(int(0))
case StringType:
itemReflectedType = reflect.TypeOf("")
case BoolType:
itemReflectedType = reflect.TypeOf(false)
case SliceType:
subItemType, err := makeSliceType(*itemType.ItemType)
if err != nil {
return nil, err
}
itemReflectedType = subItemType
// TODO(directxman12): support non-uniform slices? (probably not)
case MapType:
subItemType, err := makeMapType(*itemType.ItemType)
if err != nil {
return nil, err
}
itemReflectedType = subItemType
case AnyType:
// NB(directxman12): maps explicitly allow non-uniform item types, unlike slices at the moment
itemReflectedType = interfaceType
default:
return nil, fmt.Errorf("invalid type when constructing guessed slice out: %v", itemType.Type)
}
if itemType.Pointer {
itemReflectedType = reflect.PtrTo(itemReflectedType)
}
return reflect.MapOf(reflect.TypeOf(""), itemReflectedType), nil
}
// guessType takes an educated guess about the type of the next field. If allowSlice
// is false, it will not guess slices. It's less efficient than parsing with actual
// type information, since we need to allocate to peek ahead full tokens, and the scanner
// only allows peeking ahead one character.
// Maps are *always* non-uniform (i.e. type the AnyType item type), since they're frequently
// used to represent things like defaults for an object in JSON.
func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
if allowSlice {
maybeItem := guessType(scanner, raw, false)
subRaw := raw[scanner.Pos().Offset:]
subScanner := parserScanner(subRaw, scanner.Error)
var tok rune
for tok = subScanner.Scan(); tok != ',' && tok != sc.EOF && tok != ';'; tok = subScanner.Scan() {
// wait till we get something interesting
}
// semicolon means it's a legacy slice
if tok == ';' {
return &Argument{
Type: SliceType,
ItemType: maybeItem,
}
}
return maybeItem
}
// everything else needs a duplicate scanner to scan properly
// (so we don't consume our scanner tokens until we actually
// go to use this -- Go doesn't like scanners that can be rewound).
subRaw := raw[scanner.Pos().Offset:]
subScanner := parserScanner(subRaw, scanner.Error)
// skip whitespace
hint := peekNoSpace(subScanner)
// first, try the easy case -- quoted strings strings
switch hint {
case '"', '\'', '`':
return &Argument{Type: StringType}
}
// next, check for slices or maps
if hint == '{' {
subScanner.Scan()
// TODO(directxman12): this can't guess at empty objects, but that's generally ok.
// We'll cross that bridge when we get there.
// look ahead till we can figure out if this is a map or a slice
firstElemType := guessType(subScanner, subRaw, false)
if firstElemType.Type == StringType {
// might be a map or slice, parse the string and check for colon
// (blech, basically arbitrary look-ahead due to raw strings).
var keyVal string // just ignore this
(&Argument{Type: StringType}).parseString(subScanner, raw, reflect.Indirect(reflect.ValueOf(&keyVal)))
if subScanner.Scan() == ':' {
// it's got a string followed by a colon -- it's a map
return &Argument{
Type: MapType,
ItemType: &Argument{Type: AnyType},
}
}
}
// definitely a slice -- maps have to have string keys and have a value followed by a colon
return &Argument{
Type: SliceType,
ItemType: firstElemType,
}
}
// then, bools...
probablyString := false
if hint == 't' || hint == 'f' {
// maybe a bool
if nextTok := subScanner.Scan(); nextTok == sc.Ident {
switch subScanner.TokenText() {
case "true", "false":
// definitely a bool
return &Argument{Type: BoolType}
}
// probably a string
probablyString = true
} else {
// we shouldn't ever get here
scanner.Error(scanner, fmt.Sprintf("got a token (%q) that looked like an ident, but was not", scanner.TokenText()))
return &Argument{Type: InvalidType}
}
}
// then, integers...
if !probablyString {
nextTok := subScanner.Scan()
if nextTok == '-' {
nextTok = subScanner.Scan()
}
if nextTok == sc.Int {
return &Argument{Type: IntType}
}
}
// otherwise assume bare strings
return &Argument{Type: StringType}
}
// parseString parses either of the two accepted string forms (quoted, or bare tokens).
func (a *Argument) parseString(scanner *sc.Scanner, raw string, out reflect.Value) {
// strings are a bit weird -- the "easy" case is quoted strings (tokenized as strings),
// the "hard" case (present for backwards compat) is a bare sequence of tokens that aren't
// a comma.
tok := scanner.Scan()
if tok == sc.String || tok == sc.RawString {
// the easy case
val, err := strconv.Unquote(scanner.TokenText())
if err != nil {
scanner.Error(scanner, fmt.Sprintf("unable to parse string: %v", err))
return
}
castAndSet(out, reflect.ValueOf(val))
return
}
// the "hard" case -- bare tokens not including ',' (the argument
// separator), ';' (the slice separator), ':' (the map separator), or '}'
// (delimitted slice ender)
startPos := scanner.Position.Offset
for hint := peekNoSpace(scanner); hint != ',' && hint != ';' && hint != ':' && hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
// skip this token
scanner.Scan()
}
endPos := scanner.Position.Offset + len(scanner.TokenText())
castAndSet(out, reflect.ValueOf(raw[startPos:endPos]))
}
// parseSlice parses either of the two slice forms (curly-brace-delimitted and semicolon-separated).
func (a *Argument) parseSlice(scanner *sc.Scanner, raw string, out reflect.Value) {
// slices have two supported formats, like string:
// - `{val, val, val}` (preferred)
// - `val;val;val` (legacy)
resSlice := reflect.Zero(out.Type())
elem := reflect.Indirect(reflect.New(out.Type().Elem()))
// preferred case
if peekNoSpace(scanner) == '{' {
// NB(directxman12): supporting delimitted slices in bare slices
// would require an extra look-ahead here :-/
scanner.Scan() // skip '{'
for hint := peekNoSpace(scanner); hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
a.ItemType.parse(scanner, raw, elem, true /* parsing a slice */)
resSlice = reflect.Append(resSlice, elem)
tok := peekNoSpace(scanner)
if tok == '}' {
break
}
if !expect(scanner, ',', "comma") {
return
}
}
if !expect(scanner, '}', "close curly brace") {
return
}
castAndSet(out, resSlice)
return
}
// legacy case
for hint := peekNoSpace(scanner); hint != ',' && hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
a.ItemType.parse(scanner, raw, elem, true /* parsing a slice */)
resSlice = reflect.Append(resSlice, elem)
tok := peekNoSpace(scanner)
if tok == ',' || tok == '}' || tok == sc.EOF {
break
}
scanner.Scan()
if tok != ';' {
scanner.Error(scanner, fmt.Sprintf("expected comma, got %q", scanner.TokenText()))
return
}
}
castAndSet(out, resSlice)
}
// parseMap parses a map of the form {string: val, string: val, string: val}
func (a *Argument) parseMap(scanner *sc.Scanner, raw string, out reflect.Value) {
resMap := reflect.MakeMap(out.Type())
elem := reflect.Indirect(reflect.New(out.Type().Elem()))
key := reflect.Indirect(reflect.New(out.Type().Key()))
if !expect(scanner, '{', "open curly brace") {
return
}
for hint := peekNoSpace(scanner); hint != '}' && hint != sc.EOF; hint = peekNoSpace(scanner) {
a.parseString(scanner, raw, key)
if !expect(scanner, ':', "colon") {
return
}
a.ItemType.parse(scanner, raw, elem, false /* not in a slice */)
resMap.SetMapIndex(key, elem)
if peekNoSpace(scanner) == '}' {
break
}
if !expect(scanner, ',', "comma") {
return
}
}
if !expect(scanner, '}', "close curly brace") {
return
}
castAndSet(out, resMap)
}
// parse functions like Parse, except that it allows passing down whether or not we're
// already in a slice, to avoid duplicate legacy slice detection for AnyType
func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inSlice bool) {
// nolint:gocyclo
if a.Type == InvalidType {
scanner.Error(scanner, fmt.Sprintf("cannot parse invalid type"))
return
}
if a.Pointer {
out.Set(reflect.New(out.Type().Elem()))
out = reflect.Indirect(out)
}
switch a.Type {
case RawType:
// raw consumes everything else
castAndSet(out, reflect.ValueOf(raw[scanner.Pos().Offset:]))
// consume everything else
for tok := scanner.Scan(); tok != sc.EOF; tok = scanner.Scan() {
}
case IntType:
nextChar := scanner.Peek()
isNegative := false
if nextChar == '-' {
isNegative = true
scanner.Scan() // eat the '-'
}
if !expect(scanner, sc.Int, "integer") {
return
}
// TODO(directxman12): respect the size when parsing
text := scanner.TokenText()
if isNegative {
text = "-" + text
}
val, err := strconv.Atoi(text)
if err != nil {
scanner.Error(scanner, fmt.Sprintf("unable to parse integer: %v", err))
return
}
castAndSet(out, reflect.ValueOf(val))
case StringType:
// strings are a bit weird -- the "easy" case is quoted strings (tokenized as strings),
// the "hard" case (present for backwards compat) is a bare sequence of tokens that aren't
// a comma.
a.parseString(scanner, raw, out)
case BoolType:
if !expect(scanner, sc.Ident, "true or false") {
return
}
switch scanner.TokenText() {
case "true":
castAndSet(out, reflect.ValueOf(true))
case "false":
castAndSet(out, reflect.ValueOf(false))
default:
scanner.Error(scanner, fmt.Sprintf("expected true or false, got %q", scanner.TokenText()))
return
}
case AnyType:
guessedType := guessType(scanner, raw, !inSlice)
newOut := out
// we need to be able to construct the right element types, below
// in parse, so construct a concretely-typed value to use as "out"
switch guessedType.Type {
case SliceType:
newType, err := makeSliceType(*guessedType.ItemType)
if err != nil {
scanner.Error(scanner, err.Error())
return
}
newOut = reflect.Indirect(reflect.New(newType))
case MapType:
newType, err := makeMapType(*guessedType.ItemType)
if err != nil {
scanner.Error(scanner, err.Error())
return
}
newOut = reflect.Indirect(reflect.New(newType))
}
if !newOut.CanSet() {
panic("at the disco") // TODO(directxman12): this is left over from debugging -- it might need to be an error
}
guessedType.Parse(scanner, raw, newOut)
castAndSet(out, newOut)
case SliceType:
// slices have two supported formats, like string:
// - `{val, val, val}` (preferred)
// - `val;val;val` (legacy)
a.parseSlice(scanner, raw, out)
case MapType:
// maps are {string: val, string: val, string: val}
a.parseMap(scanner, raw, out)
}
}
// Parse attempts to consume the argument from the given scanner (based on the given
// raw input as well for collecting ranges of content), and places the output value
// in the given reflect.Value. Errors are reported via the given scanner.
func (a *Argument) Parse(scanner *sc.Scanner, raw string, out reflect.Value) {
a.parse(scanner, raw, out, false)
}
// ArgumentFromType constructs an Argument by examining the given
// raw reflect.Type. It can construct arguments from the Go types
// corresponding to any of the types listed in ArgumentType.
func ArgumentFromType(rawType reflect.Type) (Argument, error) {
if rawType == rawArgsType {
return Argument{
Type: RawType,
}, nil
}
if rawType == interfaceType {
return Argument{
Type: AnyType,
}, nil
}
arg := Argument{}
if rawType.Kind() == reflect.Ptr {
rawType = rawType.Elem()
arg.Pointer = true
arg.Optional = true
}
switch rawType.Kind() {
case reflect.String:
arg.Type = StringType
case reflect.Int, reflect.Int32: // NB(directxman12): all ints in kubernetes are int32, so explicitly support that
arg.Type = IntType
case reflect.Bool:
arg.Type = BoolType
case reflect.Slice:
arg.Type = SliceType
itemType, err := ArgumentFromType(rawType.Elem())
if err != nil {
return Argument{}, fmt.Errorf("bad slice item type: %w", err)
}
arg.ItemType = &itemType
case reflect.Map:
arg.Type = MapType
if rawType.Key().Kind() != reflect.String {
return Argument{}, fmt.Errorf("bad map key type: map keys must be strings")
}
itemType, err := ArgumentFromType(rawType.Elem())
if err != nil {
return Argument{}, fmt.Errorf("bad slice item type: %w", err)
}
arg.ItemType = &itemType
default:
return Argument{}, fmt.Errorf("type has unsupported kind %s", rawType.Kind())
}
return arg, nil
}
// TargetType describes which kind of node a given marker is associated with.
type TargetType int
const (
// DescribesPackage indicates that a marker is associated with a package.
DescribesPackage TargetType = iota
// DescribesType indicates that a marker is associated with a type declaration.
DescribesType
// DescribesField indicates that a marker is associated with a struct field.
DescribesField
)
func (t TargetType) String() string {
switch t {
case DescribesPackage:
return "package"
case DescribesType:
return "type"
case DescribesField:
return "field"
default:
return "(unknown)"
}
}
// Definition is a parsed definition of a marker.
type Definition struct {
// Output is the deserialized Go type of the marker.
Output reflect.Type
// Name is the marker's name.
Name string
// Target indicates which kind of node this marker can be associated with.
Target TargetType
// Fields lists out the types of each field that this marker has, by
// argument name as used in the marker (if the output type isn't a struct,
// it'll have a single, blank field name). This only lists exported fields,
// (as per reflection rules).
Fields map[string]Argument
// FieldNames maps argument names (as used in the marker) to struct field name
// in the output type.
FieldNames map[string]string
// Strict indicates that this definition should error out when parsing if
// not all non-optional fields were seen.
Strict bool
}
// AnonymousField indicates that the definition has one field,
// (actually the original object), and thus the field
// doesn't get named as part of the name.
func (d *Definition) AnonymousField() bool {
if len(d.Fields) != 1 {
return false
}
_, hasAnonField := d.Fields[""]
return hasAnonField
}
// Empty indicates that this definition has no fields.
func (d *Definition) Empty() bool {
return len(d.Fields) == 0
}
// argumentInfo returns information about an argument field as the marker parser's field loader
// would see it. This can be useful if you have to interact with marker definition structs
// externally (e.g. at compile time).
func argumentInfo(fieldName string, tag reflect.StructTag) (argName string, optionalOpt bool) {
argName = lowerCamelCase(fieldName)
markerTag, tagSpecified := tag.Lookup("marker")
markerTagParts := strings.Split(markerTag, ",")
if tagSpecified && markerTagParts[0] != "" {
// allow overriding to support legacy cases where we don't follow camelCase conventions
argName = markerTagParts[0]
}
optionalOpt = false
for _, tagOption := range markerTagParts[1:] {
switch tagOption {
case "optional":
optionalOpt = true
}
}
return argName, optionalOpt
}
// loadFields uses reflection to populate argument information from the Output type.
func (d *Definition) loadFields() error {
if d.Fields == nil {
d.Fields = make(map[string]Argument)
d.FieldNames = make(map[string]string)
}
if d.Output.Kind() != reflect.Struct {
// anonymous field type
argType, err := ArgumentFromType(d.Output)
if err != nil {
return err
}
d.Fields[""] = argType
d.FieldNames[""] = ""
return nil
}
for i := 0; i < d.Output.NumField(); i++ {
field := d.Output.Field(i)
if field.PkgPath != "" {
// as per the reflect package docs, pkgpath is empty for exported fields,
// so non-empty package path means a private field, which we should skip
continue
}
argName, optionalOpt := argumentInfo(field.Name, field.Tag)
argType, err := ArgumentFromType(field.Type)
if err != nil {
return fmt.Errorf("unable to extract type information for field %q: %w", field.Name, err)
}
if argType.Type == RawType {
return fmt.Errorf("RawArguments must be the direct type of a marker, and not a field")
}
argType.Optional = optionalOpt || argType.Optional
d.Fields[argName] = argType
d.FieldNames[argName] = field.Name
}
return nil
}
// parserScanner makes a new scanner appropriate for use in parsing definitions and arguments.
func parserScanner(raw string, err func(*sc.Scanner, string)) *sc.Scanner {
scanner := &sc.Scanner{}
scanner.Init(bytes.NewBufferString(raw))
scanner.Mode = sc.ScanIdents | sc.ScanInts | sc.ScanStrings | sc.ScanRawStrings | sc.SkipComments
scanner.Error = err
return scanner
}
// Parse uses the type information in this Definition to parse the given
// raw marker in the form `+a:b:c=arg,d=arg` into an output object of the
// type specified in the definition.
func (d *Definition) Parse(rawMarker string) (interface{}, error) {
name, anonName, fields := splitMarker(rawMarker)
out := reflect.Indirect(reflect.New(d.Output))
// if we're a not a struct or have no arguments, treat the full `a:b:c` as the name,
// otherwise, treat `c` as a field name, and `a:b` as the marker name.
if !d.AnonymousField() && !d.Empty() && len(anonName) >= len(name)+1 {
fields = anonName[len(name)+1:] + "=" + fields
}
var errs []error
scanner := parserScanner(fields, func(scanner *sc.Scanner, msg string) {
errs = append(errs, &ScannerError{Msg: msg, Pos: scanner.Position})
})
// TODO(directxman12): strict parsing where we error out if certain fields aren't optional
seen := make(map[string]struct{}, len(d.Fields))
if d.AnonymousField() && scanner.Peek() != sc.EOF {
// might still be a struct that something fiddled with, so double check
structFieldName := d.FieldNames[""]
outTarget := out
if structFieldName != "" {
// it's a struct field mapped to an anonymous marker
outTarget = out.FieldByName(structFieldName)
if !outTarget.CanSet() {
scanner.Error(scanner, fmt.Sprintf("cannot set field %q (might not exist)", structFieldName))
return out.Interface(), loader.MaybeErrList(errs)
}
}
// no need for trying to parse field names if we're not a struct
field := d.Fields[""]
field.Parse(scanner, fields, outTarget)
seen[""] = struct{}{} // mark as seen for strict definitions
} else if !d.Empty() && scanner.Peek() != sc.EOF {
// if we expect *and* actually have arguments passed
for {
// parse the argument name
if !expect(scanner, sc.Ident, "argument name") {
break
}
argName := scanner.TokenText()
if !expect(scanner, '=', "equals") {
break
}
// make sure we know the field
fieldName, known := d.FieldNames[argName]
if !known {
scanner.Error(scanner, fmt.Sprintf("unknown argument %q", argName))
break
}
fieldType, known := d.Fields[argName]
if !known {
scanner.Error(scanner, fmt.Sprintf("unknown argument %q", argName))
break
}
seen[argName] = struct{}{} // mark as seen for strict definitions
// parse the field value
fieldVal := out.FieldByName(fieldName)
if !fieldVal.CanSet() {
scanner.Error(scanner, fmt.Sprintf("cannot set field %q (might not exist)", fieldName))
break
}
fieldType.Parse(scanner, fields, fieldVal)
if len(errs) > 0 {
break
}
if scanner.Peek() == sc.EOF {
break
}
if !expect(scanner, ',', "comma") {
break
}
}
}
if tok := scanner.Scan(); tok != sc.EOF {
scanner.Error(scanner, fmt.Sprintf("extra arguments provided: %q", fields[scanner.Position.Offset:]))
}
if d.Strict {
for argName, arg := range d.Fields {
if _, wasSeen := seen[argName]; !wasSeen && !arg.Optional {
scanner.Error(scanner, fmt.Sprintf("missing argument %q", argName))
}
}
}
return out.Interface(), loader.MaybeErrList(errs)
}
// MakeDefinition constructs a definition from a name, type, and the output type.
// All such definitions are strict by default. If a struct is passed as the output
// type, its public fields will automatically be populated into Fields (and similar
// fields in Definition). Other values will have a single, empty-string-named Fields
// entry.
func MakeDefinition(name string, target TargetType, output interface{}) (*Definition, error) {
def := &Definition{
Name: name,
Target: target,
Output: reflect.TypeOf(output),
Strict: true,
}
if err := def.loadFields(); err != nil {
return nil, err
}
return def, nil
}
// MakeAnyTypeDefinition constructs a definition for an output struct with a
// field named `Value` of type `interface{}`. The argument to the marker will
// be parsed as AnyType and assigned to the field named `Value`.
func MakeAnyTypeDefinition(name string, target TargetType, output interface{}) (*Definition, error) {
defn, err := MakeDefinition(name, target, output)
if err != nil {
return nil, err
}
defn.FieldNames = map[string]string{"": "Value"}
defn.Fields = map[string]Argument{"": defn.Fields["value"]}
return defn, nil
}
// splitMarker takes a marker in the form of `+a:b:c=arg,d=arg` and splits it
// into the name (`a:b`), the name if it's not a struct (`a:b:c`), and the parts
// that are definitely fields (`arg,d=arg`).
func splitMarker(raw string) (name string, anonymousName string, restFields string) {
raw = raw[1:] // get rid of the leading '+'
nameFieldParts := strings.SplitN(raw, "=", 2)
if len(nameFieldParts) == 1 {
return nameFieldParts[0], nameFieldParts[0], ""
}
anonymousName = nameFieldParts[0]
name = anonymousName
restFields = nameFieldParts[1]
nameParts := strings.Split(name, ":")
if len(nameParts) > 1 {
name = strings.Join(nameParts[:len(nameParts)-1], ":")
}
return name, anonymousName, restFields
}
type ScannerError struct {
Msg string
Pos sc.Position
}
func (e *ScannerError) Error() string {
return fmt.Sprintf("%s (at %s)", e.Msg, e.Pos)
}

153
vendor/sigs.k8s.io/controller-tools/pkg/markers/reg.go generated vendored Normal file
View File

@@ -0,0 +1,153 @@
/*
Copyright 2019 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 markers
import (
"fmt"
"sync"
)
// Registry keeps track of registered definitions, and allows for easy lookup.
// It's thread-safe, and the zero-value can be safely used.
type Registry struct {
forPkg map[string]*Definition
forType map[string]*Definition
forField map[string]*Definition
helpFor map[*Definition]*DefinitionHelp
mu sync.RWMutex
initOnce sync.Once
}
func (r *Registry) init() {
r.initOnce.Do(func() {
if r.forPkg == nil {
r.forPkg = make(map[string]*Definition)
}
if r.forType == nil {
r.forType = make(map[string]*Definition)
}
if r.forField == nil {
r.forField = make(map[string]*Definition)
}
if r.helpFor == nil {
r.helpFor = make(map[*Definition]*DefinitionHelp)
}
})
}
// Define defines a new marker with the given name, target, and output type.
// It's a shortcut around
// r.Register(MakeDefinition(name, target, obj))
func (r *Registry) Define(name string, target TargetType, obj interface{}) error {
def, err := MakeDefinition(name, target, obj)
if err != nil {
return err
}
return r.Register(def)
}
// Register registers the given marker definition with this registry for later lookup.
func (r *Registry) Register(def *Definition) error {
r.init()
r.mu.Lock()
defer r.mu.Unlock()
switch def.Target {
case DescribesPackage:
r.forPkg[def.Name] = def
case DescribesType:
r.forType[def.Name] = def
case DescribesField:
r.forField[def.Name] = def
default:
return fmt.Errorf("unknown target type %v", def.Target)
}
return nil
}
// AddHelp stores the given help in the registry, marking it as associated with
// the given definition.
func (r *Registry) AddHelp(def *Definition, help *DefinitionHelp) {
r.init()
r.mu.Lock()
defer r.mu.Unlock()
r.helpFor[def] = help
}
// Lookup fetches the definition corresponding to the given name and target type.
func (r *Registry) Lookup(name string, target TargetType) *Definition {
r.init()
r.mu.RLock()
defer r.mu.RUnlock()
switch target {
case DescribesPackage:
return tryAnonLookup(name, r.forPkg)
case DescribesType:
return tryAnonLookup(name, r.forType)
case DescribesField:
return tryAnonLookup(name, r.forField)
default:
return nil
}
}
// HelpFor fetches the help for a given definition, if present.
func (r *Registry) HelpFor(def *Definition) *DefinitionHelp {
r.init()
r.mu.RLock()
defer r.mu.RUnlock()
return r.helpFor[def]
}
// AllDefinitions returns all marker definitions known to this registry.
func (r *Registry) AllDefinitions() []*Definition {
res := make([]*Definition, 0, len(r.forPkg)+len(r.forType)+len(r.forField))
for _, def := range r.forPkg {
res = append(res, def)
}
for _, def := range r.forType {
res = append(res, def)
}
for _, def := range r.forField {
res = append(res, def)
}
return res
}
// tryAnonLookup tries looking up the given marker as both an struct-based
// marker and an anonymous marker, returning whichever format matches first,
// preferring the longer (anonymous) name in case of conflicts.
func tryAnonLookup(name string, defs map[string]*Definition) *Definition {
// NB(directxman12): we look up anonymous names first to work with
// legacy style marker definitions that have a namespaced approach
// (e.g. deepcopy-gen, which uses `+k8s:deepcopy-gen=foo,bar` *and*
// `+k8s.io:deepcopy-gen:interfaces=foo`).
name, anonName, _ := splitMarker(name)
if def, exists := defs[anonName]; exists {
return def
}
return defs[name]
}

View File

@@ -0,0 +1,36 @@
/*
Copyright 2019 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 markers
// Must panics on errors creating definitions.
func Must(def *Definition, err error) *Definition {
if err != nil {
panic(err)
}
return def
}
// RegisterAll attempts to register all definitions against the given registry,
// stopping and returning if an error occurs.
func RegisterAll(reg *Registry, defs ...*Definition) error {
for _, def := range defs {
if err := reg.Register(def); err != nil {
return err
}
}
return nil
}

191
vendor/sigs.k8s.io/controller-tools/pkg/markers/zip.go generated vendored Normal file
View File

@@ -0,0 +1,191 @@
/*
Copyright 2019 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 markers
import (
"go/ast"
"go/token"
"reflect"
"strings"
"sigs.k8s.io/controller-tools/pkg/loader"
)
// extractDoc extracts documentation from the given node, skipping markers
// in the godoc and falling back to the decl if necessary (for single-line decls).
func extractDoc(node ast.Node, decl *ast.GenDecl) string {
var docs *ast.CommentGroup
switch docced := node.(type) {
case *ast.Field:
docs = docced.Doc
case *ast.File:
docs = docced.Doc
case *ast.GenDecl:
docs = docced.Doc
case *ast.TypeSpec:
docs = docced.Doc
// type Ident expr expressions get docs attached to the decl,
// so check for that case (missing Lparen == single line type decl)
if docs == nil && decl.Lparen == token.NoPos {
docs = decl.Doc
}
}
if docs == nil {
return ""
}
// filter out markers
var outGroup ast.CommentGroup
outGroup.List = make([]*ast.Comment, 0, len(docs.List))
for _, comment := range docs.List {
if isMarkerComment(comment.Text) {
continue
}
outGroup.List = append(outGroup.List, comment)
}
// split lines, and re-join together as a single
// paragraph, respecting double-newlines as
// paragraph markers.
outLines := strings.Split(outGroup.Text(), "\n")
if outLines[len(outLines)-1] == "" {
// chop off the extraneous last part
outLines = outLines[:len(outLines)-1]
}
// respect double-newline meaning actual newline
for i, line := range outLines {
if line == "" {
outLines[i] = "\n"
}
}
return strings.Join(outLines, " ")
}
// PackageMarkers collects all the package-level marker values for the given package.
func PackageMarkers(col *Collector, pkg *loader.Package) (MarkerValues, error) {
markers, err := col.MarkersInPackage(pkg)
if err != nil {
return nil, err
}
res := make(MarkerValues)
for _, file := range pkg.Syntax {
fileMarkers := markers[file]
for name, vals := range fileMarkers {
res[name] = append(res[name], vals...)
}
}
return res, nil
}
// FieldInfo contains marker values and commonly used information for a struct field.
type FieldInfo struct {
// Name is the name of the field (or "" for embedded fields)
Name string
// Doc is the Godoc of the field, pre-processed to remove markers and joine
// single newlines together.
Doc string
// Tag struct tag associated with this field (or "" if non existed).
Tag reflect.StructTag
// Markers are all registered markers associated with this field.
Markers MarkerValues
// RawField is the raw, underlying field AST object that this field represents.
RawField *ast.Field
}
// TypeInfo contains marker values and commonly used information for a type declaration.
type TypeInfo struct {
// Name is the name of the type.
Name string
// Doc is the Godoc of the type, pre-processed to remove markers and joine
// single newlines together.
Doc string
// Markers are all registered markers associated with the type.
Markers MarkerValues
// Fields are all the fields associated with the type, if it's a struct.
// (if not, Fields will be nil).
Fields []FieldInfo
// RawDecl contains the raw GenDecl that the type was declared as part of.
RawDecl *ast.GenDecl
// RawSpec contains the raw Spec that declared this type.
RawSpec *ast.TypeSpec
// RawFile contains the file in which this type was declared.
RawFile *ast.File
}
// TypeCallback is a callback called for each type declaration in a package.
type TypeCallback func(info *TypeInfo)
// EachType collects all markers, then calls the given callback for each type declaration in a package.
// Each individual spec is considered separate, so
//
// type (
// Foo string
// Bar int
// Baz struct{}
// )
//
// yields three calls to the callback.
func EachType(col *Collector, pkg *loader.Package, cb TypeCallback) error {
markers, err := col.MarkersInPackage(pkg)
if err != nil {
return err
}
loader.EachType(pkg, func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec) {
var fields []FieldInfo
if structSpec, isStruct := spec.Type.(*ast.StructType); isStruct {
for _, field := range structSpec.Fields.List {
for _, name := range field.Names {
fields = append(fields, FieldInfo{
Name: name.Name,
Doc: extractDoc(field, nil),
Tag: loader.ParseAstTag(field.Tag),
Markers: markers[field],
RawField: field,
})
}
if field.Names == nil {
fields = append(fields, FieldInfo{
Doc: extractDoc(field, nil),
Tag: loader.ParseAstTag(field.Tag),
Markers: markers[field],
RawField: field,
})
}
}
}
cb(&TypeInfo{
Name: spec.Name.Name,
Markers: markers[spec],
Doc: extractDoc(spec, decl),
Fields: fields,
RawDecl: decl,
RawSpec: spec,
RawFile: file,
})
})
return nil
}