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
34 changes: 20 additions & 14 deletions internal/cli/alpha/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,27 @@ func NewScaffoldCommand() *cobra.Command {
scaffoldCmd := &cobra.Command{
Use: "generate",
Short: "Re-scaffold a Kubebuilder project from its PROJECT file",
Long: `The 'generate' command re-creates a Kubebuilder project scaffold based on the configuration
defined in the PROJECT file, using the latest installed Kubebuilder version and plugins.
Long: `Re-generate a Kubebuilder project scaffold based on the PROJECT file configuration.

This is helpful for migrating projects to a newer Kubebuilder layout or plugin version (e.g., v3 to v4)
as update your project from any previous version to the current one.
This command uses the latest installed Kubebuilder version and plugins to regenerate
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That is not accurate. The command will use the code of the CLI version installed/used.
I think the original text here seems more accurate.

the entire project structure.

If no output directory is provided, the current working directory will be cleaned (except .git and PROJECT).`,
This is helpful for migrating projects to newer Kubebuilder
layouts or plugin version (e.g., v3 to v4)

This command deletes all files except .git/ and PROJECT before regenerating in-place when
--output-dir is not specified.

The PROJECT file must exist and contain valid plugin configuration.`,
Example: `
# **WARNING**(will delete all files to allow the re-scaffold except .git and PROJECT)
# Re-scaffold the project in-place
# Re-scaffold in-place (WARNING: deletes all files except .git and PROJECT)
kubebuilder alpha generate

# Re-scaffold the project from ./test into ./my-output
kubebuilder alpha generate --input-dir="./path/to/project" --output-dir="./my-output"
# Re-scaffold to a different directory (safe, preserves original)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I do not think we need the info under ()

kubebuilder alpha generate --output-dir="./regenerated"

# Re-scaffold from a specific project directory to a different directory
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The original # Re-scaffold the project from ./test into ./my-output direct form seems more intuitive and aligned with kubebuilder/k8s docs style.

kubebuilder alpha generate --input-dir="./my-project" --output-dir="./my-project-v4"
`,
PreRunE: func(_ *cobra.Command, _ []string) error {
return opts.Validate()
Expand All @@ -70,13 +77,12 @@ If no output directory is provided, the current working directory will be cleane
}

scaffoldCmd.Flags().StringVar(&opts.InputDir, "input-dir", "",
"Path to the directory containing the PROJECT file. "+
"Defaults to the current working directory. WARNING: delete existing files (except .git and PROJECT).")
"path to directory containing the PROJECT file (default: current directory). "+
Copy link
Copy Markdown
Member

@camilamacedo86 camilamacedo86 Apr 7, 2026

Choose a reason for hiding this comment

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

That is a good point, what is the best: Should start all with Upper case or not?
The change here shows make it better 👍

"WARNING: if --output-dir is not set, all files except .git/ and PROJECT will be deleted")

scaffoldCmd.Flags().StringVar(&opts.OutputDir, "output-dir", "",
"Directory where the new project scaffold will be written. "+
"If unset, re-scaffolding occurs in-place "+
"and will delete existing files (except .git and PROJECT).")
"path to directory where regenerated scaffold will be written. "+
"If not set, regenerates in-place and deletes existing files (except .git/ and PROJECT)")

return scaffoldCmd
}
35 changes: 18 additions & 17 deletions internal/cli/alpha/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,38 +145,39 @@ Defaults:
}

updateCmd.Flags().StringVar(&opts.FromVersion, "from-version", "",
"binary release version to upgrade from. Should match the version used to init the project and be "+
"a valid release version, e.g., v4.6.0. If not set, it defaults to the version specified in the PROJECT file.")
"Kubebuilder version to upgrade from (e.g., v4.6.0). Should match version used to initialize project "+
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think clarify that from. == init version is important. WDYT?

