Skip to content

Claimed resources synchronization - #155

Closed
lhaendler wants to merge 27 commits into
kbind-dev:mainfrom
lhaendler:claimed_resources
Closed

Claimed resources synchronization#155
lhaendler wants to merge 27 commits into
kbind-dev:mainfrom
lhaendler:claimed_resources

Conversation

@lhaendler

@lhaendler lhaendler commented Mar 9, 2023

Copy link
Copy Markdown
Contributor

Discussion doc: https://docs.google.com/document/d/1Mfk2wLIURqAwSjoTimuZlL83BhslTiqV/edit#

Scope

The prototype only implements one basic use case: downsync of all resources of a type.

This includes association of the resources to the api export in the backend, synchronization by the konnector, and basic prompts for the claim in the bind cli

Current limitations

  • No filtering implemented
  • No support for adoption
  • No support for donation
  • No support for overrides, excludings, fields, alwaysRecreate

@lhaendler
lhaendler force-pushed the claimed_resources branch from c2f3eef to 12dcb1a Compare March 9, 2023 12:04
ClaimRejected AcceptablePermissionClaimState = "Rejected"
)

// PermissionClaim identifies an object by GR and identity hash.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// PermissionClaim identifies an object by GR and identity hash.
// PermissionClaim selects objects of a GVR that the service provider ...

