use traditional controller tool to generate code
This commit is contained in:
287
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/apis.go
generated
vendored
Normal file
287
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/apis.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Copyright 2018 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 parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
)
|
||||
|
||||
type genUnversionedType struct {
|
||||
Type *types.Type
|
||||
Resource *codegen.APIResource
|
||||
}
|
||||
|
||||
func (b *APIs) parseAPIs() {
|
||||
apis := &codegen.APIs{
|
||||
Domain: b.Domain,
|
||||
Package: b.APIsPkg,
|
||||
Groups: map[string]*codegen.APIGroup{},
|
||||
Rules: b.Rules,
|
||||
Informers: b.Informers,
|
||||
}
|
||||
|
||||
for group, versionMap := range b.ByGroupVersionKind {
|
||||
apiGroup := &codegen.APIGroup{
|
||||
Group: group,
|
||||
GroupTitle: strings.Title(group),
|
||||
Domain: b.Domain,
|
||||
Versions: map[string]*codegen.APIVersion{},
|
||||
UnversionedResources: map[string]*codegen.APIResource{},
|
||||
}
|
||||
|
||||
for version, kindMap := range versionMap {
|
||||
apiVersion := &codegen.APIVersion{
|
||||
Domain: b.Domain,
|
||||
Group: group,
|
||||
Version: version,
|
||||
Resources: map[string]*codegen.APIResource{},
|
||||
}
|
||||
for kind, resource := range kindMap {
|
||||
apiResource := &codegen.APIResource{
|
||||
Domain: resource.Domain,
|
||||
Version: resource.Version,
|
||||
Group: resource.Group,
|
||||
Resource: resource.Resource,
|
||||
Type: resource.Type,
|
||||
REST: resource.REST,
|
||||
Kind: resource.Kind,
|
||||
Subresources: resource.Subresources,
|
||||
StatusStrategy: resource.StatusStrategy,
|
||||
Strategy: resource.Strategy,
|
||||
NonNamespaced: resource.NonNamespaced,
|
||||
ShortName: resource.ShortName,
|
||||
}
|
||||
parseDoc(resource, apiResource)
|
||||
apiVersion.Resources[kind] = apiResource
|
||||
// Set the package for the api version
|
||||
apiVersion.Pkg = b.context.Universe[resource.Type.Name.Package]
|
||||
// Set the package for the api group
|
||||
apiGroup.Pkg = b.context.Universe[filepath.Dir(resource.Type.Name.Package)]
|
||||
if apiGroup.Pkg != nil {
|
||||
apiGroup.PkgPath = apiGroup.Pkg.Path
|
||||
}
|
||||
|
||||
apiGroup.UnversionedResources[kind] = apiResource
|
||||
}
|
||||
|
||||
apiGroup.Versions[version] = apiVersion
|
||||
}
|
||||
b.parseStructs(apiGroup)
|
||||
apis.Groups[group] = apiGroup
|
||||
}
|
||||
apis.Pkg = b.context.Universe[b.APIsPkg]
|
||||
b.APIs = apis
|
||||
}
|
||||
|
||||
func (b *APIs) parseStructs(apigroup *codegen.APIGroup) {
|
||||
remaining := []genUnversionedType{}
|
||||
for _, version := range apigroup.Versions {
|
||||
for _, resource := range version.Resources {
|
||||
remaining = append(remaining, genUnversionedType{resource.Type, resource})
|
||||
}
|
||||
}
|
||||
for _, version := range b.SubByGroupVersionKind[apigroup.Group] {
|
||||
for _, kind := range version {
|
||||
remaining = append(remaining, genUnversionedType{kind, nil})
|
||||
}
|
||||
}
|
||||
|
||||
done := sets.String{}
|
||||
for len(remaining) > 0 {
|
||||
// Pop the next element from the list
|
||||
next := remaining[0]
|
||||
remaining[0] = remaining[len(remaining)-1]
|
||||
remaining = remaining[:len(remaining)-1]
|
||||
|
||||
// Already processed this type. Skip it
|
||||
if done.Has(next.Type.Name.Name) {
|
||||
continue
|
||||
}
|
||||
done.Insert(next.Type.Name.Name)
|
||||
|
||||
// Generate the struct and append to the list
|
||||
result, additionalTypes := parseType(next.Type)
|
||||
|
||||
// This is a resource, so generate the client
|
||||
if b.genClient(next.Type) {
|
||||
result.GenClient = true
|
||||
result.GenDeepCopy = true
|
||||
}
|
||||
|
||||
if next.Resource != nil {
|
||||
result.NonNamespaced = IsNonNamespaced(next.Type)
|
||||
}
|
||||
|
||||
if b.genDeepCopy(next.Type) {
|
||||
result.GenDeepCopy = true
|
||||
}
|
||||
apigroup.Structs = append(apigroup.Structs, result)
|
||||
|
||||
// Add the newly discovered subtypes
|
||||
for _, at := range additionalTypes {
|
||||
remaining = append(remaining, genUnversionedType{at, nil})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseType parses the type into a Struct, and returns a list of types that
|
||||
// need to be parsed
|
||||
func parseType(t *types.Type) (*codegen.Struct, []*types.Type) {
|
||||
remaining := []*types.Type{}
|
||||
|
||||
s := &codegen.Struct{
|
||||
Name: t.Name.Name,
|
||||
GenClient: false,
|
||||
GenUnversioned: true, // Generate unversioned structs by default
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+genregister:unversioned=false") {
|
||||
// Don't generate the unversioned struct
|
||||
s.GenUnversioned = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, member := range t.Members {
|
||||
uType := member.Type.Name.Name
|
||||
memberName := member.Name
|
||||
uImport := ""
|
||||
|
||||
// Use the element type for Pointers, Maps and Slices
|
||||
mSubType := member.Type
|
||||
hasElem := false
|
||||
for mSubType.Elem != nil {
|
||||
mSubType = mSubType.Elem
|
||||
hasElem = true
|
||||
}
|
||||
if hasElem {
|
||||
// Strip the package from the field type
|
||||
uType = strings.Replace(member.Type.String(), mSubType.Name.Package+".", "", 1)
|
||||
}
|
||||
|
||||
base := filepath.Base(member.Type.String())
|
||||
samepkg := t.Name.Package == mSubType.Name.Package
|
||||
|
||||
// If not in the same package, calculate the import pkg
|
||||
if !samepkg {
|
||||
parts := strings.Split(base, ".")
|
||||
if len(parts) > 1 {
|
||||
// Don't generate unversioned types for core types, just use the versioned types
|
||||
if strings.HasPrefix(mSubType.Name.Package, "k8s.io/api/") {
|
||||
// Import the package under an alias so it doesn't conflict with other groups
|
||||
// having the same version
|
||||
importAlias := path.Base(path.Dir(mSubType.Name.Package)) + path.Base(mSubType.Name.Package)
|
||||
uImport = fmt.Sprintf("%s \"%s\"", importAlias, mSubType.Name.Package)
|
||||
if hasElem {
|
||||
// Replace the full package with the alias when referring to the type
|
||||
uType = strings.Replace(member.Type.String(), mSubType.Name.Package, importAlias, 1)
|
||||
} else {
|
||||
// Replace the full package with the alias when referring to the type
|
||||
uType = fmt.Sprintf("%s.%s", importAlias, parts[1])
|
||||
}
|
||||
} else {
|
||||
switch member.Type.Name.Package {
|
||||
case "k8s.io/apimachinery/pkg/apis/meta/v1":
|
||||
// Use versioned types for meta/v1
|
||||
uImport = fmt.Sprintf("%s \"%s\"", "metav1", "k8s.io/apimachinery/pkg/apis/meta/v1")
|
||||
uType = "metav1." + parts[1]
|
||||
default:
|
||||
// Use unversioned types for everything else
|
||||
t := member.Type
|
||||
|
||||
if t.Elem != nil {
|
||||
// handle Pointers, Maps, Slices
|
||||
|
||||
// We need to parse the package from the Type String
|
||||
t = t.Elem
|
||||
str := member.Type.String()
|
||||
startPkg := strings.LastIndexAny(str, "*]")
|
||||
endPkg := strings.LastIndexAny(str, ".")
|
||||
pkg := str[startPkg+1 : endPkg]
|
||||
name := str[endPkg+1:]
|
||||
prefix := str[:startPkg+1]
|
||||
|
||||
uImportBase := path.Base(pkg)
|
||||
uImportName := path.Base(path.Dir(pkg)) + uImportBase
|
||||
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
|
||||
|
||||
uType = prefix + uImportName + "." + name
|
||||
} else {
|
||||
// handle non- Pointer, Maps, Slices
|
||||
pkg := t.Name.Package
|
||||
name := t.Name.Name
|
||||
|
||||
// Come up with the alias the package is imported under
|
||||
// Concatenate with directory package to reduce naming collisions
|
||||
uImportBase := path.Base(pkg)
|
||||
uImportName := path.Base(path.Dir(pkg)) + uImportBase
|
||||
|
||||
// Create the import statement
|
||||
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
|
||||
|
||||
// Create the field type name - should be <pkgalias>.<TypeName>
|
||||
uType = uImportName + "." + name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if member.Embedded {
|
||||
memberName = ""
|
||||
}
|
||||
|
||||
s.Fields = append(s.Fields, &codegen.Field{
|
||||
Name: memberName,
|
||||
VersionedPackage: member.Type.Name.Package,
|
||||
UnversionedImport: uImport,
|
||||
UnversionedType: uType,
|
||||
})
|
||||
|
||||
// Add this member Type for processing if it isn't a primitive and
|
||||
// is part of the same API group
|
||||
if !mSubType.IsPrimitive() && GetGroup(mSubType) == GetGroup(t) {
|
||||
remaining = append(remaining, mSubType)
|
||||
}
|
||||
}
|
||||
return s, remaining
|
||||
}
|
||||
|
||||
func (b *APIs) genClient(c *types.Type) bool {
|
||||
comments := Comments(c.CommentLines)
|
||||
resource := comments.getTag("resource", ":") + comments.getTag("kubebuilder:resource", ":")
|
||||
return len(resource) > 0
|
||||
}
|
||||
|
||||
func (b *APIs) genDeepCopy(c *types.Type) bool {
|
||||
comments := Comments(c.CommentLines)
|
||||
return comments.hasTag("subresource-request")
|
||||
}
|
||||
|
||||
func parseDoc(resource, apiResource *codegen.APIResource) {
|
||||
if HasDocAnnotation(resource.Type) {
|
||||
resource.DocAnnotation = getDocAnnotation(resource.Type, "warning", "note")
|
||||
apiResource.DocAnnotation = resource.DocAnnotation
|
||||
}
|
||||
}
|
||||
42
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/context.go
generated
vendored
Normal file
42
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/context.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2018 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 parse
|
||||
|
||||
import (
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/namer"
|
||||
"k8s.io/gengo/parser"
|
||||
)
|
||||
|
||||
// NewContext returns a new Context from the builder
|
||||
func NewContext(p *parser.Builder) (*generator.Context, error) {
|
||||
return generator.NewContext(p, NameSystems(), DefaultNameSystem())
|
||||
}
|
||||
|
||||
// DefaultNameSystem returns public by default.
|
||||
func DefaultNameSystem() string {
|
||||
return "public"
|
||||
}
|
||||
|
||||
// NameSystems returns the name system used by the generators in this package.
|
||||
// e.g. black-magic
|
||||
func NameSystems() namer.NameSystems {
|
||||
return namer.NameSystems{
|
||||
"public": namer.NewPublicNamer(1),
|
||||
"raw": namer.NewRawNamer("", nil),
|
||||
}
|
||||
}
|
||||
639
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/crd.go
generated
vendored
Normal file
639
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/crd.go
generated
vendored
Normal file
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
Copyright 2018 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 parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
// parseCRDs populates the CRD field of each Group.Version.Resource,
|
||||
// creating validations using the annotations on type fields.
|
||||
func (b *APIs) parseCRDs() {
|
||||
for _, group := range b.APIs.Groups {
|
||||
for _, version := range group.Versions {
|
||||
for _, resource := range version.Resources {
|
||||
if IsAPIResource(resource.Type) {
|
||||
resource.JSONSchemaProps, resource.Validation =
|
||||
b.typeToJSONSchemaProps(resource.Type, sets.NewString(), []string{}, true)
|
||||
|
||||
// Note: Drop the Type field at the root level of validation
|
||||
// schema. Refer to following issue for details.
|
||||
// https://github.com/kubernetes/kubernetes/issues/65293
|
||||
resource.JSONSchemaProps.Type = ""
|
||||
j, err := json.MarshalIndent(resource.JSONSchemaProps, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not Marshall validation %v\n", err)
|
||||
}
|
||||
resource.ValidationComments = string(j)
|
||||
|
||||
resource.CRD = v1beta1.CustomResourceDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apiextensions.k8s.io/v1beta1",
|
||||
Kind: "CustomResourceDefinition",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s.%s.%s", resource.Resource, resource.Group, resource.Domain),
|
||||
Labels: map[string]string{"controller-tools.k8s.io": "1.0"},
|
||||
},
|
||||
Spec: v1beta1.CustomResourceDefinitionSpec{
|
||||
Group: fmt.Sprintf("%s.%s", resource.Group, resource.Domain),
|
||||
Version: resource.Version,
|
||||
Names: v1beta1.CustomResourceDefinitionNames{
|
||||
Kind: resource.Kind,
|
||||
Plural: resource.Resource,
|
||||
},
|
||||
Validation: &v1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &resource.JSONSchemaProps,
|
||||
},
|
||||
},
|
||||
}
|
||||
if resource.NonNamespaced {
|
||||
resource.CRD.Spec.Scope = "Cluster"
|
||||
} else {
|
||||
resource.CRD.Spec.Scope = "Namespaced"
|
||||
}
|
||||
|
||||
if hasCategories(resource.Type) {
|
||||
categoriesTag := getCategoriesTag(resource.Type)
|
||||
categories := strings.Split(categoriesTag, ",")
|
||||
resource.CRD.Spec.Names.Categories = categories
|
||||
resource.Categories = categories
|
||||
}
|
||||
|
||||
if hasSingular(resource.Type) {
|
||||
singularName := getSingularName(resource.Type)
|
||||
resource.CRD.Spec.Names.Singular = singularName
|
||||
}
|
||||
|
||||
if hasStatusSubresource(resource.Type) {
|
||||
if resource.CRD.Spec.Subresources == nil {
|
||||
resource.CRD.Spec.Subresources = &v1beta1.CustomResourceSubresources{}
|
||||
}
|
||||
resource.CRD.Spec.Subresources.Status = &v1beta1.CustomResourceSubresourceStatus{}
|
||||
}
|
||||
|
||||
resource.CRD.Status.Conditions = []v1beta1.CustomResourceDefinitionCondition{}
|
||||
resource.CRD.Status.StoredVersions = []string{}
|
||||
|
||||
if hasScaleSubresource(resource.Type) {
|
||||
if resource.CRD.Spec.Subresources == nil {
|
||||
resource.CRD.Spec.Subresources = &v1beta1.CustomResourceSubresources{}
|
||||
}
|
||||
jsonPath, err := parseScaleParams(resource.Type)
|
||||
if err != nil {
|
||||
log.Fatalf("failed in parsing CRD, error: %v", err.Error())
|
||||
}
|
||||
resource.CRD.Spec.Subresources.Scale = &v1beta1.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: jsonPath[specReplicasPath],
|
||||
StatusReplicasPath: jsonPath[statusReplicasPath],
|
||||
}
|
||||
labelSelctor, ok := jsonPath[labelSelectorPath]
|
||||
if ok && labelSelctor != "" {
|
||||
resource.CRD.Spec.Subresources.Scale.LabelSelectorPath = &labelSelctor
|
||||
}
|
||||
}
|
||||
if hasPrintColumn(resource.Type) {
|
||||
result, err := parsePrintColumnParams(resource.Type)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse printcolumn annotations, error: %v", err.Error())
|
||||
}
|
||||
resource.CRD.Spec.AdditionalPrinterColumns = result
|
||||
}
|
||||
if len(resource.ShortName) > 0 {
|
||||
resource.CRD.Spec.Names.ShortNames = strings.Split(resource.ShortName, ";")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *APIs) getTime() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Format: "date-time",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) getDuration() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) getQuantity() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) objSchema() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
}`
|
||||
}
|
||||
|
||||
// typeToJSONSchemaProps returns a JSONSchemaProps object and its serialization
|
||||
// in Go that describe the JSONSchema validations for the given type.
|
||||
func (b *APIs) typeToJSONSchemaProps(t *types.Type, found sets.String, comments []string, isRoot bool) (v1beta1.JSONSchemaProps, string) {
|
||||
// Special cases
|
||||
time := types.Name{Name: "Time", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
duration := types.Name{Name: "Duration", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
quantity := types.Name{Name: "Quantity", Package: "k8s.io/apimachinery/pkg/api/resource"}
|
||||
meta := types.Name{Name: "ObjectMeta", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
unstructured := types.Name{Name: "Unstructured", Package: "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"}
|
||||
rawExtension := types.Name{Name: "RawExtension", Package: "k8s.io/apimachinery/pkg/runtime"}
|
||||
intOrString := types.Name{Name: "IntOrString", Package: "k8s.io/apimachinery/pkg/util/intstr"}
|
||||
// special types first
|
||||
specialTypeProps := v1beta1.JSONSchemaProps{
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
for _, l := range comments {
|
||||
getValidation(l, &specialTypeProps)
|
||||
}
|
||||
switch t.Name {
|
||||
case time:
|
||||
specialTypeProps.Type = "string"
|
||||
specialTypeProps.Format = "date-time"
|
||||
return specialTypeProps, b.getTime()
|
||||
case duration:
|
||||
specialTypeProps.Type = "string"
|
||||
return specialTypeProps, b.getDuration()
|
||||
case quantity:
|
||||
specialTypeProps.Type = "string"
|
||||
return specialTypeProps, b.getQuantity()
|
||||
case meta, unstructured, rawExtension:
|
||||
specialTypeProps.Type = "object"
|
||||
return specialTypeProps, b.objSchema()
|
||||
case intOrString:
|
||||
specialTypeProps.AnyOf = []v1beta1.JSONSchemaProps{
|
||||
{
|
||||
Type: "string",
|
||||
},
|
||||
{
|
||||
Type: "integer",
|
||||
},
|
||||
}
|
||||
return specialTypeProps, b.objSchema()
|
||||
}
|
||||
|
||||
var v v1beta1.JSONSchemaProps
|
||||
var s string
|
||||
switch t.Kind {
|
||||
case types.Builtin:
|
||||
v, s = b.parsePrimitiveValidation(t, found, comments)
|
||||
case types.Struct:
|
||||
v, s = b.parseObjectValidation(t, found, comments, isRoot)
|
||||
case types.Map:
|
||||
v, s = b.parseMapValidation(t, found, comments)
|
||||
case types.Slice:
|
||||
v, s = b.parseArrayValidation(t, found, comments)
|
||||
case types.Array:
|
||||
v, s = b.parseArrayValidation(t, found, comments)
|
||||
case types.Pointer:
|
||||
v, s = b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
case types.Alias:
|
||||
v, s = b.typeToJSONSchemaProps(t.Underlying, found, comments, false)
|
||||
default:
|
||||
log.Fatalf("Unknown supported Kind %v\n", t.Kind)
|
||||
}
|
||||
|
||||
return v, s
|
||||
}
|
||||
|
||||
var jsonRegex = regexp.MustCompile("json:\"([a-zA-Z0-9,]+)\"")
|
||||
|
||||
type primitiveTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
Value string
|
||||
Format string
|
||||
EnumValue string // TODO check type of enum value to match the type of field
|
||||
Description string
|
||||
}
|
||||
|
||||
var primitiveTemplate = template.Must(template.New("map-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
{{ if .Pattern -}}
|
||||
Pattern: "{{ .Pattern }}",
|
||||
{{ end -}}
|
||||
{{ if .Maximum -}}
|
||||
Maximum: getFloat({{ .Maximum }}),
|
||||
{{ end -}}
|
||||
{{ if .ExclusiveMaximum -}}
|
||||
ExclusiveMaximum: {{ .ExclusiveMaximum }},
|
||||
{{ end -}}
|
||||
{{ if .Minimum -}}
|
||||
Minimum: getFloat({{ .Minimum }}),
|
||||
{{ end -}}
|
||||
{{ if .ExclusiveMinimum -}}
|
||||
ExclusiveMinimum: {{ .ExclusiveMinimum }},
|
||||
{{ end -}}
|
||||
Type: "{{ .Value }}",
|
||||
{{ if .Format -}}
|
||||
Format: "{{ .Format }}",
|
||||
{{ end -}}
|
||||
{{ if .EnumValue -}}
|
||||
Enum: {{ .EnumValue }},
|
||||
{{ end -}}
|
||||
{{ if .MaxLength -}}
|
||||
MaxLength: getInt({{ .MaxLength }}),
|
||||
{{ end -}}
|
||||
{{ if .MinLength -}}
|
||||
MinLength: getInt({{ .MinLength }}),
|
||||
{{ end -}}
|
||||
}`))
|
||||
|
||||
// parsePrimitiveValidation returns a JSONSchemaProps object and its
|
||||
// serialization in Go that describe the validations for the given primitive
|
||||
// type.
|
||||
func (b *APIs) parsePrimitiveValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
props := v1beta1.JSONSchemaProps{Type: string(t.Name.Name)}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
|
||||
var n, f, s, d string
|
||||
switch t.Name.Name {
|
||||
case "int", "int64", "uint64":
|
||||
n = "integer"
|
||||
f = "int64"
|
||||
case "int32", "uint32":
|
||||
n = "integer"
|
||||
f = "int32"
|
||||
case "float", "float32":
|
||||
n = "number"
|
||||
f = "float"
|
||||
case "float64":
|
||||
n = "number"
|
||||
f = "double"
|
||||
case "bool":
|
||||
n = "boolean"
|
||||
case "string":
|
||||
n = "string"
|
||||
f = props.Format
|
||||
default:
|
||||
n = t.Name.Name
|
||||
}
|
||||
if props.Enum != nil {
|
||||
s = parseEnumToString(props.Enum)
|
||||
}
|
||||
d = parseDescription(comments)
|
||||
if err := primitiveTemplate.Execute(buff, primitiveTemplateArgs{props, n, f, s, d}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
props.Type = n
|
||||
props.Format = f
|
||||
props.Description = d
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
type mapTempateArgs struct {
|
||||
Result string
|
||||
SkipMapValidation bool
|
||||
}
|
||||
|
||||
var mapTemplate = template.Must(template.New("map-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
{{if not .SkipMapValidation}}AdditionalProperties: &v1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
Schema: &{{.Result}},
|
||||
},{{end}}
|
||||
}`))
|
||||
|
||||
// parseMapValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given map type.
|
||||
func (b *APIs) parseMapValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
additionalProps, result := b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
additionalProps.Description = ""
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
parseOption := b.arguments.CustomArgs.(*Options)
|
||||
if !parseOption.SkipMapValidation {
|
||||
props.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
Schema: &additionalProps}
|
||||
}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
if err := mapTemplate.Execute(buff, mapTempateArgs{Result: result, SkipMapValidation: parseOption.SkipMapValidation}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
var arrayTemplate = template.Must(template.New("array-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
Type: "{{.Type}}",
|
||||
{{ if .Format -}}
|
||||
Format: "{{.Format}}",
|
||||
{{ end -}}
|
||||
{{ if .MaxItems -}}
|
||||
MaxItems: getInt({{ .MaxItems }}),
|
||||
{{ end -}}
|
||||
{{ if .MinItems -}}
|
||||
MinItems: getInt({{ .MinItems }}),
|
||||
{{ end -}}
|
||||
{{ if .UniqueItems -}}
|
||||
UniqueItems: {{ .UniqueItems }},
|
||||
{{ end -}}
|
||||
{{ if .Items -}}
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &{{.ItemsSchema}},
|
||||
},
|
||||
{{ end -}}
|
||||
}`))
|
||||
|
||||
type arrayTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
ItemsSchema string
|
||||
}
|
||||
|
||||
// parseArrayValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given array type.
|
||||
func (b *APIs) parseArrayValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
items, result := b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
items.Description = ""
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{Schema: &items},
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
// To represent byte arrays in the generated code, the property of the OpenAPI definition
|
||||
// should have string as its type and byte as its format.
|
||||
if t.Name.Name == "[]byte" {
|
||||
props.Type = "string"
|
||||
props.Format = "byte"
|
||||
props.Items = nil
|
||||
props.Description = parseDescription(comments)
|
||||
}
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
if t.Name.Name != "[]byte" {
|
||||
// Except for the byte array special case above, the "format" property
|
||||
// should be applied to the array items and not the array itself.
|
||||
props.Format = ""
|
||||
}
|
||||
buff := &bytes.Buffer{}
|
||||
if err := arrayTemplate.Execute(buff, arrayTemplateArgs{props, result}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
type objectTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
Fields map[string]string
|
||||
Required []string
|
||||
IsRoot bool
|
||||
}
|
||||
|
||||
var objectTemplate = template.Must(template.New("object-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
{{ if not .IsRoot -}}
|
||||
Type: "object",
|
||||
{{ end -}}
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
{{ range $k, $v := .Fields -}}
|
||||
"{{ $k }}": {{ $v }},
|
||||
{{ end -}}
|
||||
},
|
||||
{{if .Required}}Required: []string{
|
||||
{{ range $k, $v := .Required -}}
|
||||
"{{ $v }}",
|
||||
{{ end -}}
|
||||
},{{ end -}}
|
||||
}`))
|
||||
|
||||
// parseObjectValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given object type.
|
||||
func (b *APIs) parseObjectValidation(t *types.Type, found sets.String, comments []string, isRoot bool) (v1beta1.JSONSchemaProps, string) {
|
||||
buff := &bytes.Buffer{}
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(t.Name.String(), "k8s.io/api") {
|
||||
if err := objectTemplate.Execute(buff, objectTemplateArgs{props, nil, nil, false}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
} else {
|
||||
m, result, required := b.getMembers(t, found)
|
||||
props.Properties = m
|
||||
props.Required = required
|
||||
|
||||
if err := objectTemplate.Execute(buff, objectTemplateArgs{props, result, required, isRoot}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
// getValidation parses the validation tags from the comment and sets the
|
||||
// validation rules on the given JSONSchemaProps.
|
||||
func getValidation(comment string, props *v1beta1.JSONSchemaProps) {
|
||||
comment = strings.TrimLeft(comment, " ")
|
||||
if !strings.HasPrefix(comment, "+kubebuilder:validation:") {
|
||||
return
|
||||
}
|
||||
c := strings.Replace(comment, "+kubebuilder:validation:", "", -1)
|
||||
parts := strings.Split(c, "=")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected +kubebuilder:validation:<key>=<value> actual: %s", comment)
|
||||
return
|
||||
}
|
||||
switch parts[0] {
|
||||
case "Maximum":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.Maximum = &f
|
||||
case "ExclusiveMaximum":
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.ExclusiveMaximum = b
|
||||
case "Minimum":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.Minimum = &f
|
||||
case "ExclusiveMinimum":
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.ExclusiveMinimum = b
|
||||
case "MaxLength":
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MaxLength = &v
|
||||
case "MinLength":
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MinLength = &v
|
||||
case "Pattern":
|
||||
props.Pattern = parts[1]
|
||||
case "MaxItems":
|
||||
if props.Type == "array" {
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MaxItems = &v
|
||||
}
|
||||
case "MinItems":
|
||||
if props.Type == "array" {
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MinItems = &v
|
||||
}
|
||||
case "UniqueItems":
|
||||
if props.Type == "array" {
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.UniqueItems = b
|
||||
}
|
||||
case "MultipleOf":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MultipleOf = &f
|
||||
case "Enum":
|
||||
if props.Type != "array" {
|
||||
value := strings.Split(parts[1], ",")
|
||||
enums := []v1beta1.JSON{}
|
||||
for _, s := range value {
|
||||
checkType(props, s, &enums)
|
||||
}
|
||||
props.Enum = enums
|
||||
}
|
||||
case "Format":
|
||||
props.Format = parts[1]
|
||||
default:
|
||||
log.Fatalf("Unsupport validation: %s", comment)
|
||||
}
|
||||
}
|
||||
|
||||
// getMembers builds maps by field name of the JSONSchemaProps and their Go
|
||||
// serializations.
|
||||
func (b *APIs) getMembers(t *types.Type, found sets.String) (map[string]v1beta1.JSONSchemaProps, map[string]string, []string) {
|
||||
members := map[string]v1beta1.JSONSchemaProps{}
|
||||
result := map[string]string{}
|
||||
required := []string{}
|
||||
|
||||
// Don't allow recursion until we support it through refs
|
||||
// TODO: Support recursion
|
||||
if found.Has(t.Name.String()) {
|
||||
fmt.Printf("Breaking recursion for type %s", t.Name.String())
|
||||
return members, result, required
|
||||
}
|
||||
found.Insert(t.Name.String())
|
||||
|
||||
for _, member := range t.Members {
|
||||
tags := jsonRegex.FindStringSubmatch(member.Tags)
|
||||
if len(tags) == 0 {
|
||||
// Skip fields without json tags
|
||||
//fmt.Printf("Skipping member %s %s\n", member.Name, member.Type.Name.String())
|
||||
continue
|
||||
}
|
||||
ts := strings.Split(tags[1], ",")
|
||||
name := member.Name
|
||||
strat := ""
|
||||
if len(ts) > 0 && len(ts[0]) > 0 {
|
||||
name = ts[0]
|
||||
}
|
||||
if len(ts) > 1 {
|
||||
strat = ts[1]
|
||||
}
|
||||
|
||||
// Inline "inline" structs
|
||||
if strat == "inline" {
|
||||
m, r, re := b.getMembers(member.Type, found)
|
||||
for n, v := range m {
|
||||
members[n] = v
|
||||
}
|
||||
for n, v := range r {
|
||||
result[n] = v
|
||||
}
|
||||
required = append(required, re...)
|
||||
} else {
|
||||
m, r := b.typeToJSONSchemaProps(member.Type, found, member.CommentLines, false)
|
||||
members[name] = m
|
||||
result[name] = r
|
||||
if !strings.HasSuffix(strat, "omitempty") {
|
||||
required = append(required, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer found.Delete(t.Name.String())
|
||||
return members, result, required
|
||||
}
|
||||
161
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/index.go
generated
vendored
Normal file
161
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/index.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2018 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 parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gobuffalo/flect"
|
||||
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
// parseIndex indexes all types with the comment "// +resource=RESOURCE" by GroupVersionKind and
|
||||
// GroupKindVersion
|
||||
func (b *APIs) parseIndex() {
|
||||
// Index resource by group, version, kind
|
||||
b.ByGroupVersionKind = map[string]map[string]map[string]*codegen.APIResource{}
|
||||
|
||||
// Index resources by group, kind, version
|
||||
b.ByGroupKindVersion = map[string]map[string]map[string]*codegen.APIResource{}
|
||||
|
||||
// Index subresources by group, version, kind
|
||||
b.SubByGroupVersionKind = map[string]map[string]map[string]*types.Type{}
|
||||
|
||||
for _, c := range b.context.Order {
|
||||
// The type is a subresource, add it to the subresource index
|
||||
if IsAPISubresource(c) {
|
||||
group := GetGroup(c)
|
||||
version := GetVersion(c, group)
|
||||
kind := GetKind(c, group)
|
||||
if _, f := b.SubByGroupVersionKind[group]; !f {
|
||||
b.SubByGroupVersionKind[group] = map[string]map[string]*types.Type{}
|
||||
}
|
||||
if _, f := b.SubByGroupVersionKind[group][version]; !f {
|
||||
b.SubByGroupVersionKind[group][version] = map[string]*types.Type{}
|
||||
}
|
||||
b.SubByGroupVersionKind[group][version][kind] = c
|
||||
}
|
||||
|
||||
// If it isn't a subresource or resource, continue to the next type
|
||||
if !IsAPIResource(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse out the resource information
|
||||
r := &codegen.APIResource{
|
||||
Type: c,
|
||||
NonNamespaced: IsNonNamespaced(c),
|
||||
}
|
||||
r.Group = GetGroup(c)
|
||||
r.Version = GetVersion(c, r.Group)
|
||||
r.Kind = GetKind(c, r.Group)
|
||||
r.Domain = b.Domain
|
||||
|
||||
// TODO: revisit the part...
|
||||
if r.Resource == "" {
|
||||
r.Resource = flect.Pluralize(strings.ToLower(r.Kind))
|
||||
}
|
||||
rt, err := parseResourceAnnotation(c)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse resource annotations, error: %v", err.Error())
|
||||
}
|
||||
if rt.Resource != "" {
|
||||
r.Resource = rt.Resource
|
||||
}
|
||||
r.ShortName = rt.ShortName
|
||||
|
||||
// Copy the Status strategy to mirror the non-status strategy
|
||||
r.StatusStrategy = strings.TrimSuffix(r.Strategy, "Strategy")
|
||||
r.StatusStrategy = fmt.Sprintf("%sStatusStrategy", r.StatusStrategy)
|
||||
|
||||
// Initialize the map entries so they aren't nill
|
||||
if _, f := b.ByGroupKindVersion[r.Group]; !f {
|
||||
b.ByGroupKindVersion[r.Group] = map[string]map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupKindVersion[r.Group][r.Kind]; !f {
|
||||
b.ByGroupKindVersion[r.Group][r.Kind] = map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupVersionKind[r.Group]; !f {
|
||||
b.ByGroupVersionKind[r.Group] = map[string]map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupVersionKind[r.Group][r.Version]; !f {
|
||||
b.ByGroupVersionKind[r.Group][r.Version] = map[string]*codegen.APIResource{}
|
||||
}
|
||||
|
||||
// Add the resource to the map
|
||||
b.ByGroupKindVersion[r.Group][r.Kind][r.Version] = r
|
||||
b.ByGroupVersionKind[r.Group][r.Version][r.Kind] = r
|
||||
r.Type = c
|
||||
}
|
||||
}
|
||||
|
||||
// resourceTags contains the tags present in a "+resource=" comment
|
||||
type resourceTags struct {
|
||||
Resource string
|
||||
REST string
|
||||
Strategy string
|
||||
ShortName string
|
||||
}
|
||||
|
||||
// resourceAnnotationValue is a helper function to extract resource annotation.
|
||||
func resourceAnnotationValue(tag string) (resourceTags, error) {
|
||||
res := resourceTags{}
|
||||
for _, elem := range strings.Split(tag, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
return resourceTags{}, fmt.Errorf("// +kubebuilder:resource: tags must be key value pairs. Expected "+
|
||||
"keys [path=<resourcepath>] "+
|
||||
"Got string: [%s]", tag)
|
||||
}
|
||||
switch key {
|
||||
case "path":
|
||||
res.Resource = value
|
||||
case "shortName":
|
||||
res.ShortName = value
|
||||
default:
|
||||
return resourceTags{}, fmt.Errorf("The given input %s is invalid", value)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// parseResourceAnnotation parses the tags in a "+resource=" comment into a resourceTags struct.
|
||||
func parseResourceAnnotation(t *types.Type) (resourceTags, error) {
|
||||
finalResult := resourceTags{}
|
||||
var resourceAnnotationFound bool
|
||||
for _, comment := range t.CommentLines {
|
||||
anno := general.GetAnnotation(comment, "kubebuilder:resource")
|
||||
if len(anno) == 0 {
|
||||
continue
|
||||
}
|
||||
result, err := resourceAnnotationValue(anno)
|
||||
if err != nil {
|
||||
return resourceTags{}, err
|
||||
}
|
||||
if resourceAnnotationFound {
|
||||
return resourceTags{}, fmt.Errorf("resource annotation should only exists once per type")
|
||||
}
|
||||
resourceAnnotationFound = true
|
||||
finalResult = result
|
||||
}
|
||||
return finalResult, nil
|
||||
}
|
||||
151
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/parser.go
generated
vendored
Normal file
151
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/parser.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2018 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 parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
)
|
||||
|
||||
// APIs is the information of a collection of API
|
||||
type APIs struct {
|
||||
context *generator.Context
|
||||
arguments *args.GeneratorArgs
|
||||
Domain string
|
||||
VersionedPkgs sets.String
|
||||
UnversionedPkgs sets.String
|
||||
APIsPkg string
|
||||
APIsPkgRaw *types.Package
|
||||
GroupNames sets.String
|
||||
|
||||
APIs *codegen.APIs
|
||||
Controllers []codegen.Controller
|
||||
|
||||
ByGroupKindVersion map[string]map[string]map[string]*codegen.APIResource
|
||||
ByGroupVersionKind map[string]map[string]map[string]*codegen.APIResource
|
||||
SubByGroupVersionKind map[string]map[string]map[string]*types.Type
|
||||
Groups map[string]types.Package
|
||||
Rules []rbacv1.PolicyRule
|
||||
Informers map[v1.GroupVersionKind]bool
|
||||
}
|
||||
|
||||
// NewAPIs returns a new APIs instance with given context.
|
||||
func NewAPIs(context *generator.Context, arguments *args.GeneratorArgs, domain, apisPkg string) *APIs {
|
||||
b := &APIs{
|
||||
context: context,
|
||||
arguments: arguments,
|
||||
Domain: domain,
|
||||
APIsPkg: apisPkg,
|
||||
}
|
||||
b.parsePackages()
|
||||
b.parseGroupNames()
|
||||
b.parseIndex()
|
||||
b.parseAPIs()
|
||||
b.parseCRDs()
|
||||
if len(b.Domain) == 0 {
|
||||
b.parseDomain()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// parseGroupNames initializes b.GroupNames with the set of all groups
|
||||
func (b *APIs) parseGroupNames() {
|
||||
b.GroupNames = sets.String{}
|
||||
for p := range b.UnversionedPkgs {
|
||||
pkg := b.context.Universe[p]
|
||||
if pkg == nil {
|
||||
// If the input had no Go files, for example.
|
||||
continue
|
||||
}
|
||||
b.GroupNames.Insert(filepath.Base(p))
|
||||
}
|
||||
}
|
||||
|
||||
// parsePackages parses out the sets of Versioned, Unversioned packages and identifies the root Apis package.
|
||||
func (b *APIs) parsePackages() {
|
||||
b.VersionedPkgs = sets.NewString()
|
||||
b.UnversionedPkgs = sets.NewString()
|
||||
for _, o := range b.context.Order {
|
||||
if IsAPIResource(o) {
|
||||
versioned := o.Name.Package
|
||||
b.VersionedPkgs.Insert(versioned)
|
||||
|
||||
unversioned := filepath.Dir(versioned)
|
||||
b.UnversionedPkgs.Insert(unversioned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseDomain parses the domain from the apis/doc.go file comment "// +domain=YOUR_DOMAIN".
|
||||
func (b *APIs) parseDomain() {
|
||||
pkg := b.context.Universe[b.APIsPkg]
|
||||
if pkg == nil {
|
||||
// If the input had no Go files, for example.
|
||||
panic(errors.Errorf("Missing apis package."))
|
||||
}
|
||||
comments := Comments(pkg.Comments)
|
||||
b.Domain = comments.getTag("domain", "=")
|
||||
if len(b.Domain) == 0 {
|
||||
b.Domain = parseDomainFromFiles(b.context.Inputs)
|
||||
if len(b.Domain) == 0 {
|
||||
panic("Could not find string matching // +domain=.+ in apis/doc.go")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseDomainFromFiles(paths []string) string {
|
||||
var domain string
|
||||
for _, path := range paths {
|
||||
if strings.HasSuffix(path, "pkg/apis") {
|
||||
filePath := strings.Join([]string{build.Default.GOPATH, "src", path, "doc.go"}, "/")
|
||||
lines := []string{}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "//") {
|
||||
lines = append(lines, strings.Replace(scanner.Text(), "// ", "", 1))
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
comments := Comments(lines)
|
||||
domain = comments.getTag("domain", "=")
|
||||
break
|
||||
}
|
||||
}
|
||||
return domain
|
||||
}
|
||||
539
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/util.go
generated
vendored
Normal file
539
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/util.go
generated
vendored
Normal file
@@ -0,0 +1,539 @@
|
||||
/*
|
||||
Copyright 2018 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 parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
const (
|
||||
specReplicasPath = "specpath"
|
||||
statusReplicasPath = "statuspath"
|
||||
labelSelectorPath = "selectorpath"
|
||||
jsonPathError = "invalid scale path. specpath, statuspath key-value pairs are required, only selectorpath key-value is optinal. For example: // +kubebuilder:subresource:scale:specpath=.spec.replica,statuspath=.status.replica,selectorpath=.spec.Label"
|
||||
printColumnName = "name"
|
||||
printColumnType = "type"
|
||||
printColumnDescr = "description"
|
||||
printColumnPath = "JSONPath"
|
||||
printColumnFormat = "format"
|
||||
printColumnPri = "priority"
|
||||
printColumnError = "invalid printcolumn path. name,type, and JSONPath are required kye-value pairs and rest of the fields are optinal. For example: // +kubebuilder:printcolumn:name=abc,type=string,JSONPath=status"
|
||||
)
|
||||
|
||||
// Options contains the parser options
|
||||
type Options struct {
|
||||
SkipMapValidation bool
|
||||
|
||||
// SkipRBACValidation flag determines whether to check RBAC annotations
|
||||
// for the controller or not at parse stage.
|
||||
SkipRBACValidation bool
|
||||
}
|
||||
|
||||
// IsAPIResource returns true if either of the two conditions become true:
|
||||
// 1. t has a +resource/+kubebuilder:resource comment tag
|
||||
// 2. t has TypeMeta and ObjectMeta in its member list.
|
||||
func IsAPIResource(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+resource") || strings.Contains(c, "+kubebuilder:resource") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
typeMetaFound, objMetaFound := false, false
|
||||
for _, m := range t.Members {
|
||||
if m.Name == "TypeMeta" && m.Type.String() == "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta" {
|
||||
typeMetaFound = true
|
||||
}
|
||||
if m.Name == "ObjectMeta" && m.Type.String() == "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" {
|
||||
objMetaFound = true
|
||||
}
|
||||
if typeMetaFound && objMetaFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNonNamespaced returns true if t has a +nonNamespaced comment tag
|
||||
func IsNonNamespaced(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+genclient:nonNamespaced") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range t.SecondClosestCommentLines {
|
||||
if strings.Contains(c, "+genclient:nonNamespaced") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsController returns true if t has a +controller or +kubebuilder:controller tag
|
||||
func IsController(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+controller") || strings.Contains(c, "+kubebuilder:controller") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRBAC returns true if t has a +rbac or +kubebuilder:rbac tag
|
||||
func IsRBAC(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+rbac") || strings.Contains(c, "+kubebuilder:rbac") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPrintColumn returns true if t has a +printcolumn or +kubebuilder:printcolumn annotation.
|
||||
func hasPrintColumn(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+printcolumn") || strings.Contains(c, "+kubebuilder:printcolumn") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInformer returns true if t has a +informers or +kubebuilder:informers tag
|
||||
func IsInformer(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+informers") || strings.Contains(c, "+kubebuilder:informers") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAPISubresource returns true if t has a +subresource-request comment tag
|
||||
func IsAPISubresource(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+subresource-request") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasSubresource returns true if t is an APIResource with one or more Subresources
|
||||
func HasSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "subresource") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasStatusSubresource returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:subresource:status
|
||||
func hasStatusSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:status") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasScaleSubresource returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:subresource:scale
|
||||
func hasScaleSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:scale") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasCategories returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:categories
|
||||
func hasCategories(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:categories") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasDocAnnotation returns true if t is an APIResource with doc annotation
|
||||
// +kubebuilder:doc
|
||||
func HasDocAnnotation(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:doc") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasSingular returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:singular
|
||||
func hasSingular(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines{
|
||||
if strings.Contains(c, "+kubebuilder:singular"){
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnversioned returns true if t is in given group, and not in versioned path.
|
||||
func IsUnversioned(t *types.Type, group string) bool {
|
||||
return IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) && GetGroup(t) == group
|
||||
}
|
||||
|
||||
// IsVersioned returns true if t is in given group, and in versioned path.
|
||||
func IsVersioned(t *types.Type, group string) bool {
|
||||
dir := filepath.Base(filepath.Dir(filepath.Dir(t.Name.Package)))
|
||||
return IsApisDir(dir) && GetGroup(t) == group
|
||||
}
|
||||
|
||||
// GetVersion returns version of t.
|
||||
func GetVersion(t *types.Type, group string) string {
|
||||
if !IsVersioned(t, group) {
|
||||
panic(errors.Errorf("Cannot get version for unversioned type %v", t.Name))
|
||||
}
|
||||
return filepath.Base(t.Name.Package)
|
||||
}
|
||||
|
||||
// GetGroup returns group of t.
|
||||
func GetGroup(t *types.Type) string {
|
||||
return filepath.Base(GetGroupPackage(t))
|
||||
}
|
||||
|
||||
// GetGroupPackage returns group package of t.
|
||||
func GetGroupPackage(t *types.Type) string {
|
||||
if IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) {
|
||||
return t.Name.Package
|
||||
}
|
||||
return filepath.Dir(t.Name.Package)
|
||||
}
|
||||
|
||||
// GetKind returns kind of t.
|
||||
func GetKind(t *types.Type, group string) string {
|
||||
if !IsVersioned(t, group) && !IsUnversioned(t, group) {
|
||||
panic(errors.Errorf("Cannot get kind for type not in group %v", t.Name))
|
||||
}
|
||||
return t.Name.Name
|
||||
}
|
||||
|
||||
// IsApisDir returns true if a directory path is a Kubernetes api directory
|
||||
func IsApisDir(dir string) bool {
|
||||
return dir == "apis" || dir == "api"
|
||||
}
|
||||
|
||||
// Comments is a structure for using comment tags on go structs and fields
|
||||
type Comments []string
|
||||
|
||||
// GetTags returns the value for the first comment with a prefix matching "+name="
|
||||
// e.g. "+name=foo\n+name=bar" would return "foo"
|
||||
func (c Comments) getTag(name, sep string) string {
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s%s", name, sep)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return strings.Replace(c, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// hasTag returns true if the Comments has a tag with the given name
|
||||
func (c Comments) hasTag(name string) bool {
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s", name)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTags returns the value for all comments with a prefix and separator. E.g. for "name" and "="
|
||||
// "+name=foo\n+name=bar" would return []string{"foo", "bar"}
|
||||
func (c Comments) getTags(name, sep string) []string {
|
||||
tags := []string{}
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s%s", name, sep)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
tags = append(tags, strings.Replace(c, prefix, "", 1))
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// getCategoriesTag returns the value of the +kubebuilder:categories tags
|
||||
func getCategoriesTag(c *types.Type) string {
|
||||
comments := Comments(c.CommentLines)
|
||||
resource := comments.getTag("kubebuilder:categories", "=")
|
||||
if len(resource) == 0 {
|
||||
panic(errors.Errorf("Must specify +kubebuilder:categories comment for type %v", c.Name))
|
||||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
// getSingularName returns the value of the +kubebuilder:singular tag
|
||||
func getSingularName(c *types.Type) string {
|
||||
comments := Comments(c.CommentLines)
|
||||
singular := comments.getTag("kubebuilder:singular", "=")
|
||||
if len(singular) == 0 {
|
||||
panic(errors.Errorf("Must specify a value to use with +kubebuilder:singular comment for type %v", c.Name))
|
||||
}
|
||||
return singular
|
||||
}
|
||||
|
||||
// getDocAnnotation parse annotations of "+kubebuilder:doc:" with tags of "warning" or "doc" for control generating doc config.
|
||||
// E.g. +kubebuilder:doc:warning=foo +kubebuilder:doc:note=bar
|
||||
func getDocAnnotation(t *types.Type, tags ...string) map[string]string {
|
||||
annotation := make(map[string]string)
|
||||
for _, tag := range tags {
|
||||
for _, c := range t.CommentLines {
|
||||
prefix := fmt.Sprintf("+kubebuilder:doc:%s=", tag)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
annotation[tag] = strings.Replace(c, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotation
|
||||
}
|
||||
|
||||
// parseByteValue returns the literal digital number values from a byte array
|
||||
func parseByteValue(b []byte) string {
|
||||
elem := strings.Join(strings.Fields(fmt.Sprintln(b)), ",")
|
||||
elem = strings.TrimPrefix(elem, "[")
|
||||
elem = strings.TrimSuffix(elem, "]")
|
||||
return elem
|
||||
}
|
||||
|
||||
// parseDescription parse comments above each field in the type definition.
|
||||
func parseDescription(res []string) string {
|
||||
var temp strings.Builder
|
||||
var desc string
|
||||
for _, comment := range res {
|
||||
if !(strings.Contains(comment, "+kubebuilder") || strings.Contains(comment, "+optional")) {
|
||||
temp.WriteString(comment)
|
||||
temp.WriteString(" ")
|
||||
desc = strings.TrimRight(temp.String(), " ")
|
||||
}
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
// parseEnumToString returns a representive validated go format string from JSONSchemaProps schema
|
||||
func parseEnumToString(value []v1beta1.JSON) string {
|
||||
res := "[]v1beta1.JSON{"
|
||||
prefix := "v1beta1.JSON{[]byte{"
|
||||
for _, v := range value {
|
||||
res = res + prefix + parseByteValue(v.Raw) + "}},"
|
||||
}
|
||||
return strings.TrimSuffix(res, ",") + "}"
|
||||
}
|
||||
|
||||
// check type of enum element value to match type of field
|
||||
func checkType(props *v1beta1.JSONSchemaProps, s string, enums *[]v1beta1.JSON) {
|
||||
|
||||
// TODO support more types check
|
||||
switch props.Type {
|
||||
case "int", "int64", "uint64":
|
||||
if _, err := strconv.ParseInt(s, 0, 64); err != nil {
|
||||
log.Fatalf("Invalid integer value [%v] for a field of integer type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "int32", "unit32":
|
||||
if _, err := strconv.ParseInt(s, 0, 32); err != nil {
|
||||
log.Fatalf("Invalid integer value [%v] for a field of integer32 type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "float", "float32":
|
||||
if _, err := strconv.ParseFloat(s, 32); err != nil {
|
||||
log.Fatalf("Invalid float value [%v] for a field of float32 type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "float64":
|
||||
if _, err := strconv.ParseFloat(s, 64); err != nil {
|
||||
log.Fatalf("Invalid float value [%v] for a field of float type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "string":
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(`"` + s + `"`)})
|
||||
}
|
||||
}
|
||||
|
||||
// Scale subresource requires specpath, statuspath, selectorpath key values, represents for JSONPath of
|
||||
// SpecReplicasPath, StatusReplicasPath, LabelSelectorPath separately. e.g.
|
||||
// +kubebuilder:subresource:scale:specpath=.spec.replica,statuspath=.status.replica,selectorpath=
|
||||
func parseScaleParams(t *types.Type) (map[string]string, error) {
|
||||
jsonPath := make(map[string]string)
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:scale") {
|
||||
paths := strings.Replace(c, "+kubebuilder:subresource:scale:", "", -1)
|
||||
path := strings.Split(paths, ",")
|
||||
if len(path) < 2 {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
for _, s := range path {
|
||||
kv := strings.Split(s, "=")
|
||||
if kv[0] == specReplicasPath || kv[0] == statusReplicasPath || kv[0] == labelSelectorPath {
|
||||
jsonPath[kv[0]] = kv[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
}
|
||||
var ok bool
|
||||
_, ok = jsonPath[specReplicasPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
_, ok = jsonPath[statusReplicasPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
return jsonPath, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
|
||||
// printColumnKV parses key-value string formatted as "foo=bar" and returns key and value.
|
||||
func printColumnKV(s string) (key, value string, err error) {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
err = fmt.Errorf("invalid key value pair")
|
||||
return key, value, err
|
||||
}
|
||||
key, value = kv[0], kv[1]
|
||||
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
return key, value, err
|
||||
}
|
||||
|
||||
// helperPrintColumn is a helper function for the parsePrintColumnParams to compute printer columns.
|
||||
func helperPrintColumn(parts string, comment string) (v1beta1.CustomResourceColumnDefinition, error) {
|
||||
config := v1beta1.CustomResourceColumnDefinition{}
|
||||
var count int
|
||||
part := strings.Split(parts, ",")
|
||||
if len(part) < 3 {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
|
||||
for _, elem := range strings.Split(parts, ",") {
|
||||
key, value, err := printColumnKV(elem)
|
||||
if err != nil {
|
||||
return v1beta1.CustomResourceColumnDefinition{},
|
||||
fmt.Errorf("//+kubebuilder:printcolumn: tags must be key value pairs.Expected "+
|
||||
"keys [name=<name>,type=<type>,description=<descr>,format=<format>] "+
|
||||
"Got string: [%s]", parts)
|
||||
}
|
||||
if key == printColumnName || key == printColumnType || key == printColumnPath {
|
||||
count++
|
||||
}
|
||||
switch key {
|
||||
case printColumnName:
|
||||
config.Name = value
|
||||
case printColumnType:
|
||||
if value == "integer" || value == "number" || value == "string" || value == "boolean" || value == "date" {
|
||||
config.Type = value
|
||||
} else {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnType)
|
||||
}
|
||||
case printColumnFormat:
|
||||
if config.Type == "integer" && (value == "int32" || value == "int64") {
|
||||
config.Format = value
|
||||
} else if config.Type == "number" && (value == "float" || value == "double") {
|
||||
config.Format = value
|
||||
} else if config.Type == "string" && (value == "byte" || value == "date" || value == "date-time" || value == "password") {
|
||||
config.Format = value
|
||||
} else {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnFormat)
|
||||
}
|
||||
case printColumnPath:
|
||||
config.JSONPath = value
|
||||
case printColumnPri:
|
||||
i, err := strconv.Atoi(value)
|
||||
v := int32(i)
|
||||
if err != nil {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnPri)
|
||||
}
|
||||
config.Priority = v
|
||||
case printColumnDescr:
|
||||
config.Description = value
|
||||
default:
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
}
|
||||
if count != 3 {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// printcolumn requires name,type,JSONPath fields and rest of the field are optional
|
||||
// +kubebuilder:printcolumn:name=<name>,type=<type>,description=<desc>,JSONPath:<.spec.Name>,priority=<int32>,format=<format>
|
||||
func parsePrintColumnParams(t *types.Type) ([]v1beta1.CustomResourceColumnDefinition, error) {
|
||||
result := []v1beta1.CustomResourceColumnDefinition{}
|
||||
for _, comment := range t.CommentLines {
|
||||
if strings.Contains(comment, "+kubebuilder:printcolumn") {
|
||||
parts := strings.Replace(comment, "+kubebuilder:printcolumn:", "", -1)
|
||||
res, err := helperPrintColumn(parts, comment)
|
||||
if err != nil {
|
||||
return []v1beta1.CustomResourceColumnDefinition{}, err
|
||||
}
|
||||
result = append(result, res)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user