|
| 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