io.fabric8.kubernetes.client.dsl.internal.OperationSupport Maven / Gradle / Ivy
The newest version!
/*
* 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.internal;
import com.fasterxml.jackson.core.type.TypeReference;
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.KubernetesResource;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Preconditions;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.api.model.StatusBuilder;
import io.fabric8.kubernetes.api.model.extensions.DeploymentRollback;
import io.fabric8.kubernetes.client.Client;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.RequestConfig;
import io.fabric8.kubernetes.client.RequestConfigBuilder;
import io.fabric8.kubernetes.client.dsl.FieldValidateable.Validation;
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
import io.fabric8.kubernetes.client.dsl.base.PatchType;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.HttpRequest;
import io.fabric8.kubernetes.client.http.HttpResponse;
import io.fabric8.kubernetes.client.impl.BaseClient;
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.fabric8.kubernetes.client.utils.URLUtils;
import io.fabric8.kubernetes.client.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
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";
public static final String JSON_MERGE_PATCH = "application/merge-patch+json";
private static final Logger LOG = LoggerFactory.getLogger(OperationSupport.class);
private static final String CLIENT_STATUS_FLAG = "CLIENT_STATUS_FLAG";
protected OperationContext context;
protected final HttpClient httpClient;
protected final Config config;
protected final String resourceT;
protected String namespace;
protected String name;
protected String subresource;
protected String apiGroupName;
protected String apiGroupVersion;
protected boolean dryRun;
public OperationSupport(Client client) {
this(new OperationContext().withClient(client));
}
public OperationSupport(OperationContext ctx) {
this.context = ctx;
this.httpClient = ctx.getHttpClient();
this.config = ctx.getConfig();
this.resourceT = ctx.getPlural();
this.namespace = ctx.getNamespace();
this.name = ctx.getName();
this.subresource = ctx.getSubresource();
this.apiGroupName = ctx.getApiGroupName();
this.dryRun = ctx.getDryRun();
if (Utils.isNotNullOrEmpty(ctx.getApiGroupVersion())) {
this.apiGroupVersion = ctx.getApiGroupVersion();
} else if (ctx.getConfig() != null && Utils.isNotNullOrEmpty(ctx.getConfig().getApiVersion())) {
this.apiGroupVersion = ctx.getConfig().getApiVersion();
} else {
this.apiGroupVersion = "v1";
}
}
public String getAPIGroupName() {
return apiGroupName;
}
public String getAPIGroupVersion() {
return apiGroupVersion;
}
public String getResourceT() {
return resourceT;
}
public String getNamespace() {
return namespace;
}
public String getName() {
return name;
}
public boolean isResourceNamespaced() {
return true;
}
protected List getRootUrlParts() {
ArrayList result = new ArrayList<>();
result.add(config.getMasterUrl());
if (!Utils.isNullOrEmpty(apiGroupName)) {
result.add("apis");
result.add(apiGroupName);
result.add(apiGroupVersion);
} else {
result.add("api");
result.add(apiGroupVersion);
}
return result;
}
protected URL getNamespacedUrl(String namespace, String type) throws MalformedURLException {
List parts = getRootUrlParts();
addNamespacedUrlPathParts(parts, namespace, type);
URL requestUrl = new URL(URLUtils.join(parts.toArray(new String[parts.size()])));
return requestUrl;
}
public URL getNamespacedUrl(String namespace) throws MalformedURLException {
return getNamespacedUrl(namespace, resourceT);
}
protected void addNamespacedUrlPathParts(List parts, String namespace, String type) {
if (!isResourceNamespaced()) {
//if resource is not namespaced don't even bother to check the namespace.
} else if (Utils.isNotNullOrEmpty(namespace)) {
parts.add("namespaces");
parts.add(namespace);
}
if (Utils.isNotNullOrEmpty(type)) {
parts.add(type);
}
}
public URL getNamespacedUrl() throws MalformedURLException {
return getNamespacedUrl(getNamespace());
}
public URL getResourceUrl(String namespace, String name, String... subresources) throws MalformedURLException {
String subresourcePath = URLUtils.pathJoin(subresources);
if (name == null) {
if (Utils.isNotNullOrEmpty(subresourcePath)) {
throw new KubernetesClientException("name not specified for an operation requiring one.");
}
return getNamespacedUrl(namespace);
}
String path = name;
if (Utils.isNotNullOrEmpty(subresourcePath)) {
path = URLUtils.pathJoin(path, subresourcePath);
}
return new URL(URLUtils.join(getNamespacedUrl(namespace).toString(), path));
}
public URL getResourceUrl(String namespace, String name) throws MalformedURLException {
return getResourceUrl(namespace, name, subresource);
}
public URL getResourceUrl() throws MalformedURLException {
return getResourceUrl(namespace, name, subresource);
}
public URL getResourceURLForWriteOperation(URL resourceURL) throws MalformedURLException {
if (dryRun) {
resourceURL = new URL(URLUtils.join(resourceURL.toString(), "?dryRun=All"));
}
if (context.fieldValidation != null) {
resourceURL = new URL(
URLUtils.join(resourceURL.toString(), "?fieldValidation=" + context.fieldValidation.parameterValue()));
}
return resourceURL;
}
public URL getResourceURLForPatchOperation(URL resourceUrl, PatchContext patchContext) throws MalformedURLException {
if (patchContext != null) {
String url = resourceUrl.toString();
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, FIELD_MANAGER_PARAM + fieldManager);
}
String fieldValidation = patchContext.getFieldValidation();
if (fieldValidation == null && this.context.fieldValidation != null) {
fieldValidation = this.context.fieldValidation.parameterValue();
}
if (fieldValidation != null) {
url = URLUtils.join(url, "?fieldValidation=" + fieldValidation);
}
return new URL(url);
}
return resourceUrl;
}
protected T correctNamespace(T item) {
if (!isResourceNamespaced() || this.context.isDefaultNamespace() || !(item instanceof HasMetadata)) {
return item;
}
String itemNs = KubernetesResourceUtil.getNamespace((HasMetadata) item);
if (Utils.isNotNullOrEmpty(namespace) && Utils.isNotNullOrEmpty(itemNs) && !namespace.equals(itemNs)) {
item = getKubernetesSerialization().clone(item);
KubernetesResourceUtil.setNamespace((HasMetadata) item, namespace);
}
return item;
}
protected String checkNamespace(T item) {
if (!isResourceNamespaced()) {
return null;
}
String operationNs = getNamespace();
String itemNs = (item instanceof HasMetadata) ? KubernetesResourceUtil.getNamespace((HasMetadata) item) : null;
if (Utils.isNullOrEmpty(operationNs) && Utils.isNullOrEmpty(itemNs)) {
if (context.isDefaultNamespace()) {
throw new KubernetesClientException(
"namespace not specified for an operation requiring one and no default was found in the Config.");
}
throw new KubernetesClientException("namespace not specified for an operation requiring one.");
} else if (!Utils.isNullOrEmpty(itemNs) && (Utils.isNullOrEmpty(operationNs)
|| this.context.isDefaultNamespace())) {
return itemNs;
}
return operationNs;
}
protected String checkName(T item) {
String operationName = getName();
ObjectMeta metadata = item instanceof HasMetadata ? ((HasMetadata) item).getMetadata() : null;
String itemName = metadata != null ? metadata.getName() : null;
if (Utils.isNullOrEmpty(operationName) && Utils.isNullOrEmpty(itemName)) {
return null;
} else if (Utils.isNullOrEmpty(itemName)) {
return operationName;
} else if (Utils.isNullOrEmpty(operationName)) {
return itemName;
} else if (Objects.equals(itemName, operationName)) {
return itemName;
}
throw new KubernetesClientException("Name mismatch. Item name:" + itemName + ". Operation name:" + operationName + ".");
}
protected T handleMetric(String resourceUrl, Class type) throws InterruptedException, IOException {
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder()
.uri(resourceUrl);
return handleResponse(requestBuilder, type);
}
protected KubernetesResource handleDelete(URL requestUrl, long gracePeriodSeconds, DeletionPropagation propagationPolicy,
String resourceVersion) throws InterruptedException, IOException {
DeleteOptions deleteOptions = new DeleteOptions();
if (gracePeriodSeconds >= 0) {
deleteOptions.setGracePeriodSeconds(gracePeriodSeconds);
}
if (resourceVersion != null) {
deleteOptions.setPreconditions(new Preconditions(resourceVersion, null));
}
if (propagationPolicy != null) {
deleteOptions.setPropagationPolicy(propagationPolicy.toString());
}
if (dryRun) {
deleteOptions.setDryRun(Collections.singletonList("All"));
}
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder()
.delete(JSON, getKubernetesSerialization().asJson(deleteOptions)).url(requestUrl);
return handleResponse(requestBuilder, KubernetesResource.class);
}
/**
* Create a resource.
*
* @param resource resource provided
* @param outputType resource type you want as output
* @param template argument for output type
* @param template argument for resource
*
* @return returns de-serialized version of apiserver response in form of type provided
* @throws InterruptedException Interrupted Exception
* @throws IOException IOException
*/
protected T handleCreate(I resource, Class outputType) throws InterruptedException, IOException {
resource = correctNamespace(resource);
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder()
.post(JSON, getKubernetesSerialization().asJson(resource))
.url(getResourceURLForWriteOperation(getResourceUrl(checkNamespace(resource), null)));
return handleResponse(requestBuilder, outputType);
}
/**
* Replace a resource.
*
* @param updated updated object
* @param type type of the object provided
* @param template argument provided
*
* @return returns de-serialized version of api server response
* @throws IOException IOException
*/
protected T handleUpdate(T updated, Class type) throws IOException {
updated = correctNamespace(updated);
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder()
.put(JSON, getKubernetesSerialization().asJson(updated))
.url(getResourceURLForWriteOperation(getResourceUrl(checkNamespace(updated), checkName(updated))));
return handleResponse(requestBuilder, type);
}
/**
* Send an http patch and handle the response.
*
* If current is not null and patchContext does not specify a patch type, then a JSON patch is assumed. Otherwise a STRATEGIC
* MERGE is assumed.
*
* @param patchContext patch options for patch request
* @param current current object
* @param updated updated object
* @param type type of object
* @param template argument provided
*
* @return returns de-serialized version of api server response
* @throws InterruptedException Interrupted Exception
* @throws IOException IOException
*/
protected T handlePatch(PatchContext patchContext, T current, T updated, Class type)
throws InterruptedException, IOException {
String patchForUpdate;
if (current != null && (patchContext == null || patchContext.getPatchType() == PatchType.JSON)) {
if (current instanceof HasMetadata) {
ObjectMeta meta = ((HasMetadata) current).getMetadata();
if (meta != null) {
// include the resourceVersion in the patch if it's specified on the updated
meta.setResourceVersion(null);
}
}
// we can't omit status unless this is not a status operation and we know this has a status subresource
patchForUpdate = PatchUtils.jsonDiff(current, updated, false, getKubernetesSerialization());
if (patchContext == null) {
patchContext = new PatchContext.Builder().withPatchType(PatchType.JSON).build();
}
} else {
patchForUpdate = getKubernetesSerialization().asJson(updated);
current = updated; // use the updated to determine the path
}
return handlePatch(patchContext, current, patchForUpdate, type);
}
/**
* Send an http patch and handle the response.
*
* @param patchContext patch options for patch request
* @param current current object
* @param patchForUpdate Patch string
* @param type type of object
* @param template argument provided
* @return returns de-serialized version of api server response
* @throws InterruptedException Interrupted Exception
* @throws IOException IOException in case of network errors
*/
protected T handlePatch(PatchContext patchContext, T current, String patchForUpdate, Class type)
throws InterruptedException, IOException {
String bodyContentType = getContentTypeFromPatchContextOrDefault(patchContext);
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder()
.patch(bodyContentType, patchForUpdate)
.url(getResourceURLForPatchOperation(getResourceUrl(checkNamespace(current), checkName(current)),
patchContext));
return handleResponse(requestBuilder, type);
}
/**
* Replace Scale of specified Kubernetes Resource
*
* @param resourceUrl Kubernetes resource URL
* @param scale Scale object which we want to inject
* @return updated Scale object
* @throws InterruptedException in case thread is interrupted
* @throws IOException in some other I/O problem
*/
protected T handleScale(String resourceUrl, T scale, Class scaleType) throws InterruptedException, IOException {
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder().uri(resourceUrl + "/scale");
if (scale != null) {
requestBuilder.put(JSON, getKubernetesSerialization().asJson(scale));
}
return handleResponse(requestBuilder, scaleType);
}
/**
* Create rollback of a Deployment
*
* @param resourceUrl resource url
* @param deploymentRollback DeploymentRollback resource
* @return Status
* @throws InterruptedException in case thread is interrupted
* @throws IOException in some other I/O problem
*/
protected Status handleDeploymentRollback(String resourceUrl, DeploymentRollback deploymentRollback)
throws InterruptedException, IOException {
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder().uri(resourceUrl + "/rollback").post(JSON,
getKubernetesSerialization().asJson(deploymentRollback));
return handleResponse(requestBuilder, Status.class);
}
/**
* Send an http get.
*
* @param resourceUrl resource URL to be processed
* @param type type of resource
* @param template argument provided
*
* @return returns a deserialized object as api server response of provided type.
* @throws IOException IOException
*/
protected T handleGet(URL resourceUrl, Class type) throws IOException {
HttpRequest.Builder requestBuilder = httpClient.newHttpRequestBuilder().url(resourceUrl);
return handleResponse(requestBuilder, type);
}
/**
* Send a raw get - where the type should be one of String, Reader, InputStream
*/
protected T handleRawGet(URL resourceUrl, Class type) throws IOException {
return handleRaw(type, resourceUrl.toString(), "GET", null);
}
HttpRequest.Builder withRequestTimeout(HttpRequest.Builder builder) {
return builder.timeout(getRequestConfig().getRequestTimeout(), TimeUnit.MILLISECONDS);
}
/**
* Waits for the provided {@link CompletableFuture} to complete and returns the result in case of success.
*
* @param future the CompletableFuture to wait for
* @param the type of the result
* @return the result of the completed future
* @throws IOException in case there's an I/O problem
*/
protected T waitForResult(CompletableFuture future) throws IOException {
try {
return future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
InterruptedIOException ie = new InterruptedIOException();
ie.initCause(e);
throw ie;
} catch (ExecutionException e) {
Throwable t = e;
if (e.getCause() != null) {
t = e.getCause();
}
// throw a new exception to preserve the calling stack trace
if (t instanceof IOException) {
throw new IOException(t.getMessage(), t);
}
if (t instanceof KubernetesClientException) {
throw ((KubernetesClientException) t).copyAsCause();
}
throw new KubernetesClientException(t.getMessage(), t);
}
}
/**
* Send an http request and handle the response.
*
* @param requestBuilder Request Builder object
* @param type type of resource
* @param template argument provided
*
* @return Returns a de-serialized object as api server response of provided type.
* @throws IOException IOException
*/
protected T handleResponse(HttpRequest.Builder requestBuilder, Class type) throws IOException {
return waitForResult(handleResponse(httpClient, withRequestTimeout(requestBuilder), new TypeReference() {
@Override
public Type getType() {
return type;
}
}));
}
/**
* Send an http request and handle the response, optionally performing placeholder substitution to the response.
*
* @param client the client
* @param requestBuilder Request builder
* @param type Type of object provided
* @param Template argument provided
*
* @return Returns a de-serialized object as api server response of provided type.
*/
protected CompletableFuture handleResponse(HttpClient client, HttpRequest.Builder requestBuilder,
TypeReference type) {
VersionUsageUtils.log(this.resourceT, this.apiGroupVersion);
HttpRequest request = requestBuilder.build();
return client.sendAsync(request, byte[].class).thenApply(response -> {
try {
assertResponseCode(request, response);
if (type != null && type.getType() != null) {
return getKubernetesSerialization().unmarshal(new ByteArrayInputStream(response.body()), type);
} else {
return null;
}
} catch (KubernetesClientException e) {
throw e;
} catch (Exception e) {
throw requestException(request, e);
}
});
}
/**
* Checks if the response status code is the expected and throws the appropriate KubernetesClientException if not.
*
* @param request The {#link HttpRequest} object.
* @param response The {@link HttpResponse} object.
*/
protected void assertResponseCode(HttpRequest request, HttpResponse> response) {
List warnings = response.headers("Warning");
if (warnings != null && !warnings.isEmpty()) {
if (context.fieldValidation == Validation.WARN) {
LOG.warn("Recieved warning(s) from request {}: {}", request.uri(), warnings);
} else {
LOG.debug("Recieved warning(s) from request {}: {}", request.uri(), warnings);
}
}
if (response.isSuccessful()) {
return;
}
throw requestFailure(request, createStatus(response, getKubernetesSerialization()));
}
public static Status createStatus(HttpResponse> response, KubernetesSerialization kubernetesSerialization) {
String statusMessage = "";
int statusCode = response != null ? response.code() : 0;
if (response == null) {
statusMessage = "No response";
} else {
try {
String bodyString = response.bodyString();
if (Utils.isNotNullOrEmpty(bodyString)) {
Status status = kubernetesSerialization.unmarshal(bodyString, Status.class);
if (status != null) {
if (status.getCode() == null) {
status = new StatusBuilder(status).withCode(statusCode).build();
}
return status;
}
}
} catch (IOException | RuntimeException e) {
LOG.debug("Exception convertion response to Status", e);
}
if (response.message() != null) {
statusMessage = response.message();
}
}
return createStatus(statusCode, statusMessage);
}
public static Status createStatus(int statusCode, String message) {
Status status = new StatusBuilder()
.withCode(statusCode)
.withMessage(message)
.build();
status.getAdditionalProperties().put(CLIENT_STATUS_FLAG, "true");
return status;
}
public static KubernetesClientException requestFailure(HttpRequest request, Status status) {
return requestFailure(request, status, null);
}
public static KubernetesClientException requestFailure(HttpRequest request, Status status, String message) {
StringBuilder sb = new StringBuilder();
if (message != null && !message.isEmpty()) {
sb.append(message).append(". ");
}
sb.append("Failure executing: ").append(request.method())
.append(" at: ").append(request.uri()).append(".");
if (status.getMessage() != null && !status.getMessage().isEmpty()) {
sb.append(" Message: ").append(status.getMessage()).append(".");
}
if (!status.getAdditionalProperties().containsKey(CLIENT_STATUS_FLAG)) {
sb.append(" Received status: ").append(status).append(".");
}
return new KubernetesClientException(sb.toString(), null, status.getCode(), status, request);
}
public static KubernetesClientException requestException(HttpRequest request, Throwable e, String message) {
StringBuilder sb = new StringBuilder();
if (message != null && !message.isEmpty()) {
sb.append(message).append(". ");
}
sb.append("Error executing: ").append(request.method())
.append(" at: ").append(request.uri())
.append(". Cause: ").append(e.getMessage());
return new KubernetesClientException(sb.toString(), e, -1, null, request);
}
public static KubernetesClientException requestException(HttpRequest request, Exception e) {
return requestException(request, e, null);
}
public Config getConfig() {
return config;
}
public OperationContext getOperationContext() {
return context;
}
public RequestConfig getRequestConfig() {
RequestConfig result = context.getRequestConfig();
if (result == null && config != null) {
result = config.getRequestConfig();
}
return new RequestConfigBuilder(result).build();
}
private String getContentTypeFromPatchContextOrDefault(PatchContext patchContext) {
if (patchContext != null && patchContext.getPatchType() != null) {
return patchContext.getPatchType().getContentType();
}
return STRATEGIC_MERGE_JSON_PATCH;
}
public R1 restCall(Class result, String... path) {
try {
URL requestUrl = new URL(config.getMasterUrl());
String url = requestUrl.toString();
if (path != null && path.length > 0) {
url = URLUtils.join(url, URLUtils.pathJoin(path));
}
HttpRequest.Builder req = httpClient.newHttpRequestBuilder().uri(url);
return handleResponse(req, result);
} catch (KubernetesClientException e) {
if (e.getCode() != HttpURLConnection.HTTP_NOT_FOUND) {
throw e;
}
return null;
} catch (IOException e) {
throw KubernetesClientException.launderThrowable(e);
}
}
/**
* Send a raw request - where the type should be one of String, Reader, InputStream
*/
public R1 handleRaw(Class result, String uri, String method, Object payload) {
try {
if (uri.startsWith("/")) {
// master ends with slash
uri = config.getMasterUrl() + uri.substring(1, uri.length());
}
String body = null;
if (payload instanceof String) {
body = (String) payload;
} else if (payload != null) {
body = getKubernetesSerialization().asJson(payload);
}
HttpRequest request = withRequestTimeout(httpClient.newHttpRequestBuilder().uri(uri).method(method, JSON, body)).build();
HttpResponse response = waitForResult(httpClient.sendAsync(request, result));
assertResponseCode(request, response);
return response.body();
} catch (IOException e) {
throw KubernetesClientException.launderThrowable(e);
}
}
public KubernetesSerialization getKubernetesSerialization() {
return context.getClient().adapt(BaseClient.class).getKubernetesSerialization();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy