Skip to content

Commit 2f5479b

Browse files
committed
update codegen
Signed-off-by: Karol Szwaj <karol.szwaj@gmail.com> On-behalf-of: @SAP karol.szwaj@sap.com
1 parent 6a7c0d6 commit 2f5479b

File tree

4 files changed

+21
-278
lines changed

4 files changed

+21
-278
lines changed

deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,9 @@ spec:
407407
type: object
408408
type: array
409409
type: object
410+
optional:
411+
description: Optional indicates whether the related resource must be referenced.
412+
type: boolean
410413
origin:
411414
description: '"service" or "kcp"'
412415
type: string

internal/sync/syncer_related.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,28 @@ func (s *ResourceSyncer) processRelatedResource(log *zap.SugaredLogger, stateSto
7272
dest = local
7373
}
7474

75+
// to find the source related object, we first need to determine its name/namespace
7576
sourceKey, err := resolveResourceReference(source.object, relRes.Reference)
7677
if err != nil {
7778
return false, fmt.Errorf("failed to determine related object's source key: %w", err)
7879
}
7980

81+
// find the source related object
8082
sourceObj := &unstructured.Unstructured{}
81-
sourceObj.SetAPIVersion("v1")
83+
sourceObj.SetAPIVersion("v1") // we only support ConfigMaps and Secrets, both are in core/v1
8284
sourceObj.SetKind(relRes.Kind)
8385

8486
err = source.client.Get(source.ctx, *sourceKey, sourceObj)
8587
if err != nil {
88+
// the source object doesn't exist yet, so we can just stop
8689
if apierrors.IsNotFound(err) {
8790
return false, nil
8891
}
92+
8993
return false, fmt.Errorf("failed to get source object: %w", err)
9094
}
9195

