-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Xabier Larrakoetxea <[email protected]>
- Loading branch information
Showing
21 changed files
with
921 additions
and
505 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/slog" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
type GenClientRunner struct { | ||
cmdArgs []string | ||
codeGenPath string | ||
apisPath string | ||
logger *slog.Logger | ||
} | ||
|
||
func NewGenClientRunner(logger *slog.Logger, codeGenPath string) *GenClientRunner { | ||
return &GenClientRunner{ | ||
codeGenPath: codeGenPath, | ||
logger: logger, | ||
} | ||
} | ||
|
||
func (g *GenClientRunner) WithWatch() *GenClientRunner { | ||
g.cmdArgs = append(g.cmdArgs, `--with-watch`) | ||
return g | ||
} | ||
|
||
func (g *GenClientRunner) WithOutputPkg(pkg string) *GenClientRunner { | ||
g.cmdArgs = append(g.cmdArgs, `--output-pkg`, pkg) | ||
return g | ||
} | ||
|
||
func (g *GenClientRunner) WithOutputDir(path string) *GenClientRunner { | ||
g.cmdArgs = append(g.cmdArgs, `--output-dir`, path) | ||
return g | ||
} | ||
|
||
func (g *GenClientRunner) WithBoilerplate(path string) *GenClientRunner { | ||
g.cmdArgs = append(g.cmdArgs, `--boilerplate`, path) | ||
return g | ||
} | ||
|
||
func (g *GenClientRunner) WithAPIsPath(path string) *GenClientRunner { | ||
g.apisPath = path | ||
return g | ||
} | ||
|
||
func (g *GenClientRunner) Run(ctx context.Context) error { | ||
kubeCodeGenSHPath := filepath.Join(g.codeGenPath, "kube_codegen.sh") | ||
bashCmd := fmt.Sprintf("source %s ; kube::codegen::gen_client %s %s", kubeCodeGenSHPath, strings.Join(g.cmdArgs, " "), g.apisPath) | ||
|
||
g.logger.DebugContext(ctx, bashCmd) | ||
cmd := exec.CommandContext(ctx, "bash", "-c", bashCmd) | ||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return fmt.Errorf("error while executing bash script: %s", string(out)) | ||
} | ||
g.logger.DebugContext(ctx, string(out)) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/slog" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
type GenHelpersRunner struct { | ||
cmdArgs []string | ||
codeGenPath string | ||
apisPath string | ||
logger *slog.Logger | ||
} | ||
|
||
func NewGenHelperstRunner(logger *slog.Logger, codeGenPath string) *GenHelpersRunner { | ||
return &GenHelpersRunner{ | ||
codeGenPath: codeGenPath, | ||
logger: logger, | ||
} | ||
} | ||
|
||
func (g *GenHelpersRunner) WithBoilerplate(path string) *GenHelpersRunner { | ||
g.cmdArgs = append(g.cmdArgs, `--boilerplate`, path) | ||
return g | ||
} | ||
|
||
func (g *GenHelpersRunner) WithAPIsPath(path string) *GenHelpersRunner { | ||
g.apisPath = path | ||
return g | ||
} | ||
|
||
func (g *GenHelpersRunner) Run(ctx context.Context) error { | ||
kubeCodeGenSHPath := filepath.Join(g.codeGenPath, "kube_codegen.sh") | ||
bashCmd := fmt.Sprintf("source %s ; kube::codegen::gen_helpers %s %s", kubeCodeGenSHPath, strings.Join(g.cmdArgs, " "), g.apisPath) | ||
|
||
g.logger.DebugContext(ctx, bashCmd) | ||
cmd := exec.CommandContext(ctx, "bash", "-c", bashCmd) | ||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return fmt.Errorf("error while executing bash script: %s", string(out)) | ||
} | ||
g.logger.DebugContext(ctx, string(out)) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/alecthomas/kingpin/v2" | ||
) | ||
|
||
// CmdConfig represents the configuration of the command. | ||
type CmdConfig struct { | ||
Debug bool | ||
|
||
CodeGenPath string | ||
APIsPath string | ||
APIsOutPath string | ||
BoilerplatePath string | ||
} | ||
|
||
// NewCmdConfig returns a new command configuration. | ||
func NewCmdConfig(args []string) (*CmdConfig, error) { | ||
c := &CmdConfig{} | ||
app := kingpin.New("kube-code-generator", "The easiest way to create Kubernetes CRD related Go code and manifests.") | ||
app.DefaultEnvars() | ||
|
||
app.Flag("debug", "Enable debug mode.").BoolVar(&c.Debug) | ||
|
||
app.Flag("codegen-path", "The path where github.com/kubernetes/code-generator app is.").Required().StringVar(&c.CodeGenPath) | ||
|
||
app.Flag("apis-in", "The path to the APIs root, it must be a relative path from the root where this app is execute.").Required().StringVar(&c.APIsPath) | ||
app.Flag("go-gen-out", "The path to the Go auto generated code, it must be a relative path from the root where this app is execute.").Required().StringVar(&c.APIsOutPath) | ||
app.Flag("boilerplate-path", "Path to boilerplate text file, this file will be used to add this information to all the autogenerated code.").StringVar(&c.BoilerplatePath) | ||
|
||
_, err := app.Parse(args[1:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module github.com/slok/kube-code-generator | ||
|
||
go 1.22.0 | ||
|
||
require github.com/alecthomas/kingpin/v2 v2.4.0 | ||
|
||
require ( | ||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect | ||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= | ||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= | ||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= | ||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | ||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= | ||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"io/fs" | ||
"log/slog" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
var ( | ||
goModuleRegexp = regexp.MustCompile(`(?m)^module ([^\s]+)$`) | ||
) | ||
|
||
func run(ctx context.Context, args []string, stdout, stderr io.Writer) error { | ||
ctx, cancel := context.WithCancel(ctx) | ||
defer cancel() | ||
|
||
// Load command flags and arguments. | ||
cmdCfg, err := NewCmdConfig(args) | ||
if err != nil { | ||
return fmt.Errorf("could not load command configuration: %w", err) | ||
} | ||
|
||
loggerOps := &slog.HandlerOptions{} | ||
if cmdCfg.Debug { | ||
loggerOps.Level = slog.LevelDebug | ||
} | ||
|
||
handler := slog.NewTextHandler(stdout, loggerOps) | ||
logger := slog.New(handler) | ||
|
||
// Prepare. | ||
err = os.MkdirAll(cmdCfg.APIsOutPath, os.ModePerm) | ||
if err != nil { | ||
return fmt.Errorf("could not create directory for go generated code: %w", err) | ||
} | ||
|
||
// We will require a boilerplate always. | ||
boilerplatePath := cmdCfg.BoilerplatePath | ||
if cmdCfg.BoilerplatePath == "" { | ||
f, err := os.CreateTemp("", "kube-code-generator-boilerplate-") | ||
if err != nil { | ||
return fmt.Errorf("could not create boilerplate empty file") | ||
} | ||
f.Close() | ||
defer os.Remove(f.Name()) | ||
boilerplatePath = f.Name() | ||
} | ||
|
||
// Start generating the code. | ||
outputPkg, err := inferGeneratedGoCodePackage(*cmdCfg) | ||
if err != nil { | ||
return fmt.Errorf("could not infer the package for the go generated code: %w", err) | ||
} | ||
logger.InfoContext(ctx, "Go generated code package inferred", "module", outputPkg) | ||
|
||
logger.InfoContext(ctx, "Generating Go clients code...") | ||
err = NewGenClientRunner(logger, cmdCfg.CodeGenPath). | ||
WithWatch(). | ||
WithBoilerplate(boilerplatePath). | ||
WithOutputPkg(outputPkg). | ||
WithOutputDir(cmdCfg.APIsOutPath). | ||
WithAPIsPath(cmdCfg.APIsPath).Run(ctx) | ||
if err != nil { | ||
return fmt.Errorf("could not generate Go clients code: %w", err) | ||
} | ||
|
||
logger.InfoContext(ctx, "Generating Go helper types code...") | ||
err = NewGenHelperstRunner(logger, cmdCfg.CodeGenPath). | ||
WithBoilerplate(boilerplatePath). | ||
WithAPIsPath(cmdCfg.APIsPath).Run(ctx) | ||
if err != nil { | ||
return fmt.Errorf("could not generate Go types code: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func getGoModule() (string, error) { | ||
// Check if we are in the Go project root and ge goMod. | ||
projectRootFS := os.DirFS(".") | ||
goMod, err := fs.ReadFile(projectRootFS, "go.mod") | ||
if err != nil { | ||
return "", fmt.Errorf(`error while reading "go.mod", you should execute this app from the project root: %w`, err) | ||
} | ||
|
||
match := goModuleRegexp.FindAllStringSubmatch(string(goMod), 1) | ||
if len(match) < 1 || len(match[0]) < 2 { | ||
return "", fmt.Errorf(`could not find module declaration on "go.mod"`) | ||
} | ||
packageName := match[0][1] | ||
|
||
return packageName, nil | ||
} | ||
|
||
func inferGeneratedGoCodePackage(cmdCfg CmdConfig) (string, error) { | ||
goModule, err := getGoModule() | ||
if err != nil { | ||
return "", fmt.Errorf("could not infer go project package: %w", err) | ||
} | ||
|
||
apisPutPath := strings.TrimSuffix(cmdCfg.APIsOutPath, "/") + "/" // Ensure slash. | ||
apisOutPkg := filepath.Dir(apisPutPath) | ||
if apisOutPkg != "." && apisOutPkg != "" { | ||
return goModule + "/" + apisOutPkg, nil | ||
} | ||
|
||
// No extra package, out code will be in the module root. | ||
return goModule, nil | ||
|
||
} | ||
|
||
func main() { | ||
err := run(context.Background(), os.Args, os.Stdout, os.Stderr) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Error: %s\n", err) | ||
os.Exit(1) | ||
} | ||
} |
Oops, something went wrong.