001/* 002 * VM-Operator 003 * Copyright (C) 2024 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.common; 020 021import io.kubernetes.client.openapi.models.V1ObjectMeta; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.EnumSet; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Optional; 029import java.util.Set; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.function.Function; 032import java.util.stream.Collectors; 033import org.jdrupes.vmoperator.util.DataPath; 034 035/** 036 * Represents a VM definition. 037 */ 038@SuppressWarnings({ "PMD.DataClass" }) 039public class VmDefinition { 040 041 private String kind; 042 private String apiVersion; 043 private V1ObjectMeta metadata; 044 private Map<String, Object> spec; 045 private Map<String, Object> status; 046 private final Map<String, Object> extra = new ConcurrentHashMap<>(); 047 048 /** 049 * The VM state from the VM definition. 050 */ 051 public enum RequestedVmState { 052 STOPPED, RUNNING 053 } 054 055 /** 056 * Permissions for accessing and manipulating the VM. 057 */ 058 public enum Permission { 059 START("start"), STOP("stop"), RESET("reset"), 060 ACCESS_CONSOLE("accessConsole"); 061 062 @SuppressWarnings("PMD.UseConcurrentHashMap") 063 private static Map<String, Permission> reprs = new HashMap<>(); 064 065 static { 066 for (var value : EnumSet.allOf(Permission.class)) { 067 reprs.put(value.repr, value); 068 } 069 } 070 071 private final String repr; 072 073 Permission(String repr) { 074 this.repr = repr; 075 } 076 077 /** 078 * Create permission from representation in CRD. 079 * 080 * @param value the value 081 * @return the permission 082 */ 083 @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") 084 public static Set<Permission> parse(String value) { 085 if ("*".equals(value)) { 086 return EnumSet.allOf(Permission.class); 087 } 088 return Set.of(reprs.get(value)); 089 } 090 091 @Override 092 public String toString() { 093 return repr; 094 } 095 } 096 097 /** 098 * Gets the kind. 099 * 100 * @return the kind 101 */ 102 public String getKind() { 103 return kind; 104 } 105 106 /** 107 * Sets the kind. 108 * 109 * @param kind the kind to set 110 */ 111 public void setKind(String kind) { 112 this.kind = kind; 113 } 114 115 /** 116 * Gets the api version. 117 * 118 * @return the apiVersion 119 */ 120 public String getApiVersion() { 121 return apiVersion; 122 } 123 124 /** 125 * Sets the api version. 126 * 127 * @param apiVersion the apiVersion to set 128 */ 129 public void setApiVersion(String apiVersion) { 130 this.apiVersion = apiVersion; 131 } 132 133 /** 134 * Gets the metadata. 135 * 136 * @return the metadata 137 */ 138 public V1ObjectMeta getMetadata() { 139 return metadata; 140 } 141 142 /** 143 * Gets the metadata. 144 * 145 * @return the metadata 146 */ 147 public V1ObjectMeta metadata() { 148 return metadata; 149 } 150 151 /** 152 * Sets the metadata. 153 * 154 * @param metadata the metadata to set 155 */ 156 public void setMetadata(V1ObjectMeta metadata) { 157 this.metadata = metadata; 158 } 159 160 /** 161 * Gets the spec. 162 * 163 * @return the spec 164 */ 165 public Map<String, Object> getSpec() { 166 return spec; 167 } 168 169 /** 170 * Gets the spec. 171 * 172 * @return the spec 173 */ 174 public Map<String, Object> spec() { 175 return spec; 176 } 177 178 /** 179 * Get a value from the spec using {@link DataPath#get}. 180 * 181 * @param <T> the generic type 182 * @param selectors the selectors 183 * @return the value, if found 184 */ 185 public <T> Optional<T> fromSpec(Object... selectors) { 186 return DataPath.get(spec, selectors); 187 } 188 189 /** 190 * Get a value from the `spec().get("vm")` using {@link DataPath#get}. 191 * 192 * @param <T> the generic type 193 * @param selectors the selectors 194 * @return the value, if found 195 */ 196 public <T> Optional<T> fromVm(Object... selectors) { 197 return DataPath.get(spec, "vm") 198 .flatMap(vm -> DataPath.get(vm, selectors)); 199 } 200 201 /** 202 * Sets the spec. 203 * 204 * @param spec the spec to set 205 */ 206 public void setSpec(Map<String, Object> spec) { 207 this.spec = spec; 208 } 209 210 /** 211 * Gets the status. 212 * 213 * @return the status 214 */ 215 public Map<String, Object> getStatus() { 216 return status; 217 } 218 219 /** 220 * Gets the status. 221 * 222 * @return the status 223 */ 224 public Map<String, Object> status() { 225 return status; 226 } 227 228 /** 229 * Get a value from the status using {@link DataPath#get}. 230 * 231 * @param <T> the generic type 232 * @param selectors the selectors 233 * @return the value, if found 234 */ 235 public <T> Optional<T> fromStatus(Object... selectors) { 236 return DataPath.get(status, selectors); 237 } 238 239 /** 240 * Sets the status. 241 * 242 * @param status the status to set 243 */ 244 public void setStatus(Map<String, Object> status) { 245 this.status = status; 246 } 247 248 /** 249 * Set extra data (locally used, unknown to kubernetes). 250 * 251 * @param property the property 252 * @param value the value 253 * @return the VM definition 254 */ 255 public VmDefinition extra(String property, Object value) { 256 extra.put(property, value); 257 return this; 258 } 259 260 /** 261 * Return extra data. 262 * 263 * @param property the property 264 * @return the object 265 */ 266 @SuppressWarnings("unchecked") 267 public <T> T extra(String property) { 268 return (T) extra.get(property); 269 } 270 271 /** 272 * Returns the definition's name. 273 * 274 * @return the string 275 */ 276 public String name() { 277 return metadata.getName(); 278 } 279 280 /** 281 * Returns the definition's namespace. 282 * 283 * @return the string 284 */ 285 public String namespace() { 286 return metadata.getNamespace(); 287 } 288 289 /** 290 * Return the requested VM state 291 * 292 * @return the string 293 */ 294 public RequestedVmState vmState() { 295 // TODO 296 return fromVm("state") 297 .map(s -> "Running".equals(s) ? RequestedVmState.RUNNING 298 : RequestedVmState.STOPPED) 299 .orElse(RequestedVmState.STOPPED); 300 } 301 302 /** 303 * Collect all permissions for the given user with the given roles. 304 * 305 * @param user the user 306 * @param roles the roles 307 * @return the sets the 308 */ 309 public Set<Permission> permissionsFor(String user, 310 Collection<String> roles) { 311 return this.<List<Map<String, Object>>> fromSpec("permissions") 312 .orElse(Collections.emptyList()).stream() 313 .filter(p -> DataPath.get(p, "user").map(u -> u.equals(user)) 314 .orElse(false) 315 || DataPath.get(p, "role").map(roles::contains).orElse(false)) 316 .map(p -> DataPath.<List<String>> get(p, "may") 317 .orElse(Collections.emptyList()).stream()) 318 .flatMap(Function.identity()) 319 .map(Permission::parse).map(Set::stream) 320 .flatMap(Function.identity()).collect(Collectors.toSet()); 321 } 322 323 /** 324 * Get the display password serial. 325 * 326 * @return the optional 327 */ 328 public Optional<Long> displayPasswordSerial() { 329 return this.<Number> fromStatus("displayPasswordSerial") 330 .map(Number::longValue); 331 } 332}