96+
// do the same to find the destination object
9297
destKey, err := resolveResourceReference(dest.object, relRes.Reference)
9398
if err != nil {
9499
return false, fmt.Errorf("failed to determine related object's destination key: %w", err)
@@ -237,8 +242,10 @@ func resolveResourceLocator(jsonData string, loc syncagentv1alpha1.ResourceLocat
237242
if err != nil {
238243
return "", fmt.Errorf("invalid pattern %q: %w", re.Pattern, err)
239244
}
245+
// this does apply some coalescing, like turning numbers into strings
246+
strVal := gval.String()
240247

241-
return expr.ReplaceAllString(gval.String(), re.Replacement), nil
248+
return expr.ReplaceAllString(strVal, re.Replacement), nil
242249
}
243250

244251
return gval.String(), nil

internal/sync/syncer_test.go

-276
Original file line numberDiff line numberDiff line change
@@ -1320,282 +1320,6 @@ func TestSyncerProcessingSingleResourceWithStatus(t *testing.T) {
13201320
})
13211321
}
13221322
}
1323-
func TestSyncerProcessingRelatedResources(t *testing.T) {
1324-
type testcase struct {
1325-
name string
1326-
remoteAPIGroup string
1327-
localCRD *apiextensionsv1.CustomResourceDefinition
1328-
pubRes *syncagentv1alpha1.PublishedResource
1329-
remoteObject *unstructured.Unstructured
1330-
localObject *unstructured.Unstructured
1331-
existingState string
1332-
performRequeues bool
1333-
expectedRemoteObject *unstructured.Unstructured
1334-
expectedLocalObject *unstructured.Unstructured
1335-
expectedState string
1336-
customVerification func(t *testing.T, requeue bool, processErr error, finalRemoteObject *unstructured.Unstructured, finalLocalObject *unstructured.Unstructured, testcase testcase)
1337-
}
1338-
1339-
clusterName := logicalcluster.Name("testcluster")
1340-
1341-
remoteThingPR := &syncagentv1alpha1.PublishedResource{
1342-
Spec: syncagentv1alpha1.PublishedResourceSpec{
1343-
Resource: syncagentv1alpha1.SourceResourceDescriptor{
1344-
APIGroup: dummyv1alpha1.GroupName,
1345-
Version: dummyv1alpha1.GroupVersion,
1346-
Kind: "Thing",
1347-
},
1348-
Projection: &syncagentv1alpha1.ResourceProjection{
1349-
Kind: "RemoteThing",
1350-
},
1351-
// include explicit naming rules to be independent of possible changes to the defaults
1352-
Naming: &syncagentv1alpha1.ResourceNaming{
1353-
Name: "$remoteClusterName-$remoteName", // Things are Cluster-scoped
1354-
},
1355-
Related: []syncagentv1alpha1.RelatedResourceSpec{
1356-
{
1357-
Identifier: "mandatory-secret",
1358-
Origin: "service",
1359-
Kind: "Thing",
1360-
Reference: syncagentv1alpha1.RelatedResourceReference{
1361-
Name: syncagentv1alpha1.ResourceLocator{
1362-
Path: "spec.otherTest.name",
1363-
},
1364-
Namespace: &syncagentv1alpha1.ResourceLocator{
1365-
Path: "spec.otherTest.namespace",
1366-
},
1367-
},
1368-
Optional: false,
1369-
},
1370-
{
1371-
Identifier: "optional-secret",
1372-
Origin: "kcp",
1373-
Kind: "Thing",
1374-
Reference: syncagentv1alpha1.RelatedResourceReference{
1375-
Name: syncagentv1alpha1.ResourceLocator{
1376-
Path: "spec.test.name",
1377-
},
1378-
Namespace: &syncagentv1alpha1.ResourceLocator{
1379-
Path: "spec.test.namespace",
1380-
},
1381-
},
1382-
Optional: true,
1383-
},
1384-
},
1385-
},
1386-
}
1387-
1388-
testcases := []testcase{
1389-
{
1390-
name: "optional related resource does not exist",
1391-
remoteAPIGroup: "remote.example.corp",
1392-
localCRD: loadCRD("things"),
1393-
pubRes: remoteThingPR,
1394-
performRequeues: true,
1395-
1396-
remoteObject: newUnstructured(&dummyv1alpha1.Thing{
1397-
ObjectMeta: metav1.ObjectMeta{
1398-
Name: "my-test-thing",
1399-
},
1400-
Spec: dummyv1alpha1.ThingSpec{
1401-
Username: "Colonel Mustard",
1402-
},
1403-
}, withGroupKind("remote.example.corp", "RemoteThing")),
1404-
localObject: nil,
1405-
existingState: "",
1406-
1407-
expectedRemoteObject: newUnstructured(&dummyv1alpha1.Thing{
1408-
ObjectMeta: metav1.ObjectMeta{
1409-
Name: "my-test-thing",
1410-
Finalizers: []string{
1411-
deletionFinalizer,
1412-
},
1413-
},
1414-
Spec: dummyv1alpha1.ThingSpec{
1415-
Username: "Colonel Mustard",
1416-
},
1417-
}, withGroupKind("remote.example.corp", "RemoteThing")),
1418-
expectedLocalObject: newUnstructured(&dummyv1alpha1.Thing{
1419-
ObjectMeta: metav1.ObjectMeta{
1420-
Name: "testcluster-my-test-thing",
1421-
Labels: map[string]string{
1422-
agentNameLabel: "textor-the-doctor",
1423-
remoteObjectClusterLabel: "testcluster",
1424-
remoteObjectNameHashLabel: "c346c8ceb5d104cc783d09b95e8ea7032c190948",
1425-
},
1426-
Annotations: map[string]string{
1427-
remoteObjectNameAnnotation: "my-test-thing",
1428-
},
1429-
},
1430-
Spec: dummyv1alpha1.ThingSpec{
1431-
Username: "Colonel Mustard",
1432-
},
1433-
}),
1434-
expectedState: `{"apiVersion":"remote.example.corp/v1alpha1","kind":"RemoteThing","metadata":{"name":"my-test-thing"},"spec":{"username":"Colonel Mustard"}}`,
1435-
},
1436-
{
1437-
name: "mandatory related resource does not exist",
1438-
remoteAPIGroup: "remote.example.corp",
1439-
localCRD: loadCRD("things"),
1440-
pubRes: remoteThingPR,
1441-
performRequeues: true,
1442-
1443-
remoteObject: newUnstructured(&dummyv1alpha1.Thing{
1444-
ObjectMeta: metav1.ObjectMeta{
1445-
Name: "my-test-thing",
1446-
},
1447-
Spec: dummyv1alpha1.ThingSpec{
1448-
Username: "Colonel Mustard",
1449-
},
1450-
}, withGroupKind("remote.example.corp", "RemoteThing")),
1451-
localObject: nil,
1452-
existingState: "",
1453-
1454-
expectedRemoteObject: newUnstructured(&dummyv1alpha1.Thing{
1455-
ObjectMeta: metav1.ObjectMeta{
1456-
Name: "my-test-thing",
1457-
Finalizers: []string{
1458-
deletionFinalizer,
1459-
},
1460-
},
1461-
Spec: dummyv1alpha1.ThingSpec{
1462-
Username: "Colonel Mustard",
1463-
},
1464-
}, withGroupKind("remote.example.corp", "RemoteThing")),
1465-
expectedLocalObject: newUnstructured(&dummyv1alpha1.Thing{
1466-
ObjectMeta: metav1.ObjectMeta{
1467-
Name: "testcluster-my-test-thing",
1468-
Labels: map[string]string{
1469-
agentNameLabel: "textor-the-doctor",
1470-
remoteObjectClusterLabel: "testcluster",
1471-
remoteObjectNameHashLabel: "c346c8ceb5d104cc783d09b95e8ea7032c190948",
1472-
},
1473-
Annotations: map[string]string{
1474-
remoteObjectNameAnnotation: "my-test-thing",
1475-
},
1476-
},
1477-
Spec: dummyv1alpha1.ThingSpec{
1478-
Username: "Colonel Mustard",
1479-
},
1480-
}),
1481-
expectedState: `{"apiVersion":"remote.example.corp/v1alpha1","kind":"RemoteThing","metadata":{"name":"my-test-thing"},"spec":{"username":"Colonel Mustard"}}`,
1482-
},
1483-
}
1484-
1485-
const stateNamespace = "kcp-system"
1486-
1487-
for _, testcase := range testcases {
1488-
t.Run(testcase.name, func(t *testing.T) {
1489-
localClient := buildFakeClient(testcase.localObject)
1490-
remoteClient := buildFakeClient(testcase.remoteObject)
1491-
1492-
syncer, err := NewResourceSyncer(
1493-
// zap.Must(zap.NewDevelopment()).Sugar(),
1494-
zap.NewNop().Sugar(),
1495-
localClient,
1496-
remoteClient,
1497-
testcase.pubRes,
1498-
testcase.localCRD,
1499-
testcase.remoteAPIGroup,
1500-
nil,
1501-
stateNamespace,
1502-
"textor-the-doctor",
1503-
)
1504-
if err != nil {
1505-
t.Fatalf("Failed to create syncer: %v", err)
1506-
}
1507-
1508-
localCtx := context.Background()
1509-
remoteCtx := kontext.WithCluster(localCtx, clusterName)
1510-
ctx := NewContext(localCtx, remoteCtx)
1511-
1512-
// setup a custom state backend that we can prime
1513-
var backend *kubernetesBackend
1514-
syncer.newObjectStateStore = func(primaryObject, stateCluster syncSide) ObjectStateStore {
1515-
// .Process() is called multiple times, but we want the state to persist between reconciles.
1516-
if backend == nil {
1517-
backend = newKubernetesBackend(stateNamespace, primaryObject, stateCluster)
1518-
if testcase.existingState != "" {
1519-
if err := backend.Put(testcase.remoteObject, clusterName, []byte(testcase.existingState)); err != nil {
1520-
t.Fatalf("Failed to prime state store: %v", err)
1521-
}
1522-
}
1523-
}
1524-
1525-
return &objectStateStore{
1526-
backend: backend,
1527-
}
1528-
}
1529-
1530-
var requeue bool
1531-
1532-
if testcase.performRequeues {
1533-
target := testcase.remoteObject.DeepCopy()
1534-
1535-
for i := 0; true; i++ {
1536-
if i > 20 {
1537-
t.Fatalf("Detected potential infinite loop, stopping after %d requeues.", i)
1538-
}
1539-
1540-
requeue, err = syncer.Process(ctx, target)
1541-
if err != nil {
1542-
break
1543-
}
1544-
1545-
if !requeue {
1546-
break
1547-
}
1548-
1549-
if err = remoteClient.Get(remoteCtx, ctrlruntimeclient.ObjectKeyFromObject(target), target); err != nil {
1550-
// it's possible for the processing to have deleted the remote object,
1551-
// so a NotFound is valid here
1552-
if apierrors.IsNotFound(err) {
1553-
break
1554-
}
1555-
1556-
t.Fatalf("Failed to get updated remote object: %v", err)
1557-
}
1558-
}
1559-
} else {
1560-
requeue, err = syncer.Process(ctx, testcase.remoteObject)
1561-
}
1562-
1563-
finalRemoteObject, getErr := getFinalObjectVersion(remoteCtx, remoteClient, testcase.remoteObject, testcase.expectedRemoteObject)
1564-
if getErr != nil {
1565-
t.Fatalf("Failed to get final remote object: %v", getErr)
1566-
}
1567-
1568-
finalLocalObject, getErr := getFinalObjectVersion(localCtx, localClient, testcase.localObject, testcase.expectedLocalObject)
1569-
if getErr != nil {
1570-
t.Fatalf("Failed to get final local object: %v", getErr)
1571-
}
1572-
1573-
if testcase.customVerification != nil {
1574-
testcase.customVerification(t, requeue, err, finalRemoteObject, finalLocalObject, testcase)
1575-
} else {
1576-
if err != nil {
1577-
t.Fatalf("Processing failed: %v", err)
1578-
}
1579-
1580-
assertObjectsEqual(t, "local", testcase.expectedLocalObject, finalLocalObject)
1581-
assertObjectsEqual(t, "remote", testcase.expectedRemoteObject, finalRemoteObject)
1582-
1583-
if testcase.expectedState != "" {
1584-
if backend == nil {
1585-
t.Fatal("Cannot check object state, state store was never instantiated.")
1586-
}
1587-
1588-
finalState, err := backend.Get(testcase.expectedRemoteObject, clusterName)
1589-
if err != nil {
1590-
t.Fatalf("Failed to get final state: %v", err)
1591-
} else if !bytes.Equal(finalState, []byte(testcase.expectedState)) {
1592-
t.Fatalf("States do not match:\n%s", diff.StringDiff(testcase.expectedState, string(finalState)))
1593-
}
1594-
}
1595-
}
1596-
})
1597-
}
1598-
}
15991323

16001324
func assertObjectsEqual(t *testing.T, kind string, expected, actual *unstructured.Unstructured) {
16011325
if expected == nil {

sdk/applyconfiguration/syncagent/v1alpha1/relatedresourcespec.go

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)