Skip to content

Commit

Permalink
feat: helm Parameters/Variables/Values.yaml can be configured via XML…
Browse files Browse the repository at this point in the history
…/DSL

Signed-off-by: Marc Nuri <marc@marcnuri.com>
  • Loading branch information
manusa committed Jan 31, 2022
1 parent 165cff7 commit 09f42ab
Show file tree
Hide file tree
Showing 20 changed files with 253 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Usage:
* Fix #1142: Log informative message whenever a goal/task is skipped
* Fix #1197: Kubernetes Gradle Build tasks don't account for image model configuration skip field
* Fix #1245: Deprecate `maven.jkube.io` annotation prefix used in enrichers in favor of `jkube.eclipse.org`
* Fix #1244: Helm Parameters/Variables/Values.yaml can be configured via XML/DSL

### 1.5.1 (2021-10-28)
* Fix #1084: Gradle dependencies should be test or provided scope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Defaults to project description.
| The Chart list of URLs to source code for this project.
|

| *maintainers*
| *<<helm-maintainers, maintainers>>*
| The Chart list of maintainers (name+email).
|

Expand Down Expand Up @@ -115,12 +115,75 @@ Defaults to project description.
| The Helm chart file extension (`tgz`, `tar.bz`, `tar.bzip2`, `tar.bz2`), default value is `tar.gz` if not provided.
| `jkube.helm.chartExtension`

| *dependencies*
| The list of dependencies for this chart
| *<<helm-dependencies, dependencies>>*
| The list of dependencies for this chart.
|

| *<<helm-parameters, parameters>>*
| The list of parameters to interpolate the Chart templates from the provided Fragments.

These parameters can represent variables, in this case the values are used to generate
the `values.yaml` file. The fragment placeholders will be replaced with a `.Values` variable.

The parameters can also represent a Golang expression
|

|===

[[helm-maintainers]]
.Helm Configuration - Maintainer
[cols="1,5"]
|===
| Element | Description

| *name*
| The maintainer user name or organization.

| *email*
| The maintainer's contact email address.

| *url*
| The maintainer URL address.

|===

[[helm-dependencies]]
.Helm Configuration - Dependency
[cols="1,5"]
|===
| Element | Description

| *name*
| The name of the chart dependency.

| *version*
| Semantic version or version range for the dependency.

| *repository*
| URL pointing to a chart repository.

|===

[[helm-parameters]]
.Helm Configuration - Parameters
[cols="1,5"]
|===
| Element | Description

| *name*
| The name of the interpolatable parameter. Will be used to replace placeholders
(`$\{name}`) in the provided YAML fragments. And to generate the `values.yaml` file.

| *required*
| Set to true if this is a required value (when used to generate values).

| *value*
| In case we are generating a `.Values` variable, the default value.

In case the placeholder has to be replaced by an expression, the Golang expression
e.g. `{{ .Chart.Name | upper }}`.

|===

In a next step you can install this via the https://github.com/helm/helm/releases[helm command line tool] as follows:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;

