diff --git a/.travis.yml b/.travis.yml index 02cf00f602e..6d9867a1a1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ install: - script: -- TRACE=1 ./testv1.sh +- TRACE=1 ./test.sh # TBD. Suppressing for now. notifications: diff --git a/build/.goreleaser.yml b/build/.goreleaser.yml index 0d0b8d86f58..1cbd4495880 100644 --- a/build/.goreleaser.yml +++ b/build/.goreleaser.yml @@ -1,18 +1,9 @@ # This is a GoReleaser configuration file for Kubebuilder release. # Make sure to check the documentation at http://goreleaser.com builds: -- main: ./cmd/kubebuilder +- main: ./cmd binary: kubebuilder - ldflags: -s -X sigs.k8s.io/kubebuilder/cmd/kubebuilder/version.kubeBuilderVersion={{.Version}} -X sigs.k8s.io/kubebuilder/cmd/kubebuilder/version.gitCommit={{.Commit}} -X sigs.k8s.io/kubebuilder/cmd/kubebuilder/version.buildDate={{.Date}} -X sigs.k8s.io/kubebuilder/cmd/kubebuilder/version.kubernetesVendorVersion={{.Env.KUBERNETES_VERSION}} - goos: - - darwin - - linux - goarch: - - amd64 - env: - - CGO_ENABLED=0 -- main: ./cmd/kubebuilder-gen - binary: kubebuilder-gen + ldflags: -s -X sigs.k8s.io/kubebuilder/cmd/version.kubeBuilderVersion={{.Version}} -X sigs.k8s.io/kubebuilder/cmd/version.gitCommit={{.Commit}} -X sigs.k8s.io/kubebuilder/cmd/version.buildDate={{.Date}} -X sigs.k8s.io/kubebuilder/cmd/version.kubernetesVendorVersion={{.Env.KUBERNETES_VERSION}} goos: - darwin - linux diff --git a/cmd/kubebuilder/alpha.go b/cmd/alpha.go similarity index 100% rename from cmd/kubebuilder/alpha.go rename to cmd/alpha.go diff --git a/cmd/kubebuilder/api.go b/cmd/api.go similarity index 99% rename from cmd/kubebuilder/api.go rename to cmd/api.go index de7cbe53870..4a88c610b5f 100644 --- a/cmd/kubebuilder/api.go +++ b/cmd/api.go @@ -28,7 +28,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder/util" + "sigs.k8s.io/kubebuilder/cmd/util" "sigs.k8s.io/kubebuilder/pkg/scaffold" "sigs.k8s.io/kubebuilder/pkg/scaffold/controller" "sigs.k8s.io/kubebuilder/pkg/scaffold/input" diff --git a/cmd/kubebuilder/docs.go b/cmd/docs.go similarity index 100% rename from cmd/kubebuilder/docs.go rename to cmd/docs.go diff --git a/cmd/kubebuilder/init_project.go b/cmd/init_project.go similarity index 99% rename from cmd/kubebuilder/init_project.go rename to cmd/init_project.go index 09faaafb5be..5f64f7b6a24 100644 --- a/cmd/kubebuilder/init_project.go +++ b/cmd/init_project.go @@ -29,7 +29,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder/util" + "sigs.k8s.io/kubebuilder/cmd/util" "sigs.k8s.io/kubebuilder/pkg/scaffold" "sigs.k8s.io/kubebuilder/pkg/scaffold/input" "sigs.k8s.io/kubebuilder/pkg/scaffold/manager" diff --git a/cmd/internal/codegen/parse/apis.go b/cmd/internal/codegen/parse/apis.go deleted file mode 100644 index 456a9e38fdf..00000000000 --- a/cmd/internal/codegen/parse/apis.go +++ /dev/null @@ -1,285 +0,0 @@ -/* -Copyright 2017 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/kubebuilder/cmd/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)] - 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 . - 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 - } -} diff --git a/cmd/internal/codegen/parse/context.go b/cmd/internal/codegen/parse/context.go deleted file mode 100644 index ed4d8eb200e..00000000000 --- a/cmd/internal/codegen/parse/context.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2017 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()) -} - -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), - } -} diff --git a/cmd/internal/codegen/parse/controllers.go b/cmd/internal/codegen/parse/controllers.go deleted file mode 100644 index dd323026dbc..00000000000 --- a/cmd/internal/codegen/parse/controllers.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2017 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 ( - "log" - "strings" - - "github.com/pkg/errors" - - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/gengo/types" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -// resourceTags contains the tags present in a "+resource=" comment -type controllerTags struct { - gvk schema.GroupVersionKind - resource string -} - -// parseControllers populates the list of controllers to generate code from the -// list of annotated types. -func (b *APIs) parseControllers() { - for _, c := range b.context.Order { - if IsController(c) { - tags := parseControllerTag(b.getControllerTag(c)) - repo := strings.Split(c.Name.Package, "/pkg/controller")[0] - pkg := b.context.Universe[c.Name.Package] - b.Controllers = append(b.Controllers, codegen.Controller{ - tags.gvk, tags.resource, pkg, repo}) - } - } -} - -func (b *APIs) getControllerTag(c *types.Type) string { - comments := Comments(c.CommentLines) - resource := comments.getTag("controller", ":") + comments.getTag("kubebuilder:controller", ":") - if len(resource) == 0 { - panic(errors.Errorf("Must specify +kubebuilder:controller comment for type %v", c.Name)) - } - return resource -} - -// parseResourceTag parses the tags in a "+resource=" comment into a resourceTags struct -func parseControllerTag(tag string) controllerTags { - result := controllerTags{} - for _, elem := range strings.Split(tag, ",") { - kv := strings.Split(elem, "=") - if len(kv) != 2 { - log.Fatalf("// +kubebuilder:controller: tags must be key value pairs. Expected "+ - "keys [group=,version=,kind=,resource=] "+ - "Got string: [%s]", tag) - } - value := kv[1] - switch kv[0] { - case "group": - result.gvk.Group = value - case "version": - result.gvk.Version = value - case "kind": - result.gvk.Kind = value - case "resource": - result.resource = value - } - } - return result -} diff --git a/cmd/internal/codegen/parse/crd.go b/cmd/internal/codegen/parse/crd.go deleted file mode 100644 index d49c75fbee9..00000000000 --- a/cmd/internal/codegen/parse/crd.go +++ /dev/null @@ -1,566 +0,0 @@ -/* -Copyright 2017 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" - - "github.com/pkg/errors" - "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) - // k8s 1.11 rejects CRDs with type property at the root if status sub-resources enabled. - // This change drops the type field for CRD at the root level. Refer to issue below: - // 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), - }, - 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{ - &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 HasStatusSubresource(resource.Type) { - subresources := &v1beta1.CustomResourceSubresources{ - Status: &v1beta1.CustomResourceSubresourceStatus{}, - } - resource.CRD.Spec.Subresources = subresources - resource.HasStatusSubresource = true - } - - if len(resource.ShortName) > 0 { - resource.CRD.Spec.Names.ShortNames = []string{resource.ShortName} - } - } - } - } - } -} - -func (b *APIs) getTime() string { - return `v1beta1.JSONSchemaProps{ - Type: "string", - Format: "date-time", -}` -} - -func (b *APIs) getMeta() 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"} - meta := types.Name{Name: "ObjectMeta", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"} - switch t.Name { - case time: - return v1beta1.JSONSchemaProps{ - Type: "string", - Format: "date-time", - }, b.getTime() - case meta: - return v1beta1.JSONSchemaProps{ - Type: "object", - }, b.getMeta() - } - - 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-Z,]+)\"") - -type primitiveTemplateArgs struct { - v1beta1.JSONSchemaProps - Value string - Format string - EnumValue string // TODO check type of enum value to match the type of field -} - -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 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" - default: - n = t.Name.Name - } - if props.Enum != nil { - s = parseEnumToString(props.Enum) - } - if err := primitiveTemplate.Execute(buff, primitiveTemplateArgs{props, n, f, s}); err != nil { - log.Fatalf("%v", err) - } - props.Type = n - props.Format = f - 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) - props := v1beta1.JSONSchemaProps{ - Type: "object", - } - parseOption := b.arguments.CustomArgs.(*ParseOptions) - if !parseOption.SkipMapValidation { - props.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{ - Allows: true, - Schema: &additionalProps} - } - - 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) - props := v1beta1.JSONSchemaProps{ - Type: "array", - Items: &v1beta1.JSONSchemaPropsOrArray{Schema: &items}, - } - // 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 - } - for _, l := range comments { - getValidation(l, &props) - } - 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 -} - -// Drop the "type" field from the CRD validation schema at the root level. -// K8s 1.11 server rejects such CRDs. Refer to issue below for more details: -// https://github.com/kubernetes/kubernetes/issues/65293 -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", - } - - 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 - - // Only add field validation for non-inlined fields - for _, l := range comments { - getValidation(l, &props) - } - - 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 - } - log.Printf("Doing %s\n", comment) - c := strings.Replace(comment, "+kubebuilder:validation:", "", -1) - parts := strings.Split(c, "=") - if len(parts) != 2 { - log.Fatalf("Expected +kubebuilder:validation:= actual: %s", comment) - return - } - log.Printf("Switch %v\n", parts) - 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 -} - -// 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 -} diff --git a/cmd/internal/codegen/parse/index.go b/cmd/internal/codegen/parse/index.go deleted file mode 100644 index 57840b75b9f..00000000000 --- a/cmd/internal/codegen/parse/index.go +++ /dev/null @@ -1,239 +0,0 @@ -/* -Copyright 2017 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/pkg/errors" - - "k8s.io/gengo/types" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -// 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 - - rt := parseResourceTag(b.getResourceTag(c)) - r.Resource = rt.Resource - r.ShortName = rt.ShortName - //r.REST = rt.REST - //r.Strategy = rt.Strategy - - // 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 - - //if !HasSubresource(c) { - // continue - //} - r.Type = c - //r.Subresources = b.getSubresources(r) - } -} - -//func (b *APIs) getSubresources(c *codegen.APIResource) map[string]*codegen.APISubresource { -// r := map[string]*codegen.APISubresource{} -// subresources := b.getSubresourceTags(c.Type) -// -// if len(subresources) == 0 { -// // Not a subresource -// return r -// } -//for _, subresource := range subresources { -// // Parse the values for each subresource -// tags := parseSubresourceTag(c, subresource) -// sr := &codegen.APISubresource{ -// Kind: tags.Kind, -// Request: tags.RequestKind, -// Path: tags.Path, -// REST: tags.REST, -// Domain: b.Domain, -// Version: c.Version, -// Resource: c.Resource, -// Group: c.Group, -// } -// if !b.isInPackage(tags) { -// // Out of package Request types require an import and are prefixed with the -// // package name - e.g. v1.Scale -// sr.Request, sr.ImportPackage = b.getNameAndImport(tags) -// } -// if v, found := r[sr.Path]; found { -// log.Fatalf("Multiple subresources registered for path %s: %v %v", -// sr.Path, v, subresource) -// } -// r[sr.Path] = sr -//} -// return r -//} - -// subresourceTags contains the tags present in a "+subresource=" comment -//type subresourceTags struct { -// Path string -// Kind string -// RequestKind string -// REST string -//} -// -//func (b *APIs) getSubresourceTags(c *types.Type) []string { -// comments := Comments(c.CommentLines) -// return comments.getTags("subresource", ":") -//} - -// Returns true if the subresource Request type is in the same package as the resource type -//func (b *APIs) isInPackage(tags subresourceTags) bool { -// return !strings.Contains(tags.RequestKind, ".") -//} -// -//// GetNameAndImport converts -//func (b *APIs) getNameAndImport(tags subresourceTags) (string, string) { -// last := strings.LastIndex(tags.RequestKind, ".") -// importPackage := tags.RequestKind[:last] -// -// // Set the request kind to the struct name -// tags.RequestKind = tags.RequestKind[last+1:] -// // Find the package -// pkg := filepath.Base(importPackage) -// // Prefix the struct name with the package it is in -// return strings.Join([]string{pkg, tags.RequestKind}, "."), importPackage -//} - -// resourceTags contains the tags present in a "+resource=" comment -type resourceTags struct { - Resource string - REST string - Strategy string - ShortName string -} - -// ParseResourceTag parses the tags in a "+resource=" comment into a resourceTags struct -func parseResourceTag(tag string) resourceTags { - result := resourceTags{} - for _, elem := range strings.Split(tag, ",") { - kv := strings.Split(elem, "=") - if len(kv) != 2 { - log.Fatalf("// +kubebuilder:resource: tags must be key value pairs. Expected "+ - "keys [path=] "+ - "Got string: [%s]", tag) - } - value := kv[1] - switch kv[0] { - //case "rest": - // result.REST = value - case "path": - result.Resource = value - //case "strategy": - // result.Strategy = value - case "shortName": - result.ShortName = value - } - } - return result -} - -// ParseSubresourceTag parses the tags in a "+subresource=" comment into a subresourceTags struct -//func parseSubresourceTag(c *codegen.APIResource, tag string) subresourceTags { -// result := subresourceTags{} -// for _, elem := range strings.Split(tag, ",") { -// kv := strings.Split(elem, "=") -// if len(kv) != 2 { -// log.Fatalf("// +subresource: tags must be key value pairs. Expected "+ -// "keys [request=,rest=,path=] "+ -// "Got string: [%s]", tag) -// } -// value := kv[1] -// switch kv[0] { -// case "request": -// result.RequestKind = value -// case "rest": -// result.REST = value -// case "path": -// // Strip the parent resource -// result.Path = strings.Replace(value, c.Resource+"/", "", -1) -// } -// } -// return result -//} - -// getResourceTag returns the value of the "+resource=" comment tag -func (b *APIs) getResourceTag(c *types.Type) string { - comments := Comments(c.CommentLines) - resource := comments.getTag("resource", ":") + comments.getTag("kubebuilder:resource", ":") - if len(resource) == 0 { - panic(errors.Errorf("Must specify +kubebuilder:resource comment for type %v", c.Name)) - } - return resource -} diff --git a/cmd/internal/codegen/parse/parser.go b/cmd/internal/codegen/parse/parser.go deleted file mode 100644 index d0b8b13ea27..00000000000 --- a/cmd/internal/codegen/parse/parser.go +++ /dev/null @@ -1,240 +0,0 @@ -/* -Copyright 2017 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" - "fmt" - "go/build" - "log" - "os" - "path/filepath" - "strings" - - "github.com/golang/glog" - "github.com/markbates/inflect" - "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/kubebuilder/cmd/internal/codegen" -) - -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 -} - -func NewAPIs(context *generator.Context, arguments *args.GeneratorArgs) *APIs { - b := &APIs{ - context: context, - arguments: arguments, - } - b.parsePackages() - b.parseDomain() - b.parseGroupNames() - b.parseIndex() - b.parseControllers() - b.parseRBAC() - b.parseInformers() - b.verifyRBACAnnotations() - b.parseAPIs() - b.parseCRDs() - return b -} - -// verifyRBACAnnotations verifies that there are corresponding RBAC annotations for -// each informer annotation. -// e.g. if there is an // +kubebuilder:informer annotation for Pods, then there -// should also be a // +kubebuilder:rbac annotation for Pods -func (b *APIs) verifyRBACAnnotations() { - parseOption := b.arguments.CustomArgs.(*ParseOptions) - if parseOption.SkipRBACValidation { - log.Println("skipping RBAC validations") - return - } - err := rbacMatchesInformers(b.Informers, b.Rules) - if err != nil { - log.Fatal(err) - } -} - -func rbacMatchesInformers(informers map[v1.GroupVersionKind]bool, rbacRules []rbacv1.PolicyRule) error { - rs := inflect.NewDefaultRuleset() - - // For each informer, look for the RBAC annotation - for gvk := range informers { - found := false - - // Search all RBAC rules for one that matches the informer group and resource - for _, rule := range rbacRules { - - // Check if the group matches the informer group - groupFound := false - for _, g := range rule.APIGroups { - // RBAC has the full group with domain, whereas informers do not. Strip the domain - // from the group before comparing. - parts := strings.Split(g, ".") - group := parts[len(parts)-1] - - // If the RBAC group is wildcard or matches, it is a match - if g == "*" || group == gvk.Group { - groupFound = true - break - } - // Edge case where "core" and "" are equivalent - if (group == "core" || group == "") && (gvk.Group == "core" || gvk.Group == "") { - groupFound = true - break - } - } - if !groupFound { - continue - } - - // The resource name is the lower-plural of the Kind - resource := rs.Pluralize(strings.ToLower(gvk.Kind)) - // Check if the resource matches the informer resource - resourceFound := false - for _, k := range rule.Resources { - // If the RBAC resource is a wildcard or matches the informer resource, it is a match - if k == "*" || k == resource { - resourceFound = true - break - } - } - if !resourceFound { - continue - } - - // Found a matching RBAC rule - found = true - break - } - if !found { - return fmt.Errorf("Missing rbac rule for %s.%s. Add with // +kubebuilder:rbac:groups=%s,"+ - "resources=%s,verbs=get;list;watch comment on controller struct "+ - "or run the command with '--skip-rbac-validation' arg", gvk.Group, gvk.Kind, gvk.Group, - inflect.NewDefaultRuleset().Pluralize(strings.ToLower(gvk.Kind))) - } - } - return nil -} - -// 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) - - if apis := filepath.Dir(unversioned); apis != b.APIsPkg && len(b.APIsPkg) > 0 { - panic(errors.Errorf( - "Found multiple apis directory paths: %v and %v. "+ - "Do you have a +resource tag on a resource that is not in a version "+ - "directory?", b.APIsPkg, apis)) - } else { - b.APIsPkg = apis - } - } - } -} - -// 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 { - glog.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 { - glog.Fatal(err) - } - - comments := Comments(lines) - domain = comments.getTag("domain", "=") - break - } - } - return domain -} diff --git a/cmd/internal/codegen/parse/parser_test.go b/cmd/internal/codegen/parse/parser_test.go deleted file mode 100644 index 9d4b4098a23..00000000000 --- a/cmd/internal/codegen/parse/parser_test.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -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 ( - "testing" - - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestrbacMatchesInformers(t *testing.T) { - tests := []struct { - informers map[v1.GroupVersionKind]bool - rbacRules []rbacv1.PolicyRule - expErr bool - }{ - { - // informer resource matches the RBAC rule - informers: map[v1.GroupVersionKind]bool{ - v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}: true, - }, - rbacRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"apps"}, Resources: []string{"deployments"}}, - }, - expErr: false, - }, - { - // RBAC rule does not match the informer resource because of missing pluralization in RBAC rules - informers: map[v1.GroupVersionKind]bool{ - v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}: true, - }, - rbacRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"apps"}, Resources: []string{"Deployment"}}, - }, - expErr: true, - }, - { - // wild-card RBAC rule should match any resource in the group - informers: map[v1.GroupVersionKind]bool{ - v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}: true, - v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}: true, - }, - rbacRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"apps"}, Resources: []string{"*"}}, - }, - expErr: false, - }, - { - // empty group name is normalized to "core" - informers: map[v1.GroupVersionKind]bool{ - v1.GroupVersionKind{Group: "core", Version: "v1", Kind: "Pod"}: true, - }, - rbacRules: []rbacv1.PolicyRule{ - {APIGroups: []string{""}, Resources: []string{"pods"}}, - }, - expErr: false, - }, - { - // empty group name is normalized to "core" - informers: map[v1.GroupVersionKind]bool{ - v1.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}: true, - }, - rbacRules: []rbacv1.PolicyRule{ - {APIGroups: []string{"core"}, Resources: []string{"pods"}}, - }, - expErr: false, - }, - } - - for _, test := range tests { - err := checkRBACMatchesInformers(test.informers, test.rbacRules) - if test.expErr { - if err == nil { - t.Errorf("RBAC rules %+v shouldn't match with informers %+v", test.rbacRules, test.informers) - } - } else { - if err != nil { - t.Errorf("RBAC rules %+v should match informers %+v, but got a mismatch error: %v", test.rbacRules, test.informers, err) - } - } - } -} diff --git a/cmd/internal/codegen/parse/rbac.go b/cmd/internal/codegen/parse/rbac.go deleted file mode 100644 index aa911d72562..00000000000 --- a/cmd/internal/codegen/parse/rbac.go +++ /dev/null @@ -1,134 +0,0 @@ -/* -Copyright 2017 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" - - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/gengo/types" -) - -// parseRBAC populates the RBAC rules for each annotated type. -func (b *APIs) parseRBAC() { - for _, c := range b.context.Order { - if IsRBAC(c) { - for _, tag := range b.getRBACTag(c) { - b.Rules = append(b.Rules, parseRBACTag(tag)) - } - } - } -} - -func (b *APIs) getRBACTag(c *types.Type) []string { - comments := Comments(c.CommentLines) - resource := comments.getTags("rbac", ":") - resource = append(resource, comments.getTags("kubebuilder:rbac", ":")...) - if len(resource) == 0 { - panic(fmt.Errorf("Must specify +kubebuilder:rbac comment for type %v", c.Name)) - } - return resource -} - -func parseRBACTag(tag string) rbacv1.PolicyRule { - result := rbacv1.PolicyRule{} - for _, elem := range strings.Split(tag, ",") { - kv := strings.Split(elem, "=") - if len(kv) != 2 { - log.Fatalf("// +kubebuilder:rbac: tags must be key value pairs. Expected "+ - "keys [groups=,resources=,verbs=] "+ - "Got string: [%s]", tag) - } - value := kv[1] - values := []string{} - if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { - value = value[1 : len(value)-1] - } - values = strings.Split(value, ";") - switch kv[0] { - case "groups": - normalized := []string{} - for _, v := range values { - if v == "core" { - normalized = append(normalized, "") - } else { - normalized = append(normalized, v) - } - } - result.APIGroups = normalized - case "resources": - result.Resources = values - case "verbs": - result.Verbs = values - case "urls": - result.NonResourceURLs = values - } - } - return result -} - -// parseInfomers populates the informers to generate on each annotated type. -func (b *APIs) parseInformers() { - for _, c := range b.context.Order { - if IsInformer(c) { - for _, tag := range b.getInformerTag(c) { - if b.Informers == nil { - b.Informers = map[v1.GroupVersionKind]bool{} - } - b.Informers[parseInformerTag(tag)] = true - } - } - } -} - -func (b *APIs) getInformerTag(c *types.Type) []string { - comments := Comments(c.CommentLines) - resource := comments.getTags("informers", ":") - resource = append(resource, comments.getTags("kubebuilder:informers", ":")...) - if len(resource) == 0 { - panic(fmt.Errorf("Must specify +kubebuilder:informers comment for type %v", c.Name)) - } - return resource -} - -func parseInformerTag(tag string) v1.GroupVersionKind { - result := v1.GroupVersionKind{} - for _, elem := range strings.Split(tag, ",") { - kv := strings.Split(elem, "=") - if len(kv) != 2 { - log.Fatalf("// +kubebuilder:informers: tags must be key value pairs. Expected "+ - "keys [group=core,version=v1,kind=Pod] "+ - "Got string: [%s]", tag) - } - value := kv[1] - switch kv[0] { - case "group": - if value == "" { - value = "core" - } - result.Group = value - case "version": - result.Version = value - case "kind": - result.Kind = value - } - } - return result -} diff --git a/cmd/internal/codegen/parse/util.go b/cmd/internal/codegen/parse/util.go deleted file mode 100644 index 38abf9d7946..00000000000 --- a/cmd/internal/codegen/parse/util.go +++ /dev/null @@ -1,306 +0,0 @@ -/* -Copyright 2017 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" -) - -type ParseOptions 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 t has a +resource/+kubebuilder:resource comment tag -func IsAPIResource(t *types.Type) bool { - for _, c := range t.CommentLines { - if strings.Contains(c, "+resource") || strings.Contains(c, "+kubebuilder:resource") { - 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 -} - -// 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 -} - -// 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 -} - -func IsUnversioned(t *types.Type, group string) bool { - return IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) && GetGroup(t) == group -} - -func IsVersioned(t *types.Type, group string) bool { - dir := filepath.Base(filepath.Dir(filepath.Dir(t.Name.Package))) - return IsApisDir(dir) && GetGroup(t) == group -} - -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) -} - -func GetGroup(t *types.Type) string { - return filepath.Base(GetGroupPackage(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) -} - -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 -} - -// 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 -} - -// 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{[]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{[]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{[]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{[]byte(fmt.Sprintf("%v", s))}) - case "string": - *enums = append(*enums, v1beta1.JSON{[]byte(`"` + s + `"`)}) - } -} diff --git a/cmd/internal/codegen/types.go b/cmd/internal/codegen/types.go deleted file mode 100644 index 24601c7f23b..00000000000 --- a/cmd/internal/codegen/types.go +++ /dev/null @@ -1,206 +0,0 @@ -/* -Copyright 2017 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 codegen - -import ( - "sort" - - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/gengo/types" -) - -type APIs struct { - // Domain is the domain portion of the group - e.g. k8s.io - Domain string - - // Package is the name of the root API package - e.g. github.com/my-org/my-repo/pkg/apis - Package string - - // Pkg the Package for the root API package - Pkg *types.Package - - // Groups is the list of API groups found under the apis package - Groups map[string]*APIGroup - - Rules []rbacv1.PolicyRule - - Informers map[v1.GroupVersionKind]bool -} - -func (apis *APIs) GetRules() []rbacv1.PolicyRule { - rules := []rbacv1.PolicyRule{} - rulesIndex := map[v1.GroupResource]sets.String{} - for _, rule := range apis.Rules { - for _, g := range rule.APIGroups { - for _, r := range rule.Resources { - gr := v1.GroupResource{ - Group: g, - Resource: r, - } - if _, found := rulesIndex[gr]; !found { - rulesIndex[gr] = sets.NewString() - } - rulesIndex[gr].Insert(rule.Verbs...) - } - } - } - for gr, v := range rulesIndex { - verbs := v.List() - sort.Strings(verbs) - rule := rbacv1.PolicyRule{ - Resources: []string{gr.Resource}, - APIGroups: []string{gr.Group}, - Verbs: verbs, - } - rules = append(rules, rule) - } - return rules -} - -type APIGroup struct { - // Package is the name of the go package the api group is under - e.g. github.com/me/apiserver-helloworld/apis - Package string - // Domain is the domain portion of the group - e.g. k8s.io - Domain string - // Group is the short name of the group - e.g. mushroomkingdom - Group string - GroupTitle string - // Versions is the list of all versions for this group keyed by name - Versions map[string]*APIVersion - - UnversionedResources map[string]*APIResource - - // Structs is a list of unversioned definitions that must be generated - Structs []*Struct - Pkg *types.Package - PkgPath string -} - -type Struct struct { - // Name is the name of the type - Name string - // genClient - GenClient bool - GenDeepCopy bool - NonNamespaced bool - - GenUnversioned bool - // Fields is the list of fields appearing in the struct - Fields []*Field -} - -type Field struct { - // Name is the name of the field - Name string - // For versioned Kubernetes types, this is the versioned package - VersionedPackage string - // For versioned Kubernetes types, this is the unversioned package - UnversionedImport string - UnversionedType string -} - -type APIVersion struct { - // Domain is the group domain - e.g. k8s.io - Domain string - // Group is the group name - e.g. mushroomkingdom - Group string - // Version is the api version - e.g. v1beta1 - Version string - // Resources is a list of resources appearing in the API version keyed by name - Resources map[string]*APIResource - // Pkg is the Package object from code-gen - Pkg *types.Package -} - -type APIResource struct { - // Domain is the group domain - e.g. k8s.io - Domain string - // Group is the group name - e.g. mushroomkingdom - Group string - // Version is the api version - e.g. v1beta1 - Version string - // Kind is the resource name - e.g. PeachesCastle - Kind string - // Resource is the resource name - e.g. peachescastles - Resource string - // REST is the rest.Storage implementation used to handle requests - // This field is optional. The standard REST implementation will be used - // by default. - REST string - // Subresources is a map of subresources keyed by name - Subresources map[string]*APISubresource - // Type is the Type object from code-gen - Type *types.Type - // Strategy is name of the struct to use for the strategy - Strategy string - // Strategy is name of the struct to use for the strategy - StatusStrategy string - // NonNamespaced indicates that the resource kind is non namespaced - NonNamespaced bool - - ShortName string - - JSONSchemaProps v1beta1.JSONSchemaProps - CRD v1beta1.CustomResourceDefinition - Validation string - ValidationComments string - // DocAnnotation is a map of annotations by name for doc. e.g. warning, notes message - DocAnnotation map[string]string - // HasStatusSubresource indicates that the resource has a status subresource - HasStatusSubresource bool - // Categories is a list of categories the resource is part of. - Categories []string -} - -type APISubresource struct { - // Domain is the group domain - e.g. k8s.io - Domain string - // Group is the group name - e.g. mushroomkingdom - Group string - // Version is the api version - e.g. v1beta1 - Version string - // Kind is the resource name - e.g. PeachesCastle - Kind string - // Resource is the resource name - e.g. peachescastles - Resource string - // Request is the subresource request type - e.g. ScaleCastle - Request string - // REST is the rest.Storage implementation used to handle requests - REST string - // Path is the subresource path - e.g. scale - Path string - - // ImportPackage is the import statement that must appear for the Request - ImportPackage string - - // RequestType is the type of the request - RequestType *types.Type - - // RESTType is the type of the request handler - RESTType *types.Type -} - -type Controller struct { - Target schema.GroupVersionKind - Resource string - Pkg *types.Package - Repo string -} diff --git a/cmd/kubebuilder-gen/codegen/generators.go b/cmd/kubebuilder-gen/codegen/generators.go deleted file mode 100644 index 75294ce7e47..00000000000 --- a/cmd/kubebuilder-gen/codegen/generators.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2017 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 codegen - -import ( - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -// ResourceGenerator provides a code generator that takes a package of an API GroupVersion -// and generates a file -type ResourceGenerator interface { - // Returns a Generator for a versioned resource package e.g. pkg/apis// - GenerateVersionedResource( - apiversion *codegen.APIVersion, apigroup *codegen.APIGroup, filename string) generator.Generator -} - -// ControllerGenerator provides a code generator that takes a package of a controller -// and generates a file -type ControllerGenerator interface { - // GenerateInject returns a Generator for the controller package e.g. pkg/controller - GenerateInject(controllers []codegen.Controller, apis *codegen.APIs, filename string) generator.Generator -} diff --git a/cmd/kubebuilder-gen/codegen/run/generator.go b/cmd/kubebuilder-gen/codegen/run/generator.go deleted file mode 100644 index 40c1631d00e..00000000000 --- a/cmd/kubebuilder-gen/codegen/run/generator.go +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright 2017 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 run - -import ( - "github.com/golang/glog" - "github.com/spf13/pflag" - - "k8s.io/gengo/args" - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen/parse" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder-gen/codegen" -) - -// CodeGenerator generates code for Kubernetes resources and controllers -type CodeGenerator struct { - resourceGenerators []codegen.ResourceGenerator - controllerGenerators []codegen.ControllerGenerator - - // OutputFileBaseName is the base name used for output files - OutputFileBaseName string -} - -// AddControllerGenerator adds a controller generator that will be called with parsed controllers -func (g *CodeGenerator) AddControllerGenerator(generator codegen.ControllerGenerator) *CodeGenerator { - g.controllerGenerators = append(g.controllerGenerators, generator) - return g -} - -// AddResourceGenerator adds a resource generator that will be called with parsed resources -func (g *CodeGenerator) AddResourceGenerator(generator codegen.ResourceGenerator) *CodeGenerator { - g.resourceGenerators = append(g.resourceGenerators, generator) - return g -} - -// Execute parses packages and executes the code generators against the resource and controller packages -func (g *CodeGenerator) Execute() error { - arguments := args.Default() - - // Custom args. - customArgs := &parse.ParseOptions{} - pflag.CommandLine.BoolVar(&customArgs.SkipMapValidation, "skip-map-validation", true, - "if set to true, skip generating validation schema for map type in CRD.") - pflag.CommandLine.BoolVar(&customArgs.SkipRBACValidation, "skip-rbac-validation", false, - "if set to true, skip validation for RBAC annotations for the controller.") - arguments.CustomArgs = customArgs - - arguments.OutputFileBaseName = g.OutputFileBaseName - - err := arguments.Execute(parse.NameSystems(), parse.DefaultNameSystem(), g.packages) - if err != nil { - glog.Fatalf("Error: %v", err) - } - return nil -} - -// packages parses the observed packages and creates code generators -func (g *CodeGenerator) packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { - p := packages{} - - b := parse.NewAPIs(context, arguments) - - // Add resource generators - for _, apigroup := range b.APIs.Groups { - for _, apiversion := range apigroup.Versions { - // Do the versioned resource packages - for _, r := range g.resourceGenerators { - g := r.GenerateVersionedResource(apiversion, apigroup, arguments.OutputFileBaseName) - if g != nil { - p.add(apiversion.Pkg.Path, g) - } - } - } - // Do the unversioned packages - //for _, r := range g.resourceGenerators { - // g := r.GenerateUnversionedResource(apigroup, arguments.OutputFileBaseName) - // if g != nil { - // p.add(apigroup.Pkg.Path, g) - // } - //} - //// Do the install generators - //for _, r := range g.resourceGenerators { - // g := r.GenerateInstall(apigroup, arguments.OutputFileBaseName) - // if g != nil { - // p.add(filepath.Join(apigroup.Pkg.Path, "install"), g) - // } - //} - } - // Do apis package - //for _, r := range g.resourceGenerators { - // g := r.GenerateAPIs(b.APIs, arguments.OutputFileBaseName) - // if g != nil { - // p.add(b.APIs.Pkg.Path, g) - // } - //} - - // Find the repo - repo := "" - for _, c := range b.Controllers { - repo = c.Repo - } - - // Do inject package - if len(b.Controllers) > 0 { - for _, cg := range g.controllerGenerators { - g := cg.GenerateInject(b.Controllers, b.APIs, arguments.OutputFileBaseName) - if g != nil { - p.add(context.Universe[repo+"/pkg/inject"].Path, g) - } - } - } - - return p.value -} diff --git a/cmd/kubebuilder-gen/codegen/run/util.go b/cmd/kubebuilder-gen/codegen/run/util.go deleted file mode 100644 index 2a41ec211d3..00000000000 --- a/cmd/kubebuilder-gen/codegen/run/util.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2017 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 run - -import ( - "io/ioutil" - "path/filepath" - "strings" - - "k8s.io/gengo/generator" - "k8s.io/gengo/types" -) - -// generatorToPackage creates a new package from a generator and package name -func generatorToPackage(pkg string, gen generator.Generator) generator.Package { - name := strings.Split(filepath.Base(pkg), ".")[0] - return &generator.DefaultPackage{ - PackageName: name, - PackagePath: pkg, - HeaderText: generatedGoHeader(), - GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { - return []generator.Generator{gen} - }, - FilterFunc: func(c *generator.Context, t *types.Type) bool { - // Generators only see Types in the same package as the generator - return t.Name.Package == pkg - }, - } -} - -// generatedGoHeader returns the header to preprend to generated go files -func generatedGoHeader() []byte { - cr, err := ioutil.ReadFile(filepath.Join("hack", "boilerplate.go.txt")) - if err != nil { - return []byte{} - } - return cr -} - -// packages wraps a collection of generator.Packages -type packages struct { - value generator.Packages -} - -// add creates a new generator.Package from gen and adds it to the collection -func (g *packages) add(pkg string, gen generator.Generator) { - g.value = append(g.value, generatorToPackage(pkg, gen)) -} diff --git a/cmd/kubebuilder-gen/internal/controllergen/generator.go b/cmd/kubebuilder-gen/internal/controllergen/generator.go deleted file mode 100644 index 665b28a07cb..00000000000 --- a/cmd/kubebuilder-gen/internal/controllergen/generator.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2017 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 controllergen - -import ( - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type Generator struct{} - -// GenerateInject returns a Generator for the controller package e.g. pkg/controller -func (g *Generator) GenerateInject(controllers []codegen.Controller, apis *codegen.APIs, filename string) generator.Generator { - return &injectGenerator{ - generator.DefaultGen{OptionalName: filename}, - controllers, - apis, - } -} diff --git a/cmd/kubebuilder-gen/internal/controllergen/inject.go b/cmd/kubebuilder-gen/internal/controllergen/inject.go deleted file mode 100644 index 598059c80fb..00000000000 --- a/cmd/kubebuilder-gen/internal/controllergen/inject.go +++ /dev/null @@ -1,182 +0,0 @@ -/* -Copyright 2017 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 controllergen - -import ( - "fmt" - "io" - "path" - "strings" - "text/template" - - "github.com/markbates/inflect" - - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type injectGenerator struct { - generator.DefaultGen - Controllers []codegen.Controller - APIS *codegen.APIs -} - -var _ generator.Generator = &injectGenerator{} - -func (d *injectGenerator) Imports(c *generator.Context) []string { - if len(d.Controllers) == 0 { - return []string{} - } - - repo := d.Controllers[0].Repo - im := []string{ - "sigs.k8s.io/kubebuilder/pkg/controller", - "k8s.io/client-go/rest", - repo + "/pkg/controller/sharedinformers", - repo + "/pkg/client/informers/externalversions", - repo + "/pkg/inject/args", - "rbacv1 \"k8s.io/api/rbac/v1\"", - } - - if len(d.APIS.Groups) > 0 { - im = append(im, []string{ - "time", - "k8s.io/client-go/kubernetes/scheme", - "rscheme " + "\"" + repo + "/pkg/client/clientset/versioned/scheme\""}..., - ) - } - // Import package for each controller - repos := map[string]string{} - for _, c := range d.Controllers { - repos[c.Pkg.Path] = "" - } - for k, _ := range repos { - im = append(im, k) - } - - libs := map[string]string{} - for i := range d.APIS.Informers { - libs[i.Group+i.Version] = "k8s.io/api/" + i.Group + "/" + i.Version - } - - for i, d := range libs { - im = append(im, fmt.Sprintf("%s \"%s\"", i, d)) - } - - // Import package for each API groupversion - gvk := map[string]string{} - for _, g := range d.APIS.Groups { - for _, v := range g.Versions { - k := fmt.Sprintf("%s%s \"%s\"", g.Group, v.Version, - path.Join(repo, "pkg", "apis", g.Group, v.Version)) - gvk[k] = "" - } - } - for k, _ := range gvk { - im = append(im, k) - } - - return im -} - -func (d *injectGenerator) Finalize(context *generator.Context, w io.Writer) error { - temp := template.Must(template.New("all-controller-template").Funcs( - template.FuncMap{ - "title": strings.Title, - "plural": inflect.NewDefaultRuleset().Pluralize, - }, - ).Parse(injectAPITemplate)) - return temp.Execute(w, d) -} - -var injectAPITemplate = ` -func init() { - {{ $length := len .APIS.Groups }}{{if ne $length 0 }}rscheme.AddToScheme(scheme.Scheme){{ end }} - - // Inject Informers - Inject = append(Inject, func(arguments args.InjectArgs) error { - Injector.ControllerManager = arguments.ControllerManager - - {{ range $group := .APIS.Groups }}{{ range $version := $group.Versions }}{{ range $res := $version.Resources -}} - if err := arguments.ControllerManager.AddInformerProvider(&{{.Group}}{{.Version}}.{{.Kind}}{}, arguments.Informers.{{title .Group}}().{{title .Version}}().{{plural .Kind}}()); err != nil { - return err - } - {{ end }}{{ end }}{{ end }} - - // Add Kubernetes informers - {{ range $informer, $found := .APIS.Informers -}} - if err := arguments.ControllerManager.AddInformerProvider(&{{$informer.Group}}{{$informer.Version}}.{{$informer.Kind}}{}, arguments.KubernetesInformers.{{title $informer.Group}}().{{title $informer.Version}}().{{plural $informer.Kind}}()); err != nil { - return err - } - {{ end }} - - {{ range $c := .Controllers -}} - if c, err := {{ $c.Pkg.Name }}.ProvideController(arguments); err != nil { - return err - } else { - arguments.ControllerManager.AddController(c) - } - {{ end -}} - return nil - }) - - // Inject CRDs - {{ range $group := .APIS.Groups -}} - {{ range $version := $group.Versions -}} - {{ range $res := $version.Resources -}} - Injector.CRDs = append(Injector.CRDs, &{{ $group.Group }}{{ $version.Version }}.{{$res.Kind}}CRD) - {{ end }}{{ end }}{{ end -}} - - - // Inject PolicyRules - {{ range $group := .APIS.Groups -}} - Injector.PolicyRules = append(Injector.PolicyRules, rbacv1.PolicyRule{ - APIGroups: []string{"{{ $group.Group }}.{{ $group.Domain }}"}, - Resources: []string{"*"}, - Verbs: []string{"*"}, - }) - {{ end -}} - {{ range $rule := .APIS.GetRules -}} - Injector.PolicyRules = append(Injector.PolicyRules, rbacv1.PolicyRule{ - APIGroups: []string{ - {{ range $group := $rule.APIGroups -}}"{{ $group }}",{{ end }} - }, - Resources: []string{ - {{ range $resource := $rule.Resources -}}"{{ $resource }}",{{ end }} - }, - Verbs: []string{ - {{ range $rule := $rule.Verbs -}}"{{ $rule }}",{{ end }} - }, - }) - {{ end -}} - - - // Inject GroupVersions - {{ range $group := .APIS.Groups -}} - {{ range $version := $group.Versions -}} - Injector.GroupVersions = append(Injector.GroupVersions, schema.GroupVersion{ - Group: "{{ $group.Group }}.{{ $group.Domain }}", - Version: "{{ $version.Version }}", - }) - {{ end }}{{ end -}} - - Injector.RunFns = append(Injector.RunFns, func(arguments run.RunArguments) error { - Injector.ControllerManager.RunInformersAndControllers(arguments) - return nil - }) -} -` diff --git a/cmd/kubebuilder-gen/internal/resourcegen/apis_generator.go b/cmd/kubebuilder-gen/internal/resourcegen/apis_generator.go deleted file mode 100644 index f7ea38ef294..00000000000 --- a/cmd/kubebuilder-gen/internal/resourcegen/apis_generator.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2017 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 resourcegen - -import ( - "io" - "text/template" - - "fmt" - - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type apiGenerator struct { - generator.DefaultGen - apis *codegen.APIs -} - -var _ generator.Generator = &apiGenerator{} - -func (d *apiGenerator) Imports(c *generator.Context) []string { - imports := []string{ - "k8s.io/apimachinery/pkg/runtime/schema", - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", - "sigs.k8s.io/kubebuilder/pkg/builders", - "rbacv1 \"k8s.io/api/rbac/v1\"", - } - for _, group := range d.apis.Groups { - imports = append(imports, group.PkgPath) - for _, version := range group.Versions { - imports = append(imports, fmt.Sprintf( - "%s%s \"%s\"", group.Group, version.Version, version.Pkg.Path)) - } - } - return imports -} - -func (d *apiGenerator) Finalize(context *generator.Context, w io.Writer) error { - temp := template.Must(template.New("apis-template").Parse(APIsTemplate)) - err := temp.Execute(w, d.apis) - if err != nil { - return err - } - return err -} - -var APIsTemplate = ` -type MetaData struct {} -var DefaultMetaData = MetaData{} - -// GetCRDs returns all the CRDs for known resource types -func (MetaData) GetCRDs() []v1beta1.CustomResourceDefinition { - return []v1beta1.CustomResourceDefinition{ - {{ range $group := .Groups -}} - {{ range $version := $group.Versions -}} - {{ range $res := $version.Resources -}} - {{ $group.Group }}{{ $version.Version }}.{{$res.Kind}}CRD, - {{ end }}{{ end }}{{ end -}} - } -} - -func (MetaData) GetRules() []rbacv1.PolicyRule { - return []rbacv1.PolicyRule{ - {{ range $group := .Groups -}} - { - APIGroups: []string{"{{ $group.Group }}.{{ $group.Domain }}"}, - Resources: []string{"*"}, - Verbs: []string{"*"}, - }, - {{ end -}} - {{ range $rule := .GetRules -}} - { - APIGroups: []string{ - {{ range $group := $rule.APIGroups -}}"{{ $group }}",{{ end }} - }, - Resources: []string{ - {{ range $resource := $rule.Resources -}}"{{ $resource }}",{{ end }} - }, - Verbs: []string{ - {{ range $rule := $rule.Verbs -}}"{{ $rule }}",{{ end }} - }, - }, - {{ end -}} - } -} - -func (MetaData) GetGroupVersions() []schema.GroupVersion { - return []schema.GroupVersion{ - {{ range $group := .Groups -}} - {{ range $version := $group.Versions -}} - { - Group: "{{ $group.Group }}.{{ $group.Domain }}", - Version: "{{ $version.Version }}", - }, - {{ end }}{{ end -}} - } -} -` diff --git a/cmd/kubebuilder-gen/internal/resourcegen/generator.go b/cmd/kubebuilder-gen/internal/resourcegen/generator.go deleted file mode 100644 index 04ae59706b3..00000000000 --- a/cmd/kubebuilder-gen/internal/resourcegen/generator.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2017 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 resourcegen - -import ( - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type Generator struct{} - -// -//// Returns a Generator for a versioned resource package e.g. pkg/apis// -//func (g *Generator) GenerateInstall(apigroup *codegen.APIGroup, filename string) generator.Generator { -// return &installGenerator{ -// generator.DefaultGen{OptionalName: filename}, -// apigroup, -// } -//} - -// Returns a Generator for a versioned resource package e.g. pkg/apis// -func (g *Generator) GenerateVersionedResource(apiversion *codegen.APIVersion, apigroup *codegen.APIGroup, filename string) generator.Generator { - return &versionedGenerator{ - generator.DefaultGen{OptionalName: filename}, - apiversion, - apigroup, - } -} - -//// GenerateUnversionedResource returns a Generator for an unversioned resource package e.g. pkg/apis/ -//func (g *Generator) GenerateUnversionedResource(apigroup *codegen.APIGroup, filename string) generator.Generator { -// return &unversionedGenerator{ -// generator.DefaultGen{OptionalName: filename}, -// apigroup, -// } -//} - -//// GenerateAPIs returns a Generator for the apis package e.g. pkg/apis -//func (g *Generator) GenerateAPIs(apis *codegen.APIs, filename string) generator.Generator { -// return &apiGenerator{ -// generator.DefaultGen{OptionalName: filename}, -// apis, -// } -//} diff --git a/cmd/kubebuilder-gen/internal/resourcegen/install_generator.go b/cmd/kubebuilder-gen/internal/resourcegen/install_generator.go deleted file mode 100644 index 22ca75041eb..00000000000 --- a/cmd/kubebuilder-gen/internal/resourcegen/install_generator.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2017 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 resourcegen - -import ( - "io" - "path" - "text/template" - - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type installGenerator struct { - generator.DefaultGen - apigroup *codegen.APIGroup -} - -var _ generator.Generator = &installGenerator{} - -func (d *installGenerator) Imports(c *generator.Context) []string { - return []string{ - "k8s.io/apimachinery/pkg/apimachinery/announced", - "k8s.io/apimachinery/pkg/apimachinery/registered", - "k8s.io/apimachinery/pkg/runtime", - path.Dir(d.apigroup.Pkg.Path), - } -} - -func (d *installGenerator) Finalize(context *generator.Context, w io.Writer) error { - temp := template.Must(template.New("install-template").Parse(InstallAPITemplate)) - err := temp.Execute(w, d.apigroup) - if err != nil { - return err - } - return err -} - -var InstallAPITemplate = ` -func Install( - groupFactoryRegistry announced.APIGroupFactoryRegistry, - registry *registered.APIRegistrationManager, - scheme *runtime.Scheme) { - - apis.Get{{ .GroupTitle }}APIBuilder().Install(groupFactoryRegistry, registry, scheme) -} -` diff --git a/cmd/kubebuilder-gen/internal/resourcegen/unversioned_generator.go b/cmd/kubebuilder-gen/internal/resourcegen/unversioned_generator.go deleted file mode 100644 index 4acd2a6642b..00000000000 --- a/cmd/kubebuilder-gen/internal/resourcegen/unversioned_generator.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2017 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 resourcegen - -import ( - "io" - "text/template" - - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type unversionedGenerator struct { - generator.DefaultGen - apigroup *codegen.APIGroup -} - -var _ generator.Generator = &unversionedGenerator{} - -func (d *unversionedGenerator) Imports(c *generator.Context) []string { - imports := sets.NewString() - return imports.List() -} - -func (d *unversionedGenerator) Finalize(context *generator.Context, w io.Writer) error { - temp := template.Must(template.New("unversioned-wiring-template").Parse(UnversionedAPITemplate)) - err := temp.Execute(w, d.apigroup) - if err != nil { - return err - } - return err -} - -var UnversionedAPITemplate = ` -const ( - GroupName = "{{.Group}}.{{.Domain}}" -) -` diff --git a/cmd/kubebuilder-gen/internal/resourcegen/versioned_generator.go b/cmd/kubebuilder-gen/internal/resourcegen/versioned_generator.go deleted file mode 100644 index 35f8b0d53c4..00000000000 --- a/cmd/kubebuilder-gen/internal/resourcegen/versioned_generator.go +++ /dev/null @@ -1,155 +0,0 @@ -/* -Copyright 2017 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 resourcegen - -import ( - "io" - "text/template" - - "k8s.io/gengo/generator" - "sigs.k8s.io/kubebuilder/cmd/internal/codegen" -) - -type versionedGenerator struct { - generator.DefaultGen - apiversion *codegen.APIVersion - apigroup *codegen.APIGroup -} - -var _ generator.Generator = &versionedGenerator{} - -//func hasSubresources(version *codegen.APIVersion) bool { -// for _, v := range version.Resources { -// if len(v.Subresources) != 0 { -// return true -// } -// } -// return false -//} - -func (d *versionedGenerator) Imports(c *generator.Context) []string { - imports := []string{ - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", - "metav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"", - "k8s.io/apimachinery/pkg/runtime", - "k8s.io/apimachinery/pkg/runtime/schema", - d.apigroup.Pkg.Path, - } - //if hasSubresources(d.apiversion) { - // imports = append(imports, "k8s.io/apiserver/pkg/registry/rest") - //} - - return imports -} - -func (d *versionedGenerator) Finalize(context *generator.Context, w io.Writer) error { - temp := template.Must(template.New("versioned-template").Parse(versionedAPITemplate)) - return temp.Execute(w, d.apiversion) -} - -var versionedAPITemplate = ` -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: "{{.Group}}.{{.Domain}}", Version: "{{.Version}}"} - -// Kind takes an unqualified kind and returns back a Group qualified GroupKind -func Kind(kind string) schema.GroupKind { - return SchemeGroupVersion.WithKind(kind).GroupKind() -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -// Adds the list of known types to Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - {{ range $api := .Resources -}} - &{{.Kind}}{}, - &{{.Kind}}List{}, - {{ end -}} - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} - -{{ range $api := .Resources -}} -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type {{$api.Kind}}List struct { - metav1.TypeMeta ` + "`json:\",inline\"`" + ` - metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + ` - Items []{{$api.Kind}} ` + "`json:\"items\"`" + ` -} -{{ end }} - -// CRD Generation -func getFloat(f float64) *float64 { - return &f -} - -func getInt(i int64) *int64 { - return &i -} - -var ( - {{ range $api := .Resources -}} - // Define CRDs for resources - {{.Kind}}CRD = v1beta1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "{{.Resource}}.{{.Group}}.{{.Domain}}", - }, - Spec: v1beta1.CustomResourceDefinitionSpec { - Group: "{{.Group}}.{{.Domain}}", - Version: "{{.Version}}", - Names: v1beta1.CustomResourceDefinitionNames{ - Kind: "{{.Kind}}", - Plural: "{{.Resource}}", - {{ if .ShortName -}} - ShortNames: []string{"{{.ShortName}}"}, - {{ end -}} - {{ if .Categories -}} - Categories: []string{ - {{ range .Categories -}} - "{{ . }}", - {{ end -}} - }, - {{ end -}} - }, - {{ if .NonNamespaced -}} - Scope: "Cluster", - {{ else -}} - Scope: "Namespaced", - {{ end -}} - Validation: &v1beta1.CustomResourceValidation{ - OpenAPIV3Schema: &{{.Validation}}, - }, - {{ if .HasStatusSubresource -}} - Subresources: &v1beta1.CustomResourceSubresources{ - Status: &v1beta1.CustomResourceSubresourceStatus{}, - }, - {{ end -}} - }, - } - {{ end -}} -) -` diff --git a/cmd/kubebuilder-gen/main.go b/cmd/kubebuilder-gen/main.go deleted file mode 100644 index 86c36c1f598..00000000000 --- a/cmd/kubebuilder-gen/main.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2017 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 main - -import ( - "github.com/golang/glog" - "k8s.io/apiserver/pkg/util/logs" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder-gen/codegen/run" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder-gen/internal/controllergen" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder-gen/internal/resourcegen" -) - -func main() { - logs.InitLogs() - defer logs.FlushLogs() - - (&run.CodeGenerator{OutputFileBaseName: "zz_generated.kubebuilder"}). - AddControllerGenerator(&controllergen.Generator{}). - AddResourceGenerator(&resourcegen.Generator{}). - Execute() - - glog.V(2).Info("Completed successfully.") -} diff --git a/cmd/kubebuilder/main.go b/cmd/main.go similarity index 96% rename from cmd/kubebuilder/main.go rename to cmd/main.go index 294404b9be0..31ddb296ad4 100644 --- a/cmd/kubebuilder/main.go +++ b/cmd/main.go @@ -25,12 +25,11 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder/util" - "sigs.k8s.io/kubebuilder/cmd/kubebuilder/version" + "sigs.k8s.io/kubebuilder/cmd/util" + "sigs.k8s.io/kubebuilder/cmd/version" ) func main() { - util.CheckInstall() gopath := os.Getenv("GOPATH") if len(gopath) == 0 { gopath = gobuild.Default.GOPATH diff --git a/cmd/kubebuilder/util/stdin.go b/cmd/util/stdin.go similarity index 100% rename from cmd/kubebuilder/util/stdin.go rename to cmd/util/stdin.go diff --git a/cmd/kubebuilder/util/util.go b/cmd/util/util.go similarity index 84% rename from cmd/kubebuilder/util/util.go rename to cmd/util/util.go index 1d7801d6b4b..4e2d07f56e8 100644 --- a/cmd/kubebuilder/util/util.go +++ b/cmd/util/util.go @@ -143,31 +143,6 @@ func DoCmd(cmd string, args ...string) { } } -func CheckInstall() { - bins := []string{"kubebuilder-gen", "client-gen", "deepcopy-gen", "gen-apidocs", "informer-gen", - "openapi-gen", "kubebuilder", "conversion-gen", "defaulter-gen", "lister-gen"} - missing := []string{} - - e, err := os.Executable() - if err != nil { - log.Fatal("unable to get directory of kubebuilder tools") - } - - dir := filepath.Dir(e) - for _, b := range bins { - _, err = os.Stat(filepath.Join(dir, b)) - if err != nil { - missing = append(missing, b) - } - } - if len(missing) > 0 { - log.Fatalf("Error running kubebuilder."+ - "\nThe following files are missing [%s]"+ - "\nkubebuilder must be installed using a release tar.gz downloaded from the git repo.", - strings.Join(missing, ",")) - } -} - func IsNewVersion() bool { _, err := os.Stat("PROJECT") if err != nil { diff --git a/cmd/kubebuilder/vendor_update.go b/cmd/vendor_update.go similarity index 100% rename from cmd/kubebuilder/vendor_update.go rename to cmd/vendor_update.go diff --git a/cmd/kubebuilder/version/version.go b/cmd/version/version.go similarity index 100% rename from cmd/kubebuilder/version/version.go rename to cmd/version/version.go diff --git a/cmd/kubebuilder/webhook.go b/cmd/webhook.go similarity index 100% rename from cmd/kubebuilder/webhook.go rename to cmd/webhook.go diff --git a/common.sh b/common.sh index 88a2cefe09c..c6578bb2fa5 100644 --- a/common.sh +++ b/common.sh @@ -96,7 +96,6 @@ function prepare_staging_dir { rm -rf "$kb_root_dir" else rm -f "$kb_root_dir/kubebuilder/bin/kubebuilder" - rm -f "$kb_root_dir/kubebuilder/bin/kubebuilder-gen" fi } @@ -124,11 +123,10 @@ function build_kb { opts="" else # TODO: what does this thing do. - opts=-ldflags "-X sigs.k8s.io/kubebuilder/cmd/kubebuilder/version.kubeBuilderVersion=$INJECT_KB_VERSION" + opts=-ldflags "-X sigs.k8s.io/kubebuilder/cmd/version.kubeBuilderVersion=$INJECT_KB_VERSION" fi - go build $opts -o $tmp_root/kubebuilder/bin/kubebuilder ./cmd/kubebuilder - go build $opts -o $tmp_root/kubebuilder/bin/kubebuilder-gen ./cmd/kubebuilder-gen + go build $opts -o $tmp_root/kubebuilder/bin/kubebuilder ./cmd } function prepare_testdir_under_gopath { diff --git a/generated_golden.sh b/generated_golden.sh index 5264e1b83a4..a3d57b8dd83 100755 --- a/generated_golden.sh +++ b/generated_golden.sh @@ -16,7 +16,7 @@ set -e -go build -o ./bin/kubebuilder sigs.k8s.io/kubebuilder/cmd/kubebuilder +go build -o ./bin/kubebuilder sigs.k8s.io/kubebuilder/cmd rm -rf ./test/project/* cd test/project ln -s ../../vendor vendor diff --git a/testv1.sh b/test.sh similarity index 100% rename from testv1.sh rename to test.sh diff --git a/test/e2e/e2e_v1.go b/test/e2e/e2e.go similarity index 100% rename from test/e2e/e2e_v1.go rename to test/e2e/e2e.go diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 8a0df998abe..82489a45389 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -55,18 +55,15 @@ var _ = BeforeSuite(func(done Done) { // $ kubectl create clusterrolebinding myname-cluster-admin-binding --clusterrole=cluster-admin --user=myname@mycompany.com framework.TestContext.BinariesDir = "/tmp/kubebuilder/bin/" // build a kubebuilder - targets := []string{"kubebuilder", "kubebuilder-gen"} - for _, target := range targets { - buildOptions := []string{ - "build", "-o", path.Join(framework.TestContext.BinariesDir, target), path.Join("sigs.k8s.io/kubebuilder/cmd", target)} - cmd := exec.Command("go", buildOptions...) - cmd.Env = os.Environ() - command := strings.Join(cmd.Args, " ") - log.Printf("running %v", command) - output, err := cmd.CombinedOutput() - log.Printf("output when running:\n%s", output) - Expect(err).NotTo(HaveOccurred()) - } + buildOptions := []string{ + "build", "-o", path.Join(framework.TestContext.BinariesDir, "kubebuilder"), "sigs.k8s.io/kubebuilder/cmd"} + cmd := exec.Command("go", buildOptions...) + cmd.Env = os.Environ() + command := strings.Join(cmd.Args, " ") + log.Printf("running %v", command) + output, err := cmd.CombinedOutput() + log.Printf("output when running:\n%s", output) + Expect(err).NotTo(HaveOccurred()) close(done) }, 60)