Skip to content

Commit

Permalink
fix: adding PodResource.patchReadinessGateStatus
Browse files Browse the repository at this point in the history
also adding a general subresource method

closes fabric8io#5496
  • Loading branch information
shawkins committed Jan 8, 2024
1 parent 58898be commit 9f19625
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

public interface EditReplacePatchable<T>
extends Replaceable<T> {
public interface EditReplacePatchable<T> extends Updatable<T> {

/**
* Issues a JSON patch against the item based upon the changes made to the object returned by the function.
Expand Down Expand Up @@ -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<T> function);

/**
* Does a PATCH request to the /status subresource ignoring changes to anything except the status stanza.
* <p>
* This method has the same patching behavior as {@link #patch(PatchContext)}, with
* {@link PatchType#JSON_MERGE} but against the status subresource.
* <p>
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* @see <a href="https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/">About Ephemeral Containers</a>
* @see <a href=
* "hhttps://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#-strong-ephemeralcontainers-operations-pod-v1-core-strong-">Ephemeral
* "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#-strong-ephemeralcontainers-operations-pod-v1-core-strong-">Ephemeral
* Containers Operations</a>
*/
public interface EphemeralContainersResource extends EditReplacePatchable<Pod> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public interface ItemWritableOperation<T> extends DeletableWithOptions, ItemRepl
T updateStatus(T item);

/**
* See {@link EditReplacePatchable#patchStatus()}
* See {@link NonDeletingOperation#patchStatus()}
*
* @param item kubernetes object
* @return updated object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> extends
CreateOrReplaceable<T>,
Expand Down Expand Up @@ -44,4 +48,30 @@ public interface NonDeletingOperation<T> extends
* @return NonDeletingOperation that may act on the unlocked item
*/
NonDeletingOperation<T> unlock();

/**
* Edit the status subresource
*
* @param function to produce a modified status
* @return updated object
*/
T editStatus(UnaryOperator<T> function);

/**
* Does a PATCH request to the /status subresource ignoring changes to anything except the status stanza.
* <p>
* This method has the same patching behavior as {@link #patch(PatchContext)}, with
* {@link PatchType#JSON_MERGE} but against the status subresource.
* <p>
* 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<T> subresource(String subresource);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pod>,
Loggable,
Containerable<String, ContainerResource>,
Expand Down Expand Up @@ -49,4 +51,12 @@ public interface PodResource extends Resource<Pod>,
*/
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<String, Boolean> readiness);

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,7 @@
*/
package io.fabric8.kubernetes.client.dsl;

public interface Replaceable<T> {

/**
* Replace the server's state with the given item.
*
* <p>
* If {@link Resource#lockResourceVersion(String)} has been used to lock the resourceVersion,
* this operation is effectively a single update attempt against that version.
* <p>
* 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 <a href=
* "https://github.com/fabric8io/kubernetes-client/blob/main/doc/FAQ.md#alternatives-to-createOrReplace-and-replace"
* >Migration FAQ</a>
*/
@Deprecated
T replace();
public interface Replaceable<T> extends Updatable<T> {

/**
* Similar to {@link Replaceable#replace()}, but only affects the status subresource
Expand All @@ -61,15 +38,4 @@ public interface Replaceable<T> {
*/
T updateStatus();

/**
* Update the server's state with the given item (PUT).
* <p>
* 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();

}
Original file line number Diff line number Diff line change
@@ -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<T> {

/**
* Replace the server's state with the given item.
*
* <p>
* If {@link Resource#lockResourceVersion(String)} has been used to lock the resourceVersion,
* this operation is effectively a single update attempt against that version.
* <p>
* 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 <a href=
* "https://github.com/fabric8io/kubernetes-client/blob/main/doc/FAQ.md#alternatives-to-createOrReplace-and-replace"
* >Migration FAQ</a>
*/
@Deprecated
T replace();

/**
* Update the server's state with the given item (PUT).
* <p>
* 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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public interface ExtensibleResource<T> extends Resource<T>, TimeoutableScalable<
@Override
ExtensibleResource<T> unlock();

@Override
ExtensibleResource<T> subresource(String subresource);

@Override
default ExtensibleResource<T> withTimeoutInMillis(long timeoutInMillis) {
return withTimeout(timeoutInMillis, TimeUnit.MILLISECONDS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,9 @@ public ExtensibleResource<T> unlock() {
return newInstance().init(resource.unlock(), client);
}

@Override
public ExtensibleResource<T> subresource(String subresource) {
return newInstance().init(resource.subresource(subresource), client);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -380,4 +381,9 @@ public NonDeletingOperation<T> unlock() {
return resource.unlock();
}

@Override
public EditReplacePatchable<T> subresource(String subresource) {
return resource.subresource(subresource);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1202,4 +1202,9 @@ public Scale scale(Scale scale) {
throw new KubernetesClientException(READ_ONLY_UPDATE_EXCEPTION_MESSAGE);
}

@Override
public ExtensibleResource<T> subresource(String subresource) {
return newInstance(context.withSubresource(subresource));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<String, Boolean> readiness) {
Map<String, PodCondition> 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<String, Boolean> 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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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-1")
.withImage("alpine")
.withCommand("sh")
.endEphemeralContainer()
.endSpec()
.build());

List<EphemeralContainer> containers = pod.getSpec().getEphemeralContainers();
assertTrue(containers.stream().anyMatch(c -> c.getName().equals("debugger-1")));
}

@Test
void replace() {
Pod item = client.pods().withName("pod-standard").get();
Expand Down
12 changes: 12 additions & 0 deletions kubernetes-itests/src/test/java/io/fabric8/kubernetes/PodIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -137,6 +140,15 @@ void update() {
assertEquals("bar", pod1.getMetadata().getLabels().get("foo"));
}

@Test
void readinessGate() {
Map<String, Boolean> 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());
Expand Down

0 comments on commit 9f19625

Please sign in to comment.