
org.microbean.helm.chart.Configs Maven / Gradle / Ivy
Show all versions of microbean-helm Show documentation
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
* Copyright © 2017-2018 microBean.
*
* 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 org.microbean.helm.chart;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import hapi.chart.ChartOuterClass.ChartOrBuilder;
import hapi.chart.ConfigOuterClass.Config;
import hapi.chart.ConfigOuterClass.ConfigOrBuilder;
import hapi.chart.ConfigOuterClass.ValueOrBuilder;
import hapi.chart.MetadataOuterClass.Metadata;
import hapi.services.tiller.Tiller.InstallReleaseRequest; // for javadoc only
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Replicates the intended behavior of the {@code
* CoalesceValues} function found in Helm's {@code chartutil}
* package.
*
* @author Laird Nelson
*
* @see {@code
* CoalesceValues}
*/
final class Configs {
/*
* Constructors.
*/
/**
* Creates a new {@link Configs}.
*/
private Configs() {
super();
}
/*
* Static methods.
*/
/**
* Given a {@link ChartOrBuilder}, flattens its
* {@linkplain ChartOrBuilder#getValues() default values} into
* a {@link Map}.
*
* This method never returns {@code null}.
*
* This method calls the {@link
* #toValuesMap(ChartOuterClass.ChartOrBuilder,
* ConfigOuterClass.ConfigOrBuilder)} with {@code chart} as its
* first argument and {@code null} as its second argument and
* returns its return value.
*
* The {@link Map} returned by this method may have nested {@link
* Map Map<String, Object>}s as its values. It is, in other
* words, a {@link Map} representation of YAML.
*
* @param chart the {@link ChartOrBuilder} whose {@linkplain
* ChartOrBuilder#getValues() values} will be taken into
* consideration; may be {@code null}
*
* @return a {@link Map} of values that, for example, can be
* {@linkplain Yaml#dump(Object) marshalled to YAML} and passed to,
* for example, {@link
* InstallReleaseRequest.Builder#setValues(Config)}; never {@code
* null}
*
* @see #toValuesMap(ChartOuterClass.ChartOrBuilder, ConfigOuterClass.ConfigOrBuilder)
*/
static final Map toDefaultValuesMap(final ChartOrBuilder chart) {
return toValuesMap(chart, (ConfigOrBuilder)null);
}
/**
* Given an optional set of overriding values in {@link
* ConfigOrBuilder} form, and a {@link ChartOrBuilder} whose
* {@linkplain ChartOrBuilder#getValues() default values} are being
* overridden, flattens both {@link ConfigOrBuilder} instances into
* a {@link Map}, such that the overriding values are dominant and
* the {@link ChartOrBuilder}'s {@linkplain
* ChartOrBuilder#getValues() values} are recessive.
*
* This method never returns {@code null}.
*
* The {@link Map} returned by this method may have nested {@link
* Map Map<String, Object>}s as its values. It is, in other
* words, a {@link Map} representation of YAML.
*
* @param chart the {@link ChartOrBuilder} whose {@linkplain
* ChartOrBuilder#getValues() values} will be taken into
* consideration; may be {@code null}
*
* @param config the overriding values; may be {@code null}
*
* @return a {@link Map} of values that, for example, can be
* {@linkplain Yaml#dump(Object) marshalled to YAML} and passed to,
* for example, {@link
* InstallReleaseRequest.Builder#setValues(Config)}; never {@code
* null}
*/
static final Map toValuesMap(final ChartOrBuilder chart, final ConfigOrBuilder config) {
final Map configAsMap;
if (config == null) {
configAsMap = null;
} else {
configAsMap = toMap(config);
}
final Map map = toValuesMap(chart, configAsMap);
assert map != null;
final Map returnValue = coalesceDependencies(chart, map);
return returnValue;
}
/*
* Private static methods.
*/
/**
* Gets the supplied {@link ChartOrBuilder}'s {@link
* ChartOrBuilder#getValues() ConfigOrBuilder} representing its
* default values, grabs its {@linkplain ConfigOrBuilder#getRaw()
* YAML representation}, marshals it into a {@link Map} using the
* {@link Yaml#loadAs(String, Class)} method, and then passes that
* {@link Map} as the first parameter—and the {@code
* targetMap} as the second parameter—to the {@link
* Values#coalesceMaps(Map, Map)} method and returns its result.
*
* This method never returns {@code null}.
*
* This method does not consider subcharts or global values in
* any way.
*
* @param chart the {@link ChartOrBuilder} whose default values
* should be harvested; may be {@code null}
*
* @param targetMap the {@link Map} of typically user-supplied
* values that will be (possibly) modified and returned (if
* non-{@code null})
*
* @return {@code targetMap}, with possibly changed contents, if it
* is non-{@code null}, or a new {@link Map}
*
* @see Values#coalesceMaps(Map, Map)
*
* @see ChartOrBuilder#getValues()
*
* @see ConfigOrBuilder#getRaw()
*
* @see Yaml#loadAs(String, Class)
*/
private static final Map computeEffectiveValues(final ChartOrBuilder chart, Map targetMap) {
if (targetMap == null) {
targetMap = new HashMap<>();
}
if (chart != null) {
final ConfigOrBuilder config = chart.getValues();
if (config != null) {
targetMap = computeEffectiveValues(config, targetMap);
}
}
return targetMap;
}
static final Map toMap(final ConfigOrBuilder config) {
return computeEffectiveValues(config, null);
}
private static final Map computeEffectiveValues(final ConfigOrBuilder config, Map targetMap) {
if (targetMap == null) {
targetMap = new HashMap<>();
}
if (config != null) {
final Map sourceMap;
final String raw = config.getRaw();
if (raw == null || raw.isEmpty()) {
final Map extends String, ? extends ValueOrBuilder> valuesMap = config.getValuesMap();
if (valuesMap == null || valuesMap.isEmpty()) {
sourceMap = null;
} else {
sourceMap = new HashMap<>();
final Collection extends Entry extends String, ? extends ValueOrBuilder>> entrySet = valuesMap.entrySet();
assert entrySet != null;
assert !entrySet.isEmpty();
for (final Entry extends String, ? extends ValueOrBuilder> entry : entrySet) {
if (entry != null) {
final String name = entry.getKey();
if (name != null) {
final ValueOrBuilder value = entry.getValue();
if (value == null) {
sourceMap.put(name, null);
} else {
sourceMap.put(name, value.getValue());
}
}
}
}
}
} else {
@SuppressWarnings("unchecked")
final Map temp = new Yaml(new SafeConstructor()).load(raw);
sourceMap = temp;
}
targetMap = Values.coalesceMaps(sourceMap, targetMap);
}
return targetMap;
}
/**
* First gets the "right" values to use by blending the supplied
* {@link Map} of typically user-supplied values with the
* {@linkplain ChartOrBuilder#getValues() default values present in
* the supplied ChartOrBuilder
}, and then calls {@link
* #coalesceDependencies(ChartOuterClass.ChartOrBuilder, Map)} on
* the results.
*
* This method first calls {@link
* #computeEffectiveValues(ChartOuterClass.ChartOrBuilder, Map)},
* producing a {@link Map} that combines user-specified and default
* values, and then passes the supplied {@code chart} and the values
* {@link Map} to the {@link
* #coalesceDependencies(ChartOuterClass.ChartOrBuilder, Map)}
* method and returns its result.
*
* @param chart a {@link ChartOrBuilder}
*
* @param suppliedValues the {@link Map} that will ultimately be
* modified and returned
*
* @return {@code suppliedValues}
*
* @see #coalesceDependencies(ChartOuterClass.ChartOrBuilder, Map)
*
* @see #computeEffectiveValues(ChartOuterClass.ChartOrBuilder, Map)
*/
private static final Map toValuesMap(final ChartOrBuilder chart, Map suppliedValues) {
final Map effectiveValues = computeEffectiveValues(chart, suppliedValues);
assert suppliedValues == null || effectiveValues == suppliedValues;
assert effectiveValues != null;
final Map returnValue = coalesceDependencies(chart, effectiveValues);
assert returnValue == effectiveValues;
return returnValue;
}
private static final Map coalesceDependencies(final ChartOrBuilder chart) {
final Map effectiveValues = computeEffectiveValues(chart, new HashMap<>());
assert effectiveValues != null;
return coalesceDependencies(chart, effectiveValues);
}
private static final Map coalesceDependencies(final ChartOrBuilder chart, Map returnValue) {
if (chart != null) {
returnValue = coalesceDependencies(chart.getDependenciesList(), returnValue);
assert returnValue != null;
}
return returnValue;
}
/**
* One specific part of the general flattening of the values to be
* used during a chart operation, this method adds an entry, one per
* subchart, to the supplied {@code Map}, under that subchart's
* name, containing its (flattened in turn) set of values.
*
* This method never returns {@code null}.
*
* If the supplied {@link Map} already contains an entry under a
* subchart's name, then its value must be {@code
* null}—indicating that it does not yet have an entry for
* this subchart—or a {@link Map} (or an {@link
* IllegalArgumentException} will be thrown).
*
* @param subcharts an {@link Iterable} of {@link ChartOrBuilder}
* instances, each element of which represents a subchart
* in a larger Helm chart; may be {@code null}
*
* @param returnValue a {@link Map} of values that will be treated
* as primary, or overriding; may be {@code null} in which case a
* new {@link Map} will be used instead
*
* @return {@code returnValue}, containing whatever it contained
* before together with the flattened default values from the
* supplied subcharts; never {@code null}
*
* @see Values#coalesceGlobals(Map, Map)
*
* @see #toValuesMap(ChartOuterClass.ChartOrBuilder, Map)
*/
private static final Map coalesceDependencies(final Iterable extends ChartOrBuilder> subcharts, Map returnValue) {
if (returnValue == null) {
returnValue = new HashMap<>();
}
if (subcharts != null) {
for (final ChartOrBuilder subchart : subcharts) {
if (subchart != null) {
final Metadata subchartMetadata = subchart.getMetadata();
if (subchartMetadata != null) {
final String subchartName = subchartMetadata.getName();
if (subchartName != null) {
final Map subchartValuesMap;
// See if the user-supplied values have a key under
// which values destined for a given subchart live.
// E.g. you might see redis.frob = "boo"; in that case
// we are hoping that the value indexed under "redis" is
// a Map, one of whose keys would be "frob" (whose value
// would be "boo").
final Object subchartValuesObject = returnValue.get(subchartName);
if (subchartValuesObject == null) {
// We didn't find anything under "redis". So go ahead
// and put in an empty mutable Map under that key to
// indicate that there are no dependent values for it
// yet but there might be (this method ends up
// indirectly calling itself recursively)
subchartValuesMap = new HashMap<>();
returnValue.put(subchartName, subchartValuesMap);
} else if (subchartValuesObject instanceof Map) {
@SuppressWarnings("unchecked")
final Map temp = (Map)subchartValuesObject;
subchartValuesMap = temp;
} else {
throw new IllegalArgumentException("returnValue.get(" + subchartName + "): not a map: " + subchartValuesObject);
}
// Now that we've found, e.g., a Map indexed under
// "redis", make sure our "flattened" map "receiver" has
// access to global values...
Values.coalesceGlobals(returnValue, subchartValuesMap);
// ...then call toValuesMap() on it (which calls
// this very method recursively, but doesn't overwrite
// anything in subchartValuesMap. So this whole thing
// flattens all the subchart default values and their
// globals into one map.
final Map temp = toValuesMap(subchart, subchartValuesMap);
assert temp == subchartValuesMap;
returnValue.put(subchartName, temp);
}
}
}
}
}
return returnValue;
}
}