-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Alpha Support for Server Side Apply (2nd attempt) #246
Changes from all commits
0a80474
7dbb59d
38fbeed
1eb961b
bdb1ab9
a32d003
8f6fde9
03a91c9
2d5921e
d330b8a
8f12bb2
ca10c79
d826199
0310747
494655c
b308d22
7288822
b6b5a6f
2f52697
459c241
e0c2969
6ed51bc
ebdedd7
3c81ae4
31135af
39acb53
0d5cfb3
af39abb
46ac77a
cd578c8
d5616a5
e5e7adc
716be07
7f74deb
81c421c
89e0397
9f52210
e8b8576
c1eb6c2
8752c45
88580c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Note: This example is for the alpha feature of server side apply. | ||
# It requires the provider to be started with the --enable-server-side-apply flag. | ||
apiVersion: kubernetes.crossplane.io/v1alpha2 | ||
kind: Object | ||
metadata: | ||
name: sample-service-labeler | ||
spec: | ||
# Note: This resource will only patch/update the manifest below. | ||
# It will not delete or create the resource. | ||
# As a limitation, it will not clean up the changes it made during its deletion. | ||
# This requires the Server Side Apply feature to be enabled in the provider | ||
# with the --enable-server-side-apply flag. | ||
managementPolicies: ["Observe", "Update"] | ||
forProvider: | ||
manifest: | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: sample-service | ||
namespace: default | ||
labels: | ||
another-key: another-value | ||
providerConfigRef: | ||
name: kubernetes-provider |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Note: This example is for the alpha feature of server side apply. | ||
# It requires the provider to be started with the --enable-server-side-apply flag. | ||
apiVersion: kubernetes.crossplane.io/v1alpha2 | ||
kind: Object | ||
metadata: | ||
name: sample-service-owner | ||
annotations: | ||
uptest.upbound.io/pre-assert-hook: testhooks/enable-ssa.sh | ||
uptest.upbound.io/post-assert-hook: testhooks/validate-ssa.sh | ||
uptest.upbound.io/timeout: "60" | ||
spec: | ||
forProvider: | ||
manifest: | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: sample-service | ||
namespace: default | ||
labels: | ||
some-key: some-value | ||
spec: | ||
selector: | ||
app.kubernetes.io/name: MyApp | ||
ports: | ||
- protocol: TCP | ||
port: 80 | ||
targetPort: 9376 | ||
providerConfigRef: | ||
name: kubernetes-provider |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env bash | ||
set -aeuo pipefail | ||
|
||
echo "Enabling ssa feature for the provider" | ||
${KUBECTL} patch deploymentruntimeconfig runtimeconfig-provider-kubernetes --type='json' -p='[{"op":"replace","path":"/spec/deploymentTemplate/spec/template/spec/containers/0/args", "value":["--debug", "--enable-server-side-apply"]}]' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#!/usr/bin/env bash | ||
set -aeuo pipefail | ||
|
||
# This script is used to validate the ssa feature, triggered by the | ||
# uptest framework via `uptest.upbound.io/post-assert-hook`: https://github.com/crossplane/uptest/tree/e64457e2cce153ada54da686c8bf96143f3f6329?tab=readme-ov-file#hooks | ||
|
||
LABELER_OBJECT="examples/object/object-ssa-labeler.yaml" | ||
${KUBECTL} apply -f ${LABELER_OBJECT} | ||
${KUBECTL} wait -f ${LABELER_OBJECT} --for condition=ready --timeout=1m | ||
|
||
if ! ${KUBECTL} get service sample-service -o jsonpath='{.metadata.annotations}' | grep -v "last-applied-configuration"; then # This annotation should not be present when SSA is enabled | ||
echo "SSA validation failed! Annotation 'last-applied-configuration' should not exist when SSA is enabled!" | ||
#exit 1 | ||
fi | ||
if ! (${KUBECTL} get service sample-service -o jsonpath='{.metadata.labels.some-key}' | grep -q "some-value" && ${KUBECTL} get service sample-service -o jsonpath='{.metadata.labels.another-key}' | grep -q "another-value"); then | ||
echo "SSA validation failed! Labels 'some-key' and 'another-key' from both Objects should exist with values 'some-value' and 'another-value' respectively!" | ||
#exit 1 | ||
fi | ||
echo "Successfully validated the SSA feature!" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this the only e2e test we have? 😱 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, we rely on the uptest framework for e2e tests just like other providers and what we can do with uptest is limited, but still better than nothing. Also due to some current limitations that is about to be solved, we couldn't really integrate this test into the CI. I would suggest:
|
||
|
||
${KUBECTL} delete -f ${LABELER_OBJECT} | ||
|
||
echo "Disabling SSA feature for the provider" | ||
${KUBECTL} patch deploymentruntimeconfig runtimeconfig-provider-kubernetes --type='json' -p='[{"op":"replace","path":"/spec/deploymentTemplate/spec/template/spec/containers/0/args", "value":["--debug"]}]' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package fake | ||
|
||
import ( | ||
"context" | ||
|
||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
|
||
"github.com/crossplane-contrib/provider-kubernetes/apis/object/v1alpha2" | ||
) | ||
|
||
// A ResourceSyncer is a fake ResourceSyncer. | ||
type ResourceSyncer struct { | ||
GetObservedStateFn func(ctx context.Context, obj *v1alpha2.Object, current *unstructured.Unstructured) (*unstructured.Unstructured, error) | ||
GetDesiredStateFn func(ctx context.Context, obj *v1alpha2.Object, manifest *unstructured.Unstructured) (*unstructured.Unstructured, error) | ||
SyncResourceFn func(ctx context.Context, obj *v1alpha2.Object, desired *unstructured.Unstructured) (*unstructured.Unstructured, error) | ||
} | ||
|
||
// GetObservedState calls the GetObservedStateFn. | ||
func (r *ResourceSyncer) GetObservedState(ctx context.Context, obj *v1alpha2.Object, current *unstructured.Unstructured) (*unstructured.Unstructured, error) { | ||
return r.GetObservedStateFn(ctx, obj, current) | ||
} | ||
|
||
// GetDesiredState calls the GetDesiredStateFn. | ||
func (r *ResourceSyncer) GetDesiredState(ctx context.Context, obj *v1alpha2.Object, manifest *unstructured.Unstructured) (*unstructured.Unstructured, error) { | ||
return r.GetDesiredStateFn(ctx, obj, manifest) | ||
} | ||
|
||
// SyncResource calls the SyncResourceFn. | ||
func (r *ResourceSyncer) SyncResource(ctx context.Context, obj *v1alpha2.Object, desired *unstructured.Unstructured) (*unstructured.Unstructured, error) { | ||
return r.SyncResourceFn(ctx, obj, desired) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,9 +66,9 @@ func IndexByProviderGVK(o client.Object) []string { | |
} | ||
|
||
// Index the desired object. | ||
// We don't expect errors here, as the getDesired function is already called | ||
// We don't expect errors here, as the parseManifest function is already called | ||
// in the reconciler and the desired object already validated. | ||
d, _ := getDesired(obj) | ||
d, _ := parseManifest(obj) | ||
keys = append(keys, refKeyProviderGVK(obj.Spec.ProviderConfigReference.Name, d.GetKind(), d.GroupVersionKind().Group, d.GroupVersionKind().Version)) // unification is done by the informer. | ||
|
||
// unification is done by the informer. | ||
|
@@ -98,9 +98,9 @@ func IndexByProviderNamespacedNameGVK(o client.Object) []string { | |
} | ||
|
||
// Index the desired object. | ||
// We don't expect errors here, as the getDesired function is already called | ||
// We don't expect errors here, as the parseManifest function is already called | ||
// in the reconciler and the desired object already validated. | ||
d, _ := getDesired(obj) | ||
d, _ := parseManifest(obj) | ||
keys = append(keys, refKeyProviderNamespacedNameGVK(obj.Spec.ProviderConfigReference.Name, d.GetNamespace(), d.GetName(), d.GetKind(), d.GetAPIVersion())) // unification is done by the informer. | ||
|
||
return keys | ||
|
@@ -123,8 +123,12 @@ func enqueueObjectsForReferences(ca cache.Cache, log logging.Logger) func(ctx co | |
} | ||
// queue those Objects for reconciliation | ||
for _, o := range objects.Items { | ||
log.Info("Enqueueing Object because referenced resource changed", "name", o.GetName(), "referencedGVK", rGVK.String(), "referencedName", ev.Object.GetName(), "providerConfig", pc) | ||
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: o.GetName()}}) | ||
// We only enqueue the Object if it has the Watch flag set to true. | ||
// Not every referencing Object watches the referenced resource. | ||
if o.Spec.Watch { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only want to enqueue the Object if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not questioning the watch field. But why we make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. Watches started based on GVK not including their namespaced name, so, another object with |
||
log.Info("Enqueueing Object because referenced resource changed", "name", o.GetName(), "referencedGVK", rGVK.String(), "referencedName", ev.Object.GetName(), "providerConfig", pc) | ||
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: o.GetName()}}) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant as a feature gate? Doesn't look like from the name. I don't like to force ppl to understand so low-level concepts, i.e. this kind of flag should not exist medium-term.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is wrong with name, what could be an alternative?
I agree that people shouldn't need to understand details on how we sync the desired state, but don't feel comfortable to replace existing mechanism in place, so wanted to put behind a feature flag. I think similar to https://github.com/crossplane/crossplane/blob/2a427fc89da4a2e86f75eaecfa8328f53e19f33b/cmd/crossplane/core/core.go#L115
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a feature gate that goes away after alpha/beta, then this is fine. Wasn't sure whether it is meant to stay forever as a knob.