diff --git a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClient.java b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClient.java index 8c38a2574..c04943227 100644 --- a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClient.java +++ b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClient.java @@ -101,6 +101,14 @@ default Release installChart(String releaseName, Chart chart) { */ void testChart(String releaseName, TestChartOptions options); + /** + * Executes the Helm CLI {@code dependency update} sub-command and updates the dependencies of the specified Helm + * chart. + * + * @param chartName the name of the chart to update. + */ + void dependencyUpdate(String chartName); + /** * Creates a new {@link HelmClientBuilder} instance with the default configuration. * diff --git a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClientBuilder.java b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClientBuilder.java index 17824ffb3..76a118908 100644 --- a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClientBuilder.java +++ b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/HelmClientBuilder.java @@ -40,6 +40,15 @@ public interface HelmClientBuilder { */ HelmClientBuilder defaultNamespace(String namespace); + /** + * Sets the working directory for the {@link HelmClient} instance. + * @param workingDirectory the working directory. + * @return the {@link HelmClientBuilder} instance. + * @implNote The working directory is set to the pwd if not explicitly provided, if that fails it will use the + * parent folder of the helm executable. + */ + HelmClientBuilder workingDirectory(Path workingDirectory); + /** * Sets the Kubernetes API server address and port number for the {@link HelmClient} instance. * diff --git a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClient.java b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClient.java index bad753a0c..d8922c042 100644 --- a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClient.java +++ b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClient.java @@ -28,6 +28,7 @@ import com.hedera.fullstack.helm.client.model.test.TestChartOptions; import com.hedera.fullstack.helm.client.proxy.request.HelmRequest; import com.hedera.fullstack.helm.client.proxy.request.authentication.KubeAuthentication; +import com.hedera.fullstack.helm.client.proxy.request.chart.ChartDependencyUpdateRequest; import com.hedera.fullstack.helm.client.proxy.request.chart.ChartInstallRequest; import com.hedera.fullstack.helm.client.proxy.request.chart.ChartTestRequest; import com.hedera.fullstack.helm.client.proxy.request.chart.ChartUninstallRequest; @@ -69,6 +70,11 @@ public final class DefaultHelmClient implements HelmClient { */ private final String defaultNamespace; + /** + * The working directory to use when executing Helm commands. + */ + private final Path workingDirectory; + /** * Creates a new instance of the {@link DefaultHelmClient} class. * @@ -78,9 +84,26 @@ public final class DefaultHelmClient implements HelmClient { */ public DefaultHelmClient( final Path helmExecutable, final KubeAuthentication authentication, final String defaultNamespace) { + this(helmExecutable, authentication, defaultNamespace, null); + } + + /** + * Creates a new instance of the {@link DefaultHelmClient} class. + * + * @param helmExecutable the path to the Helm executable. + * @param authentication the authentication configuration to use when executing Helm commands. + * @param defaultNamespace the default namespace to use when executing Helm commands. + * @param workingDirectory the working directory to use when executing Helm commands. + */ + public DefaultHelmClient( + final Path helmExecutable, + final KubeAuthentication authentication, + final String defaultNamespace, + final Path workingDirectory) { this.helmExecutable = Objects.requireNonNull(helmExecutable, "helmExecutable must not be null"); this.authentication = Objects.requireNonNull(authentication, "authentication must not be null"); this.defaultNamespace = defaultNamespace; + this.workingDirectory = workingDirectory; } @Override @@ -130,6 +153,14 @@ public void testChart(final String releaseName, final TestChartOptions options) }); } + @Override + public void dependencyUpdate(final String chartName) { + executeInternal(new ChartDependencyUpdateRequest(chartName), Void.class, (b, c) -> { + b.call(); + return null; + }); + } + /** * Applies the default namespace and authentication configuration to the given builder. * @@ -140,11 +171,16 @@ private void applyBuilderDefaults(final HelmExecutionBuilder builder) { builder.argument(NAMESPACE_ARG_NAME, defaultNamespace); } + if (workingDirectory != null) { + builder.workingDirectory(workingDirectory); + } + authentication.apply(builder); } /** - * Executes the given request and returns the response as the given class. The request is executed using the default namespace. + * Executes the given request and returns the response as the given class. The request is executed using the default + * namespace. * * @param request the request to execute. * @param responseClass the class of the response. @@ -172,7 +208,8 @@ private R execute( } /** - * Executes the given request and returns the response as a list of the given class. The request is executed using the default namespace. + * Executes the given request and returns the response as a list of the given class. The request is executed using + * the default namespace. * * @param request the request to execute. * @param responseClass the class of the response. diff --git a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClientBuilder.java b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClientBuilder.java index fb13f852b..9695456bc 100644 --- a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClientBuilder.java +++ b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/impl/DefaultHelmClientBuilder.java @@ -33,6 +33,11 @@ public final class DefaultHelmClientBuilder implements HelmClientBuilder { */ private String defaultNamespace; + /** + * The working directory to be used by the {@link HelmClient}. + */ + private Path workingDirectory; + /** * The kubernetes API server address and port number to which the client should connect. Defaults to a {@code null} * value which indicates that the Helm {@code --kube-apiserver } argument should not be specified. @@ -86,6 +91,12 @@ public HelmClientBuilder defaultNamespace(final String namespace) { return this; } + @Override + public HelmClientBuilder workingDirectory(final Path workingDirectory) { + this.workingDirectory = workingDirectory; + return this; + } + @Override public HelmClientBuilder kubeApiServer(String kubeApiServer) { this.kubeApiServer = kubeApiServer; @@ -139,6 +150,6 @@ public HelmClient build() { kubeTlsServerName, kubeToken, kubeConfig); - return new DefaultHelmClient(helmExecutable, kubeAuthentication, defaultNamespace); + return new DefaultHelmClient(helmExecutable, kubeAuthentication, defaultNamespace, workingDirectory); } } diff --git a/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/proxy/request/chart/ChartDependencyUpdateRequest.java b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/proxy/request/chart/ChartDependencyUpdateRequest.java new file mode 100644 index 000000000..d40fa829e --- /dev/null +++ b/fullstack-helm-client/src/main/java/com/hedera/fullstack/helm/client/proxy/request/chart/ChartDependencyUpdateRequest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * 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 com.hedera.fullstack.helm.client.proxy.request.chart; + +import com.hedera.fullstack.helm.client.execution.HelmExecutionBuilder; +import com.hedera.fullstack.helm.client.proxy.request.HelmRequest; +import java.util.Objects; + +/** + * A request to do a dependency update on a chart. + * + * @param chartName the name of the chart to update. + */ +public record ChartDependencyUpdateRequest(String chartName) implements HelmRequest { + public ChartDependencyUpdateRequest { + Objects.requireNonNull(chartName, "chartName must not be null"); + if (chartName.isBlank()) { + throw new IllegalArgumentException("chartName must not be blank"); + } + } + + @Override + public void apply(HelmExecutionBuilder builder) { + builder.subcommands("dependency", "update"); + builder.positional(chartName); + } +} diff --git a/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/HelmClientTest.java b/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/HelmClientTest.java index b0023216e..d106b57bb 100644 --- a/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/HelmClientTest.java +++ b/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/HelmClientTest.java @@ -18,10 +18,12 @@ import static com.hedera.fullstack.base.api.util.ExceptionUtils.suppressExceptions; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Named.named; import com.hedera.fullstack.base.api.version.SemanticVersion; import com.hedera.fullstack.helm.client.HelmClient; +import com.hedera.fullstack.helm.client.HelmExecutionException; import com.hedera.fullstack.helm.client.model.Chart; import com.hedera.fullstack.helm.client.model.Repository; import com.hedera.fullstack.helm.client.model.chart.Release; @@ -32,6 +34,7 @@ import com.jcovalent.junit.logging.LogEntryBuilder; import com.jcovalent.junit.logging.LoggingOutput; import com.jcovalent.junit.logging.assertj.LoggingOutputAssert; +import java.io.File; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.*; @@ -88,8 +91,10 @@ private record ChartInstallOptionsTestParameters(InstallChartOptions options, Li @BeforeAll static void beforeAll() { - helmClient = - HelmClient.builder().defaultNamespace("helm-client-test-ns").build(); + helmClient = HelmClient.builder() + .defaultNamespace("helm-client-test-ns") + .workingDirectory(new File(".").toPath()) + .build(); assertThat(helmClient).isNotNull(); } @@ -359,4 +364,21 @@ void testTestChartWithOptions() { suppressExceptions(() -> helmClient.uninstallChart(HAPROXY_RELEASE_NAME)); } } + + @Test + @DisplayName("Test Helm dependency update subcommand") + void testHelmDependencyUpdate() { + helmClient.dependencyUpdate("../charts/hedera-network"); + } + + @Test + @DisplayName("Test Helm dependency build subcommand failure") + void testHelmDependencyBuildFailure() { + HelmExecutionException exception = + assertThrows(HelmExecutionException.class, () -> helmClient.dependencyUpdate("../charts/not-a-chart")); + assertThat(exception.getMessage()).contains("Execution of the Helm command failed with exit code: 1"); + assertThat(exception.getStdOut()) + .contains( + "Error: could not find ../charts/not-a-chart: stat ../charts/not-a-chart: no such file or directory"); + } } diff --git a/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/proxy/request/chart/ChartDependencyUpdateRequestTest.java b/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/proxy/request/chart/ChartDependencyUpdateRequestTest.java new file mode 100644 index 000000000..92dd2818f --- /dev/null +++ b/fullstack-helm-client/src/test/java/com/hedera/fullstack/helm/client/test/proxy/request/chart/ChartDependencyUpdateRequestTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * 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 com.hedera.fullstack.helm.client.test.proxy.request.chart; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.hedera.fullstack.helm.client.execution.HelmExecutionBuilder; +import com.hedera.fullstack.helm.client.proxy.request.chart.ChartDependencyUpdateRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChartDependencyUpdateRequestTest { + @Mock + HelmExecutionBuilder helmExecutionBuilderMock; + + @Test + @DisplayName("Verify ChartDependencyUpdateRequest apply") + void testChartDependencyUpdateRequestApply() { + final ChartDependencyUpdateRequest request = new ChartDependencyUpdateRequest("mocked"); + assertThat(request).isNotNull(); + assertThat(request.chartName()).isEqualTo("mocked"); + + when(helmExecutionBuilderMock.subcommands("dependency", "update")).thenReturn(helmExecutionBuilderMock); + when(helmExecutionBuilderMock.positional("mocked")).thenReturn(helmExecutionBuilderMock); + request.apply(helmExecutionBuilderMock); + verify(helmExecutionBuilderMock, times(1)).subcommands("dependency", "update"); + verify(helmExecutionBuilderMock, times(1)).positional("mocked"); + } +}