|
19 | 19 | package org.jdrupes.vmoperator.manager; |
20 | 20 |
|
21 | 21 | import com.google.gson.JsonObject; |
| 22 | +import freemarker.template.AdapterTemplateModel; |
22 | 23 | import freemarker.template.Configuration; |
23 | 24 | import freemarker.template.TemplateException; |
| 25 | +import freemarker.template.TemplateMethodModelEx; |
| 26 | +import freemarker.template.TemplateModel; |
| 27 | +import freemarker.template.TemplateModelException; |
| 28 | +import freemarker.template.utility.DeepUnwrap; |
24 | 29 | import io.kubernetes.client.custom.V1Patch; |
25 | 30 | import io.kubernetes.client.openapi.ApiClient; |
26 | 31 | import io.kubernetes.client.openapi.ApiException; |
| 32 | +import io.kubernetes.client.openapi.models.V1ObjectMeta; |
27 | 33 | import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; |
28 | 34 | import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; |
29 | 35 | import io.kubernetes.client.util.generic.dynamic.Dynamics; |
30 | 36 | import io.kubernetes.client.util.generic.options.ListOptions; |
31 | 37 | import io.kubernetes.client.util.generic.options.PatchOptions; |
32 | 38 | import java.io.IOException; |
33 | 39 | import java.io.StringWriter; |
| 40 | +import java.util.HashMap; |
| 41 | +import java.util.List; |
34 | 42 | import java.util.Map; |
| 43 | +import java.util.Objects; |
| 44 | +import java.util.Optional; |
35 | 45 | import java.util.logging.Logger; |
36 | 46 | import org.jdrupes.vmoperator.common.K8s; |
37 | 47 | import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; |
@@ -66,48 +76,59 @@ public ConfigMapReconciler(Configuration fmConfig) { |
66 | 76 | * |
67 | 77 | * @param model the model |
68 | 78 | * @param channel the channel |
69 | | - * @return the dynamic kubernetes object |
70 | 79 | * @throws IOException Signals that an I/O exception has occurred. |
71 | 80 | * @throws TemplateException the template exception |
72 | 81 | * @throws ApiException the api exception |
73 | 82 | */ |
74 | | - public Map<String, Object> reconcile(Map<String, Object> model, |
75 | | - VmChannel channel) |
| 83 | + @SuppressWarnings("PMD.AvoidDuplicateLiterals") |
| 84 | + public void reconcile(Map<String, Object> model, VmChannel channel) |
76 | 85 | throws IOException, TemplateException, ApiException { |
77 | 86 | // Combine template and data and parse result |
| 87 | + model.put("adjustCloudInitMeta", adjustCloudInitMetaModel); |
78 | 88 | var fmTemplate = fmConfig.getTemplate("runnerConfig.ftl.yaml"); |
79 | 89 | StringWriter out = new StringWriter(); |
80 | 90 | fmTemplate.process(model, out); |
81 | 91 | // Avoid Yaml.load due to |
82 | 92 | // https://github.com/kubernetes-client/java/issues/2741 |
83 | | - var mapDef = Dynamics.newFromYaml( |
| 93 | + var newCm = Dynamics.newFromYaml( |
84 | 94 | new Yaml(new SafeConstructor(new LoaderOptions())), out.toString()); |
85 | 95 |
|
86 | 96 | // Maybe override logging.properties from reconciler configuration. |
87 | 97 | DataPath.<String> get(model, "reconciler", "loggingProperties") |
88 | 98 | .ifPresent(props -> { |
89 | | - GsonPtr.to(mapDef.getRaw()).getAs(JsonObject.class, "data") |
| 99 | + GsonPtr.to(newCm.getRaw()).getAs(JsonObject.class, "data") |
90 | 100 | .get().addProperty("logging.properties", props); |
91 | 101 | }); |
92 | 102 |
|
93 | 103 | // Maybe override logging.properties from VM definition. |
94 | 104 | DataPath.<String> get(model, "cr", "spec", "loggingProperties") |
95 | 105 | .ifPresent(props -> { |
96 | | - GsonPtr.to(mapDef.getRaw()).getAs(JsonObject.class, "data") |
| 106 | + GsonPtr.to(newCm.getRaw()).getAs(JsonObject.class, "data") |
97 | 107 | .get().addProperty("logging.properties", props); |
98 | 108 | }); |
99 | 109 |
|
100 | | - // Get API |
| 110 | + // Look for changes |
| 111 | + var oldCm = channel |
| 112 | + .associated(getClass(), DynamicKubernetesObject.class).orElse(null); |
| 113 | + channel.setAssociated(getClass(), newCm); |
| 114 | + if (oldCm != null && Objects.equals(oldCm.getRaw().get("data"), |
| 115 | + newCm.getRaw().get("data"))) { |
| 116 | + logger.finer(() -> "No changes in config map for " |
| 117 | + + DataPath.<String> get(model, "cr", "name").get()); |
| 118 | + model.put("configMapResourceVersion", |
| 119 | + oldCm.getMetadata().getResourceVersion()); |
| 120 | + return; |
| 121 | + } |
| 122 | + |
| 123 | + // Get API and update |
101 | 124 | DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1", |
102 | 125 | "configmaps", channel.client()); |
103 | 126 |
|
104 | 127 | // Apply and maybe force pod update |
105 | | - var newState = K8s.apply(cmApi, mapDef, mapDef.getRaw().toString()); |
106 | | - maybeForceUpdate(channel.client(), newState); |
107 | | - @SuppressWarnings("unchecked") |
108 | | - var res = (Map<String, Object>) channel.client().getJSON().getGson() |
109 | | - .fromJson(newState.getRaw(), Map.class); |
110 | | - return res; |
| 128 | + var updatedCm = K8s.apply(cmApi, newCm, newCm.getRaw().toString()); |
| 129 | + maybeForceUpdate(channel.client(), updatedCm); |
| 130 | + model.put("configMapResourceVersion", |
| 131 | + updatedCm.getMetadata().getResourceVersion()); |
111 | 132 | } |
112 | 133 |
|
113 | 134 | /** |
@@ -153,4 +174,28 @@ private void maybeForceUpdate(ApiClient client, |
153 | 174 | } |
154 | 175 | } |
155 | 176 |
|
| 177 | + private final TemplateMethodModelEx adjustCloudInitMetaModel |
| 178 | + = new TemplateMethodModelEx() { |
| 179 | + @Override |
| 180 | + @SuppressWarnings("PMD.PreserveStackTrace") |
| 181 | + public Object exec(@SuppressWarnings("rawtypes") List arguments) |
| 182 | + throws TemplateModelException { |
| 183 | + @SuppressWarnings("unchecked") |
| 184 | + var res = new HashMap<>((Map<String, Object>) DeepUnwrap |
| 185 | + .unwrap((TemplateModel) arguments.get(0))); |
| 186 | + var metadata |
| 187 | + = (V1ObjectMeta) ((AdapterTemplateModel) arguments.get(1)) |
| 188 | + .getAdaptedObject(Object.class); |
| 189 | + if (!res.containsKey("instance-id")) { |
| 190 | + res.put("instance-id", |
| 191 | + Optional.ofNullable(metadata.getGeneration()) |
| 192 | + .map(s -> "v" + s).orElse("v1")); |
| 193 | + } |
| 194 | + if (!res.containsKey("local-hostname")) { |
| 195 | + res.put("local-hostname", metadata.getName()); |
| 196 | + } |
| 197 | + return res; |
| 198 | + } |
| 199 | + }; |
| 200 | + |
156 | 201 | } |
0 commit comments