Skip to content

Commit

Permalink
Merge pull request #3185 from yuwenma/single-cmd
Browse files Browse the repository at this point in the history
feat: a simple cmd to add a basic scifi reconciler
  • Loading branch information
google-oss-prow[bot] authored Nov 18, 2024
2 parents 3954a2d + 8471a6b commit 8b7eb83
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 44 deletions.
2 changes: 2 additions & 0 deletions dev/tools/controllerbuilder/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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))
Expand Down
9 changes: 8 additions & 1 deletion dev/tools/controllerbuilder/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package generatecontroller

import (
"context"
"fmt"
"strings"

Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions dev/tools/controllerbuilder/template/apis/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 8b7eb83

Please sign in to comment.