"If not set, uses version from PROJECT file")
updateCmd.Flags().StringVar(&opts.ToVersion, "to-version", "",
"binary release version to upgrade to. Should be a valid release version, e.g., v4.7.0. "+
"If not set, it defaults to the latest release version available in the project repository.")
"Kubebuilder version to upgrade to (e.g., v4.7.0). "+
"If not set, uses latest version available in the project repository")
updateCmd.Flags().StringVar(&opts.FromBranch, "from-branch", "",
"Git branch to use as current state of the project for the update.")
"Git branch containing current project state (default: main)")
updateCmd.Flags().BoolVar(&opts.Force, "force", false,
"Force the update even if conflicts occur. Conflicted files will include conflict markers, and a "+
"commit will be created automatically. Ideal for automation (e.g., cronjobs, CI).")
"if true, commit even with conflicts (adds conflict markers). "+
"Ideal for automation (CI/CD pipelines, cronjobs)")
updateCmd.Flags().BoolVar(&opts.ShowCommits, "show-commits", false,
"If set, the update will keep the full history instead of squashing into a single commit.")
"if true, keep full commit history instead of squashing. "+
"Cannot be used with --restore-path")
updateCmd.Flags().StringArrayVar(&opts.RestorePath, "restore-path", nil,
"Paths to preserve from the base branch (repeatable). Not supported with --show-commits.")
"paths to preserve from base branch (repeatable, e.g., --restore-path .github/workflows). "+
"Cannot be used with --show-commits")
updateCmd.Flags().StringVar(&opts.OutputBranch, "output-branch", "",
"Override the default output branch name (default: kubebuilder-update-from-<from-version>-to-<to-version>).")
updateCmd.Flags().BoolVar(&opts.Push, "push", false,
"Push the output branch to the remote repository after the update.")
"if true, push output branch to origin after update")
updateCmd.Flags().StringVar(&opts.CommitMessage, "merge-message", "",
"Custom commit message for successful merges (no conflicts). "+
"Defaults to 'chore(kubebuilder): update scaffold <from> -> <to>'.")
"custom commit message for clean merges (no conflicts)"+
"(default: 'chore(kubebuilder): update scaffold <from> -> <to>')")
updateCmd.Flags().StringVar(&opts.CommitMessageConflict, "conflict-message", "",
"Custom commit message for merges with conflicts. "+
"Defaults to 'chore(kubebuilder): (:warning: manual conflict resolution required) update scaffold <from> -> <to>'.")
"custom commit message for merges with conflicts. "+
"(default: 'chore(kubebuilder): (:warning: manual conflict resolution required) update scaffold <from> -> <to>')")
updateCmd.Flags().BoolVar(&opts.OpenGhIssue, "open-gh-issue", false,
"Create a GitHub issue with a pre-filled checklist and compare link after the update completes (requires `gh`).")
"if true, create GitHub issue with a pre-filled checklist and compare link (requires gh CLI)")
updateCmd.Flags().BoolVar(
&opts.UseGhModels,
"use-gh-models",
false,
"Generate and post an AI summary comment to the GitHub Issue using `gh models run`. "+
"Requires --open-gh-issue and GitHub CLI (`gh`) with the `gh-models` extension.")
"if true, add AI-generated summary comment to GitHub issue (requires --open-gh-issue and gh CLI with gh-models extension)")
updateCmd.Flags().StringArrayVar(
&gitCfg,
"git-config",
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (c CLI) newRootCmd() *cobra.Command {
cmd.PersistentFlags().StringSlice(pluginsFlag, nil, "plugin keys to be used for this subcommand execution")

// Register --project-version on the root command so that it shows up in help.
cmd.Flags().String(projectVersionFlag, c.defaultProjectVersion.String(), "project version")
cmd.Flags().String(projectVersionFlag, c.defaultProjectVersion.String(), "project version to scaffold (default: latest stable)")

// As the root command will be used to shot the help message under some error conditions,
// like during plugin resolving, we need to allow unknown flags to prevent parsing errors.
Expand Down
24 changes: 12 additions & 12 deletions pkg/plugins/golang/v4/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ make generate will be run.
%[1]s create api --group ship --version v1beta1 --kind Frigate

# Edit the API Scheme

nano api/v1beta1/frigate_types.go

# Edit the Controller
Expand All @@ -91,34 +90,35 @@ make generate will be run.
}

func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after scaffolding (default: true)")

fs.BoolVar(&p.force, "force", false,
"attempt to create resource even if it already exists")
"if true, attempt to create resource even if it already exists")

p.options = &goPlugin.Options{}

fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
fs.StringVar(&p.options.Plural, "plural", "", "specify irregular plural form of the resource (e.g., 'mice' for 'Mouse')")

fs.BoolVar(&p.options.DoAPI, "resource", true,
"if set, generate the resource without prompting the user")
"if true, scaffold the API resource types without prompting (default: true)")
p.resourceFlag = fs.Lookup("resource")
fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")
fs.BoolVar(&p.options.Namespaced, "namespaced", true, "if true, resource is namespace-scoped rather than cluster-scoped (default: true)")

