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

fix(helm): teach rosco to write helm overrides to a file #1135

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,17 @@ protected List<String> buildOverrideList(Map<String, Object> overrides) {
.collect(Collectors.toList());
}

/** Accessor for whether to expand artifact reference URIs */
protected boolean isExpandArtifactReferenceURIs() {
return (artifactStore != null && helmConfig.isExpandOverrides());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the use case for this a little bit? Im unsure why you want to not expand something in rosco? Or maybe Im misunderstanding how this works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this change, artifact references would sometimes remain in the values file, e.g.:

Created overrides file at /tmp/rosco-9390928878072864271/overrides_d07566cf-e592-4b43-ad94-d947782f4a88.yml with the following contents:
---
my-override: "ref://dbyrontest/7678d0be07b31c883c436a70522c58ec0f724f1bf8bbeb293165e330f495e321"

Copy link
Contributor Author

@dbyron-sf dbyron-sf Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The override in question was configured as:

"my-override": "${#stage(\"Bake (Manifest)\").outputs.resolvedExpectedArtifacts[0].boundArtifact.reference}"

as in, there's a bake stage with an override whose value is the result of a prior bake stage.

}

/**
* In the event that we encounter and ArtifactReferenceURI, we want to pull down that artifact
* instead of using the raw URI as a value for helm.
*/
private Object expandArtifactReferenceURIs(Object value) {
if (artifactStore == null || !(helmConfig.isExpandOverrides() && value instanceof String)) {
protected Object expandArtifactReferenceURIs(Object value) {
if (!isExpandArtifactReferenceURIs() || !(value instanceof String)) {
return value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,22 @@ private Path writeOverridesToFile(
BakeManifestEnvironment env, Map<String, Object> overrides, boolean rawOverrides) {
String fileName = OVERRIDES_FILE_PREFIX + UUID.randomUUID().toString() + YML_FILE_EXTENSION;
Path filePath = env.resolvePath(fileName);
if (!rawOverrides) {

// If rawOverrides is true and expansion of artifact store references is
// disabled, there's no need to traverse the overrides map. Use it as is.
//
// In other words, if we're meant to stringify overrides, or expand
// references, traverse the overrides map and do so.
if (!rawOverrides || isExpandArtifactReferenceURIs()) {
overrides =
overrides.entrySet().stream()
.collect(
Collectors.toMap(Map.Entry::getKey, entry -> String.valueOf(entry.getValue())));
Collectors.toMap(
Map.Entry::getKey,
entry -> {
Object newValue = expandArtifactReferenceURIs(entry.getValue());
return rawOverrides ? newValue : String.valueOf(newValue);
}));
}
try (Writer writer = Files.newBufferedWriter(filePath)) {
yamlObjectMapper.writeValue(writer, overrides);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.http.HttpStatus;

final class HelmTemplateUtilsTest {
Expand All @@ -84,15 +85,16 @@ final class HelmTemplateUtilsTest {
/** Configuration for the default values in the event artifact store had been turned off. */
private ArtifactStoreConfigurationProperties artifactStoreConfig;

private ArtifactStoreConfigurationProperties.HelmConfig helmConfig;

@BeforeEach
void init(TestInfo testInfo) {
System.out.println("--------------- Test " + testInfo.getDisplayName());

artifactDownloader = mock(ArtifactDownloader.class);
helmConfigurationProperties = new RoscoHelmConfigurationProperties();
artifactStoreConfig = new ArtifactStoreConfigurationProperties();
ArtifactStoreConfigurationProperties.HelmConfig helmConfig =
new ArtifactStoreConfigurationProperties.HelmConfig();
helmConfig = new ArtifactStoreConfigurationProperties.HelmConfig();
artifactStoreConfig.setHelm(helmConfig);
helmConfig.setExpandOverrides(false);
helmTemplateUtils =
Expand Down Expand Up @@ -451,15 +453,25 @@ public void buildBakeRecipeIncludingCRDsWithHelm3() throws IOException {
}
}

@ParameterizedTest
@ParameterizedTest(
name =
"ensureOverrides reference: {0} overrides: {1} expected: {2} artifactStoreNull: {3}, expandOverrides: {4}, overridesInValuesFile: {5}")
@MethodSource("ensureOverrides")
public void ensureOverridesGetEvaluated(
String reference,
Map<String, Object> overrides,
String expected,
Map<String, Object> expectedMap,
Boolean artifactStoreNull,
Boolean expandOverrides)
Boolean expandOverrides,
Boolean overridesInValuesFile)
throws IOException {

// Assume that expectedMap has one element, and turn it into an expected argument for
// --set/--set-string
assertThat(expectedMap).hasSize(1);
Map.Entry<String, Object> entry = expectedMap.entrySet().iterator().next();
String expected = entry.getKey() + "=" + entry.getValue();

ArtifactStore artifactStore = null;
if (!artifactStoreNull) {
artifactStore = mock(ArtifactStore.class);
Expand All @@ -468,6 +480,12 @@ public void ensureOverridesGetEvaluated(
RoscoHelmConfigurationProperties helmConfigurationProperties =
new RoscoHelmConfigurationProperties();

if (overridesInValuesFile) {
// Set the threshold so that rosco always uses a values file for
// overrides, no matter their size.
helmConfigurationProperties.setOverridesFileThreshold(1);
}

// do not use the artifactStoreConfig field as we are trying to test various
// values of expandOverrides
ArtifactStoreConfigurationProperties artifactStoreConfiguration =
Expand All @@ -490,15 +508,62 @@ public void ensureOverridesGetEvaluated(
try (BakeManifestEnvironment env = BakeManifestEnvironment.create()) {
BakeRecipe recipe =
helmTemplateUtils.buildCommand(request, List.of(), Path.of("template_path"), env);
assertThat(recipe.getCommand().contains(expected)).isTrue();
if (overridesInValuesFile) {
List<String> helmTemplateCommand = recipe.getCommand();
assertThat(Collections.frequency(helmTemplateCommand, "--values")).isEqualTo(1);
String lastValuesArgument =
helmTemplateCommand.get(helmTemplateCommand.lastIndexOf("--values") + 1);
assertThat(lastValuesArgument).matches(OVERRIDES_FILE_PATH_PATTERN);
List<String> overridesYamlContents = Files.readAllLines(Path.of(lastValuesArgument));
assertThat(helmTemplateCommand).doesNotContain("--set");
assertThat(helmTemplateCommand).doesNotContain("--set-string");
assertThat(helmTemplateCommand).contains("--values");
assertThat(
new ObjectMapper(new YAMLFactory())
.readValue(
String.join(System.lineSeparator(), overridesYamlContents),
new TypeReference<Map<String, Object>>() {}))
.isEqualTo(expectedMap);
} else {
assertThat(recipe.getCommand().contains(expected)).isTrue();
}
}
}

private static Stream<Arguments> ensureOverrides() {
return Stream.of(
Arguments.of("test", Map.of("foo", "ref://bar/baz"), "foo=test", false, true),
Arguments.of("test", Map.of("foo", "ref://bar/baz"), "foo=ref://bar/baz", false, false),
Arguments.of("test", Map.of("foo", "ref://bar/baz"), "foo=ref://bar/baz", true, false));
Arguments.of(
"test", Map.of("foo", "ref://bar/baz"), Map.of("foo", "test"), false, true, false),
Arguments.of(
"test", Map.of("foo", "ref://bar/baz"), Map.of("foo", "test"), false, true, true),
Arguments.of(
"test",
Map.of("foo", "ref://bar/baz"),
Map.of("foo", "ref://bar/baz"),
false,
false,
false),
Arguments.of(
"test",
Map.of("foo", "ref://bar/baz"),
Map.of("foo", "ref://bar/baz"),
false,
false,
true),
Arguments.of(
"test",
Map.of("foo", "ref://bar/baz"),
Map.of("foo", "ref://bar/baz"),
true,
false,
false),
Arguments.of(
"test",
Map.of("foo", "ref://bar/baz"),
Map.of("foo", "ref://bar/baz"),
true,
false,
true));
}

@ParameterizedTest
Expand Down Expand Up @@ -626,10 +691,27 @@ public void testOverrideThresholdExceedsLimitWithRawOverridesAsFalse() throws IO
}
}

@Test
public void testOverrideThresholdExceedsLimitWithRawOverridesAsTrue() throws IOException {
@ParameterizedTest(
name = "testOverrideThresholdExceedsLimitWithRawOverridesAsTrue: expandOverrides {0}")
@ValueSource(booleans = {true, false})
public void testOverrideThresholdExceedsLimitWithRawOverridesAsTrue(boolean expandOverrides)
throws IOException {
Artifact chartArtifact = Artifact.builder().name("test-artifact").build();
Artifact valuesArtifact = Artifact.builder().name("test-artifact_values").build();

ArtifactStore artifactStore = null;
if (expandOverrides) {
artifactStore = mock(ArtifactStore.class);
}
helmConfig.setExpandOverrides(expandOverrides);

helmTemplateUtils =
new HelmTemplateUtils(
artifactDownloader,
Optional.ofNullable(artifactStore),
artifactStoreConfig,
helmConfigurationProperties);

bakeManifestRequest = new HelmBakeManifestRequest();
bakeManifestRequest.setInputArtifacts(ImmutableList.of(chartArtifact, valuesArtifact));
bakeManifestRequest.setOverrides(
Expand Down