Skip to content

Commit a5fc2ac

Browse files
committed
feature: from yaml injection
1 parent 3cf16a9 commit a5fc2ac

File tree

7 files changed

+243
-25
lines changed

7 files changed

+243
-25
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.spring.extended.manifests;
14+
15+
import com.google.common.io.Resources;
16+
import io.kubernetes.client.spring.extended.manifests.annotation.FromYaml;
17+
import io.kubernetes.client.util.Yaml;
18+
import java.io.IOException;
19+
import java.lang.reflect.Field;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.nio.file.Paths;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
import org.springframework.beans.BeansException;
26+
import org.springframework.beans.factory.BeanCreationException;
27+
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.beans.factory.BeanFactoryAware;
29+
import org.springframework.beans.factory.ListableBeanFactory;
30+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
31+
import org.springframework.beans.factory.config.BeanPostProcessor;
32+
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
33+
import org.springframework.util.ReflectionUtils;
34+
import org.yaml.snakeyaml.error.YAMLException;
35+
36+
public class KubernetesFromYamlProcessor
37+
implements InstantiationAwareBeanPostProcessor, BeanPostProcessor, BeanFactoryAware {
38+
39+
private static final Logger log = LoggerFactory.getLogger(KubernetesFromYamlProcessor.class);
40+
41+
private ListableBeanFactory beanFactory;
42+
43+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
44+
45+
FromYaml fromYamlAnnotation;
46+
try {
47+
fromYamlAnnotation = beanFactory.findAnnotationOnBean(beanName, FromYaml.class);
48+
if (fromYamlAnnotation != null) {
49+
Object loadedObj = loadFromYaml(fromYamlAnnotation.filePath());
50+
if (!bean.getClass().isAssignableFrom(loadedObj.getClass())) {
51+
log.warn(
52+
"Incompatible types for @FromYaml, {} cannot apply to {}",
53+
fromYamlAnnotation.filePath(),
54+
bean.getClass());
55+
return bean;
56+
}
57+
return loadedObj;
58+
}
59+
} catch (NoSuchBeanDefinitionException e) {
60+
return bean;
61+
}
62+
63+
for (Field field : bean.getClass().getDeclaredFields()) {
64+
ReflectionUtils.makeAccessible(field);
65+
try {
66+
if (field.get(bean) != null) {
67+
continue; // field already set, skip processing
68+
}
69+
} catch (IllegalAccessException e) {
70+
log.warn("Failed inject resource for @FromYaml annotated field {}", field, e);
71+
continue;
72+
}
73+
74+
fromYamlAnnotation = field.getAnnotation(FromYaml.class);
75+
if (fromYamlAnnotation == null) {
76+
continue; // skip if the field doesn't have the annotation
77+
}
78+
79+
Object loadedObj = loadFromYaml(fromYamlAnnotation.filePath());
80+
ReflectionUtils.setField(field, bean, loadedObj);
81+
}
82+
return bean;
83+
}
84+
85+
@Override
86+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
87+
this.beanFactory = (ListableBeanFactory) beanFactory;
88+
}
89+
90+
private Object loadFromYaml(String targetFilePath) {
91+
Path targetPath = Paths.get(targetFilePath);
92+
if (!Files.exists(Paths.get(targetFilePath))) { // checks if it exists on the machine
93+
// otherwise use load from classpath resources
94+
Path classPath = Paths.get(Resources.getResource(targetFilePath).getPath());
95+
if (Files.exists(classPath)) { // use classpath it works
96+
targetPath = classPath;
97+
} else {
98+
throw new BeanCreationException(
99+
"No such file " + targetFilePath + " either on the machine or classpaths");
100+
}
101+
}
102+
103+
try {
104+
String yamlContent = new String(Files.readAllBytes(targetPath));
105+
Object loadedObj = Yaml.load(yamlContent);
106+
return loadedObj;
107+
} catch (IOException e) {
108+
log.error("Failed reading resource for @FromYaml annotated from {}", targetFilePath, e);
109+
throw new BeanCreationException("Failed reading Yaml resource from file", e);
110+
} catch (YAMLException e) {
111+
log.error("Failed parsing yaml file {}", targetFilePath, e);
112+
throw new BeanCreationException("Failed parsing Yaml", e);
113+
}
114+
}
115+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.spring.extended.manifests.annotation;
14+
15+
import java.lang.annotation.ElementType;
16+
import java.lang.annotation.Retention;
17+
import java.lang.annotation.RetentionPolicy;
18+
import java.lang.annotation.Target;
19+
20+
/**
21+
* Injecting resources by reading from Yaml files. If the given is not present on the machine, it
22+
* will be looking for the file under class paths.
23+
*/
24+
@Target({ElementType.FIELD, ElementType.METHOD})
25+
@Retention(RetentionPolicy.RUNTIME)
26+
public @interface FromYaml {
27+
28+
/**
29+
* File path string.
30+
*
31+
* @return the string
32+
*/
33+
String filePath();
34+
}

