Add viewer conlet (#25)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled

This commit is contained in:
Michael N. Lipp 2024-05-27 12:57:01 +02:00 committed by GitHub
parent b6f0299932
commit a6525a2289
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 2642 additions and 250 deletions

View file

@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.common;
/**
* Some constants.
*/
@SuppressWarnings("PMD.DataClass")
public class Constants {
/** The Constant APP_NAME. */

View file

@ -44,7 +44,6 @@ import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
// TODO: Auto-generated Javadoc
/**
* Helpers for K8s API.
*/
@ -168,6 +167,7 @@ public class K8s {
* @return the object
*/
@Deprecated
@SuppressWarnings("PMD.GenericsNaming")
public static <T extends KubernetesObject, LT extends KubernetesListObject>
Optional<T>
get(GenericKubernetesApi<T, LT> api, V1ObjectMeta meta) {
@ -189,6 +189,7 @@ public class K8s {
* @return the t
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.GenericsNaming")
public static <T extends KubernetesObject, LT extends KubernetesListObject>
T apply(GenericKubernetesApi<T, LT> api, T existing, String update)
throws ApiException {

View file

@ -0,0 +1,401 @@
/*
* VM-Operator
* Copyright (C) 2024 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
import io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.Strings;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import io.kubernetes.client.util.generic.options.GetOptions;
import io.kubernetes.client.util.generic.options.ListOptions;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
/**
* A stub for cluster scoped objects. This stub provides the
* functions common to all Kubernetes objects, but uses variables
* for all types. This class should be used as base class only.
*
* @param <O> the generic type
* @param <L> the generic type
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sClusterGenericStub<O extends KubernetesObject,
L extends KubernetesListObject> {
protected final K8sClient client;
private final GenericKubernetesApi<O, L> api;
protected final APIResource context;
protected final String name;
/**
* Instantiates a new stub for the object specified. If the object
* exists in the context specified, the version (see
* {@link #version()} is bound to the existing object's version.
* Else the stub is dangling with the version set to the context's
* preferred version.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param name the name
*/
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
protected K8sClusterGenericStub(Class<O> objectClass,
Class<L> objectListClass, K8sClient client, APIResource context,
String name) {
this.client = client;
this.name = name;
// Bind version
var foundVersion = context.getPreferredVersion();
GenericKubernetesApi<O, L> testApi = null;
GetOptions mdOpts
= new GetOptions().isPartialObjectMetadataRequest(true);
for (var version : candidateVersions(context)) {
testApi = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), version, context.getResourcePlural(),
client);
if (testApi.get(name, mdOpts).isSuccess()) {
foundVersion = version;
break;
}
}
if (foundVersion.equals(context.getPreferredVersion())) {
this.context = context;
} else {
this.context = K8s.preferred(context, foundVersion);
}
api = Optional.ofNullable(testApi)
.orElseGet(() -> new GenericKubernetesApi<>(objectClass,
objectListClass, group(), version(), plural(), client));
}
/**
* Gets the context.
*
* @return the context
*/
public APIResource context() {
return context;
}
/**
* Gets the group.
*
* @return the group
*/
public String group() {
return context.getGroup();
}
/**
* Gets the version.
*
* @return the version
*/
public String version() {
return context.getPreferredVersion();
}
/**
* Gets the kind.
*
* @return the kind
*/
public String kind() {
return context.getKind();
}
/**
* Gets the plural.
*
* @return the plural
*/
public String plural() {
return context.getResourcePlural();
}
/**
* Gets the name.
*
* @return the name
*/
public String name() {
return name;
}
/**
* Delete the Kubernetes object.
*
* @throws ApiException the API exception
*/
public void delete() throws ApiException {
var result = api.delete(name);
if (result.isSuccess()
|| result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return;
}
result.throwsApiException();
}
/**
* Retrieves and returns the current state of the object.
*
* @return the object's state
* @throws ApiException the api exception
*/
public Optional<O> model() throws ApiException {
return K8s.optional(api.get(name));
}
/**
* Updates the object's status.
*
* @param object the current state of the object (passed to `status`)
* @param status function that returns the new status
* @return the updated model or empty if not successful
* @throws ApiException the api exception
*/
public Optional<O> updateStatus(O object,
Function<O, Object> status) throws ApiException {
return K8s.optional(api.updateStatus(object, status));
}
/**
* Updates the status.
*
* @param status the status
* @return the kubernetes api response
* the updated model or empty if not successful
* @throws ApiException the api exception
*/
public Optional<O> updateStatus(Function<O, Object> status)
throws ApiException {
return updateStatus(api.get(name).throwsApiException().getObject(),
status);
}
/**
* Patch the object.
*
* @param patchType the patch type
* @param patch the patch
* @param options the options
* @return the kubernetes api response
* @throws ApiException the api exception
*/
public Optional<O> patch(String patchType, V1Patch patch,
PatchOptions options) throws ApiException {
return K8s
.optional(api.patch(name, patchType, patch, options));
}
/**
* Patch the object using default options.
*
* @param patchType the patch type
* @param patch the patch
* @return the kubernetes api response
* @throws ApiException the api exception
*/
public Optional<O>
patch(String patchType, V1Patch patch) throws ApiException {
PatchOptions opts = new PatchOptions();
return patch(patchType, patch, opts);
}
/**
* A supplier for generic stubs.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the result type
*/
public interface GenericSupplier<O extends KubernetesObject,
L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>> {
/**
* Gets a new stub.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the API resource
* @param name the name
* @return the result
*/
@SuppressWarnings("PMD.UseObjectForClearerAPI")
R get(Class<O> objectClass, Class<L> objectListClass, K8sClient client,
APIResource context, String name);
}
@Override
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
public String toString() {
return (Strings.isNullOrEmpty(group()) ? "" : group() + "/")
+ version().toUpperCase() + kind() + " " + name;
}
/**
* Get an object stub. If the version in parameter
* `gvk` is an empty string, the stub refers to the first object
* found with matching group and kind.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param gvk the group, version and kind
* @param name the name
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R get(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, GroupVersionKind gvk, String name,
GenericSupplier<O, L, R> provider) throws ApiException {
var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(),
gvk.getKind());
if (context.isEmpty()) {
throw new ApiException("No known API for " + gvk.getGroup()
+ "/" + gvk.getVersion() + " " + gvk.getKind());
}
return provider.get(objectClass, objectListClass, client, context.get(),
name);
}
/**
* Get an object stub.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param name the name
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.UseObjectForClearerAPI" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R get(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, String name,
GenericSupplier<O, L, R> provider) {
return provider.get(objectClass, objectListClass, client, context,
name);
}
/**
* Get an object stub for a newly created object.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param model the model
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R create(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, O model,
GenericSupplier<O, L, R> provider) throws ApiException {
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), context.getPreferredVersion(),
context.getResourcePlural(), client);
api.create(model).throwsApiException();
return provider.get(objectClass, objectListClass, client,
context, model.getMetadata().getName());
}
/**
* Get the stubs for the objects that match
* the criteria from the given options.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param options the options
* @param provider the provider
* @return the collection
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
Collection<R> list(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context,
ListOptions options, GenericSupplier<O, L, R> provider)
throws ApiException {
var result = new ArrayList<R>();
for (var version : candidateVersions(context)) {
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), version, context.getResourcePlural(),
client);
var objs = api.list(options).throwsApiException();
for (var item : objs.getObject().getItems()) {
result.add(provider.get(objectClass, objectListClass, client,
context, item.getMetadata().getName()));
}
}
return result;
}
private static List<String> candidateVersions(APIResource context) {
var result = new LinkedList<>(context.getVersions());
result.remove(context.getPreferredVersion());
result.add(0, context.getPreferredVersion());
return result;
}
}

View file

@ -44,12 +44,13 @@ public class K8sDynamicModelTypeAdapterFactory implements TypeAdapterFactory {
* this factory
*/
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (TypeToken.get(K8sDynamicModel.class).equals(typeToken)) {
return (TypeAdapter<T>) (new K8sDynamicModelCreator(gson));
return (TypeAdapter<T>) new K8sDynamicModelCreator(gson);
}
if (TypeToken.get(K8sDynamicModels.class).equals(typeToken)) {
return (TypeAdapter<T>) (new K8sDynamicModelsCreator(gson));
return (TypeAdapter<T>) new K8sDynamicModelsCreator(gson);
}
return null;
}

View file

@ -87,7 +87,7 @@ public class K8sObserver<O extends KubernetesObject,
context.getResourcePlural(), client);
thread = new Thread(() -> {
try {
logger.info(() -> "Watching " + context.getResourcePlural()
logger.config(() -> "Watching " + context.getResourcePlural()
+ " (" + context.getPreferredVersion() + ")"
+ " in " + namespace);

View file

@ -0,0 +1,85 @@
/*
* VM-Operator
* Copyright (C) 2024 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
import io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Node;
import io.kubernetes.client.openapi.models.V1NodeList;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.util.Collection;
import java.util.List;
/**
* A stub for nodes (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
"v1", "Node", true, "nodes", "node");
/**
* Instantiates a new stub.
*
* @param client the client
* @param name the name
*/
protected K8sV1NodeStub(K8sClient client, String name) {
super(V1Node.class, V1NodeList.class, client, CONTEXT, name);
}
/**
* Gets the stub for the given name.
*
* @param client the client
* @param name the name
* @return the config map stub
*/
public static K8sV1NodeStub get(K8sClient client, String name) {
return new K8sV1NodeStub(client, name);
}
/**
* Get the stubs for the objects that match
* the criteria from the given options.
*
* @param client the client
* @param options the options
* @return the collection
* @throws ApiException the api exception
*/
public static Collection<K8sV1NodeStub> list(K8sClient client,
ListOptions options) throws ApiException {
return K8sClusterGenericStub.list(V1Node.class, V1NodeList.class,
client, CONTEXT, options, K8sV1NodeStub::getGeneric);
}
/**
* Provide {@link GenericSupplier}.
*/
@SuppressWarnings({ "PMD.UnusedFormalParameter",
"PMD.UnusedPrivateMethod" })
private static K8sV1NodeStub getGeneric(Class<V1Node> objectClass,
Class<V1NodeList> objectListClass, K8sClient client,
APIResource context, String name) {
return new K8sV1NodeStub(client, name);
}
}

View file

@ -79,7 +79,8 @@ public class K8sV1PodStub extends K8sGenericStub<V1Pod, V1PodList> {
/**
* Provide {@link GenericSupplier}.
*/
@SuppressWarnings("PMD.UnusedFormalParameter")
@SuppressWarnings({ "PMD.UnusedFormalParameter",
"PMD.UnusedPrivateMethod" })
private static K8sV1PodStub getGeneric(Class<V1Pod> objectClass,
Class<V1PodList> objectListClass, K8sClient client,
APIResource context, String namespace, String name) {

View file

@ -81,7 +81,8 @@ public class K8sV1SecretStub extends K8sGenericStub<V1Secret, V1SecretList> {
/**
* Provide {@link GenericSupplier}.
*/
@SuppressWarnings("PMD.UnusedFormalParameter")
@SuppressWarnings({ "PMD.UnusedFormalParameter",
"PMD.UnusedPrivateMethod" })
private static K8sV1SecretStub getGeneric(Class<V1Secret> objectClass,
Class<V1SecretList> objectListClass, K8sClient client,
APIResource context, String namespace, String name) {

View file

@ -0,0 +1,92 @@
/*
* VM-Operator
* Copyright (C) 2024 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
import io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServiceList;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.util.Collection;
import java.util.List;
import org.jdrupes.vmoperator.common.K8sGenericStub.GenericSupplier;
/**
* A stub for secrets (v1).
*/
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
public class K8sV1ServiceStub extends K8sGenericStub<V1Service, V1ServiceList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
"v1", "Service", true, "services", "service");
/**
* Instantiates a new stub.
*
* @param client the client
* @param namespace the namespace
* @param name the name
*/
protected K8sV1ServiceStub(K8sClient client, String namespace,
String name) {
super(V1Service.class, V1ServiceList.class, client, CONTEXT, namespace,
name);
}
/**
* Gets the stub for the given namespace and name.
*
* @param client the client
* @param namespace the namespace
* @param name the name
* @return the config map stub
*/
public static K8sV1ServiceStub get(K8sClient client, String namespace,
String name) {
return new K8sV1ServiceStub(client, namespace, name);
}
/**
* Get the stubs for the objects in the given namespace that match
* the criteria from the given options.
*
* @param client the client
* @param namespace the namespace
* @param options the options
* @return the collection
* @throws ApiException the api exception
*/
public static Collection<K8sV1ServiceStub> list(K8sClient client,
String namespace, ListOptions options) throws ApiException {
return K8sGenericStub.list(V1Service.class, V1ServiceList.class, client,
CONTEXT, namespace, options, K8sV1ServiceStub::getGeneric);
}
/**
* Provide {@link GenericSupplier}.
*/
@SuppressWarnings({ "PMD.UnusedFormalParameter",
"PMD.UnusedPrivateMethod" })
private static K8sV1ServiceStub getGeneric(Class<V1Service> objectClass,
Class<V1ServiceList> objectListClass, K8sClient client,
APIResource context, String namespace, String name) {
return new K8sV1ServiceStub(client, namespace, name);
}
}