Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add helm dependency update subcommand #377

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand All @@ -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
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
Expand Down Expand Up @@ -172,7 +208,8 @@ private <T extends HelmRequest, R> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <address_and_port>} argument should not be specified.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -139,6 +150,6 @@ public HelmClient build() {
kubeTlsServerName,
kubeToken,
kubeConfig);
return new DefaultHelmClient(helmExecutable, kubeAuthentication, defaultNamespace);
return new DefaultHelmClient(helmExecutable, kubeAuthentication, defaultNamespace, workingDirectory);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.*;
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}