From cc7e410a8425c076f1312d7aa6f5da8fa27befb0 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Wed, 18 Sep 2024 14:31:06 -0400 Subject: [PATCH] Fix operator docs reference generator bug 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. --- ...resources.teleport.dev_oktaimportrules.mdx | 4 +- integrations/operator/crdgen/format.go | 143 +++++++++--------- .../operator/crdgen/handlerequest_test.go | 67 ++++++++ 3 files changed, 141 insertions(+), 73 deletions(-) diff --git a/docs/pages/reference/operator-resources/resources.teleport.dev_oktaimportrules.mdx b/docs/pages/reference/operator-resources/resources.teleport.dev_oktaimportrules.mdx index f5000508e5202..c28c50efeefc2 100644 --- a/docs/pages/reference/operator-resources/resources.teleport.dev_oktaimportrules.mdx +++ b/docs/pages/reference/operator-resources/resources.teleport.dev_oktaimportrules.mdx @@ -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 diff --git a/integrations/operator/crdgen/format.go b/integrations/operator/crdgen/format.go index c7e39a56a0d3d..3cdd0afb6add3 100644 --- a/integrations/operator/crdgen/format.go +++ b/integrations/operator/crdgen/format.go @@ -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) { diff --git a/integrations/operator/crdgen/handlerequest_test.go b/integrations/operator/crdgen/handlerequest_test.go index ece3da8555255..710567b7ed3a5 100644 --- a/integrations/operator/crdgen/handlerequest_test.go +++ b/integrations/operator/crdgen/handlerequest_test.go @@ -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 {