Skip to content

Commit

Permalink
fix: do not require deploy namespace, restore CRD validation support
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Laprun <[email protected]>
  • Loading branch information
metacosm committed Apr 25, 2024
1 parent 9e0f6c8 commit 885d178
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@

public class AddClusterRolesDecorator extends ResourceProvidingDecorator<KubernetesListBuilder> {

static final String JOSDK_CRD_VALIDATING_CLUSTER_ROLE = "josdk-crd-validating-cluster-role";
@SuppressWarnings("rawtypes")
private final Collection<QuarkusControllerConfiguration> configs;
public static final String JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME = "josdk-crd-validating-cluster-role";
private static final ClusterRoleBuilder CRD_VALIDATING_CLUSTER_ROLE_BUILDER = new ClusterRoleBuilder().withNewMetadata()
.withName(JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME).endMetadata()
.addToRules(new PolicyRuleBuilder()
.addToApiGroups("apiextensions.k8s.io")
.addToResources("customresourcedefinitions")
.addToVerbs("get", "list")
.build());
private static final String CR_API_VERSION = HasMetadata.getApiVersion(ClusterRole.class);
private static final String CR_KIND = HasMetadata.getKind(ClusterRole.class);
private final Collection<QuarkusControllerConfiguration<?>> configs;

private final boolean validateCRDs;

@SuppressWarnings("rawtypes")
public AddClusterRolesDecorator(Collection<QuarkusControllerConfiguration> configs, boolean validateCRDs) {
public AddClusterRolesDecorator(Collection<QuarkusControllerConfiguration<?>> configs, boolean validateCRDs) {
this.configs = configs;
this.validateCRDs = validateCRDs;
}
Expand All @@ -39,15 +46,8 @@ public void visit(KubernetesListBuilder list) {

// if we're asking to validate the CRDs, also add CRDs permissions, once
if (validateCRDs) {
final var crName = JOSDK_CRD_VALIDATING_CLUSTER_ROLE;

if (!contains(list, HasMetadata.getApiVersion(ClusterRole.class), HasMetadata.getKind(ClusterRole.class), crName)) {
list.addToItems(new ClusterRoleBuilder().withNewMetadata().withName(crName).endMetadata()
.addToRules(new PolicyRuleBuilder()
.addToApiGroups("apiextensions.k8s.io")
.addToResources("customresourcedefinitions")
.addToVerbs("get", "list")
.build()));
if (!contains(list, CR_API_VERSION, CR_KIND, JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME)) {
list.addToItems(CRD_VALIDATING_CLUSTER_ROLE_BUILDER);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkiverse.operatorsdk.deployment;

import static io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator.JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME;
import static io.quarkiverse.operatorsdk.deployment.AddClusterRolesDecorator.getClusterRoleName;

import java.util.ArrayList;
Expand Down Expand Up @@ -29,15 +30,17 @@ public class AddRoleBindingsDecorator extends ResourceProvidingDecorator<Kuberne

private static final Logger log = Logger.getLogger(AddRoleBindingsDecorator.class);
protected static final String RBAC_AUTHORIZATION_GROUP = "rbac.authorization.k8s.io";
protected static final String CLUSTER_ROLE = "ClusterRole";
public static final String CLUSTER_ROLE = "ClusterRole";
protected static final String SERVICE_ACCOUNT = "ServiceAccount";
private final Collection<QuarkusControllerConfiguration> configs;
private final Collection<QuarkusControllerConfiguration<?>> configs;
private final BuildTimeOperatorConfiguration operatorConfiguration;
private static final ConcurrentMap<QuarkusControllerConfiguration, List<HasMetadata>> cachedBindings = new ConcurrentHashMap<>();
private static final Optional<String> deployNamespace = ConfigProvider.getConfig()
.getOptionalValue("quarkus.kubernetes.namespace", String.class);
public static final RoleRef CRD_VALIDATING_ROLE_REF = new RoleRef(RBAC_AUTHORIZATION_GROUP, CLUSTER_ROLE,
JOSDK_CRD_VALIDATING_CLUSTER_ROLE_NAME);

public AddRoleBindingsDecorator(Collection<QuarkusControllerConfiguration> configs,
public AddRoleBindingsDecorator(Collection<QuarkusControllerConfiguration<?>> configs,
BuildTimeOperatorConfiguration operatorConfiguration) {
this.configs = configs;
this.operatorConfiguration = operatorConfiguration;
Expand All @@ -62,9 +65,9 @@ private List<HasMetadata> bindingsFor(QuarkusControllerConfiguration<?> controll
// if we validate the CRDs, also create a binding for the CRD validating role
List<HasMetadata> itemsToAdd;
if (operatorConfiguration.crd.validate) {
final var crBindingName = controllerName + "-crd-validating-role-binding";
final var crBindingName = getCRDValidatingBindingName(controllerName);
final var crdValidatorRoleBinding = createClusterRoleBinding(serviceAccountName, controllerName,
crBindingName, "validate CRDs", null);
crBindingName, "validate CRDs", CRD_VALIDATING_ROLE_REF);
itemsToAdd = new ArrayList<>(desiredWatchedNamespaces.size() + 1);
itemsToAdd.add(crdValidatorRoleBinding);
} else {
Expand All @@ -74,24 +77,30 @@ private List<HasMetadata> bindingsFor(QuarkusControllerConfiguration<?> controll
final var roleBindingName = getRoleBindingName(controllerName);
if (controllerConfiguration.watchCurrentNamespace()) {
// create a RoleBinding that will be applied in the current namespace if watching only the current NS
itemsToAdd.add(createRoleBinding(roleBindingName, serviceAccountName,
deployNamespace.orElseThrow(), createDefaultRoleRef(getClusterRoleName(controllerName))));
//add additional Role Bindings
itemsToAdd.add(createRoleBinding(roleBindingName, serviceAccountName, null,
createDefaultRoleRef(getClusterRoleName(controllerName))));
// add additional Role Bindings
controllerConfiguration.getAdditionalRBACRoleRefs().forEach(
roleRef -> {
final var specificRoleBindingName = getSpecificRoleBindingName(controllerName, roleRef);
itemsToAdd.add(createRoleBinding(specificRoleBindingName, serviceAccountName,
deployNamespace.orElseThrow(), roleRef));
itemsToAdd.add(createRoleBinding(specificRoleBindingName, serviceAccountName, null, roleRef));
});
} else if (controllerConfiguration.watchAllNamespaces()) {
itemsToAdd.add(createClusterRoleBinding(serviceAccountName, controllerName,
controllerName + "-cluster-role-binding", "watch all namespaces",
getClusterRoleBindingName(controllerName), "watch all namespaces",
null));
//add additional Role Bindings
// add additional cluster role bindings only if they target cluster roles
controllerConfiguration.getAdditionalRBACRoleRefs().forEach(
roleRef -> itemsToAdd.add(createClusterRoleBinding(serviceAccountName, controllerName,
roleRef.getName() + "-" + controllerName + "-cluster-role-binding",
"watch all namespaces", roleRef)));
roleRef -> {
if (!CLUSTER_ROLE.equals(roleRef.getKind())) {
log.warnv("Cannot create a ClusterRoleBinding for RoleRef ''{0}'' because it's not a ClusterRole",
roleRef);
} else {
itemsToAdd.add(createClusterRoleBinding(serviceAccountName, controllerName,
roleRef.getName() + "-" + getClusterRoleBindingName(controllerName),
"watch all namespaces", roleRef));
}
});
} else {
// create a RoleBinding using either the provided deployment namespace or the desired watched namespace name
desiredWatchedNamespaces
Expand All @@ -111,12 +120,24 @@ private List<HasMetadata> bindingsFor(QuarkusControllerConfiguration<?> controll
return itemsToAdd;
}

public static String getCRDValidatingBindingName(String controllerName) {
return controllerName + "-crd-validating-role-binding";
}

private static String getClusterRoleBindingName(String controllerName) {
return controllerName + "-cluster-role-binding";
}

public static String getRoleBindingName(String controllerName) {
return controllerName + "-role-binding";
}

public static String getSpecificRoleBindingName(String controllerName, String roleRefName) {
return roleRefName + "-" + getRoleBindingName(controllerName);
}

public static String getSpecificRoleBindingName(String controllerName, RoleRef roleRef) {
return roleRef.getName() + "-" + getRoleBindingName(controllerName);
return getSpecificRoleBindingName(controllerName, roleRef.getName());
}

private static RoleRef createDefaultRoleRef(String controllerName) {
Expand All @@ -127,13 +148,14 @@ private static RoleRef createDefaultRoleRef(String controllerName) {

private static RoleBinding createRoleBinding(String roleBindingName,
String serviceAccountName,
String namespace,
String targetNamespace,
RoleRef roleRef) {
log.infov("Creating ''{0}'' RoleBinding to be applied to {1}", roleBindingName, namespace);
final var nsMsg = (targetNamespace == null ? "current" : "'" + targetNamespace + "'") + " namespace";
log.infov("Creating ''{0}'' RoleBinding to be applied to {1}", roleBindingName, nsMsg);
return new RoleBindingBuilder()
.withNewMetadata()
.withName(roleBindingName)
.withNamespace(namespace)
.withNamespace(targetNamespace)
.endMetadata()
.withRoleRef(roleRef)
.addNewSubject(null, SERVICE_ACCOUNT, serviceAccountName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
@SuppressWarnings("rawtypes")
public final class ControllerConfigurationsBuildItem extends SimpleBuildItem {

private final Map<String, QuarkusControllerConfiguration> controllerConfigs;
private final Map<String, QuarkusControllerConfiguration<?>> controllerConfigs;

public ControllerConfigurationsBuildItem(List<QuarkusControllerConfiguration> controllerConfigs) {
this.controllerConfigs = new HashMap<>(controllerConfigs.size());

controllerConfigs.forEach(c -> this.controllerConfigs.put(c.getName(), c));
}

public Map<String, QuarkusControllerConfiguration> getControllerConfigs() {
public Map<String, QuarkusControllerConfiguration<?>> getControllerConfigs() {
return controllerConfigs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.jboss.jandex.*;
Expand Down Expand Up @@ -309,50 +310,38 @@ static QuarkusControllerConfiguration createConfiguration(
}

private static List<PolicyRule> extractAdditionalRBACRules(ClassInfo info) {
// if there are multiple annotations they should be found under an automatically generated AdditionalRBACRules
final var additionalRuleAnnotations = ConfigurationUtils.annotationValueOrDefault(
info.declaredAnnotation(ADDITIONAL_RBAC_RULES),
return extractRepeatableAnnotations(info, RBAC_RULE, ADDITIONAL_RBAC_RULES,
QuarkusControllerConfigurationBuildStep::extractRule);
}

private static <T> List<T> extractRepeatableAnnotations(ClassInfo info, DotName singleAnnotationName,
DotName repeatableHolderName, Function<AnnotationInstance, T> extractor) {
// if there are multiple annotations they should be found under an automatically generated repeatable holder annotation
final var additionalAnnotations = ConfigurationUtils.annotationValueOrDefault(
info.declaredAnnotation(repeatableHolderName),
"value",
AnnotationValue::asNestedArray,
() -> null);
List<PolicyRule> additionalRBACRules = Collections.emptyList();
if (additionalRuleAnnotations != null && additionalRuleAnnotations.length > 0) {
additionalRBACRules = new ArrayList<>(additionalRuleAnnotations.length);
for (AnnotationInstance ruleAnnotation : additionalRuleAnnotations) {
additionalRBACRules.add(extractRule(ruleAnnotation));
List<T> repeatables = Collections.emptyList();
if (additionalAnnotations != null && additionalAnnotations.length > 0) {
repeatables = new ArrayList<>(additionalAnnotations.length);
for (AnnotationInstance annotation : additionalAnnotations) {
repeatables.add(extractor.apply(annotation));
}
}

// if there's only one, it will be found under RBACRule annotation
final var rbacRuleAnnotation = info.declaredAnnotation(RBAC_RULE);
if (rbacRuleAnnotation != null) {
additionalRBACRules = List.of(extractRule(rbacRuleAnnotation));
final var singleAnnotation = info.declaredAnnotation(singleAnnotationName);
if (singleAnnotation != null) {
repeatables = List.of(extractor.apply(singleAnnotation));
}

return additionalRBACRules;
return repeatables;
}

private static List<RoleRef> extractAdditionalRBACRoleRefs(ClassInfo info) {
// if there are multiple annotations they should be found under an automatically generated AdditionalRBACRules
final var additionalRoleRefAnnotations = ConfigurationUtils.annotationValueOrDefault(
info.declaredAnnotation(ADDITIONAL_RBAC_ROLE_REFS),
"value",
AnnotationValue::asNestedArray,
() -> null);
List<RoleRef> additionalRBACRoleRefs = Collections.emptyList();
if (additionalRoleRefAnnotations != null && additionalRoleRefAnnotations.length > 0) {
additionalRBACRoleRefs = new ArrayList<>(additionalRoleRefAnnotations.length);
for (AnnotationInstance roleRefAnnotation : additionalRoleRefAnnotations) {
additionalRBACRoleRefs.add(extractRoleRef(roleRefAnnotation));
}
}

// if there's only one, it will be found under RBACRoleRef annotation
final var rbacRoleRefAnnotation = info.declaredAnnotation(RBAC_ROLE_REF);
if (rbacRoleRefAnnotation != null) {
additionalRBACRoleRefs = List.of(extractRoleRef(rbacRoleRefAnnotation));
}
return additionalRBACRoleRefs;
return extractRepeatableAnnotations(info, RBAC_ROLE_REF, ADDITIONAL_RBAC_ROLE_REFS,
QuarkusControllerConfigurationBuildStep::extractRoleRef);
}

private static PolicyRule extractRule(AnnotationInstance ruleAnnotation) {
Expand Down
Loading

0 comments on commit 885d178

Please sign in to comment.