Skip to content

Commit f654031

Browse files
authored
Add optional key to the FieldExport target (#100)
FieldExport generates the key name with an output structure like `<namespace>.<FieldExport-resource-name>`. This default naming structure creates non-conflicting names as the keys. However, many applications expect short names as keys. This is important, especially when the Secret/ConfigMap is mounted as files; the file names are nothing but the key names. This PR adds an optional attribute (.spec.to.key) to specify the key name in the FieldExport resource. If this attribute is specified, use this key name instead of the default. Signed-off-by: Baiju Muthukadan <[email protected]> Issue #, if available: aws-controllers-k8s/community#1410 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 9b4021b commit f654031

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed

apis/core/v1alpha1/field_export.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type FieldExportTarget struct {
2424
// Namespace is marked as optional, so we cannot compose `NamespacedName`
2525
Namespace *string `json:"namespace,omitempty"`
2626
Kind FieldExportOutputType `json:"kind"`
27+
// Key overrides the default value (`<namespace>.<FieldExport-resource-name>`) for the FieldExport target
28+
Key *string `json:"key,omitempty"`
2729
}
2830

2931
// FieldExportSpec defines the desired state of the FieldExport.

apis/core/v1alpha1/zz_generated.deepcopy.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/services.k8s.aws_fieldexports.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ spec:
6666
description: FieldExportTarget provides the values necessary to identify
6767
the output path for a field export.
6868
properties:
69+
key:
70+
description: Key overrides the default value (`<namespace>.<FieldExport-resource-name>`)
71+
for the FieldExport target
72+
type: string
6973
kind:
7074
description: FieldExportOutputType represents all types that can
7175
be produced by a field export operation

pkg/runtime/field_export_reconciler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ func (r *fieldExportReconciler) writeToConfigMap(
296296
) error {
297297
// Construct the data key
298298
key := fmt.Sprintf("%s.%s", desired.Namespace, desired.Name)
299+
if desired.Spec.To != nil && desired.Spec.To.Key != nil && strings.TrimSpace(*desired.Spec.To.Key) != "" {
300+
key = *desired.Spec.To.Key
301+
}
299302

300303
// Get the initial configmap
301304
nsn := types.NamespacedName{
@@ -340,6 +343,9 @@ func (r *fieldExportReconciler) writeToSecret(
340343
) error {
341344
// Construct the data key
342345
key := fmt.Sprintf("%s.%s", desired.Namespace, desired.Name)
346+
if desired.Spec.To != nil && desired.Spec.To.Key != nil && strings.TrimSpace(*desired.Spec.To.Key) != "" {
347+
key = *desired.Spec.To.Key
348+
}
343349

344350
// Get the initial secret
345351
nsn := types.NamespacedName{

pkg/runtime/field_export_reconciler_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,36 @@ func fieldExportWithPath(namespace, name string, kind ackv1alpha1.FieldExportOut
163163
}
164164
}
165165

166+
func fieldExportWithKey(namespace, name string, kind ackv1alpha1.FieldExportOutputType, key string) *ackv1alpha1.FieldExport {
167+
path := ".spec.name"
168+
return &ackv1alpha1.FieldExport{
169+
TypeMeta: v1.TypeMeta{},
170+
ObjectMeta: v1.ObjectMeta{
171+
Namespace: namespace,
172+
Name: name,
173+
Finalizers: []string{"finalizers.services.k8s.aws/FieldExport"},
174+
},
175+
Spec: ackv1alpha1.FieldExportSpec{
176+
From: &ackv1alpha1.ResourceFieldSelector{
177+
Path: &path,
178+
Resource: ackv1alpha1.NamespacedResource{
179+
GroupKind: v1.GroupKind{
180+
Group: BookGVK.Group,
181+
Kind: BookGVK.Kind,
182+
},
183+
Name: strPtr(SourceResourceName),
184+
},
185+
},
186+
To: &ackv1alpha1.FieldExportTarget{
187+
Name: strPtr("fake-export-output"),
188+
Kind: kind,
189+
Key: &key,
190+
},
191+
},
192+
Status: ackv1alpha1.FieldExportStatus{},
193+
}
194+
}
195+
166196
func mockFieldExportList() []ackv1alpha1.FieldExport {
167197
// Matching cases
168198
defaultConfigMap := fieldExportConfigMap(FieldExportNamespace, "export-1")
@@ -452,6 +482,66 @@ func TestSync_HappyCaseResourceNoExports(t *testing.T) {
452482
require.Len(exports, 0)
453483
}
454484

485+
func TestSync_SetKeyNameExplicitly(t *testing.T) {
486+
// Setup
487+
require := require.New(t)
488+
// Mock resource creation
489+
r, kc, apiReader := mockFieldExportReconciler()
490+
descriptor, res, _ := mockDescriptorAndAWSResource()
491+
manager := mockManager()
492+
fieldExport := fieldExportWithKey(FieldExportNamespace, FieldExportName, ackv1alpha1.FieldExportOutputTypeSecret, "new-key")
493+
sourceResource, _, _ := mockSourceResource()
494+
ctx := context.TODO()
495+
statusWriter := &ctrlrtclientmock.StatusWriter{}
496+
497+
//Mock behavior setup
498+
setupMockClientForFieldExport(kc, statusWriter, ctx, fieldExport)
499+
setupMockApiReaderForFieldExport(apiReader, ctx, res)
500+
setupMockManager(manager, ctx, res)
501+
setupMockDescriptor(descriptor, res)
502+
setupMockUnstructuredConverter()
503+
504+
// Call
505+
latest, err := r.Sync(ctx, sourceResource, *fieldExport)
506+
507+
//Assertions
508+
require.Nil(err)
509+
require.NotNil(latest.Status)
510+
require.Len(latest.Status.Conditions, 0)
511+
assertPatchedConfigMap(false, t, ctx, kc)
512+
assertPatchedSecretWithKey(true, t, ctx, kc, "new-key")
513+
}
514+
515+
func TestSync_SetKeyNameExplicitlyWithEmptyString(t *testing.T) {
516+
// Setup
517+
require := require.New(t)
518+
// Mock resource creation
519+
r, kc, apiReader := mockFieldExportReconciler()
520+
descriptor, res, _ := mockDescriptorAndAWSResource()
521+
manager := mockManager()
522+
fieldExport := fieldExportWithKey(FieldExportNamespace, FieldExportName, ackv1alpha1.FieldExportOutputTypeSecret, "")
523+
sourceResource, _, _ := mockSourceResource()
524+
ctx := context.TODO()
525+
statusWriter := &ctrlrtclientmock.StatusWriter{}
526+
527+
//Mock behavior setup
528+
setupMockClientForFieldExport(kc, statusWriter, ctx, fieldExport)
529+
setupMockApiReaderForFieldExport(apiReader, ctx, res)
530+
setupMockManager(manager, ctx, res)
531+
setupMockDescriptor(descriptor, res)
532+
setupMockUnstructuredConverter()
533+
534+
// Call
535+
latest, err := r.Sync(ctx, sourceResource, *fieldExport)
536+
537+
//Assertions
538+
require.Nil(err)
539+
require.NotNil(latest.Status)
540+
require.Len(latest.Status.Conditions, 0)
541+
assertPatchedConfigMap(false, t, ctx, kc)
542+
assertPatchedSecret(true, t, ctx, kc)
543+
}
544+
455545
// Assertions
456546

457547
func assertPatchedConfigMap(expected bool, t *testing.T, ctx context.Context, kc *ctrlrtclientmock.Client) {
@@ -492,6 +582,24 @@ func assertPatchedSecret(expected bool, t *testing.T, ctx context.Context, kc *c
492582
}
493583
}
494584

585+
func assertPatchedSecretWithKey(expected bool, t *testing.T, ctx context.Context, kc *ctrlrtclientmock.Client, key string) {
586+
dataMatcher := mock.MatchedBy(func(cm *corev1.Secret) bool {
587+
if cm.Data == nil {
588+
return false
589+
}
590+
val, ok := cm.Data[key]
591+
if !ok {
592+
return false
593+
}
594+
return bytes.Equal(val, []byte("test-book-name"))
595+
})
596+
if expected {
597+
kc.AssertCalled(t, "Patch", ctx, dataMatcher, mock.Anything)
598+
} else {
599+
kc.AssertNotCalled(t, "Patch", ctx, dataMatcher, mock.Anything)
600+
}
601+
}
602+
495603
// assertRecoverableCondition asserts that 'ConditionTypeRecoverable' condition
496604
// is present in the resource's status and that it's value is equal to
497605
// 'conditionStatus' parameter

0 commit comments

Comments
 (0)