Skip to content

Commit 8eb620d

Browse files
committed
Merge branch 'main' into feat/docs-pages
Signed-off-by: Mirza Kopic <[email protected]>
2 parents f61bcb2 + e6be33c commit 8eb620d

38 files changed

+2012
-552
lines changed

.github/pull_request_template.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
<!--
2-
Thanks for creating a pull request!
32
3+
Thanks for creating a pull request!
44
If this is your first time, please make sure to review CONTRIBUTING.MD.
55
6-
Please copy the appropriate `:text:` or icon to the beginning of your PR title:
6+
-->
7+
8+
## Summary
9+
10+
## What Type of PR Is This?
11+
12+
<!--
713
8-
:sparkles: ✨ feature
9-
:bug: 🐛 bug fix
10-
:book: 📖 docs
11-
:memo: 📝 proposal
12-
:warning: ⚠️ breaking change
13-
:seedling: 🌱 other/misc
14-
:question: ❓ requires manual review/categorization
14+
Add one of the following kinds:
15+
/kind bug
16+
/kind cleanup
17+
/kind documentation
18+
/kind feature
19+
20+
Optionally add one or more of the following kinds if applicable:
21+
/kind api-change
22+
/kind deprecation
23+
/kind failing-test
24+
/kind flake
25+
/kind regression
1526
1627
-->
17-
## Summary
1828

19-
## Related issue(s)
29+
## Related Issue(s)
2030

2131
Fixes #
2232

.github/workflows/pr-verifier.yaml

Lines changed: 0 additions & 23 deletions
This file was deleted.

cmd/api-syncagent/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func run(ctx context.Context, log *zap.SugaredLogger, opts *Options) error {
117117
return fmt.Errorf("failed to resolve APIExport: %w", err)
118118
}
119119

120-
log.Infow("Resolved APIExport", "apigroup", opts.APIExportRef, "workspace", lcPath, "logicalcluster", lcName)
120+
log.Infow("Resolved APIExport", "workspace", lcPath, "logicalcluster", lcName)
121121

122122
// init the "permanent" kcp cluster connection
123123
kcpCluster, err := setupKcpCluster(kcpRestConfig, opts)
@@ -131,7 +131,7 @@ func run(ctx context.Context, log *zap.SugaredLogger, opts *Options) error {
131131
return fmt.Errorf("failed to add kcp cluster runnable: %w", err)
132132
}
133133

