Skip to content

Commit 220eea4

Browse files
authored
fix: use Kubernetes configuration for name and version when available (#805)
* fix: use Kubernetes configuration for name and version when available Fixes #801 Signed-off-by: Chris Laprun <[email protected]> * refactor: RBACRule should be used instead of PermissionRule RBACRules should be used when the rules should apply to the service account of the Reconciler since they would also impact the generated Kubernetes manifests and be automatically propagated to the CSV, whereas the reverse is not true (i.e. PermissionRule will not appear in the generated Kubernetes manifests). Signed-off-by: Chris Laprun <[email protected]> * docs: clarify service account name role in PermissionRule Signed-off-by: Chris Laprun <[email protected]> --------- Signed-off-by: Chris Laprun <[email protected]>
1 parent 69cd08c commit 220eea4

File tree

6 files changed

+45
-16
lines changed

6 files changed

+45
-16
lines changed

annotations/src/main/java/io/quarkiverse/operatorsdk/annotations/CSVMetadata.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@
9191
boolean supported() default true;
9292
}
9393

94+
/**
95+
* Additional RBAC rules that need to be provided because they cannot be inferred automatically. Note that RBAC rules added
96+
* to your reconciler via {@link RBACRule} should already be handled automatically, under the service account name
97+
* associated with your Reconciler so this annotation should only be used to add additional rules to other service accounts
98+
* or for rules that you don't want to appear in the generated Kubernetes manifests.
99+
*/
94100
@interface PermissionRule {
95101
String[] apiGroups();
96102

@@ -99,8 +105,11 @@
99105
String[] verbs() default { "get", "list", "watch", "create", "delete", "patch", "update" };
100106

101107
/**
102-
* @return the service account name for the permission rule. If not provided, it will use the default service account
103-
* name.
108+
* @return the service account name to which the permission rule will be assigned. If not provided, the default service
109+
* account name as defined for your operator will be used. Note that for the rule to be effectively added to the
110+
* CSV, a service account with that name <em>must</em> exist in the generated kubernetes manifests as this is
111+
* the base upon which the bundle generator works. This means that if you add a rule that targets a service
112+
* account that is not present in the generated manifest, then this rule won't appear in the generated CSV.
104113
*/
105114
String serviceAccountName() default "";
106115
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@ public boolean getAsBoolean() {
6666

6767
@SuppressWarnings({ "unused" })
6868
@BuildStep(onlyIf = IsGenerationEnabled.class)
69-
CSVMetadataBuildItem gatherCSVMetadata(ApplicationInfoBuildItem configuration,
69+
CSVMetadataBuildItem gatherCSVMetadata(KubernetesConfig kubernetesConfig,
70+
ApplicationInfoBuildItem appConfiguration,
7071
BundleGenerationConfiguration bundleConfiguration,
7172
CombinedIndexBuildItem combinedIndexBuildItem) {
7273
final var index = combinedIndexBuildItem.getIndex();
73-
final var defaultName = bundleConfiguration.packageName.orElse(configuration.getName());
74+
final var defaultName = bundleConfiguration.packageName
75+
.orElse(ResourceNameUtil.getResourceName(kubernetesConfig, appConfiguration));
7476

7577
// note that version, replaces, etc. should probably be settable at the reconciler level
7678
// use version specified in bundle configuration, if not use the one extracted from the project, if available
77-
final var version = configuration.getVersion();
79+
final var version = kubernetesConfig.getVersion().orElse(appConfiguration.getVersion());
7880
final var defaultVersion = bundleConfiguration.version
7981
.orElse(ApplicationInfoBuildItem.UNSET_VALUE.equals(version) ? null : version);
8082

bundle-generator/deployment/src/test/java/io/quarkiverse/operatorsdk/bundle/MultipleOperatorsBundleTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import io.fabric8.kubernetes.api.model.HasMetadata;
1313
import io.fabric8.kubernetes.api.model.Pod;
14+
import io.fabric8.kubernetes.api.model.rbac.PolicyRuleBuilder;
1415
import io.quarkiverse.operatorsdk.bundle.sources.*;
1516
import io.quarkiverse.operatorsdk.common.ConfigurationUtils;
1617
import io.quarkus.test.ProdBuildResults;
@@ -50,6 +51,14 @@ public void shouldWriteBundleForTheOperators() throws IOException {
5051
assertEquals(BUNDLE_PACKAGE, bundleMeta.getAnnotations().get("operators.operatorframework.io.bundle.package.v1"));
5152

5253
checkBundleFor(bundle, "second-operator", Second.class);
54+
csv = getCSVFor(bundle, "second-operator");
55+
final var permissions = csv.getSpec().getInstall().getSpec().getPermissions();
56+
assertEquals(1, permissions.size());
57+
assertTrue(permissions.get(0).getRules().contains(new PolicyRuleBuilder()
58+
.addToApiGroups(SecondReconciler.RBAC_RULE_GROUP)
59+
.addToResources(SecondReconciler.RBAC_RULE_RES)
60+
.addToVerbs(SecondReconciler.RBAC_RULE_VERBS)
61+
.build()));
5362

5463
checkBundleFor(bundle, "third-operator", Third.class);
5564
// also check that external CRD is present

bundle-generator/deployment/src/test/java/io/quarkiverse/operatorsdk/bundle/sources/SecondReconciler.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package io.quarkiverse.operatorsdk.bundle.sources;
22

33
import io.javaoperatorsdk.operator.api.reconciler.Context;
4+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
45
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
56
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
67
import io.quarkiverse.operatorsdk.annotations.CSVMetadata;
8+
import io.quarkiverse.operatorsdk.annotations.RBACRule;
79

810
@CSVMetadata(name = "second-operator")
11+
@RBACRule(apiGroups = SecondReconciler.RBAC_RULE_GROUP, resources = SecondReconciler.RBAC_RULE_RES, verbs = SecondReconciler.RBAC_RULE_VERBS)
12+
@ControllerConfiguration(namespaces = "foo")
913
public class SecondReconciler implements Reconciler<Second> {
14+
public static final String RBAC_RULE_GROUP = "halkyon.io";
15+
public static final String RBAC_RULE_RES = "SomeResource";
16+
public static final String RBAC_RULE_VERBS = "write";
1017

1118
@Override
1219
public UpdateControl<Second> reconcile(Second request, Context<Second> context) {

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
3434
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
3535
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
36-
import io.quarkus.kubernetes.spi.*;
36+
import io.quarkus.kubernetes.deployment.KubernetesConfig;
37+
import io.quarkus.kubernetes.deployment.ResourceNameUtil;
3738
import io.quarkus.qute.Qute;
3839

3940
@BuildSteps(onlyIf = HelmGenerationEnabled.class)
@@ -128,9 +129,9 @@ void addClusterRolesForReconcilers(HelmTargetDirectoryBuildItem helmTargetDirect
128129
@Produce(ArtifactResultBuildItem.class)
129130
void addExplicitlyAddedKubernetesResources(DeserializedKubernetesResourcesBuildItem generatedKubernetesResources,
130131
HelmTargetDirectoryBuildItem helmDirBI,
131-
ApplicationInfoBuildItem appInfo) {
132+
ApplicationInfoBuildItem appInfo, KubernetesConfig kubernetesConfig) {
132133
var resources = generatedKubernetesResources.getResources();
133-
resources = filterOutStandardResources(resources, appInfo);
134+
resources = filterOutStandardResources(resources, ResourceNameUtil.getResourceName(kubernetesConfig, appInfo));
134135
if (!resources.isEmpty()) {
135136
final var kubernetesManifest = helmDirBI.getPathToTemplatesDir().resolve("kubernetes.yml");
136137
String yaml = FileUtils.asYaml(resources);
@@ -142,7 +143,7 @@ void addExplicitlyAddedKubernetesResources(DeserializedKubernetesResourcesBuildI
142143
}
143144
}
144145

145-
private List<HasMetadata> filterOutStandardResources(List<HasMetadata> resources, ApplicationInfoBuildItem appInfo) {
146+
private List<HasMetadata> filterOutStandardResources(List<HasMetadata> resources, String operatorName) {
146147
return resources.stream().filter(r -> {
147148
if (r instanceof ClusterRole) {
148149
return !r.getMetadata().getName().endsWith("-cluster-role");
@@ -151,11 +152,11 @@ private List<HasMetadata> filterOutStandardResources(List<HasMetadata> resources
151152
return !r.getMetadata().getName().endsWith("-crd-validating-role-binding");
152153
}
153154
if (r instanceof RoleBinding) {
154-
return !r.getMetadata().getName().equals(appInfo.getName() + "-view") &&
155+
return !r.getMetadata().getName().equals(operatorName + "-view") &&
155156
!r.getMetadata().getName().endsWith("-role-binding");
156157
}
157158
if (r instanceof Service || r instanceof Deployment || r instanceof ServiceAccount) {
158-
return !r.getMetadata().getName().equals(appInfo.getName());
159+
return !r.getMetadata().getName().equals(operatorName);
159160
}
160161
return true;
161162
}).collect(Collectors.toList());
@@ -178,10 +179,9 @@ void addGeneratedDeployment(HelmTargetDirectoryBuildItem helmDirBI,
178179
.filter(Deployment.class::isInstance).findFirst()
179180
.orElseThrow();
180181
final var envs = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv();
181-
controllerConfigurations.getControllerConfigs().values().forEach(c -> {
182-
envs.add(new EnvVar(ConfigurationUtils.getNamespacesPropertyName(c.getName(), true),
183-
"{watchNamespaces}", null));
184-
});
182+
controllerConfigurations.getControllerConfigs()
183+
.forEach((name, unused) -> envs.add(new EnvVar(ConfigurationUtils.getNamespacesPropertyName(name, true),
184+
"{watchNamespaces}", null)));
185185

186186
// a bit hacky solution to get the exact placeholder without brackets
187187
final var template = FileUtils.asYaml(deployment);

samples/joke/src/main/java/io/quarkiverse/operatorsdk/samples/joke/JokeRequestReconciler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
3131
import io.quarkiverse.operatorsdk.annotations.CSVMetadata;
3232
import io.quarkiverse.operatorsdk.annotations.CSVMetadata.Icon;
33+
import io.quarkiverse.operatorsdk.annotations.RBACRule;
3334
import io.quarkiverse.operatorsdk.samples.joke.JokeRequestSpec.ExcludedTopic;
3435
import io.quarkiverse.operatorsdk.samples.joke.JokeRequestStatus.State;
3536

36-
@CSVMetadata(permissionRules = @CSVMetadata.PermissionRule(apiGroups = Joke.GROUP, resources = "jokes"), requiredCRDs = @CSVMetadata.RequiredCRD(kind = "Joke", name = Joke.NAME, version = Joke.VERSION), icon = @Icon(fileName = "icon.png", mediatype = "image/png"))
37+
@CSVMetadata(requiredCRDs = @CSVMetadata.RequiredCRD(kind = "Joke", name = Joke.NAME, version = Joke.VERSION), icon = @Icon(fileName = "icon.png", mediatype = "image/png"))
3738
@ControllerConfiguration(namespaces = WATCH_CURRENT_NAMESPACE)
39+
@RBACRule(apiGroups = Joke.GROUP, resources = "jokes", verbs = RBACRule.ALL)
3840
@SuppressWarnings("unused")
3941
public class JokeRequestReconciler implements Reconciler<JokeRequest> {
4042
@Inject

0 commit comments

Comments
 (0)