From 1ade5f6f88dd9970b99363b78065ef19db1537c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Suszy=C5=84ski?= Date: Thu, 30 Nov 2023 18:25:09 +0100 Subject: [PATCH] Go-native Helpers code-gen * Go-native code-gen entrypoint structure bcedd4a2087d7d1e1b2a359ef856f4d3bbe64f7e * Parsing of gen-client flags 1160fc77962d1eb95c88e004903fac796170b60a * Structure for all commands of the K8s code-gen db6a5d70c332fb03d206dc07d83c9eb7d504fcbf * Helpers-gen calls deepcopy-gen aaf0d268222adcd1fa08f6f439780b05f4b49bc2 * Remove unneeded Printer interface eb6748cd96a38e909e82c31073582ebfba2c258b * Setup a default boilerplate 67bcbb070ab17f865d6aabd5c43f6eefcd5b94df * Helper-gen calls defaulter-gen 5b524d10b8a4179739eccd2a3d9251a2013389b2 * Streamline logging b5969570cad6c2e9723ba8a5a048806e4264c43f * Fixing tests 323501a210550e5d86a7efae430dba22b501d361 * Helpers-gen calls the conversion-gen 2831fdd6854bdfc86256052a1208368b3786e2fd * Replace shell code with golang tooling 0615e85f527255a74fa5a707f692e8e0a7902282 * Fixing lint errors 30a4a7eadf46ffe71e4ffba2335748db262140c3 * Auto-skip if git isn't available 79a1e6a17614465c0c2233b52aeb0992f0a5ca6e * Add k8s.io/code-generator to examples go.mod b760ba56813a7c84e488433ceb865ecf8ce6b45f * Printing stderr when exec.Command fails 7280dd2360443a8ab4c707b58b8d37101c21cb80 * Update boilerplate 5c27b82ca6a010d904f968ef1566b3df296de377 * Calculate sourcedir from regular sources, not test ones 8bfa3c529d212b3e7f60b8dd2039ff1832652801 * Migrate off using `git-*` shell outs to search Go files 7b61cf6485ac0e53107881041a46ed0097c9102c * Auto-skip failing test when executed in bad GOPATH 079f581d75ad7861de84d1d37230f8ee448891ae * Fix lint errors 41d722df6271efcb1e3f1cb85b31dddd5649e509 * Use deepcopy gen fn for deepcopy-gen main 5be3f7092c531189b19f8e2ca8f112755c0162cb * Ensure to run codegen main with GO modules 1cd3950a2f062557afa3a2a67a0781f6008c68e4 --- .../conversion-gen/generators/conversion.go | 4 +- .../cmd/conversion-gen/generators/embed.go | 51 +++++ .../conversion-gen/generators/embed_test.go | 43 ++++ .../code-generator/cmd/conversion-gen/main.go | 26 +-- .../cmd/deepcopy-gen/generators/embed.go | 51 +++++ .../cmd/deepcopy-gen/generators/embed_test.go | 42 ++++ .../code-generator/cmd/deepcopy-gen/main.go | 26 +-- .../cmd/defaulter-gen/generators/embed.go | 51 +++++ .../defaulter-gen/generators/embed_test.go | 43 ++++ .../code-generator/cmd/defaulter-gen/main.go | 26 +-- staging/src/k8s.io/code-generator/codegen.go | 40 ++++ .../src/k8s.io/code-generator/codegen_test.go | 47 +++++ staging/src/k8s.io/code-generator/doc.go | 2 +- .../src/k8s.io/code-generator/examples/go.mod | 6 + .../src/k8s.io/code-generator/examples/go.sum | 12 ++ .../code-generator/examples/go.work.sum | 5 + .../code-generator/examples/hack/tools.go | 21 ++ .../internal/codegen/command.go | 31 +++ .../internal/codegen/command/client/cmd.go | 73 +++++++ .../codegen/command/client/cmd_test.go | 111 ++++++++++ .../internal/codegen/command/client/flags.go | 83 ++++++++ .../internal/codegen/command/client/help.go | 41 ++++ .../internal/codegen/command/help/cmd.go | 114 +++++++++++ .../internal/codegen/command/help/cmd_test.go | 70 +++++++ .../internal/codegen/command/help/invalid.go | 51 +++++ .../codegen/command/help/invalid_test.go | 68 +++++++ .../internal/codegen/command/helpers/cmd.go | 87 ++++++++ .../codegen/command/helpers/cmd_test.go | 115 +++++++++++ .../internal/codegen/command/helpers/flags.go | 38 ++++ .../internal/codegen/command/helpers/help.go | 56 +++++ .../internal/codegen/command/openapi/cmd.go | 73 +++++++ .../codegen/command/openapi/cmd_test.go | 111 ++++++++++ .../internal/codegen/command/openapi/flags.go | 68 +++++++ .../internal/codegen/command/openapi/help.go | 41 ++++ .../internal/codegen/command/usage.go | 27 +++ .../internal/codegen/execution/exec.go | 54 +++++ .../internal/codegen/execution/print.go | 31 +++ .../internal/codegen/execution/print_test.go | 37 ++++ .../code-generator/internal/codegen/parse.go | 35 ++++ .../code-generator/internal/codegen/run.go | 50 +++++ .../internal/codegen/run_test.go | 56 +++++ .../src/k8s.io/code-generator/kube_codegen.sh | 153 +------------- .../code-generator/pkg/codegen/client/args.go | 32 +++ .../code-generator/pkg/codegen/client/gen.go | 29 +++ .../pkg/codegen/helpers/args.go | 90 +++++++++ .../pkg/codegen/helpers/conversion.go | 30 +++ .../pkg/codegen/helpers/deepcopy.go | 29 +++ .../pkg/codegen/helpers/defaulter.go | 30 +++ .../pkg/codegen/helpers/generator.go | 49 +++++ .../pkg/codegen/helpers/generator_test.go | 144 +++++++++++++ .../pkg/codegen/helpers/generic.go | 191 ++++++++++++++++++ .../pkg/codegen/openapi/args.go | 29 +++ .../code-generator/pkg/codegen/openapi/gen.go | 28 +++ .../code-generator/pkg/fs/current/dir.go | 42 ++++ .../code-generator/pkg/fs/current/dir_test.go | 43 ++++ .../code-generator/pkg/fs/gosrc/search.go | 107 ++++++++++ .../k8s.io/code-generator/pkg/fs/rootdir.go | 31 +++ .../code-generator/pkg/fs/within_dir.go | 36 ++++ .../pkg/osbin/golang/packageof.go | 43 ++++ .../pkg/osbin/golang/packageof_test.go | 33 +++ staging/src/k8s.io/code-generator/tools.go | 2 +- 61 files changed, 2965 insertions(+), 223 deletions(-) create mode 100644 staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed.go create mode 100644 staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed_test.go create mode 100644 staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed.go create mode 100644 staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed_test.go create mode 100644 staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed.go create mode 100644 staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed_test.go create mode 100644 staging/src/k8s.io/code-generator/codegen.go create mode 100644 staging/src/k8s.io/code-generator/codegen_test.go create mode 100644 staging/src/k8s.io/code-generator/examples/hack/tools.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd_test.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/client/flags.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/client/help.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd_test.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid_test.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd_test.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/helpers/flags.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/helpers/help.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd_test.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/openapi/flags.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/openapi/help.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/command/usage.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/execution/exec.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/execution/print.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/execution/print_test.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/parse.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/run.go create mode 100644 staging/src/k8s.io/code-generator/internal/codegen/run_test.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/client/args.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/client/gen.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/args.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/conversion.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/deepcopy.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/defaulter.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator_test.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/openapi/args.go create mode 100644 staging/src/k8s.io/code-generator/pkg/codegen/openapi/gen.go create mode 100644 staging/src/k8s.io/code-generator/pkg/fs/current/dir.go create mode 100644 staging/src/k8s.io/code-generator/pkg/fs/current/dir_test.go create mode 100644 staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go create mode 100644 staging/src/k8s.io/code-generator/pkg/fs/rootdir.go create mode 100644 staging/src/k8s.io/code-generator/pkg/fs/within_dir.go create mode 100644 staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof.go create mode 100644 staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof_test.go diff --git a/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go b/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go index f58130af255a4..9c04dea82fabc 100644 --- a/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go +++ b/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go @@ -338,7 +338,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target // from being a candidate for unsafe conversion for k, v := range manualConversions { if isCopyOnly(v.CommentLines) { - klog.V(4).Infof("Conversion function %s will not block memory copy because it is copy-only", v.Name) + klog.V(4).Infof("GenerateConversion function %s will not block memory copy because it is copy-only", v.Name) continue } // this type should be excluded from all equivalence, because the converter must be called. @@ -738,7 +738,7 @@ func (g *genConversion) generateConversion(inType, outType *types.Type, sw *gene // There is a public manual Conversion method: use it. } else if skipped := g.skippedFields[inType]; len(skipped) != 0 { // The inType had some fields we could not generate. - klog.Errorf("Warning: could not find nor generate a final Conversion function for %v -> %v", inType, outType) + klog.Errorf("Warning: could not find nor generate a final GenerateConversion function for %v -> %v", inType, outType) klog.Errorf(" the following fields need manual conversion:") for _, f := range skipped { klog.Errorf(" - %v", f) diff --git a/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed.go b/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed.go new file mode 100644 index 0000000000000..9938c191a3fb6 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed.go @@ -0,0 +1,51 @@ +/* + Copyright 2024 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 generators + +import ( + "github.com/spf13/pflag" + generatorargs "k8s.io/code-generator/cmd/conversion-gen/args" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" +) + +// GenerateConversion runs the conversion-gen with given flagset and process args. +func GenerateConversion(fs *pflag.FlagSet, processArgs []string) error { + args := generatorargs.New() + args.AddFlags(fs) + + if err := fs.Parse(processArgs); err != nil { + return err + } + + if err := args.Validate(); err != nil { + return err + } + + myTargets := func(context *generator.Context) []generator.Target { + return GetTargets(context, args) + } + + // Run it. + return gengo.Execute( + NameSystems(), + DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + fs.Args(), + ) +} diff --git a/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed_test.go b/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed_test.go new file mode 100644 index 0000000000000..b917dac31ac54 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/embed_test.go @@ -0,0 +1,43 @@ +/* + Copyright 2024 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 generators_test + +import ( + "bytes" + "errors" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/conversion-gen/generators" + + "testing" +) + +func TestConversion(t *testing.T) { + t.Parallel() + var out bytes.Buffer + fs := pflag.NewFlagSet("test", pflag.ContinueOnError) + fs.SetOutput(&out) + args := []string{"--help"} + + err := generators.GenerateConversion(fs, args) + if !errors.Is(err, pflag.ErrHelp) { + t.Errorf("unexpected error: %v", err) + } + if out.Len() == 0 { + t.Errorf("expected output, got none") + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/conversion-gen/main.go b/staging/src/k8s.io/code-generator/cmd/conversion-gen/main.go index cd52a9b9643f2..3647b155be126 100644 --- a/staging/src/k8s.io/code-generator/cmd/conversion-gen/main.go +++ b/staging/src/k8s.io/code-generator/cmd/conversion-gen/main.go @@ -96,41 +96,21 @@ package main import ( "flag" + "os" "github.com/spf13/pflag" "k8s.io/klog/v2" - generatorargs "k8s.io/code-generator/cmd/conversion-gen/args" "k8s.io/code-generator/cmd/conversion-gen/generators" - "k8s.io/gengo/v2" - "k8s.io/gengo/v2/generator" ) func main() { klog.InitFlags(nil) - args := generatorargs.New() - - args.AddFlags(pflag.CommandLine) - flag.Set("logtostderr", "true") + _ = flag.Set("logtostderr", "true") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - pflag.Parse() - - if err := args.Validate(); err != nil { - klog.Fatalf("Error: %v", err) - } - - myTargets := func(context *generator.Context) []generator.Target { - return generators.GetTargets(context, args) - } // Run it. - if err := gengo.Execute( - generators.NameSystems(), - generators.DefaultNameSystem(), - myTargets, - gengo.StdBuildTag, - pflag.Args(), - ); err != nil { + if err := generators.GenerateConversion(pflag.CommandLine, os.Args); err != nil { klog.Fatalf("Error: %v", err) } klog.V(2).Info("Completed successfully.") diff --git a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed.go b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed.go new file mode 100644 index 0000000000000..7d2b83c80ba5c --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed.go @@ -0,0 +1,51 @@ +/* +Copyright 2023 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 generators + +import ( + "github.com/spf13/pflag" + generatorargs "k8s.io/code-generator/cmd/deepcopy-gen/args" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" +) + +// GenerateDeepCopy runs the deepcopy-gen with given flagset and process args. +func GenerateDeepCopy(fs *pflag.FlagSet, processArgs []string) error { + args := generatorargs.New() + args.AddFlags(fs) + + if err := fs.Parse(processArgs); err != nil { + return err + } + + if err := args.Validate(); err != nil { + return err + } + + myTargets := func(context *generator.Context) []generator.Target { + return GetTargets(context, args) + } + + // Run it. + return gengo.Execute( + NameSystems(), + DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + fs.Args(), + ) +} diff --git a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed_test.go b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed_test.go new file mode 100644 index 0000000000000..ee2183deeee68 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/generators/embed_test.go @@ -0,0 +1,42 @@ +/* + Copyright 2023 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 generators_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/deepcopy-gen/generators" +) + +func TestGenerateDeepCopy(t *testing.T) { + t.Parallel() + var out bytes.Buffer + fs := pflag.NewFlagSet("test", pflag.ContinueOnError) + fs.SetOutput(&out) + args := []string{"--help"} + + err := generators.GenerateDeepCopy(fs, args) + if !errors.Is(err, pflag.ErrHelp) { + t.Errorf("unexpected error: %v", err) + } + if out.Len() == 0 { + t.Errorf("expected output, got none") + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/main.go b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/main.go index aaa3155a010e1..ea21b27b32eb7 100644 --- a/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/main.go +++ b/staging/src/k8s.io/code-generator/cmd/deepcopy-gen/main.go @@ -70,40 +70,20 @@ package main import ( "flag" + "os" "github.com/spf13/pflag" - "k8s.io/code-generator/cmd/deepcopy-gen/args" "k8s.io/code-generator/cmd/deepcopy-gen/generators" - "k8s.io/gengo/v2" - "k8s.io/gengo/v2/generator" "k8s.io/klog/v2" ) func main() { klog.InitFlags(nil) - args := args.New() - - args.AddFlags(pflag.CommandLine) - flag.Set("logtostderr", "true") + _ = flag.Set("logtostderr", "true") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - pflag.Parse() - - if err := args.Validate(); err != nil { - klog.Fatalf("Error: %v", err) - } - - myTargets := func(context *generator.Context) []generator.Target { - return generators.GetTargets(context, args) - } // Run it. - if err := gengo.Execute( - generators.NameSystems(), - generators.DefaultNameSystem(), - myTargets, - gengo.StdBuildTag, - pflag.Args(), - ); err != nil { + if err := generators.GenerateDeepCopy(pflag.CommandLine, os.Args); err != nil { klog.Fatalf("Error: %v", err) } klog.V(2).Info("Completed successfully.") diff --git a/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed.go b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed.go new file mode 100644 index 0000000000000..6fc50d79acb4e --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed.go @@ -0,0 +1,51 @@ +/* + Copyright 2024 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 generators + +import ( + "github.com/spf13/pflag" + generatorargs "k8s.io/code-generator/cmd/defaulter-gen/args" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" +) + +// GenerateDefaults runs the defaulter-gen with given flagset and process args. +func GenerateDefaults(fs *pflag.FlagSet, processArgs []string) error { + args := generatorargs.New() + args.AddFlags(fs) + + if err := fs.Parse(processArgs); err != nil { + return err + } + + if err := args.Validate(); err != nil { + return err + } + + myTargets := func(context *generator.Context) []generator.Target { + return GetTargets(context, args) + } + + // Run it. + return gengo.Execute( + NameSystems(), + DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + fs.Args(), + ) +} diff --git a/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed_test.go b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed_test.go new file mode 100644 index 0000000000000..329c97a49c218 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/generators/embed_test.go @@ -0,0 +1,43 @@ +/* + Copyright 2024 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 generators_test + +import ( + "bytes" + "errors" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/defaulter-gen/generators" + + "testing" +) + +func TestDefaulter(t *testing.T) { + t.Parallel() + var out bytes.Buffer + fs := pflag.NewFlagSet("test", pflag.ContinueOnError) + fs.SetOutput(&out) + args := []string{"--help"} + + err := generators.GenerateDefaults(fs, args) + if !errors.Is(err, pflag.ErrHelp) { + t.Errorf("unexpected error: %v", err) + } + if out.Len() == 0 { + t.Errorf("expected output, got none") + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/defaulter-gen/main.go b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/main.go index d57ca0666cd0a..dc2069cdaaaca 100644 --- a/staging/src/k8s.io/code-generator/cmd/defaulter-gen/main.go +++ b/staging/src/k8s.io/code-generator/cmd/defaulter-gen/main.go @@ -43,40 +43,20 @@ package main import ( "flag" + "os" "github.com/spf13/pflag" - "k8s.io/code-generator/cmd/defaulter-gen/args" "k8s.io/code-generator/cmd/defaulter-gen/generators" - "k8s.io/gengo/v2" - "k8s.io/gengo/v2/generator" "k8s.io/klog/v2" ) func main() { klog.InitFlags(nil) - args := args.New() - - args.AddFlags(pflag.CommandLine) - flag.Set("logtostderr", "true") + _ = flag.Set("logtostderr", "true") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - pflag.Parse() - - if err := args.Validate(); err != nil { - klog.Fatalf("Error: %v", err) - } - - myTargets := func(context *generator.Context) []generator.Target { - return generators.GetTargets(context, args) - } // Run it. - if err := gengo.Execute( - generators.NameSystems(), - generators.DefaultNameSystem(), - myTargets, - gengo.StdBuildTag, - pflag.Args(), - ); err != nil { + if err := generators.GenerateDefaults(pflag.CommandLine, os.Args); err != nil { klog.Fatalf("Error: %v", err) } klog.V(2).Info("Completed successfully.") diff --git a/staging/src/k8s.io/code-generator/codegen.go b/staging/src/k8s.io/code-generator/codegen.go new file mode 100644 index 0000000000000..d2d8098025c81 --- /dev/null +++ b/staging/src/k8s.io/code-generator/codegen.go @@ -0,0 +1,40 @@ +/* +Copyright 2023 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 ( + "flag" + "k8s.io/code-generator/internal/codegen" + "k8s.io/code-generator/internal/codegen/execution" + "k8s.io/klog/v2" +) + +var options []execution.Option + +func main() { + klog.InitFlags(nil) + _ = flag.Set("logtostderr", "true") + codegen.Run(options...) +} + +// RunMain is a wrapper for main() to allow for testing. +func RunMain(opts ...execution.Option) { + old := options + defer func() { options = old }() + options = append(make([]execution.Option, 0, len(opts)), opts...) + main() +} diff --git a/staging/src/k8s.io/code-generator/codegen_test.go b/staging/src/k8s.io/code-generator/codegen_test.go new file mode 100644 index 0000000000000..c450f8947ccac --- /dev/null +++ b/staging/src/k8s.io/code-generator/codegen_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2023 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_test + +import ( + "bytes" + "k8s.io/code-generator" + "k8s.io/code-generator/internal/codegen/execution" + "testing" +) + +func TestMainFn(t *testing.T) { + var out, err bytes.Buffer + var retcode *int + main.RunMain(func(ex *execution.Vars) { + ex.Args = []string{"--help"} + ex.Out = &out + ex.Out = &err + ex.Exit = func(code int) { + retcode = &code + } + }) + + if retcode != nil { + t.Errorf("expected exit func will not be called, but was with %d", *retcode) + } + if out.Len() != 0 { + t.Errorf("expected no output, got %#v", out.String()) + } + if !bytes.Contains(err.Bytes(), []byte("Command:")) { + t.Errorf("expected usage output, got %#v", err.String()) + } +} diff --git a/staging/src/k8s.io/code-generator/doc.go b/staging/src/k8s.io/code-generator/doc.go index fd867306974ec..2b5d14377ff1a 100644 --- a/staging/src/k8s.io/code-generator/doc.go +++ b/staging/src/k8s.io/code-generator/doc.go @@ -14,4 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -package codegenerator // import "k8s.io/code-generator" +package main // import "k8s.io/code-generator" diff --git a/staging/src/k8s.io/code-generator/examples/go.mod b/staging/src/k8s.io/code-generator/examples/go.mod index aa613fd6525b4..2a9f515bd494b 100644 --- a/staging/src/k8s.io/code-generator/examples/go.mod +++ b/staging/src/k8s.io/code-generator/examples/go.mod @@ -8,6 +8,7 @@ require ( k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 k8s.io/client-go v0.0.0 + k8s.io/code-generator v0.0.0 k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 ) @@ -33,17 +34,21 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.16.1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect @@ -54,4 +59,5 @@ replace ( k8s.io/api => ../../api k8s.io/apimachinery => ../../apimachinery k8s.io/client-go => ../../client-go + k8s.io/code-generator => ../ ) diff --git a/staging/src/k8s.io/code-generator/examples/go.sum b/staging/src/k8s.io/code-generator/examples/go.sum index 740c427306a6f..7b93c280f201e 100644 --- a/staging/src/k8s.io/code-generator/examples/go.sum +++ b/staging/src/k8s.io/code-generator/examples/go.sum @@ -6,6 +6,7 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -24,11 +25,13 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= @@ -41,6 +44,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -85,6 +89,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -113,6 +119,7 @@ golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= @@ -128,6 +135,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -138,6 +146,9 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= +k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= @@ -148,5 +159,6 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/staging/src/k8s.io/code-generator/examples/go.work.sum b/staging/src/k8s.io/code-generator/examples/go.work.sum index 28229a0729786..d3d984e5baac6 100644 --- a/staging/src/k8s.io/code-generator/examples/go.work.sum +++ b/staging/src/k8s.io/code-generator/examples/go.work.sum @@ -34,11 +34,16 @@ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= diff --git a/staging/src/k8s.io/code-generator/examples/hack/tools.go b/staging/src/k8s.io/code-generator/examples/hack/tools.go new file mode 100644 index 0000000000000..7e5ed0612d06e --- /dev/null +++ b/staging/src/k8s.io/code-generator/examples/hack/tools.go @@ -0,0 +1,21 @@ +//go:build tools + +/* +Copyright 2023 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 hack + +import _ "k8s.io/code-generator" diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command.go b/staging/src/k8s.io/code-generator/internal/codegen/command.go new file mode 100644 index 0000000000000..834a575772afd --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command.go @@ -0,0 +1,31 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package codegen + +import ( + "k8s.io/code-generator/internal/codegen/command" + "k8s.io/code-generator/internal/codegen/execution" +) + +// Command is a command that can be run by code-gen. +type Command interface { + // Matches returns true if the command matches the execution context. + Matches(ex *execution.Vars) bool + // Run runs the command. + Run(ex *execution.Vars) + command.Usage +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd.go b/staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd.go new file mode 100644 index 0000000000000..9ab79def3780f --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd.go @@ -0,0 +1,73 @@ +/* +Copyright 2023 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 client + +import ( + goflag "flag" + "github.com/spf13/pflag" + "k8s.io/code-generator/internal/codegen/execution" + "k8s.io/code-generator/pkg/codegen/client" +) + +// Generator is the interface for generating client code. +type Generator interface { + Generate(args *client.Args) error +} + +// Command is the command for generating client code. +type Command struct { + Gen Generator + *goflag.FlagSet +} + +func (c Command) Matches(ex *execution.Vars) bool { + return len(ex.Args) >= 1 && ex.Args[0] == c.Name() +} + +func (c Command) Run(ex *execution.Vars) { + args := &client.Args{} + fs := pflag.NewFlagSet(ex.Args[0], pflag.ContinueOnError) + fs.AddGoFlagSet(c.flags()) // make sure we get the klog flags + defineFlags(fs, args) + if err := fs.Parse(ex.Args[1:]); err != nil { + ex.Println("Error parsing arguments:", err) + ex.Println() + ex.Println(c.Help()) + ex.Exit(8) + return + } + gen := c.createOrGetGenerator() + if err := gen.Generate(args); err != nil { + ex.Println("Error generating clients:", err) + ex.Exit(9) + return + } +} + +func (c Command) createOrGetGenerator() Generator { + if c.Gen == nil { + return &client.Generator{} + } + return c.Gen +} + +func (c Command) flags() *goflag.FlagSet { + if c.FlagSet == nil { + return goflag.CommandLine + } + return c.FlagSet +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd_test.go b/staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd_test.go new file mode 100644 index 0000000000000..4a71509527524 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/client/cmd_test.go @@ -0,0 +1,111 @@ +/* +Copyright 2023 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 client_test + +import ( + "bytes" + goflag "flag" + "k8s.io/code-generator/internal/codegen/command/client" + "k8s.io/code-generator/internal/codegen/execution" + pkgclient "k8s.io/code-generator/pkg/codegen/client" + "math" + "testing" +) + +func TestCommandMatches(t *testing.T) { + t.Parallel() + + cmd := client.Command{Gen: &testGen{}} + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{cmd.Name()} + }) + if !cmd.Matches(ex) { + t.Errorf("expected command to match") + } +} + +func TestCommandRun(t *testing.T) { + t.Parallel() + + gen := &testGen{} + cmd := client.Command{ + Gen: gen, + FlagSet: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{ + cmd.Name(), + "--input-pkg-root", "foo", + "--output-pkg-root", "bar", + "--boilerplate", "baz", + } + }) + cmd.Run(ex) + if len(gen.calls) != 1 { + t.Errorf("expected gen to be called once, got %d", len(gen.calls)) + } + call := gen.calls[0] + if call.InputPkgRoot != "foo" { + t.Errorf("expected input package to be foo, got %s", call.InputPkgRoot) + } + if call.OutputPkgRoot != "bar" { + t.Errorf("expected output package to be bar, got %s", call.OutputPkgRoot) + } + if call.Boilerplate != "baz" { + t.Errorf("expected boilerplate to be baz, got %s", call.Boilerplate) + } +} + +func TestCommandRunInvalidArgs(t *testing.T) { + t.Parallel() + var errstream bytes.Buffer + retcode := math.MinInt32 + gen := &testGen{} + cmd := client.Command{ + Gen: gen, + FlagSet: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{cmd.Name(), "--input-foo", "foo"} + v.Out = &errstream + v.Exit = func(code int) { + retcode = code + } + }) + cmd.Run(ex) + if len(gen.calls) != 0 { + t.Errorf("expected gen to be called zero times, got %d", len(gen.calls)) + } + if retcode != 8 { + t.Errorf("expected exit code 8, got %d", retcode) + } + if errstream.String() == "" { + t.Errorf("expected error message to be printed") + } +} + +type testGen struct { + calls []pkgclient.Args +} + +func (t *testGen) Generate(args *pkgclient.Args) error { + if t.calls == nil { + t.calls = make([]pkgclient.Args, 0, 1) + } + t.calls = append(t.calls, *args) + return nil +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/client/flags.go b/staging/src/k8s.io/code-generator/internal/codegen/command/client/flags.go new file mode 100644 index 0000000000000..903c3bedc2651 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/client/flags.go @@ -0,0 +1,83 @@ +/* +Copyright 2023 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 client + +import ( + "reflect" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/codegen/client" +) + +func defineFlags(fs *pflag.FlagSet, args *client.Args) { + ty := reflect.TypeOf(*args) + if f, ok := ty.FieldByName("InputDir"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.InputPkgRoot, "input-pkg-root", "", usage) + } + } + if f, ok := ty.FieldByName("OutputPkgRoot"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.OutputPkgRoot, "output-pkg-root", "", usage) + } + } + if f, ok := ty.FieldByName("OutputBase"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.OutputBase, "output-base", "", usage) + } + } + if f, ok := ty.FieldByName("Boilerplate"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.Boilerplate, "boilerplate", "", usage) + } + } + if f, ok := ty.FieldByName("ClientsetName"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.ClientsetName, "clientset-name", "", usage) + } + } + if f, ok := ty.FieldByName("VersionedName"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.VersionedName, "versioned-name", "", usage) + } + } + if f, ok := ty.FieldByName("WithApplyConfig"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.BoolVar(&args.WithApplyConfig, "with-applyconfig", false, usage) + } + } + if f, ok := ty.FieldByName("ApplyConfigName"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.ApplyConfigName, "applyconfig-name", "", usage) + } + } + if f, ok := ty.FieldByName("WithWatch"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.BoolVar(&args.WithWatch, "with-watch", false, usage) + } + } + if f, ok := ty.FieldByName("ListersName"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.ListersName, "listers-name", "", usage) + } + } + if f, ok := ty.FieldByName("InformersName"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.InformersName, "informers-name", "", usage) + } + } +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/client/help.go b/staging/src/k8s.io/code-generator/internal/codegen/command/client/help.go new file mode 100644 index 0000000000000..3169711caa750 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/client/help.go @@ -0,0 +1,41 @@ +/* +Copyright 2023 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 client + +import ( + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/codegen/client" +) + +func (c Command) Name() string { + return "gen-client" +} + +func (c Command) OneLine() string { + return "Generate client code" +} + +func (c Command) Help() string { + args := &client.Args{} + fs := pflag.NewFlagSet("help", pflag.ContinueOnError) + defineFlags(fs, args) + return "Usage: code-generator " + c.Name() + " [options]\n" + + "\n" + + c.OneLine() + "\n" + + "\n" + + "Options:\n" + fs.FlagUsagesWrapped(100) +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd.go b/staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd.go new file mode 100644 index 0000000000000..bc245296b4a52 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd.go @@ -0,0 +1,114 @@ +/* +Copyright 2023 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 help + +import ( + "fmt" + "k8s.io/code-generator/internal/codegen/command" + "k8s.io/code-generator/internal/codegen/execution" + "strings" +) + +// Command prints the usage information on the standard output. +type Command struct { + Others []command.Usage +} + +func (c Command) Name() string { + return "help" +} + +func (c Command) OneLine() string { + return "Print this message. You can specify a command to get help for that command." +} + +func (c Command) Help() string { + var sb strings.Builder + _, _ = sb.WriteString(`Usage: code-generator [command] [options] + +Command: +`) + cmds := append([]command.Usage{c}, c.Others...) + longest := 0 + type mesg struct { + name string + desc string + } + var mesgs []mesg + for _, usage := range cmds { + currLen := len(usage.Name()) + if currLen > longest { + longest = currLen + } + mesgs = append(mesgs, mesg{name: usage.Name(), desc: usage.OneLine()}) + } + longest += 3 + for _, m := range mesgs { + _, _ = sb.WriteString(fmt.Sprintf(" %s %s\n", + pad(m.name, longest), m.desc)) + } + return sb.String() +} + +func pad(s string, l int) string { + pl := l - len(s) + if pl < 0 { + pl = 0 + } + return s + strings.Repeat(" ", pl) +} + +func (c Command) Matches(ex *execution.Vars) bool { + if len(ex.Args) >= 1 && ex.Args[0] == "help" { + return true + } + for _, arg := range ex.Args { + if arg == "--help" || arg == "-h" { + return true + } + } + return false +} + +func (c Command) Run(ex *execution.Vars) { + args := stripHelp(ex.Args) + if len(args) > 0 { + cmdName := args[0] + cmds := append([]command.Usage{c}, c.Others...) + for _, c := range cmds { + if c.Name() == cmdName { + ex.Println(c.Help()) + return + } + } + i := InvalidCommand{} + i.Run(ex) + return + } + ex.Println(c.Help()) +} + +func stripHelp(args []string) []string { + filtered := make([]string, 0, len(args)) + for _, arg := range args { + if arg == "help" || arg == "--help" || arg == "-h" { + continue + } + filtered = append(filtered, arg) + } + return filtered +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd_test.go b/staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd_test.go new file mode 100644 index 0000000000000..85f03c3f6b530 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/help/cmd_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2023 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 help_test + +import ( + "bytes" + "k8s.io/code-generator/internal/codegen/command" + "k8s.io/code-generator/internal/codegen/command/help" + "k8s.io/code-generator/internal/codegen/execution" + "testing" +) + +func TestHelpCommand(t *testing.T) { + t.Parallel() + c := help.Command{ + Others: []command.Usage{ + testUsage{"foo", "foo one line", "foo help"}, + testUsage{"bar", "bar one line", "bar help"}, + }, + } + var err bytes.Buffer + c.Run(execution.New(func(v *execution.Vars) { + v.Out = &err + v.Args = []string{"help"} + })) + + want := `Usage: code-generator [command] [options] + +Command: + help Print this message. You can specify a command to get help for that command. + foo foo one line + bar bar one line + +` + if err.String() != want { + t.Errorf("Output missmaches:\nwant %q,\n got %q", want, err.String()) + } +} + +type testUsage struct { + name string + one string + help string +} + +func (t testUsage) Name() string { + return t.name +} + +func (t testUsage) OneLine() string { + return t.one +} + +func (t testUsage) Help() string { + return t.help +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid.go b/staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid.go new file mode 100644 index 0000000000000..d46c9e35d739f --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid.go @@ -0,0 +1,51 @@ +/* +Copyright 2023 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 help + +import ( + "k8s.io/code-generator/internal/codegen/execution" + "strings" +) + +// InvalidCommand is a command that prints an error message and the usage. +type InvalidCommand struct { + Usage Command +} + +func (i InvalidCommand) Matches(ex *execution.Vars) bool { + // isn't used + return false +} + +func (i InvalidCommand) Run(ex *execution.Vars) { + ex.Printf("%s: %s\n", i.OneLine(), strings.Join(ex.Args, " ")) + ex.Println() + ex.Println(i.Usage.Help()) + ex.Exit(6) +} + +func (i InvalidCommand) Name() string { + return "invalid" +} + +func (i InvalidCommand) OneLine() string { + return "Invalid arguments given" +} + +func (i InvalidCommand) Help() string { + return i.OneLine() +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid_test.go b/staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid_test.go new file mode 100644 index 0000000000000..8f61d4d6a9225 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/help/invalid_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2023 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 help_test + +import ( + "bytes" + "k8s.io/code-generator/internal/codegen/command/help" + "k8s.io/code-generator/internal/codegen/execution" + "math" + "testing" +) + +func TestInvalidCommand(t *testing.T) { + t.Parallel() + i := help.InvalidCommand{ + Usage: help.Command{}, + } + var err bytes.Buffer + retcode := math.MinInt32 + v := execution.New(func(v *execution.Vars) { + v.Args = []string{"invalid-cmd"} + v.Out = &err + v.Exit = func(code int) { + retcode = code + } + }) + if i.Matches(v) { + t.Error("Expected no match") + } + if i.Name() != "invalid" { + t.Errorf("Expected name invalid, got %q", i.Name()) + } + if i.OneLine() != "Invalid arguments given" { + t.Errorf("Expected one line message, got %q", i.OneLine()) + } + if i.Help() != "Invalid arguments given" { + t.Errorf("Expected help message, got %q", i.Help()) + } + i.Run(v) + if retcode != 6 { + t.Errorf("Expected exit code 6, got %d", retcode) + } + want := `Invalid arguments given: invalid-cmd + +Usage: code-generator [command] [options] + +Command: + help Print this message. You can specify a command to get help for that command. + +` + if err.String() != want { + t.Errorf("Output missmaches:\nwant %q,\n got %q", want, err.String()) + } +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd.go b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd.go new file mode 100644 index 0000000000000..94545b8aea3f5 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd.go @@ -0,0 +1,87 @@ +/* +Copyright 2023 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 helpers + +import ( + goflag "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/internal/codegen/execution" + "k8s.io/code-generator/pkg/codegen/helpers" +) + +// Generator is the interface for generating helper code. +type Generator interface { + Generate(args *helpers.Args) error +} + +type Command struct { + Gen Generator + *goflag.FlagSet +} + +func (c Command) Matches(ex *execution.Vars) bool { + return len(ex.Args) >= 1 && ex.Args[0] == c.Name() +} + +func (c Command) Run(ex *execution.Vars) { + args := &helpers.Args{} + fs := pflag.NewFlagSet(ex.Args[0], pflag.ContinueOnError) + fs.AddGoFlagSet(c.flags()) // make sure we get the klog flags + defineFlags(fs, args) + if err := fs.Parse(ex.Args[1:]); err != nil { + c.printErrorWithUsage(ex, "Error parsing arguments:", err) + return + } + if len(fs.Args()) != 1 { + c.printErrorWithUsage(ex, "Wrong number of arguments:", fs.Args(), + "expected only one") + return + } + args.InputDir = fs.Args()[0] + if err := args.Validate(); err != nil { + c.printErrorWithUsage(ex, err) + return + } + gen := c.createOrGetGenerator() + if err := gen.Generate(args); err != nil { + ex.Println("Error generating helpers:", err) + ex.Exit(11) + return + } +} + +func (c Command) printErrorWithUsage(ex *execution.Vars, i ...any) { + ex.Println(i...) + ex.Println() + ex.Println(c.Help()) + ex.Exit(10) +} + +func (c Command) createOrGetGenerator() Generator { + if c.Gen == nil { + return &helpers.Generator{} + } + return c.Gen +} + +func (c Command) flags() *goflag.FlagSet { + if c.FlagSet == nil { + return goflag.CommandLine + } + return c.FlagSet +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd_test.go b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd_test.go new file mode 100644 index 0000000000000..bd8358d52a0e8 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/cmd_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2023 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 helpers_test + +import ( + "bytes" + goflag "flag" + "math" + "os" + "path" + "strings" + "testing" + + "k8s.io/code-generator/internal/codegen/command/helpers" + "k8s.io/code-generator/internal/codegen/execution" + pkghelpers "k8s.io/code-generator/pkg/codegen/helpers" +) + +func TestCommandMatches(t *testing.T) { + t.Parallel() + + cmd := helpers.Command{Gen: &testGen{}} + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{cmd.Name()} + }) + if !cmd.Matches(ex) { + t.Errorf("expected command to match") + } +} + +func TestCommandRun(t *testing.T) { + t.Parallel() + + gen := &testGen{} + cmd := helpers.Command{ + Gen: gen, + FlagSet: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + ob := t.TempDir() + inputDir := path.Join(ob, "foo") + _ = os.MkdirAll(inputDir, 0x755) + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{ + cmd.Name(), + "--input-dir", inputDir, + } + }) + cmd.Run(ex) + if len(gen.calls) != 1 { + t.Errorf("expected gen to be called once, got %d", len(gen.calls)) + } + call := gen.calls[0] + if call.InputDir != inputDir { + t.Errorf("expected input package to be %s, got %s", + inputDir, call.InputDir) + } + if !strings.Contains(call.Boilerplate, "boilerplate.go.txt") { + t.Errorf("expected boilerplate to point to boilerplate.go.txt, got %s", + call.Boilerplate) + } +} + +func TestCommandRunInvalidArgs(t *testing.T) { + t.Parallel() + var errstream bytes.Buffer + retcode := math.MinInt32 + gen := &testGen{} + cmd := helpers.Command{ + Gen: gen, + FlagSet: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{cmd.Name(), "--input-foo", "foo"} + v.Out = &errstream + v.Exit = func(code int) { + retcode = code + } + }) + cmd.Run(ex) + if len(gen.calls) != 0 { + t.Errorf("expected gen to be called zero times, got %d", len(gen.calls)) + } + if retcode != 10 { + t.Errorf("expected exit code 10, got %d", retcode) + } + if errstream.String() == "" { + t.Errorf("expected error message to be printed") + } +} + +type testGen struct { + calls []pkghelpers.Args +} + +func (t *testGen) Generate(args *pkghelpers.Args) error { + if t.calls == nil { + t.calls = make([]pkghelpers.Args, 0, 1) + } + t.calls = append(t.calls, *args) + return nil +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/flags.go b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/flags.go new file mode 100644 index 0000000000000..bb3c1ea9c8020 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/flags.go @@ -0,0 +1,38 @@ +/* +Copyright 2023 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 helpers + +import ( + "reflect" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/codegen/helpers" +) + +func defineFlags(fs *pflag.FlagSet, args *helpers.Args) { + ty := reflect.TypeOf(*args) + if f, ok := ty.FieldByName("Boilerplate"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.Boilerplate, "boilerplate", "", usage) + } + } + if f, ok := ty.FieldByName("ExtraPeerDirs"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringSliceVar(&args.ExtraPeerDirs, "extra-peer-dir", nil, usage) + } + } +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/help.go b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/help.go new file mode 100644 index 0000000000000..c542478445d9c --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/helpers/help.go @@ -0,0 +1,56 @@ +/* +Copyright 2023 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 helpers + +import ( + "reflect" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/codegen/helpers" +) + +func (c Command) Name() string { + return "gen-helpers" +} + +func (c Command) OneLine() string { + return "Generate tagged helper code: conversions, deepcopy, and defaults" +} + +func (c Command) Help() string { + args := &helpers.Args{} + fs := pflag.NewFlagSet("help", pflag.ContinueOnError) + defineFlags(fs, args) + return "Usage: code-generator " + c.Name() + " [options] \n" + + "\n" + + c.OneLine() + "\n" + + "\n" + + "Arguments:\n" + + " " + inputDirDoc(args) + "\n" + + "\n" + + "Options:\n" + fs.FlagUsagesWrapped(100) +} + +func inputDirDoc(args *helpers.Args) string { + ty := reflect.TypeOf(*args) + if f, ok := ty.FieldByName("InputDir"); ok { + if usage, ook := f.Tag.Lookup("input-dir"); ook { + return usage + } + } + return "The input directory to read the input files from" +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd.go b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd.go new file mode 100644 index 0000000000000..f4d79cc812277 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd.go @@ -0,0 +1,73 @@ +/* +Copyright 2023 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 openapi + +import ( + goflag "flag" + "github.com/spf13/pflag" + "k8s.io/code-generator/internal/codegen/execution" + "k8s.io/code-generator/pkg/codegen/openapi" +) + +// Generator is the interface for generating openapi code. +type Generator interface { + Generate(args *openapi.Args) error +} + +// Command is the command for generating openapi code. +type Command struct { + Gen Generator + *goflag.FlagSet +} + +func (c Command) Matches(ex *execution.Vars) bool { + return len(ex.Args) >= 1 && ex.Args[0] == c.Name() +} + +func (c Command) Run(ex *execution.Vars) { + args := &openapi.Args{} + fs := pflag.NewFlagSet(ex.Args[0], pflag.ContinueOnError) + fs.AddGoFlagSet(c.flags()) // make sure we get the klog flags + defineFlags(fs, args) + if err := fs.Parse(ex.Args[1:]); err != nil { + ex.Println("Error parsing arguments:", err) + ex.Println() + ex.Println(c.Help()) + ex.Exit(12) + return + } + gen := c.createOrGetGenerator() + if err := gen.Generate(args); err != nil { + ex.Println("Error generating openapi:", err) + ex.Exit(13) + return + } +} + +func (c Command) createOrGetGenerator() Generator { + if c.Gen == nil { + return &openapi.Generator{} + } + return c.Gen +} + +func (c Command) flags() *goflag.FlagSet { + if c.FlagSet == nil { + return goflag.CommandLine + } + return c.FlagSet +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd_test.go b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd_test.go new file mode 100644 index 0000000000000..3a8c740e1733b --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/cmd_test.go @@ -0,0 +1,111 @@ +/* +Copyright 2023 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 openapi_test + +import ( + "bytes" + goflag "flag" + "k8s.io/code-generator/internal/codegen/command/openapi" + "k8s.io/code-generator/internal/codegen/execution" + pkgopenapi "k8s.io/code-generator/pkg/codegen/openapi" + "math" + "testing" +) + +func TestCommandMatches(t *testing.T) { + t.Parallel() + + cmd := openapi.Command{Gen: &testGen{}} + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{cmd.Name()} + }) + if !cmd.Matches(ex) { + t.Errorf("expected command to match") + } +} + +func TestCommandRun(t *testing.T) { + t.Parallel() + + gen := &testGen{} + cmd := openapi.Command{ + Gen: gen, + FlagSet: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{ + cmd.Name(), + "--input-pkg-root", "foo", + "--output-base", "bar", + "--boilerplate", "baz", + } + }) + cmd.Run(ex) + if len(gen.calls) != 1 { + t.Errorf("expected gen to be called once, got %d", len(gen.calls)) + } + call := gen.calls[0] + if call.InputPkgRoot != "foo" { + t.Errorf("expected input package to be foo, got %s", call.InputPkgRoot) + } + if call.OutputBase != "bar" { + t.Errorf("expected output base to be bar, got %s", call.OutputBase) + } + if call.Boilerplate != "baz" { + t.Errorf("expected boilerplate to be baz, got %s", call.Boilerplate) + } +} + +func TestCommandRunInvalidArgs(t *testing.T) { + t.Parallel() + var errstream bytes.Buffer + retcode := math.MinInt32 + gen := &testGen{} + cmd := openapi.Command{ + Gen: gen, + FlagSet: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + ex := execution.New(func(v *execution.Vars) { + v.Args = []string{cmd.Name(), "--input-foo", "foo"} + v.Out = &errstream + v.Exit = func(code int) { + retcode = code + } + }) + cmd.Run(ex) + if len(gen.calls) != 0 { + t.Errorf("expected gen to be called zero times, got %d", len(gen.calls)) + } + if retcode != 12 { + t.Errorf("expected exit code 12, got %d", retcode) + } + if errstream.String() == "" { + t.Errorf("expected error message to be printed") + } +} + +type testGen struct { + calls []pkgopenapi.Args +} + +func (t *testGen) Generate(args *pkgopenapi.Args) error { + if t.calls == nil { + t.calls = make([]pkgopenapi.Args, 0, 1) + } + t.calls = append(t.calls, *args) + return nil +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/flags.go b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/flags.go new file mode 100644 index 0000000000000..ce9db90eb537d --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/flags.go @@ -0,0 +1,68 @@ +/* +Copyright 2023 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 openapi + +import ( + "reflect" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/codegen/openapi" +) + +func defineFlags(fs *pflag.FlagSet, args *openapi.Args) { + ty := reflect.TypeOf(*args) + if f, ok := ty.FieldByName("InputDir"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.InputPkgRoot, "input-pkg-root", "", usage) + } + } + if f, ok := ty.FieldByName("OutputPkgRoot"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.OutputPkgRoot, "output-pkg-root", "", usage) + } + } + if f, ok := ty.FieldByName("OutputBase"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.OutputBase, "output-base", "", usage) + } + } + if f, ok := ty.FieldByName("OpenapiName"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.OpenapiName, "openapi-name", "openapi", usage) + } + } + if f, ok := ty.FieldByName("ExtraPkgs"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.ExtraPkgs, "extra-pkg", "", usage) + } + } + if f, ok := ty.FieldByName("ReportFile"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.ReportFile, "report-file", "", usage) + } + } + if f, ok := ty.FieldByName("UpdateReport"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.BoolVar(&args.UpdateReport, "update-report", false, usage) + } + } + if f, ok := ty.FieldByName("Boilerplate"); ok { + if usage, ook := f.Tag.Lookup("doc"); ook { + fs.StringVar(&args.Boilerplate, "boilerplate", "", usage) + } + } +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/help.go b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/help.go new file mode 100644 index 0000000000000..884533a3e03ce --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/openapi/help.go @@ -0,0 +1,41 @@ +/* +Copyright 2023 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 openapi + +import ( + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/codegen/openapi" +) + +func (c Command) Name() string { + return "gen-openapi" +} + +func (c Command) OneLine() string { + return "Generate openapi code" +} + +func (c Command) Help() string { + args := &openapi.Args{} + fs := pflag.NewFlagSet("help", pflag.ContinueOnError) + defineFlags(fs, args) + return "Usage: code-generator " + c.Name() + " [options]\n" + + "\n" + + c.OneLine() + "\n" + + "\n" + + "Options:\n" + fs.FlagUsagesWrapped(100) +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/command/usage.go b/staging/src/k8s.io/code-generator/internal/codegen/command/usage.go new file mode 100644 index 0000000000000..5000c215a8d98 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/command/usage.go @@ -0,0 +1,27 @@ +/* +Copyright 2023 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 command + +// Usage provides usage information for a command. +type Usage interface { + // Name returns the name of the command. + Name() string + // OneLine returns a one-line usage string. + OneLine() string + // Help returns a multi-line usage string. + Help() string +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/execution/exec.go b/staging/src/k8s.io/code-generator/internal/codegen/execution/exec.go new file mode 100644 index 0000000000000..6b085a5dd65ba --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/execution/exec.go @@ -0,0 +1,54 @@ +/* +Copyright 2023 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 execution + +import ( + "io" + "os" +) + +// New returns a new Vars with the given options applied. +func New(options ...Option) *Vars { + ex := new(Vars) + for _, option := range options { + option(ex) + } + ex.fillDefaults() + return ex +} + +// Option is a functional option for Vars. +type Option func(v *Vars) + +// Vars is the execution context for the code-generator. +type Vars struct { + Out io.Writer + Args []string + Exit func(int) +} + +func (v *Vars) fillDefaults() { + if v.Out == nil { + v.Out = os.Stderr + } + if v.Exit == nil { + v.Exit = os.Exit + } + if v.Args == nil { + v.Args = os.Args[1:] + } +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/execution/print.go b/staging/src/k8s.io/code-generator/internal/codegen/execution/print.go new file mode 100644 index 0000000000000..b025935314e25 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/execution/print.go @@ -0,0 +1,31 @@ +/* +Copyright 2023 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 execution + +import "fmt" + +func (v *Vars) Print(i ...any) { + _, _ = fmt.Fprint(v.Out, i...) +} + +func (v *Vars) Println(i ...any) { + v.Print(fmt.Sprintln(i...)) +} + +func (v *Vars) Printf(format string, i ...any) { + v.Print(fmt.Sprintf(format, i...)) +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/execution/print_test.go b/staging/src/k8s.io/code-generator/internal/codegen/execution/print_test.go new file mode 100644 index 0000000000000..ae34b8e321a0a --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/execution/print_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 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 execution_test + +import ( + "bytes" + "k8s.io/code-generator/internal/codegen/execution" + "testing" +) + +func TestPrint(t *testing.T) { + t.Parallel() + var out bytes.Buffer + v := execution.New() + v.Out = &out + v.Print("foo") + v.Printf("bar%s", "\n") + v.Println("fizz") + + if out.String() != "foobar\nfizz\n" { + t.Errorf("unexpected output: %s", out.String()) + } +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/parse.go b/staging/src/k8s.io/code-generator/internal/codegen/parse.go new file mode 100644 index 0000000000000..73534fd71eb57 --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/parse.go @@ -0,0 +1,35 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package codegen + +import ( + "k8s.io/code-generator/internal/codegen/command/help" + "k8s.io/code-generator/internal/codegen/execution" +) + +func parse(ex *execution.Vars, cmds []Command) Command { + var usage help.Command + for _, cmd := range cmds { + if cmd.Matches(ex) { + return cmd + } + if u, ok := cmd.(help.Command); ok { + usage = u + } + } + return help.InvalidCommand{Usage: usage} +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/run.go b/staging/src/k8s.io/code-generator/internal/codegen/run.go new file mode 100644 index 0000000000000..b763d2ebcc3df --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/run.go @@ -0,0 +1,50 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package codegen + +import ( + "k8s.io/code-generator/internal/codegen/command" + "k8s.io/code-generator/internal/codegen/command/client" + "k8s.io/code-generator/internal/codegen/command/help" + "k8s.io/code-generator/internal/codegen/command/helpers" + "k8s.io/code-generator/internal/codegen/command/openapi" + "k8s.io/code-generator/internal/codegen/execution" +) + +// Run will run the K8s code-generator Vars. +func Run(opts ...execution.Option) { + ex := execution.New(opts...) + cmds := []Command{ + helpers.Command{}, + openapi.Command{}, + client.Command{}, + } + cmd := parse(ex, append([]Command{ + help.Command{ + Others: asUsages(cmds), + }, + }, cmds...)) + cmd.Run(ex) +} + +func asUsages(cmds []Command) []command.Usage { + usages := make([]command.Usage, 0, len(cmds)) + for _, cmd := range cmds { + usages = append(usages, cmd) + } + return usages +} diff --git a/staging/src/k8s.io/code-generator/internal/codegen/run_test.go b/staging/src/k8s.io/code-generator/internal/codegen/run_test.go new file mode 100644 index 0000000000000..d2081ddf6756e --- /dev/null +++ b/staging/src/k8s.io/code-generator/internal/codegen/run_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2023 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_test + +import ( + "bytes" + "k8s.io/code-generator/internal/codegen" + "k8s.io/code-generator/internal/codegen/execution" + "math" + "testing" +) + +func TestRun(t *testing.T) { + t.Parallel() + var err bytes.Buffer + codegen.Run(func(v *execution.Vars) { + v.Args = []string{"help", "gen-client"} + v.Out = &err + }) + if !bytes.Contains(err.Bytes(), []byte("Usage: code-generator gen-client [options]")) { + t.Errorf("Expected usage, got: %#v", err.String()) + } +} + +func TestInvalid(t *testing.T) { + t.Parallel() + var err bytes.Buffer + retcode := math.MinInt32 + codegen.Run(func(ex *execution.Vars) { + ex.Args = []string{"invalid"} + ex.Out = &err + ex.Exit = func(code int) { + retcode = code + } + }) + if !bytes.Contains(err.Bytes(), []byte("Invalid arguments given: invalid")) { + t.Errorf("Expected invalid arguments, got: %#v", err.String()) + } + if retcode != 6 { + t.Errorf("Expected exit code 6, got: %d", retcode) + } +} diff --git a/staging/src/k8s.io/code-generator/kube_codegen.sh b/staging/src/k8s.io/code-generator/kube_codegen.sh index 1f3f83abadd68..7e7e583360c08 100755 --- a/staging/src/k8s.io/code-generator/kube_codegen.sh +++ b/staging/src/k8s.io/code-generator/kube_codegen.sh @@ -63,156 +63,9 @@ function kube::codegen::internal::grep() { # directories to consider during conversion generation. # function kube::codegen::gen_helpers() { - local in_dir="" - local boilerplate="${KUBE_CODEGEN_ROOT}/hack/boilerplate.go.txt" - local v="${KUBE_VERBOSE:-0}" - local extra_peers=() - - while [ "$#" -gt 0 ]; do - case "$1" in - "--boilerplate") - boilerplate="$2" - shift 2 - ;; - "--extra-peer-dir") - extra_peers+=("$2") - shift 2 - ;; - *) - if [[ "$1" =~ ^-- ]]; then - echo "unknown argument: $1" >&2 - return 1 - fi - if [ -n "$in_dir" ]; then - echo "too many arguments: $1 (already have $in_dir)" >&2 - return 1 - fi - in_dir="$1" - shift - ;; - esac - done - - if [ -z "${in_dir}" ]; then - echo "input-dir argument is required" >&2 - return 1 - fi - - ( - # To support running this from anywhere, first cd into this directory, - # and then install with forced module mode on and fully qualified name. - cd "${KUBE_CODEGEN_ROOT}" - BINS=( - conversion-gen - deepcopy-gen - defaulter-gen - ) - # shellcheck disable=2046 # printf word-splitting is intentional - GO111MODULE=on go install $(printf "k8s.io/code-generator/cmd/%s " "${BINS[@]}") - ) - # Go installs in $GOBIN if defined, and $GOPATH/bin otherwise - gobin="${GOBIN:-$(go env GOPATH)/bin}" - - # Deepcopy - # - local input_pkgs=() - while read -r dir; do - pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)" - input_pkgs+=("${pkg}") - done < <( - ( kube::codegen::internal::grep -l --null \ - -e '+k8s:deepcopy-gen=' \ - -r "${in_dir}" \ - --include '*.go' \ - || true \ - ) | while read -r -d $'\0' F; do dirname "${F}"; done \ - | LC_ALL=C sort -u - ) - - if [ "${#input_pkgs[@]}" != 0 ]; then - echo "Generating deepcopy code for ${#input_pkgs[@]} targets" - - kube::codegen::internal::findz \ - "${in_dir}" \ - -type f \ - -name zz_generated.deepcopy.go \ - | xargs -0 rm -f - - "${gobin}/deepcopy-gen" \ - -v "${v}" \ - --output-file zz_generated.deepcopy.go \ - --go-header-file "${boilerplate}" \ - "${input_pkgs[@]}" - fi - - # Defaults - # - local input_pkgs=() - while read -r dir; do - pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)" - input_pkgs+=("${pkg}") - done < <( - ( kube::codegen::internal::grep -l --null \ - -e '+k8s:defaulter-gen=' \ - -r "${in_dir}" \ - --include '*.go' \ - || true \ - ) | while read -r -d $'\0' F; do dirname "${F}"; done \ - | LC_ALL=C sort -u - ) - - if [ "${#input_pkgs[@]}" != 0 ]; then - echo "Generating defaulter code for ${#input_pkgs[@]} targets" - - kube::codegen::internal::findz \ - "${in_dir}" \ - -type f \ - -name zz_generated.defaults.go \ - | xargs -0 rm -f - - "${gobin}/defaulter-gen" \ - -v "${v}" \ - --output-file zz_generated.defaults.go \ - --go-header-file "${boilerplate}" \ - "${input_pkgs[@]}" - fi - - # Conversions - # - local input_pkgs=() - while read -r dir; do - pkg="$(cd "${dir}" && GO111MODULE=on go list -find .)" - input_pkgs+=("${pkg}") - done < <( - ( kube::codegen::internal::grep -l --null \ - -e '+k8s:conversion-gen=' \ - -r "${in_dir}" \ - --include '*.go' \ - || true \ - ) | while read -r -d $'\0' F; do dirname "${F}"; done \ - | LC_ALL=C sort -u - ) - - if [ "${#input_pkgs[@]}" != 0 ]; then - echo "Generating conversion code for ${#input_pkgs[@]} targets" - - kube::codegen::internal::findz \ - "${in_dir}" \ - -type f \ - -name zz_generated.conversion.go \ - | xargs -0 rm -f - - local extra_peer_args=() - for arg in "${extra_peers[@]:+"${extra_peers[@]}"}"; do - extra_peer_args+=("--extra-peer-dirs" "$arg") - done - "${gobin}/conversion-gen" \ - -v "${v}" \ - --output-file zz_generated.conversion.go \ - --go-header-file "${boilerplate}" \ - "${extra_peer_args[@]:+"${extra_peer_args[@]}"}" \ - "${input_pkgs[@]}" - fi + # TODO: Add deprecation message, when other functions in this file are + # rewritten to use the new go-native code-generator. + GO111MODULE=on go run k8s.io/code-generator gen-helpers "$@" } # Generate openapi code diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/client/args.go b/staging/src/k8s.io/code-generator/pkg/codegen/client/args.go new file mode 100644 index 0000000000000..8fb9d5e5466a6 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/client/args.go @@ -0,0 +1,32 @@ +/* +Copyright 2023 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 client + +// Args are the arguments for the client generator. +type Args struct { + InputPkgRoot string `doc:"The root package under which to search for types.go files which request clients to be generated. This must be Go package syntax, e.g. \"k8s.io/foo/bar\"."` + OutputPkgRoot string `doc:"The root package into which generated directories and files will be placed. This must be Go package syntax, e.g. \"k8s.io/foo/bar\"."` + OutputBase string `doc:"The root directory under which to emit code. The concatenation of + must be valid."` + Boilerplate string `doc:"The path to a file containing a header to insert into generated files."` + ClientsetName string `doc:"An optional override for the leaf name of the generated \"clientset\" directory."` + VersionedName string `doc:"An optional override for the leaf name of the generated \"/versioned\" directory."` + WithApplyConfig bool `doc:"Enables generation of applyconfiguration files."` + ApplyConfigName string `doc:"An optional override for the leaf name of the generated \"applyconfiguration\" directory."` + WithWatch bool `doc:"Enables generation of listers and informers for APIs which support WATCH."` + ListersName string `doc:"An optional override for the leaf name of the generated \"listers\" directory."` + InformersName string `doc:"An optional override for the leaf name of the generated \"informers\" directory."` +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/client/gen.go b/staging/src/k8s.io/code-generator/pkg/codegen/client/gen.go new file mode 100644 index 0000000000000..969b0875250e7 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/client/gen.go @@ -0,0 +1,29 @@ +/* +Copyright 2023 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 client + +import ( + "errors" +) + +// Generator is the client generator. +type Generator struct{} + +func (g *Generator) Generate(args *Args) error { + // TODO: implement me + return errors.New("not yet implemented") +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/args.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/args.go new file mode 100644 index 0000000000000..49abc3b2f8d3c --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/args.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 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 helpers + +import ( + "errors" + "os" + "path" + "path/filepath" + + "k8s.io/code-generator/pkg/fs/current" +) + +var ( + // ErrInputDirIsRequired is returned when the input directory is not specified. + ErrInputDirIsRequired = errors.New("--input-dir is required to be a valid directory") + // ErrBoilerplateIsntReadable is returned when the boilerplate file is not readable. + ErrBoilerplateIsntReadable = errors.New("--boilerplate needs to point to a readable file") +) + +// Args are the arguments for the helper generator. +type Args struct { + InputDir string `doc:"The root directory under which to search for Go files which request code to be generated. This must be a local path, not a Go package."` + Boilerplate string `doc:"An optional override for the header file to insert into generated files."` + ExtraPeerDirs []string `doc:"An optional list (this flag may be specified multiple times) of \"extra\" directories to consider during conversion generation."` +} + +// Validate the arguments. +func (a *Args) Validate() error { + if len(a.InputDir) == 0 { + return ErrInputDirIsRequired + } + if fp, err := filepath.Abs(a.InputDir); err != nil { + return err + } else { + a.InputDir = fp + } + if len(a.Boilerplate) == 0 { + if b, err := defaultBoilerplate(); err != nil { + return err + } else { + a.Boilerplate = b + } + } + if len(a.Boilerplate) > 0 && !isReadable(a.Boilerplate) { + return ErrBoilerplateIsntReadable + } + if fp, err := filepath.Abs(a.Boilerplate); err != nil { + return err + } else { + a.Boilerplate = fp + } + return nil +} + +func defaultBoilerplate() (string, error) { + dir, err := current.Dir() + if err != nil { + return "", err + } + codegenRoot := path.Dir(path.Dir(path.Dir(dir))) + return path.Join(codegenRoot, "examples", "hack", "boilerplate.go.txt"), nil +} + +func isReadable(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func isDir(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/conversion.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/conversion.go new file mode 100644 index 0000000000000..3bde894969cad --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/conversion.go @@ -0,0 +1,30 @@ +/* +Copyright 2023 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 helpers + +import ( + "k8s.io/code-generator/cmd/conversion-gen/generators" +) + +func (g *Generator) generateConversion(args *Args) error { + return g.generateGen(args, genConf{ + name: "conversion", + genFn: generators.GenerateConversion, + useExtraPeerDirs: true, + searchPattern: "+k8s:conversion-gen=", + }) +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/deepcopy.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/deepcopy.go new file mode 100644 index 0000000000000..8d46d4f2e30fa --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/deepcopy.go @@ -0,0 +1,29 @@ +/* +Copyright 2023 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 helpers + +import ( + "k8s.io/code-generator/cmd/deepcopy-gen/generators" +) + +func (g *Generator) generateDeepCopy(args *Args) error { + return g.generateGen(args, genConf{ + name: "deepcopy", + genFn: generators.GenerateDeepCopy, + searchPattern: "+k8s:deepcopy-gen=", + }) +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/defaulter.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/defaulter.go new file mode 100644 index 0000000000000..630514aa4bccb --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/defaulter.go @@ -0,0 +1,30 @@ +/* +Copyright 2023 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 helpers + +import ( + "k8s.io/code-generator/cmd/defaulter-gen/generators" +) + +func (g *Generator) generateDefaulter(args *Args) error { + return g.generateGen(args, genConf{ + name: "defaulter", + fileSuffix: "defaults", + genFn: generators.GenerateDefaults, + searchPattern: "+k8s:defaulter-gen=", + }) +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator.go new file mode 100644 index 0000000000000..ede77db62bddc --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator.go @@ -0,0 +1,49 @@ +/* +Copyright 2023 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 helpers + +import ( + goflag "flag" + + "k8s.io/klog/v2" +) + +// Generator will generate the helpers for the given args. +type Generator struct { + Flags *goflag.FlagSet +} + +func (g *Generator) Generate(args *Args) error { + klog.V(1).Infof("Generating helpers for %s", args.InputDir) + klog.V(2).Infof("Helpers generator config %#v", args) + + if err := g.generateDeepCopy(args); err != nil { + return err + } + + if err := g.generateDefaulter(args); err != nil { + return err + } + + if err := g.generateConversion(args); err != nil { + return err + } + + klog.V(1).Infof("Successfully generated helpers for %v", + args.InputDir) + return nil +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator_test.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator_test.go new file mode 100644 index 0000000000000..0ed05eff9f4c5 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generator_test.go @@ -0,0 +1,144 @@ +/* +Copyright 2023 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 helpers_test + +import ( + "crypto/sha256" + "encoding/hex" + goflag "flag" + "os" + "os/exec" + "path" + "path/filepath" + "reflect" + "strings" + "testing" + + "k8s.io/code-generator/pkg/codegen/helpers" + "k8s.io/code-generator/pkg/fs" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + klogtest "k8s.io/klog/v2/test" +) + +func TestGenerate(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + currPkg := reflect.TypeOf(empty{}).PkgPath() + if !strings.HasPrefix(currPkg, "k8s.io/code-generator") { + t.Skipf("skipping test in vendored package %s", currPkg) + } + + _ = klogtest.InitKlog(t) + klog.SetLogger(ktesting.NewLogger(t, ktesting.NewConfig())) + + workdir, wantDigest := prepareWorkdir(t) + + args := &helpers.Args{ + InputDir: workdir, + } + if err := args.Validate(); err != nil { + t.Fatal(err) + } + + // Run the generator. + gen := helpers.Generator{ + Flags: goflag.NewFlagSet("test", goflag.ContinueOnError), + } + if err := gen.Generate(args); err != nil { + t.Fatal(err) + } + + // Check the output. + if gotDigest, err := hashdir(workdir); err != nil { + t.Fatal(err) + } else if gotDigest != wantDigest { + t.Errorf("got %q, want %q", gotDigest, wantDigest) + } +} + +func prepareWorkdir(tb testing.TB) (string, string) { + var sourcedir string + if root, err := fs.RootDir(); err != nil { + tb.Fatal(err) + } else { + sourcedir = path.Join(root, "examples") + } + if rd, err := filepath.EvalSymlinks(sourcedir); err != nil { + tb.Fatal(err) + } else { + sourcedir = rd + } + if _, err := os.Stat(sourcedir); err != nil { + tb.Fatal(err) + } + if err := restoreSources(sourcedir); err != nil { + tb.Fatal(err) + } + var initDigest string + if d, err := hashdir(sourcedir); err != nil { + tb.Fatal(err) + } else { + initDigest = d + } + + return sourcedir, initDigest +} + +func restoreSources(sourcedir string) error { + klog.V(5).Infof("Restoring %s", sourcedir) + err := fs.WithinDirectory(sourcedir, func() error { + klog.V(5).Infof("Calling `git checkout .`") + if err := exec.Command("git", "checkout", ".").Run(); err != nil { + return err + } + klog.V(5).Infof("Calling `git clean -fdx .`") + if err := exec.Command("git", "clean", "-fdx", ".").Run(); err != nil { + return err + } + return nil + }) + if err != nil { + klog.Errorf("Error restoring %s: %#v", sourcedir, err) + } + return err +} + +// hashdir returns a sha256 hex encoded digest of the directory. +func hashdir(dir string) (string, error) { + hash := sha256.New() + if err := filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + data, err1 := os.ReadFile(p) + if err1 != nil { + return err1 + } + if _, err1 := hash.Write(data); err1 != nil { + return err1 + } + return nil + }); err != nil { + return "", err + } + // as hex encoded string + return hex.EncodeToString(hash.Sum(nil)), nil +} + +type empty struct{} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go new file mode 100644 index 0000000000000..3d70b62ed8524 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go @@ -0,0 +1,191 @@ +/* +Copyright 2023 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 helpers + +import ( + goflag "flag" + "fmt" + "os" + "path" + "regexp" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/fs" + "k8s.io/code-generator/pkg/fs/gosrc" + "k8s.io/code-generator/pkg/osbin/golang" + "k8s.io/klog/v2" +) + +type genFn func(fs *pflag.FlagSet, args []string) error + +type genConf struct { + name string + fileSuffix string + searchPattern string + useExtraPeerDirs bool + genFn +} + +func (gc genConf) suffix() string { + if gc.fileSuffix == "" { + return gc.name + } + return gc.fileSuffix +} + +func zzGeneratedPathspec(name string) string { + return fmt.Sprintf("zz_generated.%s.go", name) +} + +func (g *Generator) generateGen(args *Args, gc genConf) error { + return fs.WithinDirectory(args.InputDir, func() error { + pkgs, err := collectPackages(args, gc) + if err != nil { + return err + } + klog.V(2).Infof("Found %d packages with %s-gen tags", + len(pkgs), gc.name) + if len(pkgs) > 0 { + if err = deleteGenerated(args, gc); err != nil { + return err + } + } + if err := g.generatePackages(args, gc, pkgs); err != nil { + return err + } + + klog.V(2).Infof("%s-gen completed successfully", gc.name) + + return nil + }) +} + +func collectPackages(args *Args, conf genConf) ([]string, error) { + var inputPkgs = make(map[string]bool, 1) + matcher := gosrc.FileContains(regexp.MustCompile( + regexp.QuoteMeta(conf.searchPattern), + )) + if files, err := gosrc.Find(args.InputDir, matcher); err != nil { + return nil, err + } else { + klog.V(3).Infof("Found %d files with %s-gen tags", + len(files), conf.name) + klog.V(4).Infof("Files with %s-gen tags: %#v", + conf.name, files) + + for _, file := range files { + klog.V(4).Infof("Resolving package for %s", file) + if p, errr := resolvePackage(file); errr != nil { + klog.Errorf("Error finding package for %s: %s", file, errr) + return nil, errr + } else { + klog.V(4).Infof("Found package %s", p) + inputPkgs[p] = true + } + } + } + var pkgs []string + for p := range inputPkgs { + pkgs = append(pkgs, p) + } + return pkgs, nil +} + +func deleteGenerated(args *Args, gc genConf) error { + pathspec := zzGeneratedPathspec(gc.suffix()) + if genFiles, err := gosrc.Find(args.InputDir, gosrc.FileEndsWith(pathspec)); err != nil { + return err + } else { + klog.V(2).Infof("Deleting %d existing %s helpers", + len(genFiles), gc.name) + for i := len(genFiles) - 1; i >= 0; i-- { + file := genFiles[i] + if _, oserr := os.Stat(file); oserr != nil && os.IsNotExist(oserr) { + continue + } + klog.V(4).Infof("Deleting %s", file) + if err = os.Remove(file); err != nil { + return err + } + } + } + return nil +} + +func (g *Generator) generatePackages(args *Args, gc genConf, pkgs []string) error { + if len(pkgs) == 0 { + return nil + } + klog.Infof("Generating %s code for %d targets", + gc.name, len(pkgs)) + + dcargs := []string{ + "--output-file", zzGeneratedPathspec(gc.suffix()), + "--go-header-file", args.Boilerplate, + } + dcargs = append(dcargs, pkgs...) + if gc.useExtraPeerDirs { + for _, pkg := range args.ExtraPeerDirs { + dcargs = append(dcargs, "--extra-peer-dirs", pkg) + } + } + genName := gc.name + "-gen" + fl := pflag.NewFlagSet(genName, pflag.ContinueOnError) + fl.AddGoFlagSet(g.flags()) // make sure we get the klog flags + klog.V(3).Infof("Running %s with args %q", + genName, dcargs) + + return asBinary(genName, func() error { + return gc.genFn(fl, dcargs) + }) +} + +func (g *Generator) flags() *goflag.FlagSet { + if g.Flags == nil { + return goflag.CommandLine + } + return g.Flags +} + +func resolvePackage(file string) (string, error) { + dir := path.Dir(file) + var foundPkg string + if err := fs.WithinDirectory(dir, func() error { + if p, err := golang.PackageOf("."); err != nil { + klog.Errorf("Error finding package for %s: %s", dir, err) + return err + } else { + foundPkg = p + return nil + } + }); err != nil { + return "", err + } + return foundPkg, nil +} + +// asBinary runs the given function as if it were the given binary. +// It sets os.Args[0] to binName, and restores it when done. This is required +// so the generator can identify itself as it was executed directly. +func asBinary(binName string, fn func() error) error { + curr := os.Args[0] + defer func() { + os.Args[0] = curr + }() + os.Args[0] = binName + return fn() +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/openapi/args.go b/staging/src/k8s.io/code-generator/pkg/codegen/openapi/args.go new file mode 100644 index 0000000000000..ebe55951770be --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/openapi/args.go @@ -0,0 +1,29 @@ +/* +Copyright 2023 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 openapi + +// Args are the arguments for the helper generator. +type Args struct { + InputPkgRoot string `doc:"The root package under which to search for files which request openapi to be generated. This must be Go package syntax, e.g. \"k8s.io/foo/bar\"."` + OutputPkgRoot string `doc:"The root package under which generated directories and files will be placed. This must be go package syntax, e.g. \"k8s.io/foo/bar\"."` + OutputBase string `doc:"The root directory under which to emit code. The concatenation of + must be valid."` + OpenapiName string `doc:"An optional override for the leaf name of the generated directory. Defaults to \"openapi\""` + ExtraPkgs string `doc:"An optional list of additional packages to be imported during openapi generation. The argument must be Go package syntax, e.g. \"k8s.io/foo/bar\". It may be a single value or a comma-delimited list. This flag may be repeated."` + ReportFile string `doc:"An optional path at which to write an API violations report. \"-\" means stdout. Defaults to \"/dev/null\""` + UpdateReport bool `doc:"If specified, update the report file in place, rather than diffing it."` + Boilerplate string `doc:"An optional override for the header file to insert into generated files."` +} diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/openapi/gen.go b/staging/src/k8s.io/code-generator/pkg/codegen/openapi/gen.go new file mode 100644 index 0000000000000..0395a5ea8d112 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/codegen/openapi/gen.go @@ -0,0 +1,28 @@ +/* +Copyright 2023 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 openapi + +import ( + "errors" +) + +type Generator struct{} + +func (g *Generator) Generate(args *Args) error { + // TODO: implement me + return errors.New("not yet implemented") +} diff --git a/staging/src/k8s.io/code-generator/pkg/fs/current/dir.go b/staging/src/k8s.io/code-generator/pkg/fs/current/dir.go new file mode 100644 index 0000000000000..0f62d0b0e72bd --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/fs/current/dir.go @@ -0,0 +1,42 @@ +/* +Copyright 2023 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 current + +import ( + "errors" + "path/filepath" + "runtime" +) + +// Dir is the __dirname equivalent. +func Dir() (string, error) { + filename, err := file(2) + if err != nil { + return "", err + } + return filepath.Dir(filename), nil +} + +var ErrCantGetCurrentFilename = errors.New("unable to get the current filename") + +func file(skip int) (string, error) { + _, filename, _, ok := runtime.Caller(skip) + if !ok { + return "", ErrCantGetCurrentFilename + } + return filename, nil +} diff --git a/staging/src/k8s.io/code-generator/pkg/fs/current/dir_test.go b/staging/src/k8s.io/code-generator/pkg/fs/current/dir_test.go new file mode 100644 index 0000000000000..510430e97318e --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/fs/current/dir_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2023 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 current_test + +import ( + "k8s.io/code-generator/pkg/fs/current" + "os" + "strings" + "testing" +) + +func TestDir(t *testing.T) { + t.Parallel() + dir, err := current.Dir() + if err != nil { + t.Errorf("no error expected, got %v", err) + } + want := "pkg/fs/current" + if !strings.Contains(dir, want) { + t.Errorf("Wanted %#v in %#v, but not found", want, dir) + } + st, ferr := os.Stat(dir) + if ferr != nil { + t.Errorf("no error expected, got %v", ferr) + } + if !st.IsDir() { + t.Errorf("Wanted %#v to be a directory", dir) + } +} diff --git a/staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go b/staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go new file mode 100644 index 0000000000000..9d73ecd0cf9fb --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go @@ -0,0 +1,107 @@ +/* +Copyright 2023 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 gosrc + +import ( + "bufio" + "io/fs" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +// Find returns the list of files matching the given matchers. The search is +// performed recursively from the given root directory. The file is only +// considered if it matches all the given matchers. +func Find(root string, matchers ...FileMatcher) ([]string, error) { + var files []string + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return filepath.SkipDir + } + n := path.Base(p) + if !(d.Type().IsRegular() && + strings.HasSuffix(n, ".go") && + !strings.HasSuffix(n, "_test.go")) { + return nil + } + for _, m := range matchers { + if !m.MatchFile(p) { + return nil + } + } + files = append(files, p) + return nil + + }) + return files, err +} + +// FileEndsWith returns a FileMatcher that matches files with the given suffix. +func FileEndsWith(suffix string) FileMatcher { + return FileMatcherFunc(func(fp string) bool { + return strings.HasSuffix(fp, suffix) + }) +} + +// FileContains returns a FileMatcher that matches files containing the given +// pattern. +func FileContains(pattern *regexp.Regexp) FileMatcher { + return FileMatcherFunc(func(fp string) bool { + return match(pattern, fp) + }) +} + +// FileMatcher matches files. +type FileMatcher interface { + // MatchFile returns true if the given file path matches. + MatchFile(string) bool +} + +// FileMatcherFunc is a function that matches files. +type FileMatcherFunc func(string) bool + +// MatchFile implements FileMatcher. +func (f FileMatcherFunc) MatchFile(fp string) bool { + return f(fp) +} + +func match(pattern *regexp.Regexp, fp string) bool { + // instead of reading the whole file into memory and matching against + // the whole file contents. + // The following code is similar to the following shell command: + // grep -q -e "$pattern" "$fp" + // The -q flag is used to suppress the output. + f, err := os.Open(fp) + if err != nil { + return false + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + if pattern.MatchString(scanner.Text()) { + return true + } + } + return false +} diff --git a/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go b/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go new file mode 100644 index 0000000000000..d3094df92db8c --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go @@ -0,0 +1,31 @@ +/* +Copyright 2023 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 fs + +import ( + "k8s.io/code-generator/pkg/fs/current" + "path" +) + +// RootDir returns the root directory of the code-generator repository. +func RootDir() (string, error) { + if c, err := current.Dir(); err != nil { + return "", err + } else { + return path.Dir(path.Dir(c)), nil + } +} diff --git a/staging/src/k8s.io/code-generator/pkg/fs/within_dir.go b/staging/src/k8s.io/code-generator/pkg/fs/within_dir.go new file mode 100644 index 0000000000000..280600862d031 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/fs/within_dir.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 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 fs + +import "os" + +// WithinDirectory changes the current working directory to dir, and then +// executes fn. It then changes the current working directory back to the +// original directory. +func WithinDirectory(dir string, fn func() error) error { + if wd, err := os.Getwd(); err != nil { + return err + } else { + defer func() { + _ = os.Chdir(wd) + }() + } + if err := os.Chdir(dir); err != nil { + return err + } + return fn() +} diff --git a/staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof.go b/staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof.go new file mode 100644 index 0000000000000..e261641a79dbd --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof.go @@ -0,0 +1,43 @@ +/* +Copyright 2023 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 golang + +import ( + "errors" + "k8s.io/klog/v2" + "os" + "os/exec" + "strings" +) + +// PackageOf returns the package name of the given path. +// +// TODO: Consider rewriting this to use go/packages instead of shelling out. +func PackageOf(path string) (string, error) { + c := exec.Command("go", "list", "-find", path) + c.Env = append(os.Environ(), "GO111MODULE=on") + klog.V(3).Infof("Running: %q", c) + out, err := c.Output() + if err != nil { + var ee *exec.ExitError + if errors.As(err, &ee) { + klog.Errorf("go list stderr: %s", ee.Stderr) + } + return "", err + } + return strings.Trim(string(out), "\n"), nil +} diff --git a/staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof_test.go b/staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof_test.go new file mode 100644 index 0000000000000..78dc97d191937 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/osbin/golang/packageof_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2023 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 golang_test + +import ( + "k8s.io/code-generator/pkg/osbin/golang" + "testing" +) + +func TestPackageOf(t *testing.T) { + pkg, err := golang.PackageOf(".") + if err != nil { + t.Errorf("no error expected, got %v", err) + } + want := "k8s.io/code-generator/pkg/osbin/golang" + if pkg != want { + t.Errorf("Missmatch\nwant %#v\n got %#v", want, pkg) + } +} diff --git a/staging/src/k8s.io/code-generator/tools.go b/staging/src/k8s.io/code-generator/tools.go index d0e2c7764f2e7..6f2ea031ff26e 100644 --- a/staging/src/k8s.io/code-generator/tools.go +++ b/staging/src/k8s.io/code-generator/tools.go @@ -19,7 +19,7 @@ limitations under the License. // This package contains code generation utilities // This package imports things required by build scripts, to force `go mod` to see them as dependencies -package codegenerator +package main import ( _ "k8s.io/code-generator/cmd/applyconfiguration-gen"