Skip to content

Commit

Permalink
Fix operator docs reference generator bug (#46732)
Browse files Browse the repository at this point in the history
In the reference page for one Kubernetes operator resource, some
Markdown links are malformed.

The issue is that some fields of custom resource definitions used by the
operator consist of arrays of anonymous objects with fields that are
also objects. When creating docs based on these fields, the operator
resource docs generator creates a malformed link reference.

This change modifies the generator to replace any spaces with hyphens
before outputting link references, causing the resulting internal links
to work correctly.

This change also does some light refactoring to remove an unnecessary
`switch` statement.
  • Loading branch information
ptgott authored Sep 19, 2024
1 parent dccdcd3 commit 154a6ab
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ resource, which you can apply after installing the Teleport Kubernetes operator.

|Field|Type|Description|
|---|---|---|
|add_labels|[object](#specmappings itemsadd_labels)|AddLabels specifies which labels to add if any of the previous matches match.|
|match|[][object](#specmappings itemsmatch-items)|Match is a set of matching rules for this mapping. If any of these match, then the mapping will be applied.|
|add_labels|[object](#specmappings-itemsadd_labels)|AddLabels specifies which labels to add if any of the previous matches match.|
|match|[][object](#specmappings-itemsmatch-items)|Match is a set of matching rules for this mapping. If any of these match, then the mapping will be applied.|

### spec.mappings items.add_labels

Expand Down
143 changes: 72 additions & 71 deletions integrations/operator/crdgen/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,91 +138,92 @@ const statusDescription = "Status defines the observed state of the Teleport res
const statusName = "status"

func propertyTable(currentFieldName string, props *apiextv1.JSONSchemaProps) ([]PropertyTable, error) {
switch props.Type {
case "object":
tab := PropertyTable{
Name: currentFieldName,
// Only create a property table for an object field. For other types, we can
// describe the type within a table row.
if props.Type != "object" {
return nil, nil
}
tab := PropertyTable{
Name: currentFieldName,
}
fields := []PropertyTableField{}
tables := []PropertyTable{}
var i int
for k, v := range props.Properties {
// Don't document the Status field, which is for
// internal use.
if k == statusName && strings.HasPrefix(v.Description, statusDescription) {
continue
}
// Name the table after the hierarchy of
// field names to avoid duplication.
var tableName string
if currentFieldName != "" {
tableName = currentFieldName + "." + k
} else {
tableName = k
}
fields := []PropertyTableField{}
tables := []PropertyTable{}
var i int
for k, v := range props.Properties {
// Don't document the Status field, which is for
// internal use.
if k == statusName && strings.HasPrefix(v.Description, statusDescription) {
continue
}
// Name the table after the hierarchy of
// field names to avoid duplication.
var tableName string
if currentFieldName != "" {
tableName = currentFieldName + "." + k
} else {
tableName = k
}

var fieldType string
var fieldDesc string
switch v.Type {
case "object":
fieldType = "object"
if len(v.Properties) == 0 {
break
}
var fieldType string
var fieldDesc string
switch v.Type {
case "object":
fieldType = "object"
if len(v.Properties) == 0 {
break
}

extra, err := propertyTable(
tableName,
&v,
)
if err != nil {
return nil, err
}
fieldType = fmt.Sprintf("[object](#%v)", strings.ReplaceAll(strings.ReplaceAll(tableName, ".", ""), " ", "-"))
tables = append(tables, extra...)
case "array":
var subtp string
if v.Items.Schema.Type == "object" {
extra, err := propertyTable(
tableName,
&v,
fmt.Sprintf("%v items", tableName),
v.Items.Schema,
)
if err != nil {
return nil, err
}
fieldType = fmt.Sprintf("[object](#%v)", strings.ReplaceAll(tableName, ".", ""))
tables = append(tables, extra...)
case "array":
var subtp string
if v.Items.Schema.Type == "object" {
extra, err := propertyTable(
fmt.Sprintf("%v items", tableName),
v.Items.Schema,
)
if err != nil {
return nil, err
}
tables = append(tables, extra...)
subtp = fmt.Sprintf("[object](#%v-items)", strings.ReplaceAll(tableName, ".", ""))
} else {
subtp = v.Items.Schema.Type
}
fieldType = fmt.Sprintf("[]%v", subtp)
case "":
if !v.XIntOrString {
fieldType = v.Type
break
}
fieldType = "string or integer"
fieldDesc = strings.TrimSuffix(v.Description, ".") + ". " + "Can be either the string or the integer representation of each option."
default:
fieldType = v.Type
subtp = fmt.Sprintf("[object](#%v-items)", strings.ReplaceAll(strings.ReplaceAll(tableName, ".", ""), " ", "-"))
} else {
subtp = v.Items.Schema.Type
}

if fieldDesc == "" {
fieldDesc = v.Description
fieldType = fmt.Sprintf("[]%v", subtp)
case "":
if !v.XIntOrString {
fieldType = v.Type
break
}
fieldType = "string or integer"
fieldDesc = strings.TrimSuffix(v.Description, ".") + ". " + "Can be either the string or the integer representation of each option."
default:
fieldType = v.Type
}

fields = append(fields, PropertyTableField{
Name: k,
Type: fieldType,
Description: fieldDesc,
})
i++
if fieldDesc == "" {
fieldDesc = v.Description
}
tab.Fields = fields
sort.Sort(tab)
tables = append([]PropertyTable{tab}, tables...)
return tables, nil

fields = append(fields, PropertyTableField{
Name: k,
Type: fieldType,
Description: fieldDesc,
})
i++
}
return nil, nil
tab.Fields = fields
sort.Sort(tab)
tables = append([]PropertyTable{tab}, tables...)
return tables, nil
}

func formatAsDocsPage(crd apiextv1.CustomResourceDefinition) ([]byte, string, error) {
Expand Down
67 changes: 67 additions & 0 deletions integrations/operator/crdgen/handlerequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,73 @@ state of this API Resource.\n---\nThis struct is intended for direct use as an a
},
},
},
{
description: "array of objects with object field",
input: apiextv1.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextv1.JSONSchemaProps{
"mappings": apiextv1.JSONSchemaProps{
Type: "array",
Description: "Mappings is a list of matches that will map match conditions to labels.",
Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextv1.JSONSchemaProps{
"add_labels": apiextv1.JSONSchemaProps{
Type: "object",
Description: "AddLabels specifies which labels to add if any of the previous matches match.",
Nullable: true,
Properties: map[string]apiextv1.JSONSchemaProps{
"key": apiextv1.JSONSchemaProps{
Type: "string",
},
"value": apiextv1.JSONSchemaProps{
Type: "string",
},
},
},
},
},
},
},
},
},
expected: []PropertyTable{
{
Name: "",
Fields: []PropertyTableField{
{
Name: "mappings",
Type: "[][object](#mappings-items)",
Description: "Mappings is a list of matches that will map match conditions to labels.",
},
},
},
{
Name: "mappings items",
Fields: []PropertyTableField{
{
Name: "add_labels",
Type: "[object](#mappings-itemsadd_labels)",
Description: "AddLabels specifies which labels to add if any of the previous matches match.",
},
},
},
{
Name: "mappings items.add_labels",
Fields: []PropertyTableField{
{
Name: "key",
Type: "string",
},
{
Name: "value",
Type: "string",
},
},
},
},
},
}

for _, c := range cases {
Expand Down

0 comments on commit 154a6ab

Please sign in to comment.