diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/JKubeException.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/JKubeException.java
new file mode 100644
index 0000000000..16748c6b38
--- /dev/null
+++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/JKubeException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2019 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at:
+ *
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.jkube.kit.common;
+
+public class JKubeException extends RuntimeException {
+
+ public JKubeException(String message) {
+ super(message);
+ }
+
+}
diff --git a/jkube-kit/helm/pom.xml b/jkube-kit/helm/pom.xml
index 72a9b264e9..38e267f188 100644
--- a/jkube-kit/helm/pom.xml
+++ b/jkube-kit/helm/pom.xml
@@ -42,6 +42,10 @@
commons-codec
commons-codec
+
+ com.marcnuri.helm-java
+ helm-java
+
org.eclipse.jkube
diff --git a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmConfig.java b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmConfig.java
index b6b35a1d25..a35a3ac841 100644
--- a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmConfig.java
+++ b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmConfig.java
@@ -71,6 +71,9 @@ public class HelmConfig {
private HelmRepository stableRepository;
private HelmRepository snapshotRepository;
private String security;
+ private boolean lintStrict;
+ private boolean lintQuiet;
+
@JsonProperty("dependencies")
private List dependencies;
diff --git a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java
index 7606de43c4..391787a32b 100644
--- a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java
+++ b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java
@@ -16,6 +16,8 @@
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -28,7 +30,11 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import com.marcnuri.helm.Helm;
+import com.marcnuri.helm.LintCommand;
+import com.marcnuri.helm.LintResult;
import org.eclipse.jkube.kit.common.JKubeConfiguration;
+import org.eclipse.jkube.kit.common.JKubeException;
import org.eclipse.jkube.kit.common.KitLogger;
import org.eclipse.jkube.kit.common.RegistryConfig;
import org.eclipse.jkube.kit.common.RegistryServerConfiguration;
@@ -155,6 +161,34 @@ public void uploadHelmChart(HelmConfig helm) throws BadUploadException, IOExcept
}
}
+ public void lint(HelmConfig helmConfig) {
+ for (HelmConfig.HelmType helmType : helmConfig.getTypes()) {
+ final Path helmPackage = resolveTarballFile(helmConfig, helmType);
+ logger.info("Linting %s %s", helmConfig.getChart(), helmConfig.getVersion());
+ logger.info("Using packaged file: %s", helmPackage.toFile().getAbsolutePath());
+ final LintCommand lintCommand = new Helm(helmPackage).lint();
+ if (helmConfig.isLintStrict()) {
+ lintCommand.strict();
+ }
+ if (helmConfig.isLintQuiet()) {
+ lintCommand.quiet();
+ }
+ final LintResult lintResult = lintCommand.call();
+ if (lintResult.isFailed()) {
+ for (String message : lintResult.getMessages()) {
+ // [[W]] see AnsiUtil.COLOR_MAP and computeEmphasisColor to understand the color guides
+ logger.error("[[W]]%s", message);
+ }
+ throw new JKubeException("Linting failed");
+ } else {
+ for (String message : lintResult.getMessages()) {
+ logger.info("[[W]]%s", message);
+ }
+ logger.info("Linting successful");
+ }
+ }
+ }
+
private void uploadHelmChart(HelmConfig helmConfig, HelmRepository helmRepository)
throws IOException, BadUploadException {
@@ -162,18 +196,17 @@ private void uploadHelmChart(HelmConfig helmConfig, HelmRepository helmRepositor
for (HelmConfig.HelmType helmType : helmConfig.getTypes()) {
logger.info("Uploading Helm Chart \"%s\" to %s", helmConfig.getChart(), helmRepository.getName());
logger.debug("OutputDir: %s", helmConfig.getOutputDir());
-
- final File tarballOutputDir =
- new File(Objects.requireNonNull(helmConfig.getTarballOutputDir(),
- "Tarball output directory is required"), helmType.getOutputDir());
- final File tarballFile = new File(tarballOutputDir, String.format("%s-%s%s.%s",
- helmConfig.getChart(), helmConfig.getVersion(), resolveHelmClassifier(helmConfig), helmConfig.getChartExtension()));
-
- helmUploaderManager.getHelmUploader(helmRepository.getType()).uploadSingle(tarballFile, helmRepository);
+ helmUploaderManager.getHelmUploader(helmRepository.getType())
+ .uploadSingle(resolveTarballFile(helmConfig, helmType).toFile(), helmRepository);
logger.info("Upload Successful");
}
}
+ private static Path resolveTarballFile(HelmConfig helmConfig, HelmConfig.HelmType helmType) {
+ return Paths.get(Objects.requireNonNull(helmConfig.getTarballOutputDir(), "Tarball output directory is required"))
+ .resolve(helmType.getOutputDir())
+ .resolve(String.format("%s-%s%s.%s", helmConfig.getChart(), helmConfig.getVersion(), resolveHelmClassifier(helmConfig), helmConfig.getChartExtension()));
+ }
static File prepareSourceDir(HelmConfig helmConfig, HelmConfig.HelmType type) throws IOException {
final File sourceDir = new File(helmConfig.getSourceDir(), type.getSourceDir());
diff --git a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtil.java b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtil.java
index dee59cf17a..d29d669616 100644
--- a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtil.java
+++ b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtil.java
@@ -37,6 +37,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -73,6 +74,9 @@ public class HelmServiceUtil {
protected static final String PROPERTY_SECURITY = "jkube.helm.security";
protected static final String DEFAULT_SECURITY = "~/.m2/settings-security.xml";
+ protected static final String PROPERTY_HELM_LINT_STRICT = "jkube.helm.lint.strict";
+ protected static final String PROPERTY_HELM_LINT_QUIET = "jkube.helm.lint.quiet";
+
private HelmServiceUtil() { }
public static HelmConfig.HelmConfigBuilder initHelmConfig(
@@ -110,6 +114,8 @@ public static HelmConfig.HelmConfigBuilder initHelmConfig(
helmConfig.setTarFileClassifier(resolveFromPropertyOrDefault(PROPERTY_TARBALL_CLASSIFIER, project, helmConfig::getTarFileClassifier, () -> EMPTY));
helmConfig.setTarballOutputDir(resolveFromPropertyOrDefault(PROPERTY_TARBALL_OUTPUT_DIR, project, helmConfig::getTarballOutputDir,
helmConfig::getOutputDir));
+ helmConfig.setLintStrict(resolveBooleanFromPropertyOrDefault(PROPERTY_HELM_LINT_STRICT, project, helmConfig::isLintStrict));
+ helmConfig.setLintQuiet(resolveBooleanFromPropertyOrDefault(PROPERTY_HELM_LINT_QUIET, project, helmConfig::isLintQuiet));
return helmConfig.toBuilder();
}
@@ -165,6 +171,13 @@ static String resolveFromPropertyOrDefault(String property, JavaProject project,
.orElseGet(defaultValue == null ? () -> null : defaultValue));
}
+ static boolean resolveBooleanFromPropertyOrDefault(String property, JavaProject project, BooleanSupplier getter) {
+ return Optional.ofNullable(getProperty(property, project))
+ .filter(StringUtils::isNotBlank)
+ .map(Boolean::parseBoolean)
+ .orElse(getter.getAsBoolean());
+ }
+
static List getAdditionalFiles(HelmConfig helm, JavaProject project) {
List additionalFiles = new ArrayList<>();
if (helm.getAdditionalFiles() != null) {
diff --git a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceLintIT.java b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceLintIT.java
new file mode 100644
index 0000000000..7f674304ec
--- /dev/null
+++ b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceLintIT.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2019 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at:
+ *
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.jkube.kit.resource.helm;
+
+import com.marcnuri.helm.Helm;
+import org.eclipse.jkube.kit.common.JKubeConfiguration;
+import org.eclipse.jkube.kit.common.JKubeException;
+import org.eclipse.jkube.kit.common.KitLogger;
+import org.eclipse.jkube.kit.config.resource.ResourceServiceConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.endsWith;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@DisplayName("HelmService.uploadHelmChart")
+class HelmServiceLintIT {
+
+ @TempDir
+ private Path tempDir;
+ private KitLogger kitLogger;
+ private Path outputDir;
+ private HelmConfig helmConfig;
+ private HelmService helmService;
+
+ @BeforeEach
+ void setUp() {
+ kitLogger = spy(new KitLogger.SilentLogger());
+ outputDir = tempDir.resolve("output");
+ helmConfig = HelmConfig.builder()
+ .chart("helm-test")
+ .version("0.1.0")
+ .chartExtension("tgz")
+ .types(Arrays.asList(HelmConfig.HelmType.KUBERNETES, HelmConfig.HelmType.OPENSHIFT))
+ .tarballOutputDir(outputDir.toFile().getAbsolutePath())
+ .build();
+ helmService = new HelmService(JKubeConfiguration.builder().build(), new ResourceServiceConfig(), kitLogger);
+ }
+
+ @Nested
+ class Valid {
+
+ @BeforeEach
+ void validChartPackage() throws IOException {
+ final Helm helm = Helm.create().withName("helm-test").withDir(tempDir).call();
+ // Create templates as file (instead of dir) to force a warning
+ Files.walk(tempDir.resolve("helm-test").resolve("templates"))
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ Files.createFile(tempDir.resolve("helm-test").resolve("templates"));
+ helm
+ .packageIt().withDestination(outputDir.resolve("kubernetes")).call()
+ .packageIt().withDestination(outputDir.resolve("openshift")).call();
+ }
+
+ @Test
+ void genericInfoMessage() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, atLeastOnce())
+ .info("Linting %s %s", "helm-test", "0.1.0");
+ }
+
+ @Test
+ void kubernetesPriorInfoMessage() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, times(1))
+ .info(eq("Using packaged file: %s"), endsWith("kubernetes" + File.separator + "helm-test-0.1.0.tgz"));
+ }
+
+ @Test
+ void openshiftPriorInfoMessage() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, times(1))
+ .info(eq("Using packaged file: %s"), endsWith("openshift" + File.separator + "helm-test-0.1.0.tgz"));
+ }
+
+ @Test
+ void lintInfoMessageInWhite() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, atLeastOnce())
+ .info("[[W]]%s", "[INFO] Chart.yaml: icon is recommended");
+ }
+
+ @Test
+ void successMessage() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, atLeastOnce()).info("Linting successful");
+ }
+
+ @Nested
+ class Strict {
+ @BeforeEach
+ void setUp() {
+ helmConfig = helmConfig.toBuilder().lintStrict(true).build();
+ helmService = new HelmService(JKubeConfiguration.builder().build(), new ResourceServiceConfig(), kitLogger);
+ }
+
+ @Test
+ void lintErrorMessageInWhite() {
+ assertThatExceptionOfType(JKubeException.class).isThrownBy(() -> helmService.lint(helmConfig));
+ verify(kitLogger, atLeastOnce())
+ .error("[[W]]%s", "[WARNING] templates/: not a directory");
+ }
+
+ @Test
+ void lintingException() {
+ assertThatExceptionOfType(JKubeException.class)
+ .isThrownBy(() -> helmService.lint(helmConfig))
+ .withMessage("Linting failed");
+ }
+ }
+
+ @Nested
+ class Quiet {
+ @BeforeEach
+ void setUp() {
+ helmConfig = helmConfig.toBuilder().lintQuiet(true).build();
+ helmService = new HelmService(JKubeConfiguration.builder().build(), new ResourceServiceConfig(), kitLogger);
+ }
+
+ @Test
+ void lintInfoMessageOmitted() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, never())
+ .info("[[W]]%s", "[INFO] Chart.yaml: icon is recommended");
+ }
+
+ @Test
+ void lintWarnMessage() {
+ helmService.lint(helmConfig);
+ verify(kitLogger, atLeastOnce())
+ .info("[[W]]%s", "[WARNING] templates/: not a directory");
+ }
+ }
+
+ }
+
+ @Nested
+ class Invalid {
+
+ @BeforeEach
+ void invalidChartPackage() throws IOException {
+ final Helm chart = Helm.create().withName("helm-test").withDir(tempDir).call();
+ Files.write(tempDir.resolve("helm-test").resolve("Chart.yaml"),
+ "\nicon: ://invalid-url".getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.APPEND
+ );
+ chart
+ .packageIt().withDestination(outputDir.resolve("kubernetes")).call()
+ .packageIt().withDestination(outputDir.resolve("openshift")).call();
+ }
+
+ @Test
+ void genericInfoMessage() {
+ assertThatExceptionOfType(JKubeException.class).isThrownBy(() -> helmService.lint(helmConfig));
+ verify(kitLogger, atLeastOnce())
+ .info("Linting %s %s", "helm-test", "0.1.0");
+ }
+
+ @Test
+ void kubernetesPriorInfoMessage() {
+ assertThatExceptionOfType(JKubeException.class).isThrownBy(() -> helmService.lint(helmConfig));
+ verify(kitLogger, times(1))
+ .info(eq("Using packaged file: %s"), endsWith("kubernetes" + File.separator + "helm-test-0.1.0.tgz"));
+ }
+
+ @Test
+ void openshiftPriorInfoMessageNotThrownDueToPriorExceptionHaltingProcessing() {
+ assertThatExceptionOfType(JKubeException.class).isThrownBy(() -> helmService.lint(helmConfig));
+ verify(kitLogger, never())
+ .info(eq("Using packaged file: %s"), endsWith("openshift" + File.separator + "helm-test-0.1.0.tgz"));
+ }
+
+ @Test
+ void lintErrorMessageInWhite() {
+ assertThatExceptionOfType(JKubeException.class).isThrownBy(() -> helmService.lint(helmConfig));
+ verify(kitLogger, atLeastOnce())
+ .error("[[W]]%s", "[ERROR] Chart.yaml: invalid icon URL '://invalid-url'");
+ }
+
+ @Test
+ void lintingException() {
+ assertThatExceptionOfType(JKubeException.class)
+ .isThrownBy(() -> helmService.lint(helmConfig))
+ .withMessage("Linting failed");
+ }
+ }
+}
diff --git a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtilTest.java b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtilTest.java
index ece97ce988..576454b688 100644
--- a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtilTest.java
+++ b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceUtilTest.java
@@ -86,7 +86,9 @@ void initHelmConfig_withNoConfig_shouldInitConfigWithDefaultValues() throws IOEx
.hasFieldOrPropertyWithValue("types", Collections.singletonList(HelmConfig.HelmType.KUBERNETES))
.hasFieldOrPropertyWithValue("additionalFiles", Collections.emptyList())
.hasFieldOrPropertyWithValue("parameterTemplates", Collections.emptyList())
- .hasFieldOrProperty("icon");
+ .hasFieldOrProperty("icon")
+ .hasFieldOrPropertyWithValue("lintStrict", false)
+ .hasFieldOrPropertyWithValue("lintQuiet", false);
assertThat(result.getSourceDir()).endsWith("target/classes/META-INF/jkube/");
assertThat(result.getOutputDir()).endsWith("target/jkube/helm/artifact-id");
assertThat(result.getTarballOutputDir()).endsWith("target/jkube/helm/artifact-id");
@@ -103,6 +105,8 @@ void initHelmConfig_withOriginalConfig_shouldInitConfigWithoutOverriding() throw
.maintainers(Collections.emptyList())
.sourceDir("sources")
.outputDir("output")
+ .lintStrict(true)
+ .lintQuiet(true)
.build();
// When
final HelmConfig result = HelmServiceUtil
@@ -120,7 +124,9 @@ void initHelmConfig_withOriginalConfig_shouldInitConfigWithoutOverriding() throw
.hasFieldOrPropertyWithValue("sources", Collections.emptyList())
.hasFieldOrPropertyWithValue("maintainers", Collections.emptyList())
.hasFieldOrPropertyWithValue("sourceDir", "sources")
- .hasFieldOrPropertyWithValue("outputDir", "output");
+ .hasFieldOrPropertyWithValue("outputDir", "output")
+ .hasFieldOrPropertyWithValue("lintStrict", true)
+ .hasFieldOrPropertyWithValue("lintQuiet", true);
}
@Test
@@ -142,6 +148,21 @@ void initHelmConfig_withTypeProperty_shouldInitConfigWithForSpecifiedTypes() thr
);
}
+ @Test
+ void initHelmConfig_withLintProperties_shouldInitConfigWithLintSettings() throws IOException {
+ // Given
+ javaProject.getProperties().put("jkube.helm.lint.strict", "True");
+ javaProject.getProperties().put("jkube.helm.lint.quiet", "trUe");
+ // When
+ final HelmConfig result = HelmServiceUtil
+ .initHelmConfig(HelmConfig.HelmType.KUBERNETES, javaProject, templateDir, null)
+ .build();
+ // Then
+ assertThat(result)
+ .hasFieldOrPropertyWithValue("lintStrict", true)
+ .hasFieldOrPropertyWithValue("lintQuiet", true);
+ }
+
@Test
void initHelmConfig_whenValuesSchemaJsonPresentInProjectBaseDir_thenAddToHelmConfig() throws IOException {
// Given