diff --git a/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmBakeManifestRequest.java b/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmBakeManifestRequest.java index 4c28bc039..0e874f901 100644 --- a/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmBakeManifestRequest.java +++ b/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmBakeManifestRequest.java @@ -3,12 +3,16 @@ import com.netflix.spinnaker.kork.artifacts.model.Artifact; import com.netflix.spinnaker.rosco.manifests.BakeManifestRequest; import java.util.List; +import javax.annotation.Nullable; import lombok.Data; import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) public class HelmBakeManifestRequest extends BakeManifestRequest { + @Nullable String apiVersions; + @Nullable String kubeVersion; + String namespace; /** @@ -19,6 +23,13 @@ public class HelmBakeManifestRequest extends BakeManifestRequest { boolean rawOverrides; + /** + * Helm v3 adds a new flag to include custom resource definition manifests in the templated + * output. In the previous versions crds were usually included as part of templates, so the `helm + * template` command always included them in the rendered output. + */ + boolean includeCRDs; + /** * When the helm chart is (in) a git/repo artifact, the path to the chart. * diff --git a/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtils.java b/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtils.java index 3608306df..439ad6686 100644 --- a/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtils.java +++ b/rosco-manifests/src/main/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtils.java @@ -20,6 +20,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; @Component @Slf4j @@ -108,6 +109,23 @@ public BakeRecipe buildBakeRecipe(BakeManifestEnvironment env, HelmBakeManifestR command.add(namespace); } + if (request.isIncludeCRDs() + && request.getTemplateRenderer() == BakeManifestRequest.TemplateRenderer.HELM3) { + command.add("--include-crds"); + } + + String apiVersions = request.getApiVersions(); + if (StringUtils.hasText(apiVersions)) { + command.add("--api-versions"); + command.add(apiVersions); + } + + String kubeVersion = request.getKubeVersion(); + if (StringUtils.hasText(kubeVersion)) { + command.add("--kube-version"); + command.add(kubeVersion); + } + Map overrides = request.getOverrides(); if (!overrides.isEmpty()) { List overrideList = new ArrayList<>(); diff --git a/rosco-manifests/src/test/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtilsTest.java b/rosco-manifests/src/test/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtilsTest.java index b1b7dc1e0..3b5224c61 100644 --- a/rosco-manifests/src/test/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtilsTest.java +++ b/rosco-manifests/src/test/java/com/netflix/spinnaker/rosco/manifests/helm/HelmTemplateUtilsTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doThrow; @@ -342,6 +343,111 @@ public void buildBakeRecipeWithGitRepoArtifactUsingHelmChartFilePath(@TempDir Pa } } + @Test + public void buildBakeRecipeIncludingHelmVersionsOptionsWithHelm3() throws IOException { + ArtifactDownloader artifactDownloader = mock(ArtifactDownloader.class); + RoscoHelmConfigurationProperties helmConfigurationProperties = + new RoscoHelmConfigurationProperties(); + HelmTemplateUtils helmTemplateUtils = + new HelmTemplateUtils(artifactDownloader, helmConfigurationProperties); + + HelmBakeManifestRequest request = new HelmBakeManifestRequest(); + Artifact artifact = Artifact.builder().build(); + request.setTemplateRenderer(BakeManifestRequest.TemplateRenderer.HELM3); + request.setApiVersions("customApiVersion"); + request.setKubeVersion("customKubernetesVersion"); + request.setInputArtifacts(Collections.singletonList(artifact)); + request.setOverrides(Collections.emptyMap()); + + try (BakeManifestEnvironment env = BakeManifestEnvironment.create()) { + BakeRecipe bakeRecipe = helmTemplateUtils.buildBakeRecipe(env, request); + assertTrue(bakeRecipe.getCommand().contains("--api-versions")); + assertTrue(bakeRecipe.getCommand().indexOf("--api-versions") > 3); + assertTrue(bakeRecipe.getCommand().contains("--kube-version")); + assertTrue(bakeRecipe.getCommand().indexOf("--kube-version") > 5); + } + } + + @Test + public void buildBakeRecipeIncludingHelmVersionsOptionsWithHelm2() throws IOException { + ArtifactDownloader artifactDownloader = mock(ArtifactDownloader.class); + RoscoHelmConfigurationProperties helmConfigurationProperties = + new RoscoHelmConfigurationProperties(); + HelmTemplateUtils helmTemplateUtils = + new HelmTemplateUtils(artifactDownloader, helmConfigurationProperties); + + HelmBakeManifestRequest request = new HelmBakeManifestRequest(); + Artifact artifact = Artifact.builder().build(); + request.setTemplateRenderer(BakeManifestRequest.TemplateRenderer.HELM2); + request.setApiVersions("customApiVersion"); + request.setKubeVersion("customKubernetesVersion"); + request.setInputArtifacts(Collections.singletonList(artifact)); + request.setOverrides(Collections.emptyMap()); + + try (BakeManifestEnvironment env = BakeManifestEnvironment.create()) { + BakeRecipe bakeRecipe = helmTemplateUtils.buildBakeRecipe(env, request); + assertTrue(bakeRecipe.getCommand().contains("--api-versions")); + assertTrue(bakeRecipe.getCommand().indexOf("--api-versions") > 3); + assertTrue(bakeRecipe.getCommand().contains("--kube-version")); + assertTrue(bakeRecipe.getCommand().indexOf("--kube-version") > 5); + } + } + + @Test + public void buildBakeRecipeIncludingCRDsWithHelm3() throws IOException { + ArtifactDownloader artifactDownloader = mock(ArtifactDownloader.class); + RoscoHelmConfigurationProperties helmConfigurationProperties = + new RoscoHelmConfigurationProperties(); + HelmTemplateUtils helmTemplateUtils = + new HelmTemplateUtils(artifactDownloader, helmConfigurationProperties); + + HelmBakeManifestRequest request = new HelmBakeManifestRequest(); + Artifact artifact = Artifact.builder().build(); + request.setTemplateRenderer(BakeManifestRequest.TemplateRenderer.HELM3); + request.setIncludeCRDs(true); + request.setInputArtifacts(Collections.singletonList(artifact)); + request.setOverrides(Collections.emptyMap()); + + try (BakeManifestEnvironment env = BakeManifestEnvironment.create()) { + BakeRecipe recipe = helmTemplateUtils.buildBakeRecipe(env, request); + assertTrue(recipe.getCommand().contains("--include-crds")); + // Assert that the flag position goes after 'helm template' subcommand + assertTrue(recipe.getCommand().indexOf("--include-crds") > 1); + } + } + + @ParameterizedTest + @MethodSource("helmRendererArgsCRDs") + public void buildBakeRecipeNotIncludingCRDs( + boolean includeCRDs, BakeManifestRequest.TemplateRenderer templateRenderer) + throws IOException { + ArtifactDownloader artifactDownloader = mock(ArtifactDownloader.class); + RoscoHelmConfigurationProperties helmConfigurationProperties = + new RoscoHelmConfigurationProperties(); + HelmTemplateUtils helmTemplateUtils = + new HelmTemplateUtils(artifactDownloader, helmConfigurationProperties); + + HelmBakeManifestRequest request = new HelmBakeManifestRequest(); + Artifact artifact = Artifact.builder().build(); + request.setInputArtifacts(Collections.singletonList(artifact)); + request.setOverrides(Collections.emptyMap()); + + request.setTemplateRenderer(templateRenderer); + request.setIncludeCRDs(includeCRDs); + + try (BakeManifestEnvironment env = BakeManifestEnvironment.create()) { + BakeRecipe recipe = helmTemplateUtils.buildBakeRecipe(env, request); + assertFalse(recipe.getCommand().contains("--include-crds")); + } + } + + private static Stream helmRendererArgsCRDs() { + return Stream.of( + Arguments.of(true, BakeManifestRequest.TemplateRenderer.HELM2), + Arguments.of(false, BakeManifestRequest.TemplateRenderer.HELM2), + Arguments.of(false, BakeManifestRequest.TemplateRenderer.HELM3)); + } + @Test public void httpExceptionDownloading() throws IOException { // When artifactDownloader throws a SpinnakerHttpException, make sure we