Skip to content

Commit 51faf4e

Browse files
committed
📖 Add docs: custom markers for unsupported file extensions
Shows how to use Kubebuilder as a library to create custom marker support for external plugins with non-Go file extensions like .rs, .java, .py
1 parent 9eafd53 commit 51faf4e

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

docs/book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
- [Extending](./plugins/extending.md)
131131
- [CLI and Plugins](./plugins/extending/extending_cli_features_and_plugins.md)
132132
- [External Plugins](./plugins/extending/external-plugins.md)
133+
- [Custom Markers](./plugins/extending/custom-markers.md)
133134
- [E2E Tests](./plugins/extending/testing-plugins.md)
134135
- [Plugins Versioning](./plugins/plugins-versioning.md)
135136

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Creating Custom Markers
2+
3+
## Overview
4+
5+
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.
6+
7+
## When to Use Custom Markers
8+
9+
Custom markers are useful when:
10+
11+
- You're building an external plugin for languages not natively supported by Kubebuilder
12+
- You want to scaffold files with custom extensions (`.rs`, `.java`, `.py`, `.tpl`, etc.)
13+
- You need scaffolding markers in non-Go files for your own use cases
14+
- Your file extensions aren't (and shouldn't be) part of the core `commentsByExt` map
15+
16+
## Understanding Markers
17+
18+
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.
19+
20+
Example of a marker in a Go file:
21+
```go
22+
// +kubebuilder:scaffold:imports
23+
```
24+
25+
## Implementation Example
26+
27+
Here's how to implement custom markers for Rust files (`.rs`). This same pattern can be applied to any file extension.
28+
29+
### Define Your Marker Type
30+
31+
```go
32+
// pkg/markers/rust.go
33+
package markers
34+
35+
import (
36+
"fmt"
37+
"path/filepath"
38+
"strings"
39+
)
40+
41+
const RustPluginPrefix = "+rust:scaffold:"
42+
43+
type RustMarker struct {
44+
prefix string
45+
comment string
46+
value string
47+
}
48+
49+
func NewRustMarker(path string, value string) (RustMarker, error) {
50+
ext := filepath.Ext(path)
51+
if ext != ".rs" {
52+
return RustMarker{}, fmt.Errorf("expected .rs file, got %s", ext)
53+
}
54+
55+
return RustMarker{
56+
prefix: formatPrefix(RustPluginPrefix),
57+
comment: "//",
58+
value: value,
59+
}, nil
60+
}
61+
62+
func (m RustMarker) String() string {
63+
return m.comment + " " + m.prefix + m.value
64+
}
65+
66+
func formatPrefix(prefix string) string {
67+
trimmed := strings.TrimSpace(prefix)
68+
var builder strings.Builder
69+
if !strings.HasPrefix(trimmed, "+") {
70+
builder.WriteString("+")
71+
}
72+
builder.WriteString(trimmed)
73+
if !strings.HasSuffix(trimmed, ":") {
74+
builder.WriteString(":")
75+
}
76+
return builder.String()
77+
}
78+
```
79+
80+
### Use in Template Files
81+
82+
```go
83+
package templates
84+
85+
import (
86+
"fmt"
87+
"path/filepath"
88+
"sigs.k8s.io/kubebuilder/v4/pkg/machinery"
89+
"github.com/yourorg/yourplugin/pkg/markers"
90+
)
91+
92+
type RustMainFile struct {
93+
machinery.TemplateMixin
94+
machinery.ProjectNameMixin
95+
}
96+
97+
func (f *RustMainFile) SetTemplateDefaults() error {
98+
if f.Path == "" {
99+
f.Path = filepath.Join("src", "main.rs")
100+
}
101+
102+
marker, err := markers.NewRustMarker(f.Path, "imports")
103+
if err != nil {
104+
return err
105+
}
106+
107+
f.TemplateBody = fmt.Sprintf(`// Generated by Rust Plugin
108+
%s
109+
110+
use std::error::Error;
111+
112+
fn main() -> Result<(), Box<dyn Error>> {
113+
println!("Hello from %s!");
114+
Ok(())
115+
}
116+
`, marker.String(), f.ProjectName)
117+
118+
return nil
119+
}
120+
```
121+
122+
### Integrate with External Plugin
123+
124+
```go
125+
package main
126+
127+
import (
128+
"sigs.k8s.io/kubebuilder/v4/pkg/plugin"
129+
"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external"
130+
"github.com/yourorg/yourplugin/pkg/templates"
131+
)
132+
133+
func main() {
134+
p := &external.Plugin{
135+
Name: "rust.kubebuilder.io",
136+
Version: plugin.Version{Number: 1, Stage: plugin.Alpha},
137+
138+
Init: func(req external.PluginRequest) external.PluginResponse {
139+
mainFile := &templates.RustMainFile{}
140+
141+
return external.PluginResponse{
142+
Universe: req.Universe,
143+
Files: []machinery.File{mainFile},
144+
}
145+
},
146+
}
147+
148+
external.Run(p)
149+
}
150+
```
151+
152+
## Adapting for Other Languages
153+
154+
To support other file extensions, modify the marker implementation by changing:
155+
156+
- The comment syntax (e.g., `//` for Java, `#` for Python, `{{/* ... */}}` for templates)
157+
- The file extension check (e.g., `.java`, `.py`, `.tpl`)
158+
- The marker prefix (e.g., `+java:scaffold:`, `+python:scaffold:`)
159+
160+
## Key Considerations
161+
162+
1. **Unique Prefixes**: Choose a unique prefix for your plugin to avoid conflicts (e.g., `+rust:scaffold:`, `+java:scaffold:`)
163+
164+
2. **Comment Syntax**: Different languages have different comment syntax. Ensure you map the correct comment style for each file extension
165+
166+
3. **Error Handling**: Validate file extensions and return clear error messages for unsupported files
167+
168+
4. **Testing**: Test your marker implementation with various scenarios to ensure reliability
169+
170+
For more information on creating external plugins, see [External Plugins](external-plugins.md).

0 commit comments

Comments
 (0)