Skip to content

Commit

Permalink
final commit
Browse files Browse the repository at this point in the history
  • Loading branch information
vaidikcode committed Oct 24, 2024
1 parent f9c82f3 commit 8140b31
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,29 @@
*/
package io.kubernetes.client.extended.kubectl;

import io.kubernetes.client.Discovery;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
import io.kubernetes.client.util.ModelMapper;
import io.kubernetes.client.util.Namespaces;
import io.kubernetes.client.util.Strings;
import io.kubernetes.client.util.Yaml;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import io.kubernetes.client.util.generic.KubernetesApiResponse;
import io.kubernetes.client.util.generic.options.CreateOptions;

import java.io.Reader;
import java.text.ParseException;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class KubectlCreate<ApiType extends KubernetesObject>
extends Kubectl.ResourceBuilder<ApiType, KubectlCreate<ApiType>>
implements Kubectl.Executable<ApiType> {
Expand Down Expand Up @@ -65,4 +78,118 @@ public ApiType execute() throws KubectlException {
}
}
}
/**
* Loads an API object from a YAML string representation without knowing the object type
* and submits any type of resource.
*
* Note: This method works for single-instance YAML files only.
* If the YAML contains multiple resources, it will fail.
* Support for multiple resources in a single YAML file is not implemented yet.
*
* @param content The YAML content as a Reader
* @return The created resource object
* @throws Exception If there is an error in parsing and submitting resources.
*/
public static Object loadAndSubmitResource(Reader content) throws Exception {
// Initialize the API client and Discovery instance
ApiClient client = Configuration.getDefaultApiClient();
Discovery discovery = new Discovery(client);

// Load the YAML content as an unstructured object
Object unstructuredObject = Yaml.load(content);

// Ensure the unstructured object is a Map
if (!(unstructuredObject instanceof Map)) {
throw new ParseException("Invalid YAML", 0);
}

// Typecasting the unstructured object to a Map
Map<String, Object> yamlMap = (Map<String, Object>) unstructuredObject;

// Getting apiVersion and kind
String apiVersion = (String) yamlMap.get("apiVersion");
String kind = (String) yamlMap.get("kind");

// Validate apiVersion and kind
if (apiVersion == null || kind == null) {
throw new ParseException("YAML does not contain apiVersion or kind", 0);
}

// Finding the resource in the discovery class
Set<Discovery.APIResource> resources = discovery.findAll();
Optional<Discovery.APIResource> apiResource = resources.stream()
.filter(r -> r.getKind().equalsIgnoreCase(kind))
.findFirst();

// Check if the resource kind was found
if (apiResource.isPresent()) {
String group = apiResource.get().getGroup();
String version = apiResource.get().getPreferredVersion();
String resourcePlural = apiResource.get().getResourcePlural();

// Getting the strongly typed class from ModelMapper
Class<?> modelClass = ModelMapper.getApiTypeClass(group, version, kind);

// Validate model class
if (modelClass == null) {
throw new IllegalArgumentException("Could not determine model class for group: "
+ group + ", version: " + version + ", kind: " + kind);
}

// Reload the YAML into a strongly typed object
KubernetesObject typedObject = (KubernetesObject) Yaml.loadAs(content, modelClass);

// Submit the resource to Kubernetes API
return submitResourceToApi(typedObject, (Class<KubernetesObject>) modelClass, group, version, resourcePlural);
} else {
throw new IllegalArgumentException("Invalid resource kind: " + kind);
}
}

/**
* Submits any resource to Kubernetes API.
*
* @param resource The resource to be submitted
* @param apiTypeClass The class type of the API resource
* @param group The API group of the resource
* @param version The API version of the resource
* @param resourcePlural The plural form of the resource
* @return The created resource object from the API response
* @throws Exception If there is an error in submitting the resource
*/
public static <ApiType extends KubernetesObject> Object submitResourceToApi(
ApiType resource,
Class<ApiType> apiTypeClass,
String group,
String version,
String resourcePlural) throws Exception {

// Creating an API client and configuring it
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);

// Using the apiTypeClass and apiListTypeClass for list of resources
Class<?> apiListTypeClass = ModelMapper.getApiTypeClass(group, version, resourcePlural);

// Configuring the GenericKubernetesApi handler
GenericKubernetesApi<ApiType, ?> genericApi = new GenericKubernetesApi<>(
apiTypeClass,
apiListTypeClass,
group,
version,
resourcePlural,
new CustomObjectsApi(client)
);

// Creating the resource in Kubernetes
KubernetesApiResponse<ApiType> apiResponse = genericApi.create(resource);

// Check if the resource creation was successful
if (!apiResponse.isSuccess()) {
throw new RuntimeException("Failed to create resource: " + apiResponse.getStatus());
}

// Return the created resource
return apiResponse.getObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,29 @@
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.util.ClientBuilder;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

import io.kubernetes.client.util.Yaml;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

class KubectlCreateTest {

Expand Down Expand Up @@ -106,4 +114,50 @@ void createConfigMap() throws KubectlException, IOException {
apiServer.verify(1, postRequestedFor(urlPathEqualTo("/api/v1/namespaces/foo/configmaps")));
assertThat(configMap).isNotNull();
}
@Test
void testLoadAndSubmitResourceSuccess() throws Exception {
String yamlContent =
"apiVersion: v1\n" +
"kind: Pod\n" +
"metadata:\n" +
" name: test-pod\n";

Reader reader = new StringReader(yamlContent);

try (MockedStatic<Yaml> yamlMock = Mockito.mockStatic(Yaml.class)) {
V1Pod mockPod = new V1Pod();
yamlMock.when(() -> Yaml.load(reader)).thenReturn(mockPod);

Object result = KubectlCreate.loadAndSubmitResource(reader);

assertThat(result).isNotNull();
assertThat(result).isInstanceOf(V1Pod.class);
}
}

@Test
void testLoadAndSubmitResourceInvalidYaml() {
String invalidYamlContent = "invalid: yaml";
Reader reader = new StringReader(invalidYamlContent);

Exception exception = assertThrows(Exception.class, () -> {
KubectlCreate.loadAndSubmitResource(reader);
});

assertThat(exception.getMessage()).contains("Invalid YAML");
}

@Test
void testSubmitResourceToApiFailure() throws Exception {
V1Pod pod = new V1Pod();
String group = "apps";
String version = "v1";
String resourcePlural = "pods";

Exception exception = assertThrows(RuntimeException.class, () -> {
KubectlCreate.submitResourceToApi(pod, V1Pod.class, group, version, resourcePlural);
});

assertThat(exception.getMessage()).contains("Failed to create resource");
}
}

0 comments on commit 8140b31

Please sign in to comment.