Skip to content

Commit

Permalink
feat(helm): HelmService exposes Helm test functionality (3399)
Browse files Browse the repository at this point in the history
feat (jkube-kit/helm) : HelmService exposes Helm test functionality

Add `test` method in HelmService

Signed-off-by: Rohan Kumar <[email protected]>
---
review: helm test service

Signed-off-by: Marc Nuri <[email protected]>

Co-authored-by: Marc Nuri <[email protected]>
  • Loading branch information
rohanKanojia and manusa authored Sep 27, 2024
1 parent d02162a commit 47f9136
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ void runTask_withHelmDependencyPresent_shouldSucceed() {
// When
kubernetesHelmInstallTask.runTask();
// Then
verify(taskEnvironment.logger).lifecycle("k8s: NAME : empty-project");
verify(taskEnvironment.logger).lifecycle("k8s: NAMESPACE : ");
verify(taskEnvironment.logger).lifecycle("k8s: STATUS : deployed");
verify(taskEnvironment.logger).lifecycle("k8s: REVISION : 1");
verify(taskEnvironment.logger).lifecycle("k8s: NAME: empty-project");
verify(taskEnvironment.logger).lifecycle("k8s: NAMESPACE: ");
verify(taskEnvironment.logger).lifecycle("k8s: STATUS: deployed");
verify(taskEnvironment.logger).lifecycle("k8s: REVISION: 1");
verify(taskEnvironment.logger).lifecycle("k8s: Saving 1 charts");
verify(taskEnvironment.logger).lifecycle("k8s: Deleting outdated charts");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ void runTask_withHelmDependencyPresent_shouldSucceed() {
// When
openShiftHelmInstallTask.runTask();
// Then
verify(taskEnvironment.logger).lifecycle("oc: NAME : empty-project");
verify(taskEnvironment.logger).lifecycle("oc: NAMESPACE : ");
verify(taskEnvironment.logger).lifecycle("oc: STATUS : deployed");
verify(taskEnvironment.logger).lifecycle("oc: REVISION : 1");
verify(taskEnvironment.logger).lifecycle("oc: NAME: empty-project");
verify(taskEnvironment.logger).lifecycle("oc: NAMESPACE: ");
verify(taskEnvironment.logger).lifecycle("oc: STATUS: deployed");
verify(taskEnvironment.logger).lifecycle("oc: REVISION: 1");
verify(taskEnvironment.logger).lifecycle("oc: Saving 1 charts");
verify(taskEnvironment.logger).lifecycle("oc: Deleting outdated charts");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,22 @@ public PrintStreamLogger(PrintStream out) {

@Override
public void debug(String format, Object... params) {
out.println(String.format(format, params));
out.printf("[DEBUG] " + format + "%n", params);
}

@Override
public void info(String format, Object... params) {
out.println(String.format(format, params));
out.printf("[INFO] " + format + "%n", params);
}

@Override
public void warn(String format, Object... params) {
out.println(String.format(format, params));
out.printf("[WARN] " + format + "%n", params);
}

@Override
public void error(String format, Object... params) {
out.println(String.format(format, params));
out.printf("[ERROR] " + format + "%n", params);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ private static APIResourceList createNewAPIResourceList() {
.withName("services")
.withSingularName("service")
.build());
apiResourceListBuilder.addToResources(new APIResourceBuilder()
.withNamespaced()
.withKind("Pod")
.withName("pods")
.withSingularName("pod")
.build());
apiResourceListBuilder.addToResources(new APIResourceBuilder()
.withName("deployments")
.withKind("Deployment")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@

/**
* Configuration for a helm chart
* @author roland
* @since 11/08/16
*/
@Builder(toBuilder = true)
@AllArgsConstructor
Expand Down Expand Up @@ -82,6 +80,10 @@ public class HelmConfig {
private boolean installDependencyUpdate;
private boolean installWaitReady;
private boolean disableOpenAPIValidation;
/**
* Timeout in seconds
*/
private int timeout;


@JsonProperty("dependencies")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.marcnuri.helm.LintCommand;
import com.marcnuri.helm.LintResult;
import com.marcnuri.helm.Release;
import com.marcnuri.helm.TestCommand;
import com.marcnuri.helm.UninstallCommand;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import org.eclipse.jkube.kit.common.JKubeConfiguration;
Expand Down Expand Up @@ -222,18 +223,7 @@ public void install(HelmConfig helmConfig) {
installCommand.disableOpenApiValidation();
}
installCommand.withKubeConfig(createTemporaryKubeConfigForInstall());
Release release = installCommand.call();
logger.info("[[W]]NAME : %s", release.getName());
logger.info("[[W]]NAMESPACE : %s", release.getNamespace());
logger.info("[[W]]STATUS : %s", release.getStatus());
logger.info("[[W]]REVISION : %s", release.getRevision());
logger.info("[[W]]LAST DEPLOYED : %s", release.getLastDeployed().format(DateTimeFormatter.ofPattern("E MMM dd HH:mm:ss yyyy")));
Arrays.stream(release.getOutput().split("---"))
.filter(o -> o.contains("Deleting outdated charts"))
.findFirst()
.ifPresent(s -> Arrays.stream(s.split(SYSTEM_LINE_SEPARATOR_REGEX))
.filter(StringUtils::isNotBlank)
.forEach(l -> logger.info("[[W]]%s", l)));
logRelease(installCommand.call());
}
}

Expand All @@ -247,6 +237,31 @@ public void uninstall(HelmConfig helmConfig) {
.forEach(l -> logger.info("[[W]]%s", l));
}

public void test(HelmConfig helmConfig) {
logger.info("Testing Helm Chart %s %s", helmConfig.getChart(), helmConfig.getVersion());
TestCommand testCommand = Helm.test(helmConfig.getReleaseName());
if (helmConfig.getTimeout() > 0) {
testCommand.withTimeout(helmConfig.getTimeout());
}
testCommand.withKubeConfig(createTemporaryKubeConfigForInstall());
logRelease(testCommand.call());
}

private void logRelease(Release release) {
logger.info("[[W]]NAME: %s", release.getName());
logger.info("[[W]]NAMESPACE: %s", release.getNamespace());
logger.info("[[W]]STATUS: %s", release.getStatus());
logger.info("[[W]]REVISION: %s", release.getRevision());
logger.info("[[W]]LAST DEPLOYED: %s", release.getLastDeployed().format(DateTimeFormatter.ofPattern("E MMM dd HH:mm:ss yyyy")));
logger.info("[[W]]Phase: Succeeded");
Arrays.stream(release.getOutput().split("---"))
.filter(o -> o.contains("Deleting outdated charts"))
.findFirst()
.ifPresent(s -> Arrays.stream(s.split(SYSTEM_LINE_SEPARATOR_REGEX))
.filter(StringUtils::isNotBlank)
.forEach(l -> logger.info("[[W]]%s", l)));
}

private Path createTemporaryKubeConfigForInstall() {
try {
File kubeConfigParentDir = new File(jKubeConfiguration.getProject().getBuildDirectory(), "jkube-temp");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public class HelmServiceUtil {
protected static final String PROPERTY_HELM_INSTALL_DEPENDENCY_UPDATE = "jkube.helm.install.dependencyUpdate";
protected static final String PROPERTY_HELM_INSTALL_WAIT_READY = "jkube.helm.install.waitReady";
protected static final String PROPERTY_HELM_DISABLE_OPENAPI_VALIDATION = "jkube.helm.disableOpenAPIValidation";
protected static final String PROPERTY_HELM_TEST_TIMEOUT = "jkube.helm.test.timeout";
protected static final String HELM_DEFAULT_TIMEOUT = "300";

private HelmServiceUtil() { }

Expand Down Expand Up @@ -134,6 +136,7 @@ public static HelmConfig.HelmConfigBuilder initHelmConfig(
helmConfig.setInstallDependencyUpdate(resolveBooleanFromPropertyOrDefault(PROPERTY_HELM_INSTALL_DEPENDENCY_UPDATE, project, helmConfig::isInstallDependencyUpdate));
helmConfig.setInstallWaitReady(resolveBooleanFromPropertyOrDefault(PROPERTY_HELM_INSTALL_WAIT_READY, project, helmConfig::isInstallWaitReady));
helmConfig.setDisableOpenAPIValidation(resolveBooleanFromPropertyOrDefault(PROPERTY_HELM_DISABLE_OPENAPI_VALIDATION, project, helmConfig::isDisableOpenAPIValidation));
helmConfig.setTimeout(Integer.parseInt(resolveFromPropertyOrDefault(PROPERTY_HELM_TEST_TIMEOUT, project, () -> helmConfig.getTimeout() > 0 ? Integer.toString(helmConfig.getTimeout()) : null, () -> HELM_DEFAULT_TIMEOUT)));
return helmConfig.toBuilder();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ void validChart_thenLogInstalledChartDetails() throws IOException {
// When
helmService.install(helmConfig);
// Then
verify(kitLogger, times(1)).info("[[W]]NAME : %s", "test-project");
verify(kitLogger, times(1)).info("[[W]]NAMESPACE : %s", "");
verify(kitLogger, times(1)).info("[[W]]STATUS : %s", "deployed");
verify(kitLogger, times(1)).info("[[W]]REVISION : %s", "1");
verify(kitLogger, times(1)).info("[[W]]NAME: %s", "test-project");
verify(kitLogger, times(1)).info("[[W]]NAMESPACE: %s", "");
verify(kitLogger, times(1)).info("[[W]]STATUS: %s", "deployed");
verify(kitLogger, times(1)).info("[[W]]REVISION: %s", "1");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* 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 io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretListBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesMixedDispatcher;
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
import io.fabric8.mockwebserver.Context;
import io.fabric8.mockwebserver.ServerRequest;
import io.fabric8.mockwebserver.ServerResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.apache.commons.io.IOUtils;
import org.eclipse.jkube.kit.common.JKubeConfiguration;
import org.eclipse.jkube.kit.common.JavaProject;
import org.eclipse.jkube.kit.common.KitLogger;
import org.eclipse.jkube.kit.common.access.ClusterConfiguration;
import org.eclipse.jkube.kit.common.util.AsyncUtil;
import org.eclipse.jkube.kit.config.resource.ResourceServiceConfig;
import org.junit.jupiter.api.AfterEach;
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.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.eclipse.jkube.kit.common.util.KubernetesMockServerUtil.prepareMockWebServerExpectationsForAggregatedDiscoveryEndpoints;

@DisplayName("HelmService.test")
class HelmServiceTestIT {
@TempDir
private Path tempDir;
private KubernetesMockServer server;
private KubernetesClient kubernetesClient;
private ByteArrayOutputStream logOutput;
private HelmService helmService;
private HelmConfig helmConfig;

@BeforeEach
void setUp() throws Exception {
final Map<ServerRequest, Queue<ServerResponse>> responses = new HashMap<>();
server = new KubernetesMockServer(new Context(), new MockWebServer(), responses, new KubernetesMixedDispatcher(responses), true);
server.init();
// Remove after https://github.com/fabric8io/kubernetes-client/issues/6062 is fixed
prepareMockWebServerExpectationsForAggregatedDiscoveryEndpoints(server);
kubernetesClient = server.createClient();
logOutput = new ByteArrayOutputStream();
final Path outputDir = tempDir.resolve("output");
helmService = new HelmService(JKubeConfiguration.builder()
.project(JavaProject.builder()
.buildDirectory(tempDir.resolve("target").toFile())
.build())
.clusterConfiguration(ClusterConfiguration.from(kubernetesClient.getConfiguration()).build())
.build(), ResourceServiceConfig.builder()
.resourceDirs(Collections.singletonList(
new File(Objects.requireNonNull(HelmServiceInstallIT.class.getResource("/it/helm-service-test/resources")).getFile())))
.build(), new KitLogger.PrintStreamLogger(new PrintStream(logOutput)));
helmConfig = HelmConfig.builder()
.chart("helm-test")
.version("0.1.0")
.chartExtension("tgz")
.types(Collections.singletonList(HelmConfig.HelmType.KUBERNETES))
.tarballOutputDir(outputDir.toFile().getAbsolutePath())
.outputDir(outputDir.toString())
.sourceDir(new File(Objects.requireNonNull(HelmServiceInstallIT.class.getResource("/it/sources")).toURI()).getAbsolutePath())
.releaseName("test-project")
.disableOpenAPIValidation(true)
.build();
}

@AfterEach
void stopKubernetesServer() {
kubernetesClient.close();
server.destroy();
}

@Nested
class WithChartInstalled {

@BeforeEach
void setUp() throws Exception {
// OpenAPI validation endpoints required by helm test
server.expect().get().withPath("/openapi/v3?timeout=32s")
.andReturn(200, IOUtils.toString(Objects.requireNonNull(HelmServiceTestIT.class.getResourceAsStream("/it/helm-service-test/kubernetes-openapi-v3-schema.json")), StandardCharsets.UTF_8))
.always();
server.expect().get().withPath("/openapi/v3/api/v1?timeout=32s")
.andReturn(200, IOUtils.toString(Objects.requireNonNull(HelmServiceTestIT.class.getResourceAsStream("/it/helm-service-test/kubernetes-openapi-v3-api-v1-schema-pod.json")), StandardCharsets.UTF_8))
.always();
// Chart is installed
helmService.generateHelmCharts(helmConfig);
helmService.install(helmConfig);
Secret secret = kubernetesClient.secrets().withName("sh.helm.release.v1.test-project.v1").get();
server.expect().get().withPath("/api/v1/namespaces/test/secrets?labelSelector=name%3Dtest-project%2Cowner%3Dhelm")
.andReturn(200, new SecretListBuilder()
.addToItems(secret)
.build())
.once();
}

@Test
@DisplayName("when valid chart but test timeout too low, then throw exception")
void tooLowTimeout_thenThrowException() {
// Given
helmConfig.setTimeout(1);
// Then
assertThatIllegalStateException()
.isThrownBy(() -> helmService.test(helmConfig))
.withMessageContaining("timed out waiting for the condition");
}

@Test
@DisplayName("when valid chart release provided, then log test details after test completion")
void validChartRelease_thenLogChartTestDetails() {
// Given
CompletableFuture<Boolean> helmTest = AsyncUtil.async(() -> {
helmService.test(helmConfig);
return true;
});
kubernetesClient.pods().withName("test-project-test-connection")
.waitUntilCondition(Objects::nonNull, 500, TimeUnit.MILLISECONDS);
// When
kubernetesClient.pods().withName("test-project-test-connection").editStatus(p -> new PodBuilder(p)
.editOrNewStatus()
.withPhase("Succeeded")
.endStatus()
.build());
// Then
assertThat(helmTest).succeedsWithin(5, TimeUnit.SECONDS);
assertThat(logOutput)
.asString()
.containsSubsequence(
"[INFO] Testing Helm Chart helm-test 0.1.0",
"[INFO] [[W]]NAME: test-project",
"[INFO] [[W]]NAMESPACE: ",
"[INFO] [[W]]STATUS: deployed",
"[INFO] [[W]]REVISION: 1",
"[INFO] [[W]]Phase: Succeeded");
}
}

@Test
@DisplayName("when unknown chart release provided, then throw exception")
void invalidChartRelease_thenLogChartTestDetails() {
// Given
helmConfig.setReleaseName("i-was-never-created");
// When
assertThatIllegalStateException()
.isThrownBy(() -> helmService.test(helmConfig))
.withMessageContaining("not found");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ void initHelmConfig_withNoConfig_shouldInitConfigWithDefaultValues() throws IOEx
.hasFieldOrPropertyWithValue("releaseName", "artifact-id")
.hasFieldOrPropertyWithValue("installDependencyUpdate", true)
.hasFieldOrPropertyWithValue("installWaitReady", false)
.hasFieldOrPropertyWithValue("disableOpenAPIValidation", false);
.hasFieldOrPropertyWithValue("disableOpenAPIValidation", false)
.hasFieldOrPropertyWithValue("timeout", 300);
assertThat(result.getSourceDir()).endsWith(separatorsToSystem("target/classes/META-INF/jkube/"));
assertThat(result.getOutputDir()).endsWith(separatorsToSystem("target/jkube/helm/artifact-id"));
assertThat(result.getTarballOutputDir()).endsWith(separatorsToSystem("target/jkube/helm/artifact-id"));
Expand All @@ -119,6 +120,7 @@ void initHelmConfig_withOriginalConfig_shouldInitConfigWithoutOverriding() throw
.installDependencyUpdate(false)
.installWaitReady(true)
.disableOpenAPIValidation(true)
.timeout(10)
.build();
// When
final HelmConfig result = HelmServiceUtil
Expand All @@ -145,7 +147,8 @@ void initHelmConfig_withOriginalConfig_shouldInitConfigWithoutOverriding() throw
.hasFieldOrPropertyWithValue("releaseName", "new-release-name")
.hasFieldOrPropertyWithValue("installDependencyUpdate", false)
.hasFieldOrPropertyWithValue("installWaitReady", true)
.hasFieldOrPropertyWithValue("disableOpenAPIValidation", true);
.hasFieldOrPropertyWithValue("disableOpenAPIValidation", true)
.hasFieldOrPropertyWithValue("timeout", 10);
}

@Test
Expand Down
Loading

0 comments on commit 47f9136

Please sign in to comment.