diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index a8c0e298ea943a..a55a967d0cf084 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -2628,7 +2628,7 @@
quarkus-minikube
${project.version}
-
+
io.quarkus
quarkus-minikube-deployment
${project.version}
@@ -2642,6 +2642,16 @@
io.quarkus
quarkus-kind-deployment
${project.version}
+
+
+ io.quarkus
+ quarkus-tekton
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-tekton-deployment
+ ${project.version}
io.quarkus
@@ -3161,6 +3171,12 @@
${dekorate.version}
noapt
+
+ io.dekorate
+ tekton-annotations
+ ${dekorate.version}
+ noapt
+
io.dekorate
helm-annotations
diff --git a/extensions/kubernetes/pom.xml b/extensions/kubernetes/pom.xml
index 0928b952ac209a..3286f2d1d8ec72 100644
--- a/extensions/kubernetes/pom.xml
+++ b/extensions/kubernetes/pom.xml
@@ -19,5 +19,6 @@
minikube
kind
spi
+ tekton
diff --git a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesAdditionalResourceBuildItem.java b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesAdditionalResourceBuildItem.java
new file mode 100644
index 00000000000000..a99a12675f7b0e
--- /dev/null
+++ b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesAdditionalResourceBuildItem.java
@@ -0,0 +1,23 @@
+
+package io.quarkus.kubernetes.spi;
+
+import java.util.Collection;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+public final class KubernetesAdditionalResourceBuildItem extends MultiBuildItem {
+
+ private final String name;
+
+ public KubernetesAdditionalResourceBuildItem(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public static boolean hasItem(String name, Collection items) {
+ return items.stream().filter(i -> name.equals(i.name)).findAny().isPresent();
+ }
+}
diff --git a/extensions/kubernetes/tekton/deployment/pom.xml b/extensions/kubernetes/tekton/deployment/pom.xml
new file mode 100644
index 00000000000000..3de2d0c0081d9f
--- /dev/null
+++ b/extensions/kubernetes/tekton/deployment/pom.xml
@@ -0,0 +1,57 @@
+
+
+
+ io.quarkus
+ quarkus-tekton-parent
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-tekton-deployment
+ Quarkus - Kubernetes - Tekton - Deployment
+
+
+
+ io.dekorate
+ tekton-annotations
+ noapt
+
+
+ io.sundr
+ *
+
+
+ com.sun
+ tools
+
+
+
+
+ io.quarkus
+ quarkus-tekton
+
+
+ io.quarkus
+ quarkus-kubernetes-deployment
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/ApplyParamToTaskDecorator.java b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/ApplyParamToTaskDecorator.java
new file mode 100644
index 00000000000000..cbf7e990f3593f
--- /dev/null
+++ b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/ApplyParamToTaskDecorator.java
@@ -0,0 +1,39 @@
+package io.quarkus.tekton.deployment;
+
+import io.dekorate.deps.tekton.pipeline.v1beta1.TaskSpecFluent;
+import io.dekorate.kubernetes.decorator.Decorator;
+import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
+import io.dekorate.tekton.decorator.AddParamToTaskDecorator;
+import io.dekorate.tekton.decorator.NamedTaskDecorator;
+import io.dekorate.tekton.decorator.TaskProvidingDecorator;
+
+/**
+ * Adds a param to a task.
+ * Similar to {@link AddParamToTaskDecorator} but is meant to be executed at a later point, so it can replace values added by
+ * it.
+ */
+public class ApplyParamToTaskDecorator extends NamedTaskDecorator {
+
+ private final String name;
+ private final String description;
+ private final String defaultValue;
+
+ public ApplyParamToTaskDecorator(String taskName, String name, String description, String defaultValue) {
+ super(taskName);
+ this.name = name;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public void andThenVisit(TaskSpecFluent> taskSpec) {
+ taskSpec.removeMatchingFromParams(p -> name.equals(p.getName()));
+ taskSpec.addNewParam().withName(name).withDescription(description).withNewDefault().withStringVal(defaultValue)
+ .endDefault().endParam();
+ }
+
+ @Override
+ public Class extends Decorator>[] after() {
+ return new Class[] { ResourceProvidingDecorator.class, TaskProvidingDecorator.class, AddParamToTaskDecorator.class };
+ }
+}
diff --git a/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonConfig.java b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonConfig.java
new file mode 100644
index 00000000000000..684b54a4add574
--- /dev/null
+++ b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonConfig.java
@@ -0,0 +1,190 @@
+package io.quarkus.tekton.deployment;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import io.quarkus.kubernetes.deployment.PvcVolumeConfig;
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot
+public class TektonConfig {
+
+ public static final String DEFAULT_DEPLOYER_IMAGE = "lachlanevenson/k8s-kubectl:v1.18.0";
+
+ public static final String DEFAULT_JVM_DOCKERFILE = "src/main/docker/Dockerfile.jvm";
+ public static final String DEFAULT_NATIVE_DOCKERFILE = "src/main/docker/Dockerfile.native";
+
+ /**
+ * Feature flag for tekton
+ */
+ @ConfigItem(defaultValue = "true")
+ boolean enabled;
+
+ /**
+ * The name of the group this component belongs too
+ */
+ @ConfigItem
+ Optional partOf;
+
+ /**
+ * The name of the application. This value will be used for naming Kubernetes
+ * resources like: - Deployment - Service and so on ...
+ */
+ @ConfigItem(defaultValue = "${quarkus.container-image.name}")
+ Optional name;
+
+ /**
+ * The version of the application.
+ */
+ @ConfigItem(defaultValue = "${quarkus.container-image.tag}")
+ Optional version;
+
+ /**
+ * The namespace the generated resources should belong to. If not value is set,
+ * then the 'namespace' field will not be added to the 'metadata' section of the
+ * generated manifests. This in turn means that when the manifests are applied
+ * to a cluster, the namespace will be resolved from the current Kubernetes
+ * context (see
+ * https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context
+ * for more details).
+ */
+ @ConfigItem
+ Optional namespace;
+
+ /**
+ * Custom labels to add to all resources
+ */
+ @ConfigItem
+ Map labels;
+
+ /**
+ * Custom annotations to add to all resources
+ */
+ @ConfigItem
+ Map annotations;
+
+ /**
+ * The name of an external git pipeline resource.
+ */
+ @ConfigItem
+ Optional externalGitPipelineResource;
+
+ /**
+ * The name of the source workspace.
+ */
+ @ConfigItem(defaultValue = "source")
+ Optional sourceWorkspace;
+
+ /**
+ * The name of an external PVC to be used for the source workspace.
+ */
+ @ConfigItem
+ Optional externalSourceWorkspaceClaim;
+
+ /**
+ * The persistent volume claim configuration for the source workspace. The
+ * option only makes sense when the PVC is going to be generated (no external
+ * pvc specified).
+ */
+ @ConfigItem
+ Optional sourceWorkspaceClaim;
+
+ /**
+ * The name of workspace to use as a maven artifact repository.
+ */
+ @ConfigItem
+ Optional m2Workspace;
+
+ /**
+ * The name of an external PVC to be used for the m2 artifact repository.
+ */
+ @ConfigItem
+ Optional externalM2WorkspaceClaim;
+
+ /**
+ * The persistent volume claim configuration for the artifact repository. The
+ * option only makes sense when the PVC is going to be generated (no external
+ * pvc specified).
+ */
+ @ConfigItem
+ Optional m2WorkspaceClaim;
+
+ /**
+ * The builder image to use.
+ */
+ @ConfigItem
+ Optional builderImage;
+
+ /**
+ * The builder command to use.
+ */
+ @ConfigItem
+ Optional builderCommand;
+
+ /**
+ * The builder command arguments to use.
+ */
+ @ConfigItem
+ Optional> builderArguments;
+
+ /**
+ * The docker image to be used for the deployment task. Such image needs to have
+ * kubectl available.
+ */
+ @ConfigItem(defaultValue = DEFAULT_DEPLOYER_IMAGE)
+ Optional deployerImage;
+
+ /**
+ * The service account to use for the image pushing tasks. An existing or a
+ * generated service account can be used. If no existing service account is
+ * provided one will be generated based on the context.
+ */
+ @ConfigItem
+ Optional imagePushServiceAccount;
+
+ /**
+ * The secret to use when generating an image push service account. When no
+ * existing service account is provided, one will be generated. The generated
+ * service account may or may not use an existing secret.
+ */
+ @ConfigItem
+ Optional imagePushSecret;
+
+ /**
+ * Wether to upload the local `.docker/config.json` to automatically create the
+ * secret.
+ */
+ @ConfigItem
+ boolean useLocalDockerConfigJson;
+
+ /**
+ * The username to use for generating image builder secrets.
+ */
+ @ConfigItem(defaultValue = "docker.io")
+ Optional registry;
+
+ /**
+ * The username to use for generating image builder secrets.
+ */
+ Optional registryUsername;
+
+ /**
+ * The password to use for generating image builder secrets.
+ */
+ @ConfigItem
+ Optional registryPassword;
+
+ /**
+ * The default Dockerfile to use for jvm builds
+ */
+ @ConfigItem(defaultValue = DEFAULT_JVM_DOCKERFILE)
+ public String jvmDockerfile;
+
+ /**
+ * The default Dockerfile to use for native builds
+ */
+ @ConfigItem(defaultValue = DEFAULT_NATIVE_DOCKERFILE)
+ public String nativeDockerfile;
+}
diff --git a/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonEnabled.java b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonEnabled.java
new file mode 100644
index 00000000000000..38123cc173f97d
--- /dev/null
+++ b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonEnabled.java
@@ -0,0 +1,18 @@
+
+package io.quarkus.tekton.deployment;
+
+import java.util.function.BooleanSupplier;
+
+public class TektonEnabled implements BooleanSupplier {
+
+ private final TektonConfig config;
+
+ public TektonEnabled(TektonConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ return config.enabled;
+ }
+}
diff --git a/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonProcessor.java b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonProcessor.java
new file mode 100644
index 00000000000000..c17decb069107b
--- /dev/null
+++ b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonProcessor.java
@@ -0,0 +1,89 @@
+package io.quarkus.tekton.deployment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.jboss.logging.Logger;
+
+import io.dekorate.BuildImage;
+import io.dekorate.tekton.configurator.ApplyTektonBuilderInfoConfigurator;
+import io.dekorate.tekton.configurator.UseLocaDockerConfigJsonConfigurator;
+import io.quarkus.deployment.IsNormal;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
+import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
+import io.quarkus.deployment.pkg.steps.NativeBuild;
+import io.quarkus.kubernetes.spi.ConfiguratorBuildItem;
+import io.quarkus.kubernetes.spi.DecoratorBuildItem;
+import io.quarkus.kubernetes.spi.KubernetesAdditionalResourceBuildItem;
+
+public class TektonProcessor {
+
+ private static final Logger log = Logger.getLogger(TektonProcessor.class);
+ private static final String TEKTON_TASK = "tekton-task";
+
+ @BuildStep
+ public void checkVanillaKubernetes(BuildProducer additionalResources) {
+ additionalResources.produce(new KubernetesAdditionalResourceBuildItem("tekton-task"));
+ additionalResources.produce(new KubernetesAdditionalResourceBuildItem("tekton-task-run"));
+ }
+
+ @BuildStep(onlyIf = { IsNormal.class, TektonEnabled.class }, onlyIfNot = NativeBuild.class)
+ public List createJvmConfigurators(ApplicationInfoBuildItem applicationInfo,
+ OutputTargetBuildItem outputTarget, TektonConfig config) {
+ List result = new ArrayList<>();
+ String task = TektonUtil.monolithTaskName(config);
+ result.add(new ConfiguratorBuildItem(new UseLocaDockerConfigJsonConfigurator(config.useLocalDockerConfigJson)));
+
+ Optional buildImage = TektonUtil.getBuildImage(outputTarget.getOutputDirectory().toFile());
+ String builderImage = config.builderImage.orElse(buildImage.map(BuildImage::getImage).orElse(null));
+ String builderCommand = config.builderCommand.orElse(buildImage.map(BuildImage::getCommand).orElse(null));
+ List builderArguments = config.builderArguments
+ .orElse(Arrays.asList(buildImage.map(BuildImage::getArguments).orElse(new String[0])));
+ result.add(new ConfiguratorBuildItem(new ApplyTektonBuilderInfoConfigurator(builderImage, builderCommand,
+ builderArguments.toArray(new String[builderArguments.size()]))));
+
+ return result;
+ }
+
+ @BuildStep(onlyIf = { IsNormal.class, TektonEnabled.class, NativeBuild.class })
+ public List createNativeConfigurators(ApplicationInfoBuildItem applicationInfo,
+ OutputTargetBuildItem outputTarget, TektonConfig config) {
+ List result = new ArrayList<>();
+ String task = TektonUtil.monolithTaskName(config);
+ result.add(new ConfiguratorBuildItem(new UseLocaDockerConfigJsonConfigurator(config.useLocalDockerConfigJson)));
+
+ Optional buildImage = TektonUtil.getBuildImage(outputTarget.getOutputDirectory().toFile());
+ String builderImage = config.builderImage.orElse(buildImage.map(BuildImage::getImage).orElse(null));
+ String builderCommand = config.builderCommand.orElse(buildImage.map(BuildImage::getCommand).orElse(null));
+ List builderArguments = config.builderArguments
+ .orElse(Arrays.asList(buildImage.map(BuildImage::getArguments).orElse(new String[0])));
+ result.add(new ConfiguratorBuildItem(new ApplyTektonBuilderInfoConfigurator(builderImage, builderCommand,
+ builderArguments.toArray(new String[builderArguments.size()]))));
+
+ return result;
+ }
+
+ @BuildStep(onlyIf = { IsNormal.class, TektonEnabled.class }, onlyIfNot = NativeBuild.class)
+ public List createJvmTaskDecorators(ApplicationInfoBuildItem applicationInfo,
+ OutputTargetBuildItem outputTarget, TektonConfig config) {
+ List result = new ArrayList<>();
+ String task = TektonUtil.monolithTaskName(config);
+ result.add(new DecoratorBuildItem(TEKTON_TASK,
+ new ApplyParamToTaskDecorator(task, "pathToDockerfile", "Path to Dockerfile", config.jvmDockerfile)));
+ return result;
+ }
+
+ @BuildStep(onlyIf = { IsNormal.class, TektonEnabled.class, NativeBuild.class })
+ public List createNativeTaskDecorators(ApplicationInfoBuildItem applicationInfo,
+ OutputTargetBuildItem outputTarget, TektonConfig config) {
+ List result = new ArrayList<>();
+ String task = TektonUtil.monolithTaskName(config);
+ result.add(new DecoratorBuildItem(TEKTON_TASK,
+ new ApplyParamToTaskDecorator(task, "pathToDockerfile", "Path to Dockerfile", config.nativeDockerfile)));
+ return result;
+ }
+}
diff --git a/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonUtil.java b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonUtil.java
new file mode 100644
index 00000000000000..c49fa09f486e68
--- /dev/null
+++ b/extensions/kubernetes/tekton/deployment/src/main/java/io/quarkus/tekton/deployment/TektonUtil.java
@@ -0,0 +1,57 @@
+
+package io.quarkus.tekton.deployment;
+
+import java.io.File;
+import java.util.Optional;
+
+import io.dekorate.BuildImage;
+import io.dekorate.project.BuildInfo;
+import io.dekorate.project.FileProjectFactory;
+import io.dekorate.project.Project;
+import io.dekorate.utils.Jvm;
+
+public class TektonUtil {
+
+ private static final String GIT = "git";
+ private static final String REVISION = "revision";
+ private static final String URL = "url";
+ private static final String IMAGE = "image";
+ private static final String BUILD = "build";
+ private static final String DEPLOY = "deploy";
+ private static final String WORKSPACE = "workspace";
+ private static final String RUN = "run";
+ private static final String NOW = "now";
+ private static final String JAVA = "java";
+ private static final String DASH = "-";
+ private static final String AND = "and";
+
+ public static Optional getBuildImage(File projectDir) {
+ Project project = FileProjectFactory.create(projectDir);
+ BuildInfo buildInfo = project.getBuildInfo();
+ return BuildImage.find(buildInfo.getBuildTool(), buildInfo.getBuildToolVersion(), Jvm.getVersion(), null);
+ }
+
+ public static final String outputImageResourceName(TektonConfig config) {
+ return config.name.get() + DASH + IMAGE;
+ }
+
+ public static final String imageBuildTaskName(TektonConfig config) {
+ return config.name.get() + DASH + IMAGE + DASH + BUILD;
+ }
+
+ public static final String javaBuildTaskName(TektonConfig config) {
+ return config.name.get() + DASH + JAVA + DASH + BUILD;
+ }
+
+ public static final String javaBuildStepName(TektonConfig config) {
+ return JAVA + DASH + BUILD;
+ }
+
+ public static final String deployTaskName(TektonConfig config) {
+ return config.name.get() + DASH + DEPLOY;
+ }
+
+ public static final String monolithTaskName(TektonConfig config) {
+ return config.name.get() + DASH + BUILD + DASH + AND + DASH + DEPLOY;
+ }
+}
diff --git a/extensions/kubernetes/tekton/pom.xml b/extensions/kubernetes/tekton/pom.xml
new file mode 100644
index 00000000000000..1f5ad6b79946ca
--- /dev/null
+++ b/extensions/kubernetes/tekton/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-kubernetes-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-tekton-parent
+ Quarkus - Kubernetes - Tekton
+ pom
+
+ deployment
+ runtime
+
+
diff --git a/extensions/kubernetes/tekton/runtime/pom.xml b/extensions/kubernetes/tekton/runtime/pom.xml
new file mode 100644
index 00000000000000..e653024dbb7685
--- /dev/null
+++ b/extensions/kubernetes/tekton/runtime/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-tekton-parent
+ 999-SNAPSHOT
+
+
+ quarkus-tekton
+ Quarkus - Kubernetes - Tekton - Runtime
+ Generate Tekton resources from annotations
+
+
+
+
+ io.quarkus
+ quarkus-kubernetes-client-internal
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/kubernetes/tekton/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/kubernetes/tekton/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 00000000000000..b423bb6baf74ae
--- /dev/null
+++ b/extensions/kubernetes/tekton/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,10 @@
+---
+name: "Tekton"
+metadata:
+ keywords:
+ - "kubernetes"
+ - "Tekton"
+ guide: "https://quarkus.io/guides/kubernetes"
+ categories:
+ - "cloud"
+ status: "stable"
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java
index 338948577a38ae..95d38f35444308 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java
@@ -54,6 +54,7 @@
import io.quarkus.kubernetes.spi.DecoratorBuildItem;
import io.quarkus.kubernetes.spi.DekorateOutputBuildItem;
import io.quarkus.kubernetes.spi.GeneratedKubernetesResourceBuildItem;
+import io.quarkus.kubernetes.spi.KubernetesAdditionalResourceBuildItem;
import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem;
import io.quarkus.kubernetes.spi.KubernetesOutputDirectoryBuildItem;
import io.quarkus.kubernetes.spi.KubernetesPortBuildItem;
@@ -108,6 +109,7 @@ public void build(ApplicationInfoBuildItem applicationInfo,
LaunchModeBuildItem launchMode,
List kubernetesPorts,
EnabledKubernetesDeploymentTargetsBuildItem kubernetesDeploymentTargets,
+ List additionalResources,
List configurators,
List configurationSuppliers,
List decorators,
@@ -209,7 +211,8 @@ public void build(ApplicationInfoBuildItem applicationInfo,
if (fileName.endsWith(".yml") || fileName.endsWith(".json")) {
String target = fileName.substring(0, fileName.lastIndexOf("."));
- if (!deploymentTargets.contains(target)) {
+ if (!deploymentTargets.contains(target) &&
+ !additionalResources.stream().filter(i -> target.equals(i.getName())).findAny().isPresent()) {
continue;
}
}