diff --git a/util/pom.xml b/util/pom.xml index 0a6cace80e..a1e10e366b 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -25,7 +25,7 @@ org.yaml snakeyaml - 1.18 + 1.19 commons-codec diff --git a/util/src/main/java/io/kubernetes/client/util/Yaml.java b/util/src/main/java/io/kubernetes/client/util/Yaml.java new file mode 100644 index 0000000000..ce4a11bf85 --- /dev/null +++ b/util/src/main/java/io/kubernetes/client/util/Yaml.java @@ -0,0 +1,188 @@ +/* +Copyright 2018 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.util; + +import com.google.common.reflect.ClassPath; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Yaml { + private static org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml(); + private static Map> classes = new HashMap<>(); + + static final Logger logger = LoggerFactory.getLogger(Yaml.class); + + public static String getApiGroupVersion(String name) { + if (name.startsWith("AppsV1")) { + return "apps/v1"; + } + if (name.startsWith("AppsV1beta1")) { + return "apps/v1beta1"; + } + if (name.startsWith("ExtensionsV1beta1")) { + return "extensions/v1beta1"; + } + if (name.startsWith("ExtensionsV1")) { + return "extensions/v1"; + } + if (name.startsWith("V1beta1")) { + return "v1beta1"; + } + if (name.startsWith("V1beta2")) { + return "v1beta2"; + } + if (name.startsWith("V1alpha1")) { + return "v1alpha1"; + } + if (name.startsWith("V2beta1")) { + return "v2beta1"; + } + if (name.startsWith("V2alpha1")) { + return "v2alpha1"; + } + if (name.startsWith("V1")) { + return "v1"; + } + return name; + } + + private static void initModelMap() throws IOException { + ClassPath cp = ClassPath.from(ClassLoader.getSystemClassLoader()); + Set allClasses = cp.getTopLevelClasses("io.kubernetes.client.models"); + + for (ClassPath.ClassInfo clazz : allClasses) { + String groupVersion = getApiGroupVersion(clazz.getSimpleName()); + int len = groupVersion.replace("/", "").length(); + String name = clazz.getSimpleName().substring(len); + classes.put(groupVersion + "/" + name, clazz.load()); + } + } + + static { + try { + initModelMap(); + } catch (Exception ex) { + logger.error("Unexpected exception while loading classes: " + ex); + } + } + + /** + * Add a mapping from API Group/version/kind to a Class to use when calling load(...) + * . + * + *

Shouldn't really be needed as most API Group/Version/Kind are loaded dynamically at startup. + */ + public static void addModelMap(String apiGroupVersion, String kind, Class clazz) { + classes.put(apiGroupVersion + "/" + kind, clazz); + } + + /** + * Load an API object from a YAML string representation. Returns a concrete typed object (e.g. + * V1Pod) + * + * @param content The YAML content + * @return An instantiation of the object. + * @throws IOException If an error occurs while reading the YAML. + */ + public static Object load(String content) throws IOException { + return load(new StringReader(content)); + } + + /** + * Load an API object from a YAML file. Returns a concrete typed object (e.g. V1Pod) + * + * @param f The file to load. + * @return An instantiation of the object. + * @throws IOException If an error occurs while reading the YAML. + */ + public static Object load(File f) throws IOException { + return load(new FileReader(f)); + } + + /** + * Load an API object from a stream of data. Returns a concrete typed object (e.g. V1Pod) + * + * @param reader The stream to load. + * @return An instantiation of the object. + * @throws IOException If an error occurs while reading the YAML. + */ + public static Object load(Reader reader) throws IOException { + Map data = yaml.load(reader); + String kind = (String) data.get("kind"); + if (kind == null) { + throw new IOException("Missing kind in YAML file!"); + } + String apiVersion = (String) data.get("apiVersion"); + if (apiVersion == null) { + throw new IOException("Missing apiVersion in YAML file!"); + } + + Class clazz = (Class) classes.get(apiVersion + "/" + kind); + if (clazz == null) { + throw new IOException( + "Unknown apiVersionKind: " + + apiVersion + + "/" + + kind + + " known kinds are: " + + classes.toString()); + } + return loadAs(new StringReader(yaml.dump(data)), clazz); + } + + /** + * Load an API object from a YAML string representation. Returns a concrete typed object using the + * type specified. + * + * @param content The YAML content + * @param clazz The class of object to return. + * @return An instantiation of the object. + * @throws IOException If an error occurs while reading the YAML. + */ + public static T loadAs(String content, Class clazz) { + return yaml.loadAs(new StringReader(content), clazz); + } + + /** + * Load an API object from a YAML file. Returns a concrete typed object using the type specified. + * + * @param f The YAML file + * @param clazz The class of object to return. + * @return An instantiation of the object. + * @throws IOException If an error occurs while reading the YAML. + */ + public static T loadAs(File f, Class clazz) throws IOException { + return yaml.loadAs(new FileReader(f), clazz); + } + + /** + * Load an API object from a YAML stream. Returns a concrete typed object using the type + * specified. + * + * @param reader The YAML stream + * @param clazz The class of object to return. + * @return An instantiation of the object. + * @throws IOException If an error occurs while reading the YAML. + */ + public static T loadAs(Reader reader, Class clazz) { + return yaml.loadAs(reader, clazz); + } +} diff --git a/util/src/test/java/io/kubernetes/client/util/YamlTest.java b/util/src/test/java/io/kubernetes/client/util/YamlTest.java new file mode 100644 index 0000000000..fb12d2794b --- /dev/null +++ b/util/src/test/java/io/kubernetes/client/util/YamlTest.java @@ -0,0 +1,60 @@ +/* +Copyright 2018 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.util; + +import static org.junit.Assert.*; + +import io.kubernetes.client.models.V1ObjectMeta; +import java.io.StringReader; +import java.lang.reflect.Method; +import org.junit.Test; + +public class YamlTest { + @Test + public void testLoad() { + String[] kinds = new String[] {"Pod", "Deployment", "ClusterRole", "APIService", "Scale"}; + String[] apiVersions = + new String[] {"v1", "v1beta2", "v1alpha1", "v1beta1", "extensions/v1beta1"}; + String[] classNames = + new String[] { + "V1Pod", + "V1beta2Deployment", + "V1alpha1ClusterRole", + "V1beta1APIService", + "ExtensionsV1beta1Scale" + }; + for (int i = 0; i < kinds.length; i++) { + String kind = kinds[i]; + String className = classNames[i]; + try { + String input = + "kind: " + + kind + + "\n" + + "apiVersion: " + + apiVersions[i] + + "\n" + + "metadata:\n" + + " name: foo"; + Object obj = Yaml.load(new StringReader(input)); + Method m = obj.getClass().getMethod("getMetadata"); + V1ObjectMeta metadata = (V1ObjectMeta) m.invoke(obj); + + assertEquals("foo", metadata.getName()); + assertEquals(className, obj.getClass().getSimpleName()); + } catch (Exception ex) { + assertNull("Unexpected exception: " + ex.toString(), ex); + } + } + } +}