Skip to content

Commit 551da66

Browse files
Add Yaml.createResource() methods for creating resources from arbitrary YAML
Co-authored-by: brendandburns <[email protected]>
1 parent f96a2d3 commit 551da66

File tree

8 files changed

+440
-0
lines changed

8 files changed

+440
-0
lines changed

util/src/main/java/io/kubernetes/client/util/Yaml.java

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,4 +566,157 @@ private static Object modelMapper(Map<String, Object> data) throws IOException {
566566
public static void addModelMap(String apiGroupVersion, String kind, Class<?> clazz) {
567567
ModelMapper.addModelMap(apiGroupVersion, kind, clazz);
568568
}
569+
570+
/**
571+
* Create a Kubernetes resource from a YAML string. This method automatically determines the
572+
* resource type from the YAML content (apiVersion and kind) and uses the appropriate API to
573+
* create the resource.
574+
*
575+
* <p>This is equivalent to `kubectl create -f <yaml-content>`.
576+
*
577+
* <p>Example usage:
578+
* <pre>{@code
579+
* ApiClient client = Config.defaultClient();
580+
* String yaml = "apiVersion: v1\n" +
581+
* "kind: ConfigMap\n" +
582+
* "metadata:\n" +
583+
* " name: my-config\n" +
584+
* " namespace: default\n";
585+
* Object created = Yaml.createResource(client, yaml);
586+
* }</pre>
587+
*
588+
* @param client The API client to use for creating the resource
589+
* @param content The YAML content as a string
590+
* @return The created resource object
591+
* @throws IOException If an error occurs while reading or parsing the YAML
592+
* @throws io.kubernetes.client.openapi.ApiException If an error occurs while creating the resource in the cluster
593+
*/
594+
public static Object createResource(io.kubernetes.client.openapi.ApiClient client, String content)
595+
throws IOException, io.kubernetes.client.openapi.ApiException {
596+
return createResource(client, new StringReader(content));
597+
}
598+
599+
/**
600+
* Create a Kubernetes resource from a YAML file. This method automatically determines the
601+
* resource type from the YAML content (apiVersion and kind) and uses the appropriate API to
602+
* create the resource.
603+
*
604+
* <p>This is equivalent to `kubectl create -f <yaml-file>`.
605+
*
606+
* @param client The API client to use for creating the resource
607+
* @param f The YAML file to load
608+
* @return The created resource object
609+
* @throws IOException If an error occurs while reading or parsing the YAML
610+
* @throws io.kubernetes.client.openapi.ApiException If an error occurs while creating the resource in the cluster
611+
*/
612+
public static Object createResource(io.kubernetes.client.openapi.ApiClient client, File f)
613+
throws IOException, io.kubernetes.client.openapi.ApiException {
614+
return createResource(client, new FileReader(f));
615+
}
616+
617+
/**
618+
* Create a Kubernetes resource from a YAML stream. This method automatically determines the
619+
* resource type from the YAML content (apiVersion and kind) and uses the appropriate API to
620+
* create the resource.
621+
*
622+
* <p>This is equivalent to `kubectl create -f <yaml-stream>`.
623+
*
624+
* @param client The API client to use for creating the resource
625+
* @param reader The stream to load
626+
* @return The created resource object
627+
* @throws IOException If an error occurs while reading or parsing the YAML
628+
* @throws io.kubernetes.client.openapi.ApiException If an error occurs while creating the resource in the cluster
629+
*/
630+
public static Object createResource(io.kubernetes.client.openapi.ApiClient client, Reader reader)
631+
throws IOException, io.kubernetes.client.openapi.ApiException {
632+
// Load the YAML as a map to extract apiVersion and kind
633+
Map<String, Object> data = getSnakeYaml(null).load(reader);
634+
635+
String kind = (String) data.get("kind");
636+
if (kind == null) {
637+
throw new IOException("Missing kind in YAML!");
638+
}
639+
String apiVersion = (String) data.get("apiVersion");
640+
if (apiVersion == null) {
641+
throw new IOException("Missing apiVersion in YAML!");
642+
}
643+
644+
// Use ModelMapper to get the appropriate class for this resource type
645+
Class<?> clazz = ModelMapper.getApiTypeClass(apiVersion, kind);
646+
if (clazz == null) {
647+
throw new IOException(
648+
"Unknown apiVersion/kind: " + apiVersion + "/" + kind + ". Is it registered?");
649+
}
650+
651+
// Load the YAML into the strongly typed object
652+
Object resource = loadAs(new StringReader(getSnakeYaml(clazz).dump(data)), clazz);
653+
654+
// Ensure the resource is a KubernetesObject
655+
if (!(resource instanceof io.kubernetes.client.common.KubernetesObject)) {
656+
throw new IOException(
657+
"Resource is not a KubernetesObject: " + resource.getClass().getName());
658+
}
659+
660+
io.kubernetes.client.common.KubernetesObject k8sObject =
661+
(io.kubernetes.client.common.KubernetesObject) resource;
662+
663+
// Parse apiVersion to extract group and version
664+
io.kubernetes.client.apimachinery.GroupVersionKind gvk =
665+
ModelMapper.groupVersionKindFromApiVersionAndKind(apiVersion, kind);
666+
667+
// Get the resource metadata to determine the plural name
668+
io.kubernetes.client.apimachinery.GroupVersionResource gvr =
669+
ModelMapper.getGroupVersionResourceByClass(clazz);
670+
671+
if (gvr == null) {
672+
// If no GVR mapping exists, we need to perform discovery
673+
io.kubernetes.client.Discovery discovery = new io.kubernetes.client.Discovery(client);
674+
ModelMapper.refresh(discovery);
675+
gvr = ModelMapper.getGroupVersionResourceByClass(clazz);
676+
677+
if (gvr == null) {
678+
throw new IOException(
679+
"Unable to determine resource plural name for " + apiVersion + "/" + kind);
680+
}
681+
}
682+
683+
// Create a GenericKubernetesApi for this resource type
684+
io.kubernetes.client.util.generic.GenericKubernetesApi<
685+
io.kubernetes.client.common.KubernetesObject,
686+
io.kubernetes.client.common.KubernetesListObject>
687+
api =
688+
new io.kubernetes.client.util.generic.GenericKubernetesApi<>(
689+
(Class<io.kubernetes.client.common.KubernetesObject>) clazz,
690+
io.kubernetes.client.common.KubernetesListObject.class,
691+
gvk.getGroup(),
692+
gvk.getVersion(),
693+
gvr.getResource(),
694+
client);
695+
696+
// Create the resource
697+
io.kubernetes.client.util.generic.KubernetesApiResponse<
698+
io.kubernetes.client.common.KubernetesObject>
699+
response;
700+
701+
Boolean isNamespaced = ModelMapper.isNamespaced(clazz);
702+
if (isNamespaced != null && isNamespaced) {
703+
// For namespaced resources
704+
String namespace = k8sObject.getMetadata().getNamespace();
705+
if (namespace == null || namespace.isEmpty()) {
706+
namespace = "default";
707+
}
708+
response = api.create(namespace, k8sObject, new io.kubernetes.client.util.generic.options.CreateOptions());
709+
} else {
710+
// For cluster-scoped resources
711+
response = api.create(k8sObject, new io.kubernetes.client.util.generic.options.CreateOptions());
712+
}
713+
714+
if (!response.isSuccess()) {
715+
throw new io.kubernetes.client.openapi.ApiException(
716+
response.getHttpStatusCode(),
717+
"Failed to create resource: " + response.getStatus());
718+
}
719+
720+
return response.getObject();
721+
}
569722
}

0 commit comments

Comments
 (0)