-
Notifications
You must be signed in to change notification settings - Fork 1.6k
📖 Add docs: custom markers for unsupported file extensions #5107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
51faf4e
f6f9a01
1d41656
40f6ec5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,218 @@ | ||||||||||||||||||||
| # Creating Custom Markers | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ## Overview | ||||||||||||||||||||
|
|
||||||||||||||||||||
| When using Kubebuilder as a library, you may need to scaffold files with extensions that aren't natively supported by Kubebuilder's marker system. This guide shows you how to create custom marker support for any file extension. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ## When to Use Custom Markers | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Custom markers are useful when: | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - You're building an external plugin for languages not natively supported by Kubebuilder | ||||||||||||||||||||
| - You want to scaffold files with custom extensions (`.rs`, `.java`, `.py`, `.tpl`, etc.) | ||||||||||||||||||||
| - You need scaffolding markers in non-Go files for your own use cases | ||||||||||||||||||||
| - Your file extensions aren't (and shouldn't be) part of the core `commentsByExt` map | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ## Understanding Markers | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Markers are special comments used by Kubebuilder for scaffolding purposes. They indicate where code can be inserted or modified. The core Kubebuilder marker system only supports `.go`, `.yaml`, and `.yml` files by default. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Example of a marker in a Go file: | ||||||||||||||||||||
| ```go | ||||||||||||||||||||
| // +kubebuilder:scaffold:imports | ||||||||||||||||||||
| ``` | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ## Implementation Example | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Here's how to implement custom markers for Rust files (`.rs`). This same pattern can be applied to any file extension. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ### Define Your Marker Type | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ```go | ||||||||||||||||||||
| // pkg/markers/rust.go | ||||||||||||||||||||
| package markers | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "fmt" | ||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||
| "strings" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const RustPluginPrefix = "+rust:scaffold:" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| type RustMarker struct { | ||||||||||||||||||||
| prefix string | ||||||||||||||||||||
| comment string | ||||||||||||||||||||
| value string | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func NewRustMarker(path string, value string) (RustMarker, error) { | ||||||||||||||||||||
| ext := filepath.Ext(path) | ||||||||||||||||||||
| if ext != ".rs" { | ||||||||||||||||||||
| return RustMarker{}, fmt.Errorf("expected .rs file, got %s", ext) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return RustMarker{ | ||||||||||||||||||||
| prefix: formatPrefix(RustPluginPrefix), | ||||||||||||||||||||
| comment: "//", | ||||||||||||||||||||
| value: value, | ||||||||||||||||||||
| }, nil | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func (m RustMarker) String() string { | ||||||||||||||||||||
| return m.comment + " " + m.prefix + m.value | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func formatPrefix(prefix string) string { | ||||||||||||||||||||
| trimmed := strings.TrimSpace(prefix) | ||||||||||||||||||||
| var builder strings.Builder | ||||||||||||||||||||
| if !strings.HasPrefix(trimmed, "+") { | ||||||||||||||||||||
| builder.WriteString("+") | ||||||||||||||||||||
| } | ||||||||||||||||||||
| builder.WriteString(trimmed) | ||||||||||||||||||||
| if !strings.HasSuffix(trimmed, ":") { | ||||||||||||||||||||
| builder.WriteString(":") | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return builder.String() | ||||||||||||||||||||
| } | ||||||||||||||||||||
| ``` | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ### Use in Template Generation | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ```go | ||||||||||||||||||||
| package templates | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "fmt" | ||||||||||||||||||||
| "github.com/yourorg/yourplugin/pkg/markers" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func GenerateRustFile(projectName string) (string, error) { | ||||||||||||||||||||
| marker, err := markers.NewRustMarker("src/main.rs", "imports") | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return "", err | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| content := fmt.Sprintf(`// Generated by Rust Plugin | ||||||||||||||||||||
| %s | ||||||||||||||||||||
|
|
||||||||||||||||||||
| use std::error::Error; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fn main() -> Result<(), Box<dyn Error>> { | ||||||||||||||||||||
| println!("Hello from %s!"); | ||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| `, marker.String(), projectName) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return content, nil | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func GenerateCargoToml(projectName string) string { | ||||||||||||||||||||
| return fmt.Sprintf(`[package] | ||||||||||||||||||||
| name = "%s" | ||||||||||||||||||||
| version = "0.1.0" | ||||||||||||||||||||
| edition = "2021" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| [dependencies] | ||||||||||||||||||||
| `, projectName) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| ``` | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ### Integrate with External Plugin | ||||||||||||||||||||
|
|
||||||||||||||||||||
| ```go | ||||||||||||||||||||
| package main | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "bufio" | ||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||
| "fmt" | ||||||||||||||||||||
| "io" | ||||||||||||||||||||
| "os" | ||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| "sigs.k8s.io/kubebuilder/v4/pkg/plugin/external" | ||||||||||||||||||||
| "github.com/yourorg/yourplugin/pkg/markers" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func main() { | ||||||||||||||||||||
| // External plugins communicate via JSON over STDIN/STDOUT | ||||||||||||||||||||
| reader := bufio.NewReader(os.Stdin) | ||||||||||||||||||||
| input, err := io.ReadAll(reader) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| returnError(fmt.Errorf("error reading STDIN: %w", err)) | ||||||||||||||||||||
| return | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| pluginRequest := &external.PluginRequest{} | ||||||||||||||||||||
| err = json.Unmarshal(input, pluginRequest) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| returnError(fmt.Errorf("error unmarshaling request: %w", err)) | ||||||||||||||||||||
| return | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| var response external.PluginResponse | ||||||||||||||||||||
|
|
||||||||||||||||||||
| switch pluginRequest.Command { | ||||||||||||||||||||
| case "init": | ||||||||||||||||||||
| response = handleInit(pluginRequest) | ||||||||||||||||||||
| case "flags": | ||||||||||||||||||||
| response = handleFlags(pluginRequest) | ||||||||||||||||||||
| case "metadata": | ||||||||||||||||||||
| response = handleMetadata(pluginRequest) | ||||||||||||||||||||
|
||||||||||||||||||||
| case "flags": | |
| response = handleFlags(pluginRequest) | |
| case "metadata": | |
| response = handleMetadata(pluginRequest) |
Outdated
Copilot
AI
Oct 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error is silently discarded using blank identifier. In documentation examples, it's better to demonstrate proper error handling to avoid teaching bad practices. Consider handling the error or adding a comment explaining why it's safe to ignore in this specific context.
| marker, _ := markers.NewRustMarker("src/main.rs", "imports") | |
| marker, err := markers.NewRustMarker("src/main.rs", "imports") | |
| if err != nil { | |
| return external.PluginResponse{ | |
| Command: "init", | |
| Error: true, | |
| ErrorMsgs: []string{fmt.Sprintf("failed to create Rust marker: %v", err)}, | |
| } | |
| } |
Copilot
AI
Nov 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] While there's a comment explaining that 'External plugins use "universe" to represent file changes', it would be helpful to add more context about what 'universe' represents and why it's named as such. Consider expanding the comment to explain that it's a map of file paths to their contents that gets passed through the plugin chain.
| // External plugins use "universe" to represent file changes | |
| // External plugins use "universe" to represent file changes. | |
| // "universe" is a map from file paths (as strings) to their file contents (also strings). | |
| // This map is passed through the plugin chain to communicate which files should be created or updated, | |
| // allowing plugins to coordinate file generation and modifications. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test those examples?
Because when it was raised seems like it was missing externalise something.
Is that working properly?
If so, could you please share in the description the POC / tests made?