From 8500442be55ba41a75af321f0d06297f4deb0216 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Mon, 24 Oct 2022 17:09:26 -0400 Subject: [PATCH] fix #3896: adding serverSideApply dsl methods --- .../client/dsl/NonDeletingOperation.java | 3 +- .../client/dsl/ServerSideApplicable.java | 41 +++++++++++++++++++ .../client/extension/ExtensibleResource.java | 6 +++ .../extension/ExtensibleResourceAdapter.java | 10 +++++ .../client/extension/ResourceAdapter.java | 16 ++++++++ .../client/dsl/internal/BaseOperation.java | 16 ++++++++ .../client/dsl/internal/OperationContext.java | 28 ++++++++++++- .../client/dsl/internal/OperationSupport.java | 25 +++++++---- .../kubernetes/client/impl/PatchTest.java | 16 ++++++++ .../fabric8/kubernetes/ServerSideApplyIT.java | 2 +- 10 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ServerSideApplicable.java diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/NonDeletingOperation.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/NonDeletingOperation.java index fda2e50e2ab..5e746cb2b1d 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/NonDeletingOperation.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/NonDeletingOperation.java @@ -19,5 +19,6 @@ public interface NonDeletingOperation extends CreateOrReplaceable, EditReplacePatchable, Replaceable, ItemReplacable, - ItemWritableOperation { + ItemWritableOperation, + ServerSideApplicable { } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ServerSideApplicable.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ServerSideApplicable.java new file mode 100644 index 00000000000..20f8c903f29 --- /dev/null +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ServerSideApplicable.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.kubernetes.client.dsl; + +public interface ServerSideApplicable { + + T serverSideApply(); + + /** + * FieldManager is a name associated with the actor or entity that is making these changes. + *

+ * The value must be less than or 128 characters long, and only contain printable characters + *

