Skip to content

Commit 7669f36

Browse files
Add cycle detection with panic (#346)
Fixes aws-controllers-k8s/community#1327 Description of changes: The majority of our code-generator templates and methods rely on our custom resource properties being represented as DAGs. That is, they can recurse through their children until they reach primitive values at the base. Without a major refactor, it is not possible to support a cyclic field reference. This pull request adds logic to detect a cycle as we are iterating over new fields and will panic to tell the user about the limitation. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a45f3b9 commit 7669f36

File tree

5 files changed

+1853
-2
lines changed

5 files changed

+1853
-2
lines changed

pkg/model/field.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,23 @@ func NewField(
229229
fieldNames names.Names,
230230
shapeRef *awssdkmodel.ShapeRef,
231231
cfg *ackgenconfig.FieldConfig,
232+
) *Field {
233+
return newFieldRecurse(crd, path, make(map[string]struct{}, 0), fieldNames, shapeRef, cfg)
234+
}
235+
236+
// newFieldRecurse recursively calls itself with protection against infinite
237+
// cycles
238+
func newFieldRecurse(
239+
crd *CRD,
240+
path string,
241+
// A map of shape name to Fields. Keeps track of every shape that has been
242+
// recursively discovered so that we can detect cycles. For example, the
243+
// emrcontainers `Configuration` object contains a property of type
244+
// `Configuration`.
245+
parentFields map[string]struct{},
246+
fieldNames names.Names,
247+
shapeRef *awssdkmodel.ShapeRef,
248+
cfg *ackgenconfig.FieldConfig,
232249
) *Field {
233250
memberFields := map[string]*Field{}
234251
var gte, gt, gtwp string
@@ -269,16 +286,36 @@ func NewField(
269286
}
270287
break
271288
}
289+
290+
// Copy the parent fields map so that we can use it recursively without
291+
// modifying it for other call stacks
292+
nestedParentFields := make(map[string]struct{}, len(parentFields))
293+
for k, v := range parentFields {
294+
nestedParentFields[k] = v
295+
}
296+
nestedParentFields[shapeRef.ShapeName] = struct{}{}
297+
272298
if containerShape.Type == "structure" {
273299
// "unpack" the member fields composing this struct field...
274300
for _, memberName := range containerShape.MemberNames() {
275301
cleanMemberNames := names.New(memberName)
276302
memberPath := path + "." + cleanMemberNames.Camel
277303
memberShape := containerShape.MemberRefs[memberName]
304+
305+
// Check to see if we have seen this shape before in the stack
306+
// and panic. Cyclic references are not supported
307+
if _, ok := nestedParentFields[memberShape.ShapeName]; ok {
308+
panic(fmt.Sprintf("Detected a cyclic type reference in %s"+
309+
". Add the corresponding path to `ignore.field_paths`"+
310+
" in the generator config to continue.",
311+
containerShape.ShapeName))
312+
}
313+
278314
fConfigs := crd.cfg.GetFieldConfigs(crd.Names.Original)
279-
memberField := NewField(
280-
crd, memberPath, cleanMemberNames, memberShape, fConfigs[memberPath],
315+
memberField := newFieldRecurse(
316+
crd, memberPath, nestedParentFields, cleanMemberNames, memberShape, fConfigs[memberPath],
281317
)
318+
282319
memberFields[cleanMemberNames.Camel] = memberField
283320
}
284321
}
@@ -287,6 +324,7 @@ func NewField(
287324
gt = "*string"
288325
gtwp = "*string"
289326
}
327+
290328
return &Field{
291329
CRD: crd,
292330
Names: fieldNames,

pkg/model/model_emrcontainers_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package model_test
15+
16+
import (
17+
"testing"
18+
19+
"github.com/stretchr/testify/assert"
20+
21+
"github.com/aws-controllers-k8s/code-generator/pkg/testutil"
22+
)
23+
24+
func TestEMRContainers_JobRun(t *testing.T) {
25+
assert := assert.New(t)
26+
27+
g := testutil.NewModelForServiceWithOptions(t, "emrcontainers", &testutil.TestingModelOptions{
28+
GeneratorConfigFile: "generator-with-cycle.yaml",
29+
})
30+
31+
assert.Panics(func() { g.GetCRDs() })
32+
}

0 commit comments

Comments
 (0)