type PermissionClaim struct {
GroupResource `json:","`

Version string `json:"version"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valdiation: required and non-empty

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we simply reference GroupVersionResource, why is GroupResource and Version separate?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-empty need validation, aka minlength

Version string `json:"version"`

// selector selects which resources are affected by this claim.
Selector ResourceSelector `json:"selector,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omitempty has no effect on structs. We could turn it into a pointer if that's prettier.

Selector ResourceSelector `json:"selector,omitempty"`

// required indicates whether the APIServiceBinding will work if this claim is not accepted.
Required bool `json:"required"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to make it opt in. It would make people think about whether it is actually needed. Since required claims cannot be denied, we should guide users to use required as little as needed by making the default false.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since required claims cannot be denied

we should make this fact part of the API doc.

// Global claims global resources for the given group/resource.
// This is mutually exclusive with resourceSelector.
// +optional
Global bool `json:"global,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this All?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly added it to support cluster-scoped resources. However I'm not sure if that's actually something we want to enable. The problem being that given any claim but single-name claims, the user could have access to cluster-scoped objects of other bind consumers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced we should not allow cluster scoped resources. If we do, we lift the concept of a namespace to be a tenancy boundary which might actually not be desired. If used i.e. with kcp, claiming cluster-scoped resources is actually fine, as tenancy boundaries are defined one level up, at the workspace level.

//
// create determines whether the kube-bind konnector will sync matching objects from the
// provider side down to the consumer cluster.
Create CreateOptions `json:"create"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this must be a pointer. There is no need that a service provider claims creation.


// adopt set to true means that objects created by the consumer are adopted by the provider.
// i.e. the provider will become the owner.
Adopt bool `json:"adopt"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omitempty


// update lists a number of claimed permissions for the provider.
// "field" and "preserving" are mutually exclusive.
Update UpdateOptions `json:"update"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must be a pointer, omitempty

Comment on lines +159 to +161
// providerOverrides will make the provider override any object that might already exist
// in the consumer cluster if it has the same namespaced name as a resource created by the
// provider.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// providerOverrides will make the provider override any object that might already exist
// in the consumer cluster if it has the same namespaced name as a resource created by the
// provider.
// providerOverrides will make the provider override any object that might already exist
// in the consumer cluster if it has the same namespaced name as a resource created by the
// provider, but is not the result of syncing.

type CreateOptions struct {
// donate set to true means that a newly created object by the provider is immediately owned by hte consumer.
// If false, the object stays in ownership of the provider
Donate bool `json:"donate"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omitempty

Comment on lines +164 to +165
// only for owner provider
// When recreateWhenConsumerSideDeleted is true the provider will recreate the object
// in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// only for owner provider
// When recreateWhenConsumerSideDeleted is true the provider will recreate the object
// in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally
// recreateWhenConsumerSideDeleted set to true means the provider will recreate the object
// in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally.

Comment on lines +180 to +181
// fields are the fields owned by the owner of the claim. If the owner sets values of those
// fields, they will be synced to the other participant.
// Mutually exclusive with preservings.

@sttts sttts Mar 9, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would not use the word "owner" here. The claim owner is always the provider, i.e. claims apply to the provider only.

Suggested change
// fields are the fields owned by the owner of the claim. If the owner sets values of those
// fields, they will be synced to the other participant.
// Mutually exclusive with preservings.
// fields are a list of JSON Paths describing which parts of an object the provider wants to control in case
// the object is owned by the consumer as a whole. This field cannot be set if provider owned objects
// are selected in this claim, and must be non-empty otherwise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The owner term was taken from the selector. It describes the binding party that, if not overridden, will be the side to create and delete the resource.
My interpretation of "fields" and "preserving" was that:
"fields" is a whitelist for synchronization, meaning everything in "fields" uses the owner of the claimed resource (owner can be consumer or provider) as the source of truth. Everything not in "fields" uses the other side (if the owner is consumer it used provider) as the source of truth.

"excludes" is a blacklist, meaning that fields that are referenced in "excludes" do not use the owner of the resource as the source of truth, and everything not in "excludes" uses the owner as the source of truth.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These claims are always from the point of view of the provider. There are two cases:

  1. the provider is owner => preserving defines exceptions that the provider (here the owner) does not claim to own
  2. the consumer is the owner => fields defines exceptions that the provider claims to own despite the consumer being the owner.

In other words: the subject of the claim is always the provider. There is no inversion of the subject when the owner changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are two points to be clarified here.

  1. Inversion of subject. I understand that we don't want to invert it.
  2. The blacklist/whitelist part.
    quoting parts of an example prompt:

read spec.nodeName and update status

How would this be expressed?

Comment on lines +185 to +186
// Preservings are the fields that are preserved by the konnector during synchronization.
// The owner is not able to set those fields. If the owner changes the value of these fields,
// their change will be overwritten.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Preservings are the fields that are preserved by the konnector during synchronization.
// The owner is not able to set those fields. If the owner changes the value of these fields,
// their change will be overwritten.
// preserving is a list of JSON Paths describing fields that should be preserved while updating with the
// object state on the provider side. This field cannot be set if consumer owned objects are selected in
// this claim.

Owner Owner `json:"owner"`

//
// WARNING: If adding new fields, add them to the XValidation check!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this apply?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. I would argue it makes sense to make the whole permissionClaim immutable though.
If we make it mutable, and simply take over the changes to the consumer, the provider could change the permission after it has been accepted, leading to a compromised consumer.

type Owner string

const Provider Owner = "Provider"
const Consumer Owner = "Consumer"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go doc

// +listType=map
// +listMapKey=group
// +listMapKey=resource
// +kubebuilder:validation:MaxItems=2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only 2?

// +optional
// +listType=map
// +listMapKey=group
// +listMapKey=resource

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't think we are able to define a key for this list. This is rather a set.

// a sensible default is chosen by the service provider.
Versions []string `json:"versions,omitempty"`
Versions []string `json:"versions,omitempty"`
PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc

@@ -0,0 +1,69 @@
/*
Copyright 2022 The Kube Bind Authors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2023

// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Established",type="string",JSONPath=`.status.conditions[?(@.type=="Established")].status`,priority=5
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,priority=0
type APIServiceExportTemplate struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is interesting, especially the selector. Don't think we have explored this side yet. Worth a discussion in the gdoc? My gut feeling is that this is really part of a demo backend, but could have different shape depending on context. For example the claims could differ depending on group membership or on input the UI.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a section on the gdoc called "Associating permissionClaims with Resources" which touches on this. I agree with the idea of making this part of the demo backend. Should I move the API to the example backend?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am actually not understanding why this is needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: a different API group example-backend.kube-bind.io/v1alpha1

type: object
x-kubernetes-validations:
- message: donate and adopt are mutually exclusive
rule: '!(self.create.donate && self.adopt)'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation broke the e2e tests, so i temporarily disabled it.


type OnConflictOptions struct {
// providerOverrides will make the provider override any object that might already exist
// in the consumer cluster if it has the same namespaced name as a resource created by the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the restriction to namespaced name ?

lhaendler added 7 commits June 6, 2023 11:32
Add new type APIServiceExportTemplate which provides a template for new APIServiceExports
the type associates the claimed resources with the CRD

Add claimed resource information to APIBinding and APIExport

Current Limitation:
Validation is commented out because the e2e testing infrastructure rejected the validation
for being too computationally expensive
CRDs to be displayed are now based on APIServiceExportTemplates and not on a label
Claimed resources are added to the APIServiceExport
Claimed resources are sent to the cli plugin
Add prompt for permission claim requests
Add permissionclaims including accepted state to APIBinding
Add a new controller and reconciler for downsyncing claimed resources
@lhaendler
lhaendler marked this pull request as ready for review August 16, 2023 15:26

// CreateFromFS creates the given CRDs using the target client from the
// provided filesystem and waits for it to become established. This call is blocking.
func CreateFromFS(ctx context.Context, client apiextensionsv1client.CustomResourceDefinitionInterface, fs embed.FS, grs ...metav1.GroupResource) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remind me where this code comes from and why we need it? Is it from kcp? Looks familar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const (
// GroupName is the group name used in this package
GroupName = "example.com"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example-backend.kube-bind.io would be better.

deleteServiceExportRequest: func(ctx context.Context, ns, name string) error {
return bindClient.KubeBindV1alpha1().APIServiceExportRequests(ns).Delete(ctx, name, metav1.DeleteOptions{})
},
crds: exporttemplate.NewCatalogue(config),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code follows American spelling, i.e. catalog

templates "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned"
)

type Index struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I can follow this construct. What is it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend needs to know which CRD get which claimedresources. I stored them in APIServiceExportTemplates. Index lets the backend get all exported CRDs (those that are referenced by a APIServiceExportTemplate), and to look up the template for a CRD so it can attach the claimed resources

Comment thread pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go
OnConflict *OnConflictOptions `json:"onConflict,omitempty"`

// update lists which updates to objects on the consumer cluster are claimed.
// By default, the whole object is synced, but metadata is not.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// By default, the whole object is synced, but metadata is not.
// By default, the whole object is continously synced, but metadata is not after creation.

Is this correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the reason for syncing metadata on creation, but not afterwards? IIRC we wanted to not sync metadata by default including creation

Comment on lines +228 to +230
// Even if the consumer mistakenly or intentionally
// deletes the object, the provider will recreate it. If the field is set as false,
// the provider will not recreate the object in case the object is deleted on the consumer cluster.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are redundant? Might have been old text in our gdoc. Or is there a detail I am missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, let's remove them from both places

Comment thread pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go Outdated
type UpdateOptions struct {
// fields are a list of JSON Paths describing which parts of an object the provider wants to control.
//
// This field is ignored if the owner in the claim selector is set to "Provider".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wording is strange. The selector selects objects, either consumer or provider owned. This here is about the object owner, not about the selector.

// Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces.
// If a resources namespace matches any value in namespaces, the resource namespace is considered matching.
// If the claim is for a cluster-scoped resource, namespaces has to explicitly be set to an empty array to prevent defaulting to "*".
// If the "names" field is unset, all objects of the group/resource within the listed namespaces (or cluster) will be claimed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

names cannot be unset. It's defaulted to *.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should we do for cluster-scoped objects?

// +optional
Namespaces []string `json:"namespaces,omitempty"`

// labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A disjunction I assume?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disjunction for items of the array, conjunction for key-value pairs in the map

// TODO
if onlyFirstServingVersion && !crdVersion.Storage {
continue
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would expect this to be a parameter to CRDToServiceExport and controlled through higher level means.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change can be removed. The intentions were the same as in #181

@@ -1,5 +1,5 @@
/*
Copyright 2022 The Kube Bind Authors.
Copyright 2023 The Kube Bind Authors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't update these for existing files.

@@ -0,0 +1,824 @@
/*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would love to see us merge servicebindings.go and this test, plus the API types first, to get this in step by step instead of 6000 lines at once.

}

func (opts *BindOptions) Cleanup() {
os.Remove(opts.outFile.Name())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is strange to be attached to options. Options are supposed to be just a data structure of flag values.

lhaendler and others added 4 commits October 18, 2023 14:47
Rename api group for backend configuration Custom Resources from
example.com to example-backend.kube-bind.io
Co-authored-by: Dr. Stefan Schimanski <stefan.schimanski@gmail.com>
Co-authored-by: Dr. Stefan Schimanski <stefan.schimanski@gmail.com>
@lhaendler lhaendler mentioned this pull request Oct 18, 2023
kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1"
)

const annotation = "kube-bind.io/resource-owner"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kube-bind.io/owner would be better

A missing nil check for the Selector led to controller crashes
@mjudeikis

Copy link
Copy Markdown
Contributor

implemented as part of #304

@mjudeikis mjudeikis closed this Oct 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants