Skip to content

Commit

Permalink
feat: provide per-bundle configuration via properties
Browse files Browse the repository at this point in the history
Fixes #903

Signed-off-by: Chris Laprun <claprun@redhat.com>
  • Loading branch information
metacosm committed Sep 6, 2024
1 parent d321a87 commit c3a1376
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
import io.quarkiverse.operatorsdk.annotations.CSVMetadata;
import io.quarkiverse.operatorsdk.annotations.SharedCSVMetadata;
import io.quarkiverse.operatorsdk.bundle.runtime.BundleConfiguration;
import io.quarkiverse.operatorsdk.bundle.runtime.BundleGenerationConfiguration;
import io.quarkiverse.operatorsdk.bundle.runtime.CSVMetadataHolder;
import io.quarkiverse.operatorsdk.common.*;
Expand Down Expand Up @@ -85,7 +86,11 @@ CSVMetadataBuildItem gatherCSVMetadata(KubernetesConfig kubernetesConfig,

final var defaultReplaces = bundleConfiguration.replaces().orElse(null);

final var sharedMetadataHolders = getSharedMetadataHolders(defaultName, defaultVersion, defaultReplaces, index);
final var bundleConfigs = bundleConfiguration.bundles();
final var defaultBundleConfig = bundleConfigs.get(BundleGenerationConfiguration.DEFAULT_BUNDLE_NAME);

final var sharedMetadataHolders = getSharedMetadataHolders(defaultName, defaultVersion, defaultReplaces,
defaultBundleConfig, index);
final var csvGroups = new HashMap<CSVMetadataHolder, List<ReconcilerAugmentedClassInfo>>();
ClassUtils.getKnownReconcilers(index, log)
.forEach(reconcilerInfo -> {
Expand Down Expand Up @@ -115,9 +120,19 @@ CSVMetadataBuildItem gatherCSVMetadata(KubernetesConfig kubernetesConfig,
+ "' but no SharedCSVMetadata implementation with that name exists. Please create a SharedCSVMetadata with that name to have one single source of truth and reference it via CSVMetadata annotations using that name on your reconcilers.");
}
}

// merge default bundle configuration with more specific one if they exist
var bundleConfig = bundleConfigs.get(sharedMetadataName);
if (bundleConfig != null) {
bundleConfig.mergeWithDefaults(defaultBundleConfig);
} else {
bundleConfig = defaultBundleConfig;
}

csvMetadata = createMetadataHolder(csvMetadataAnnotation,
new CSVMetadataHolder(sharedMetadataName, defaultVersion, defaultReplaces,
DEFAULT_PROVIDER_NAME, origin));
DEFAULT_PROVIDER_NAME, origin),
bundleConfig, origin);
if (DEFAULT_PROVIDER_NAME.equals(csvMetadata.providerName)) {
log.warnv(
"It is recommended that you provide a provider name provided for {0}: ''{1}'' was used as default value.",
Expand Down Expand Up @@ -265,7 +280,7 @@ void generateBundle(ApplicationInfoBuildItem configuration,
}

private Map<String, CSVMetadataHolder> getSharedMetadataHolders(String name, String version, String defaultReplaces,
IndexView index) {
BundleConfiguration defaultBundleConfig, IndexView index) {
CSVMetadataHolder csvMetadata = new CSVMetadataHolder(name, version, defaultReplaces, DEFAULT_PROVIDER_NAME,
"default");
final var sharedMetadataImpls = index.getAllKnownImplementors(SHARED_CSV_METADATA);
Expand All @@ -274,7 +289,7 @@ private Map<String, CSVMetadataHolder> getSharedMetadataHolders(String name, Str
final var csvMetadataAnn = sharedMetadataImpl.declaredAnnotation(CSV_METADATA);
if (csvMetadataAnn != null) {
final var origin = sharedMetadataImpl.name().toString();
final var metadataHolder = createMetadataHolder(csvMetadataAnn, csvMetadata, origin);
final var metadataHolder = createMetadataHolder(csvMetadataAnn, csvMetadata, defaultBundleConfig, origin);
final var existing = result.get(metadataHolder.bundleName);
if (existing != null) {
throw new IllegalStateException(
Expand Down Expand Up @@ -302,13 +317,8 @@ private static String getBundleName(AnnotationInstance csvMetadata, String defau
}
}

private CSVMetadataHolder createMetadataHolder(AnnotationInstance csvMetadata,
CSVMetadataHolder mh) {
return createMetadataHolder(csvMetadata, mh, mh.getOrigin());
}

private CSVMetadataHolder createMetadataHolder(AnnotationInstance csvMetadata, CSVMetadataHolder mh,
String origin) {
BundleConfiguration bundleConfig, String origin) {
if (csvMetadata == null) {
return mh;
}
Expand Down Expand Up @@ -357,6 +367,9 @@ private CSVMetadataHolder createMetadataHolder(AnnotationInstance csvMetadata, C
} else {
annotations = mh.annotations;
}
if (bundleConfig != null) {
annotations = CSVMetadataHolder.Annotations.override(annotations, bundleConfig.annotations());
}

final var maintainersField = csvMetadata.value("maintainers");
CSVMetadataHolder.Maintainer[] maintainers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.quarkiverse.operatorsdk.bundle.deployment.BundleGenerator.MANIFESTS;
import static io.quarkiverse.operatorsdk.bundle.deployment.BundleProcessor.CRD_DESCRIPTION;
import static io.quarkiverse.operatorsdk.bundle.deployment.BundleProcessor.CRD_DISPLAY_NAME;
import static io.quarkiverse.operatorsdk.bundle.runtime.BundleConfiguration.*;
import static java.util.Comparator.comparing;

import java.io.FileInputStream;
Expand Down Expand Up @@ -85,13 +86,13 @@ public CsvManifestsBuilder(CSVMetadataHolder metadata, BuildTimeOperatorConfigur

final var metadataBuilder = csvBuilder.withNewMetadata().withName(metadata.csvName);
if (metadata.annotations != null) {
metadataBuilder.addToAnnotations("olm.skipRange", metadata.annotations.skipRange);
metadataBuilder.addToAnnotations("containerImage", metadata.annotations.containerImage);
metadataBuilder.addToAnnotations("repository", metadata.annotations.repository);
metadataBuilder.addToAnnotations("capabilities", metadata.annotations.capabilities);
metadataBuilder.addToAnnotations("categories", metadata.annotations.categories);
metadataBuilder.addToAnnotations("certified", String.valueOf(metadata.annotations.certified));
metadataBuilder.addToAnnotations("alm-examples", metadata.annotations.almExamples);
metadataBuilder.addToAnnotations(OLM_SKIP_RANGE_ANNOTATION, metadata.annotations.skipRange);
metadataBuilder.addToAnnotations(CONTAINER_IMAGE_ANNOTATION, metadata.annotations.containerImage);
metadataBuilder.addToAnnotations(REPOSITORY_ANNOTATION, metadata.annotations.repository);
metadataBuilder.addToAnnotations(CAPABILITIES_ANNOTATION, metadata.annotations.capabilities);
metadataBuilder.addToAnnotations(CATEGORIES_ANNOTATION, metadata.annotations.categories);
metadataBuilder.addToAnnotations(CERTIFIED_ANNOTATION, String.valueOf(metadata.annotations.certified));
metadataBuilder.addToAnnotations(ALM_EXAMPLES_ANNOTATION, metadata.annotations.almExamples);
if (metadata.annotations.others != null) {
metadata.annotations.others.forEach(metadataBuilder::addToAnnotations);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.rbac.PolicyRuleBuilder;
import io.quarkiverse.operatorsdk.bundle.runtime.BundleConfiguration;
import io.quarkiverse.operatorsdk.bundle.runtime.BundleGenerationConfiguration;
import io.quarkiverse.operatorsdk.bundle.sources.*;
import io.quarkiverse.operatorsdk.common.ConfigurationUtils;
import io.quarkus.test.ProdBuildResults;
Expand All @@ -21,7 +23,13 @@
public class MultipleOperatorsBundleTest {

private static final String VERSION = "test-version";
public static final String BUNDLE_PACKAGE = "olm-package";
private static final String BUNDLE_PACKAGE = "olm-package";
private static final String OVERRIDEN_REPO_ANNOTATION = "overridden-repo-annotation";
private static final String DEFAULT_ANNOTATION_NAME = "default-annotation-name";
private static final String DEFAULT_ANNOTATION_VALUE = "default-annotation-value";
private static final String OVERRIDDEN_DEFAULT_ANNOTATION_NAME = "overridden-annotation-name";
private static final String OVERRIDEN_DEFAULT_ANNOTATION_VALUE = "initial-annotation-value";
private static final String OVERRIDEN_BY_THIRD_ANNOTATION_VALUE = "overridden-by-third-annotation-value";

@RegisterExtension
static final QuarkusProdModeTest config = new QuarkusProdModeTest()
Expand All @@ -33,7 +41,19 @@ public class MultipleOperatorsBundleTest {
ExternalDependentResource.class, PodDependentResource.class))
.overrideConfigKey("quarkus.operator-sdk.crd.generate-all", "true")
.overrideConfigKey("quarkus.operator-sdk.bundle.replaces", FirstReconciler.REPLACES)
.overrideConfigKey("quarkus.operator-sdk.bundle.package-name", BUNDLE_PACKAGE);
.overrideConfigKey("quarkus.operator-sdk.bundle.package-name", BUNDLE_PACKAGE)
.overrideConfigKey("quarkus.operator-sdk.bundle.bundles." + ThirdReconciler.BUNDLE_NAME + ".annotations."
+ BundleConfiguration.REPOSITORY_ANNOTATION, OVERRIDEN_REPO_ANNOTATION)
.overrideConfigKey(
"quarkus.operator-sdk.bundle.bundles." + BundleGenerationConfiguration.DEFAULT_BUNDLE_NAME + ".annotations."
+ DEFAULT_ANNOTATION_NAME,
DEFAULT_ANNOTATION_VALUE)
.overrideConfigKey(
"quarkus.operator-sdk.bundle.bundles." + BundleGenerationConfiguration.DEFAULT_BUNDLE_NAME + ".annotations."
+ OVERRIDDEN_DEFAULT_ANNOTATION_NAME,
OVERRIDEN_DEFAULT_ANNOTATION_VALUE)
.overrideConfigKey("quarkus.operator-sdk.bundle.bundles." + ThirdReconciler.BUNDLE_NAME + ".annotations."
+ OVERRIDDEN_DEFAULT_ANNOTATION_NAME, OVERRIDEN_BY_THIRD_ANNOTATION_VALUE);

@SuppressWarnings("unused")
@ProdBuildResults
Expand All @@ -45,13 +65,19 @@ public void shouldWriteBundleForTheOperators() throws IOException {
checkBundleFor(bundle, "first-operator", First.class);
// check that version is properly overridden
var csv = getCSVFor(bundle, "first-operator");
var metadata = csv.getMetadata();
var annotations = metadata.getAnnotations();
assertEquals(OVERRIDEN_DEFAULT_ANNOTATION_VALUE, annotations.get(OVERRIDDEN_DEFAULT_ANNOTATION_NAME));
assertEquals(FirstReconciler.VERSION, csv.getSpec().getVersion());
assertEquals(FirstReconciler.REPLACES, csv.getSpec().getReplaces());
var bundleMeta = getAnnotationsFor(bundle, "first-operator");
assertEquals(BUNDLE_PACKAGE, bundleMeta.getAnnotations().get("operators.operatorframework.io.bundle.package.v1"));

checkBundleFor(bundle, "second-operator", Second.class);
csv = getCSVFor(bundle, "second-operator");
metadata = csv.getMetadata();
annotations = metadata.getAnnotations();
assertEquals(OVERRIDEN_DEFAULT_ANNOTATION_VALUE, annotations.get(OVERRIDDEN_DEFAULT_ANNOTATION_NAME));
final var permissions = csv.getSpec().getInstall().getSpec().getPermissions();
assertEquals(1, permissions.size());
assertTrue(permissions.get(0).getRules().contains(new PolicyRuleBuilder()
Expand Down Expand Up @@ -86,10 +112,13 @@ public void shouldWriteBundleForTheOperators() throws IOException {
assertEquals(HasMetadata.getKind(Pod.class), podGVK.getKind());
assertEquals(HasMetadata.getVersion(Pod.class), podGVK.getVersion());
assertEquals("1.0.0", spec.getReplaces());
final var metadata = csv.getMetadata();
assertEquals(">=1.0.0 <1.0.3", metadata.getAnnotations().get("olm.skipRange"));
assertEquals("Test", metadata.getAnnotations().get("capabilities"));
assertEquals("bar", metadata.getAnnotations().get("foo"));
metadata = csv.getMetadata();
annotations = metadata.getAnnotations();
assertEquals(">=1.0.0 <1.0.3", annotations.get(BundleConfiguration.OLM_SKIP_RANGE_ANNOTATION));
assertEquals("Test", annotations.get(BundleConfiguration.CAPABILITIES_ANNOTATION));
assertEquals(OVERRIDEN_REPO_ANNOTATION, annotations.get(BundleConfiguration.REPOSITORY_ANNOTATION));
assertEquals(OVERRIDEN_BY_THIRD_ANNOTATION_VALUE, annotations.get(OVERRIDDEN_DEFAULT_ANNOTATION_NAME));
assertEquals("bar", annotations.get("foo"));
// version should be the default application's version since it's not provided for this reconciler
assertEquals(VERSION, spec.getVersion());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import io.quarkiverse.operatorsdk.annotations.CSVMetadata.Annotations.Annotation;
import io.quarkiverse.operatorsdk.annotations.CSVMetadata.RequiredCRD;

@CSVMetadata(bundleName = "third-operator", requiredCRDs = @RequiredCRD(kind = SecondExternal.KIND, name = "externalagains."
+ SecondExternal.GROUP, version = SecondExternal.VERSION), replaces = "1.0.0", annotations = @Annotations(skipRange = ">=1.0.0 <1.0.3", capabilities = "Test", others = @Annotation(name = "foo", value = "bar")))
@CSVMetadata(bundleName = ThirdReconciler.BUNDLE_NAME, requiredCRDs = @RequiredCRD(kind = SecondExternal.KIND, name = "externalagains."
+ SecondExternal.GROUP, version = SecondExternal.VERSION), replaces = "1.0.0", annotations = @Annotations(skipRange = ">=1.0.0 <1.0.3", capabilities = "Test", repository = "should be overridden by property", others = @Annotation(name = "foo", value = "bar")))
@ControllerConfiguration(name = ThirdReconciler.NAME, dependents = {
@Dependent(type = ExternalDependentResource.class),
@Dependent(name = "pod1", type = PodDependentResource.class),
Expand All @@ -20,6 +20,7 @@
public class ThirdReconciler implements Reconciler<Third> {

public static final String NAME = "third";
public static final String BUNDLE_NAME = "third-operator";

@Override
public UpdateControl<Third> reconcile(Third third, Context<Third> context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkiverse.operatorsdk.bundle.runtime;

import java.util.Map;

import io.quarkus.runtime.annotations.ConfigGroup;

@ConfigGroup
public interface BundleConfiguration {
String OLM_SKIP_RANGE_ANNOTATION = "olm.skipRange";
String CONTAINER_IMAGE_ANNOTATION = "containerImage";
String REPOSITORY_ANNOTATION = "repository";
String CAPABILITIES_ANNOTATION = "capabilities";
String CATEGORIES_ANNOTATION = "categories";
String CERTIFIED_ANNOTATION = "certified";
String ALM_EXAMPLES_ANNOTATION = "alm-examples";

/**
* The bundle's annotations (as found in the CSV metadata)
*/
Map<String, String> annotations();

default void mergeWithDefaults(BundleConfiguration defaults) {
final var annotations = annotations();
if (annotations != null) {
annotations.keySet().forEach(key -> annotations.computeIfAbsent(key, k -> defaults.annotations().get(k)));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkiverse.operatorsdk.bundle.runtime;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.quarkiverse.operatorsdk.annotations.CSVMetadata;
Expand All @@ -11,6 +12,8 @@
@ConfigMapping(prefix = "quarkus.operator-sdk.bundle")
@ConfigRoot
public interface BundleGenerationConfiguration {
String DEFAULT_BUNDLE_NAME = "QOSDK_DEFAULT";

/**
* Whether the extension should generate the Operator bundle.
*/
Expand Down Expand Up @@ -47,4 +50,12 @@ public interface BundleGenerationConfiguration {
*/
Optional<String> version();

/**
* Per-bundle configuration. Note that you can also provide default values that will be applied to all your bundles by
* adding configuration using the {@link #DEFAULT_BUNDLE_NAME} key. In that case, any configuration found under that key
* will be used as default for every bundle unless otherwise overridden.
*
* @since 6.8.0
*/
Map<String, BundleConfiguration> bundles();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package io.quarkiverse.operatorsdk.bundle.runtime;

import static io.quarkiverse.operatorsdk.bundle.runtime.BundleConfiguration.*;
import static io.quarkiverse.operatorsdk.bundle.runtime.BundleConfiguration.CONTAINER_IMAGE_ANNOTATION;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -45,6 +49,7 @@ public static class Annotations {
public final String almExamples;
public final String skipRange;
public final Map<String, String> others;
private final static Annotations EMPTY = new Annotations(null, null, null, null, false, null, null, Map.of());

public Annotations(String containerImage, String repository, String capabilities, String categories, boolean certified,
String almExamples, String skipRange, Map<String, String> others) {
Expand All @@ -57,6 +62,35 @@ public Annotations(String containerImage, String repository, String capabilities
this.skipRange = skipRange;
this.others = Collections.unmodifiableMap(others);
}

public static Annotations override(Annotations initial, Map<String, String> overrides) {
if (initial == null) {
initial = EMPTY;
}
final var copy = new HashMap<>(overrides);

return new Annotations(
getOrDefault(copy, CONTAINER_IMAGE_ANNOTATION, initial.containerImage),
getOrDefault(copy, REPOSITORY_ANNOTATION, initial.repository),
getOrDefault(copy, CAPABILITIES_ANNOTATION, initial.capabilities),
getOrDefault(copy, CATEGORIES_ANNOTATION, initial.categories),
Boolean.parseBoolean(getOrDefault(copy, CERTIFIED_ANNOTATION, "false")),
getOrDefault(copy, ALM_EXAMPLES_ANNOTATION, initial.almExamples),
getOrDefault(copy, OLM_SKIP_RANGE_ANNOTATION, initial.skipRange),
additionalAnnotationOverrides(initial.others, copy));
}

private static Map<String, String> additionalAnnotationOverrides(Map<String, String> others,
HashMap<String, String> overrides) {
final var initial = new HashMap<>(others);
initial.putAll(overrides);
return initial;
}

private static String getOrDefault(Map<String, String> overrides, String annotation, String initialValue) {
final var removed = overrides.remove(annotation);
return removed != null ? removed : initialValue;
}
}

public static class Maintainer {
Expand Down

0 comments on commit c3a1376

Please sign in to comment.