Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pkg/crd/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"go/ast"
"go/types"
"log/slog"
"sort"
"strings"

Expand Down Expand Up @@ -124,6 +125,12 @@ func transformPreserveUnknownFields(value bool) func(map[string]interface{}) err
}

func (g Generator) Generate(ctx *genall.GenerationContext) error {
// Extract logger and use a discard logger if nil to avoid repeated nil checks
logger := ctx.Logger
if logger == nil {
logger = slog.New(slog.DiscardHandler)
}

parser := &Parser{
Collector: ctx.Collector,
Checker: ctx.Checker,
Expand All @@ -134,30 +141,44 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta,
}

logger.Debug("starting CRD generation", "ignoreUnexported", parser.IgnoreUnexportedFields, "allowDangerous", parser.AllowDangerousTypes)

AddKnownTypes(parser)
for _, root := range ctx.Roots {
parser.NeedPackage(root)
logger.Debug("processing package", "package", root.PkgPath)
}

metav1Pkg := FindMetav1(ctx.Roots)
if metav1Pkg == nil {
// no objects in the roots, since nothing imported metav1
logger.Debug("no metav1 package found in roots, no CRDs to generate")
return nil
}

logger.Debug("found metav1 package", "package", metav1Pkg.PkgPath)

// TODO: allow selecting a specific object
kubeKinds := FindKubeKinds(parser, metav1Pkg)
if len(kubeKinds) == 0 {
// no objects in the roots
logger.Debug("no Kubernetes kinds found in packages")
return nil
}

logger.Info("found Kubernetes kinds for CRD generation", "count", len(kubeKinds))
for _, kind := range kubeKinds {
logger.Debug("processing Kubernetes kind", "group", kind.Group, "kind", kind.Kind)
}

crdVersions := g.CRDVersions

if len(crdVersions) == 0 {
crdVersions = []string{defaultVersion}
}

logger.Debug("using CRD versions", "versions", crdVersions)

var headerText string

if g.HeaderFile != "" {
Expand All @@ -166,6 +187,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
return err
}
headerText = string(headerBytes)
logger.Debug("loaded header file", "file", g.HeaderFile, "size", len(headerBytes))
}
headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year)

Expand All @@ -178,6 +200,8 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
}

for _, groupKind := range kubeKinds {
logger.Debug("generating CRD", "group", groupKind.Group, "kind", groupKind.Kind)

parser.NeedCRDFor(groupKind, g.MaxDescLen)
crdRaw := parser.CustomResourceDefinitions[groupKind]
addAttribution(&crdRaw)
Expand All @@ -202,9 +226,14 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
}

logger.Debug("writing CRD file", "filename", fileName, "group", groupKind.Group, "kind", groupKind.Kind)

if err := ctx.WriteYAML(fileName, headerText, []interface{}{crd}, yamlOpts...); err != nil {
return err
}

logger.Info("generated CRD", "filename", fileName, "group", groupKind.Group, "kind", groupKind.Kind)
}
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/genall/genall.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"os"

"golang.org/x/tools/go/packages"
Expand Down Expand Up @@ -101,6 +102,9 @@ type Runtime struct {
OutputRules OutputRules
// ErrorWriter defines where to write error messages.
ErrorWriter io.Writer
// LogLevel sets the logging level for generator operations.
// Defaults to slog.LevelInfo if not specified.
LogLevel slog.Level
}

// GenerationContext defines the common information needed for each Generator
Expand All @@ -117,6 +121,8 @@ type GenerationContext struct {
// InputRule describes how to load associated boilerplate artifacts.
// It should *not* be used to load source files.
InputRule
// Logger is the logger for verbose output. If nil, logging is disabled.
Logger *slog.Logger
}

// WriteYAMLOptions implements the Options Pattern for WriteYAML.
Expand Down Expand Up @@ -261,17 +267,37 @@ func (r *Runtime) Run() bool {
return true
}

// Set up logging based on log level setting
var logger *slog.Logger
Comment on lines +270 to +271
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not declare this inline with :=?


// Use the specified log level, defaulting to Info if not set
logLevel := r.LogLevel
if logLevel == 0 {
logLevel = slog.LevelInfo
}
Comment on lines +275 to +277
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't 0 and slog.LevelInfo equivalent?