spring/src/main/java/io/kubernetes/client/spring/extended/manifests/config/KubernetesManifestsAutoConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package io.kubernetes.client.spring.extended.manifests.config;
1414

15+
import io.kubernetes.client.spring.extended.manifests.KubernetesFromYamlProcessor;
1516
import io.kubernetes.client.spring.extended.manifests.KubernetesKubectlCreateProcessor;
1617
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
1718
import org.springframework.context.annotation.Bean;
@@ -26,4 +27,10 @@ public class KubernetesManifestsAutoConfiguration {
2627
public KubernetesKubectlCreateProcessor kubernetesKubectlCreateProcessor() {
2728
return new KubernetesKubectlCreateProcessor();
2829
}
30+
31+
@Bean
32+
@ConditionalOnMissingBean
33+
public KubernetesFromYamlProcessor kubernetesFromYamlProcessor() {
34+
return new KubernetesFromYamlProcessor();
35+
}
2936
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.spring.extended.manifests;
14+
15+
import static junit.framework.TestCase.assertNotNull;
16+
17+
import io.kubernetes.client.openapi.models.V1Service;
18+
import io.kubernetes.client.spring.extended.manifests.annotation.FromYaml;
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.beans.factory.annotation.Qualifier;
23+
import org.springframework.boot.SpringBootConfiguration;
24+
import org.springframework.boot.test.context.SpringBootTest;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.test.context.junit4.SpringRunner;
27+
28+
@RunWith(SpringRunner.class)
29+
@SpringBootTest(classes = KubernetesFromYamlTest.App.class)
30+
public class KubernetesFromYamlTest {
31+
32+
@SpringBootConfiguration
33+
static class App {
34+
35+
@Bean
36+
public KubernetesFromYamlProcessor kubernetesFromYamlProcessor() {
37+
return new KubernetesFromYamlProcessor();
38+
}
39+
40+
@Bean
41+
public MyBean myBean() {
42+
return new MyBean();
43+
}
44+
45+
@Bean
46+
@Qualifier("my-service-1")
47+
@FromYaml(filePath = "service.yaml")
48+
public V1Service service() {
49+
return new V1Service();
50+
}
51+
}
52+
53+
static class MyBean {
54+
@FromYaml(filePath = "service.yaml")
55+
private V1Service service;
56+
}
57+
58+
@Autowired private MyBean myBean;
59+
60+
@Autowired
61+
@Qualifier("my-service-1")
62+
private V1Service service;
63+
64+
@Test
65+
public void test() {
66+
assertNotNull(myBean.service);
67+
assertNotNull(service);
68+
}
69+
}

spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesManifestTest.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,13 @@
3636
import org.junit.Test;
3737
import org.junit.runner.RunWith;
3838
import org.springframework.beans.factory.annotation.Autowired;
39+
import org.springframework.boot.SpringBootConfiguration;
3940
import org.springframework.boot.test.context.SpringBootTest;
40-
import org.springframework.boot.test.context.TestConfiguration;
4141
import org.springframework.context.annotation.Bean;
42-
import org.springframework.context.annotation.Import;
4342
import org.springframework.test.context.junit4.SpringRunner;
4443

4544
@RunWith(SpringRunner.class)
46-
@SpringBootTest
47-
@Import(KubernetesManifestTest.TestConfig.class)
45+
@SpringBootTest(classes = KubernetesManifestTest.App.class)
4846
public class KubernetesManifestTest {
4947

5048
private static final String DISCOVERY_API = Resources.getResource("discovery-api.json").getPath();
@@ -57,9 +55,8 @@ public class KubernetesManifestTest {
5755

5856
@ClassRule public static WireMockRule wireMockRule = new WireMockRule(8288);
5957

60-
@TestConfiguration
61-
static class TestConfig {
62-
58+
@SpringBootConfiguration
59+
static class App {
6360
@Bean
6461
public ApiClient testingApiClient() {
6562
ApiClient apiClient = new ClientBuilder().setBasePath(wireMockRule.baseUrl()).build();

spring/src/test/java/io/kubernetes/client/spring/extended/manifests/TestApplication.java

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
labels:
5+
app: test
6+
name: test
7+
spec:
8+
ports:
9+
- name: http
10+
port: 8080
11+
targetPort: 8080
12+
selector:
13+
app: test
14+
type: ClusterIP

0 commit comments

Comments
 (0)