Skip to content

Commit

Permalink
Merge pull request GoogleCloudPlatform#3475 from yuwenma/apply-cmd-1
Browse files Browse the repository at this point in the history
feat: add  cmd to merge two go files (struct only)
  • Loading branch information
google-oss-prow[bot] authored Jan 13, 2025
2 parents c0ea763 + 9ed0424 commit aa04396
Show file tree
Hide file tree
Showing 5 changed files with 782 additions and 0 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 @@ -18,6 +18,7 @@ import (
"fmt"
"os"

"github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/commands/apply"
"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"
Expand All @@ -44,6 +45,7 @@ func Execute() {
rootCmd.AddCommand(updatetypes.BuildCommand(&generateOptions))
rootCmd.AddCommand(exportcsv.BuildCommand(&generateOptions))
rootCmd.AddCommand(exportcsv.BuildPromptCommand(&generateOptions))
rootCmd.AddCommand(apply.BuildCommand(&generateOptions))

if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
Expand Down
78 changes: 78 additions & 0 deletions dev/tools/controllerbuilder/pkg/commands/apply/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2025 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 apply

import (
"bytes"
"context"
"fmt"
"os"

kccio "github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/io"
"github.com/GoogleCloudPlatform/k8s-config-connector/dev/tools/controllerbuilder/pkg/options"
"github.com/spf13/cobra"
)

type ApplyOptions struct {
*options.GenerateOptions
SrcFile string
DestFile string
}

func (o *ApplyOptions) BindFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.DestFile, "dest-file", o.DestFile, "destination file path to write the gocode to, default to ./apis/ to update the Config Connector types")
cmd.Flags().StringVar(&o.SrcFile, "src-file", o.SrcFile, "src file path to read the new gocode from")
}

func BuildCommand(baseOptions *options.GenerateOptions) *cobra.Command {
opt := &ApplyOptions{
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: "apply",
Short: "[ALPHA] Write go code from src dir to dest dir",
PreRunE: func(cmd *cobra.Command, args []string) error {
if opt.SrcFile == "" {
return fmt.Errorf("--src-file is required")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if err := Apply(ctx, opt); err != nil {
return err
}
return nil
},
}
opt.BindFlags(cmd)

return cmd
}

func Apply(ctx context.Context, o *ApplyOptions) error {
rawData, err := os.ReadFile(o.SrcFile)
if err != nil {
return err
}
return kccio.UpdateGoFile(ctx, o.DestFile, bytes.NewBuffer(rawData))

}
45 changes: 45 additions & 0 deletions dev/tools/controllerbuilder/pkg/io/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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 io

import (
"context"
"fmt"
"os"
)

// WriteToCache write `out` to a temporary file under `dir`.
func WriteToCache(ctx context.Context, dir string, out string, namePattern string) (string, error) {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.Mkdir(dir, 0755); err != nil {
return "", fmt.Errorf("create dir %s: %w", dir, err)
}
}
if namePattern == "" {
namePattern = "unnamed-*"
}
tmpFile, err := os.CreateTemp(dir, namePattern)
if err != nil {
return "", fmt.Errorf("create file under %s: %w", dir, err)
}
defer tmpFile.Close()

fmt.Printf("out %s\n", out)
if _, err := tmpFile.WriteString(out); err != nil {
return "", fmt.Errorf("write to file %s: %w", tmpFile.Name(), err)
}

return tmpFile.Name(), nil
}
129 changes: 129 additions & 0 deletions dev/tools/controllerbuilder/pkg/io/comments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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 io

import (
"go/ast"
"go/token"
)

type StructComment struct {
Name *ast.Ident
Comment *ast.CommentGroup
Fields map[string]*ast.CommentGroup
}

func (s *StructComment) SetCommentPos(offset token.Pos) token.Pos {
newComments := []*ast.Comment{}
if s.Comment == nil {
return offset
}
for _, c := range s.Comment.List {
c.Slash = offset
offset += c.End() + 1
newComments = append(newComments, c)
}
s.Comment = &ast.CommentGroup{List: newComments}
return offset
}

func MergeStructComments(oldComments, newComments map[string]*StructComment) map[string]*StructComment {
merged := make(map[string]*StructComment)
for k, v := range oldComments {
merged[k] = v
}

// If the new Comment is non empty, override the old comment.
for k, v := range newComments {
merged[k] = v
}
return merged
}

// ast Docs and comments are not binding with the Decl but the relative position of the Node,
// so we have to handle it specifically.
func MapGoComments(f ast.Node) map[string]*StructComment {
comments := make(map[string]*StructComment)

ast.Inspect(f, func(n ast.Node) bool {
genDecl, ok := n.(*ast.GenDecl)
if !ok || genDecl.Tok != token.TYPE {
// TODO: support IMPORT, CONST, VAR
return true
}
c := mapStructComments(genDecl)
if c == nil {
return true
}
comments[c.Name.Name] = c
return true
})
return comments
}

func mapStructComments(g *ast.GenDecl) *StructComment {
if g == nil {
return nil
}
c := &StructComment{
Fields: make(map[string]*ast.CommentGroup),
}

if g.Doc != nil {
c.Comment = ResetCommentsPos(g.Doc)
}

for _, spec := range g.Specs {
typeSpec, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
c.Name = typeSpec.Name
for _, field := range typeSpec.Type.(*ast.StructType).Fields.List {
if field.Doc == nil {
continue
}
var name *ast.Ident

if field.Names == nil {
// Anonymous name
name = field.Type.(*ast.SelectorExpr).Sel
} else {
// Field could has multiple names like `A, B string`. Use the first one
name = field.Names[0]
}
c.Fields[name.Name] = field.Doc
c.Comment = ResetCommentsPos(field.Doc)
}
}
if c.Name == nil {
return nil
}
return c
}

func ResetCommentsPos(comments *ast.CommentGroup) *ast.CommentGroup {
if comments == nil {
return nil
}
newComments := []*ast.Comment{}
for _, c := range comments.List {
c.Slash = token.NoPos
newComments = append(newComments, c)
}
return &ast.CommentGroup{
List: newComments,
}
}
Loading

0 comments on commit aa04396

Please sign in to comment.