+ * the default value is "fabric8" + * + * @param manager + * @return {@link ServerSideApplicable} for continued operations + */ + ServerSideApplicable fieldManager(String manager); + + /** + * Force this request / fieldManager to take ownership over conflicting fields. + * + * @return {@link ServerSideApplicable} for continued operations + */ + ServerSideApplicable forceConflicts(); + +} diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResource.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResource.java index 5f864b70ddf..55f80e9ab8f 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResource.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResource.java @@ -78,4 +78,10 @@ public interface ExtensibleResource extends Resource { @Override ExtensibleResource fieldValidation(Validation fieldValidation); + @Override + ExtensibleResource fieldManager(String manager); + + @Override + ExtensibleResource forceConflicts(); + } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResourceAdapter.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResourceAdapter.java index da534f72c12..6ffff08654f 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResourceAdapter.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ExtensibleResourceAdapter.java @@ -106,4 +106,14 @@ public ExtensibleResource fieldValidation(Validation fieldValidation) { return newInstance().init(resource.fieldValidation(fieldValidation), client); } + @Override + public ExtensibleResource fieldManager(String manager) { + return newInstance().init(resource.fieldManager(manager), client); + } + + @Override + public ExtensibleResource forceConflicts() { + return newInstance().init(resource.forceConflicts(), client); + } + } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ResourceAdapter.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ResourceAdapter.java index 1de186f89ae..cd80145363b 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ResourceAdapter.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/extension/ResourceAdapter.java @@ -31,6 +31,7 @@ import io.fabric8.kubernetes.client.dsl.NonDeletingOperation; import io.fabric8.kubernetes.client.dsl.ReplaceDeletable; import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.ServerSideApplicable; import io.fabric8.kubernetes.client.dsl.Watchable; import io.fabric8.kubernetes.client.dsl.WritableOperation; import io.fabric8.kubernetes.client.dsl.base.PatchContext; @@ -308,4 +309,19 @@ public NonDeletingOperation fieldValidation(Validation fieldValidation) { return resource.fieldValidation(fieldValidation); } + @Override + public ServerSideApplicable fieldManager(String manager) { + return resource.fieldManager(manager); + } + + @Override + public ServerSideApplicable forceConflicts() { + return resource.forceConflicts(); + } + + @Override + public T serverSideApply() { + return resource.serverSideApply(); + } + } diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/BaseOperation.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/BaseOperation.java index dcb81422ee3..9c77ea8b3fd 100755 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/BaseOperation.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/BaseOperation.java @@ -44,6 +44,7 @@ import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; import io.fabric8.kubernetes.client.extension.ExtensibleResource; import io.fabric8.kubernetes.client.http.HttpRequest; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; @@ -1087,4 +1088,19 @@ public String getApiEndpointPath() { return parts.stream().collect(Collectors.joining("/")); } + @Override + public ExtensibleResource fieldManager(String manager) { + return newInstance(context.withFieldManager(manager)); + } + + @Override + public ExtensibleResource forceConflicts() { + return newInstance(context.withForceConflicts()); + } + + @Override + public T serverSideApply() { + return this.patch(PatchContext.of(PatchType.SERVER_SIDE_APPLY)); + } + } diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationContext.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationContext.java index 327bd12482f..01c5c4e4298 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationContext.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationContext.java @@ -51,6 +51,8 @@ public class OperationContext { protected boolean reloadingFromServer; protected boolean dryRun; protected FieldValidateable.Validation fieldValidation; + protected String fieldManager; + protected Boolean forceConflicts; // Default to -1 to respect the value set in the resource or the Kubernetes default (30 seconds) protected long gracePeriodSeconds = -1L; @@ -73,7 +75,8 @@ public OperationContext(OperationContext other) { this(other.client, other.plural, other.namespace, other.name, other.apiGroupName, other.apiGroupVersion, other.item, other.labels, other.labelsNot, other.labelsIn, other.labelsNotIn, other.fields, other.fieldsNot, other.resourceVersion, other.reloadingFromServer, other.gracePeriodSeconds, other.propagationPolicy, - other.dryRun, other.selectorAsString, other.defaultNamespace, other.fieldValidation); + other.dryRun, other.selectorAsString, other.defaultNamespace, other.fieldValidation, other.fieldManager, + other.forceConflicts); } public OperationContext(Client client, String plural, String namespace, String name, @@ -81,7 +84,8 @@ public OperationContext(Client client, String plural, String namespace, String n Map labelsNot, Map labelsIn, Map labelsNotIn, Map fields, Map fieldsNot, String resourceVersion, boolean reloadingFromServer, long gracePeriodSeconds, DeletionPropagation propagationPolicy, - boolean dryRun, String selectorAsString, boolean defaultNamespace, FieldValidateable.Validation fieldValidation) { + boolean dryRun, String selectorAsString, boolean defaultNamespace, FieldValidateable.Validation fieldValidation, + String fieldManager, Boolean forceConflicts) { this.client = client; this.item = item; this.plural = plural; @@ -102,6 +106,8 @@ public OperationContext(Client client, String plural, String namespace, String n this.dryRun = dryRun; this.selectorAsString = selectorAsString; this.fieldValidation = fieldValidation; + this.fieldManager = fieldManager; + this.forceConflicts = forceConflicts; } private void setFieldsNot(Map fieldsNot) { @@ -503,4 +509,22 @@ public OperationContext withFieldValidation(Validation fieldValidation) { return context; } + public OperationContext withFieldManager(String fieldManager) { + if (Objects.equals(fieldManager, this.fieldManager)) { + return this; + } + final OperationContext context = new OperationContext(this); + context.fieldManager = fieldManager; + return context; + } + + public OperationContext withForceConflicts() { + if (Boolean.TRUE.equals(this.forceConflicts)) { + return this; + } + final OperationContext context = new OperationContext(this); + context.forceConflicts = true; + return context; + } + } diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationSupport.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationSupport.java index 2fce3ac742d..f317e4d60a2 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationSupport.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/OperationSupport.java @@ -61,6 +61,7 @@ public class OperationSupport { + private static final String FIELD_MANAGER_PARAM = "?fieldManager="; public static final String JSON = "application/json"; public static final String JSON_PATCH = "application/json-patch+json"; public static final String STRATEGIC_MERGE_JSON_PATCH = "application/strategic-merge-patch+json"; @@ -214,23 +215,33 @@ public URL getResourceURLForWriteOperation(URL resourceURL) throws MalformedURLE public URL getResourceURLForPatchOperation(URL resourceUrl, PatchContext patchContext) throws MalformedURLException { if (patchContext != null) { String url = resourceUrl.toString(); - if (patchContext.getForce() != null) { - url = URLUtils.join(url, "?force=" + patchContext.getForce()); + Boolean forceConflicts = patchContext.getForce(); + + if (forceConflicts == null) { + forceConflicts = this.context.forceConflicts; + } + if (forceConflicts != null) { + url = URLUtils.join(url, "?force=" + forceConflicts); } if ((patchContext.getDryRun() != null && !patchContext.getDryRun().isEmpty()) || dryRun) { url = URLUtils.join(url, "?dryRun=All"); } String fieldManager = patchContext.getFieldManager(); + if (fieldManager == null) { + fieldManager = this.context.fieldManager; + } if (fieldManager == null && patchContext.getPatchType() == PatchType.SERVER_SIDE_APPLY) { fieldManager = "fabric8"; } if (fieldManager != null) { - url = URLUtils.join(url, "?fieldManager=" + fieldManager); + url = URLUtils.join(url, FIELD_MANAGER_PARAM + fieldManager); + } + String fieldValidation = patchContext.getFieldValidation(); + if (fieldValidation == null && this.context.fieldValidation != null) { + fieldValidation = this.context.fieldValidation.parameterValue(); } - if (patchContext.getFieldValidation() != null) { - url = URLUtils.join(url, "?fieldValidation=" + patchContext.getFieldValidation()); - } else if (this.context.fieldValidation != null) { - url = URLUtils.join(url, "?fieldValidation=" + this.context.fieldValidation.parameterValue()); + if (fieldValidation != null) { + url = URLUtils.join(url, "?fieldValidation=" + fieldValidation); } return new URL(url); } diff --git a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/impl/PatchTest.java b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/impl/PatchTest.java index 0987d43313c..072e92f35b1 100644 --- a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/impl/PatchTest.java +++ b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/impl/PatchTest.java @@ -15,6 +15,7 @@ */ package io.fabric8.kubernetes.client.impl; +import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.KubernetesClient; @@ -167,6 +168,21 @@ void testPatchWithPatchOptions() { OperationSupport.STRATEGIC_MERGE_JSON_PATCH); } + @Test + void testServerSideApplyWithPatchOptions() { + // Given + + // When + kubernetesClient.pods().inNamespace("ns1") + .resource(new PodBuilder().withNewMetadata().withName("pod1").endMetadata().build()) + .fieldManager("x").forceConflicts().serverSideApply(); + + // Then + verify(mockClient, times(1)).sendAsync(any(), any()); + assertRequest(0, "PATCH", "/api/v1/namespaces/ns1/pods/pod1", "fieldManager=x&force=true", + PatchType.SERVER_SIDE_APPLY.getContentType()); + } + private void assertRequest(String method, String url, String queryParam) { assertRequest(0, method, url, queryParam, null); } diff --git a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/ServerSideApplyIT.java b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/ServerSideApplyIT.java index 819c955a7bd..b69fe8c0ce0 100644 --- a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/ServerSideApplyIT.java +++ b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/ServerSideApplyIT.java @@ -64,7 +64,7 @@ void testServerSideApply() { service.getSpec().setSelector(Collections.singletonMap("app", "other")); // 2nd server side apply - service = resource.patch(PatchContext.of(PatchType.SERVER_SIDE_APPLY), service); + service = client.resource(service).serverSideApply(); assertEquals("other", service.getSpec().getSelector().get("app")); assertEquals(clusterIp, service.getSpec().getClusterIP()); }