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[] 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; } }