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}