Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions docs/yaml-create-resource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Creating Kubernetes Resources from YAML

This feature allows you to create Kubernetes resources from YAML without having to specify the resource type upfront, similar to `kubectl create -f file.yaml`.

## Overview

The `Yaml.createResource()` methods automatically:
1. Parse the YAML to extract `apiVersion` and `kind`
2. Determine the appropriate Java class for the resource type
3. Load the YAML into that strongly-typed object
4. Use the GenericKubernetesApi to create the resource in the cluster

## Usage

### Create from YAML String

```java
ApiClient client = Config.defaultClient();

String yaml =
"apiVersion: v1\n" +
"kind: ConfigMap\n" +
"metadata:\n" +
" name: my-config\n" +
" namespace: default\n" +
"data:\n" +
" key: value\n";

Object resource = Yaml.createResource(client, yaml);
```

### Create from YAML File

```java
ApiClient client = Config.defaultClient();
File yamlFile = new File("my-resource.yaml");

Object resource = Yaml.createResource(client, yamlFile);
```

### Create from Reader

```java
ApiClient client = Config.defaultClient();
Reader reader = new FileReader("my-resource.yaml");

Object resource = Yaml.createResource(client, reader);
```

## Type Casting

The returned object is the strongly-typed Kubernetes object, so you can cast it if needed:

```java
Object result = Yaml.createResource(client, yaml);

if (result instanceof V1ConfigMap) {
V1ConfigMap configMap = (V1ConfigMap) result;
System.out.println("Created ConfigMap: " + configMap.getMetadata().getName());
}
```

## Supported Resources

This feature works with any Kubernetes resource type that is registered in the ModelMapper, including:
- Core resources (Pod, Service, ConfigMap, Secret, etc.)
- Apps resources (Deployment, StatefulSet, DaemonSet, etc.)
- Custom resources that have been registered

## Error Handling

The method throws:
- `IOException` if there's an error reading or parsing the YAML
- `ApiException` if there's an error creating the resource in the cluster

```java
try {
Object resource = Yaml.createResource(client, yaml);
System.out.println("Resource created successfully");
} catch (IOException e) {
System.err.println("Failed to parse YAML: " + e.getMessage());
} catch (ApiException e) {
System.err.println("Failed to create resource: " + e.getMessage());
}
```

## Comparison with Kubectl

This feature provides Java equivalent functionality to:

```bash
kubectl create -f resource.yaml
```

Instead of having to know the resource type in advance and use type-specific APIs:

```java
// Old way - you need to know it's a ConfigMap
V1ConfigMap configMap = Yaml.loadAs(yaml, V1ConfigMap.class);
CoreV1Api api = new CoreV1Api();
api.createNamespacedConfigMap("default", configMap).execute();

// New way - works with any resource type
Object resource = Yaml.createResource(client, yaml);
```

## Discovery and Resource Mapping

The feature uses API discovery to determine the correct resource plural name and API group. On first use, it may perform discovery to refresh the ModelMapper cache. Subsequent calls will use the cached information.

## See Also

- [YamlCreateResourceExample.java](../examples/examples-release-latest/src/main/java/io/kubernetes/client/examples/YamlCreateResourceExample.java) - Complete working example
- [YamlCreateResourceTest.java](../util/src/test/java/io/kubernetes/client/util/YamlCreateResourceTest.java) - Comprehensive tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Copyright 2020 The Kubernetes Authors.
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.kubernetes.client.examples;

import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.util.Yaml;
import java.io.File;
import java.io.IOException;

