Skip to content

Commit

Permalink
Fix fabric8io#2701: Add Support for Merge Patch for Kubernetes resources
Browse files Browse the repository at this point in the history
  • Loading branch information
rohanKanojia committed Apr 22, 2021
1 parent b0282d8 commit dc78323
Show file tree
Hide file tree
Showing 9 changed files with 454 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* Fix #2980: Add DSL Support for `apps/v1#ControllerRevision` resource
* Fix #2981: Add DSL support for `storage.k8s.io/v1beta1` CSIDriver, CSINode and VolumeAttachment
* Fix #2912: Add DSL support for `storage.k8s.io/v1beta1` CSIStorageCapacity
* Fix #2701: Support for strategic merge patching in KuberntesClient

#### _**Note**_: Breaking changes in the API
##### DSL Changes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,35 @@
*/
package io.fabric8.kubernetes.client.dsl;

import io.fabric8.kubernetes.api.model.PatchOptions;

public interface Patchable<T> {

/**
* Update field(s) of a resource using strategic a JSON patch.
*
* @param item item to be patched with patched values
* @return returns deserialized version of api server response
*/
T patch(T item);

/**
* Update field(s) of a resource using strategic merge patch or a JSON patch.
*
* @param patch The patch to be applied to the resource JSON file.
* @return returns deserialized version of api server response
*/
default T patch(String patch) {
return patch(null, patch);
}

/**
* Update field(s) of a resource using strategic merge patch or a JSON patch.
*
* @param patchOptions PatchOptions for patch request
* @param patch The patch to be applied to the resource JSON file.
* @return The patch to be applied to the resource JSON file.
*/
T patch(PatchOptions patchOptions, String patch);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.fabric8.kubernetes.client.dsl.base;

import io.fabric8.kubernetes.api.model.ObjectReference;
import io.fabric8.kubernetes.api.model.PatchOptions;
import io.fabric8.kubernetes.client.WatcherException;
import io.fabric8.kubernetes.client.dsl.WritableOperation;
import io.fabric8.kubernetes.client.utils.CreateOrReplaceHelper;
Expand Down Expand Up @@ -98,6 +99,8 @@ public class BaseOperation<T extends HasMetadata, L extends KubernetesResourceLi
private static final String INVOLVED_OBJECT_RESOURCE_VERSION = "involvedObject.resourceVersion";
private static final String INVOLVED_OBJECT_API_VERSION = "involvedObject.apiVersion";
private static final String INVOLVED_OBJECT_FIELD_PATH = "involvedObject.fieldPath";
private static final String READ_ONLY_UPDATE_EXCEPTION_MESSAGE = "Cannot update read-only resources";
private static final String READ_ONLY_EDIT_EXCEPTION_MESSAGE = "Cannot edit read-only resources";

private final boolean cascading;
private final T item;
Expand Down Expand Up @@ -245,12 +248,12 @@ public RootPaths getRootPaths() {

@Override
public T edit(UnaryOperator<T> function) {
throw new KubernetesClientException("Cannot edit read-only resources");
throw new KubernetesClientException(READ_ONLY_EDIT_EXCEPTION_MESSAGE);
}

@Override
public T edit(Visitor... visitors) {
throw new KubernetesClientException("Cannot edit read-only resources");
throw new KubernetesClientException(READ_ONLY_EDIT_EXCEPTION_MESSAGE);
}

@Override
Expand All @@ -270,7 +273,7 @@ public void visit(V item) {

@Override
public T accept(Consumer<T> consumer) {
throw new KubernetesClientException("Cannot edit read-only resources");
throw new KubernetesClientException(READ_ONLY_EDIT_EXCEPTION_MESSAGE);
}

@Override
Expand Down Expand Up @@ -848,12 +851,17 @@ public Watch watch(ListOptions options, final Watcher<T> watcher) {

@Override
public T replace(T item) {
throw new KubernetesClientException("Cannot update read-only resources");
throw new KubernetesClientException(READ_ONLY_UPDATE_EXCEPTION_MESSAGE);
}

@Override
public T patch(T item) {
throw new KubernetesClientException("Cannot update read-only resources");
throw new KubernetesClientException(READ_ONLY_UPDATE_EXCEPTION_MESSAGE);
}

@Override
public T patch(PatchOptions patchOptions, String patch) {
throw new KubernetesClientException(READ_ONLY_UPDATE_EXCEPTION_MESSAGE);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@
import io.fabric8.kubernetes.api.model.DeletionPropagation;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.PatchOptions;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.Resource;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import static io.fabric8.kubernetes.client.utils.IOHelpers.convertToJson;

public class HasMetadataOperation<T extends HasMetadata, L extends KubernetesResourceList<T>, R extends Resource<T>> extends BaseOperation< T, L, R> {
public static final DeletionPropagation DEFAULT_PROPAGATION_POLICY = DeletionPropagation.BACKGROUND;
public static final long DEFAULT_GRACE_PERIOD_IN_SECONDS = -1L;
private static final String PATCH_OPERATION = "patch";

public HasMetadataOperation(OperationContext ctx) {
super(ctx);
Expand Down Expand Up @@ -142,6 +149,22 @@ public T patch(T item) {
caught = e;
}
}
throw KubernetesClientException.launderThrowable(forOperationType("patch"), caught);
throw KubernetesClientException.launderThrowable(forOperationType(PATCH_OPERATION), caught);
}

@Override
public T patch(PatchOptions patchOptions, String patch) {
try {
final T got = fromServer().get();
if (got == null) {
return null;
}
return handlePatch(patchOptions, got, convertToJson(patch), getType());
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw KubernetesClientException.launderThrowable(forOperationType(PATCH_OPERATION), interruptedException);
} catch (IOException | ExecutionException e) {
throw KubernetesClientException.launderThrowable(forOperationType(PATCH_OPERATION), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.fabric8.kubernetes.api.model.DeleteOptions;
import io.fabric8.kubernetes.api.model.DeletionPropagation;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.PatchOptions;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.api.model.StatusBuilder;
import io.fabric8.kubernetes.api.model.autoscaling.v1.Scale;
Expand Down Expand Up @@ -174,6 +175,23 @@ public URL getResourceURLForWriteOperation(URL resourceURL) throws MalformedURLE
return resourceURL;
}

public URL getResourceURLForPatchOperation(URL resourceUrl, PatchOptions patchOptions) throws MalformedURLException {
if (patchOptions != null) {
String url = resourceUrl.toString();
if (patchOptions.getForce() != null) {
url = URLUtils.join(url, "?force=" + patchOptions.getForce());
}
if ((patchOptions.getDryRun() != null && !patchOptions.getDryRun().isEmpty()) || dryRun) {
url = URLUtils.join(url, "?dryRun=All");
}
if (patchOptions.getFieldManager() != null) {
url = URLUtils.join(url, "?fieldManager=" + patchOptions.getFieldManager());
}
return new URL(url);
}
return resourceUrl;
}

protected <T> String checkNamespace(T item) {
String operationNs = getNamespace();
String itemNs = (item instanceof HasMetadata && ((HasMetadata)item).getMetadata() != null) ? ((HasMetadata) item).getMetadata().getNamespace() : null;
Expand Down Expand Up @@ -344,6 +362,26 @@ protected <T> T handlePatch(T current, Map<String, Object> patchForUpdate, Class
return handleResponse(requestBuilder, type, Collections.<String, String>emptyMap());
}

/**
* Send an http patch and handle the response.
*
* @param patchOptions patch options for patch request
* @param current current object
* @param patchForUpdate Patch string
* @param type type of object
* @param <T> template argument provided
* @return returns de-serialized version of api server response
* @throws ExecutionException Execution Exception
* @throws InterruptedException Interrupted Exception
* @throws IOException IOException in case of network errors
*/
protected <T> T handlePatch(PatchOptions patchOptions, T current, String patchForUpdate, Class<T> type) throws ExecutionException, InterruptedException, IOException {
MediaType bodyMediaType = getMediaTypeFromBodyContent(patchForUpdate);
RequestBody body = RequestBody.create(bodyMediaType, patchForUpdate);
Request.Builder requestBuilder = new Request.Builder().patch(body).url(getResourceURLForPatchOperation(getResourceUrl(checkNamespace(current), checkName(current)), patchOptions));
return handleResponse(requestBuilder, type, Collections.emptyMap());
}

/**
* Replace Scale of specified Kubernetes Resource
*
Expand Down Expand Up @@ -611,4 +649,12 @@ protected static <T> Map getObjectValueAsMap(T object) {
public Config getConfig() {
return config;
}

private MediaType getMediaTypeFromBodyContent(String requestBodyContent) {
if (requestBodyContent.contains("\"op\"")) {
return JSON_PATCH;
} else {
return STRATEGIC_MERGE_JSON_PATCH;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,11 @@ public static String convertYamlToJson(String yaml) throws IOException {
return jsonWriter.writeValueAsString(obj);
}

public static String convertToJson(String jsonOrYaml) throws IOException {
if (isJSONValid(jsonOrYaml)) {
return jsonOrYaml;
}
return convertYamlToJson(jsonOrYaml);
}

}
Loading

0 comments on commit dc78323

Please sign in to comment.