pkg/builder/quarkus.go (244 lines of code) (raw):
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/apache/camel-k/v2/pkg/util/boolean"
"github.com/apache/camel-k/v2/pkg/util/jib"
"github.com/apache/camel-k/v2/pkg/util/io"
v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
"github.com/apache/camel-k/v2/pkg/util/camel"
"github.com/apache/camel-k/v2/pkg/util/defaults"
"github.com/apache/camel-k/v2/pkg/util/digest"
"github.com/apache/camel-k/v2/pkg/util/maven"
)
const projectModePerm = 0600
func init() {
registerSteps(Quarkus)
Quarkus.CommonSteps = []Step{
Quarkus.LoadCamelQuarkusCatalog,
Quarkus.GenerateQuarkusProject,
Quarkus.BuildQuarkusMavenContext,
Quarkus.BuildQuarkusMavenProject,
}
}
type quarkusSteps struct {
LoadCamelQuarkusCatalog Step
GenerateQuarkusProject Step
BuildQuarkusMavenContext Step
BuildQuarkusMavenProject Step
ComputeQuarkusDependencies Step
PrepareProjectWithSources Step
CommonSteps []Step
}
//nolint:mnd
var Quarkus = quarkusSteps{
LoadCamelQuarkusCatalog: NewStep(InitPhase, loadCamelQuarkusCatalog),
GenerateQuarkusProject: NewStep(ProjectGenerationPhase, generateQuarkusProject),
BuildQuarkusMavenContext: NewStep(ProjectGenerationPhase+1, buildMavenContextSettings),
PrepareProjectWithSources: NewStep(ProjectBuildPhase-1, prepareProjectWithSources),
BuildQuarkusMavenProject: NewStep(ProjectBuildPhase+2, buildMavenProject),
ComputeQuarkusDependencies: NewStep(ProjectBuildPhase+1, computeQuarkusDependencies),
}
func prepareProjectWithSources(ctx *builderContext) error {
sourcesPath := filepath.Join(ctx.Path, "maven", "src", "main", "resources", "routes")
if err := os.MkdirAll(sourcesPath, os.ModePerm); err != nil {
return fmt.Errorf("failure while creating resource folder: %w", err)
}
sourceList := ""
for _, source := range ctx.Build.Sources {
if sourceList != "" {
sourceList += ","
}
sourceList += "classpath:routes/" + source.Name
if err := os.WriteFile(
filepath.Join(sourcesPath, source.Name),
[]byte(source.Content),
projectModePerm,
); err != nil {
return fmt.Errorf("failure while writing %s: %w", source.Name, err)
}
}
if sourceList != "" {
routesIncludedPattern := "camel.main.routes-include-pattern = " + sourceList
if err := os.WriteFile(
filepath.Join(filepath.Dir(sourcesPath), "application.properties"),
[]byte(routesIncludedPattern),
projectModePerm,
); err != nil {
return fmt.Errorf("failure while writing the configuration application.properties: %w", err)
}
}
return nil
}
func loadCamelQuarkusCatalog(ctx *builderContext) error {
runtime := ctx.Build.Runtime.DeepCopy()
if runtime.Provider == v1.RuntimeProviderPlainQuarkus {
// We need this workaround to load the last existing catalog
// TODO: this part will be subject to future refactoring
runtime.Version = defaults.DefaultRuntimeVersion
}
catalog, err := camel.LoadCatalog(ctx.C, ctx.Client, ctx.Namespace, *runtime)
if err != nil {
return err
}
if catalog == nil {
return fmt.Errorf("unable to find catalog matching version requirement: runtime=%s, provider=%s",
runtime.Version,
runtime.Provider)
}
ctx.Catalog = catalog
return nil
}
func generateQuarkusProject(ctx *builderContext) error {
p := generateQuarkusProjectCommon(
ctx.Build.Runtime.Provider,
ctx.Build.Runtime.Version,
ctx.Build.Runtime.Metadata["quarkus.version"],
)
// Add Maven build extensions
p.Build.Extensions = &ctx.Build.Maven.Extension
// Add Maven repositories
p.Repositories = append(p.Repositories, ctx.Build.Maven.Repositories...)
p.PluginRepositories = append(p.PluginRepositories, ctx.Build.Maven.Repositories...)
ctx.Maven.Project = p
return nil
}
func generateQuarkusProjectCommon(runtimeProvider v1.RuntimeProvider, runtimeVersion string,
quarkusPlatformVersion string) maven.Project {
if runtimeProvider == v1.RuntimeProviderPlainQuarkus {
// We need this workaround to load the last existing catalog
// TODO: this part will be subject to future refactoring
quarkusPlatformVersion = runtimeVersion
}
p := maven.NewProjectWithGAV("org.apache.camel.k.integration", "camel-k-integration", defaults.Version)
p.DependencyManagement = &maven.DependencyManagement{Dependencies: make([]maven.Dependency, 0)}
p.Dependencies = make([]maven.Dependency, 0)
p.Build = &maven.Build{Plugins: make([]maven.Plugin, 0)}
// set fast-jar packaging by default, since it gives some startup time improvements
p.Properties.Add("quarkus.package.jar.type", "fast-jar")
// Reproducible builds: https://maven.apache.org/guides/mini/guide-reproducible-builds.html
p.Properties.Add("project.build.outputTimestamp", time.Now().Format(time.RFC3339))
// DependencyManagement
if runtimeProvider == v1.RuntimeProviderPlainQuarkus {
p.DependencyManagement.Dependencies = append(p.DependencyManagement.Dependencies,
maven.Dependency{
GroupID: "io.quarkus.platform",
ArtifactID: "quarkus-camel-bom",
Version: runtimeVersion,
Type: "pom",
Scope: "import",
},
maven.Dependency{
GroupID: "io.quarkus.platform",
ArtifactID: "quarkus-bom",
Version: runtimeVersion,
Type: "pom",
Scope: "import",
},
)
} else {
// Camel K Runtime (Quarkus based) default
p.DependencyManagement.Dependencies = append(p.DependencyManagement.Dependencies,
maven.Dependency{
GroupID: "org.apache.camel.k",
ArtifactID: "camel-k-runtime-bom",
Version: runtimeVersion,
Type: "pom",
Scope: "import",
},
)
}
// Plugins
p.Build.Plugins = append(p.Build.Plugins,
maven.Plugin{
GroupID: "io.quarkus",
ArtifactID: "quarkus-maven-plugin",
Version: quarkusPlatformVersion,
Executions: []maven.Execution{
{
ID: "build-integration",
Goals: []string{
"build",
},
},
},
},
)
// Jib publish profile
p.AddProfile(jib.JibMavenProfile(jib.JibMavenPluginVersionDefault, jib.JibLayerFilterExtensionMavenVersionDefault))
return p
}
func buildMavenProject(ctx *builderContext) error {
mc := newMavenContext(ctx)
return BuildQuarkusRunnerCommon(ctx.C, *mc, ctx.Maven.Project, ctx.Build.Maven.Properties)
}
func BuildQuarkusRunnerCommon(ctx context.Context, mc maven.Context, project maven.Project, applicationProperties map[string]string) error {
resourcesPath := filepath.Join(mc.Path, "src", "main", "resources")
if err := os.MkdirAll(resourcesPath, os.ModePerm); err != nil {
return fmt.Errorf("failure while creating resource folder: %w", err)
}
if err := computeApplicationProperties(filepath.Join(resourcesPath, "application.properties"), applicationProperties); err != nil {
return err
}
if err := project.Command(mc).DoPom(ctx); err != nil {
return fmt.Errorf("failure while generating pom file: %w", err)
}
mc.AddArgument("package")
mc.AddArgument("-Dmaven.test.skip=true")
if err := project.Command(mc).Do(ctx); err != nil {
return fmt.Errorf("failure while building project: %w", err)
}
return nil
}
func computeApplicationProperties(appPropertiesPath string, applicationProperties map[string]string) error {
f, err := os.OpenFile(appPropertiesPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, io.FilePerm644)
if err != nil {
return fmt.Errorf("failure while opening/creating application.properties: %w", err)
}
fstat, err := f.Stat()
if err != nil {
return err
}
if applicationProperties == nil {
// Default build time properties
applicationProperties = make(map[string]string)
}
// disable quarkus banner
applicationProperties["quarkus.banner.enabled"] = boolean.FalseString
// camel-quarkus does route discovery at startup, but we don't want
// this to happen as routes are loaded at runtime and looking for
// routes at build time may try to load camel-k-runtime routes builder
// proxies which in some case may fail.
applicationProperties["quarkus.camel.routes-discovery.enabled"] = boolean.FalseString
// required for to resolve data type transformers at runtime with service discovery
// the different Camel runtimes use different resource paths for the service lookup
applicationProperties["quarkus.camel.service.discovery.include-patterns"] = "META-INF/services/org/apache/camel/datatype/converter/*,META-INF/services/org/apache/camel/datatype/transformer/*,META-INF/services/org/apache/camel/transformer/*"
// Workaround to prevent JS runtime errors, see https://github.com/apache/camel-quarkus/issues/5678
applicationProperties["quarkus.class-loading.parent-first-artifacts"] = "org.graalvm.regex:regex"
defer f.Close()
// Add a new line if the file is already containing some value
if fstat.Size() > 0 {
if _, err := f.WriteString("\n"); err != nil {
return err
}
}
// Fill with properties coming from user configuration
for k, v := range applicationProperties {
if _, err := f.WriteString(fmt.Sprintf("%s=%s\n", k, v)); err != nil {
return err
}
}
return nil
}
func computeQuarkusDependencies(ctx *builderContext) error {
// Quarkus fast-jar format is split into various sub-directories in quarkus-app
quarkusAppDir := filepath.Join(ctx.Path, "maven", "target", "quarkus-app")
// Process artifacts list and add it to existing artifacts
artifacts, err := processQuarkusTransitiveDependencies(quarkusAppDir)
if err != nil {
return err
}
ctx.Artifacts = append(ctx.Artifacts, artifacts...)
return nil
}
func processQuarkusTransitiveDependencies(dir string) ([]v1.Artifact, error) {
var artifacts []v1.Artifact
// Discover application dependencies from the Quarkus fast-jar directory tree
err := filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fileRelPath := strings.Replace(filePath, dir, "", 1)
if !info.IsDir() {
sha1, err := digest.ComputeSHA1(filePath)
if err != nil {
return err
}
artifacts = append(artifacts, v1.Artifact{
ID: filepath.Base(fileRelPath),
Location: filePath,
Target: filepath.Join(DependenciesDir, fileRelPath),
Checksum: "sha1:" + sha1,
})
}
return nil
})
return artifacts, err
}