fs.BoolVar(&p.options.DoController, "controller", true,
"if set, generate the controller without prompting the user")
"if true, scaffold the controller without prompting (default: true)")
p.controllerFlag = fs.Lookup("controller")

fs.StringVar(&p.options.ExternalAPIPath, "external-api-path", "",
"Specify the Go package import path for the external API. This is used to scaffold controllers for resources "+
"defined outside this project (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1).")
"specify Go package import path for external API (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1). "+
"Used to scaffold controllers for external types with --resource=false")

fs.StringVar(&p.options.ExternalAPIDomain, "external-api-domain", "",
"Specify the domain name for the external API. This domain is used to generate accurate RBAC "+
"markers and permissions for the external resources (e.g., cert-manager.io).")
"specify domain for external API (e.g., cert-manager.io). "+
"Used to generate accurate RBAC markers for external resources. Requires --external-api-path")

fs.StringVar(&p.options.ExternalAPIModule, "external-api-module", "",
"external API module with optional version (e.g., github.com/cert-manager/cert-manager@v1.18.2)")
"external API Go module with optional version (e.g., github.com/cert-manager/cert-manager@v1.18.2). "+
"Requires --external-api-path")
}

func (p *createAPISubcommand) InjectConfig(c config.Config) error {
Expand Down
80 changes: 49 additions & 31 deletions pkg/plugins/golang/v4/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,57 +43,75 @@ type editSubcommand struct {
func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
subcmdMeta.Description = `Edit project configuration to enable or disable layout settings.

Multigroup (--multigroup):
Enable or disable multi-group layout.
Changes API structure: api/<version>/ becomes api/<group>/<version>/
Automatic: Updates PROJECT file, future APIs use new structure
Manual: Move existing API files, update import paths in controllers
More info: https://book.kubebuilder.io/migration/multi-group.html

Namespaced (--namespaced):
Enable or disable namespace-scoped deployment.
Manager watches one or more specific namespaces vs all namespaces.
Namespaces to watch are configured via WATCH_NAMESPACE environment variable.
Automatic: Updates PROJECT file, scaffolds Role/RoleBinding, uses --force to regenerate manager.yaml
Manual: Add namespace= to RBAC markers in existing controllers, update cmd/main.go, run 'make manifests'
More info: https://book.kubebuilder.io/migration/namespace-scoped.html

WARNING - Webhooks and Namespace-Scoped Mode:
Webhooks remain cluster-scoped even in namespace-scoped mode.
The manager cache is restricted to WATCH_NAMESPACE, but webhooks receive requests
from ALL namespaces. You must configure namespaceSelector or objectSelector to align
webhook scope with the cache.

Force (--force):
Overwrite existing scaffolded files to apply configuration changes.
Example: With --namespaced, regenerates config/manager/manager.yaml to add WATCH_NAMESPACE env var.
Warning: This overwrites default scaffold files; manual changes in those files may be lost.
This command modifies the PROJECT file and optionally regenerates scaffolded files.
Use this to change layout settings or add optional plugins after project initialization.


Plugin flags:
--plugins: Comma-separated list of plugins to add to the project
Plugins are saved to the PROJECT file and used in future operations
Run 'kubebuilder edit --plugins --help' to see available plugins

Layout flags:
--multigroup: Enable/Disable multigroup layout to organize APIs by group
Scaffolds APIs in api/<group>/<version>/ instead of api/<version>/
Useful when managing multiple API groups (e.g., batch, apps, crew)
Automatic: Updates PROJECT file; future APIs use new structure
Manual: Move existing API files, update import paths in controllers
More info: https://book.kubebuilder.io/migration/multi-group.html

--namespaced: Enable/Disable namespace-scoped deployment instead of cluster-scoped
Manager watches one or more specific namespaces instead of all namespaces
Namespaces to watch are configured via WATCH_NAMESPACE environment variable
Uses Role/RoleBinding instead of ClusterRole/ClusterRoleBinding
Automatic: Updates PROJECT file, scaffolds Role/RoleBinding
Manual: Add namespace= to RBAC markers in controllers, update cmd/main.go, run 'make manifests'
More info: https://book.kubebuilder.io/migration/namespace-scoped.html

WARNING - Webhooks and Namespace-Scoped Mode:
Webhooks remain cluster-scoped even in namespace-scoped mode.
The manager cache is restricted to WATCH_NAMESPACE, but webhooks receive requests
from ALL namespaces. Configure namespaceSelector or objectSelector to align
webhook scope with the cache.

--force: Overwrite existing scaffolded files to apply configuration changes
Example: With --namespaced, regenerates config/manager/manager.yaml to add WATCH_NAMESPACE
Warning: Overwrites default scaffold files; manual changes may be lost

Note: To add optional plugins after initialization, use 'kubebuilder edit --plugins <plugin-name>'.
Run 'kubebuilder edit --plugins --help' to see available plugins.
`
subcmdMeta.Examples = fmt.Sprintf(` # Enable multigroup layout
%[1]s edit --multigroup

# Enable namespace-scoped permissions
# Enable namespace-scoped deployment
%[1]s edit --namespaced

# Enable with automatic file regeneration
# Enable namespace-scoped with automatic file regeneration
%[1]s edit --namespaced --force

# Disable multigroup layout
%[1]s edit --multigroup=false

# Enable/disable multiple settings
# Enable/Disable multiple settings at once
%[1]s edit --multigroup --namespaced --force

# Add Helm plugin to existing project
%[1]s edit --plugins helm/v2-alpha

# Add multiple plugins
%[1]s edit --plugins grafana/v1-alpha,autoupdate/v1-alpha
`, cliMeta.CommandName)
}

func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {
p.fs = fs
fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout")
fs.BoolVar(&p.namespaced, "namespaced", false, "enable or disable namespace-scoped deployment")
fs.BoolVar(&p.force, "force", false, "overwrite scaffolded files to apply changes (manual edits may be lost)")
fs.BoolVar(&p.multigroup, "multigroup", false,
"enable or disable multigroup layout (api/<group>/<version>/)")
fs.BoolVar(&p.namespaced, "namespaced", false,
"enable or disable namespace-scoped deployment (default: cluster-scoped)")
fs.BoolVar(&p.force, "force", false,
"overwrite scaffolded files to apply configuration changes (manual edits may be lost)")
}

func (p *editSubcommand) InjectConfig(c config.Config) error {
Expand Down
28 changes: 16 additions & 12 deletions pkg/plugins/golang/v4/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,24 @@ type initSubcommand struct {
func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
p.commandName = cliMeta.CommandName

subcmdMeta.Description = `Initialize a new project including the following files:
- a "go.mod" with project dependencies
- a "PROJECT" file that stores project configuration
- a "Makefile" with several useful make targets for the project
- several YAML files for project deployment under the "config" directory
- a "cmd/main.go" file that creates the manager that will run the project controllers
subcmdMeta.Description = `Initialize a new project within the current directory. Following files will be generated automatically:
- go.mod: Go module with project dependencies
- PROJECT: file that stores project configuration
- Makefile: provides useful make targets for the project
- config/: Kubernetes manifests for deployment
- cmd/main.go: controller manager entry point
- Dockerfile: build controller manager container image
- test/: unit tests for the project
- hack/: contains licensing boilerplate.


Required flags:
--domain: Domain for your APIs (e.g., example.org creates crew.example.org for API groups)

Configuration flags:
--repo: Go module path (e.g., github.com/user/repo); auto-detected if not provided
--owner: Owner name for copyright license headers
--license: License to use (apache2 or none, default: apache2)
--license: License to use (apache2 | none; default "apache2")

Plugin flags:
--plugins: Comma-separated list of plugins to use (default: go/v4)
Expand Down Expand Up @@ -120,21 +124,21 @@ Note: Layout settings can be changed later with 'kubebuilder edit'.

func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {
fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check",
false, "skip Go version check")
false, "if true, skip compatibility check of Kubebuilder plugin supported version vs installed Go version (default: false)")

// dependency args
fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "download dependencies after scaffolding")
fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "if true, download Go dependencies after scaffolding (default: true)")

// boilerplate args
fs.StringVar(&p.license, "license", "apache2",
"license header to use (apache2 or none)")
fs.StringVar(&p.owner, "owner", "", "copyright owner for license headers")
"license to use for boilerplate headers (apache2 or none)")
fs.StringVar(&p.owner, "owner", "", "owner name for copyright license headers")

// project args
fs.StringVar(&p.repo, "repo", "", "Go module name (e.g., github.com/user/repo); "+
"auto-detected from current directory if not provided")
fs.BoolVar(&p.multigroup, "multigroup", false,
"enable multigroup layout (organize APIs by group)")
"enable multigroup layout to organize APIs by group (api/<group>/<version>/)")
fs.BoolVar(&p.namespaced, "namespaced", false,
"enable namespace-scoped deployment (default: cluster-scoped)")
}
Expand Down
Loading