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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ docs/book/src/docs
/testdata/**legacy**

## Skip testdata files that generate by tests using TestContext
**/e2e-*/**
e2e-*
70 changes: 67 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,21 @@ Plugins implement interfaces from `pkg/plugin/`:
- `Init` - project initialization (`kubebuilder init`)
- `CreateAPI` - API creation (`kubebuilder create api`)
- `CreateWebhook` - webhook creation (`kubebuilder create webhook`)
- `DeleteAPI` - API deletion (`kubebuilder delete api`)
- `DeleteWebhook` - webhook deletion (`kubebuilder delete webhook`)
- `Edit` - post-init modifications (`kubebuilder edit`)
- `Bundle` - groups multiple plugins

**Delete = Undo of Create:**

Each plugin's delete implementation MUST undo exactly what its create implementation did:
- `go/v4`: Removes Go code (API types, controllers, main.go imports/setup, suite_test.go)
- `kustomize/v2`: Removes manifests (samples, RBAC, CRD kustomization entries)
- `deploy-image/v1-alpha`: Removes plugin metadata from PROJECT file
- When plugins run in chain (e.g., `--plugins deploy-image/v1-alpha`), both layout and additional plugins execute

**Integration tests MUST verify**: `state_before_create == state_after_delete`

**Plugin Bundles:**

Default bundle (`pkg/cli/init.go`): `go.kubebuilder.io/v4` + `kustomize.common.kubebuilder.io/v2`
Expand Down Expand Up @@ -249,7 +261,8 @@ Controllers implement `Reconcile(ctx, req) (ctrl.Result, error)`:
- **Requeue on pending work** - Return `ctrl.Result{Requeue: true}`

### Testing Pattern
E2E tests use `utils.TestContext` from `test/e2e/utils/test_context.go`:

**Integration Tests** use `utils.TestContext` from `test/e2e/utils/test_context.go`:

```go
ctx := utils.NewTestContext(util.KubebuilderBinName, "GO111MODULE=on")
Expand All @@ -259,19 +272,70 @@ ctx.Make("build", "test")
ctx.LoadImageToKindCluster()
```

**Baseline Testing (Required for Delete):**

Delete integration tests MUST verify exact state restoration:

```go
It("should restore exact state after delete", func() {
mainBefore, _ := os.ReadFile("cmd/main.go")
ctx.CreateAPI(...)
ctx.DeleteAPI(..., "-y")
mainAfter, _ := os.ReadFile("cmd/main.go")
Expect(mainAfter).To(Equal(mainBefore)) // Exact match required
})
```

## CLI Reference

After `make install`:

```bash
# Initialize project
kubebuilder init --domain example.com --repo github.com/example/myproject

# Create resources
kubebuilder create api --group batch --version v1 --kind CronJob
kubebuilder create webhook --group batch --version v1 --kind CronJob
kubebuilder edit --plugins=helm/v2-alpha

# Delete resources (complete undo of create)
kubebuilder delete api --group batch --version v1 --kind CronJob
kubebuilder delete webhook --group batch --version v1 --kind CronJob --defaulting

# Delete with plugin chain
kubebuilder delete api --group app --version v1 --kind Cache --plugins deploy-image/v1-alpha

# Delete optional plugin features
kubebuilder delete --plugins helm/v2-alpha
kubebuilder delete --plugins grafana/v1-alpha

# Edit project
kubebuilder edit --plugins helm/v2-alpha

# Alpha commands
kubebuilder alpha generate # Experimental: generate from PROJECT file
kubebuilder alpha update # Experimental: update to latest plugin versions
```

## Implementing Delete

**Rule**: If you add a `create` command, you MUST add the corresponding `delete` command.

**Key Principle**: Each plugin undoes ONLY what it created. When plugins run in chain (default: `go/v4` + `kustomize/v2`), each cleans its own artifacts:
- `go/v4` → removes Go code (types, controllers, main.go, suite_test.go)
- `kustomize/v2` → removes manifests (samples, RBAC, CRD entries)
- Additional plugins → remove their metadata from PROJECT file

**Shared Resources**: Imports/code used by multiple resources are preserved until the last one is deleted (e.g., `appv1` import kept while any app/v1 API exists).

**Integration Test**: Add `delete_integration_test.go` with baseline verification:
```go
baseline := captureState()
createResource()
deleteResource("-y")
Expect(currentState()).To(Equal(baseline)) // Exact match required
```

## Common Patterns

### Code Style
Expand Down Expand Up @@ -334,7 +398,7 @@ log.Error(err, "Failed to create Pod", "name", name)
- **Integration tests** (`*_integration_test.go` in `pkg/`) - Test multiple components together without cluster
- Must have `//go:build integration` tag at the top
- May create temp dirs, download binaries, or scaffold files
- Examples: alpha update, grafana scaffolding, helm chart generation
- **Delete tests**: MUST use baseline pattern (verify before_create == after_delete)
- **E2E tests** (`test/e2e/`) - **ONLY** for tests requiring a Kubernetes cluster (KIND)
- `v4/plugin_cluster_test.go` - Test v4 plugin deployment
- `helm/plugin_cluster_test.go` - Test Helm chart deployment
Expand Down
6 changes: 6 additions & 0 deletions docs/book/src/plugins/available/autoupdate-v1-alpha.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ kubebuilder edit --plugins="autoupdate/v1-alpha"
kubebuilder init --plugins=go/v4,autoupdate/v1-alpha
```

- To remove the auto-update workflow from your project:

```shell
kubebuilder delete --plugins autoupdate/v1-alpha
```

### Optional: GitHub Models AI Summary

By default, the workflow works without GitHub Models to avoid permission errors.
Expand Down
20 changes: 18 additions & 2 deletions docs/book/src/plugins/available/deploy-image-plugin-v1-alpha.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,25 @@ export MEMCACHED_IMAGE="memcached:1.4.36-alpine"

## Subcommands

The `deploy-image` plugin includes the following subcommand:
The `deploy-image` plugin implements:

- `create api`: Use this command to scaffold the API and controller code to manage the container image.
- `create api` - Scaffolds the API and controller code to manage the container image

### Deleting APIs Created with Deploy-Image

To delete an API created with deploy-image, you **must** include the plugin flag:

```sh
kubebuilder delete api --group <group> --version <version> --kind <kind> \
--plugins=deploy-image/v1-alpha
```

If you forget the `--plugins` flag, you'll receive an error message showing the exact command to use.

The delete operation removes:
- API and controller files (via go/v4 plugin in the chain)
- Deploy-image metadata from the PROJECT file
- Kustomize manifests (samples, RBAC)

## Affected files

Expand Down
2 changes: 2 additions & 0 deletions docs/book/src/plugins/available/go-v4-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ kubebuilder init --domain tutorial.kubebuilder.io --repo tutorial.kubebuilder.io
- Edit - `kubebuilder edit [OPTIONS]`
- Create API - `kubebuilder create api [OPTIONS]`
- Create Webhook - `kubebuilder create webhook [OPTIONS]`
- Delete API - `kubebuilder delete api [OPTIONS]`
- Delete Webhook - `kubebuilder delete webhook [OPTIONS]`

## Further resources

Expand Down
17 changes: 15 additions & 2 deletions docs/book/src/plugins/available/grafana-v1-alpha.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,24 @@ The Grafana plugin is attached to the `init` subcommand and the `edit` subcomman
kubebuilder init --plugins grafana.kubebuilder.io/v1-alpha

# Enable grafana plugin to an existing project
kubebuilder edit --plugins grafana.kubebuilder.io/v1-alpha
kubebuilder edit --plugins grafana/v1-alpha

# Remove grafana dashboards from project
kubebuilder delete --plugins grafana/v1-alpha
```

The plugin will create a new directory and scaffold the JSON files under it (i.e. `grafana/controller-runtime-metrics.json`).

### Removing Grafana Dashboards

To remove Grafana manifests from your project:

```sh
kubebuilder delete --plugins grafana/v1-alpha
```

This removes the `grafana/` directory and all dashboard files.

#### Show case:

See an example of how to use the plugin in your project:
Expand Down Expand Up @@ -203,7 +216,7 @@ customMetrics:

#### Scaffold Manifest

Once `config.yaml` is configured, you can run `kubebuilder edit --plugins grafana.kubebuilder.io/v1-alpha` again.
Once `config.yaml` is configured, you can run `kubebuilder edit --plugins grafana/v1-alpha` again.
This time, the plugin will generate `grafana/custom-metrics/custom-metrics-dashboard.json`, which can be imported to Grafana UI.

#### Show case:
Expand Down
13 changes: 13 additions & 0 deletions docs/book/src/plugins/available/helm-v2-alpha.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ kubebuilder edit --plugins=helm/v2-alpha \
--output-dir=helm-charts
```

### Removing Helm Charts

To remove the Helm chart from your project:

```shell
kubebuilder delete --plugins helm/v2-alpha
```

This removes:
- `dist/chart/` directory
- `.github/workflows/test-chart.yml`
- Plugin configuration from PROJECT file

## Chart Structure

The plugin creates a chart layout that matches your `config/`:
Expand Down
14 changes: 9 additions & 5 deletions docs/book/src/plugins/available/kustomize-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,18 @@ The kustomize plugin implements the following subcommands:

* init (`$ kubebuilder init [OPTIONS]`)
* create api (`$ kubebuilder create api [OPTIONS]`)
* create webhook (`$ kubebuilder create api [OPTIONS]`)
* create webhook (`$ kubebuilder create webhook [OPTIONS]`)
* delete api (`$ kubebuilder delete api [OPTIONS]`)
* delete webhook (`$ kubebuilder delete webhook [OPTIONS]`)

<aside class="note">
<h1>Create API and Webhook</h1>
<h1>Create and Delete</h1>

The implementation for the `create api` subcommand scaffolds the kustomize
manifests specific to each API. See more [here][kustomize-create-api].
The same applies to `create webhook`.
The `create api` subcommand scaffolds kustomize manifests specific to each API
(CRD bases, samples, RBAC). See more [here][kustomize-create-api].

The `delete api` subcommand removes these manifests when the API is deleted.
The same applies to `create webhook` and `delete webhook`.

</aside>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ of the following CLI commands:
- `init`: Initializes the project structure.
- `create api`: Scaffolds a new API and controller.
- `create webhook`: Scaffolds a new webhook.
- `edit`: edit the project structure.
- `delete api`: Deletes an API and its associated files.
- `delete webhook`: Deletes a webhook and its associated files.
- `edit`: Updates the project structure.

Here’s an example of using the `init` subcommand with a custom plugin:

Expand Down
2 changes: 2 additions & 0 deletions docs/book/src/plugins/extending/external-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ External plugins can support the following Kubebuilder subcommands:
- `init`: Project initialization
- `create api`: Scaffold Kubernetes API definitions
- `create webhook`: Scaffold Kubernetes webhooks
- `delete api`: Delete Kubernetes API definitions and associated files
- `delete webhook`: Delete Kubernetes webhooks and associated files
- `edit`: Update project configuration

**Optional subcommands for enhanced user experience:**
Expand Down
4 changes: 4 additions & 0 deletions docs/book/src/plugins/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ kubebuilder create api --plugins=pluginA,pluginB,pluginC
OR
kubebuilder create webhook --plugins=pluginA,pluginB,pluginC
OR
kubebuilder delete api --plugins=pluginA,pluginB,pluginC
OR
kubebuilder delete webhook --plugins=pluginA,pluginB,pluginC
OR
kubebuilder edit --plugins=pluginA,pluginB,pluginC
```

Expand Down
38 changes: 37 additions & 1 deletion pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
log "log/slog"
"os"
"slices"
"strings"

"github.com/spf13/afero"
Expand Down Expand Up @@ -361,6 +362,7 @@ func (c *CLI) getInfoFromFlags(hasConfigFile bool) error {
}

// If any plugin key was provided, replace those from the project configuration file
// Exception: for delete commands, APPEND to layout plugins instead of replacing
if pluginKeys, err := fs.GetStringSlice(pluginsFlag); err != nil {
return fmt.Errorf("invalid flag %q: %w", pluginsFlag, err)
} else if len(pluginKeys) != 0 {
Expand All @@ -372,7 +374,24 @@ func (c *CLI) getInfoFromFlags(hasConfigFile bool) error {
}
}

c.pluginKeys = pluginKeys
// For delete commands, if we have layout plugins from PROJECT, keep them
// and append the user-specified plugins (avoiding duplicates).
// Extract the command name from positional args (after flags are parsed).
isDeleteCommand := c.isDeleteCommand(fs.Args())

if isDeleteCommand && len(c.pluginKeys) > 0 {
// Append user plugins to layout plugins (deduplicate)
combined := append([]string{}, c.pluginKeys...)
for _, userPlugin := range pluginKeys {
found := slices.Contains(combined, userPlugin)
if !found {
combined = append(combined, userPlugin)
}
}
c.pluginKeys = combined
} else {
c.pluginKeys = pluginKeys
}
}

// If the project version flag was accepted but not provided keep the empty version and try to resolve it later,
Expand All @@ -386,6 +405,14 @@ func (c *CLI) getInfoFromFlags(hasConfigFile bool) error {
return nil
}

// isDeleteCommand checks if the command being executed is a delete command
// by examining the positional arguments (excluding flags).
func (c *CLI) isDeleteCommand(args []string) bool {
// Args contains positional arguments after pflag parsing (flags are removed).
// Check if the first positional argument is "delete".
return len(args) > 0 && args[0] == "delete"
}

// getInfoFromDefaults obtains the plugin keys, and maybe the project version from the default values
func (c *CLI) getInfoFromDefaults() {
// Should not use default values if a plugin was already set
Expand Down Expand Up @@ -519,6 +546,15 @@ func (c *CLI) addSubcommands() {
c.cmd.AddCommand(createCmd)
}

// kubebuilder delete
deleteCmd := c.newDeleteCmd()
// kubebuilder delete api
deleteCmd.AddCommand(c.newDeleteAPICmd())
deleteCmd.AddCommand(c.newDeleteWebhookCmd())
if deleteCmd.HasSubCommands() {
c.cmd.AddCommand(deleteCmd)
}

// kubebuilder edit
c.cmd.AddCommand(c.newEditCmd())

Expand Down
Loading
Loading