diff --git a/CHANGELOG.md b/CHANGELOG.md index 162da2f2259..e9bd0635582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ #### Improvements * Fix #5429: moved crd generator annotations to generator-annotations instead of crd-generator-api. Using generator-annotations introduces no transitive dependencies. * Fix #5535: Add lombok and sundrio dependencies to the generated bom +* Fix #5496: Added PodResource.patchReadinessGateStatus and a general subresource method to use any of the patch/edit/update methods with any subresource #### Dependency Upgrade * Updated okio to version 1.17.6 to avoid CVE-2023-3635 diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EditReplacePatchable.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EditReplacePatchable.java index 0e8953e718c..1c4ea685824 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EditReplacePatchable.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EditReplacePatchable.java @@ -23,8 +23,7 @@ import java.util.function.Consumer; import java.util.function.UnaryOperator; -public interface EditReplacePatchable - extends Replaceable { +public interface EditReplacePatchable extends Updatable { /** * Issues a JSON patch against the item based upon the changes made to the object returned by the function. @@ -132,26 +131,6 @@ default T patch(String patch) { */ T patch(PatchContext patchContext, String patch); - /** - * Edit the status subresource - * - * @param function to produce a modified status - * @return updated object - */ - T editStatus(UnaryOperator function); - - /** - * Does a PATCH request to the /status subresource ignoring changes to anything except the status stanza. - *

