From 143748cac66ea34034fb8ab5adbfdf3b099ff84c Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 2 Dec 2025 10:46:23 +0000 Subject: [PATCH] Add configuration to control ServiceAccount generation for Knative image pull secrets Relates to: https://github.com/quarkusio/quarkus/issues/32545 --- ...magePullSecretToRevisionSpecDecorator.java | 20 ++++++ .../kubernetes/deployment/KnativeConfig.java | 10 +++ .../deployment/KnativeProcessor.java | 20 ++++-- .../KnativeWithImagePullSecretsTest.java | 64 +++++++++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddImagePullSecretToRevisionSpecDecorator.java create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretsTest.java diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddImagePullSecretToRevisionSpecDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddImagePullSecretToRevisionSpecDecorator.java new file mode 100644 index 0000000000000..0b9ed8bc4551f --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddImagePullSecretToRevisionSpecDecorator.java @@ -0,0 +1,20 @@ +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.decorator.NamedResourceDecorator; +import io.fabric8.knative.serving.v1.RevisionSpecFluent; +import io.fabric8.kubernetes.api.model.ObjectMeta; + +public class AddImagePullSecretToRevisionSpecDecorator extends NamedResourceDecorator> { + + private final String imagePullSecret; + + public AddImagePullSecretToRevisionSpecDecorator(String name, String imagePullSecret) { + super(name); + this.imagePullSecret = imagePullSecret; + } + + @Override + public void andThenVisit(RevisionSpecFluent revisionSpec, ObjectMeta resourceMeta) { + revisionSpec.addNewImagePullSecret(imagePullSecret); + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeConfig.java index 2e1fef316bf76..4c485a1dd6695 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeConfig.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeConfig.java @@ -79,4 +79,14 @@ default String targetPlatformName() { */ @WithDefault("CreateOrUpdate") DeployStrategy deployStrategy(); + + /** + * Whether to add image pull secrets to a generated ServiceAccount. + * When set to true (default), image pull secrets are added to a ServiceAccount resource. + * When set to false, image pull secrets are added directly to the pod spec. + * Setting this to false is useful when deploying to environments like IBM Code Engine where users do not have + * permission to create ServiceAccount resources. + */ + @WithDefault("true") + boolean addImagePullSecretsToServiceAccount(); } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java index a19726c00c5da..6dde40fa4dcb6 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java @@ -301,12 +301,20 @@ public List createDecorators(ApplicationInfoBuildItem applic //Handle Image Pull Secrets config.imagePullSecrets().ifPresent(imagePullSecrets -> { - String serviceAccountName = config.serviceAccount().orElse(name); - result.add(new DecoratorBuildItem(KNATIVE, new AddServiceAccountResourceDecorator(name))); - result.add( - new DecoratorBuildItem(KNATIVE, new ApplyServiceAccountToRevisionSpecDecorator(name, serviceAccountName))); - result.add(new DecoratorBuildItem(KNATIVE, - new AddImagePullSecretToServiceAccountDecorator(serviceAccountName, imagePullSecrets))); + if (config.addImagePullSecretsToServiceAccount()) { + String serviceAccountName = config.serviceAccount().orElse(name); + result.add(new DecoratorBuildItem(KNATIVE, new AddServiceAccountResourceDecorator(name))); + result.add( + new DecoratorBuildItem(KNATIVE, + new ApplyServiceAccountToRevisionSpecDecorator(name, serviceAccountName))); + result.add(new DecoratorBuildItem(KNATIVE, + new AddImagePullSecretToServiceAccountDecorator(serviceAccountName, imagePullSecrets))); + } else { + for (String imagePullSecret : imagePullSecrets) { + result.add(new DecoratorBuildItem(KNATIVE, + new AddImagePullSecretToRevisionSpecDecorator(name, imagePullSecret))); + } + } }); return result; diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretsTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretsTest.java new file mode 100644 index 0000000000000..e9471d1add622 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretsTest.java @@ -0,0 +1,64 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.knative.serving.v1.Service; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.quarkus.builder.Version; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KnativeWithImagePullSecretsTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) + .setApplicationName("knative-with-image-pull-secrets") + .setApplicationVersion("0.1-SNAPSHOT") + .overrideConfigKey("quarkus.kubernetes.deployment-target", "knative") + .overrideConfigKey("quarkus.knative.image-pull-secrets", "my-secret") + .overrideConfigKey("quarkus.knative.add-image-pull-secrets-to-service-account", "false") + .setForcedDependencies(List.of(Dependency.of("io.quarkus", "quarkus-kubernetes", Version.getVersion()))); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("knative.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("knative.yml")); + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("knative.yml")); + + assertThat(kubernetesList).filteredOn(i -> "Service".equals(i.getKind())).singleElement().satisfies(i -> { + assertThat(i).isInstanceOfSatisfying(Service.class, s -> { + assertThat(s.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo("knative-with-image-pull-secrets"); + }); + + assertThat(s.getSpec()).satisfies(spec -> { + assertThat(spec.getTemplate()).satisfies(template -> { + assertThat(template.getSpec()).satisfies(revisionSpec -> { + assertThat(revisionSpec.getImagePullSecrets()).hasSize(1); + assertThat(revisionSpec.getImagePullSecrets().get(0).getName()).isEqualTo("my-secret"); + }); + }); + }); + }); + }); + + // Ensure no ServiceAccount was generated + assertThat(kubernetesList).filteredOn(i -> "ServiceAccount".equals(i.getKind())).isEmpty(); + } +}