134-
if err := apiresourceschema.Add(mgr, kcpCluster, lcName, log, 4, opts.AgentName, opts.APIExportRef, opts.PublishedResourceSelector); err != nil {
134+
if err := apiresourceschema.Add(mgr, kcpCluster, lcName, log, 4, opts.AgentName, opts.PublishedResourceSelector); err != nil {
135135
return fmt.Errorf("failed to add apiresourceschema controller: %w", err)
136136
}
137137

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

Lines changed: 224 additions & 18 deletions
Large diffs are not rendered by default.

docs/content/getting-started.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ the RBAC rules that grant the Agent access.
151151

152152
The Sync Agent needs to
153153

154+
* access the workspace of its `APIExport`,
155+
* get the `LogicalCluster`,
154156
* manage its `APIExport`,
155157
* manage `APIResourceSchemas` and
156158
* access the virtual workspace for its `APIExport`.
@@ -163,6 +165,15 @@ kind: ClusterRole
163165
metadata:
164166
name: api-syncagent-mango
165167
rules:
168+
# get the LogicalCluster
169+
- apiGroups:
170+
- core.kcp.io
171+
resources:
172+
- logicalclusters
173+
resourceNames:
174+
- cluster
175+
verbs:
176+
- get
166177
# manage its APIExport
167178
- apiGroups:
168179
- apis.kcp.io
@@ -200,14 +211,27 @@ rules:
200211
apiVersion: rbac.authorization.k8s.io/v1
201212
kind: ClusterRoleBinding
202213
metadata:
203-
name: api-syncagent-columbo:mango-system
214+
name: api-syncagent-mango:system
204215
roleRef:
205216
apiGroup: rbac.authorization.k8s.io
206217
kind: ClusterRole
207218
name: api-syncagent-mango
208219
subjects:
209220
- kind: User
210221
name: api-syncagent-mango
222+
223+
---
224+
apiVersion: rbac.authorization.k8s.io/v1
225+
kind: ClusterRoleBinding
226+
metadata:
227+
name: api-syncagent-mango:access
228+
roleRef:
229+
apiGroup: rbac.authorization.k8s.io
230+
kind: ClusterRole
231+
name: system:kcp:workspace:access
232+
subjects:
233+
- kind: User
234+
name: api-syncagent-mango
211235
```
212236

213237
## Publish Resources

docs/content/publish-resources.md

Lines changed: 142 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -228,25 +228,42 @@ Likewise it's possible for auxiliary resources having to be created by the user,
228228
the user has to provide credentials.
229229

230230
To handle these cases, a `PublishedResource` can define multiple "related resources". Each related
231-
resource currently represents exactly one object to synchronize between user workspace and service
232-
cluster (i.e. you cannot express "sync all Secrets"). While the main published resource sync is
233-
always workspace->service cluster, related resources can originate on either side and so either can
234-
work as the source of truth.
231+
resource represents usually one, but can be multiple objects to synchronize between user workspace
232+
and service cluster. While the main published resource sync is always workspace->service cluster,
233+
related resources can originate on either side and so either can work as the source of truth.
235234

236235
At the moment, only `ConfigMaps` and `Secrets` are allowed related resource kinds.
237236

238-
For each related resource, the Sync Agent needs to be told their name/namespace. This is done by
239-
selecting a field in the main resource (for a `Certificate` this would mean `spec.secretName`). Both
240-
name and namespace need to be part of the main object (or be fixed values, like a hardcoded
241-
`kube-system` namespace).
237+
For each related resource, the Sync Agent needs to be told how to find the object on the origin side
238+
and where to create it on the destination side. There are multiple options that you can choose from.
242239

243-
The path expressions for name and namespace are evaluated against the main object on either side
244-
to determine their values. So if you had a `Certificate` in your workspace with
245-
`spec.secretName = "my-cert"` and after syncing it down, the copy on the service cluster has a
246-
rewritten/mutated `spec.secretName = "jk23h4wz47329rz2r72r92-cert"` (e.g. to prevent naming
247-
collisions), the expression `spec.secretName` would yield `"my-cert"` for the name in the workspace
248-
and `"jk...."` as the name on the service cluster. Once the object exists with that name on the
249-
originating side, the Sync Agent will begin to sync it to the other side.
240+
By default all related objects live in the same namespace as the primary object (their owner/parent).
241+
If the primary object is cluster scoped, admins must configure additional rules to specify what
242+
namespace the ConfigMap/Secret shall be read from and created in.
243+
244+
Related resources are always optional. Even if references (see below) are used and their path
245+
expression points to a non-existing field in the primary object (e.g. `spec.secretName` is configured,
246+
but that field does not exist in Certificate object), this will simply be treated as "not _yet_
247+
existing" and not create an error.
248+
249+
#### References
250+
251+
A reference is a JSONPath-like expression that are evaluated on both sides of the synchronization.
252+
You configure a single path expression (like `spec.secretName`) and the sync agent will evaluate it
253+
in the original primary object (in kcp) and again in the copied primary object (on the service
254+
cluster). Since the primary object has already been mutated, the `spec.secretName` is already
255+
rewritten/adjusted to work on the service cluster (for example it was changed from `my-secret` to
256+
`jk23h4wz47329rz2r72r92-secret` on the service cluster side). By doing it this way, admins only have
257+
to think about mutations and rewrites once (when configuring the primary object in the
258+
PublishedResource) and the path will yield 2 ready to use values (`my-secret` and the computed value).
259+
260+
The value selected by the path expression must be a string (or number, but it will be coalesced into
261+
a string) and can then be further adjusted by applying a regular expression to it.
262+
263+
References can only ever select 1 related object. Their upside is that they are simple to understand
264+
and easy to use, but require a "link" in the primary object that would point to the related object.
265+
266+
Here's an example on how to use references to locate the related object.
250267

251268
```yaml
252269
apiVersion: syncagent.kcp.io/v1alpha1
@@ -279,10 +296,11 @@ spec:
279296
# there is no GVK projection for related resources
280297
kind: Secret
281298
282-
# configure where in the parent object we can find
283-
# the name/namespace of the related resource (the child)
284-
reference:
285-
name:
299+
# configure where in the parent object we can find the child object
300+
object:
301+
# Object can use either reference, labelSelector or expressions. In this
302+
# example we use references.
303+
reference:
286304
# This path is evaluated in both the local and remote objects, to figure out
287305
# the local and remote names for the related object. This saves us from having
288306
# to remember mutated fields before their mutation (similar to the last-known
@@ -291,22 +309,115 @@ spec:
291309
292310
# namespace part is optional; if not configured,
293311
# Sync Agent assumes the same namespace as the owning resource
294-
#
295312
# namespace:
296-
# path: spec.secretName
297-
# regex:
298-
# pattern: '...'
299-
# replacement: '...'
300-
#
301-
# to inject static values, select a meaningless string value
302-
# and leave the pattern empty
303-
#
313+
# reference:
314+
# path: spec.secretName
315+
# regex:
316+
# pattern: '...'
317+
# replacement: '...'
318+
```
319+
320+
#### Label Selectors
321+
322+
In some cases, the primary object does not have a link to its child/children objects. In these cases,
323+
a label selector can be used. This allows to configure the labels that any related object must have
324+
to be included.
325+
326+
Notably, this allows for _multiple_ objects that are synced for a single configured related resource.
327+
The sync agent will not prevent misconfigurations, so great care must be taken when configuring
328+
selectors to not accidentally include too many objects.
329+
330+
Additionally, it is assumed that
331+
332+
* Primary objects synced from kcp to a service cluster will be renamed, to prevent naming collisions.
333+
* The renamed objects on the service cluster might contain private, sensitive information that should
334+
not be leaked into kcp workspaces.
335+
* When there is no explicit name being requested (like by setting `spec.secretName`), it can be
336+
assumed that the operator on the service cluster that is actually processing the primary object
337+
will use the primary object's name (at least in parts) to construct the names of related objects,
338+
for example a Certificate `yaddasupersecretyadda` might automatically get a Secret created named
339+
`yaddasupersecretyadda-secret`.
340+
341+
Since the name of the related object must not leak into a kcp workspace, admins who configure a
342+
label selector also always have to provide a naming scheme for the copies of the related objects on
343+
the destination side.
344+
345+
Namespaces work the same as with references, i.e. by default the same namespace as the primary object
346+
is assumed. However you can actually also use label selectors to find the origin _namespaces_
347+
dynamically. So you can configure two label selectors, and then agent will first use the namespace
348+
selector to find all applicable namespaces, and then use the other label selector _in each of the
349+
applicable namespaces_ to finally locate the related objects. How useful this is depends a lot on
350+
how crazy the underlying operators on the service clusters are.
351+
352+
Here is an example on how to use label selectors:
353+
354+
```yaml
355+
apiVersion: syncagent.kcp.io/v1alpha1
356+
kind: PublishedResource
357+
metadata:
358+
name: publish-certmanager-certs
359+
spec:
360+
resource:
361+
kind: Certificate
362+
apiGroup: cert-manager.io
363+
version: v1
364+
365+
naming:
366+
namespace: kube-system
367+
name: "$remoteClusterName-$remoteNamespaceHash-$remoteNameHash"
368+
369+
related:
370+
- identifier: tls-secrets
371+
372+
# "service" or "kcp"
373+
origin: service
374+
375+
# for now, only "Secret" and "ConfigMap" are supported;
376+
# there is no GVK projection for related resources
377+
kind: Secret
378+
379+
# configure where in the parent object we can find the child object
380+
object:
381+
# A selector is a standard Kubernetes label selector, supporting
382+
# matchLabels and matchExpressions.
383+
selector:
384+
matchLabels:
385+
my-key: my-value
386+
another: pair
387+
388+
# You also need to provide rules on how objects found by this selector
389+
# should be named on the destination side of the sync.
390+
# Rewrites are either using regular expressions or templated strings,
391+
# never both.
392+
# The rewrite config is applied to each individual found object.
393+
rewrite:
394+
regex:
395+
pattern: "foo-(.+)"
396+
replacement: "bar-\\1"
397+
398+
# or
399+
template:
400+
template: "{{ .Name }}-foo"
401+
402+
# Like with references, the namespace can (or must) be configured explicitly.
403+
# You do not need to also use label selectors here, you can mix and match
404+
# freely.
304405
# namespace:
305-
# path: metadata.uid
306-
# regex:
307-
# replacement: kube-system
406+
# reference:
407+
# path: metadata.namespace
408+
# regex:
409+
# pattern: '...'
410+
# replacement: '...'
308411
```
309412

413+
#### Templates
414+
415+
The third option to configure how to find/create related objects are templates. These are simple
416+
Go template strings (like `{{ .Variable }}`) that allow to easily configure static values with a
417+
sprinkling of dynamic values.
418+
419+
This feature has not been fully implemented yet.
420+
310421
## Examples
311422

312423
### Provide Certificates

internal/controller/apiexport/reconciler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import (
3434
func (r *Reconciler) createAPIExportReconciler(availableResourceSchemas sets.Set[string], claimedResourceKinds sets.Set[string], agentName string, apiExportName string) reconciling.NamedAPIExportReconcilerFactory {
3535
return func() (string, reconciling.APIExportReconciler) {
3636
return apiExportName, func(existing *kcpdevv1alpha1.APIExport) (*kcpdevv1alpha1.APIExport, error) {
37-
known := sets.New[string](existing.Spec.LatestResourceSchemas...)
37+
known := sets.New(existing.Spec.LatestResourceSchemas...)
3838

3939
if existing.Annotations == nil {
4040
existing.Annotations = map[string]string{}

0 commit comments

Comments
 (0)