Skip to content

Commit 5e1b51a

Browse files
committed
support field custom documentation strings
Adds support for extending the documentation strings for fields and setting docstrings for custom fields. A new `FieldConfig.Documentation` configuration option is now supported. When present, this string *extends* any documentation provided for the existing field by the doc-2.json files. If no such documentation is provided by the existing field OR if the field is a custom field, the `FieldConfig.Documentation` configuration option is used for the field's docstring in the Go type files. Example usage for an existing nested field (from a hypothetical ec2 generator.yaml file). The `LaunchTemplateData.HibernationOptions.Configured` field has a docstring already that contains the following: ``` // If you set this parameter to true, the instance is enabled for hibernation. // // Default: false ``` Setting the `generator.yaml` file to the following: ```yaml resources: LaunchTemplate: fields: LaunchTemplateData.HibernationOptions.Configured: documentation: XXX extended docs XXX ``` would cause the field's Documentation string to look like this: ``` // If you set this parameter to true, the instance is enabled for hibernation. // // Default: false // // XXX extended docs XXX", ``` Issue aws-controllers-k8s/community#1223 Signed-off-by: Jay Pipes <[email protected]>
1 parent a676537 commit 5e1b51a

File tree

5 files changed

+236
-53
lines changed

5 files changed

+236
-53
lines changed

pkg/config/field.go

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
package config
1515

16-
import "strings"
16+
import (
17+
"strings"
18+
)
1719

