From c665b4f11402d5dd30c25ce4ae593409bace39bf Mon Sep 17 00:00:00 2001 From: Yuwen Ma Date: Thu, 14 Nov 2024 20:55:44 +0000 Subject: [PATCH 1/2] feat: a simple cmd to add a basic scifi reconciler --- dev/tools/controllerbuilder/cmd/root.go | 2 + .../generatecontrollercommand.go | 38 +++-- .../generatedirectreconciler/command.go | 138 ++++++++++++++++++ .../generatemapper/generatemappercommand.go | 34 +++-- .../generatetypes/generatetypescommand.go | 21 +-- .../controllerbuilder/template/apis/types.go | 8 +- 6 files changed, 198 insertions(+), 43 deletions(-) create mode 100644 dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go diff --git a/dev/tools/controllerbuilder/cmd/root.go b/dev/tools/controllerbuilder/cmd/root.go index b8915ad755..a8f57a08cd 100644 --- a/dev/tools/controllerbuilder/cmd/root.go +++ b/dev/tools/controllerbuilder/cmd/root.go @@ -20,6 +20,7 @@ import ( "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/exportcsv" "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatecontroller" + "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler" "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatemapper" "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatetypes" "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/updatetypes" @@ -34,6 +35,7 @@ func Execute() { rootCmd := &cobra.Command{} generateOptions.BindPersistentFlags(rootCmd) + rootCmd.AddCommand(generatedirectreconciler.BuildCommand(&generateOptions)) rootCmd.AddCommand(generatecontroller.BuildCommand(&generateOptions)) rootCmd.AddCommand(generatetypes.BuildCommand(&generateOptions)) rootCmd.AddCommand(generatemapper.BuildCommand(&generateOptions)) diff --git a/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go b/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go index ba1749cc1f..780c9e20ec 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatecontroller/generatecontrollercommand.go @@ -15,6 +15,7 @@ package generatecontroller import ( + "context" "fmt" "strings" @@ -67,24 +68,33 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { return nil }, RunE: func(cmd *cobra.Command, args []string) error { - gv, _ := schema.ParseGroupVersion(baseOptions.APIVersion) - gcpTokens := strings.Split(baseOptions.ServiceName, ".") - version := gcpTokens[len(gcpTokens)-1] - if version[0] != 'v' { - return fmt.Errorf("--service does not contain GCP version") - } - serviceName := strings.TrimSuffix(gv.Group, ".cnrm.cloud.google.com") - cArgs := &cctemplate.ControllerArgs{ - KCCService: serviceName, - KCCVersion: gv.Version, - Kind: opt.Kind, - ProtoResource: opt.ProtoName, - ProtoVersion: version, + + ctx := cmd.Context() + if err := RunController(ctx, opt); err != nil { + return err } - return scaffold.Scaffold(serviceName, opt.ProtoName, cArgs) + return nil }, } opt.BindFlags(cmd) return cmd } + +func RunController(ctx context.Context, o *GenerateControllerOptions) error { + gv, _ := schema.ParseGroupVersion(o.GenerateOptions.APIVersion) + gcpTokens := strings.Split(o.GenerateOptions.ServiceName, ".") + version := gcpTokens[len(gcpTokens)-1] + if version[0] != 'v' { + return fmt.Errorf("--service does not contain GCP version") + } + serviceName := strings.TrimSuffix(gv.Group, ".cnrm.cloud.google.com") + cArgs := &cctemplate.ControllerArgs{ + KCCService: serviceName, + KCCVersion: gv.Version, + Kind: o.Kind, + ProtoResource: o.ProtoName, + ProtoVersion: version, + } + return scaffold.Scaffold(serviceName, o.ProtoName, cArgs) +} diff --git a/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go b/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go new file mode 100644 index 0000000000..37830bf3c7 --- /dev/null +++ b/dev/tools/controllerbuilder/pkg/commands/generatedirectreconciler/command.go @@ -0,0 +1,138 @@ +// Copyright 2024 Google LLC +// +// 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 generatedirectreconciler + +import ( + "context" + "fmt" + "os" + + "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatecontroller" + "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatemapper" + "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/generatetypes" + "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/options" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type GenerateBasicReconcilerOptions struct { + *options.GenerateOptions + Kind string + ProtoName string + // OutputAPIDirectory string + + APIGoPackagePath string + APIDirectory string + OutputMapperDirectory string +} + +func (o *GenerateBasicReconcilerOptions) BindFlags(cmd *cobra.Command) { + cmd.Flags().StringVarP(&o.ProtoName, "proto-resource", "p", "", "the GCP resource proto name. It should match the name in the proto apis. i.e. For resource google.storage.v1.bucket, the `--proto-resource` should be `bucket`. If `--kind` is not given, the `--proto-resource` value will also be used as the kind name with a capital letter `Storage`.") + cmd.Flags().StringVarP(&o.Kind, "kind", "k", "", "the KCC resource Kind. requires `--proto-resource`.") + // cmd.Flags().StringVar(&o.OutputAPIDirectory, "output-api", o.OutputAPIDirectory, "base directory for writing APIs") + cmd.Flags().StringVar(&o.APIGoPackagePath, "api-go-package-path", o.APIGoPackagePath, "package path") + cmd.Flags().StringVar(&o.APIDirectory, "api-dir", o.APIDirectory, "base directory for reading APIs") + cmd.Flags().StringVar(&o.OutputMapperDirectory, "output-dir", o.OutputMapperDirectory, "base directory for writing mappers") +} + +func (o *GenerateBasicReconcilerOptions) InitDefaults() error { + root, err := options.RepoRoot() + if err != nil { + return nil + } + o.ProtoSourcePath = root + "/dev/tools/proto-to-mapper/build/googleapis.pb" + o.APIGoPackagePath = "github.com/GoogleCloudPlatform/k8s-config-connector/apis/" + o.APIDirectory = root + "/apis/" + // o.OutputAPIDirectory = root + "/apis/" + o.OutputMapperDirectory = root + "/pkg/controller/direct/" + return nil +} + +func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { + opt := &GenerateBasicReconcilerOptions{ + GenerateOptions: baseOptions, + } + + if err := opt.InitDefaults(); err != nil { + fmt.Fprintf(os.Stderr, "Error initializing defaults: %v\n", err) + os.Exit(1) + } + + cmd := &cobra.Command{ + Use: "generate-direct-reconciler", + Short: "[ALPHA] generate a basic direct reconciler that is up and run", + PreRunE: func(cmd *cobra.Command, args []string) error { + if opt.Kind == "" { + return fmt.Errorf("--kind is required") + } + if opt.ProtoName == "" { + return fmt.Errorf("--proto-resource is required") + } + if baseOptions.APIVersion == "" { + return fmt.Errorf("--api-version is required") + } + _, err := schema.ParseGroupVersion(baseOptions.APIVersion) + if err != nil { + return fmt.Errorf("unable to parse --api-version: %w", err) + } + + if baseOptions.ServiceName == "" { + return fmt.Errorf("--service is required") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + if err := RunGenerateBasicReconciler(ctx, opt); err != nil { + return err + } + return nil + }, + } + opt.BindFlags(cmd) + + return cmd +} + +func RunGenerateBasicReconciler(ctx context.Context, o *GenerateBasicReconcilerOptions) error { + crdOps := &generatetypes.GenerateCRDOptions{ + GenerateOptions: o.GenerateOptions, + OutputAPIDirectory: o.APIDirectory, + Resources: generatetypes.ResourceList{ + generatetypes.Resource{Kind: o.Kind, ProtoName: o.ProtoName}, + }, + } + if err := generatetypes.RunGenerateCRD(ctx, crdOps); err != nil { + return fmt.Errorf("generate types: %w", err) + } + mapperOps := &generatemapper.GenerateMapperOptions{ + GenerateOptions: o.GenerateOptions, + APIGoPackagePath: o.APIGoPackagePath, + APIDirectory: o.APIDirectory, + OutputMapperDirectory: o.OutputMapperDirectory, + } + if err := generatemapper.RunGenerateMapper(ctx, mapperOps); err != nil { + return fmt.Errorf("generate types: %w", err) + } + controllerOps := &generatecontroller.GenerateControllerOptions{ + GenerateOptions: o.GenerateOptions, + Kind: o.Kind, + ProtoName: o.ProtoName, + } + if err := generatecontroller.RunController(ctx, controllerOps); err != nil { + return fmt.Errorf("generate types: %w", err) + } + return nil +} diff --git a/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go b/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go index 3609bdc259..edd525c8e7 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatemapper/generatemappercommand.go @@ -68,6 +68,24 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { cmd := &cobra.Command{ Use: "generate-mapper", Short: "generate mapper functions for a proto service", + PreRunE: func(cmd *cobra.Command, args []string) error { + if opt.ServiceName == "" { + return fmt.Errorf("ServiceName is required") + } + if opt.GenerateOptions.ProtoSourcePath == "" { + return fmt.Errorf("ProtoSourcePath is required") + } + if opt.APIGoPackagePath == "" { + return fmt.Errorf("GoPackagePath is required") + } + if opt.OutputMapperDirectory == "" { + return fmt.Errorf("OutputMapperDirectory is required") + } + if opt.APIVersion == "" { + return fmt.Errorf("APIVersion is required") + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() if err := RunGenerateMapper(ctx, opt); err != nil { @@ -83,22 +101,6 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { } func RunGenerateMapper(ctx context.Context, o *GenerateMapperOptions) error { - if o.ServiceName == "" { - return fmt.Errorf("ServiceName is required") - } - if o.GenerateOptions.ProtoSourcePath == "" { - return fmt.Errorf("ProtoSourcePath is required") - } - if o.APIGoPackagePath == "" { - return fmt.Errorf("GoPackagePath is required") - } - if o.OutputMapperDirectory == "" { - return fmt.Errorf("OutputMapperDirectory is required") - } - if o.APIVersion == "" { - return fmt.Errorf("APIVersion is required") - } - gv, err := schema.ParseGroupVersion(o.APIVersion) if err != nil { return fmt.Errorf("APIVersion %q is not valid: %w", o.APIVersion, err) diff --git a/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go b/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go index ef5d38725f..12ee86e34f 100644 --- a/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go +++ b/dev/tools/controllerbuilder/pkg/commands/generatetypes/generatetypescommand.go @@ -99,6 +99,18 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { cmd := &cobra.Command{ Use: "generate-types", Short: "generate KRM types for a proto service", + PreRunE: func(cmd *cobra.Command, args []string) error { + if opt.ServiceName == "" { + return fmt.Errorf("`service` is required") + } + if opt.GenerateOptions.ProtoSourcePath == "" { + return fmt.Errorf("`proto-source-path` is required") + } + if len(opt.Resources) == 0 { + return fmt.Errorf("`--resource` is required") + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() if err := RunGenerateCRD(ctx, opt); err != nil { @@ -116,15 +128,6 @@ func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command { func RunGenerateCRD(ctx context.Context, o *GenerateCRDOptions) error { log := klog.FromContext(ctx) - if o.ServiceName == "" { - return fmt.Errorf("`service` is required") - } - if o.GenerateOptions.ProtoSourcePath == "" { - return fmt.Errorf("`proto-source-path` is required") - } - if len(o.Resources) == 0 { - return fmt.Errorf("`--resource` is required") - } // o.ResourceProtoName = capitalizeFirstRune(o.ResourceProtoName) gv, err := schema.ParseGroupVersion(o.APIVersion) diff --git a/dev/tools/controllerbuilder/template/apis/types.go b/dev/tools/controllerbuilder/template/apis/types.go index 07175b1cf4..6821e94681 100644 --- a/dev/tools/controllerbuilder/template/apis/types.go +++ b/dev/tools/controllerbuilder/template/apis/types.go @@ -32,9 +32,6 @@ import ( var {{ .Kind }}GVK = GroupVersion.WithKind("{{ .Kind }}") -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - // {{ .Kind }}Spec defines the desired state of {{ .Kind }} {{- if .KindProtoTag }} // +kcc:proto={{ .KindProtoTag }} @@ -62,11 +59,14 @@ type {{ .Kind }}Status struct { ObservedState *{{ .Kind }}ObservedState ` + "`" + `json:"observedState,omitempty"` + "`" + ` } +// {{ .Kind }}Spec defines the desired state of {{ .Kind }} +{{- if .KindProtoTag }} +// +kcc:proto={{ .KindProtoTag }} +{{- end }} // {{ .Kind }}ObservedState is the state of the {{ .Kind }} resource as most recently observed in GCP. type {{ .Kind }}ObservedState struct { } - // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // TODO(user): make sure the pluralizaiton below is correct From 8471a6b84984baa23ba5721e70869d3f742af91c Mon Sep 17 00:00:00 2001 From: Yuwen Ma Date: Fri, 15 Nov 2024 01:24:29 +0000 Subject: [PATCH 2/2] example --- dev/tools/controllerbuilder/generate.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dev/tools/controllerbuilder/generate.sh b/dev/tools/controllerbuilder/generate.sh index cff0fb27dd..2020994faf 100755 --- a/dev/tools/controllerbuilder/generate.sh +++ b/dev/tools/controllerbuilder/generate.sh @@ -227,7 +227,14 @@ go run . generate-mapper \ --output-dir $REPO_ROOT/pkg/controller/direct/ \ --api-dir $REPO_ROOT/apis/ - +go run . generate-direct-reconciler \ + --kind SecretManagerSecretVersion \ + --proto-resource SecretVersion \ + --api-version "secretmanager.cnrm.cloud.google.com/v1beta1" \ + --service "google.cloud.secretmanager.v1" \ + --proto-source-path ../proto-to-mapper/build/googleapis.pb + +# Spanner go run main.go generate-types \ --service google.spanner.admin.instance.v1 \ --proto-source-path ../proto-to-mapper/build/googleapis.pb \