/**
* A simple example of how to use Yaml.createResource() to create Kubernetes resources from YAML
* without specifying the type upfront. This is equivalent to `kubectl create -f <yaml-file>`.
*
* <p>Easiest way to run this: mvn exec:java
* -Dexec.mainClass="io.kubernetes.client.examples.YamlCreateResourceExample"
*
* <p>From inside $REPO_DIR/examples
*/
public class YamlCreateResourceExample {
public static void main(String[] args) throws IOException, ApiException {
// Initialize the API client
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);

// Example 1: Create a ConfigMap from YAML string
// This method automatically determines the resource type (ConfigMap)
// and uses the appropriate API to create it
String configMapYaml =
"apiVersion: v1\n"
+ "kind: ConfigMap\n"
+ "metadata:\n"
+ " name: example-config\n"
+ " namespace: default\n"
+ "data:\n"
+ " database.url: jdbc:postgresql://localhost/mydb\n"
+ " database.user: admin\n";

System.out.println("Creating ConfigMap from YAML string...");
Object configMapResult = Yaml.createResource(client, configMapYaml);
System.out.println("Created: " + configMapResult);

// Example 2: Create a Pod from YAML string
// Again, no need to specify V1Pod.class - the method determines it automatically
String podYaml =
"apiVersion: v1\n"
+ "kind: Pod\n"
+ "metadata:\n"
+ " name: example-pod\n"
+ " namespace: default\n"
+ "spec:\n"
+ " containers:\n"
+ " - name: nginx\n"
+ " image: nginx:1.14.2\n"
+ " ports:\n"
+ " - containerPort: 80\n";

System.out.println("\nCreating Pod from YAML string...");
Object podResult = Yaml.createResource(client, podYaml);
System.out.println("Created: " + podResult);

// Example 3: Create a resource from a YAML file
// This works with any Kubernetes resource type
File yamlFile = new File("example-resource.yaml");
if (yamlFile.exists()) {
System.out.println("\nCreating resource from YAML file...");
Object fileResult = Yaml.createResource(client, yamlFile);
System.out.println("Created: " + fileResult);
}

// Example 4: Type casting if you need to access specific fields
// The returned object is the strongly-typed Kubernetes object
V1ConfigMap configMap = (V1ConfigMap) configMapResult;
System.out.println("\nConfigMap name: " + configMap.getMetadata().getName());
System.out.println("ConfigMap data: " + configMap.getData());

V1Pod pod = (V1Pod) podResult;
System.out.println("\nPod name: " + pod.getMetadata().getName());
System.out.println("Pod phase: " + pod.getStatus().getPhase());

// Clean up - delete the created resources
CoreV1Api api = new CoreV1Api();
System.out.println("\nCleaning up...");
api.deleteNamespacedConfigMap("example-config", "default").execute();
System.out.println("Deleted ConfigMap");

api.deleteNamespacedPod("example-pod", "default").execute();
System.out.println("Deleted Pod");
}
}
158 changes: 158 additions & 0 deletions util/src/main/java/io/kubernetes/client/util/Yaml.java
Original file line number Diff line number Diff line change
Expand Up @@ -566,4 +566,162 @@ private static Object modelMapper(Map<String, Object> data) throws IOException {
public static void addModelMap(String apiGroupVersion, String kind, Class<?> clazz) {
ModelMapper.addModelMap(apiGroupVersion, kind, clazz);
}

/**
* Create a Kubernetes resource from a YAML string. This method automatically determines the
* resource type from the YAML content (apiVersion and kind) and uses the appropriate API to
* create the resource.
*
* <p>This is equivalent to `kubectl create -f <yaml-content>`.
*
* <p>Example usage:
* <pre>{@code
* ApiClient client = Config.defaultClient();
* String yaml = "apiVersion: v1\n" +
* "kind: ConfigMap\n" +
* "metadata:\n" +
* " name: my-config\n" +
* " namespace: default\n";
* Object created = Yaml.createResource(client, yaml);
* }</pre>
*
* @param client The API client to use for creating the resource
* @param content The YAML content as a string
* @return The created resource object
* @throws IOException If an error occurs while reading or parsing the YAML
* @throws io.kubernetes.client.openapi.ApiException If an error occurs while creating the resource in the cluster
*/
public static Object createResource(io.kubernetes.client.openapi.ApiClient client, String content)
throws IOException, io.kubernetes.client.openapi.ApiException {
return createResource(client, new StringReader(content));
}

/**
* Create a Kubernetes resource from a YAML file. This method automatically determines the
* resource type from the YAML content (apiVersion and kind) and uses the appropriate API to
* create the resource.
*
* <p>This is equivalent to `kubectl create -f <yaml-file>`.
*
* @param client The API client to use for creating the resource
* @param f The YAML file to load
* @return The created resource object
* @throws IOException If an error occurs while reading or parsing the YAML
* @throws io.kubernetes.client.openapi.ApiException If an error occurs while creating the resource in the cluster
*/
public static Object createResource(io.kubernetes.client.openapi.ApiClient client, File f)
throws IOException, io.kubernetes.client.openapi.ApiException {
return createResource(client, new FileReader(f));
}

/**
* Create a Kubernetes resource from a YAML stream. This method automatically determines the
* resource type from the YAML content (apiVersion and kind) and uses the appropriate API to
* create the resource.
*
* <p>This is equivalent to `kubectl create -f <yaml-stream>`.
*
* @param client The API client to use for creating the resource
* @param reader The stream to load
* @return The created resource object
* @throws IOException If an error occurs while reading or parsing the YAML
* @throws io.kubernetes.client.openapi.ApiException If an error occurs while creating the resource in the cluster
*/
public static Object createResource(io.kubernetes.client.openapi.ApiClient client, Reader reader)
throws IOException, io.kubernetes.client.openapi.ApiException {
// Load the YAML as a map to extract apiVersion and kind
// Note: The getSnakeYaml() method already configures LoaderOptions with appropriate
// security settings to prevent YAML bombs and other attacks
Map<String, Object> data = getSnakeYaml(null).load(reader);

String kind = (String) data.get("kind");
if (kind == null) {
throw new IOException("Missing kind in YAML!");
}
String apiVersion = (String) data.get("apiVersion");
if (apiVersion == null) {
throw new IOException("Missing apiVersion in YAML!");
}

// Use ModelMapper to get the appropriate class for this resource type
Class<?> clazz = ModelMapper.getApiTypeClass(apiVersion, kind);
if (clazz == null) {
throw new IOException(
"Unknown apiVersion/kind: " + apiVersion + "/" + kind + ". Is it registered?");
}

// Load the YAML into the strongly typed object
// Note: This double-loading approach (first as Map, then as typed object) follows the
// design recommended in the issue discussion to properly handle type determination
Object resource = loadAs(new StringReader(getSnakeYaml(clazz).dump(data)), clazz);

// Ensure the resource is a KubernetesObject
if (!(resource instanceof io.kubernetes.client.common.KubernetesObject)) {
throw new IOException(
"Resource is not a KubernetesObject: " + resource.getClass().getName());
}

io.kubernetes.client.common.KubernetesObject k8sObject =
(io.kubernetes.client.common.KubernetesObject) resource;

// Parse apiVersion to extract group and version
io.kubernetes.client.apimachinery.GroupVersionKind gvk =
ModelMapper.groupVersionKindFromApiVersionAndKind(apiVersion, kind);

// Get the resource metadata to determine the plural name
io.kubernetes.client.apimachinery.GroupVersionResource gvr =
ModelMapper.getGroupVersionResourceByClass(clazz);

if (gvr == null) {
// If no GVR mapping exists, we need to perform discovery
io.kubernetes.client.Discovery discovery = new io.kubernetes.client.Discovery(client);
ModelMapper.refresh(discovery);
gvr = ModelMapper.getGroupVersionResourceByClass(clazz);

if (gvr == null) {
throw new IOException(
"Unable to determine resource plural name for " + apiVersion + "/" + kind);
}
}

// Create a GenericKubernetesApi for this resource type
io.kubernetes.client.util.generic.GenericKubernetesApi<
io.kubernetes.client.common.KubernetesObject,
io.kubernetes.client.common.KubernetesListObject>
api =
new io.kubernetes.client.util.generic.GenericKubernetesApi<>(
(Class<io.kubernetes.client.common.KubernetesObject>) clazz,
io.kubernetes.client.common.KubernetesListObject.class,
gvk.getGroup(),
gvk.getVersion(),
gvr.getResource(),
client);

// Create the resource
io.kubernetes.client.util.generic.KubernetesApiResponse<
io.kubernetes.client.common.KubernetesObject>
response;

Boolean isNamespaced = ModelMapper.isNamespaced(clazz);
if (isNamespaced != null && isNamespaced) {
// For namespaced resources
String namespace = k8sObject.getMetadata().getNamespace();
if (namespace == null || namespace.isEmpty()) {
// Default to "default" namespace, matching kubectl behavior
namespace = "default";
}
response = api.create(namespace, k8sObject, new io.kubernetes.client.util.generic.options.CreateOptions());
} else {
// For cluster-scoped resources
response = api.create(k8sObject, new io.kubernetes.client.util.generic.options.CreateOptions());
}

if (!response.isSuccess()) {
throw new io.kubernetes.client.openapi.ApiException(
response.getHttpStatusCode(),
"Failed to create resource: " + response.getStatus());
}

return response.getObject();
}
}
Loading