1820
// SourceFieldConfig instructs the code generator how to handle a field in the
1921
// Resource's SpecFields/StatusFields collection that takes its value from an
@@ -49,23 +51,23 @@ import "strings"
4951
// CreateFunction API call, which has a `Code` member of its Input shape that
5052
// looks like this:
5153
//
52-
// "Code": {
53-
// "ImageUri": "string",
54-
// "S3Bucket": "string",
55-
// "S3Key": "string",
56-
// "S3ObjectVersion": "string",
57-
// "ZipFile": blob
58-
// },
54+
// "Code": {
55+
// "ImageUri": "string",
56+
// "S3Bucket": "string",
57+
// "S3Key": "string",
58+
// "S3ObjectVersion": "string",
59+
// "ZipFile": blob
60+
// },
5961
//
6062
// The GetFunction API call's Output shape has a same-named field called
6163
// `Code` in it, but this field looks like this:
6264
//
63-
// "Code": {
64-
// "ImageUri": "string",
65-
// "Location": "string",
66-
// "RepositoryType": "string",
67-
// "ResolvedImageUri": "string"
68-
// },
65+
// "Code": {
66+
// "ImageUri": "string",
67+
// "Location": "string",
68+
// "RepositoryType": "string",
69+
// "ResolvedImageUri": "string"
70+
// },
6971
//
7072
// This presents a conundrum to the ACK code generator, which, as noted above,
7173
// assumes the data types of same-named fields in the Create Operation's Input
@@ -80,23 +82,24 @@ import "strings"
8082
// ReadOne Operation's Output shape:
8183
//
8284
// resources:
83-
// Function:
84-
// fields:
85-
// CodeLocation:
86-
// is_read_only: true
87-
// from:
88-
// operation: GetFunction
89-
// path: Code.Location
90-
// CodeRepositoryType:
91-
// is_read_only: true
92-
// from:
93-
// operation: GetFunction
94-
// path: Code.RepositoryType
95-
// CodeRegisteredImageURI:
96-
// is_read_only: true
97-
// from:
98-
// operation: GetFunction
99-
// path: Code.RegisteredImageUri
85+
//
86+
// Function:
87+
// fields:
88+
// CodeLocation:
89+
// is_read_only: true
90+
// from:
91+
// operation: GetFunction
92+
// path: Code.Location
93+
// CodeRepositoryType:
94+
// is_read_only: true
95+
// from:
96+
// operation: GetFunction
97+
// path: Code.RepositoryType
98+
// CodeRegisteredImageURI:
99+
// is_read_only: true
100+
// from:
101+
// operation: GetFunction
102+
// path: Code.RegisteredImageUri
100103
type SourceFieldConfig struct {
101104
// Operation refers to the ID of the API Operation where we will
102105
// determine the field's Go type.
@@ -135,17 +138,20 @@ type SourceFieldConfig struct {
135138
// the SetResource generator:
136139
//
137140
// ```go
138-
// if resp.DBInstance.DBSecurityGroups != nil {
139-
// f17 := []*string{}
140-
// for _, f17iter := range resp.DBInstance.DBSecurityGroups {
141-
// var f17elem string
142-
// f17elem = *f17iter.DBSecurityGroupName
143-
// f17 = append(f17, &f17elem)
144-
// }
145-
// ko.Spec.DBSecurityGroupNames = f17
146-
// } else {
147-
// ko.Spec.DBSecurityGroupNames = nil
148-
// }
141+
//
142+
// if resp.DBInstance.DBSecurityGroups != nil {
143+
// f17 := []*string{}
144+
// for _, f17iter := range resp.DBInstance.DBSecurityGroups {
145+
// var f17elem string
146+
// f17elem = *f17iter.DBSecurityGroupName
147+
// f17 = append(f17, &f17elem)
148+
// }
149+
// ko.Spec.DBSecurityGroupNames = f17
150+
// } else {
151+
//
152+
// ko.Spec.DBSecurityGroupNames = nil
153+
// }
154+
//
149155
// ```
150156
//
151157
// [0] https://github.com/aws/aws-sdk-go/blob/0a01aef9caf16d869c7340e729080205760dc2a2/models/apis/rds/2014-10-31/api-2.json#L2985
@@ -274,12 +280,14 @@ type LateInitializeConfig struct {
274280
// Example:
275281
// ```
276282
// Integration:
277-
// fields:
278-
// ApiId:
279-
// references:
280-
// resource: API
281-
// path: Status.APIID
282-
//```
283+
//
284+
// fields:
285+
// ApiId:
286+
// references:
287+
// resource: API
288+
// path: Status.APIID
289+
//
290+
// ```
283291
// The above configuration will result in generation of a new field 'APIRef'
284292
// of type 'AWSResourceReference' for ApiGatewayv2-Integration crd.
285293
// When 'APIRef' field is present in custom resource manifest, reconciler will
@@ -384,10 +392,15 @@ type FieldConfig struct {
384392
// TODO(jaypipes,crtbry): Figure out if we can roll the CustomShape stuff
385393
// into this type override...
386394
Type *string `json:"type,omitempty"`
395+
// Documentation is a string that is added *in addition to* any existing
396+
// field documentation derived from the field's doc-2.json contents. For
397+
// custom fields, this allows you to add custom documentation for the
398+
// field.
399+
Documentation *string `json:"documentation,omitempty"`
387400
}
388401

389402
// GetFieldConfigs returns all FieldConfigs for a given resource as a map.
390-
// The map is keyed by the resource's field names after applying renames, if applicable.
403+
// The map is keyed by the resource's field paths
391404
func (c *Config) GetFieldConfigs(resourceName string) map[string]*FieldConfig {
392405
if c == nil {
393406
return map[string]*FieldConfig{}

pkg/model/field.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,65 @@ type Field struct {
6464
MemberFields map[string]*Field
6565
}
6666

67+
// GetDocumentation returns a string containing the field's
68+
// description/docstring. If the field has a ShapeRef that has non-empty
69+
// Documentation AND the field has a Documentation configuration option, then
70+
// the docstring contained in the Documentation configuration option will be
71+
// appended to ShapeRef's docstring following 2 line breaks with Go comment
72+
// line beginnings.
73+
//
74+
// In other words, if there is a field with a ShapeRef that has a Documentation string containing:
75+
//
76+
// "// This field contains the identifier for the cluster
77+
//
78+
// // running the cache services"
79+
//
80+
// and the field has a FieldConfig.Documentation string containing:
81+
//
82+
// "please note that this field is updated on the service
83+
//
84+
// side"
85+
//
86+
// then the string returned from this method will be:
87+
//
88+
// "// This field contains the identifier for the cluster
89+
//
90+
// // running the cache services
91+
// //
92+
// // please note that this field is updated on the service
93+
// // side"
94+
func (f *Field) GetDocumentation() string {
95+
hasShapeDoc := false
96+
var sb strings.Builder
97+
if f.ShapeRef != nil {
98+
if f.ShapeRef.Documentation != "" {
99+
hasShapeDoc = true
100+
sb.WriteString(f.ShapeRef.Documentation)
101+
}
102+
}
103+
if f.FieldConfig != nil {
104+
if f.FieldConfig.Documentation != nil {
105+
if hasShapeDoc {
106+
sb.WriteString("\n//\n")
107+
}
108+
// Strip any leading comment slashes from the config option
109+
// docstring since we'll be automatically adding the Go comment
110+
// slashes to the beginning of each new line
111+
cfgDoc := *f.FieldConfig.Documentation
112+
lines := strings.Split(cfgDoc, "\n")
113+
numLines := len(lines)
114+
for x, line := range lines {
115+
sb.WriteString("// ")
116+
sb.WriteString(strings.TrimLeft(line, "/ "))
117+
if x < (numLines - 1) {
118+
sb.WriteString("\n")
119+
}
120+
}
121+
}
122+
}
123+
return sb.String()
124+
}
125+
67126
// IsRequired checks the FieldConfig for Field and returns if the field is
68127
// marked as required or not.A
69128
//

pkg/model/field_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,51 @@ import (
1414
"github.com/aws-controllers-k8s/code-generator/pkg/testutil"
1515
)
1616

17+
func TestFieldDocumentation(t *testing.T) {
18+
require := require.New(t)
19+
20+
g := testutil.NewModelForServiceWithOptions(t, "ec2",
21+
&testutil.TestingModelOptions{
22+
GeneratorConfigFile: "generator-with-doc-overrides.yaml",
23+
},
24+
)
25+
26+
crds, err := g.GetCRDs()
27+
require.Nil(err)
28+
29+
crd := getCRDByName("LaunchTemplate", crds)
30+
require.NotNil(crd)
31+
32+
specFields := crd.SpecFields
33+
34+
// We have not altered the docstring for LaunchTemplateData from the
35+
// docstring that comes in the doc-2.json file...
36+
ltdField := specFields["LaunchTemplateData"]
37+
require.NotNil(ltdField)
38+
require.NotNil(ltdField.ShapeRef)
39+
40+
require.Equal(
41+
"// The information for the launch template.",
42+
ltdField.GetDocumentation(),
43+
)
44+
45+
// We have added an additional docstring for
46+
// LaunchTemplateData.HibernationOptions.Configured to the docstring that
47+
// comes in the doc-2.json file...
48+
hoField := ltdField.MemberFields["HibernationOptions"]
49+
require.NotNil(hoField)
50+
require.NotNil(hoField.ShapeRef)
51+
hocField := hoField.MemberFields["Configured"]
52+
require.NotNil(hocField)
53+
require.NotNil(hocField.ShapeRef)
54+
55+
require.Equal(
56+
"// If you set this parameter to true, the instance is enabled for hibernation.\n// \n// Default: false\n//\n// XXX extended docs XXX",
57+
hocField.GetDocumentation(),
58+
)
59+
60+
}
61+
1762
func TestMemberFields(t *testing.T) {
1863
assert := assert.New(t)
1964
require := require.New(t)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
ignore:
2+
resource_names:
3+
- AccountAttribute
4+
- CapacityReservation
5+
- CarrierGateway
6+
- ClientVpnEndpoint
7+
- ClientVpnRoute
8+
- CustomerGateway
9+
- DefaultSubnet
10+
- DefaultVpc
11+
- DhcpOptions
12+
- EgressOnlyInternetGateway
13+
- Fleet
14+
- FpgaImage
15+
- Image
16+
- Instance
17+
- InstanceExportTask
18+
- InternetGateway
19+
- KeyPair
20+
- LaunchTemplateVersion
21+
#- LaunchTemplate
22+
- LocalGatewayRouteTableVpcAssociation
23+
- LocalGatewayRoute
24+
- ManagedPrefixList
25+
- NatGateway
26+
- NetworkAclEntry
27+
- NetworkAcl
28+
- NetworkInsightsPath
29+
- NetworkInterfacePermission
30+
- NetworkInterface
31+
- PlacementGroup
32+
- ReservedInstancesListing
33+
- RouteTable
34+
- Route
35+
- SecurityGroup
36+
- Snapshot
37+
- SpotDatafeedSubscription
38+
- Subnet
39+
- TrafficMirrorFilterRule
40+
- TrafficMirrorFilter
41+
- TrafficMirrorSession
42+
- TrafficMirrorTarget
43+
- TransitGatewayConnectPeer
44+
- TransitGatewayConnect
45+
- TransitGatewayMulticastDomain
46+
- TransitGatewayPeeringAttachment
47+
- TransitGatewayPrefixListReference
48+
- TransitGatewayRouteTable
49+
- TransitGatewayRoute
50+
- TransitGatewayVpcAttachment
51+
- TransitGateway
52+
- Volume
53+
- VpcEndpointConnectionNotification
54+
- VpcEndpointServiceConfiguration
55+
- VpcEndpoint
56+
- Vpc
57+
- VpcCidrBlock
58+
- VpcPeeringConnection
59+
- VpnConnectionRoute
60+
- VpnConnection
61+
- VpnGateway
62+
resources:
63+
LaunchTemplate:
64+
fields:
65+
LaunchTemplateData.HibernationOptions.Configured:
66+
documentation: XXX extended docs XXX

templates/apis/crd.go.tpl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616
{{ .CRD.Documentation }}
1717
type {{ .CRD.Kind }}Spec struct {
1818
{{ range $fieldName, $field := .CRD.SpecFields }}
19-
{{ if and ($field.ShapeRef) ($field.ShapeRef.Documentation) -}}
20-
{{ $field.ShapeRef.Documentation }}
19+
{{ if $field.GetDocumentation -}}
20+
{{ $field.GetDocumentation }}
2121
{{ end -}}
2222
{{- if and ($field.IsRequired) (not $field.HasReference) -}}
2323
// +kubebuilder:validation:Required
@@ -40,8 +40,8 @@ type {{ .CRD.Kind }}Status struct {
4040
// +kubebuilder:validation:Optional
4141
Conditions []*ackv1alpha1.Condition `json:"conditions"`
4242
{{- range $fieldName, $field := .CRD.StatusFields }}
43-
{{- if and ($field.ShapeRef) ($field.ShapeRef.Documentation) }}
44-
{{ $field.ShapeRef.Documentation }}
43+
{{- if $field.GetDocumentation }}
44+
{{ $field.GetDocumentation }}
4545
{{- end }}
4646
// +kubebuilder:validation:Optional
4747
{{ $field.Names.Camel }} {{ $field.GoType }} `json:"{{ $field.Names.CamelLower }},omitempty"`

0 commit comments

Comments
 (0)