Skip to content

Commit 23c00bc

Browse files
thbkrkrsimonrw
authored andcommitted
Support +required, +k8s:required, +optional and +k8s:optional markers (elastic#193)
This adds support for the +required, +k8s:required, +optional and +k8s:optional markers, aligning with upstream Kubernetes marker conventions. Additionally, it improves the escaping of pipe character for validation value in Asciidoctor output to prevent formatting issues. Based on the work from elastic#192, with additional changes.
1 parent c073e1c commit 23c00bc

7 files changed

Lines changed: 67 additions & 5 deletions

File tree

processor/processor.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ func mkRegistry(customMarkers []config.Marker) (*markers.Registry, error) {
505505
}
506506
}
507507

508+
// Register k8s:* markers - sig apimachinery plans to unify CRD and native types on these.
509+
if err := registry.Define("k8s:required", markers.DescribesField, struct{}{}); err != nil {
510+
return nil, err
511+
}
512+
if err := registry.Define("k8s:optional", markers.DescribesField, struct{}{}); err != nil {
513+
return nil, err
514+
}
515+
508516
for _, marker := range customMarkers {
509517
t := markers.DescribesField
510518
switch marker.Target {
@@ -554,6 +562,17 @@ func parseMarkers(markers markers.MarkerValues) (string, []string) {
554562
validation = append(validation, fmt.Sprintf("%s: %v", name, value))
555563
}
556564

565+
// Handle standalone +required and +k8s:required marker
566+
// This is equivalent to +kubebuilder:validation:Required
567+
if name == "required" || name == "k8s:required" {
568+
validation = append(validation, "Required: {}")
569+
}
570+
// Handle standalone +optional and +k8s:optional marker
571+
// This is equivalent to +kubebuilder:validation:Optional
572+
if name == "optional" || name == "k8s:optional" {
573+
validation = append(validation, "Optional: {}")
574+
}
575+
557576
if name == "kubebuilder:default" {
558577
if value, ok := value.(crdmarkers.Default); ok {
559578
defaultValue = fmt.Sprintf("%v", value.Value)

renderer/asciidoctor.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (adr *AsciidoctorRenderer) TemplateValue(key string) string {
151151
func (adr *AsciidoctorRenderer) RenderFieldDoc(text string) string {
152152
// Escape the pipe character, which has special meaning for asciidoc as a way to format tables,
153153
// so that including | in a comment does not result in wonky tables.
154-
out := strings.ReplaceAll(text, "|", "\\|")
154+
out := escapePipe(text)
155155

156156
// Trim any leading and trailing whitespace from each line.
157157
lines := strings.Split(out, "\n")
@@ -169,6 +169,7 @@ func (adr *AsciidoctorRenderer) RenderFieldDoc(text string) string {
169169

170170
func (adr *AsciidoctorRenderer) RenderValidation(text string) string {
171171
renderedText := escapeFirstAsterixInEachPair(text)
172+
renderedText = escapePipe(renderedText)
172173
return escapeCurlyBraces(renderedText)
173174
}
174175

@@ -190,9 +191,15 @@ func escapeFirstAsterixInEachPair(text string) string {
190191
return text
191192
}
192193

194+
// escapePipe ensures sufficient escapes are added to pipe characters, so they are not mistaken
195+
// for asciidoctor table formatting.
196+
func escapePipe(text string) string {
197+
return strings.ReplaceAll(text, "|", "\\|")
198+
}
199+
193200
// escapeCurlyBraces ensures sufficient escapes are added to curly braces, so they are not mistaken
194201
// for asciidoctor id attributes.
195202
func escapeCurlyBraces(text string) string {
196203
// Per asciidoctor docs, only the leading curly brace needs to be escaped.
197-
return strings.Replace(text, "{", "\\{", -1)
204+
return strings.ReplaceAll(text, "{", "\\{")
198205
}

renderer/asciidoctor_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ func Test_escapeCurlyBraces(t *testing.T) {
1919
assert.Equal(t, "[a-fA-F0-9]\\{64}", escapeCurlyBraces("[a-fA-F0-9]{64}"))
2020
assert.Equal(t, "[a-fA-F0-9]\\\\{64\\}", escapeCurlyBraces("[a-fA-F0-9]\\{64\\}"))
2121
}
22+
23+
func Test_escapePipe(t *testing.T) {
24+
assert.Equal(t, "[0-9]", escapePipe("[0-9]"))
25+
assert.Equal(t, `[0-9]*\|\s`, escapePipe(`[0-9]*|\s`))
26+
}

test/api/v1/guestbook_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ type PositiveInt int
124124
// GuestbookEntry defines an entry in a guest book.
125125
type GuestbookEntry struct {
126126
// Name of the guest (pipe | should be escaped)
127+
// +kubebuilder:validation:Required
127128
// +kubebuilder:validation:MaxLength=80
128129
// +kubebuilder:validation:Pattern=`0*[a-z0-9]*[a-z]*[0-9]`
129130
Name string `json:"name,omitempty"`
@@ -146,6 +147,19 @@ type GuestbookEntry struct {
146147
Comment string `json:"comment,omitempty"`
147148
// Rating provided by the guest
148149
Rating Rating `json:"rating,omitempty"`
150+
151+
// Email is the email address of the guest (required field using +required marker)
152+
// +required
153+
Email string `json:"email"`
154+
// Location is the location of the guest (required field using +k8s:required marker)
155+
// +k8s:required
156+
Location string `json:"location"`
157+
// Phone is the phone number of the guest (optional field using +optional marker)
158+
// +optional
159+
Phone string `json:"phone"`
160+
// Company is the company of the guest (optional field using +k8s:optional marker)
161+
// +k8s:optional
162+
Company string `json:"company"`
149163
}
150164

151165
// GuestbookStatus defines the observed state of Guestbook.

test/expected.asciidoc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ GuestbookEntry defines an entry in a guest book.
162162
| Field | Description | Default | Validation
163163
| *`name`* __string__ | Name of the guest (pipe \| should be escaped) + | | MaxLength: 80 +
164164
Pattern: `0\*[a-z0-9]*[a-z]*[0-9]` +
165+
Required: \{} +
165166

166167
| *`tags`* __string array__ | Tags of the entry. + | | items:Pattern: `[a-z]*` +
167168

@@ -174,11 +175,19 @@ Now let's test a list: +
174175

175176
Another isolated comment. +
176177

177-
Looks good? + | | Pattern: `0\*[a-z0-9]*[a-z]\*[0-9]*|\s` +
178+
Looks good? + | | Pattern: `0\*[a-z0-9]*[a-z]\*[0-9]*\|\s` +
178179

179180
| *`rating`* __xref:{anchor_prefix}-github-com-elastic-crd-ref-docs-api-v1-rating[$$Rating$$]__ | Rating provided by the guest + | | Maximum: 5 +
180181
Minimum: 1 +
181182

183+
| *`email`* __string__ | Email is the email address of the guest (required field using +required marker) + | | Required: \{} +
184+
185+
| *`location`* __string__ | Location is the location of the guest (required field using +k8s:required marker) + | | Required: \{} +
186+
187+
| *`phone`* __string__ | Phone is the phone number of the guest (optional field using +optional marker) + | | Optional: \{} +
188+
189+
| *`company`* __string__ | Company is the company of the guest (optional field using +k8s:optional marker) + | | Optional: \{} +
190+
182191
|===
183192

184193

test/expected.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,15 @@ _Appears in:_
127127

128128
| Field | Description | Default | Validation |
129129
| --- | --- | --- | --- |
130-
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br /> |
130+
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
131131
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*` <br /> |
132132
| `time` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Time of entry | | |
133133
| `comment` _string_ | Comment by guest. This can be a multi-line comment.<br />Like this one.<br />Now let's test a list:<br />* a<br />* b<br />Another isolated comment.<br />Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s` <br /> |
134134
| `rating` _[Rating](#rating)_ | Rating provided by the guest | | Maximum: 5 <br />Minimum: 1 <br /> |
135+
| `email` _string_ | Email is the email address of the guest (required field using +required marker) | | Required: \{\} <br /> |
136+
| `location` _string_ | Location is the location of the guest (required field using +k8s:required marker) | | Required: \{\} <br /> |
137+
| `phone` _string_ | Phone is the phone number of the guest (optional field using +optional marker) | | Optional: \{\} <br /> |
138+
| `company` _string_ | Company is the company of the guest (optional field using +k8s:optional marker) | | Optional: \{\} <br /> |
135139

136140

137141
#### GuestbookHeader

test/hide.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,15 @@ _Appears in:_
128128

129129
| Field | Description | Default | Validation |
130130
| --- | --- | --- | --- |
131-
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br /> |
131+
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
132132
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*` <br /> |
133133
| `time` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Time of entry | | |
134134
| `comment` _string_ | Comment by guest. This can be a multi-line comment.<br />Like this one.<br />Now let's test a list:<br />* a<br />* b<br />Another isolated comment.<br />Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s` <br /> |
135135
| `rating` _[Rating](#rating)_ | Rating provided by the guest | | Maximum: 5 <br />Minimum: 1 <br /> |
136+
| `email` _string_ | Email is the email address of the guest (required field using +required marker) | | Required: \{\} <br /> |
137+
| `location` _string_ | Location is the location of the guest (required field using +k8s:required marker) | | Required: \{\} <br /> |
138+
| `phone` _string_ | Phone is the phone number of the guest (optional field using +optional marker) | | Optional: \{\} <br /> |
139+
| `company` _string_ | Company is the company of the guest (optional field using +k8s:optional marker) | | Optional: \{\} <br /> |
136140

137141

138142
#### GuestbookHeader

0 commit comments

Comments
 (0)