logger = slog.New(slog.NewTextHandler(r.ErrorWriter, &slog.HandlerOptions{
Level: logLevel,
}))

if logLevel <= slog.LevelDebug {
logger.Info("debug logging enabled")
}

hadErrs := false
for _, gen := range r.Generators {
ctx := r.GenerationContext // make a shallow copy
ctx.OutputRule = r.OutputRules.ForGenerator(gen)
ctx.Logger = logger

// don't pass a typechecker to generators that don't provide a filter
// to avoid accidents
if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking {
ctx.Checker = nil
}

logger.Debug("running generator", "generator", fmt.Sprintf("%T", *gen))

if err := (*gen).Generate(&ctx); err != nil {
fmt.Fprintln(r.ErrorWriter, err)
hadErrs = true
Expand Down
52 changes: 51 additions & 1 deletion pkg/genall/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package genall

import (
"fmt"
"log/slog"
"strings"

"golang.org/x/tools/go/packages"
Expand All @@ -26,6 +27,7 @@ import (

var (
InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil)))
LogLevelMarker = markers.Must(markers.MakeDefinition("loglevel", markers.DescribesPackage, LogLevel("")))
)

// +controllertools:marker:generateHelp:category=""
Expand All @@ -35,16 +37,52 @@ var (
// Multiple paths can be specified using "{path1, path2, path3}".
type InputPaths []string

// +controllertools:marker:generateHelp:category=""

// LogLevel sets the logging level for generator operations.
// Valid values are "debug", "info", "warn", "error".
// Defaults to "info" if not specified.
type LogLevel string

const (
LogLevelDebug LogLevel = "debug"
LogLevelInfo LogLevel = "info"
LogLevelWarn LogLevel = "warn"
LogLevelError LogLevel = "error"
)

// ToSlogLevel converts the LogLevel to slog.Level
func (l LogLevel) ToSlogLevel() slog.Level {
switch l {
case LogLevelDebug:
return slog.LevelDebug
case LogLevelInfo:
return slog.LevelInfo
case LogLevelWarn:
return slog.LevelWarn
case LogLevelError:
return slog.LevelError
default:
return slog.LevelInfo // default fallback
}
}

// RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.
// At this point, that's just InputPaths.
// At this point, that's just InputPaths and LogLevel.
func RegisterOptionsMarkers(into *markers.Registry) error {
if err := into.Register(InputPathsMarker); err != nil {
return err
}
if err := into.Register(LogLevelMarker); err != nil {
return err
}
// NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen
if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp {
into.AddHelp(InputPathsMarker, helpGiver.Help())
}
if helpGiver, hasHelp := ((interface{})(LogLevel(""))).(HasHelp); hasHelp {
into.AddHelp(LogLevelMarker, helpGiver.Help())
}
return nil
}

Expand Down Expand Up @@ -90,6 +128,13 @@ func FromOptionsWithConfig(cfg *packages.Config, optionsRegistry *markers.Regist
return nil, err
}

// Set log level from the parsed options
if protoRt.LogLevel != "" {
genRuntime.LogLevel = protoRt.LogLevel.ToSlogLevel()
} else {
genRuntime.LogLevel = slog.LevelInfo // default
}

// attempt to figure out what the user wants without a lot of verbose specificity:
// if the user specifies a default rule, assume that they probably want to fall back
// to that. Otherwise, assume that they just wanted to customize one option from the
Expand Down Expand Up @@ -117,6 +162,7 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
ByGenerator: make(map[*Generator]OutputRule),
}
var paths []string
var logLevel LogLevel

// collect the generators first, so that we can key the output on the actual
// generator, which matters if there's settings in the gen object and it's not a pointer.
Expand Down Expand Up @@ -156,6 +202,8 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
continue
case InputPaths:
paths = append(paths, val...)
case LogLevel:
logLevel = val
default:
return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name)
}
Expand All @@ -176,6 +224,7 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot
Generators: gens,
OutputRules: rules,
GeneratorsByName: gensByName,
LogLevel: logLevel,
}, nil
}

Expand All @@ -186,6 +235,7 @@ type protoRuntime struct {
Generators Generators
OutputRules OutputRules
GeneratorsByName map[string]*Generator
LogLevel LogLevel
}

// splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule"
Expand Down
11 changes: 11 additions & 0 deletions pkg/genall/zz_generated.markerhelp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.