use traditional controller tool to generate code
This commit is contained in:
201
vendor/sigs.k8s.io/controller-tools/LICENSE
generated
vendored
Normal file
201
vendor/sigs.k8s.io/controller-tools/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
210
vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go
generated
vendored
Normal file
210
vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
crdgenerator "sigs.k8s.io/controller-tools/pkg/crd/generator"
|
||||
"sigs.k8s.io/controller-tools/pkg/rbac"
|
||||
"sigs.k8s.io/controller-tools/pkg/webhook"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "controller-gen",
|
||||
Short: "A reference implementation generation tool for Kubernetes APIs.",
|
||||
Long: `A reference implementation generation tool for Kubernetes APIs.`,
|
||||
Example: ` # Generate RBAC manifests for a project
|
||||
controller-gen rbac
|
||||
|
||||
# Generate CRD manifests for a project
|
||||
controller-gen crd
|
||||
|
||||
# Run all the generators for a given project
|
||||
controller-gen all
|
||||
`,
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(
|
||||
newRBACCmd(),
|
||||
newCRDCmd(),
|
||||
newWebhookCmd(),
|
||||
newAllSubCmd(),
|
||||
)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func newRBACCmd() *cobra.Command {
|
||||
o := &rbac.ManifestOptions{}
|
||||
o.SetDefaults()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rbac",
|
||||
Short: "Generates RBAC manifests",
|
||||
Long: `Generate RBAC manifests from the RBAC annotations in Go source files.
|
||||
Usage:
|
||||
# controller-gen rbac [--name manager] [--input-dir input_dir] [--output-dir output_dir]
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if err := rbac.Generate(o); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("RBAC manifests generated under '%s' directory\n", o.OutputDir)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.Name, "name", o.Name, "name to be used as prefix in identifier for manifests")
|
||||
f.StringVar(&o.ServiceAccount, "service-account", o.ServiceAccount, "service account to bind the role to")
|
||||
f.StringVar(&o.Namespace, "service-account-namespace", o.Namespace, "namespace of the service account to bind the role to")
|
||||
f.StringVar(&o.InputDir, "input-dir", o.InputDir, "input directory pointing to Go source files")
|
||||
f.StringVar(&o.OutputDir, "output-dir", o.OutputDir, "output directory where generated manifests will be saved")
|
||||
f.StringVar(&o.RoleFile, "role-file", o.RoleFile, "output file for the role manifest")
|
||||
f.StringVar(&o.BindingFile, "binding-file", o.BindingFile, "output file for the role binding manifest")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newCRDCmd() *cobra.Command {
|
||||
g := &crdgenerator.Generator{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "crd",
|
||||
Short: "Generates CRD manifests",
|
||||
Long: `Generate CRD manifests from the Type definitions in Go source files.
|
||||
Usage:
|
||||
# controller-gen crd [--domain k8s.io] [--root-path input_dir] [--output-dir output_dir]
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if err := g.ValidateAndInitFields(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := g.Do(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("CRD files generated, files can be found under path %s.\n", g.OutputDir)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&g.RootPath, "root-path", "", "working dir, must have PROJECT file under the path or parent path if domain not set")
|
||||
f.StringVar(&g.OutputDir, "output-dir", "", "output directory, default to 'config/crds' under root path")
|
||||
f.StringVar(&g.Domain, "domain", "", "domain of the resources, will try to fetch it from PROJECT file if not specified")
|
||||
f.StringVar(&g.Namespace, "namespace", "", "CRD namespace, treat it as cluster scoped if not set")
|
||||
f.BoolVar(&g.SkipMapValidation, "skip-map-validation", true, "if set to true, skip generating OpenAPI validation schema for map type in CRD.")
|
||||
f.StringVar(&g.APIsPath, "apis-path", "pkg/apis", "the path to search for apis relative to the current directory")
|
||||
f.StringVar(&g.APIsPkg, "apis-pkg", "", "the absolute Go pkg name for current project's api pkg.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newAllSubCmd() *cobra.Command {
|
||||
var (
|
||||
projectDir, namespace string
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "all",
|
||||
Short: "runs all generators for a project",
|
||||
Long: `Run all available generators for a given project
|
||||
Usage:
|
||||
# controller-gen all
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if projectDir == "" {
|
||||
currDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("project-dir missing, failed to use current directory: %v", err)
|
||||
}
|
||||
projectDir = currDir
|
||||
}
|
||||
crdGen := &crdgenerator.Generator{
|
||||
RootPath: projectDir,
|
||||
OutputDir: filepath.Join(projectDir, "config", "crds"),
|
||||
Namespace: namespace,
|
||||
SkipMapValidation: true,
|
||||
}
|
||||
if err := crdGen.ValidateAndInitFields(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := crdGen.Do(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("CRD manifests generated under '%s' \n", crdGen.OutputDir)
|
||||
|
||||
// RBAC generation
|
||||
rbacOptions := &rbac.ManifestOptions{}
|
||||
rbacOptions.SetDefaults()
|
||||
if err := rbac.Generate(rbacOptions); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("RBAC manifests generated under '%s' \n", rbacOptions.OutputDir)
|
||||
|
||||
o := &webhook.Options{
|
||||
WriterOptions: webhook.WriterOptions{
|
||||
InputDir: filepath.Join(projectDir, "pkg"),
|
||||
OutputDir: filepath.Join(projectDir, "config", "webhook"),
|
||||
PatchOutputDir: filepath.Join(projectDir, "config", "default"),
|
||||
},
|
||||
}
|
||||
o.SetDefaults()
|
||||
if err := webhook.Generate(o); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("webhook manifests generated under '%s' directory\n", o.OutputDir)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&projectDir, "project-dir", "", "project directory, it must have PROJECT file")
|
||||
f.StringVar(&namespace, "namespace", "", "CRD namespace, treat it as cluster scoped if not set")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newWebhookCmd() *cobra.Command {
|
||||
o := &webhook.Options{}
|
||||
o.SetDefaults()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "webhook",
|
||||
Short: "Generates webhook related manifests",
|
||||
Long: `Generate webhook related manifests from the webhook annotations in Go source files.
|
||||
Usage:
|
||||
# controller-gen webhook [--input-dir input_dir] [--output-dir output_dir] [--patch-output-dir patch-output_dir]
|
||||
`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if err := webhook.Generate(o); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("webhook manifests generated under '%s' directory\n", o.OutputDir)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.InputDir, "input-dir", o.InputDir, "input directory pointing to Go source files")
|
||||
f.StringVar(&o.OutputDir, "output-dir", o.OutputDir, "output directory where generated manifests will be saved.")
|
||||
f.StringVar(&o.PatchOutputDir, "patch-output-dir", o.PatchOutputDir, "output directory where generated kustomize patch will be saved.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
207
vendor/sigs.k8s.io/controller-tools/pkg/crd/generator/generator.go
generated
vendored
Normal file
207
vendor/sigs.k8s.io/controller-tools/pkg/crd/generator/generator.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/afero"
|
||||
extensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/types"
|
||||
crdutil "sigs.k8s.io/controller-tools/pkg/crd/util"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen/parse"
|
||||
"sigs.k8s.io/controller-tools/pkg/util"
|
||||
)
|
||||
|
||||
// Generator generates CRD manifests from API resource definitions defined in Go source files.
|
||||
type Generator struct {
|
||||
RootPath string
|
||||
OutputDir string
|
||||
Domain string
|
||||
Namespace string
|
||||
SkipMapValidation bool
|
||||
|
||||
// OutFs is filesystem to be used for writing out the result
|
||||
OutFs afero.Fs
|
||||
|
||||
// apisPkg is the absolute Go pkg name for current project's 'pkg/apis' pkg.
|
||||
// This is needed to determine if a Type belongs to the project or it is a referred Type.
|
||||
apisPkg string
|
||||
|
||||
// APIsPath and APIsPkg allow customized generation for Go types existing under directories other than pkg/apis
|
||||
APIsPath string
|
||||
APIsPkg string
|
||||
}
|
||||
|
||||
// ValidateAndInitFields validate and init generator fields.
|
||||
func (c *Generator) ValidateAndInitFields() error {
|
||||
var err error
|
||||
|
||||
if c.OutFs == nil {
|
||||
c.OutFs = afero.NewOsFs()
|
||||
}
|
||||
|
||||
if len(c.RootPath) == 0 {
|
||||
// Take current path as root path if not specified.
|
||||
c.RootPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Validate root path is under go src path
|
||||
if !crdutil.IsUnderGoSrcPath(c.RootPath) {
|
||||
return fmt.Errorf("command must be run from path under $GOPATH/src/<package>")
|
||||
}
|
||||
|
||||
// If Domain is not explicitly specified,
|
||||
// try to search for PROJECT file as a basis.
|
||||
if len(c.Domain) == 0 {
|
||||
if !crdutil.PathHasProjectFile(c.RootPath) {
|
||||
return fmt.Errorf("PROJECT file missing in dir %s", c.RootPath)
|
||||
}
|
||||
c.Domain = crdutil.GetDomainFromProject(c.RootPath)
|
||||
}
|
||||
|
||||
err = c.setAPIsPkg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Init output directory
|
||||
if c.OutputDir == "" {
|
||||
c.OutputDir = path.Join(c.RootPath, "config/crds")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do manages CRD generation.
|
||||
func (c *Generator) Do() error {
|
||||
arguments := args.Default()
|
||||
b, err := arguments.NewBuilder()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed making a parser: %v", err)
|
||||
}
|
||||
|
||||
// Switch working directory to root path.
|
||||
if err := os.Chdir(c.RootPath); err != nil {
|
||||
return fmt.Errorf("failed switching working dir: %v", err)
|
||||
}
|
||||
|
||||
if err := b.AddDirRecursive("./" + c.APIsPath); err != nil {
|
||||
return fmt.Errorf("failed making a parser: %v", err)
|
||||
}
|
||||
ctx, err := parse.NewContext(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed making a context: %v", err)
|
||||
}
|
||||
|
||||
arguments.CustomArgs = &parse.Options{SkipMapValidation: c.SkipMapValidation}
|
||||
|
||||
// TODO: find an elegant way to fulfill the domain in APIs.
|
||||
p := parse.NewAPIs(ctx, arguments, c.Domain, c.apisPkg)
|
||||
crds := c.getCrds(p)
|
||||
|
||||
return c.writeCRDs(crds)
|
||||
}
|
||||
|
||||
func (c *Generator) writeCRDs(crds map[string][]byte) error {
|
||||
// Ensure output dir exists.
|
||||
if err := c.OutFs.MkdirAll(c.OutputDir, os.FileMode(0700)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for file, crd := range crds {
|
||||
outFile := path.Join(c.OutputDir, file)
|
||||
if err := (&util.FileWriter{Fs: c.OutFs}).WriteFile(outFile, crd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCRDFileName(resource *codegen.APIResource) string {
|
||||
elems := []string{resource.Group, resource.Version, strings.ToLower(resource.Kind)}
|
||||
return strings.Join(elems, "_") + ".yaml"
|
||||
}
|
||||
|
||||
func (c *Generator) getCrds(p *parse.APIs) map[string][]byte {
|
||||
crds := map[string]extensionsv1beta1.CustomResourceDefinition{}
|
||||
for _, g := range p.APIs.Groups {
|
||||
for _, v := range g.Versions {
|
||||
for _, r := range v.Resources {
|
||||
crd := r.CRD
|
||||
// ignore types which do not belong to this project
|
||||
if !c.belongsToAPIsPkg(r.Type) {
|
||||
continue
|
||||
}
|
||||
if len(c.Namespace) > 0 {
|
||||
crd.Namespace = c.Namespace
|
||||
}
|
||||
fileName := getCRDFileName(r)
|
||||
crds[fileName] = crd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string][]byte{}
|
||||
for file, crd := range crds {
|
||||
b, err := yaml.Marshal(crd)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
result[file] = b
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// belongsToAPIsPkg returns true if type t is defined under pkg/apis pkg of
|
||||
// current project.
|
||||
func (c *Generator) belongsToAPIsPkg(t *types.Type) bool {
|
||||
return strings.HasPrefix(t.Name.Package, c.apisPkg)
|
||||
}
|
||||
|
||||
func (c *Generator) setAPIsPkg() error {
|
||||
var err error
|
||||
if c.APIsPath == "" {
|
||||
c.APIsPath = "pkg/apis"
|
||||
}
|
||||
|
||||
c.apisPkg = c.APIsPkg
|
||||
if c.apisPkg == "" {
|
||||
// Validate apis directory exists under working path
|
||||
apisPath := path.Join(c.RootPath, c.APIsPath)
|
||||
if _, err := os.Stat(apisPath); err != nil {
|
||||
return fmt.Errorf("error validating apis path %s: %v", apisPath, err)
|
||||
}
|
||||
|
||||
c.apisPkg, err = crdutil.DirToGoPkg(apisPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
117
vendor/sigs.k8s.io/controller-tools/pkg/crd/util/util.go
generated
vendored
Normal file
117
vendor/sigs.k8s.io/controller-tools/pkg/crd/util/util.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
gobuild "go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsGoSrcPath validate if given path is of path $GOPATH/src.
|
||||
func IsGoSrcPath(filePath string) bool {
|
||||
for _, gopath := range getGoPaths() {
|
||||
goSrc := path.Join(gopath, "src")
|
||||
if filePath == goSrc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnderGoSrcPath validate if given path is under path $GOPATH/src.
|
||||
func IsUnderGoSrcPath(filePath string) bool {
|
||||
for _, gopath := range getGoPaths() {
|
||||
goSrc := path.Join(gopath, "src")
|
||||
if strings.HasPrefix(filepath.Dir(filePath), goSrc) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DirToGoPkg returns the Gopkg for the given directory if it exists
|
||||
// under a GOPATH otherwise returns error. For example,
|
||||
// /Users/x/go/src/github.com/y/z ==> github.com/y/z
|
||||
func DirToGoPkg(dir string) (pkg string, err error) {
|
||||
goPaths := getGoPaths()
|
||||
for _, gopath := range goPaths {
|
||||
goSrc := path.Join(gopath, "src")
|
||||
if !strings.HasPrefix(dir, goSrc) {
|
||||
continue
|
||||
}
|
||||
pkg, err := filepath.Rel(goSrc, dir)
|
||||
if err == nil {
|
||||
return pkg, err
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("dir '%s' does not exist under any GOPATH %v", dir, goPaths)
|
||||
}
|
||||
|
||||
func getGoPaths() []string {
|
||||
gopaths := os.Getenv("GOPATH")
|
||||
if len(gopaths) == 0 {
|
||||
gopaths = gobuild.Default.GOPATH
|
||||
}
|
||||
return filepath.SplitList(gopaths)
|
||||
}
|
||||
|
||||
// PathHasProjectFile validate if PROJECT file exists under the path.
|
||||
func PathHasProjectFile(filePath string) bool {
|
||||
if _, err := os.Stat(path.Join(filePath, "PROJECT")); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetDomainFromProject get domain information from the PROJECT file under the path.
|
||||
func GetDomainFromProject(rootPath string) string {
|
||||
var domain string
|
||||
|
||||
file, err := os.Open(path.Join(rootPath, "PROJECT"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "domain:") {
|
||||
domainInfo := strings.Split(scanner.Text(), ":")
|
||||
if len(domainInfo) != 2 {
|
||||
log.Fatalf("Unexpected domain info: %s", scanner.Text())
|
||||
}
|
||||
domain = strings.Replace(domainInfo[1], " ", "", -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
287
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/apis.go
generated
vendored
Normal file
287
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/apis.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
)
|
||||
|
||||
type genUnversionedType struct {
|
||||
Type *types.Type
|
||||
Resource *codegen.APIResource
|
||||
}
|
||||
|
||||
func (b *APIs) parseAPIs() {
|
||||
apis := &codegen.APIs{
|
||||
Domain: b.Domain,
|
||||
Package: b.APIsPkg,
|
||||
Groups: map[string]*codegen.APIGroup{},
|
||||
Rules: b.Rules,
|
||||
Informers: b.Informers,
|
||||
}
|
||||
|
||||
for group, versionMap := range b.ByGroupVersionKind {
|
||||
apiGroup := &codegen.APIGroup{
|
||||
Group: group,
|
||||
GroupTitle: strings.Title(group),
|
||||
Domain: b.Domain,
|
||||
Versions: map[string]*codegen.APIVersion{},
|
||||
UnversionedResources: map[string]*codegen.APIResource{},
|
||||
}
|
||||
|
||||
for version, kindMap := range versionMap {
|
||||
apiVersion := &codegen.APIVersion{
|
||||
Domain: b.Domain,
|
||||
Group: group,
|
||||
Version: version,
|
||||
Resources: map[string]*codegen.APIResource{},
|
||||
}
|
||||
for kind, resource := range kindMap {
|
||||
apiResource := &codegen.APIResource{
|
||||
Domain: resource.Domain,
|
||||
Version: resource.Version,
|
||||
Group: resource.Group,
|
||||
Resource: resource.Resource,
|
||||
Type: resource.Type,
|
||||
REST: resource.REST,
|
||||
Kind: resource.Kind,
|
||||
Subresources: resource.Subresources,
|
||||
StatusStrategy: resource.StatusStrategy,
|
||||
Strategy: resource.Strategy,
|
||||
NonNamespaced: resource.NonNamespaced,
|
||||
ShortName: resource.ShortName,
|
||||
}
|
||||
parseDoc(resource, apiResource)
|
||||
apiVersion.Resources[kind] = apiResource
|
||||
// Set the package for the api version
|
||||
apiVersion.Pkg = b.context.Universe[resource.Type.Name.Package]
|
||||
// Set the package for the api group
|
||||
apiGroup.Pkg = b.context.Universe[filepath.Dir(resource.Type.Name.Package)]
|
||||
if apiGroup.Pkg != nil {
|
||||
apiGroup.PkgPath = apiGroup.Pkg.Path
|
||||
}
|
||||
|
||||
apiGroup.UnversionedResources[kind] = apiResource
|
||||
}
|
||||
|
||||
apiGroup.Versions[version] = apiVersion
|
||||
}
|
||||
b.parseStructs(apiGroup)
|
||||
apis.Groups[group] = apiGroup
|
||||
}
|
||||
apis.Pkg = b.context.Universe[b.APIsPkg]
|
||||
b.APIs = apis
|
||||
}
|
||||
|
||||
func (b *APIs) parseStructs(apigroup *codegen.APIGroup) {
|
||||
remaining := []genUnversionedType{}
|
||||
for _, version := range apigroup.Versions {
|
||||
for _, resource := range version.Resources {
|
||||
remaining = append(remaining, genUnversionedType{resource.Type, resource})
|
||||
}
|
||||
}
|
||||
for _, version := range b.SubByGroupVersionKind[apigroup.Group] {
|
||||
for _, kind := range version {
|
||||
remaining = append(remaining, genUnversionedType{kind, nil})
|
||||
}
|
||||
}
|
||||
|
||||
done := sets.String{}
|
||||
for len(remaining) > 0 {
|
||||
// Pop the next element from the list
|
||||
next := remaining[0]
|
||||
remaining[0] = remaining[len(remaining)-1]
|
||||
remaining = remaining[:len(remaining)-1]
|
||||
|
||||
// Already processed this type. Skip it
|
||||
if done.Has(next.Type.Name.Name) {
|
||||
continue
|
||||
}
|
||||
done.Insert(next.Type.Name.Name)
|
||||
|
||||
// Generate the struct and append to the list
|
||||
result, additionalTypes := parseType(next.Type)
|
||||
|
||||
// This is a resource, so generate the client
|
||||
if b.genClient(next.Type) {
|
||||
result.GenClient = true
|
||||
result.GenDeepCopy = true
|
||||
}
|
||||
|
||||
if next.Resource != nil {
|
||||
result.NonNamespaced = IsNonNamespaced(next.Type)
|
||||
}
|
||||
|
||||
if b.genDeepCopy(next.Type) {
|
||||
result.GenDeepCopy = true
|
||||
}
|
||||
apigroup.Structs = append(apigroup.Structs, result)
|
||||
|
||||
// Add the newly discovered subtypes
|
||||
for _, at := range additionalTypes {
|
||||
remaining = append(remaining, genUnversionedType{at, nil})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseType parses the type into a Struct, and returns a list of types that
|
||||
// need to be parsed
|
||||
func parseType(t *types.Type) (*codegen.Struct, []*types.Type) {
|
||||
remaining := []*types.Type{}
|
||||
|
||||
s := &codegen.Struct{
|
||||
Name: t.Name.Name,
|
||||
GenClient: false,
|
||||
GenUnversioned: true, // Generate unversioned structs by default
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+genregister:unversioned=false") {
|
||||
// Don't generate the unversioned struct
|
||||
s.GenUnversioned = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, member := range t.Members {
|
||||
uType := member.Type.Name.Name
|
||||
memberName := member.Name
|
||||
uImport := ""
|
||||
|
||||
// Use the element type for Pointers, Maps and Slices
|
||||
mSubType := member.Type
|
||||
hasElem := false
|
||||
for mSubType.Elem != nil {
|
||||
mSubType = mSubType.Elem
|
||||
hasElem = true
|
||||
}
|
||||
if hasElem {
|
||||
// Strip the package from the field type
|
||||
uType = strings.Replace(member.Type.String(), mSubType.Name.Package+".", "", 1)
|
||||
}
|
||||
|
||||
base := filepath.Base(member.Type.String())
|
||||
samepkg := t.Name.Package == mSubType.Name.Package
|
||||
|
||||
// If not in the same package, calculate the import pkg
|
||||
if !samepkg {
|
||||
parts := strings.Split(base, ".")
|
||||
if len(parts) > 1 {
|
||||
// Don't generate unversioned types for core types, just use the versioned types
|
||||
if strings.HasPrefix(mSubType.Name.Package, "k8s.io/api/") {
|
||||
// Import the package under an alias so it doesn't conflict with other groups
|
||||
// having the same version
|
||||
importAlias := path.Base(path.Dir(mSubType.Name.Package)) + path.Base(mSubType.Name.Package)
|
||||
uImport = fmt.Sprintf("%s \"%s\"", importAlias, mSubType.Name.Package)
|
||||
if hasElem {
|
||||
// Replace the full package with the alias when referring to the type
|
||||
uType = strings.Replace(member.Type.String(), mSubType.Name.Package, importAlias, 1)
|
||||
} else {
|
||||
// Replace the full package with the alias when referring to the type
|
||||
uType = fmt.Sprintf("%s.%s", importAlias, parts[1])
|
||||
}
|
||||
} else {
|
||||
switch member.Type.Name.Package {
|
||||
case "k8s.io/apimachinery/pkg/apis/meta/v1":
|
||||
// Use versioned types for meta/v1
|
||||
uImport = fmt.Sprintf("%s \"%s\"", "metav1", "k8s.io/apimachinery/pkg/apis/meta/v1")
|
||||
uType = "metav1." + parts[1]
|
||||
default:
|
||||
// Use unversioned types for everything else
|
||||
t := member.Type
|
||||
|
||||
if t.Elem != nil {
|
||||
// handle Pointers, Maps, Slices
|
||||
|
||||
// We need to parse the package from the Type String
|
||||
t = t.Elem
|
||||
str := member.Type.String()
|
||||
startPkg := strings.LastIndexAny(str, "*]")
|
||||
endPkg := strings.LastIndexAny(str, ".")
|
||||
pkg := str[startPkg+1 : endPkg]
|
||||
name := str[endPkg+1:]
|
||||
prefix := str[:startPkg+1]
|
||||
|
||||
uImportBase := path.Base(pkg)
|
||||
uImportName := path.Base(path.Dir(pkg)) + uImportBase
|
||||
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
|
||||
|
||||
uType = prefix + uImportName + "." + name
|
||||
} else {
|
||||
// handle non- Pointer, Maps, Slices
|
||||
pkg := t.Name.Package
|
||||
name := t.Name.Name
|
||||
|
||||
// Come up with the alias the package is imported under
|
||||
// Concatenate with directory package to reduce naming collisions
|
||||
uImportBase := path.Base(pkg)
|
||||
uImportName := path.Base(path.Dir(pkg)) + uImportBase
|
||||
|
||||
// Create the import statement
|
||||
uImport = fmt.Sprintf("%s \"%s\"", uImportName, pkg)
|
||||
|
||||
// Create the field type name - should be <pkgalias>.<TypeName>
|
||||
uType = uImportName + "." + name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if member.Embedded {
|
||||
memberName = ""
|
||||
}
|
||||
|
||||
s.Fields = append(s.Fields, &codegen.Field{
|
||||
Name: memberName,
|
||||
VersionedPackage: member.Type.Name.Package,
|
||||
UnversionedImport: uImport,
|
||||
UnversionedType: uType,
|
||||
})
|
||||
|
||||
// Add this member Type for processing if it isn't a primitive and
|
||||
// is part of the same API group
|
||||
if !mSubType.IsPrimitive() && GetGroup(mSubType) == GetGroup(t) {
|
||||
remaining = append(remaining, mSubType)
|
||||
}
|
||||
}
|
||||
return s, remaining
|
||||
}
|
||||
|
||||
func (b *APIs) genClient(c *types.Type) bool {
|
||||
comments := Comments(c.CommentLines)
|
||||
resource := comments.getTag("resource", ":") + comments.getTag("kubebuilder:resource", ":")
|
||||
return len(resource) > 0
|
||||
}
|
||||
|
||||
func (b *APIs) genDeepCopy(c *types.Type) bool {
|
||||
comments := Comments(c.CommentLines)
|
||||
return comments.hasTag("subresource-request")
|
||||
}
|
||||
|
||||
func parseDoc(resource, apiResource *codegen.APIResource) {
|
||||
if HasDocAnnotation(resource.Type) {
|
||||
resource.DocAnnotation = getDocAnnotation(resource.Type, "warning", "note")
|
||||
apiResource.DocAnnotation = resource.DocAnnotation
|
||||
}
|
||||
}
|
||||
42
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/context.go
generated
vendored
Normal file
42
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/context.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/namer"
|
||||
"k8s.io/gengo/parser"
|
||||
)
|
||||
|
||||
// NewContext returns a new Context from the builder
|
||||
func NewContext(p *parser.Builder) (*generator.Context, error) {
|
||||
return generator.NewContext(p, NameSystems(), DefaultNameSystem())
|
||||
}
|
||||
|
||||
// DefaultNameSystem returns public by default.
|
||||
func DefaultNameSystem() string {
|
||||
return "public"
|
||||
}
|
||||
|
||||
// NameSystems returns the name system used by the generators in this package.
|
||||
// e.g. black-magic
|
||||
func NameSystems() namer.NameSystems {
|
||||
return namer.NameSystems{
|
||||
"public": namer.NewPublicNamer(1),
|
||||
"raw": namer.NewRawNamer("", nil),
|
||||
}
|
||||
}
|
||||
639
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/crd.go
generated
vendored
Normal file
639
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/crd.go
generated
vendored
Normal file
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
// parseCRDs populates the CRD field of each Group.Version.Resource,
|
||||
// creating validations using the annotations on type fields.
|
||||
func (b *APIs) parseCRDs() {
|
||||
for _, group := range b.APIs.Groups {
|
||||
for _, version := range group.Versions {
|
||||
for _, resource := range version.Resources {
|
||||
if IsAPIResource(resource.Type) {
|
||||
resource.JSONSchemaProps, resource.Validation =
|
||||
b.typeToJSONSchemaProps(resource.Type, sets.NewString(), []string{}, true)
|
||||
|
||||
// Note: Drop the Type field at the root level of validation
|
||||
// schema. Refer to following issue for details.
|
||||
// https://github.com/kubernetes/kubernetes/issues/65293
|
||||
resource.JSONSchemaProps.Type = ""
|
||||
j, err := json.MarshalIndent(resource.JSONSchemaProps, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Could not Marshall validation %v\n", err)
|
||||
}
|
||||
resource.ValidationComments = string(j)
|
||||
|
||||
resource.CRD = v1beta1.CustomResourceDefinition{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apiextensions.k8s.io/v1beta1",
|
||||
Kind: "CustomResourceDefinition",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s.%s.%s", resource.Resource, resource.Group, resource.Domain),
|
||||
Labels: map[string]string{"controller-tools.k8s.io": "1.0"},
|
||||
},
|
||||
Spec: v1beta1.CustomResourceDefinitionSpec{
|
||||
Group: fmt.Sprintf("%s.%s", resource.Group, resource.Domain),
|
||||
Version: resource.Version,
|
||||
Names: v1beta1.CustomResourceDefinitionNames{
|
||||
Kind: resource.Kind,
|
||||
Plural: resource.Resource,
|
||||
},
|
||||
Validation: &v1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &resource.JSONSchemaProps,
|
||||
},
|
||||
},
|
||||
}
|
||||
if resource.NonNamespaced {
|
||||
resource.CRD.Spec.Scope = "Cluster"
|
||||
} else {
|
||||
resource.CRD.Spec.Scope = "Namespaced"
|
||||
}
|
||||
|
||||
if hasCategories(resource.Type) {
|
||||
categoriesTag := getCategoriesTag(resource.Type)
|
||||
categories := strings.Split(categoriesTag, ",")
|
||||
resource.CRD.Spec.Names.Categories = categories
|
||||
resource.Categories = categories
|
||||
}
|
||||
|
||||
if hasSingular(resource.Type) {
|
||||
singularName := getSingularName(resource.Type)
|
||||
resource.CRD.Spec.Names.Singular = singularName
|
||||
}
|
||||
|
||||
if hasStatusSubresource(resource.Type) {
|
||||
if resource.CRD.Spec.Subresources == nil {
|
||||
resource.CRD.Spec.Subresources = &v1beta1.CustomResourceSubresources{}
|
||||
}
|
||||
resource.CRD.Spec.Subresources.Status = &v1beta1.CustomResourceSubresourceStatus{}
|
||||
}
|
||||
|
||||
resource.CRD.Status.Conditions = []v1beta1.CustomResourceDefinitionCondition{}
|
||||
resource.CRD.Status.StoredVersions = []string{}
|
||||
|
||||
if hasScaleSubresource(resource.Type) {
|
||||
if resource.CRD.Spec.Subresources == nil {
|
||||
resource.CRD.Spec.Subresources = &v1beta1.CustomResourceSubresources{}
|
||||
}
|
||||
jsonPath, err := parseScaleParams(resource.Type)
|
||||
if err != nil {
|
||||
log.Fatalf("failed in parsing CRD, error: %v", err.Error())
|
||||
}
|
||||
resource.CRD.Spec.Subresources.Scale = &v1beta1.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: jsonPath[specReplicasPath],
|
||||
StatusReplicasPath: jsonPath[statusReplicasPath],
|
||||
}
|
||||
labelSelctor, ok := jsonPath[labelSelectorPath]
|
||||
if ok && labelSelctor != "" {
|
||||
resource.CRD.Spec.Subresources.Scale.LabelSelectorPath = &labelSelctor
|
||||
}
|
||||
}
|
||||
if hasPrintColumn(resource.Type) {
|
||||
result, err := parsePrintColumnParams(resource.Type)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse printcolumn annotations, error: %v", err.Error())
|
||||
}
|
||||
resource.CRD.Spec.AdditionalPrinterColumns = result
|
||||
}
|
||||
if len(resource.ShortName) > 0 {
|
||||
resource.CRD.Spec.Names.ShortNames = strings.Split(resource.ShortName, ";")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *APIs) getTime() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Format: "date-time",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) getDuration() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) getQuantity() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
}`
|
||||
}
|
||||
|
||||
func (b *APIs) objSchema() string {
|
||||
return `v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
}`
|
||||
}
|
||||
|
||||
// typeToJSONSchemaProps returns a JSONSchemaProps object and its serialization
|
||||
// in Go that describe the JSONSchema validations for the given type.
|
||||
func (b *APIs) typeToJSONSchemaProps(t *types.Type, found sets.String, comments []string, isRoot bool) (v1beta1.JSONSchemaProps, string) {
|
||||
// Special cases
|
||||
time := types.Name{Name: "Time", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
duration := types.Name{Name: "Duration", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
quantity := types.Name{Name: "Quantity", Package: "k8s.io/apimachinery/pkg/api/resource"}
|
||||
meta := types.Name{Name: "ObjectMeta", Package: "k8s.io/apimachinery/pkg/apis/meta/v1"}
|
||||
unstructured := types.Name{Name: "Unstructured", Package: "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"}
|
||||
rawExtension := types.Name{Name: "RawExtension", Package: "k8s.io/apimachinery/pkg/runtime"}
|
||||
intOrString := types.Name{Name: "IntOrString", Package: "k8s.io/apimachinery/pkg/util/intstr"}
|
||||
// special types first
|
||||
specialTypeProps := v1beta1.JSONSchemaProps{
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
for _, l := range comments {
|
||||
getValidation(l, &specialTypeProps)
|
||||
}
|
||||
switch t.Name {
|
||||
case time:
|
||||
specialTypeProps.Type = "string"
|
||||
specialTypeProps.Format = "date-time"
|
||||
return specialTypeProps, b.getTime()
|
||||
case duration:
|
||||
specialTypeProps.Type = "string"
|
||||
return specialTypeProps, b.getDuration()
|
||||
case quantity:
|
||||
specialTypeProps.Type = "string"
|
||||
return specialTypeProps, b.getQuantity()
|
||||
case meta, unstructured, rawExtension:
|
||||
specialTypeProps.Type = "object"
|
||||
return specialTypeProps, b.objSchema()
|
||||
case intOrString:
|
||||
specialTypeProps.AnyOf = []v1beta1.JSONSchemaProps{
|
||||
{
|
||||
Type: "string",
|
||||
},
|
||||
{
|
||||
Type: "integer",
|
||||
},
|
||||
}
|
||||
return specialTypeProps, b.objSchema()
|
||||
}
|
||||
|
||||
var v v1beta1.JSONSchemaProps
|
||||
var s string
|
||||
switch t.Kind {
|
||||
case types.Builtin:
|
||||
v, s = b.parsePrimitiveValidation(t, found, comments)
|
||||
case types.Struct:
|
||||
v, s = b.parseObjectValidation(t, found, comments, isRoot)
|
||||
case types.Map:
|
||||
v, s = b.parseMapValidation(t, found, comments)
|
||||
case types.Slice:
|
||||
v, s = b.parseArrayValidation(t, found, comments)
|
||||
case types.Array:
|
||||
v, s = b.parseArrayValidation(t, found, comments)
|
||||
case types.Pointer:
|
||||
v, s = b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
case types.Alias:
|
||||
v, s = b.typeToJSONSchemaProps(t.Underlying, found, comments, false)
|
||||
default:
|
||||
log.Fatalf("Unknown supported Kind %v\n", t.Kind)
|
||||
}
|
||||
|
||||
return v, s
|
||||
}
|
||||
|
||||
var jsonRegex = regexp.MustCompile("json:\"([a-zA-Z0-9,]+)\"")
|
||||
|
||||
type primitiveTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
Value string
|
||||
Format string
|
||||
EnumValue string // TODO check type of enum value to match the type of field
|
||||
Description string
|
||||
}
|
||||
|
||||
var primitiveTemplate = template.Must(template.New("map-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
{{ if .Pattern -}}
|
||||
Pattern: "{{ .Pattern }}",
|
||||
{{ end -}}
|
||||
{{ if .Maximum -}}
|
||||
Maximum: getFloat({{ .Maximum }}),
|
||||
{{ end -}}
|
||||
{{ if .ExclusiveMaximum -}}
|
||||
ExclusiveMaximum: {{ .ExclusiveMaximum }},
|
||||
{{ end -}}
|
||||
{{ if .Minimum -}}
|
||||
Minimum: getFloat({{ .Minimum }}),
|
||||
{{ end -}}
|
||||
{{ if .ExclusiveMinimum -}}
|
||||
ExclusiveMinimum: {{ .ExclusiveMinimum }},
|
||||
{{ end -}}
|
||||
Type: "{{ .Value }}",
|
||||
{{ if .Format -}}
|
||||
Format: "{{ .Format }}",
|
||||
{{ end -}}
|
||||
{{ if .EnumValue -}}
|
||||
Enum: {{ .EnumValue }},
|
||||
{{ end -}}
|
||||
{{ if .MaxLength -}}
|
||||
MaxLength: getInt({{ .MaxLength }}),
|
||||
{{ end -}}
|
||||
{{ if .MinLength -}}
|
||||
MinLength: getInt({{ .MinLength }}),
|
||||
{{ end -}}
|
||||
}`))
|
||||
|
||||
// parsePrimitiveValidation returns a JSONSchemaProps object and its
|
||||
// serialization in Go that describe the validations for the given primitive
|
||||
// type.
|
||||
func (b *APIs) parsePrimitiveValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
props := v1beta1.JSONSchemaProps{Type: string(t.Name.Name)}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
|
||||
var n, f, s, d string
|
||||
switch t.Name.Name {
|
||||
case "int", "int64", "uint64":
|
||||
n = "integer"
|
||||
f = "int64"
|
||||
case "int32", "uint32":
|
||||
n = "integer"
|
||||
f = "int32"
|
||||
case "float", "float32":
|
||||
n = "number"
|
||||
f = "float"
|
||||
case "float64":
|
||||
n = "number"
|
||||
f = "double"
|
||||
case "bool":
|
||||
n = "boolean"
|
||||
case "string":
|
||||
n = "string"
|
||||
f = props.Format
|
||||
default:
|
||||
n = t.Name.Name
|
||||
}
|
||||
if props.Enum != nil {
|
||||
s = parseEnumToString(props.Enum)
|
||||
}
|
||||
d = parseDescription(comments)
|
||||
if err := primitiveTemplate.Execute(buff, primitiveTemplateArgs{props, n, f, s, d}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
props.Type = n
|
||||
props.Format = f
|
||||
props.Description = d
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
type mapTempateArgs struct {
|
||||
Result string
|
||||
SkipMapValidation bool
|
||||
}
|
||||
|
||||
var mapTemplate = template.Must(template.New("map-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
{{if not .SkipMapValidation}}AdditionalProperties: &v1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
Schema: &{{.Result}},
|
||||
},{{end}}
|
||||
}`))
|
||||
|
||||
// parseMapValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given map type.
|
||||
func (b *APIs) parseMapValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
additionalProps, result := b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
additionalProps.Description = ""
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
parseOption := b.arguments.CustomArgs.(*Options)
|
||||
if !parseOption.SkipMapValidation {
|
||||
props.AdditionalProperties = &v1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
Schema: &additionalProps}
|
||||
}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
if err := mapTemplate.Execute(buff, mapTempateArgs{Result: result, SkipMapValidation: parseOption.SkipMapValidation}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
var arrayTemplate = template.Must(template.New("array-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
Type: "{{.Type}}",
|
||||
{{ if .Format -}}
|
||||
Format: "{{.Format}}",
|
||||
{{ end -}}
|
||||
{{ if .MaxItems -}}
|
||||
MaxItems: getInt({{ .MaxItems }}),
|
||||
{{ end -}}
|
||||
{{ if .MinItems -}}
|
||||
MinItems: getInt({{ .MinItems }}),
|
||||
{{ end -}}
|
||||
{{ if .UniqueItems -}}
|
||||
UniqueItems: {{ .UniqueItems }},
|
||||
{{ end -}}
|
||||
{{ if .Items -}}
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &{{.ItemsSchema}},
|
||||
},
|
||||
{{ end -}}
|
||||
}`))
|
||||
|
||||
type arrayTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
ItemsSchema string
|
||||
}
|
||||
|
||||
// parseArrayValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given array type.
|
||||
func (b *APIs) parseArrayValidation(t *types.Type, found sets.String, comments []string) (v1beta1.JSONSchemaProps, string) {
|
||||
items, result := b.typeToJSONSchemaProps(t.Elem, found, comments, false)
|
||||
items.Description = ""
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "array",
|
||||
Items: &v1beta1.JSONSchemaPropsOrArray{Schema: &items},
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
// To represent byte arrays in the generated code, the property of the OpenAPI definition
|
||||
// should have string as its type and byte as its format.
|
||||
if t.Name.Name == "[]byte" {
|
||||
props.Type = "string"
|
||||
props.Format = "byte"
|
||||
props.Items = nil
|
||||
props.Description = parseDescription(comments)
|
||||
}
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
if t.Name.Name != "[]byte" {
|
||||
// Except for the byte array special case above, the "format" property
|
||||
// should be applied to the array items and not the array itself.
|
||||
props.Format = ""
|
||||
}
|
||||
buff := &bytes.Buffer{}
|
||||
if err := arrayTemplate.Execute(buff, arrayTemplateArgs{props, result}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
type objectTemplateArgs struct {
|
||||
v1beta1.JSONSchemaProps
|
||||
Fields map[string]string
|
||||
Required []string
|
||||
IsRoot bool
|
||||
}
|
||||
|
||||
var objectTemplate = template.Must(template.New("object-template").Parse(
|
||||
`v1beta1.JSONSchemaProps{
|
||||
{{ if not .IsRoot -}}
|
||||
Type: "object",
|
||||
{{ end -}}
|
||||
Properties: map[string]v1beta1.JSONSchemaProps{
|
||||
{{ range $k, $v := .Fields -}}
|
||||
"{{ $k }}": {{ $v }},
|
||||
{{ end -}}
|
||||
},
|
||||
{{if .Required}}Required: []string{
|
||||
{{ range $k, $v := .Required -}}
|
||||
"{{ $v }}",
|
||||
{{ end -}}
|
||||
},{{ end -}}
|
||||
}`))
|
||||
|
||||
// parseObjectValidation returns a JSONSchemaProps object and its serialization in
|
||||
// Go that describe the validations for the given object type.
|
||||
func (b *APIs) parseObjectValidation(t *types.Type, found sets.String, comments []string, isRoot bool) (v1beta1.JSONSchemaProps, string) {
|
||||
buff := &bytes.Buffer{}
|
||||
props := v1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Description: parseDescription(comments),
|
||||
}
|
||||
|
||||
for _, l := range comments {
|
||||
getValidation(l, &props)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(t.Name.String(), "k8s.io/api") {
|
||||
if err := objectTemplate.Execute(buff, objectTemplateArgs{props, nil, nil, false}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
} else {
|
||||
m, result, required := b.getMembers(t, found)
|
||||
props.Properties = m
|
||||
props.Required = required
|
||||
|
||||
if err := objectTemplate.Execute(buff, objectTemplateArgs{props, result, required, isRoot}); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
return props, buff.String()
|
||||
}
|
||||
|
||||
// getValidation parses the validation tags from the comment and sets the
|
||||
// validation rules on the given JSONSchemaProps.
|
||||
func getValidation(comment string, props *v1beta1.JSONSchemaProps) {
|
||||
comment = strings.TrimLeft(comment, " ")
|
||||
if !strings.HasPrefix(comment, "+kubebuilder:validation:") {
|
||||
return
|
||||
}
|
||||
c := strings.Replace(comment, "+kubebuilder:validation:", "", -1)
|
||||
parts := strings.Split(c, "=")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected +kubebuilder:validation:<key>=<value> actual: %s", comment)
|
||||
return
|
||||
}
|
||||
switch parts[0] {
|
||||
case "Maximum":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.Maximum = &f
|
||||
case "ExclusiveMaximum":
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.ExclusiveMaximum = b
|
||||
case "Minimum":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.Minimum = &f
|
||||
case "ExclusiveMinimum":
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.ExclusiveMinimum = b
|
||||
case "MaxLength":
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MaxLength = &v
|
||||
case "MinLength":
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MinLength = &v
|
||||
case "Pattern":
|
||||
props.Pattern = parts[1]
|
||||
case "MaxItems":
|
||||
if props.Type == "array" {
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MaxItems = &v
|
||||
}
|
||||
case "MinItems":
|
||||
if props.Type == "array" {
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
v := int64(i)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse int from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MinItems = &v
|
||||
}
|
||||
case "UniqueItems":
|
||||
if props.Type == "array" {
|
||||
b, err := strconv.ParseBool(parts[1])
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse bool from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.UniqueItems = b
|
||||
}
|
||||
case "MultipleOf":
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse float from %s: %v", comment, err)
|
||||
return
|
||||
}
|
||||
props.MultipleOf = &f
|
||||
case "Enum":
|
||||
if props.Type != "array" {
|
||||
value := strings.Split(parts[1], ",")
|
||||
enums := []v1beta1.JSON{}
|
||||
for _, s := range value {
|
||||
checkType(props, s, &enums)
|
||||
}
|
||||
props.Enum = enums
|
||||
}
|
||||
case "Format":
|
||||
props.Format = parts[1]
|
||||
default:
|
||||
log.Fatalf("Unsupport validation: %s", comment)
|
||||
}
|
||||
}
|
||||
|
||||
// getMembers builds maps by field name of the JSONSchemaProps and their Go
|
||||
// serializations.
|
||||
func (b *APIs) getMembers(t *types.Type, found sets.String) (map[string]v1beta1.JSONSchemaProps, map[string]string, []string) {
|
||||
members := map[string]v1beta1.JSONSchemaProps{}
|
||||
result := map[string]string{}
|
||||
required := []string{}
|
||||
|
||||
// Don't allow recursion until we support it through refs
|
||||
// TODO: Support recursion
|
||||
if found.Has(t.Name.String()) {
|
||||
fmt.Printf("Breaking recursion for type %s", t.Name.String())
|
||||
return members, result, required
|
||||
}
|
||||
found.Insert(t.Name.String())
|
||||
|
||||
for _, member := range t.Members {
|
||||
tags := jsonRegex.FindStringSubmatch(member.Tags)
|
||||
if len(tags) == 0 {
|
||||
// Skip fields without json tags
|
||||
//fmt.Printf("Skipping member %s %s\n", member.Name, member.Type.Name.String())
|
||||
continue
|
||||
}
|
||||
ts := strings.Split(tags[1], ",")
|
||||
name := member.Name
|
||||
strat := ""
|
||||
if len(ts) > 0 && len(ts[0]) > 0 {
|
||||
name = ts[0]
|
||||
}
|
||||
if len(ts) > 1 {
|
||||
strat = ts[1]
|
||||
}
|
||||
|
||||
// Inline "inline" structs
|
||||
if strat == "inline" {
|
||||
m, r, re := b.getMembers(member.Type, found)
|
||||
for n, v := range m {
|
||||
members[n] = v
|
||||
}
|
||||
for n, v := range r {
|
||||
result[n] = v
|
||||
}
|
||||
required = append(required, re...)
|
||||
} else {
|
||||
m, r := b.typeToJSONSchemaProps(member.Type, found, member.CommentLines, false)
|
||||
members[name] = m
|
||||
result[name] = r
|
||||
if !strings.HasSuffix(strat, "omitempty") {
|
||||
required = append(required, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer found.Delete(t.Name.String())
|
||||
return members, result, required
|
||||
}
|
||||
161
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/index.go
generated
vendored
Normal file
161
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/index.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gobuffalo/flect"
|
||||
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
// parseIndex indexes all types with the comment "// +resource=RESOURCE" by GroupVersionKind and
|
||||
// GroupKindVersion
|
||||
func (b *APIs) parseIndex() {
|
||||
// Index resource by group, version, kind
|
||||
b.ByGroupVersionKind = map[string]map[string]map[string]*codegen.APIResource{}
|
||||
|
||||
// Index resources by group, kind, version
|
||||
b.ByGroupKindVersion = map[string]map[string]map[string]*codegen.APIResource{}
|
||||
|
||||
// Index subresources by group, version, kind
|
||||
b.SubByGroupVersionKind = map[string]map[string]map[string]*types.Type{}
|
||||
|
||||
for _, c := range b.context.Order {
|
||||
// The type is a subresource, add it to the subresource index
|
||||
if IsAPISubresource(c) {
|
||||
group := GetGroup(c)
|
||||
version := GetVersion(c, group)
|
||||
kind := GetKind(c, group)
|
||||
if _, f := b.SubByGroupVersionKind[group]; !f {
|
||||
b.SubByGroupVersionKind[group] = map[string]map[string]*types.Type{}
|
||||
}
|
||||
if _, f := b.SubByGroupVersionKind[group][version]; !f {
|
||||
b.SubByGroupVersionKind[group][version] = map[string]*types.Type{}
|
||||
}
|
||||
b.SubByGroupVersionKind[group][version][kind] = c
|
||||
}
|
||||
|
||||
// If it isn't a subresource or resource, continue to the next type
|
||||
if !IsAPIResource(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse out the resource information
|
||||
r := &codegen.APIResource{
|
||||
Type: c,
|
||||
NonNamespaced: IsNonNamespaced(c),
|
||||
}
|
||||
r.Group = GetGroup(c)
|
||||
r.Version = GetVersion(c, r.Group)
|
||||
r.Kind = GetKind(c, r.Group)
|
||||
r.Domain = b.Domain
|
||||
|
||||
// TODO: revisit the part...
|
||||
if r.Resource == "" {
|
||||
r.Resource = flect.Pluralize(strings.ToLower(r.Kind))
|
||||
}
|
||||
rt, err := parseResourceAnnotation(c)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse resource annotations, error: %v", err.Error())
|
||||
}
|
||||
if rt.Resource != "" {
|
||||
r.Resource = rt.Resource
|
||||
}
|
||||
r.ShortName = rt.ShortName
|
||||
|
||||
// Copy the Status strategy to mirror the non-status strategy
|
||||
r.StatusStrategy = strings.TrimSuffix(r.Strategy, "Strategy")
|
||||
r.StatusStrategy = fmt.Sprintf("%sStatusStrategy", r.StatusStrategy)
|
||||
|
||||
// Initialize the map entries so they aren't nill
|
||||
if _, f := b.ByGroupKindVersion[r.Group]; !f {
|
||||
b.ByGroupKindVersion[r.Group] = map[string]map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupKindVersion[r.Group][r.Kind]; !f {
|
||||
b.ByGroupKindVersion[r.Group][r.Kind] = map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupVersionKind[r.Group]; !f {
|
||||
b.ByGroupVersionKind[r.Group] = map[string]map[string]*codegen.APIResource{}
|
||||
}
|
||||
if _, f := b.ByGroupVersionKind[r.Group][r.Version]; !f {
|
||||
b.ByGroupVersionKind[r.Group][r.Version] = map[string]*codegen.APIResource{}
|
||||
}
|
||||
|
||||
// Add the resource to the map
|
||||
b.ByGroupKindVersion[r.Group][r.Kind][r.Version] = r
|
||||
b.ByGroupVersionKind[r.Group][r.Version][r.Kind] = r
|
||||
r.Type = c
|
||||
}
|
||||
}
|
||||
|
||||
// resourceTags contains the tags present in a "+resource=" comment
|
||||
type resourceTags struct {
|
||||
Resource string
|
||||
REST string
|
||||
Strategy string
|
||||
ShortName string
|
||||
}
|
||||
|
||||
// resourceAnnotationValue is a helper function to extract resource annotation.
|
||||
func resourceAnnotationValue(tag string) (resourceTags, error) {
|
||||
res := resourceTags{}
|
||||
for _, elem := range strings.Split(tag, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
return resourceTags{}, fmt.Errorf("// +kubebuilder:resource: tags must be key value pairs. Expected "+
|
||||
"keys [path=<resourcepath>] "+
|
||||
"Got string: [%s]", tag)
|
||||
}
|
||||
switch key {
|
||||
case "path":
|
||||
res.Resource = value
|
||||
case "shortName":
|
||||
res.ShortName = value
|
||||
default:
|
||||
return resourceTags{}, fmt.Errorf("The given input %s is invalid", value)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// parseResourceAnnotation parses the tags in a "+resource=" comment into a resourceTags struct.
|
||||
func parseResourceAnnotation(t *types.Type) (resourceTags, error) {
|
||||
finalResult := resourceTags{}
|
||||
var resourceAnnotationFound bool
|
||||
for _, comment := range t.CommentLines {
|
||||
anno := general.GetAnnotation(comment, "kubebuilder:resource")
|
||||
if len(anno) == 0 {
|
||||
continue
|
||||
}
|
||||
result, err := resourceAnnotationValue(anno)
|
||||
if err != nil {
|
||||
return resourceTags{}, err
|
||||
}
|
||||
if resourceAnnotationFound {
|
||||
return resourceTags{}, fmt.Errorf("resource annotation should only exists once per type")
|
||||
}
|
||||
resourceAnnotationFound = true
|
||||
finalResult = result
|
||||
}
|
||||
return finalResult, nil
|
||||
}
|
||||
151
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/parser.go
generated
vendored
Normal file
151
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/parser.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/args"
|
||||
"k8s.io/gengo/generator"
|
||||
"k8s.io/gengo/types"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/codegen"
|
||||
)
|
||||
|
||||
// APIs is the information of a collection of API
|
||||
type APIs struct {
|
||||
context *generator.Context
|
||||
arguments *args.GeneratorArgs
|
||||
Domain string
|
||||
VersionedPkgs sets.String
|
||||
UnversionedPkgs sets.String
|
||||
APIsPkg string
|
||||
APIsPkgRaw *types.Package
|
||||
GroupNames sets.String
|
||||
|
||||
APIs *codegen.APIs
|
||||
Controllers []codegen.Controller
|
||||
|
||||
ByGroupKindVersion map[string]map[string]map[string]*codegen.APIResource
|
||||
ByGroupVersionKind map[string]map[string]map[string]*codegen.APIResource
|
||||
SubByGroupVersionKind map[string]map[string]map[string]*types.Type
|
||||
Groups map[string]types.Package
|
||||
Rules []rbacv1.PolicyRule
|
||||
Informers map[v1.GroupVersionKind]bool
|
||||
}
|
||||
|
||||
// NewAPIs returns a new APIs instance with given context.
|
||||
func NewAPIs(context *generator.Context, arguments *args.GeneratorArgs, domain, apisPkg string) *APIs {
|
||||
b := &APIs{
|
||||
context: context,
|
||||
arguments: arguments,
|
||||
Domain: domain,
|
||||
APIsPkg: apisPkg,
|
||||
}
|
||||
b.parsePackages()
|
||||
b.parseGroupNames()
|
||||
b.parseIndex()
|
||||
b.parseAPIs()
|
||||
b.parseCRDs()
|
||||
if len(b.Domain) == 0 {
|
||||
b.parseDomain()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// parseGroupNames initializes b.GroupNames with the set of all groups
|
||||
func (b *APIs) parseGroupNames() {
|
||||
b.GroupNames = sets.String{}
|
||||
for p := range b.UnversionedPkgs {
|
||||
pkg := b.context.Universe[p]
|
||||
if pkg == nil {
|
||||
// If the input had no Go files, for example.
|
||||
continue
|
||||
}
|
||||
b.GroupNames.Insert(filepath.Base(p))
|
||||
}
|
||||
}
|
||||
|
||||
// parsePackages parses out the sets of Versioned, Unversioned packages and identifies the root Apis package.
|
||||
func (b *APIs) parsePackages() {
|
||||
b.VersionedPkgs = sets.NewString()
|
||||
b.UnversionedPkgs = sets.NewString()
|
||||
for _, o := range b.context.Order {
|
||||
if IsAPIResource(o) {
|
||||
versioned := o.Name.Package
|
||||
b.VersionedPkgs.Insert(versioned)
|
||||
|
||||
unversioned := filepath.Dir(versioned)
|
||||
b.UnversionedPkgs.Insert(unversioned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseDomain parses the domain from the apis/doc.go file comment "// +domain=YOUR_DOMAIN".
|
||||
func (b *APIs) parseDomain() {
|
||||
pkg := b.context.Universe[b.APIsPkg]
|
||||
if pkg == nil {
|
||||
// If the input had no Go files, for example.
|
||||
panic(errors.Errorf("Missing apis package."))
|
||||
}
|
||||
comments := Comments(pkg.Comments)
|
||||
b.Domain = comments.getTag("domain", "=")
|
||||
if len(b.Domain) == 0 {
|
||||
b.Domain = parseDomainFromFiles(b.context.Inputs)
|
||||
if len(b.Domain) == 0 {
|
||||
panic("Could not find string matching // +domain=.+ in apis/doc.go")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseDomainFromFiles(paths []string) string {
|
||||
var domain string
|
||||
for _, path := range paths {
|
||||
if strings.HasSuffix(path, "pkg/apis") {
|
||||
filePath := strings.Join([]string{build.Default.GOPATH, "src", path, "doc.go"}, "/")
|
||||
lines := []string{}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "//") {
|
||||
lines = append(lines, strings.Replace(scanner.Text(), "// ", "", 1))
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
comments := Comments(lines)
|
||||
domain = comments.getTag("domain", "=")
|
||||
break
|
||||
}
|
||||
}
|
||||
return domain
|
||||
}
|
||||
539
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/util.go
generated
vendored
Normal file
539
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/parse/util.go
generated
vendored
Normal file
@@ -0,0 +1,539 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/gengo/types"
|
||||
)
|
||||
|
||||
const (
|
||||
specReplicasPath = "specpath"
|
||||
statusReplicasPath = "statuspath"
|
||||
labelSelectorPath = "selectorpath"
|
||||
jsonPathError = "invalid scale path. specpath, statuspath key-value pairs are required, only selectorpath key-value is optinal. For example: // +kubebuilder:subresource:scale:specpath=.spec.replica,statuspath=.status.replica,selectorpath=.spec.Label"
|
||||
printColumnName = "name"
|
||||
printColumnType = "type"
|
||||
printColumnDescr = "description"
|
||||
printColumnPath = "JSONPath"
|
||||
printColumnFormat = "format"
|
||||
printColumnPri = "priority"
|
||||
printColumnError = "invalid printcolumn path. name,type, and JSONPath are required kye-value pairs and rest of the fields are optinal. For example: // +kubebuilder:printcolumn:name=abc,type=string,JSONPath=status"
|
||||
)
|
||||
|
||||
// Options contains the parser options
|
||||
type Options struct {
|
||||
SkipMapValidation bool
|
||||
|
||||
// SkipRBACValidation flag determines whether to check RBAC annotations
|
||||
// for the controller or not at parse stage.
|
||||
SkipRBACValidation bool
|
||||
}
|
||||
|
||||
// IsAPIResource returns true if either of the two conditions become true:
|
||||
// 1. t has a +resource/+kubebuilder:resource comment tag
|
||||
// 2. t has TypeMeta and ObjectMeta in its member list.
|
||||
func IsAPIResource(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+resource") || strings.Contains(c, "+kubebuilder:resource") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
typeMetaFound, objMetaFound := false, false
|
||||
for _, m := range t.Members {
|
||||
if m.Name == "TypeMeta" && m.Type.String() == "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta" {
|
||||
typeMetaFound = true
|
||||
}
|
||||
if m.Name == "ObjectMeta" && m.Type.String() == "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" {
|
||||
objMetaFound = true
|
||||
}
|
||||
if typeMetaFound && objMetaFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNonNamespaced returns true if t has a +nonNamespaced comment tag
|
||||
func IsNonNamespaced(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+genclient:nonNamespaced") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range t.SecondClosestCommentLines {
|
||||
if strings.Contains(c, "+genclient:nonNamespaced") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsController returns true if t has a +controller or +kubebuilder:controller tag
|
||||
func IsController(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+controller") || strings.Contains(c, "+kubebuilder:controller") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRBAC returns true if t has a +rbac or +kubebuilder:rbac tag
|
||||
func IsRBAC(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+rbac") || strings.Contains(c, "+kubebuilder:rbac") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPrintColumn returns true if t has a +printcolumn or +kubebuilder:printcolumn annotation.
|
||||
func hasPrintColumn(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+printcolumn") || strings.Contains(c, "+kubebuilder:printcolumn") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInformer returns true if t has a +informers or +kubebuilder:informers tag
|
||||
func IsInformer(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+informers") || strings.Contains(c, "+kubebuilder:informers") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAPISubresource returns true if t has a +subresource-request comment tag
|
||||
func IsAPISubresource(t *types.Type) bool {
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+subresource-request") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasSubresource returns true if t is an APIResource with one or more Subresources
|
||||
func HasSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "subresource") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasStatusSubresource returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:subresource:status
|
||||
func hasStatusSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:status") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasScaleSubresource returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:subresource:scale
|
||||
func hasScaleSubresource(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:scale") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasCategories returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:categories
|
||||
func hasCategories(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:categories") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasDocAnnotation returns true if t is an APIResource with doc annotation
|
||||
// +kubebuilder:doc
|
||||
func HasDocAnnotation(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:doc") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasSingular returns true if t is an APIResource annotated with
|
||||
// +kubebuilder:singular
|
||||
func hasSingular(t *types.Type) bool {
|
||||
if !IsAPIResource(t) {
|
||||
return false
|
||||
}
|
||||
for _, c := range t.CommentLines{
|
||||
if strings.Contains(c, "+kubebuilder:singular"){
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnversioned returns true if t is in given group, and not in versioned path.
|
||||
func IsUnversioned(t *types.Type, group string) bool {
|
||||
return IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) && GetGroup(t) == group
|
||||
}
|
||||
|
||||
// IsVersioned returns true if t is in given group, and in versioned path.
|
||||
func IsVersioned(t *types.Type, group string) bool {
|
||||
dir := filepath.Base(filepath.Dir(filepath.Dir(t.Name.Package)))
|
||||
return IsApisDir(dir) && GetGroup(t) == group
|
||||
}
|
||||
|
||||
// GetVersion returns version of t.
|
||||
func GetVersion(t *types.Type, group string) string {
|
||||
if !IsVersioned(t, group) {
|
||||
panic(errors.Errorf("Cannot get version for unversioned type %v", t.Name))
|
||||
}
|
||||
return filepath.Base(t.Name.Package)
|
||||
}
|
||||
|
||||
// GetGroup returns group of t.
|
||||
func GetGroup(t *types.Type) string {
|
||||
return filepath.Base(GetGroupPackage(t))
|
||||
}
|
||||
|
||||
// GetGroupPackage returns group package of t.
|
||||
func GetGroupPackage(t *types.Type) string {
|
||||
if IsApisDir(filepath.Base(filepath.Dir(t.Name.Package))) {
|
||||
return t.Name.Package
|
||||
}
|
||||
return filepath.Dir(t.Name.Package)
|
||||
}
|
||||
|
||||
// GetKind returns kind of t.
|
||||
func GetKind(t *types.Type, group string) string {
|
||||
if !IsVersioned(t, group) && !IsUnversioned(t, group) {
|
||||
panic(errors.Errorf("Cannot get kind for type not in group %v", t.Name))
|
||||
}
|
||||
return t.Name.Name
|
||||
}
|
||||
|
||||
// IsApisDir returns true if a directory path is a Kubernetes api directory
|
||||
func IsApisDir(dir string) bool {
|
||||
return dir == "apis" || dir == "api"
|
||||
}
|
||||
|
||||
// Comments is a structure for using comment tags on go structs and fields
|
||||
type Comments []string
|
||||
|
||||
// GetTags returns the value for the first comment with a prefix matching "+name="
|
||||
// e.g. "+name=foo\n+name=bar" would return "foo"
|
||||
func (c Comments) getTag(name, sep string) string {
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s%s", name, sep)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return strings.Replace(c, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// hasTag returns true if the Comments has a tag with the given name
|
||||
func (c Comments) hasTag(name string) bool {
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s", name)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTags returns the value for all comments with a prefix and separator. E.g. for "name" and "="
|
||||
// "+name=foo\n+name=bar" would return []string{"foo", "bar"}
|
||||
func (c Comments) getTags(name, sep string) []string {
|
||||
tags := []string{}
|
||||
for _, c := range c {
|
||||
prefix := fmt.Sprintf("+%s%s", name, sep)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
tags = append(tags, strings.Replace(c, prefix, "", 1))
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// getCategoriesTag returns the value of the +kubebuilder:categories tags
|
||||
func getCategoriesTag(c *types.Type) string {
|
||||
comments := Comments(c.CommentLines)
|
||||
resource := comments.getTag("kubebuilder:categories", "=")
|
||||
if len(resource) == 0 {
|
||||
panic(errors.Errorf("Must specify +kubebuilder:categories comment for type %v", c.Name))
|
||||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
// getSingularName returns the value of the +kubebuilder:singular tag
|
||||
func getSingularName(c *types.Type) string {
|
||||
comments := Comments(c.CommentLines)
|
||||
singular := comments.getTag("kubebuilder:singular", "=")
|
||||
if len(singular) == 0 {
|
||||
panic(errors.Errorf("Must specify a value to use with +kubebuilder:singular comment for type %v", c.Name))
|
||||
}
|
||||
return singular
|
||||
}
|
||||
|
||||
// getDocAnnotation parse annotations of "+kubebuilder:doc:" with tags of "warning" or "doc" for control generating doc config.
|
||||
// E.g. +kubebuilder:doc:warning=foo +kubebuilder:doc:note=bar
|
||||
func getDocAnnotation(t *types.Type, tags ...string) map[string]string {
|
||||
annotation := make(map[string]string)
|
||||
for _, tag := range tags {
|
||||
for _, c := range t.CommentLines {
|
||||
prefix := fmt.Sprintf("+kubebuilder:doc:%s=", tag)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
annotation[tag] = strings.Replace(c, prefix, "", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotation
|
||||
}
|
||||
|
||||
// parseByteValue returns the literal digital number values from a byte array
|
||||
func parseByteValue(b []byte) string {
|
||||
elem := strings.Join(strings.Fields(fmt.Sprintln(b)), ",")
|
||||
elem = strings.TrimPrefix(elem, "[")
|
||||
elem = strings.TrimSuffix(elem, "]")
|
||||
return elem
|
||||
}
|
||||
|
||||
// parseDescription parse comments above each field in the type definition.
|
||||
func parseDescription(res []string) string {
|
||||
var temp strings.Builder
|
||||
var desc string
|
||||
for _, comment := range res {
|
||||
if !(strings.Contains(comment, "+kubebuilder") || strings.Contains(comment, "+optional")) {
|
||||
temp.WriteString(comment)
|
||||
temp.WriteString(" ")
|
||||
desc = strings.TrimRight(temp.String(), " ")
|
||||
}
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
// parseEnumToString returns a representive validated go format string from JSONSchemaProps schema
|
||||
func parseEnumToString(value []v1beta1.JSON) string {
|
||||
res := "[]v1beta1.JSON{"
|
||||
prefix := "v1beta1.JSON{[]byte{"
|
||||
for _, v := range value {
|
||||
res = res + prefix + parseByteValue(v.Raw) + "}},"
|
||||
}
|
||||
return strings.TrimSuffix(res, ",") + "}"
|
||||
}
|
||||
|
||||
// check type of enum element value to match type of field
|
||||
func checkType(props *v1beta1.JSONSchemaProps, s string, enums *[]v1beta1.JSON) {
|
||||
|
||||
// TODO support more types check
|
||||
switch props.Type {
|
||||
case "int", "int64", "uint64":
|
||||
if _, err := strconv.ParseInt(s, 0, 64); err != nil {
|
||||
log.Fatalf("Invalid integer value [%v] for a field of integer type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "int32", "unit32":
|
||||
if _, err := strconv.ParseInt(s, 0, 32); err != nil {
|
||||
log.Fatalf("Invalid integer value [%v] for a field of integer32 type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "float", "float32":
|
||||
if _, err := strconv.ParseFloat(s, 32); err != nil {
|
||||
log.Fatalf("Invalid float value [%v] for a field of float32 type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "float64":
|
||||
if _, err := strconv.ParseFloat(s, 64); err != nil {
|
||||
log.Fatalf("Invalid float value [%v] for a field of float type", s)
|
||||
}
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(fmt.Sprintf("%v", s))})
|
||||
case "string":
|
||||
*enums = append(*enums, v1beta1.JSON{Raw: []byte(`"` + s + `"`)})
|
||||
}
|
||||
}
|
||||
|
||||
// Scale subresource requires specpath, statuspath, selectorpath key values, represents for JSONPath of
|
||||
// SpecReplicasPath, StatusReplicasPath, LabelSelectorPath separately. e.g.
|
||||
// +kubebuilder:subresource:scale:specpath=.spec.replica,statuspath=.status.replica,selectorpath=
|
||||
func parseScaleParams(t *types.Type) (map[string]string, error) {
|
||||
jsonPath := make(map[string]string)
|
||||
for _, c := range t.CommentLines {
|
||||
if strings.Contains(c, "+kubebuilder:subresource:scale") {
|
||||
paths := strings.Replace(c, "+kubebuilder:subresource:scale:", "", -1)
|
||||
path := strings.Split(paths, ",")
|
||||
if len(path) < 2 {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
for _, s := range path {
|
||||
kv := strings.Split(s, "=")
|
||||
if kv[0] == specReplicasPath || kv[0] == statusReplicasPath || kv[0] == labelSelectorPath {
|
||||
jsonPath[kv[0]] = kv[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
}
|
||||
var ok bool
|
||||
_, ok = jsonPath[specReplicasPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
_, ok = jsonPath[statusReplicasPath]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
return jsonPath, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf(jsonPathError)
|
||||
}
|
||||
|
||||
// printColumnKV parses key-value string formatted as "foo=bar" and returns key and value.
|
||||
func printColumnKV(s string) (key, value string, err error) {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
err = fmt.Errorf("invalid key value pair")
|
||||
return key, value, err
|
||||
}
|
||||
key, value = kv[0], kv[1]
|
||||
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
return key, value, err
|
||||
}
|
||||
|
||||
// helperPrintColumn is a helper function for the parsePrintColumnParams to compute printer columns.
|
||||
func helperPrintColumn(parts string, comment string) (v1beta1.CustomResourceColumnDefinition, error) {
|
||||
config := v1beta1.CustomResourceColumnDefinition{}
|
||||
var count int
|
||||
part := strings.Split(parts, ",")
|
||||
if len(part) < 3 {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
|
||||
for _, elem := range strings.Split(parts, ",") {
|
||||
key, value, err := printColumnKV(elem)
|
||||
if err != nil {
|
||||
return v1beta1.CustomResourceColumnDefinition{},
|
||||
fmt.Errorf("//+kubebuilder:printcolumn: tags must be key value pairs.Expected "+
|
||||
"keys [name=<name>,type=<type>,description=<descr>,format=<format>] "+
|
||||
"Got string: [%s]", parts)
|
||||
}
|
||||
if key == printColumnName || key == printColumnType || key == printColumnPath {
|
||||
count++
|
||||
}
|
||||
switch key {
|
||||
case printColumnName:
|
||||
config.Name = value
|
||||
case printColumnType:
|
||||
if value == "integer" || value == "number" || value == "string" || value == "boolean" || value == "date" {
|
||||
config.Type = value
|
||||
} else {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnType)
|
||||
}
|
||||
case printColumnFormat:
|
||||
if config.Type == "integer" && (value == "int32" || value == "int64") {
|
||||
config.Format = value
|
||||
} else if config.Type == "number" && (value == "float" || value == "double") {
|
||||
config.Format = value
|
||||
} else if config.Type == "string" && (value == "byte" || value == "date" || value == "date-time" || value == "password") {
|
||||
config.Format = value
|
||||
} else {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnFormat)
|
||||
}
|
||||
case printColumnPath:
|
||||
config.JSONPath = value
|
||||
case printColumnPri:
|
||||
i, err := strconv.Atoi(value)
|
||||
v := int32(i)
|
||||
if err != nil {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf("invalid value for %s printcolumn", printColumnPri)
|
||||
}
|
||||
config.Priority = v
|
||||
case printColumnDescr:
|
||||
config.Description = value
|
||||
default:
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
}
|
||||
if count != 3 {
|
||||
return v1beta1.CustomResourceColumnDefinition{}, fmt.Errorf(printColumnError)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// printcolumn requires name,type,JSONPath fields and rest of the field are optional
|
||||
// +kubebuilder:printcolumn:name=<name>,type=<type>,description=<desc>,JSONPath:<.spec.Name>,priority=<int32>,format=<format>
|
||||
func parsePrintColumnParams(t *types.Type) ([]v1beta1.CustomResourceColumnDefinition, error) {
|
||||
result := []v1beta1.CustomResourceColumnDefinition{}
|
||||
for _, comment := range t.CommentLines {
|
||||
if strings.Contains(comment, "+kubebuilder:printcolumn") {
|
||||
parts := strings.Replace(comment, "+kubebuilder:printcolumn:", "", -1)
|
||||
res, err := helperPrintColumn(parts, comment)
|
||||
if err != nil {
|
||||
return []v1beta1.CustomResourceColumnDefinition{}, err
|
||||
}
|
||||
result = append(result, res)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
213
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/types.go
generated
vendored
Normal file
213
vendor/sigs.k8s.io/controller-tools/pkg/internal/codegen/types.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
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 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"
|
||||
)
|
||||
|
||||
// APIs is the information of a collection of API
|
||||
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
|
||||
}
|
||||
|
||||
// GetRules get rules of the APIs
|
||||
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
|
||||
}
|
||||
|
||||
// APIGroup contains information of an API group.
|
||||
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
|
||||
}
|
||||
|
||||
// Struct contains information of a struct.
|
||||
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
|
||||
}
|
||||
|
||||
// Field contains information of a 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
|
||||
}
|
||||
|
||||
// APIVersion contains information of an API version.
|
||||
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
|
||||
}
|
||||
|
||||
// APIResource contains information of an API resource.
|
||||
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
|
||||
// Categories is a list of categories the resource is part of.
|
||||
Categories []string
|
||||
}
|
||||
|
||||
// APISubresource contains information of an API subresource.
|
||||
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
|
||||
}
|
||||
|
||||
// Controller contains information of a controller.
|
||||
type Controller struct {
|
||||
Target schema.GroupVersionKind
|
||||
Resource string
|
||||
Pkg *types.Package
|
||||
Repo string
|
||||
}
|
||||
102
vendor/sigs.k8s.io/controller-tools/pkg/internal/general/util.go
generated
vendored
Normal file
102
vendor/sigs.k8s.io/controller-tools/pkg/internal/general/util.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
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 general
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isGoFile filters files from parsing.
|
||||
func isGoFile(f os.FileInfo) bool {
|
||||
// ignore non-Go or Go test files
|
||||
name := f.Name()
|
||||
return !f.IsDir() &&
|
||||
!strings.HasPrefix(name, ".") &&
|
||||
!strings.HasSuffix(name, "_test.go") &&
|
||||
strings.HasSuffix(name, ".go")
|
||||
}
|
||||
|
||||
// GetAnnotation extracts the annotation from comment text.
|
||||
// It will return "foo" for comment "+kubebuilder:webhook:foo" .
|
||||
func GetAnnotation(c, name string) string {
|
||||
prefix := fmt.Sprintf("+%s:", name)
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
return strings.TrimPrefix(c, prefix)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ParseKV parses key-value string formatted as "foo=bar" and returns key and value.
|
||||
func ParseKV(s string) (key, value string, err error) {
|
||||
kv := strings.Split(s, "=")
|
||||
if len(kv) != 2 {
|
||||
err = fmt.Errorf("invalid key value pair")
|
||||
return key, value, err
|
||||
}
|
||||
key, value = kv[0], kv[1]
|
||||
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
return key, value, err
|
||||
}
|
||||
|
||||
// ParseDir parses the Go files under given directory and parses the annotation by
|
||||
// invoking the parseFn function on each comment group (multi-lines comments).
|
||||
// TODO(droot): extend it to multiple dirs
|
||||
func ParseDir(dir string, parseFn func(string) error) error {
|
||||
fset := token.NewFileSet()
|
||||
|
||||
err := filepath.Walk(dir,
|
||||
func(path string, info os.FileInfo, _ error) error {
|
||||
if !isGoFile(info) {
|
||||
// TODO(droot): enable this output based on verbose flag
|
||||
// fmt.Println("skipping non-go file", path)
|
||||
return nil
|
||||
}
|
||||
return ParseFile(fset, path, nil, parseFn)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ParseFile parses given filename or content src and parses annotations by
|
||||
// invoking the parseFn function on each comment group (multi-lines comments).
|
||||
func ParseFile(fset *token.FileSet, filename string, src interface{}, parseFn func(string) error) error {
|
||||
f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
|
||||
if err != nil {
|
||||
fmt.Printf("error from parse.ParseFile: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// using commentMaps here because it sanitizes the comment text by removing
|
||||
// comment markers, compresses newlines etc.
|
||||
cmap := ast.NewCommentMap(fset, f, f.Comments)
|
||||
|
||||
for _, commentGroup := range cmap.Comments() {
|
||||
err = parseFn(commentGroup.Text())
|
||||
if err != nil {
|
||||
fmt.Print("error when parsing annotation")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
170
vendor/sigs.k8s.io/controller-tools/pkg/rbac/manifests.go
generated
vendored
Normal file
170
vendor/sigs.k8s.io/controller-tools/pkg/rbac/manifests.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
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 rbac
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
// ManifestOptions represent options for generating the RBAC manifests.
|
||||
type ManifestOptions struct {
|
||||
InputDir string
|
||||
OutputDir string
|
||||
RoleFile string
|
||||
BindingFile string
|
||||
Name string
|
||||
ServiceAccount string
|
||||
Namespace string
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// SetDefaults sets up the default options for RBAC Manifest generator.
|
||||
func (o *ManifestOptions) SetDefaults() {
|
||||
o.Name = "manager"
|
||||
o.InputDir = filepath.Join(".", "pkg")
|
||||
o.OutputDir = filepath.Join(".", "config", "rbac")
|
||||
o.ServiceAccount = "default"
|
||||
o.Namespace = "system"
|
||||
}
|
||||
|
||||
// RoleName returns the RBAC role name to be used in the manifests.
|
||||
func (o *ManifestOptions) RoleName() string {
|
||||
return o.Name + "-role"
|
||||
}
|
||||
|
||||
// RoleFileName returns the name of the manifest file to use for the role.
|
||||
func (o *ManifestOptions) RoleFileName() string {
|
||||
if len(o.RoleFile) == 0 {
|
||||
return "rbac_role.yaml"
|
||||
}
|
||||
// TODO: validate file name
|
||||
return o.RoleFile
|
||||
}
|
||||
|
||||
// RoleBindingName returns the RBAC role binding name to be used in the manifests.
|
||||
func (o *ManifestOptions) RoleBindingName() string {
|
||||
return o.Name + "-rolebinding"
|
||||
}
|
||||
|
||||
// RoleBindingFileName returns the name of the manifest file to use for the role binding.
|
||||
func (o *ManifestOptions) RoleBindingFileName() string {
|
||||
if len(o.BindingFile) == 0 {
|
||||
return "rbac_role_binding.yaml"
|
||||
}
|
||||
// TODO: validate file name
|
||||
return o.BindingFile
|
||||
}
|
||||
|
||||
// Validate validates the input options.
|
||||
func (o *ManifestOptions) Validate() error {
|
||||
if _, err := os.Stat(o.InputDir); err != nil {
|
||||
return fmt.Errorf("invalid input directory '%s' %v", o.InputDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate generates RBAC manifests by parsing the RBAC annotations in Go source
|
||||
// files specified in the input directory.
|
||||
func Generate(o *ManifestOptions) error {
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ops := parserOptions{
|
||||
rules: []rbacv1.PolicyRule{},
|
||||
}
|
||||
err := general.ParseDir(o.InputDir, ops.parseAnnotation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse the input dir %v", err)
|
||||
}
|
||||
if len(ops.rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
roleManifest, err := getClusterRoleManifest(ops.rules, o)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate role manifest %v", err)
|
||||
}
|
||||
|
||||
roleBindingManifest, err := getClusterRoleBindingManifest(o)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate role binding manifests %v", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(o.OutputDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output dir %v", err)
|
||||
}
|
||||
roleManifestFile := filepath.Join(o.OutputDir, o.RoleFileName())
|
||||
if err := ioutil.WriteFile(roleManifestFile, roleManifest, 0666); err != nil {
|
||||
return fmt.Errorf("failed to write role manifest YAML file %v", err)
|
||||
}
|
||||
|
||||
roleBindingManifestFile := filepath.Join(o.OutputDir, o.RoleBindingFileName())
|
||||
if err := ioutil.WriteFile(roleBindingManifestFile, roleBindingManifest, 0666); err != nil {
|
||||
return fmt.Errorf("failed to write role manifest YAML file %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getClusterRoleManifest(rules []rbacv1.PolicyRule, o *ManifestOptions) ([]byte, error) {
|
||||
role := rbacv1.ClusterRole{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ClusterRole",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.RoleName(),
|
||||
Labels: o.Labels,
|
||||
},
|
||||
Rules: rules,
|
||||
}
|
||||
return yaml.Marshal(role)
|
||||
}
|
||||
|
||||
func getClusterRoleBindingManifest(o *ManifestOptions) ([]byte, error) {
|
||||
rolebinding := &rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
Kind: "ClusterRoleBinding",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.RoleBindingName(),
|
||||
Labels: o.Labels,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Name: o.ServiceAccount,
|
||||
Namespace: o.Namespace,
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
Name: o.RoleName(),
|
||||
Kind: "ClusterRole",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
},
|
||||
}
|
||||
return yaml.Marshal(rolebinding)
|
||||
}
|
||||
83
vendor/sigs.k8s.io/controller-tools/pkg/rbac/parser.go
generated
vendored
Normal file
83
vendor/sigs.k8s.io/controller-tools/pkg/rbac/parser.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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 rbac contain libraries for generating RBAC manifests from RBAC
|
||||
// annotations in Go source files.
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
type parserOptions struct {
|
||||
rules []rbacv1.PolicyRule
|
||||
}
|
||||
|
||||
// parseAnnotation parses RBAC annotations
|
||||
func (o *parserOptions) parseAnnotation(commentText string) error {
|
||||
for _, comment := range strings.Split(commentText, "\n") {
|
||||
comment := strings.TrimSpace(comment)
|
||||
if strings.HasPrefix(comment, "+rbac") {
|
||||
if ann := general.GetAnnotation(comment, "rbac"); ann != "" {
|
||||
o.rules = append(o.rules, parseRBACTag(ann))
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(comment, "+kubebuilder:rbac") {
|
||||
if ann := general.GetAnnotation(comment, "kubebuilder:rbac"); ann != "" {
|
||||
o.rules = append(o.rules, parseRBACTag(ann))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseRBACTag parses the given RBAC annotation in to an RBAC PolicyRule.
|
||||
// This is copied from Kubebuilder code.
|
||||
func parseRBACTag(tag string) rbacv1.PolicyRule {
|
||||
result := rbacv1.PolicyRule{}
|
||||
for _, elem := range strings.Split(tag, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
log.Fatalf("// +kubebuilder:rbac: tags must be key value pairs. Expected "+
|
||||
"keys [groups=<group1;group2>,resources=<resource1;resource2>,verbs=<verb1;verb2>] "+
|
||||
"Got string: [%s]", tag)
|
||||
}
|
||||
values := strings.Split(value, ";")
|
||||
switch key {
|
||||
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
|
||||
}
|
||||
77
vendor/sigs.k8s.io/controller-tools/pkg/util/util.go
generated
vendored
Normal file
77
vendor/sigs.k8s.io/controller-tools/pkg/util/util.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// FileWriter is a io wrapper to write files
|
||||
type FileWriter struct {
|
||||
Fs afero.Fs
|
||||
}
|
||||
|
||||
// WriteCloser returns a WriteCloser to write to given path
|
||||
func (fw *FileWriter) WriteCloser(path string) (io.Writer, error) {
|
||||
if fw.Fs == nil {
|
||||
fw.Fs = afero.NewOsFs()
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
err := fw.Fs.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := fw.Fs.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
// WriteFile write given content to the file path
|
||||
func (fw *FileWriter) WriteFile(filePath string, content []byte) error {
|
||||
if fw.Fs == nil {
|
||||
fw.Fs = afero.NewOsFs()
|
||||
}
|
||||
f, err := fw.WriteCloser(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
if c, ok := f.(io.Closer); ok {
|
||||
defer func() {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
_, err = f.Write(content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
vendor/sigs.k8s.io/controller-tools/pkg/webhook/admission.go
generated
vendored
Normal file
89
vendor/sigs.k8s.io/controller-tools/pkg/webhook/admission.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// admissionWebhook contains bits needed for generating a admissionWebhook Configuration
|
||||
type admissionWebhook struct {
|
||||
// name is the name of the webhook
|
||||
name string
|
||||
// typ is the webhook type, i.e. mutating, validating
|
||||
typ webhookType
|
||||
// path is the path this webhook will serve.
|
||||
path string
|
||||
// rules maps to the rules field in admissionregistrationv1beta1.admissionWebhook
|
||||
rules []admissionregistrationv1beta1.RuleWithOperations
|
||||
// failurePolicy maps to the failurePolicy field in admissionregistrationv1beta1.admissionWebhook
|
||||
// This optional. If not set, will be defaulted to Ignore (fail-open) by the server.
|
||||
// More details: https://github.com/kubernetes/api/blob/f5c295feaba2cbc946f0bbb8b535fc5f6a0345ee/admissionregistration/v1beta1/types.go#L144-L147
|
||||
failurePolicy *admissionregistrationv1beta1.FailurePolicyType
|
||||
// namespaceSelector maps to the namespaceSelector field in admissionregistrationv1beta1.admissionWebhook
|
||||
// This optional.
|
||||
namespaceSelector *metav1.LabelSelector
|
||||
}
|
||||
|
||||
func (w *admissionWebhook) setDefaults() {
|
||||
if len(w.path) == 0 {
|
||||
if len(w.rules) == 0 || len(w.rules[0].Resources) == 0 {
|
||||
// can't do defaulting, skip it.
|
||||
return
|
||||
}
|
||||
if w.typ == mutatingWebhook {
|
||||
w.path = "/mutate-" + w.rules[0].Resources[0]
|
||||
} else if w.typ == validatingWebhook {
|
||||
w.path = "/validate-" + w.rules[0].Resources[0]
|
||||
}
|
||||
}
|
||||
if len(w.name) == 0 {
|
||||
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
processedPath := strings.ToLower(reg.ReplaceAllString(w.path, ""))
|
||||
w.name = processedPath + ".example.com"
|
||||
}
|
||||
}
|
||||
|
||||
var _ webhook = &admissionWebhook{}
|
||||
|
||||
// GetType returns the type of the webhook.
|
||||
func (w *admissionWebhook) GetType() webhookType {
|
||||
return w.typ
|
||||
}
|
||||
|
||||
// Validate validates if the webhook is valid.
|
||||
func (w *admissionWebhook) Validate() error {
|
||||
if len(w.rules) == 0 {
|
||||
return errors.New("field rules should not be empty")
|
||||
}
|
||||
if len(w.name) == 0 {
|
||||
return errors.New("field name should not be empty")
|
||||
}
|
||||
if w.typ != mutatingWebhook && w.typ != validatingWebhook {
|
||||
return fmt.Errorf("unsupported Type: %v, only mutatingWebhook and validatingWebhook are supported", w.typ)
|
||||
}
|
||||
if len(w.path) == 0 {
|
||||
return errors.New("field path should not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
334
vendor/sigs.k8s.io/controller-tools/pkg/webhook/generator.go
generated
vendored
Normal file
334
vendor/sigs.k8s.io/controller-tools/pkg/webhook/generator.go
generated
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
admissionregistration "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
type generatorOptions struct {
|
||||
// webhooks maps a path to a webhoook.
|
||||
webhooks map[string]webhook
|
||||
|
||||
// port is the port number that the server will serve.
|
||||
// It will be defaulted to 443 if unspecified.
|
||||
port int32
|
||||
|
||||
// certDir is the directory that contains the server key and certificate.
|
||||
certDir string
|
||||
|
||||
// mutatingWebhookConfigName is the name that used for creating the MutatingWebhookConfiguration object.
|
||||
mutatingWebhookConfigName string
|
||||
// validatingWebhookConfigName is the name that used for creating the ValidatingWebhookConfiguration object.
|
||||
validatingWebhookConfigName string
|
||||
|
||||
// secret is the location for storing the certificate for the admission server.
|
||||
// The server should have permission to create a secret in the namespace.
|
||||
secret *apitypes.NamespacedName
|
||||
|
||||
// service is a k8s service fronting the webhook server pod(s).
|
||||
// One and only one of service and host can be set.
|
||||
// This maps to field .Webhooks.ClientConfig.Service
|
||||
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L260
|
||||
service *service
|
||||
// host is the host name of .Webhooks.ClientConfig.URL
|
||||
// https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L250
|
||||
// One and only one of service and host can be set.
|
||||
// If neither service nor host is unspecified, host will be defaulted to "localhost".
|
||||
host *string
|
||||
}
|
||||
|
||||
// service contains information for creating a Service
|
||||
type service struct {
|
||||
// name of the Service
|
||||
name string
|
||||
// namespace of the Service
|
||||
namespace string
|
||||
// selectors is the selector of the Service.
|
||||
// This must select the pods that runs this webhook server.
|
||||
selectors map[string]string
|
||||
}
|
||||
|
||||
// setDefaults does defaulting for the generatorOptions.
|
||||
func (o *generatorOptions) setDefaults() {
|
||||
if o.webhooks == nil {
|
||||
o.webhooks = map[string]webhook{}
|
||||
}
|
||||
if o.port <= 0 {
|
||||
o.port = 443
|
||||
}
|
||||
if len(o.certDir) == 0 {
|
||||
o.certDir = path.Join("/tmp", "k8s-webhook-server", "serving-certs")
|
||||
}
|
||||
|
||||
if len(o.mutatingWebhookConfigName) == 0 {
|
||||
o.mutatingWebhookConfigName = "mutating-webhook-configuration"
|
||||
}
|
||||
if len(o.validatingWebhookConfigName) == 0 {
|
||||
o.validatingWebhookConfigName = "validating-webhook-configuration"
|
||||
}
|
||||
if o.host == nil && o.service == nil {
|
||||
varString := "localhost"
|
||||
o.host = &varString
|
||||
}
|
||||
}
|
||||
|
||||
// Generate creates the AdmissionWebhookConfiguration objects and Service if any.
|
||||
// It also provisions the certificate for the admission server.
|
||||
func (o *generatorOptions) Generate() ([]runtime.Object, error) {
|
||||
// do defaulting if necessary
|
||||
o.setDefaults()
|
||||
|
||||
webhookConfigurations, err := o.whConfigs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := o.getService()
|
||||
objects := append(webhookConfigurations, svc)
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// whConfigs creates a mutatingWebhookConfiguration and(or) a validatingWebhookConfiguration.
|
||||
func (o *generatorOptions) whConfigs() ([]runtime.Object, error) {
|
||||
for _, webhook := range o.webhooks {
|
||||
if err := webhook.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
objs := []runtime.Object{}
|
||||
mutatingWH, err := o.mutatingWHConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mutatingWH != nil {
|
||||
objs = append(objs, mutatingWH)
|
||||
}
|
||||
validatingWH, err := o.validatingWHConfigs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if validatingWH != nil {
|
||||
objs = append(objs, validatingWH)
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
// mutatingWHConfig creates mutatingWebhookConfiguration.
|
||||
func (o *generatorOptions) mutatingWHConfig() (runtime.Object, error) {
|
||||
mutatingWebhooks := []v1beta1.Webhook{}
|
||||
for path, webhook := range o.webhooks {
|
||||
if webhook.GetType() != mutatingWebhook {
|
||||
continue
|
||||
}
|
||||
|
||||
aw := webhook.(*admissionWebhook)
|
||||
wh, err := o.admissionWebhook(path, aw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mutatingWebhooks = append(mutatingWebhooks, *wh)
|
||||
}
|
||||
|
||||
sort.Slice(mutatingWebhooks, func(i, j int) bool {
|
||||
return mutatingWebhooks[i].Name < mutatingWebhooks[j].Name
|
||||
})
|
||||
|
||||
if len(mutatingWebhooks) > 0 {
|
||||
return &admissionregistration.MutatingWebhookConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: metav1.GroupVersion{Group: admissionregistration.GroupName, Version: "v1beta1"}.String(),
|
||||
Kind: "MutatingWebhookConfiguration",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.mutatingWebhookConfigName,
|
||||
Annotations: map[string]string{
|
||||
// TODO(DirectXMan12): Change the annotation to the format that cert-manager decides to use.
|
||||
"alpha.admissionwebhook.cert-manager.io": "true",
|
||||
},
|
||||
},
|
||||
Webhooks: mutatingWebhooks,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *generatorOptions) validatingWHConfigs() (runtime.Object, error) {
|
||||
validatingWebhooks := []v1beta1.Webhook{}
|
||||
for path, webhook := range o.webhooks {
|
||||
var aw *admissionWebhook
|
||||
if webhook.GetType() != validatingWebhook {
|
||||
continue
|
||||
}
|
||||
|
||||
aw = webhook.(*admissionWebhook)
|
||||
wh, err := o.admissionWebhook(path, aw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validatingWebhooks = append(validatingWebhooks, *wh)
|
||||
}
|
||||
|
||||
sort.Slice(validatingWebhooks, func(i, j int) bool {
|
||||
return validatingWebhooks[i].Name < validatingWebhooks[j].Name
|
||||
})
|
||||
|
||||
if len(validatingWebhooks) > 0 {
|
||||
return &admissionregistration.ValidatingWebhookConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: metav1.GroupVersion{Group: admissionregistration.GroupName, Version: "v1beta1"}.String(),
|
||||
Kind: "ValidatingWebhookConfiguration",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.validatingWebhookConfigName,
|
||||
Annotations: map[string]string{
|
||||
// TODO(DirectXMan12): Change the annotation to the format that cert-manager decides to use.
|
||||
"alpha.admissionwebhook.cert-manager.io": "true",
|
||||
},
|
||||
},
|
||||
Webhooks: validatingWebhooks,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (o *generatorOptions) admissionWebhook(path string, wh *admissionWebhook) (*admissionregistration.Webhook, error) {
|
||||
if wh.namespaceSelector == nil && o.service != nil && len(o.service.namespace) > 0 {
|
||||
wh.namespaceSelector = &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "control-plane",
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
webhook := &admissionregistration.Webhook{
|
||||
Name: wh.name,
|
||||
Rules: wh.rules,
|
||||
FailurePolicy: wh.failurePolicy,
|
||||
NamespaceSelector: wh.namespaceSelector,
|
||||
}
|
||||
cc, err := o.getClientConfigWithPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhook.ClientConfig = *cc
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// getClientConfigWithPath constructs a WebhookClientConfig based on the server generatorOptions.
|
||||
// It will use path to the set the path in WebhookClientConfig.
|
||||
func (o *generatorOptions) getClientConfigWithPath(path string) (*admissionregistration.WebhookClientConfig, error) {
|
||||
cc, err := o.getClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc, setPath(cc, path)
|
||||
}
|
||||
|
||||
func (o *generatorOptions) getClientConfig() (*admissionregistration.WebhookClientConfig, error) {
|
||||
if o.host != nil && o.service != nil {
|
||||
return nil, errors.New("URL and service can't be set at the same time")
|
||||
}
|
||||
cc := &admissionregistration.WebhookClientConfig{
|
||||
// Put an non-empty and not harmful CABundle here.
|
||||
// Not doing this will cause the field
|
||||
CABundle: []byte(`\n`),
|
||||
}
|
||||
if o.host != nil {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(*o.host, strconv.Itoa(int(o.port))),
|
||||
}
|
||||
urlString := u.String()
|
||||
cc.URL = &urlString
|
||||
}
|
||||
if o.service != nil {
|
||||
cc.Service = &admissionregistration.ServiceReference{
|
||||
Name: o.service.name,
|
||||
Namespace: o.service.namespace,
|
||||
// Path will be set later
|
||||
}
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// setPath sets the path in the WebhookClientConfig.
|
||||
func setPath(cc *admissionregistration.WebhookClientConfig, path string) error {
|
||||
if cc.URL != nil {
|
||||
u, err := url.Parse(*cc.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Path = path
|
||||
urlString := u.String()
|
||||
cc.URL = &urlString
|
||||
}
|
||||
if cc.Service != nil {
|
||||
cc.Service.Path = &path
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getService creates a corev1.Service object fronting the admission server.
|
||||
func (o *generatorOptions) getService() runtime.Object {
|
||||
if o.service == nil {
|
||||
return nil
|
||||
}
|
||||
svc := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.service.name,
|
||||
Namespace: o.service.namespace,
|
||||
Annotations: map[string]string{
|
||||
// Secret here only need name, since it will be in the same namespace as the service.
|
||||
// TODO(DirectXMan12): Change the annotation to the format that cert-manager decides to use.
|
||||
"alpha.service.cert-manager.io/serving-cert-secret-name": o.secret.Name,
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: o.service.selectors,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
// When using service, kube-apiserver will send admission request to port 443.
|
||||
Port: 443,
|
||||
TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: o.port},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return svc
|
||||
}
|
||||
151
vendor/sigs.k8s.io/controller-tools/pkg/webhook/manifests.go
generated
vendored
Normal file
151
vendor/sigs.k8s.io/controller-tools/pkg/webhook/manifests.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
// Options represent options for generating the webhook manifests.
|
||||
type Options struct {
|
||||
// WriterOptions specifies the input and output
|
||||
WriterOptions
|
||||
|
||||
generatorOptions
|
||||
}
|
||||
|
||||
// Generate generates RBAC manifests by parsing the RBAC annotations in Go source
|
||||
// files specified in the input directory.
|
||||
func Generate(o *Options) error {
|
||||
if err := o.WriterOptions.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := general.ParseDir(o.InputDir, o.parseAnnotation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse the input dir: %v", err)
|
||||
}
|
||||
|
||||
if len(o.webhooks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
objs, err := o.Generate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = o.WriteObjectsToDisk(objs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.controllerManagerPatch()
|
||||
}
|
||||
|
||||
func (o *Options) controllerManagerPatch() error {
|
||||
var kustomizeLabelPatch = `apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller-manager
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Labels }}
|
||||
labels:
|
||||
{{ toYaml . | indent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
ports:
|
||||
- containerPort: {{ .Port }}
|
||||
name: webhook-server
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- mountPath: {{ .CertDir }}
|
||||
name: cert
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: cert
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: {{ .SecretName }}
|
||||
`
|
||||
|
||||
type KustomizeLabelPatch struct {
|
||||
Labels map[string]string
|
||||
SecretName string
|
||||
Port int32
|
||||
CertDir string
|
||||
}
|
||||
|
||||
p := KustomizeLabelPatch{
|
||||
Labels: o.service.selectors,
|
||||
SecretName: o.secret.Name,
|
||||
Port: o.port,
|
||||
CertDir: o.certDir,
|
||||
}
|
||||
funcMap := template.FuncMap{
|
||||
"toYaml": toYAML,
|
||||
"indent": indent,
|
||||
}
|
||||
temp, err := template.New("kustomizeLabelPatch").Funcs(funcMap).Parse(kustomizeLabelPatch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := temp.Execute(buf, p); err != nil {
|
||||
return err
|
||||
}
|
||||
return afero.WriteFile(o.outFs, path.Join(o.PatchOutputDir, "manager_patch.yaml"), buf.Bytes(), 0644)
|
||||
}
|
||||
|
||||
func toYAML(m map[string]string) (string, error) {
|
||||
d, err := yaml.Marshal(m)
|
||||
return string(d), err
|
||||
}
|
||||
|
||||
func indent(n int, s string) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, elem := range strings.Split(s, "\n") {
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := buf.WriteRune(' ')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
_, err := buf.WriteString(elem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = buf.WriteRune('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return strings.TrimRight(buf.String(), " \n"), nil
|
||||
}
|
||||
236
vendor/sigs.k8s.io/controller-tools/pkg/webhook/parser.go
generated
vendored
Normal file
236
vendor/sigs.k8s.io/controller-tools/pkg/webhook/parser.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"sigs.k8s.io/controller-tools/pkg/internal/general"
|
||||
)
|
||||
|
||||
const webhookAnnotationPrefix = "kubebuilder:webhook"
|
||||
|
||||
var (
|
||||
webhookTags = sets.NewString([]string{"groups", "versions", "resources", "verbs", "type", "name", "path", "failure-policy"}...)
|
||||
serverTags = sets.NewString([]string{"port", "cert-dir", "service", "selector", "secret", "host", "mutating-webhook-config-name", "validating-webhook-config-name"}...)
|
||||
)
|
||||
|
||||
// parseAnnotation parses webhook annotations
|
||||
func (o *Options) parseAnnotation(commentText string) error {
|
||||
webhookKVMap, serverKVMap := map[string]string{}, map[string]string{}
|
||||
for _, comment := range strings.Split(commentText, "\n") {
|
||||
comment := strings.TrimSpace(comment)
|
||||
anno := general.GetAnnotation(comment, webhookAnnotationPrefix)
|
||||
if len(anno) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, elem := range strings.Split(anno, ",") {
|
||||
key, value, err := general.ParseKV(elem)
|
||||
if err != nil {
|
||||
log.Fatalf("// +kubebuilder:webhook: tags must be key value pairs. Example "+
|
||||
"keys [groups=<group1;group2>,resources=<resource1;resource2>,verbs=<verb1;verb2>] "+
|
||||
"Got string: [%s]", anno)
|
||||
}
|
||||
switch {
|
||||
case webhookTags.Has(key):
|
||||
webhookKVMap[key] = value
|
||||
case serverTags.Has(key):
|
||||
serverKVMap[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := o.parseWebhookAnnotation(webhookKVMap); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.parseServerAnnotation(serverKVMap)
|
||||
}
|
||||
|
||||
// parseWebhookAnnotation parses webhook annotations in the same comment group
|
||||
// nolint: gocyclo
|
||||
func (o *Options) parseWebhookAnnotation(kvMap map[string]string) error {
|
||||
if len(kvMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
rule := admissionregistrationv1beta1.RuleWithOperations{}
|
||||
w := &admissionWebhook{}
|
||||
for key, value := range kvMap {
|
||||
switch key {
|
||||
case "groups":
|
||||
values := strings.Split(value, ";")
|
||||
normalized := []string{}
|
||||
for _, v := range values {
|
||||
if v == "core" {
|
||||
normalized = append(normalized, "")
|
||||
} else {
|
||||
normalized = append(normalized, v)
|
||||
}
|
||||
}
|
||||
rule.APIGroups = values
|
||||
|
||||
case "versions":
|
||||
values := strings.Split(value, ";")
|
||||
rule.APIVersions = values
|
||||
|
||||
case "resources":
|
||||
values := strings.Split(value, ";")
|
||||
rule.Resources = values
|
||||
|
||||
case "verbs":
|
||||
values := strings.Split(value, ";")
|
||||
var ops []admissionregistrationv1beta1.OperationType
|
||||
for _, v := range values {
|
||||
switch strings.ToLower(v) {
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Create)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Create)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Update)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Update)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Delete)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Delete)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Connect)):
|
||||
ops = append(ops, admissionregistrationv1beta1.Connect)
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.OperationAll)):
|
||||
ops = append(ops, admissionregistrationv1beta1.OperationAll)
|
||||
default:
|
||||
return fmt.Errorf("unknown operation: %v", v)
|
||||
}
|
||||
}
|
||||
rule.Operations = ops
|
||||
|
||||
case "type":
|
||||
switch strings.ToLower(value) {
|
||||
case "mutating":
|
||||
w.typ = mutatingWebhook
|
||||
case "validating":
|
||||
w.typ = validatingWebhook
|
||||
default:
|
||||
return fmt.Errorf("unknown webhook type: %v", value)
|
||||
}
|
||||
|
||||
case "name":
|
||||
w.name = value
|
||||
|
||||
case "path":
|
||||
w.path = value
|
||||
|
||||
case "failure-policy":
|
||||
switch strings.ToLower(value) {
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Ignore)):
|
||||
fp := admissionregistrationv1beta1.Ignore
|
||||
w.failurePolicy = &fp
|
||||
case strings.ToLower(string(admissionregistrationv1beta1.Fail)):
|
||||
fp := admissionregistrationv1beta1.Fail
|
||||
w.failurePolicy = &fp
|
||||
default:
|
||||
return fmt.Errorf("unknown webhook failure policy: %v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.rules = []admissionregistrationv1beta1.RuleWithOperations{rule}
|
||||
if o.webhooks == nil {
|
||||
o.webhooks = map[string]webhook{}
|
||||
}
|
||||
o.webhooks[w.path] = w
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseWebhookAnnotation parses webhook server annotations in the same comment group
|
||||
// nolint: gocyclo
|
||||
func (o *Options) parseServerAnnotation(kvMap map[string]string) error {
|
||||
if len(kvMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
for key, value := range kvMap {
|
||||
switch key {
|
||||
case "port":
|
||||
port, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.port = int32(port)
|
||||
case "cert-dir":
|
||||
o.certDir = value
|
||||
case "service":
|
||||
// format: <service=namespace:name>
|
||||
split := strings.Split(value, ":")
|
||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
||||
return fmt.Errorf("invalid service format: expect <namespace:name>, but got %q", value)
|
||||
}
|
||||
if o.service == nil {
|
||||
o.service = &service{}
|
||||
}
|
||||
o.service.namespace = split[0]
|
||||
o.service.name = split[1]
|
||||
case "selector":
|
||||
// selector of the service. Format: <selector=label1:value1;label2:value2>
|
||||
split := strings.Split(value, ";")
|
||||
if len(split) == 0 {
|
||||
return fmt.Errorf("invalid selector format: expect <label1:value1;label2:value2>, but got %q", value)
|
||||
}
|
||||
if o.service == nil {
|
||||
o.service = &service{}
|
||||
}
|
||||
for _, v := range split {
|
||||
l := strings.Split(v, ":")
|
||||
if len(l) != 2 || len(l[0]) == 0 || len(l[1]) == 0 {
|
||||
return fmt.Errorf("invalid selector format: expect <label1:value1;label2:value2>, but got %q", value)
|
||||
}
|
||||
if o.service.selectors == nil {
|
||||
o.service.selectors = map[string]string{}
|
||||
}
|
||||
o.service.selectors[l[0]] = l[1]
|
||||
}
|
||||
case "host":
|
||||
if len(value) == 0 {
|
||||
return errors.New("host should not be empty if specified")
|
||||
}
|
||||
o.host = &value
|
||||
|
||||
case "mutating-webhook-config-name":
|
||||
if len(value) == 0 {
|
||||
return errors.New("mutating-webhook-config-name should not be empty if specified")
|
||||
}
|
||||
o.mutatingWebhookConfigName = value
|
||||
|
||||
case "validating-webhook-config-name":
|
||||
if len(value) == 0 {
|
||||
return errors.New("validating-webhook-config-name should not be empty if specified")
|
||||
}
|
||||
o.validatingWebhookConfigName = value
|
||||
|
||||
case "secret":
|
||||
// format: <secret=namespace:name>
|
||||
split := strings.Split(value, ":")
|
||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
||||
return fmt.Errorf("invalid secret format: expect <namespace:name>, but got %q", value)
|
||||
}
|
||||
if o.secret == nil {
|
||||
o.secret = &types.NamespacedName{}
|
||||
}
|
||||
o.secret.Namespace = split[0]
|
||||
o.secret.Name = split[1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
38
vendor/sigs.k8s.io/controller-tools/pkg/webhook/types.go
generated
vendored
Normal file
38
vendor/sigs.k8s.io/controller-tools/pkg/webhook/types.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
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 webhook
|
||||
|
||||
// webhookType defines the type of a webhook
|
||||
type webhookType int
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
// mutatingWebhook represents mutating type webhook
|
||||
mutatingWebhook webhookType = iota
|
||||
// validatingWebhook represents validating type webhook
|
||||
validatingWebhook
|
||||
)
|
||||
|
||||
// webhook defines the basics that a webhook should support.
|
||||
type webhook interface {
|
||||
// GetType returns the Type of the webhook.
|
||||
// e.g. mutating or validating
|
||||
GetType() webhookType
|
||||
// Validate validates if the webhook itself is valid.
|
||||
// If invalid, a non-nil error will be returned.
|
||||
Validate() error
|
||||
}
|
||||
92
vendor/sigs.k8s.io/controller-tools/pkg/webhook/writer.go
generated
vendored
Normal file
92
vendor/sigs.k8s.io/controller-tools/pkg/webhook/writer.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// WriterOptions specifies the input and output.
|
||||
type WriterOptions struct {
|
||||
InputDir string
|
||||
OutputDir string
|
||||
PatchOutputDir string
|
||||
|
||||
// inFs is filesystem to be used for reading input
|
||||
inFs afero.Fs
|
||||
// outFs is filesystem to be used for writing out the result
|
||||
outFs afero.Fs
|
||||
}
|
||||
|
||||
// SetDefaults sets up the default options for RBAC Manifest generator.
|
||||
func (o *WriterOptions) SetDefaults() {
|
||||
if o.inFs == nil {
|
||||
o.inFs = afero.NewOsFs()
|
||||
}
|
||||
if o.outFs == nil {
|
||||
o.outFs = afero.NewOsFs()
|
||||
}
|
||||
|
||||
if len(o.InputDir) == 0 {
|
||||
o.InputDir = filepath.Join(".", "pkg", "webhook")
|
||||
}
|
||||
if len(o.OutputDir) == 0 {
|
||||
o.OutputDir = filepath.Join(".", "config", "webhook")
|
||||
}
|
||||
if len(o.PatchOutputDir) == 0 {
|
||||
o.PatchOutputDir = filepath.Join(".", "config", "default")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the input options.
|
||||
func (o *WriterOptions) Validate() error {
|
||||
if _, err := o.inFs.Stat(o.InputDir); err != nil {
|
||||
return fmt.Errorf("invalid input directory '%s' %v", o.InputDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteObjectsToDisk writes object to the location specified in WriterOptions.
|
||||
func (o *WriterOptions) WriteObjectsToDisk(objects ...runtime.Object) error {
|
||||
exists, err := afero.DirExists(o.outFs, o.OutputDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
err = o.outFs.MkdirAll(o.OutputDir, 0766)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
isFirstObject := true
|
||||
for _, obj := range objects {
|
||||
if !isFirstObject {
|
||||
_, err = buf.WriteString("---\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
marshalled, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = buf.Write(marshalled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isFirstObject = false
|
||||
}
|
||||
err = afero.WriteFile(o.outFs, path.Join(o.OutputDir, "webhookmanifests.yaml"), buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user