/*
* https://github.com/helm/helm/blob/v3.7.2/pkg/chart/metadata.go#L26
*/
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
Expand All @@ -39,10 +42,16 @@ public class Maintainer implements Serializable {

private static final long serialVersionUID = -968020668786188166L;

public Maintainer(String name, String email) {
this(name, email, null);
}

@JsonProperty
private String name;

@JsonProperty
private String email;

@JsonProperty
private String url;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
package org.eclipse.jkube.kit.resource.helm;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.fabric8.openshift.api.model.Parameter;
import io.fabric8.openshift.api.model.Template;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Singular;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jkube.kit.common.Maintainer;

Expand Down Expand Up @@ -55,7 +57,12 @@ public class HelmConfig {
private List<String> keywords;
private String engine;
private List<File> additionalFiles;
private List<Template> templates;
/**
* OpenShift Template YAML files containing the parameters to interpolate in the Chart templates, and generate
* the values.yaml file.
*/
private List<Template> parameterTemplates;
private List<Parameter> parameters;

private List<HelmType> types;
private String sourceDir;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -26,8 +27,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jkube.kit.common.JKubeConfiguration;
import org.eclipse.jkube.kit.common.KitLogger;
import org.eclipse.jkube.kit.common.RegistryConfig;
Expand All @@ -40,14 +39,18 @@
import org.eclipse.jkube.kit.common.util.ResourceUtil;
import org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil;

import com.google.common.collect.Streams;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.openshift.api.model.Parameter;
import io.fabric8.openshift.api.model.Template;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import static org.eclipse.jkube.kit.common.util.TemplateUtil.escapeYamlTemplate;
import static org.eclipse.jkube.kit.resource.helm.HelmServiceUtil.selectHelmRepository;
import static org.eclipse.jkube.kit.resource.helm.HelmServiceUtil.isRepositoryValid;
import static org.eclipse.jkube.kit.resource.helm.HelmServiceUtil.selectHelmRepository;
import static org.eclipse.jkube.kit.resource.helm.HelmServiceUtil.setAuthentication;

public class HelmService {
Expand Down Expand Up @@ -91,8 +94,12 @@ public void generateHelmCharts(HelmConfig helmConfig) throws IOException {
createChartYaml(helmConfig, outputDir);
logger.debug("Copying additional files");
copyAdditionalFiles(helmConfig, outputDir);
logger.debug("Processing YAML templates");
createTemplateParameters(helmConfig, outputDir, templatesDir);
logger.debug("Gathering parameters for placeholders");
final List<HelmParameter> parameters = collectParameters(helmConfig);
logger.debug("Generating values.yaml");
createValuesYaml(parameters, outputDir);
logger.debug("Interpolating YAML Chart templates");
interpolateChartTemplates(parameters, templatesDir);
final File tarballFile = new File(tarballOutputDir, String.format("%s-%s-%s.%s",
helmConfig.getChart(), helmConfig.getVersion(), helmType.getClassifier(), helmConfig.getChartExtension()));
logger.debug("Creating Helm configuration Tarball: '%s'", tarballFile.getAbsolutePath());
Expand Down Expand Up @@ -284,21 +291,35 @@ private static void interpolateTemplateParameterExpressionsWithHelmExpressions(F
}
}

private static void createTemplateParameters(HelmConfig helmConfig, File outputDir, File templatesDir) throws IOException {
final List<HelmParameter> helmParameters = Optional.ofNullable(helmConfig.getTemplates())
.orElse(Collections.emptyList()).stream()
.map(Template::getParameters).flatMap(List::stream)
.map(HelmParameter::new).collect(Collectors.toList());
private static void interpolateChartTemplates(List<HelmParameter> helmParameters, File templatesDir) throws IOException {
// now lets replace all the parameter expressions in each template
for (File file : listYamls(templatesDir)) {
interpolateTemplateParameterExpressionsWithHelmExpressions(file, helmParameters);
}
}

private static void createValuesYaml(List<HelmParameter> helmParameters, File outputDir) throws IOException {
final Map<String, String> values = helmParameters.stream()
.filter(hp -> hp.getParameter().getValue() != null)
// Placeholders replaced by Go expressions don't need to be persisted in the values.yaml file
.filter(hp -> !hp.getParameter().getValue().trim().matches(GOLANG_EXPRESSION_REGEX))
.collect(Collectors.toMap(HelmParameter::getHelmName, hp -> hp.getParameter().getValue()));

File outputChartFile = new File(outputDir, VALUES_FILENAME);
ResourceUtil.save(outputChartFile, values, ResourceFileType.yaml);
}

// now lets replace all the parameter expressions in each template
for (File file : listYamls(templatesDir)) {
interpolateTemplateParameterExpressionsWithHelmExpressions(file, helmParameters);
}
private static List<HelmParameter> collectParameters(HelmConfig helmConfig) {
final List<HelmParameter> parameters = new ArrayList<>();
final Stream<Parameter> fromYaml = Optional.ofNullable(helmConfig.getParameterTemplates())
.orElse(Collections.emptyList()).stream()
.map(Template::getParameters).flatMap(List::stream);
final Stream<Parameter> fromConfig = Optional.ofNullable(helmConfig.getParameters())
.orElse(Collections.emptyList()).stream();
Streams.concat(fromYaml, fromConfig).map(HelmParameter::new).forEach(parameters::add);
parameters.stream().filter(p -> p.getParameter().getName() == null).findAny().ifPresent(p -> {
throw new IllegalArgumentException("Helm parameters must be declared with a valid name: " + p.getParameter().toString());
});
return parameters;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public static HelmConfig.HelmConfigBuilder initHelmConfig(
}
original.setIcon(resolveFromPropertyOrDefault(PROPERTY_ICON, project, original::getIcon, findIconURL(manifest)));
original.setAdditionalFiles(getAdditionalFiles(original, project));
if (original.getTemplates() == null) {
original.setTemplates(findTemplates(template));
if (original.getParameterTemplates() == null) {
original.setParameterTemplates(findTemplates(template));
}
original.setTypes(resolveHelmTypes(defaultHelmType, project));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.List;

import io.fabric8.openshift.api.model.ParameterBuilder;
import org.eclipse.jkube.kit.common.Maintainer;
import org.eclipse.jkube.kit.resource.helm.HelmConfig.HelmType;

Expand Down Expand Up @@ -100,7 +101,8 @@ public void deserialize() throws Exception {
"\"snapshotRepository\":{}," +
"\"stableRepository\":{}," +
"\"tarballOutputDir\":\"./tar-output\"," +
"\"templates\":[{}]," +
"\"parameterTemplates\":[{}]," +
"\"parameters\": [{\"name\":\"key\"}]," +
"\"description\":\"The description\"," +
"\"home\":\"e.t.\"," +
"\"icon\":\"Warhol\"," +
Expand Down Expand Up @@ -128,7 +130,8 @@ public void deserialize() throws Exception {
.hasFieldOrPropertyWithValue("snapshotRepository", new HelmRepository())
.hasFieldOrPropertyWithValue("stableRepository", new HelmRepository())
.hasFieldOrPropertyWithValue("tarballOutputDir", "./tar-output")
.hasFieldOrPropertyWithValue("templates", Collections.singletonList(new Template()))
.hasFieldOrPropertyWithValue("parameterTemplates", Collections.singletonList(new Template()))
.hasFieldOrPropertyWithValue("parameters", Collections.singletonList(new ParameterBuilder().withName("key").build()))
.hasFieldOrPropertyWithValue("description", "The description")
.hasFieldOrPropertyWithValue("home", "e.t.")
.hasFieldOrPropertyWithValue("icon", "Warhol")
Expand Down Expand Up @@ -159,7 +162,7 @@ public void createHelmConfig() throws IOException {
helmConfig.setSnapshotRepository(new HelmRepository());
helmConfig.setStableRepository(new HelmRepository());
helmConfig.setTarballOutputDir("tarballOutputDir");
helmConfig.setTemplates(Collections.singletonList(new Template()));
helmConfig.setParameterTemplates(Collections.singletonList(new Template()));
helmConfig.setDescription("description");
helmConfig.setHome("home");
helmConfig.setIcon("icon");
Expand All @@ -182,7 +185,7 @@ public void createHelmConfig() throws IOException {
assertThat(helmConfig.getSnapshotRepository()).isNotNull();
assertThat(helmConfig.getStableRepository()).isNotNull();
assertThat(helmConfig.getTarballOutputDir()).isEqualTo("tarballOutputDir");
assertThat(helmConfig.getTemplates()).isNotEmpty();
assertThat(helmConfig.getParameterTemplates()).isNotEmpty();
assertThat(helmConfig.getDescription()).isEqualTo("description");
assertThat(helmConfig.getHome()).isEqualTo("home");
assertThat(helmConfig.getIcon()).isEqualTo("icon");
Expand All @@ -207,7 +210,7 @@ public void equals() {
.snapshotRepository(new HelmRepository())
.stableRepository(new HelmRepository())
.tarballOutputDir("tarballOutputDir")
.templates(Collections.singletonList(new Template()))
.parameterTemplates(Collections.singletonList(new Template()))
.description("description")
.home("e.t.")
.icon("Warhol")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.fabric8.openshift.api.model.ParameterBuilder;
import io.fabric8.openshift.api.model.Template;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
Expand All @@ -39,6 +40,7 @@
import org.junit.rules.TemporaryFolder;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;

public class HelmServiceIT {

Expand All @@ -47,32 +49,35 @@ public class HelmServiceIT {

private ObjectMapper mapper;
private HelmService helmService;
private HelmConfig helmConfig;
private File helmOutputDir;

@Before
public void setUp() throws Exception {
mapper = new ObjectMapper(new YAMLFactory());
helmService = new HelmService(new JKubeConfiguration(), new KitLogger.SilentLogger());
helmOutputDir = temporaryFolder.newFolder("helm-output");
helmConfig = new HelmConfig();
helmConfig.setSourceDir(new File(HelmServiceIT.class.getResource("/it/sources").toURI()).getAbsolutePath());
helmConfig.setOutputDir(helmOutputDir.getAbsolutePath());
helmConfig.setTarballOutputDir(helmOutputDir.getAbsolutePath());
helmConfig.setChartExtension("tar");
}

@Test
public void generateHelmChartsTest() throws Exception {
// Given
final HelmConfig helmConfig = new HelmConfig();
helmConfig.setChart("ITChart");
helmConfig.setVersion("1.33.7");
helmConfig.setTypes(Arrays.asList(HelmConfig.HelmType.OPENSHIFT, HelmConfig.HelmType.KUBERNETES));
helmConfig.setSourceDir(new File(HelmServiceIT.class.getResource("/it/sources").toURI()).getAbsolutePath());
helmConfig.setOutputDir(helmOutputDir.getAbsolutePath());
helmConfig.setTarballOutputDir(helmOutputDir.getAbsolutePath());
helmConfig.setChartExtension("tar");
helmConfig.setAdditionalFiles(Collections.singletonList(
new File(HelmServiceIT.class.getResource("/it/sources/additional-file.txt").toURI())
));
helmConfig.setTemplates(Collections.singletonList(
helmConfig.setParameterTemplates(Collections.singletonList(
ResourceUtil.load(new File(HelmServiceIT.class.getResource("/it/sources/global-template.yml").toURI()), Template.class)
));
helmConfig.setParameters(Collections.singletonList(new ParameterBuilder()
.withName("annotation_from_config").withValue("{{ .Chart.Name | upper }}").build()));
final AtomicInteger generatedChartCount = new AtomicInteger(0);
helmConfig.setGeneratedChartListeners(Collections.singletonList(
(helmConfig1, type, chartFile) -> generatedChartCount.incrementAndGet()));
Expand Down Expand Up @@ -107,6 +112,18 @@ public void generateHelmChartsTest() throws Exception {
assertThat(generatedChartCount).hasValue(2);
}

@Test
public void generateHelmChartsTest_withInvalidParameters_throwsException() {
// Given
helmConfig.setTypes(Collections.singletonList(HelmConfig.HelmType.KUBERNETES));
helmConfig.setParameters(Collections.singletonList(new ParameterBuilder()
.withValue("{{ .Chart.Name | upper }}").build()));
// When
final IllegalArgumentException result = assertThrows(IllegalArgumentException.class, () ->
helmService.generateHelmCharts(helmConfig));
// Then
assertThat(result).hasMessageStartingWith("Helm parameters must be declared with a valid name:");
}
private void assertYamls() throws Exception {
final Path expectations = new File(HelmServiceIT.class.getResource("/it/expected").toURI()).toPath();
final Path generatedYamls = helmOutputDir.toPath();
Expand Down
Loading

0 comments on commit 09f42ab

Please sign in to comment.