@@ -31,53 +31,60 @@ import (
3131
3232 "github.com/kcp-dev/api-syncagent/internal/test/diff"
3333 syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
34+ "github.com/kcp-dev/api-syncagent/test/crds"
3435 "github.com/kcp-dev/api-syncagent/test/utils"
3536
3637 corev1 "k8s.io/api/core/v1"
38+ apierrors "k8s.io/apimachinery/pkg/api/errors"
3739 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3940 "k8s.io/apimachinery/pkg/runtime/schema"
40- "k8s.io/apimachinery/pkg/types"
4141 "k8s.io/apimachinery/pkg/util/wait"
4242 ctrlruntime "sigs.k8s.io/controller-runtime"
43+ ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
4344 "sigs.k8s.io/controller-runtime/pkg/kontext"
4445)
4546
46- func TestSyncSecretBackToKcp (t * testing.T ) {
47- const (
48- apiExportName = "kcp.example.com"
49- orgWorkspace = "sync-related-secret-to-kcp"
50- )
47+ func TestSyncRelatedObjects (t * testing.T ) {
48+ const apiExportName = "kcp.example.com"
5149
52- ctx := context .Background ()
5350 ctrlruntime .SetLogger (logr .Discard ())
5451
55- // setup a test environment in kcp
56- orgKubconfig := utils .CreateOrganization (t , ctx , orgWorkspace , apiExportName )
52+ testcases := []struct {
53+ // the name of this testcase
54+ name string
55+ //the org workspace everything should happen in
56+ workspace logicalcluster.Name
57+ // the configuration for the related resource
58+ relatedConfig syncagentv1alpha1.RelatedResourceSpec
59+ // the primary object created by the user in kcp
60+ mainResource crds.Crontab
61+ // the original related object (will automatically be created on either the
62+ // kcp or service side, depending on the relatedConfig above)
63+ sourceRelatedObject corev1.Secret
5764
58- // start a service cluster
59- envtestKubeconfig , envtestClient , _ := utils . RunEnvtest ( t , [] string {
60- "test/crds/crontab.yaml" ,
61- })
62-
63- // publish Crontabs and Backups
64- t . Logf ( "Publishing CRDs…" )
65- prCrontabs := & syncagentv1alpha1. PublishedResource {
66- ObjectMeta : metav1. ObjectMeta {
67- Name : "publish-crontabs" ,
68- } ,
69- Spec : syncagentv1alpha1. PublishedResourceSpec {
70- Resource : syncagentv1alpha1. SourceResourceDescriptor {
71- APIGroup : "example.com" ,
72- Version : "v1 " ,
73- Kind : "CronTab " ,
74- },
75- // These rules make finding the local object easier, but should not be used in production.
76- Naming : & syncagentv1alpha1. ResourceNaming {
77- Name : "$remoteName " ,
78- Namespace : "synced-$remoteNamespace" ,
65+ // expectation: this is how the copy of the related object should look
66+ // like after the sync has completed
67+ expectedSyncedRelatedObject corev1. Secret
68+ // expectation: how the original primary object should have been updated
69+ // (not the primary object's copy, but the source)
70+ //
71+ // not yet implemented
72+ // expectedUpdatedMainObject crds.Crontab
73+ } {
74+ {
75+ name : "sync referenced Secret up from service cluster to kcp" ,
76+ workspace : "sync-referenced-secret-up" ,
77+ mainResource : crds. Crontab {
78+ ObjectMeta : metav1. ObjectMeta {
79+ Name : "my-crontab " ,
80+ Namespace : "default " ,
81+ },
82+ Spec : crds. CrontabSpec {
83+ CronSpec : "* * *" ,
84+ Image : "ubuntu:latest " ,
85+ } ,
7986 },
80- Related : [] syncagentv1alpha1.RelatedResourceSpec { {
87+ relatedConfig : syncagentv1alpha1.RelatedResourceSpec {
8188 Identifier : "credentials" ,
8289 Origin : "service" ,
8390 Kind : "Secret" ,
@@ -101,106 +108,152 @@ func TestSyncSecretBackToKcp(t *testing.T) {
101108 },
102109 },
103110 },
104- }},
111+ },
112+ sourceRelatedObject : corev1.Secret {
113+ ObjectMeta : metav1.ObjectMeta {
114+ Name : "my-credentials" ,
115+ Namespace : "synced-default" ,
116+ },
117+ Data : map [string ][]byte {
118+ "password" : []byte ("hunter2" ),
119+ },
120+ Type : corev1 .SecretTypeOpaque ,
121+ },
122+
123+ expectedSyncedRelatedObject : corev1.Secret {
124+ ObjectMeta : metav1.ObjectMeta {
125+ Name : "my-credentials" ,
126+ Namespace : "default" ,
127+ },
128+ Data : map [string ][]byte {
129+ "password" : []byte ("hunter2" ),
130+ },
131+ Type : corev1 .SecretTypeOpaque ,
132+ },
105133 },
106134 }
107135
108- if err := envtestClient . Create ( ctx , prCrontabs ); err != nil {
109- t .Fatalf ( "Failed to create PublishedResource: %v" , err )
110- }
136+ for _ , testcase := range testcases {
137+ t .Run ( testcase . name , func ( t * testing. T ) {
138+ ctx := context . Background ()
111139
112- // start the agent in the background to update the APIExport with the CronTabs API
113- utils .RunAgent (ctx , t , "bob" , orgKubconfig , envtestKubeconfig , apiExportName )
114-
115- // wait until the API is available
116- teamCtx := kontext .WithCluster (ctx , logicalcluster .Name (fmt .Sprintf ("root:%s:team-1" , orgWorkspace )))
117- kcpClient := utils .GetKcpAdminClusterClient (t )
118- utils .WaitForBoundAPI (t , teamCtx , kcpClient , schema.GroupVersionResource {
119- Group : apiExportName ,
120- Version : "v1" ,
121- Resource : "crontabs" ,
122- })
123-
124- // create a Crontab object in a team workspace
125- t .Log ("Creating CronTab in kcp…" )
126- crontab := yamlToUnstructured (t , `
127- apiVersion: kcp.example.com/v1
128- kind: CronTab
129- metadata:
130- namespace: default
131- name: my-crontab
132- spec:
133- cronSpec: '* * *'
134- image: ubuntu:latest
135- ` )
136-
137- if err := kcpClient .Create (teamCtx , crontab ); err != nil {
138- t .Fatalf ("Failed to create CronTab in kcp: %v" , err )
139- }
140+ // setup a test environment in kcp
141+ orgKubconfig := utils .CreateOrganization (t , ctx , testcase .workspace , apiExportName )
140142
141- // fake operator: create a credential Secret
142- t . Log ( "Creating credential Secret in service cluster…" )
143- namespace := & corev1. Namespace {}
144- namespace . Name = "synced-default"
143+ // start a service cluster
144+ envtestKubeconfig , envtestClient , _ := utils . RunEnvtest ( t , [] string {
145+ "test/crds/crontab.yaml" ,
146+ })
145147
146- if err := envtestClient .Create (ctx , namespace ); err != nil {
147- t .Fatalf ("Failed to create namespace in kcp: %v" , err )
148- }
148+ // publish Crontabs and Backups
149+ t .Logf ("Publishing CRDs…" )
150+ prCrontabs := & syncagentv1alpha1.PublishedResource {
151+ ObjectMeta : metav1.ObjectMeta {
152+ Name : "publish-crontabs" ,
153+ },
154+ Spec : syncagentv1alpha1.PublishedResourceSpec {
155+ Resource : syncagentv1alpha1.SourceResourceDescriptor {
156+ APIGroup : "example.com" ,
157+ Version : "v1" ,
158+ Kind : "CronTab" ,
159+ },
160+ // These rules make finding the local object easier, but should not be used in production.
161+ Naming : & syncagentv1alpha1.ResourceNaming {
162+ Name : "$remoteName" ,
163+ Namespace : "synced-$remoteNamespace" ,
164+ },
165+ Related : []syncagentv1alpha1.RelatedResourceSpec {testcase .relatedConfig },
166+ },
167+ }
149168
150- credentials := & corev1.Secret {}
151- credentials .Name = "my-credentials"
152- credentials .Namespace = namespace .Name
153- credentials .Labels = map [string ]string {
154- "hello" : "world" ,
155- }
156- credentials .Data = map [string ][]byte {
157- "password" : []byte ("hunter2" ),
158- }
169+ if err := envtestClient .Create (ctx , prCrontabs ); err != nil {
170+ t .Fatalf ("Failed to create PublishedResource: %v" , err )
171+ }
159172
160- if err := envtestClient .Create (ctx , credentials ); err != nil {
161- t .Fatalf ("Failed to create Secret in service cluster: %v" , err )
162- }
173+ // start the agent in the background to update the APIExport with the CronTabs API
174+ utils .RunAgent (ctx , t , "bob" , orgKubconfig , envtestKubeconfig , apiExportName )
163175
164- // wait for the agent to sync the object down into the service cluster and
165- // the Secret back up to kcp
166- t .Logf ("Wait for CronTab/Secret to be synced…" )
167- copy := & unstructured.Unstructured {}
168- copy .SetAPIVersion ("example.com/v1" )
169- copy .SetKind ("CronTab" )
170-
171- err := wait .PollUntilContextTimeout (ctx , 500 * time .Millisecond , 30 * time .Second , false , func (ctx context.Context ) (done bool , err error ) {
172- copyKey := types.NamespacedName {Namespace : "synced-default" , Name : "my-crontab" }
173- return envtestClient .Get (ctx , copyKey , copy ) == nil , nil
174- })
175- if err != nil {
176- t .Fatalf ("Failed to wait for CronTab to be synced down: %v" , err )
177- }
176+ // wait until the API is available
177+ teamCtx := kontext .WithCluster (ctx , logicalcluster .Name (fmt .Sprintf ("root:%s:team-1" , testcase .workspace )))
178+ kcpClient := utils .GetKcpAdminClusterClient (t )
179+ utils .WaitForBoundAPI (t , teamCtx , kcpClient , schema.GroupVersionResource {
180+ Group : apiExportName ,
181+ Version : "v1" ,
182+ Resource : "crontabs" ,
183+ })
178184
179- copySecret := & corev1.Secret {}
185+ // create a Crontab object in a team workspace
186+ t .Log ("Creating CronTab in kcp…" )
180187
181- err = wait .PollUntilContextTimeout (ctx , 500 * time .Millisecond , 30 * time .Second , false , func (ctx context.Context ) (done bool , err error ) {
182- copyKey := types.NamespacedName {Namespace : "default" , Name : "my-credentials" }
183- return kcpClient .Get (teamCtx , copyKey , copySecret ) == nil , nil
184- })
185- if err != nil {
186- t .Fatalf ("Failed to wait for Secret to be synced up: %v" , err )
187- }
188+ crontab := utils .ToUnstructured (t , & testcase .mainResource )
189+ crontab .SetAPIVersion ("kcp.example.com/v1" )
190+ crontab .SetKind ("CronTab" )
188191
189- // ensure the secret in kcp does not have any sync-related metadata
190- maps .DeleteFunc (copySecret .Labels , func (k , v string ) bool {
191- return strings .HasPrefix (k , "claimed.internal.apis.kcp.io/" )
192- })
192+ if err := kcpClient .Create (teamCtx , crontab ); err != nil {
193+ t .Fatalf ("Failed to create CronTab in kcp: %v" , err )
194+ }
193195
194- if changes := diff .ObjectDiff (credentials .Labels , copySecret .Labels ); changes != "" {
195- t .Errorf ("Secret in kcp has unexpected labels:\n %s" , changes )
196- }
196+ // fake operator: create a credential Secret
197+ t .Logf ("Creating credential Secret on the %s side…" , testcase .relatedConfig .Origin )
198+
199+ originClient := envtestClient
200+ originContext := ctx
201+ destClient := kcpClient
202+ destContext := teamCtx
197203
198- delete (copySecret .Annotations , "kcp.io/cluster" )
199- if len (copySecret .Annotations ) == 0 {
200- copySecret .Annotations = nil
204+ if testcase .relatedConfig .Origin == "kcp" {
205+ originClient , destClient = destClient , originClient
206+ originContext , destContext = destContext , originContext
207+ }
208+
209+ ensureNamespace (t , originContext , originClient , testcase .sourceRelatedObject .Namespace )
210+
211+ if err := originClient .Create (originContext , & testcase .sourceRelatedObject ); err != nil {
212+ t .Fatalf ("Failed to create Secret: %v" , err )
213+ }
214+
215+ // wait for the agent to do its magic
216+ t .Log ("Wait for Secret to be synced…" )
217+ copySecret := & corev1.Secret {}
218+ err := wait .PollUntilContextTimeout (destContext , 500 * time .Millisecond , 30 * time .Second , false , func (ctx context.Context ) (done bool , err error ) {
219+ copyKey := ctrlruntimeclient .ObjectKeyFromObject (& testcase .expectedSyncedRelatedObject )
220+ return destClient .Get (ctx , copyKey , copySecret ) == nil , nil
221+ })
222+ if err != nil {
223+ t .Fatalf ("Failed to wait for Secret to be synced: %v" , err )
224+ }
225+
226+ // ensure the secret in kcp does not have any sync-related metadata
227+ maps .DeleteFunc (copySecret .Labels , func (k , v string ) bool {
228+ return strings .HasPrefix (k , "claimed.internal.apis.kcp.io/" )
229+ })
230+
231+ delete (copySecret .Annotations , "kcp.io/cluster" )
232+ if len (copySecret .Annotations ) == 0 {
233+ copySecret .Annotations = nil
234+ }
235+
236+ orig := testcase .expectedSyncedRelatedObject
237+ copySecret .CreationTimestamp = orig .CreationTimestamp
238+ copySecret .Generation = orig .Generation
239+ copySecret .ResourceVersion = orig .ResourceVersion
240+ copySecret .ManagedFields = orig .ManagedFields
241+ copySecret .UID = orig .UID
242+
243+ if changes := diff .ObjectDiff (orig , copySecret ); changes != "" {
244+ t .Errorf ("Synced secret does not match expected Secret:\n %s" , changes )
245+ }
246+ })
201247 }
248+ }
249+
250+ func ensureNamespace (t * testing.T , ctx context.Context , client ctrlruntimeclient.Client , name string ) {
251+ namespace := & corev1.Namespace {}
252+ namespace .Name = name
202253
203- if changes := diff .ObjectDiff (credentials .Annotations , copySecret .Annotations ); changes != "" {
204- t .Errorf ("Secret in kcp has unexpected annotations:\n %s" , changes )
254+ if err := client .Create (ctx , namespace ); err != nil {
255+ if ! apierrors .IsAlreadyExists (err ) {
256+ t .Fatalf ("Failed to create namespace %s in kcp: %v" , name , err )
257+ }
205258 }
206259}
0 commit comments