Skip to content

Commit 1515cb7

Browse files
csvirimetacosm
andauthored
feat: generate simple generic helm chart (#665)
Co-authored-by: Chris Laprun <metacosm@gmail.com>
1 parent bddef54 commit 1515cb7

File tree

27 files changed

+908
-113
lines changed

27 files changed

+908
-113
lines changed

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

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator.ALL_VERBS;
44

5-
import java.io.ByteArrayInputStream;
65
import java.io.IOException;
76
import java.nio.file.Path;
87
import java.util.ArrayList;
@@ -22,7 +21,6 @@
2221
import org.jboss.jandex.IndexView;
2322
import org.jboss.logging.Logger;
2423

25-
import io.dekorate.utils.Serialization;
2624
import io.fabric8.kubernetes.api.model.ServiceAccount;
2725
import io.fabric8.kubernetes.api.model.apps.Deployment;
2826
import io.fabric8.kubernetes.api.model.rbac.ClusterRole;
@@ -34,10 +32,7 @@
3432
import io.quarkiverse.operatorsdk.bundle.runtime.CSVMetadata.Icon;
3533
import io.quarkiverse.operatorsdk.bundle.runtime.CSVMetadataHolder;
3634
import io.quarkiverse.operatorsdk.bundle.runtime.SharedCSVMetadata;
37-
import io.quarkiverse.operatorsdk.common.ClassUtils;
38-
import io.quarkiverse.operatorsdk.common.ConfigurationUtils;
39-
import io.quarkiverse.operatorsdk.common.ReconciledAugmentedClassInfo;
40-
import io.quarkiverse.operatorsdk.common.ReconcilerAugmentedClassInfo;
35+
import io.quarkiverse.operatorsdk.common.*;
4136
import io.quarkiverse.operatorsdk.deployment.GeneratedCRDInfoBuildItem;
4237
import io.quarkiverse.operatorsdk.deployment.VersionBuildItem;
4338
import io.quarkiverse.operatorsdk.runtime.CRDInfo;
@@ -192,44 +187,37 @@ void generateBundle(ApplicationInfoBuildItem configuration,
192187
final var roles = new LinkedList<Role>();
193188
final var deployments = new LinkedList<Deployment>();
194189

195-
generatedKubernetesManifests.stream()
196-
.filter(bi -> bi.getName().equals("kubernetes.yml"))
197-
.findAny()
198-
.ifPresent(
199-
bi -> {
200-
final var resources = Serialization
201-
.unmarshalAsList(new ByteArrayInputStream(bi.getContent()));
202-
resources.getItems().forEach(r -> {
203-
if (r instanceof ServiceAccount) {
204-
serviceAccounts.add((ServiceAccount) r);
205-
return;
206-
}
207-
208-
if (r instanceof ClusterRoleBinding) {
209-
clusterRoleBindings.add((ClusterRoleBinding) r);
210-
return;
211-
}
212-
213-
if (r instanceof ClusterRole) {
214-
clusterRoles.add((ClusterRole) r);
215-
return;
216-
}
217-
218-
if (r instanceof RoleBinding) {
219-
roleBindings.add((RoleBinding) r);
220-
return;
221-
}
222-
223-
if (r instanceof Role) {
224-
roles.add((Role) r);
225-
return;
226-
}
227-
228-
if (r instanceof Deployment) {
229-
deployments.add((Deployment) r);
230-
}
231-
});
232-
});
190+
final var resources = GeneratedResourcesUtils.loadFrom(generatedKubernetesManifests);
191+
resources.forEach(r -> {
192+
if (r instanceof ServiceAccount) {
193+
serviceAccounts.add((ServiceAccount) r);
194+
return;
195+
}
196+
197+
if (r instanceof ClusterRoleBinding) {
198+
clusterRoleBindings.add((ClusterRoleBinding) r);
199+
return;
200+
}
201+
202+
if (r instanceof ClusterRole) {
203+
clusterRoles.add((ClusterRole) r);
204+
return;
205+
}
206+
207+
if (r instanceof RoleBinding) {
208+
roleBindings.add((RoleBinding) r);
209+
return;
210+
}
211+
212+
if (r instanceof Role) {
213+
roles.add((Role) r);
214+
return;
215+
}
216+
217+
if (r instanceof Deployment) {
218+
deployments.add((Deployment) r);
219+
}
220+
});
233221
final var generated = BundleGenerator.prepareGeneration(bundleConfiguration, versionBuildItem.getVersion(),
234222
csvMetadata.getCsvGroups(), crds, outputTarget.getOutputDirectory());
235223
generated.forEach(manifestBuilder -> {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.quarkiverse.operatorsdk.common;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.File;
5+
import java.util.List;
6+
7+
import io.fabric8.kubernetes.api.model.HasMetadata;
8+
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
9+
10+
public class FileUtils {
11+
private final static KubernetesSerialization serializer = new KubernetesSerialization();
12+
13+
public static void ensureDirectoryExists(File dir) {
14+
if (!dir.exists()) {
15+
if (!dir.mkdirs()) {
16+
throw new IllegalArgumentException("Couldn't create " + dir.getAbsolutePath());
17+
}
18+
}
19+
}
20+
21+
public static List<HasMetadata> unmarshalFrom(byte[] yamlOrJson) {
22+
return serializer.unmarshal(new ByteArrayInputStream(yamlOrJson));
23+
}
24+
25+
public static String asYaml(Object toSerialize) {
26+
return serializer.asYaml(toSerialize);
27+
}
28+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.quarkiverse.operatorsdk.common;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
import java.util.stream.Collectors;
6+
7+
import org.jboss.logging.Logger;
8+
9+
import io.fabric8.kubernetes.api.model.HasMetadata;
10+
import io.quarkus.kubernetes.spi.GeneratedKubernetesResourceBuildItem;
11+
12+
public class GeneratedResourcesUtils {
13+
public static final String KUBERNETES_YAML = "kubernetes.yml";
14+
private static final Logger log = Logger.getLogger(GeneratedResourcesUtils.class.getName());
15+
16+
public static List<HasMetadata> loadFrom(List<GeneratedKubernetesResourceBuildItem> generatedResources,
17+
String resourceName) {
18+
if (generatedResources.isEmpty()) {
19+
log.debugv("Couldn't load resource {0} because no resources were generated", resourceName);
20+
return Collections.emptyList();
21+
}
22+
var buildItem = generatedResources.stream()
23+
.filter(r -> resourceName.equals(r.getName()))
24+
.findAny();
25+
return buildItem.map(bi -> FileUtils.unmarshalFrom(bi.getContent()))
26+
.orElseThrow(() -> new IllegalArgumentException("Couldn't find resource " + resourceName +
27+
" in generated resources: " + generatedResources.stream()
28+
.map(GeneratedKubernetesResourceBuildItem::getName).collect(Collectors.toSet())));
29+
}
30+
31+
public static List<HasMetadata> loadFrom(List<GeneratedKubernetesResourceBuildItem> generatedResources) {
32+
return loadFrom(generatedResources, KUBERNETES_YAML);
33+
}
34+
}

core/deployment/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@
5151
<artifactId>semver4j</artifactId>
5252
<version>5.1.0</version>
5353
</dependency>
54+
<dependency>
55+
<groupId>io.dekorate</groupId>
56+
<artifactId>helm-annotations</artifactId>
57+
<classifier>noapt</classifier>
58+
<exclusions>
59+
<exclusion>
60+
<groupId>io.sundr</groupId>
61+
<artifactId>*</artifactId>
62+
</exclusion>
63+
<exclusion>
64+
<groupId>com.sun</groupId>
65+
<artifactId>tools</artifactId>
66+
</exclusion>
67+
</exclusions>
68+
</dependency>
5469
<dependency>
5570
<groupId>io.dekorate</groupId>
5671
<artifactId>kubernetes-annotations</artifactId>

core/deployment/src/main/java/io/quarkiverse/operatorsdk/deployment/AddClusterRolesDecorator.java

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -51,59 +51,8 @@ public AddClusterRolesDecorator(Collection<QuarkusControllerConfiguration> confi
5151
@Override
5252
public void visit(KubernetesListBuilder list) {
5353
configs.forEach(cri -> {
54-
final var rule = new PolicyRuleBuilder();
55-
final var resourceClass = cri.getResourceClass();
56-
final var plural = HasMetadata.getPlural(resourceClass);
57-
rule.addToResources(plural);
58-
59-
// if the resource has a non-Void status, also add the status resource
60-
if (cri.isStatusPresentAndNotVoid()) {
61-
rule.addToResources(plural + "/status");
62-
}
63-
64-
// add finalizers sub-resource because it's used in several contexts, even in the absence of finalizers
65-
// see: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement
66-
rule.addToResources(plural + "/finalizers");
67-
68-
rule.addToApiGroups(HasMetadata.getGroup(resourceClass))
69-
.addToVerbs(ALL_VERBS)
70-
.build();
71-
72-
final var clusterRoleBuilder = new ClusterRoleBuilder()
73-
.withNewMetadata()
74-
.withName(getClusterRoleName(cri.getName()))
75-
.endMetadata()
76-
.addToRules(rule.build());
77-
78-
@SuppressWarnings({ "rawtypes", "unchecked" })
79-
final Map<String, DependentResourceSpecMetadata> dependentsMetadata = cri.getDependentsMetadata();
80-
dependentsMetadata.forEach((name, spec) -> {
81-
final var dependentResourceClass = spec.getDependentResourceClass();
82-
final var associatedResourceClass = spec.getDependentType();
83-
84-
// only process Kubernetes dependents
85-
if (HasMetadata.class.isAssignableFrom(associatedResourceClass)) {
86-
final var dependentRule = new PolicyRuleBuilder()
87-
.addToApiGroups(HasMetadata.getGroup(associatedResourceClass))
88-
.addToResources(HasMetadata.getPlural(associatedResourceClass))
89-
.addToVerbs(READ_VERBS);
90-
if (Updater.class.isAssignableFrom(dependentResourceClass)) {
91-
dependentRule.addToVerbs(UPDATE_VERBS);
92-
}
93-
if (Deleter.class.isAssignableFrom(dependentResourceClass)) {
94-
dependentRule.addToVerbs(DELETE_VERB);
95-
}
96-
if (Creator.class.isAssignableFrom(dependentResourceClass)) {
97-
dependentRule.addToVerbs(CREATE_VERB);
98-
if (!dependentRule.getVerbs().contains(PATCH_VERB)) {
99-
dependentRule.addToVerbs(PATCH_VERB);
100-
}
101-
}
102-
clusterRoleBuilder.addToRules(dependentRule.build());
103-
}
104-
});
105-
106-
list.addToItems(clusterRoleBuilder.build());
54+
var clusterRole = createClusterRole(cri);
55+
list.addToItems(clusterRole);
10756
});
10857

10958
// if we're asking to validate the CRDs, also add CRDs permissions, once
@@ -121,6 +70,61 @@ public void visit(KubernetesListBuilder list) {
12170
}
12271
}
12372

73+
public static ClusterRole createClusterRole(QuarkusControllerConfiguration<?> cri) {
74+
final var rule = new PolicyRuleBuilder();
75+
final var resourceClass = cri.getResourceClass();
76+
final var plural = HasMetadata.getPlural(resourceClass);
77+
rule.addToResources(plural);
78+
79+
// if the resource has a non-Void status, also add the status resource
80+
if (cri.isStatusPresentAndNotVoid()) {
81+
rule.addToResources(plural + "/status");
82+
}
83+
84+
// add finalizers sub-resource because it's used in several contexts, even in the absence of finalizers
85+
// see: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement
86+
rule.addToResources(plural + "/finalizers");
87+
88+
rule.addToApiGroups(HasMetadata.getGroup(resourceClass))
89+
.addToVerbs(ALL_VERBS)
90+
.build();
91+
92+
final var clusterRoleBuilder = new ClusterRoleBuilder()
93+
.withNewMetadata()
94+
.withName(getClusterRoleName(cri.getName()))
95+
.endMetadata()
96+
.addToRules(rule.build());
97+
98+
final Map<String, DependentResourceSpecMetadata<?, ?, ?>> dependentsMetadata = cri.getDependentsMetadata();
99+
dependentsMetadata.forEach((name, spec) -> {
100+
final var dependentResourceClass = spec.getDependentResourceClass();
101+
final var associatedResourceClass = spec.getDependentType();
102+
103+
// only process Kubernetes dependents
104+
if (HasMetadata.class.isAssignableFrom(associatedResourceClass)) {
105+
final var dependentRule = new PolicyRuleBuilder()
106+
.addToApiGroups(HasMetadata.getGroup(associatedResourceClass))
107+
.addToResources(HasMetadata.getPlural(associatedResourceClass))
108+
.addToVerbs(READ_VERBS);
109+
if (Updater.class.isAssignableFrom(dependentResourceClass)) {
110+
dependentRule.addToVerbs(UPDATE_VERBS);
111+
}
112+
if (Deleter.class.isAssignableFrom(dependentResourceClass)) {
113+
dependentRule.addToVerbs(DELETE_VERB);
114+
}
115+
if (Creator.class.isAssignableFrom(dependentResourceClass)) {
116+
dependentRule.addToVerbs(CREATE_VERB);
117+
if (!dependentRule.getVerbs().contains(PATCH_VERB)) {
118+
dependentRule.addToVerbs(PATCH_VERB);
119+
}
120+
}
121+
clusterRoleBuilder.addToRules(dependentRule.build());
122+
}
123+
124+
});
125+
return clusterRoleBuilder.build();
126+
}
127+
124128
public static String getClusterRoleName(String controller) {
125129
return controller + "-cluster-role";
126130
}

core/deployment/src/main/java/io/quarkiverse/operatorsdk/deployment/CRDGeneration.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.fabric8.crd.generator.CustomResourceInfo;
1717
import io.fabric8.kubernetes.client.CustomResource;
1818
import io.quarkiverse.operatorsdk.common.CustomResourceAugmentedClassInfo;
19+
import io.quarkiverse.operatorsdk.common.FileUtils;
1920
import io.quarkiverse.operatorsdk.runtime.CRDConfiguration;
2021
import io.quarkiverse.operatorsdk.runtime.CRDGenerationInfo;
2122
import io.quarkiverse.operatorsdk.runtime.CRDInfo;
@@ -76,11 +77,7 @@ CRDGenerationInfo generate(OutputTargetBuildItem outputTarget,
7677
.map(d -> Paths.get("").toAbsolutePath().resolve(d))
7778
.orElse(outputTarget.getOutputDirectory().resolve(KUBERNETES));
7879
final var outputDir = targetDirectory.toFile();
79-
if (!outputDir.exists()) {
80-
if (!outputDir.mkdirs()) {
81-
throw new IllegalArgumentException("Couldn't create " + outputDir.getAbsolutePath());
82-
}
83-
}
80+
FileUtils.ensureDirectoryExists(outputDir);
8481

8582
// generate CRDs with detailed information
8683
final var info = generator.forCRDVersions(crdConfiguration.versions).inOutputDir(outputDir).detailedGenerate();

core/deployment/src/main/java/io/quarkiverse/operatorsdk/deployment/ReconcilerInfosBuildItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import io.quarkiverse.operatorsdk.common.ReconcilerAugmentedClassInfo;
66
import io.quarkus.builder.item.SimpleBuildItem;
77

8-
final class ReconcilerInfosBuildItem extends SimpleBuildItem {
8+
public final class ReconcilerInfosBuildItem extends SimpleBuildItem {
99
private final Map<String, ReconcilerAugmentedClassInfo> reconcilers;
1010

1111
public ReconcilerInfosBuildItem(Map<String, ReconcilerAugmentedClassInfo> reconcilers) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.quarkiverse.operatorsdk.deployment.helm;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import io.dekorate.WithSession;
7+
import io.dekorate.kubernetes.config.BaseConfigFluent;
8+
import io.dekorate.kubernetes.config.Configurator;
9+
10+
/**
11+
* Used to disable default Dekorate Helm chart generator, which would get automatically triggered by depending on the Dekorate
12+
* Helm annotations and the Quarkus Kubernetes extension.
13+
*/
14+
public class DisableDefaultHelmListener extends Configurator<BaseConfigFluent<?>> implements WithSession {
15+
@Override
16+
public void visit(BaseConfigFluent<?> baseConfigFluent) {
17+
Map<String, Object> helmConfig = new HashMap<>();
18+
helmConfig.put("enabled", "false");
19+
20+
Map<String, Object> config = new HashMap<>();
21+
config.put("helm", helmConfig);
22+
23+
WithSession.super.getSession().addPropertyConfiguration(config);
24+
}
25+
}

0 commit comments

Comments
 (0)