Skip to content

Commit

Permalink
fix #3896: adding serverSideApply dsl methods
Browse files Browse the repository at this point in the history
  • Loading branch information
shawkins authored and manusa committed Nov 15, 2022
1 parent 26fa3c2 commit fc0bda5
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,7 @@

#### New Features
* Fix #4136: added support for fieldValidation as a dsl method for POST/PUT/PATCH operations
* Fix #3896: added dsl support for server side apply

#### _**Note**_: Breaking changes in the API

Expand Down
Expand Up @@ -19,5 +19,6 @@ public interface NonDeletingOperation<T> extends
CreateOrReplaceable<T>,
EditReplacePatchable<T>,
Replaceable<T>, ItemReplacable<T>,
ItemWritableOperation<T> {
ItemWritableOperation<T>,
ServerSideApplicable<T> {
}
@@ -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> {

T serverSideApply();

/**
* FieldManager is a name associated with the actor or entity that is making these changes.
* <p>
* The value must be less than or 128 characters long, and only contain printable characters
* <p>
* the default value is "fabric8"
*
* @param manager
* @return {@link ServerSideApplicable} for continued operations
*/
ServerSideApplicable<T> fieldManager(String manager);

/**
* Force this request / fieldManager to take ownership over conflicting fields.
*
* @return {@link ServerSideApplicable} for continued operations
*/
ServerSideApplicable<T> forceConflicts();

}
Expand Up @@ -78,4 +78,10 @@ public interface ExtensibleResource<T> extends Resource<T> {
@Override
ExtensibleResource<T> fieldValidation(Validation fieldValidation);

@Override
ExtensibleResource<T> fieldManager(String manager);

@Override
ExtensibleResource<T> forceConflicts();

}
Expand Up @@ -106,4 +106,14 @@ public ExtensibleResource<T> fieldValidation(Validation fieldValidation) {
return newInstance().init(resource.fieldValidation(fieldValidation), client);
}

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

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

}
Expand Up @@ -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;
Expand Down Expand Up @@ -308,4 +309,19 @@ public NonDeletingOperation<T> fieldValidation(Validation fieldValidation) {
return resource.fieldValidation(fieldValidation);
}

@Override
public ServerSideApplicable<T> fieldManager(String manager) {
return resource.fieldManager(manager);
}

@Override
public ServerSideApplicable<T> forceConflicts() {
return resource.forceConflicts();
}

@Override
public T serverSideApply() {
return resource.serverSideApply();
}

}
Expand Up @@ -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;
Expand Down Expand Up @@ -1087,4 +1088,19 @@ public String getApiEndpointPath() {
return parts.stream().collect(Collectors.joining("/"));
}

@Override
public ExtensibleResource<T> fieldManager(String manager) {
return newInstance(context.withFieldManager(manager));
}

@Override
public ExtensibleResource<T> forceConflicts() {
return newInstance(context.withForceConflicts());
}

@Override
public T serverSideApply() {
return this.patch(PatchContext.of(PatchType.SERVER_SIDE_APPLY));
}

}
Expand Up @@ -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;
Expand All @@ -73,15 +75,17 @@ 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,
String apiGroupName, String apiGroupVersion, Object item, Map<String, String> labels,
Map<String, String[]> labelsNot, Map<String, String[]> labelsIn, Map<String, String[]> labelsNotIn,
Map<String, String> fields, Map<String, String[]> 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;
Expand All @@ -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<String, String[]> fieldsNot) {
Expand Down Expand Up @@ -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;
}

}
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
Expand Up @@ -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());
}
Expand Down

0 comments on commit fc0bda5

Please sign in to comment.