Skip to content

Commit 503735d

Browse files
csvirimetacosm
andauthored
feat: support for additional kubernetes resource in helm charts (#769)
* fix: add rest of resources to helm resources Signed-off-by: Attila Mészáros <[email protected]> * impl Signed-off-by: Attila Mészáros <[email protected]> * filtering fixes Signed-off-by: Attila Mészáros <[email protected]> * revert test changes Signed-off-by: Attila Mészáros <[email protected]> * refactor: make build step conditional Signed-off-by: Chris Laprun <[email protected]> * refactor: make deserialization of kube resources a separate step This allows for this to happen only once as needed and could be done in parallel to other build steps. Signed-off-by: Chris Laprun <[email protected]> --------- Signed-off-by: Attila Mészáros <[email protected]> Signed-off-by: Chris Laprun <[email protected]> Co-authored-by: Chris Laprun <[email protected]>
1 parent 44bf6aa commit 503735d

File tree

4 files changed

+186
-93
lines changed

4 files changed

+186
-93
lines changed

bundle-generator/deployment/src/main/java/io/quarkiverse/operatorsdk/bundle/deployment/BundleProcessor.java

Lines changed: 61 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
4545
import io.quarkus.kubernetes.deployment.KubernetesConfig;
4646
import io.quarkus.kubernetes.deployment.ResourceNameUtil;
47-
import io.quarkus.kubernetes.spi.GeneratedKubernetesResourceBuildItem;
4847

4948
public class BundleProcessor {
5049

@@ -183,77 +182,74 @@ void generateBundle(ApplicationInfoBuildItem configuration,
183182
VersionBuildItem versionBuildItem,
184183
BuildProducer<GeneratedBundleBuildItem> doneGeneratingCSV,
185184
GeneratedCRDInfoBuildItem generatedCustomResourcesDefinitions,
186-
List<GeneratedKubernetesResourceBuildItem> generatedKubernetesManifests,
185+
DeserializedKubernetesResourcesBuildItem generatedKubernetesResources,
187186
BuildProducer<GeneratedFileSystemResourceBuildItem> generatedCSVs) {
188-
if (bundleConfiguration.enabled) {
189-
final var crds = generatedCustomResourcesDefinitions.getCRDGenerationInfo().getCrds()
190-
.values().stream()
191-
.flatMap(entry -> entry.values().stream())
192-
.collect(Collectors.toMap(CRDInfo::getCrdName, Function.identity()));
193-
final var outputDir = outputTarget.getOutputDirectory().resolve(BUNDLE);
194-
final var serviceAccounts = new LinkedList<ServiceAccount>();
195-
final var clusterRoleBindings = new LinkedList<ClusterRoleBinding>();
196-
final var clusterRoles = new LinkedList<ClusterRole>();
197-
final var roleBindings = new LinkedList<RoleBinding>();
198-
final var roles = new LinkedList<Role>();
199-
final var deployments = new LinkedList<Deployment>();
200-
201-
final var resources = GeneratedResourcesUtils.loadFrom(generatedKubernetesManifests);
202-
resources.forEach(r -> {
203-
if (r instanceof ServiceAccount) {
204-
serviceAccounts.add((ServiceAccount) r);
205-
return;
206-
}
187+
final var crds = generatedCustomResourcesDefinitions.getCRDGenerationInfo().getCrds()
188+
.values().stream()
189+
.flatMap(entry -> entry.values().stream())
190+
.collect(Collectors.toMap(CRDInfo::getCrdName, Function.identity()));
191+
final var outputDir = outputTarget.getOutputDirectory().resolve(BUNDLE);
192+
final var serviceAccounts = new LinkedList<ServiceAccount>();
193+
final var clusterRoleBindings = new LinkedList<ClusterRoleBinding>();
194+
final var clusterRoles = new LinkedList<ClusterRole>();
195+
final var roleBindings = new LinkedList<RoleBinding>();
196+
final var roles = new LinkedList<Role>();
197+
final var deployments = new LinkedList<Deployment>();
198+
199+
final var resources = generatedKubernetesResources.getResources();
200+
resources.forEach(r -> {
201+
if (r instanceof ServiceAccount) {
202+
serviceAccounts.add((ServiceAccount) r);
203+
return;
204+
}
207205

208-
if (r instanceof ClusterRoleBinding) {
209-
clusterRoleBindings.add((ClusterRoleBinding) r);
210-
return;
211-
}
206+
if (r instanceof ClusterRoleBinding) {
207+
clusterRoleBindings.add((ClusterRoleBinding) r);
208+
return;
209+
}
212210

213-
if (r instanceof ClusterRole) {
214-
clusterRoles.add((ClusterRole) r);
215-
return;
216-
}
211+
if (r instanceof ClusterRole) {
212+
clusterRoles.add((ClusterRole) r);
213+
return;
214+
}
217215

218-
if (r instanceof RoleBinding) {
219-
roleBindings.add((RoleBinding) r);
220-
return;
221-
}
216+
if (r instanceof RoleBinding) {
217+
roleBindings.add((RoleBinding) r);
218+
return;
219+
}
222220

223-
if (r instanceof Role) {
224-
roles.add((Role) r);
225-
return;
226-
}
221+
if (r instanceof Role) {
222+
roles.add((Role) r);
223+
return;
224+
}
227225

228-
if (r instanceof Deployment) {
229-
deployments.add((Deployment) r);
230-
}
231-
});
232-
233-
final var deploymentName = ResourceNameUtil.getResourceName(kubernetesConfig, configuration);
234-
final var generated = BundleGenerator.prepareGeneration(bundleConfiguration, operatorConfiguration,
235-
versionBuildItem.getVersion(),
236-
csvMetadata.getCsvGroups(), crds, outputTarget.getOutputDirectory(), deploymentName);
237-
generated.forEach(manifestBuilder -> {
238-
final var fileName = manifestBuilder.getFileName();
239-
try {
240-
generatedCSVs.produce(
241-
new GeneratedFileSystemResourceBuildItem(
242-
Path.of(BUNDLE).resolve(manifestBuilder.getName()).resolve(fileName).toString(),
243-
manifestBuilder.getManifestData(serviceAccounts, clusterRoleBindings, clusterRoles,
244-
roleBindings, roles, deployments)));
245-
log.infov("Generating {0} for ''{1}'' controller -> {2}",
246-
manifestBuilder.getManifestType(),
247-
manifestBuilder.getName(),
248-
outputDir.resolve(manifestBuilder.getName()).resolve(fileName));
249-
} catch (IOException e) {
250-
log.errorv("Cannot generate {0} for ''{1}'' controller: {2}",
251-
manifestBuilder.getManifestType(), manifestBuilder.getName(), e.getMessage());
252-
}
253-
});
254-
doneGeneratingCSV.produce(new GeneratedBundleBuildItem());
226+
if (r instanceof Deployment) {
227+
deployments.add((Deployment) r);
228+
}
229+
});
255230

256-
}
231+
final var deploymentName = ResourceNameUtil.getResourceName(kubernetesConfig, configuration);
232+
final var generated = BundleGenerator.prepareGeneration(bundleConfiguration, operatorConfiguration,
233+
versionBuildItem.getVersion(),
234+
csvMetadata.getCsvGroups(), crds, outputTarget.getOutputDirectory(), deploymentName);
235+
generated.forEach(manifestBuilder -> {
236+
final var fileName = manifestBuilder.getFileName();
237+
try {
238+
generatedCSVs.produce(
239+
new GeneratedFileSystemResourceBuildItem(
240+
Path.of(BUNDLE).resolve(manifestBuilder.getName()).resolve(fileName).toString(),
241+
manifestBuilder.getManifestData(serviceAccounts, clusterRoleBindings, clusterRoles,
242+
roleBindings, roles, deployments)));
243+
log.infov("Generating {0} for ''{1}'' controller -> {2}",
244+
manifestBuilder.getManifestType(),
245+
manifestBuilder.getName(),
246+
outputDir.resolve(manifestBuilder.getName()).resolve(fileName));
247+
} catch (IOException e) {
248+
log.errorv("Cannot generate {0} for ''{1}'' controller: {2}",
249+
manifestBuilder.getManifestType(), manifestBuilder.getName(), e.getMessage());
250+
}
251+
});
252+
doneGeneratingCSV.produce(new GeneratedBundleBuildItem());
257253
}
258254

259255
private Map<String, CSVMetadataHolder> getSharedMetadataHolders(String name, String version, String defaultReplaces,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.quarkiverse.operatorsdk.common;
2+
3+
import java.util.List;
4+
5+
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
import io.quarkus.builder.item.SimpleBuildItem;
7+
8+
public final class DeserializedKubernetesResourcesBuildItem extends SimpleBuildItem {
9+
private final List<HasMetadata> resources;
10+
11+
public DeserializedKubernetesResourcesBuildItem(List<HasMetadata> resources) {
12+
this.resources = resources;
13+
}
14+
15+
public List<HasMetadata> getResources() {
16+
return resources;
17+
}
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.quarkiverse.operatorsdk.deployment;
2+
3+
import java.util.List;
4+
import java.util.function.BooleanSupplier;
5+
6+
import org.eclipse.microprofile.config.ConfigProvider;
7+
8+
import io.quarkiverse.operatorsdk.common.DeserializedKubernetesResourcesBuildItem;
9+
import io.quarkiverse.operatorsdk.common.GeneratedResourcesUtils;
10+
import io.quarkus.deployment.annotations.BuildStep;
11+
import io.quarkus.kubernetes.spi.GeneratedKubernetesResourceBuildItem;
12+
13+
public class GeneratedKubernetesManifestsProcessor {
14+
private static class NeedResourcesDeserialization implements BooleanSupplier {
15+
@Override
16+
public boolean getAsBoolean() {
17+
final var helmEnabled = ConfigProvider.getConfig()
18+
.getOptionalValue("quarkus.operator-sdk.helm.enabled", Boolean.class).orElse(false);
19+
final var bundleEnabled = ConfigProvider.getConfig()
20+
.getOptionalValue("quarkus.operator-sdk.bundle.enabled", Boolean.class).orElse(false);
21+
return helmEnabled || bundleEnabled;
22+
}
23+
}
24+
25+
@BuildStep(onlyIf = NeedResourcesDeserialization.class)
26+
DeserializedKubernetesResourcesBuildItem deserializeGeneratedKubernetesResources(
27+
List<GeneratedKubernetesResourceBuildItem> generatedResources) {
28+
return new DeserializedKubernetesResourcesBuildItem(GeneratedResourcesUtils.loadFrom(generatedResources));
29+
}
30+
}

core/deployment/src/main/java/io/quarkiverse/operatorsdk/deployment/helm/HelmChartProcessor.java

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@
1111
import java.util.Collection;
1212
import java.util.List;
1313
import java.util.Map;
14+
import java.util.function.BooleanSupplier;
1415
import java.util.stream.Collectors;
1516

1617
import org.jboss.logging.Logger;
1718

1819
import io.dekorate.helm.model.Chart;
19-
import io.fabric8.kubernetes.api.model.EnvVar;
20+
import io.fabric8.kubernetes.api.model.*;
2021
import io.fabric8.kubernetes.api.model.apps.Deployment;
22+
import io.fabric8.kubernetes.api.model.rbac.ClusterRole;
23+
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBinding;
24+
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
25+
import io.quarkiverse.operatorsdk.common.DeserializedKubernetesResourcesBuildItem;
2126
import io.quarkiverse.operatorsdk.common.FileUtils;
22-
import io.quarkiverse.operatorsdk.common.GeneratedResourcesUtils;
2327
import io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator;
2428
import io.quarkiverse.operatorsdk.deployment.ControllerConfigurationsBuildItem;
2529
import io.quarkiverse.operatorsdk.deployment.GeneratedCRDInfoBuildItem;
@@ -28,11 +32,11 @@
2832
import io.quarkus.container.spi.ContainerImageInfoBuildItem;
2933
import io.quarkus.deployment.annotations.BuildProducer;
3034
import io.quarkus.deployment.annotations.BuildStep;
35+
import io.quarkus.deployment.annotations.Produce;
3136
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
3237
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
3338
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
34-
import io.quarkus.kubernetes.spi.ConfiguratorBuildItem;
35-
import io.quarkus.kubernetes.spi.GeneratedKubernetesResourceBuildItem;
39+
import io.quarkus.kubernetes.spi.*;
3640
import io.quarkus.qute.Qute;
3741

3842
public class HelmChartProcessor {
@@ -56,45 +60,90 @@ public class HelmChartProcessor {
5660
public static final String CRD_DIR = "crds";
5761
public static final String CRD_ROLE_BINDING_TEMPLATE_PATH = "/helm/crd-role-binding-template.yaml";
5862

59-
@BuildStep
60-
public void handleHelmCharts(
61-
// to make it produce a build item, so it gets executed
62-
@SuppressWarnings("unused") BuildProducer<ArtifactResultBuildItem> dummy,
63-
List<GeneratedKubernetesResourceBuildItem> generatedResources,
63+
private static class HelmGenerationEnabled implements BooleanSupplier {
64+
private BuildTimeOperatorConfiguration config;
65+
66+
@Override
67+
public boolean getAsBoolean() {
68+
return config.helm.enabled;
69+
}
70+
}
71+
72+
@BuildStep(onlyIfNot = HelmGenerationEnabled.class)
73+
@Produce(ArtifactResultBuildItem.class)
74+
void outputHelmGenerationDisabled() {
75+
log.debug("Generating Helm chart is disabled");
76+
}
77+
78+
@BuildStep(onlyIf = HelmGenerationEnabled.class)
79+
@Produce(ArtifactResultBuildItem.class) // to make it produce a build item, so it gets executed
80+
void handleHelmCharts(DeserializedKubernetesResourcesBuildItem generatedKubernetesResources,
6481
ControllerConfigurationsBuildItem controllerConfigurations,
65-
BuildTimeOperatorConfiguration buildTimeConfiguration,
6682
GeneratedCRDInfoBuildItem generatedCRDInfoBuildItem,
6783
OutputTargetBuildItem outputTarget,
6884
ApplicationInfoBuildItem appInfo,
6985
ContainerImageInfoBuildItem containerImageInfoBuildItem) {
7086

71-
if (buildTimeConfiguration.helm.enabled) {
72-
final var helmDir = outputTarget.getOutputDirectory().resolve("helm").toFile();
73-
log.infov("Generating helm chart to {0}", helmDir);
74-
var controllerConfigs = controllerConfigurations.getControllerConfigs().values();
75-
76-
createRelatedDirectories(helmDir);
77-
addTemplateFiles(helmDir);
78-
addClusterRolesForReconcilers(helmDir, controllerConfigs);
79-
addPrimaryClusterRoleBindings(helmDir, controllerConfigs);
80-
addGeneratedDeployment(helmDir, generatedResources, controllerConfigurations, appInfo);
81-
addChartYaml(helmDir, appInfo.getName(), appInfo.getVersion());
82-
addValuesYaml(helmDir, containerImageInfoBuildItem.getTag());
83-
addReadmeAndSchema(helmDir);
84-
addCRDs(new File(helmDir, CRD_DIR), generatedCRDInfoBuildItem);
85-
} else {
86-
log.debug("Generating helm chart is disabled");
87+
final var helmDir = outputTarget.getOutputDirectory().resolve("helm").toFile();
88+
log.infov("Generating helm chart to {0}", helmDir);
89+
var controllerConfigs = controllerConfigurations.getControllerConfigs().values();
90+
91+
final var resources = generatedKubernetesResources.getResources();
92+
createRelatedDirectories(helmDir);
93+
addTemplateFiles(helmDir);
94+
addClusterRolesForReconcilers(helmDir, controllerConfigs);
95+
addPrimaryClusterRoleBindings(helmDir, controllerConfigs);
96+
addGeneratedDeployment(helmDir, resources, controllerConfigurations, appInfo);
97+
addChartYaml(helmDir, appInfo.getName(), appInfo.getVersion());
98+
addValuesYaml(helmDir, containerImageInfoBuildItem.getTag());
99+
addReadmeAndSchema(helmDir);
100+
addCRDs(new File(helmDir, CRD_DIR), generatedCRDInfoBuildItem);
101+
addExplicitlyAddedKubernetesResources(helmDir, resources, appInfo);
102+
}
103+
104+
private void addExplicitlyAddedKubernetesResources(File helmDir,
105+
List<HasMetadata> resources, ApplicationInfoBuildItem appInfo) {
106+
resources = filterOutStandardResources(resources, appInfo);
107+
if (!resources.isEmpty()) {
108+
addResourceToHelmDir(helmDir, resources);
109+
}
110+
}
111+
112+
private List<HasMetadata> filterOutStandardResources(List<HasMetadata> resources, ApplicationInfoBuildItem appInfo) {
113+
return resources.stream().filter(r -> {
114+
if (r instanceof ClusterRole) {
115+
return !r.getMetadata().getName().endsWith("-cluster-role");
116+
}
117+
if (r instanceof ClusterRoleBinding) {
118+
return !r.getMetadata().getName().endsWith("-crd-validating-role-binding");
119+
}
120+
if (r instanceof RoleBinding) {
121+
return !r.getMetadata().getName().equals(appInfo.getName() + "-view") &&
122+
!r.getMetadata().getName().endsWith("-role-binding");
123+
}
124+
if (r instanceof Service || r instanceof Deployment || r instanceof ServiceAccount) {
125+
return !r.getMetadata().getName().equals(appInfo.getName());
126+
}
127+
return true;
128+
}).collect(Collectors.toList());
129+
}
130+
131+
private void addResourceToHelmDir(File helmDir, List<HasMetadata> list) {
132+
String yaml = FileUtils.asYaml(list);
133+
try {
134+
Files.writeString(Path.of(helmDir.getPath(), TEMPLATES_DIR, "kubernetes.yml"), yaml);
135+
} catch (IOException e) {
136+
throw new IllegalStateException(e);
87137
}
88138
}
89139

90140
private void addTemplateFiles(File helmDir) {
91141
copyTemplates(helmDir.toPath().resolve(TEMPLATES_DIR), TEMPLATE_FILES);
92142
}
93143

94-
private void addGeneratedDeployment(File helmDir, List<GeneratedKubernetesResourceBuildItem> generatedResources,
144+
private void addGeneratedDeployment(File helmDir, List<HasMetadata> resources,
95145
ControllerConfigurationsBuildItem controllerConfigurations,
96146
ApplicationInfoBuildItem appInfo) {
97-
final var resources = GeneratedResourcesUtils.loadFrom(generatedResources);
98147
Deployment deployment = (Deployment) resources.stream()
99148
.filter(Deployment.class::isInstance).findFirst()
100149
.orElseThrow();

0 commit comments

Comments
 (0)