- * This method has the same patching behavior as {@link #patch(PatchContext)}, with - * {@link PatchType#JSON_MERGE} but against the status subresource. - *

- * Set the resourceVersion to null to prevent optimistic locking. - * - * @return updated object - */ - T patchStatus(); - /** * Update field(s) of a resource using a JSON patch which will be computed using the latest * server state as the base. diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EphemeralContainersResource.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EphemeralContainersResource.java index 95453fbf512..4ab8398e866 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EphemeralContainersResource.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/EphemeralContainersResource.java @@ -24,7 +24,7 @@ * * @see About Ephemeral Containers * @see Ephemeral + * "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#-strong-ephemeralcontainers-operations-pod-v1-core-strong-">Ephemeral * Containers Operations */ public interface EphemeralContainersResource extends EditReplacePatchable { diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ItemWritableOperation.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ItemWritableOperation.java index 4adbe43c44d..24c3cbcf469 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ItemWritableOperation.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/ItemWritableOperation.java @@ -68,7 +68,7 @@ public interface ItemWritableOperation extends DeletableWithOptions, ItemRepl T updateStatus(T item); /** - * See {@link EditReplacePatchable#patchStatus()} + * See {@link NonDeletingOperation#patchStatus()} * * @param item kubernetes object * @return updated object 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 02d426984f9..8b557cc6e0d 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 @@ -15,7 +15,11 @@ */ package io.fabric8.kubernetes.client.dsl; +import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; + import java.util.function.Function; +import java.util.function.UnaryOperator; public interface NonDeletingOperation extends CreateOrReplaceable, @@ -44,4 +48,30 @@ public interface NonDeletingOperation extends * @return NonDeletingOperation that may act on the unlocked item */ NonDeletingOperation unlock(); + + /** + * Edit the status subresource + * + * @param function to produce a modified status + * @return updated object + */ + T editStatus(UnaryOperator function); + + /** + * Does a PATCH request to the /status subresource ignoring changes to anything except the status stanza. + *

+ * This method has the same patching behavior as {@link #patch(PatchContext)}, with + * {@link PatchType#JSON_MERGE} but against the status subresource. + *

+ * Set the resourceVersion to null to prevent optimistic locking. + * + * @return updated object + */ + T patchStatus(); + + /** + * Provides edit, patch, and replace methods for the given subresource + */ + EditReplacePatchable subresource(String subresource); + } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/PodResource.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/PodResource.java index 7cf598fdf7f..2f0023eab50 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/PodResource.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/PodResource.java @@ -18,6 +18,8 @@ import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.policy.v1.Eviction; +import java.util.Map; + public interface PodResource extends Resource, Loggable, Containerable, @@ -49,4 +51,12 @@ public interface PodResource extends Resource, */ EphemeralContainersResource ephemeralContainers(); + /** + * Set the Pod status readiness gate fields to match the given map. + * + * @param readiness + * @return a new pod instance with the updated readiness gates + */ + Pod patchReadinessGateStatus(Map readiness); + } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Replaceable.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Replaceable.java index 0e327a28f7a..c6b2c41e0a7 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Replaceable.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Replaceable.java @@ -15,30 +15,7 @@ */ package io.fabric8.kubernetes.client.dsl; -public interface Replaceable { - - /** - * Replace the server's state with the given item. - * - *

- * If {@link Resource#lockResourceVersion(String)} has been used to lock the resourceVersion, - * this operation is effectively a single update attempt against that version. - *

- * If {@link Resource#lockResourceVersion(String)} has not been called, this operation - * will be retried a number of times in the event of a conflict. If a resourceVersion has been set - * on the item, the first update attempt will be made against that version. Subsequent attempts will fetch - * the latest resourceVersion from the server. - * - * @return returns deserialized version of api server response - * - * @deprecated use {@link #update()} instead - * - * @see Migration FAQ - */ - @Deprecated - T replace(); +public interface Replaceable extends Updatable { /** * Similar to {@link Replaceable#replace()}, but only affects the status subresource @@ -61,15 +38,4 @@ public interface Replaceable { */ T updateStatus(); - /** - * Update the server's state with the given item (PUT). - *

- * If the resourceVersion is on the resource, the update will be performed with optimistic locking, and may - * result in a conflict (409 error). If no resourceVersion is on the resource, the latest resourceVersion will - * be obtained from the server prior to the update call - which may still be a conflict in a rare circumstance. - * - * @return returns deserialized version of api server response - */ - T update(); - } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Updatable.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Updatable.java new file mode 100644 index 00000000000..3483279f2b6 --- /dev/null +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/dsl/Updatable.java @@ -0,0 +1,54 @@ +/** + * 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 Updatable { + + /** + * Replace the server's state with the given item. + * + *

+ * If {@link Resource#lockResourceVersion(String)} has been used to lock the resourceVersion, + * this operation is effectively a single update attempt against that version. + *

+ * If {@link Resource#lockResourceVersion(String)} has not been called, this operation + * will be retried a number of times in the event of a conflict. If a resourceVersion has been set + * on the item, the first update attempt will be made against that version. Subsequent attempts will fetch + * the latest resourceVersion from the server. + * + * @return returns deserialized version of api server response + * + * @deprecated use {@link #update()} instead + * + * @see Migration FAQ + */ + @Deprecated + T replace(); + + /** + * Update the server's state with the given item (PUT). + *

+ * If the resourceVersion is on the resource, the update will be performed with optimistic locking, and may + * result in a conflict (409 error). If no resourceVersion is on the resource, the latest resourceVersion will + * be obtained from the server prior to the update call - which may still be a conflict in a rare circumstance. + * + * @return returns deserialized version of api server response + */ + T update(); + +} 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 756bbe596cd..dcde9cec371 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 @@ -93,6 +93,9 @@ public interface ExtensibleResource extends Resource, TimeoutableScalable< @Override ExtensibleResource unlock(); + @Override + ExtensibleResource subresource(String subresource); + @Override default ExtensibleResource withTimeoutInMillis(long timeoutInMillis) { return withTimeout(timeoutInMillis, TimeUnit.MILLISECONDS); 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 43e923e4b87..70a020d5192 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 @@ -132,4 +132,9 @@ public ExtensibleResource unlock() { return newInstance().init(resource.unlock(), client); } + @Override + public ExtensibleResource subresource(String subresource) { + return newInstance().init(resource.subresource(subresource), 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 63660e041d4..e1dd2435836 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 @@ -27,6 +27,7 @@ import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.dsl.Deletable; +import io.fabric8.kubernetes.client.dsl.EditReplacePatchable; import io.fabric8.kubernetes.client.dsl.Gettable; import io.fabric8.kubernetes.client.dsl.Informable; import io.fabric8.kubernetes.client.dsl.NonDeletingOperation; @@ -380,4 +381,9 @@ public NonDeletingOperation unlock() { return resource.unlock(); } + @Override + public EditReplacePatchable subresource(String subresource) { + return resource.subresource(subresource); + } + } 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 47d94ade223..6958005d349 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 @@ -1202,4 +1202,9 @@ public Scale scale(Scale scale) { throw new KubernetesClientException(READ_ONLY_UPDATE_EXCEPTION_MESSAGE); } + @Override + public ExtensibleResource subresource(String subresource) { + return newInstance(context.withSubresource(subresource)); + } + } diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/core/v1/PodOperationsImpl.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/core/v1/PodOperationsImpl.java index afaebbf8ed3..bcaeb1d79eb 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/core/v1/PodOperationsImpl.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/core/v1/PodOperationsImpl.java @@ -20,8 +20,11 @@ import io.fabric8.kubernetes.api.model.EphemeralContainer; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodCondition; +import io.fabric8.kubernetes.api.model.PodConditionBuilder; import io.fabric8.kubernetes.api.model.PodList; import io.fabric8.kubernetes.api.model.PodSpec; +import io.fabric8.kubernetes.api.model.PodStatus; import io.fabric8.kubernetes.api.model.policy.v1beta1.Eviction; import io.fabric8.kubernetes.api.model.policy.v1beta1.EvictionBuilder; import io.fabric8.kubernetes.client.Client; @@ -82,9 +85,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static io.fabric8.kubernetes.client.utils.internal.OptionalDependencyWrapper.wrapRunWithOptionalDependency; @@ -640,4 +646,30 @@ public static String shellQuote(String value) { public PodOperationsImpl terminateOnError() { return new PodOperationsImpl(getContext().toBuilder().terminateOnError(true).build(), context); } + + @Override + public Pod patchReadinessGateStatus(Map readiness) { + Map conditions = new LinkedHashMap<>(); + Pod pod = getItemOrRequireFromServer(); + if (pod.getStatus() == null) { + pod.setStatus(new PodStatus()); + } + pod.getStatus().getConditions().forEach(pc -> conditions.put(pc.getType(), pc)); + for (Map.Entry entry : readiness.entrySet()) { + if (entry.getValue() == null) { // effectively false, may not even need to account for this case + conditions.remove(entry.getKey()); + } else { + PodCondition condition = conditions.get(entry.getKey()); + String valueString = entry.getValue() ? "True" : "False"; + if (condition == null) { + conditions.put(entry.getKey(), new PodConditionBuilder().withStatus(valueString).withType(entry.getKey()).build()); + } else { + condition.setStatus(valueString); + } + } + } + pod.getStatus().setConditions(conditions.values().stream().collect(Collectors.toList())); + return this.resource(pod).subresource("status").patch(); + } + } diff --git a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodEphemeralContainersIT.java b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodEphemeralContainersIT.java index 6599fa37f2b..394488f920b 100644 --- a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodEphemeralContainersIT.java +++ b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodEphemeralContainersIT.java @@ -57,6 +57,25 @@ void edit() { assertTrue(containers.stream().anyMatch(c -> c.getName().equals("debugger-1"))); } + @Test + void editSubresource() { + Pod pod = client.pods().withName("pod-standard") + .subresource("ephemeralcontainers") + .edit(p -> new PodBuilder(p) + .editMetadata().withResourceVersion(null).endMetadata() + .editSpec() + .addNewEphemeralContainer() + .withName("debugger-sub") + .withImage("alpine") + .withCommand("sh") + .endEphemeralContainer() + .endSpec() + .build()); + + List containers = pod.getSpec().getEphemeralContainers(); + assertTrue(containers.stream().anyMatch(c -> c.getName().equals("debugger-sub"))); + } + @Test void replace() { Pod item = client.pods().withName("pod-standard").get(); diff --git a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java index 423a776dbf2..faf048c6780 100644 --- a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java +++ b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java @@ -61,7 +61,10 @@ import java.time.Duration; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -137,6 +140,15 @@ void update() { assertEquals("bar", pod1.getMetadata().getLabels().get("foo")); } + @Test + void readinessGate() { + Map readiness = new HashMap<>(); + readiness.put("test", true); + Pod pod1 = client.pods().withName("pod-standard").patchReadinessGateStatus(readiness); + assertEquals("True", pod1.getStatus().getConditions().stream().filter(pc -> pc.getType().equals("test")).findFirst() + .orElseThrow(() -> new NoSuchElementException()).getStatus()); + } + @Test void delete() { assertEquals(1, client.pods().withName("pod-delete").delete().size());