From 0429052fb9ff830258e924b601353b4234e93067 Mon Sep 17 00:00:00 2001 From: Gaelle Fournier Date: Wed, 31 May 2023 10:37:23 +0200 Subject: [PATCH] feat(#1656) : jib publish strategy * Add publish jib strategy compatible with incremental build and native build * Use google jib maven plugin * Use builder maven profiles trait to configure jib plugin * The ConfigMap is created with: * maven jib plugin profile content in profile.xml * kit as an owner * The Jib Profile is used in the Jib published strategy --- .github/workflows/builder.yml | 2 +- config/crd/bases/camel.apache.org_builds.yaml | 37 ++ .../samples/patch-integration-platform.yaml | 4 +- .../ROOT/partials/apis/camel-k-crds.adoc | 353 ++++++++++++++++++ helm/camel-k/crds/crd-build.yaml | 37 ++ pkg/apis/camel/v1/build_types.go | 8 + pkg/apis/camel/v1/common_types.go | 11 + .../camel/v1/integrationkit_types_support.go | 2 + .../camel/v1/integrationplatform_types.go | 4 + pkg/apis/camel/v1/maven_types.go | 51 +++ pkg/apis/camel/v1/zz_generated.deepcopy.go | 200 ++++++++++ pkg/builder/jib.go | 137 +++++++ pkg/builder/project.go | 7 +- pkg/builder/tasks.go | 12 + .../applyconfiguration/camel/v1/jibtask.go | 73 ++++ .../camel/applyconfiguration/camel/v1/task.go | 9 + pkg/client/camel/applyconfiguration/utils.go | 2 + pkg/controller/build/build_pod.go | 2 + pkg/controller/build/monitor_routine.go | 7 + pkg/trait/builder.go | 26 +- pkg/trait/builder_test.go | 23 ++ pkg/trait/quarkus.go | 4 +- pkg/util/jib/configuration.go | 159 ++++++++ pkg/util/jib/configuration_test.go | 74 ++++ pkg/util/maven/maven_command.go | 22 +- pkg/util/maven/maven_log.go | 2 +- pkg/util/maven/maven_log_test.go | 2 +- pkg/util/maven/maven_types.go | 11 +- pkg/util/util.go | 11 +- 29 files changed, 1269 insertions(+), 23 deletions(-) create mode 100644 pkg/builder/jib.go create mode 100644 pkg/client/camel/applyconfiguration/camel/v1/jibtask.go create mode 100644 pkg/util/jib/configuration.go create mode 100644 pkg/util/jib/configuration_test.go diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index d301d1f614..2f38b31b69 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -74,7 +74,7 @@ jobs: strategy: fail-fast: false matrix: - publisher: ["Buildah", "Spectrum", "Kaniko"] + publisher: ["Buildah", "Spectrum", "Kaniko", "Jib"] steps: - name: Checkout code diff --git a/config/crd/bases/camel.apache.org_builds.yaml b/config/crd/bases/camel.apache.org_builds.yaml index d5fff8964e..bc1981b710 100644 --- a/config/crd/bases/camel.apache.org_builds.yaml +++ b/config/crd/bases/camel.apache.org_builds.yaml @@ -696,6 +696,43 @@ spec: description: name of the task type: string type: object + jib: + description: a JibTask, for Jib strategy + properties: + baseImage: + description: base image layer + type: string + contextDir: + description: can be useful to share info with other tasks + type: string + image: + description: final image name + type: string + name: + description: name of the task + type: string + registry: + description: where to publish the final image + properties: + address: + description: the URI to access + type: string + ca: + description: the configmap which stores the Certificate + Authority + type: string + insecure: + description: if the container registry is insecure (ie, + http only) + type: boolean + organization: + description: the registry organization + type: string + secret: + description: the secret where credentials are stored + type: string + type: object + type: object kaniko: description: a KanikoTask, for Kaniko strategy properties: diff --git a/config/samples/patch-integration-platform.yaml b/config/samples/patch-integration-platform.yaml index f53bd4783d..e7ea4f540b 100644 --- a/config/samples/patch-integration-platform.yaml +++ b/config/samples/patch-integration-platform.yaml @@ -44,9 +44,9 @@ spec: # # # Build publish strategy for integrations - # ie. Buildah, Kaniko, S2I, Spectrum + # ie. Buildah, Kaniko, S2I, Spectrum, Jib # - # publishStrategy: Buildah | Kaniko | S2I | Spectrum + # publishStrategy: Buildah | Kaniko | S2I | Spectrum | Jib # # Set the camel-k runtime version # diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index 1db2f63eab..a97959a309 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -330,6 +330,30 @@ AddonTrait represents the configuration of an addon trait Generic raw message, typically a map containing the keys (trait parameters) and the values (either single text or array) +|=== + +[#_camel_apache_org_v1_Args] +=== Args + +*Appears on:* + +* <<#_camel_apache_org_v1_Container, Container>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`arg` + +string +| + + + + + |=== [#_camel_apache_org_v1_Artifact] @@ -385,6 +409,7 @@ a checksum (SHA1) of the content * <<#_camel_apache_org_v1_BuildahTask, BuildahTask>> * <<#_camel_apache_org_v1_BuilderTask, BuilderTask>> +* <<#_camel_apache_org_v1_JibTask, JibTask>> * <<#_camel_apache_org_v1_KanikoTask, KanikoTask>> * <<#_camel_apache_org_v1_S2iTask, S2iTask>> * <<#_camel_apache_org_v1_SpectrumTask, SpectrumTask>> @@ -1366,6 +1391,37 @@ string the value to assign to the configuration (syntax may vary depending on the `Type`) +|=== + +[#_camel_apache_org_v1_Container] +=== Container + +*Appears on:* + +* <<#_camel_apache_org_v1_PluginConfiguration, PluginConfiguration>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`entrypoint` + +string +| + + + + +|`args` + +*xref:#_camel_apache_org_v1_Args[Args]* +| + + + + + |=== [#_camel_apache_org_v1_DataSpec] @@ -1848,6 +1904,37 @@ string +|=== + +[#_camel_apache_org_v1_ExtraDirectories] +=== ExtraDirectories + +*Appears on:* + +* <<#_camel_apache_org_v1_PluginConfiguration, PluginConfiguration>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`paths>path` + +*xref:#_camel_apache_org_v1_Path[[\]Path]* +| + + + + +|`permissions>permission` + +*xref:#_camel_apache_org_v1_Permission[[\]Permission]* +| + + + + + |=== [#_camel_apache_org_v1_Failure] @@ -1925,6 +2012,37 @@ maximum number of attempts time of the attempt execution +|=== + +[#_camel_apache_org_v1_Filter] +=== Filter + +*Appears on:* + +* <<#_camel_apache_org_v1_PluginExtensionConfiguration, PluginExtensionConfiguration>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`glob` + +string +| + + + + +|`toLayer` + +string +| + + + + + |=== [#_camel_apache_org_v1_Flow] @@ -3420,6 +3538,37 @@ string JSONSchemaURL represents a schema url. +[#_camel_apache_org_v1_JibTask] +=== JibTask + +*Appears on:* + +* <<#_camel_apache_org_v1_Task, Task>> + +JibTask is used to configure Jib + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`BaseTask` + +*xref:#_camel_apache_org_v1_BaseTask[BaseTask]* +|(Members of `BaseTask` are embedded into this type.) + + + + +|`PublishTask` + +*xref:#_camel_apache_org_v1_PublishTask[PublishTask]* +|(Members of `PublishTask` are embedded into this type.) + + + + + +|=== + [#_camel_apache_org_v1_KameletCondition] === KameletCondition @@ -3891,6 +4040,75 @@ e.g., `-V,--no-transfer-progress,-Dstyle.color=never`. See https://maven.apache.org/ref/3.8.4/maven-embedder/cli.html. +|=== + +[#_camel_apache_org_v1_Path] +=== Path + +*Appears on:* + +* <<#_camel_apache_org_v1_ExtraDirectories, ExtraDirectories>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`from` + +string +| + + + + +|`into` + +string +| + + + + +|`excludes>exclude` + +[]string +| + + + + + +|=== + +[#_camel_apache_org_v1_Permission] +=== Permission + +*Appears on:* + +* <<#_camel_apache_org_v1_ExtraDirectories, ExtraDirectories>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`file` + +string +| + + + + +|`mode` + +string +| + + + + + |=== [#_camel_apache_org_v1_PipeCondition] @@ -4095,6 +4313,133 @@ string Selector allows to identify pods belonging to the pipe +|=== + +[#_camel_apache_org_v1_PluginConfiguration] +=== PluginConfiguration + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`container` + +*xref:#_camel_apache_org_v1_Container[Container]* +| + + + + +|`allowInsecureRegistries` + +string +| + + + + +|`extraDirectories` + +*xref:#_camel_apache_org_v1_ExtraDirectories[ExtraDirectories]* +| + + + + +|`pluginExtensions` + +*xref:#_camel_apache_org_v1_PluginExtensions[PluginExtensions]* +| + + + + + +|=== + +[#_camel_apache_org_v1_PluginExtension] +=== PluginExtension + +*Appears on:* + +* <<#_camel_apache_org_v1_PluginExtensions, PluginExtensions>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`implementation` + +string +| + + + + +|`configuration` + +*xref:#_camel_apache_org_v1_PluginExtensionConfiguration[PluginExtensionConfiguration]* +| + + + + + +|=== + +[#_camel_apache_org_v1_PluginExtensionConfiguration] +=== PluginExtensionConfiguration + +*Appears on:* + +* <<#_camel_apache_org_v1_PluginExtension, PluginExtension>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`filters>Filter` + +*xref:#_camel_apache_org_v1_Filter[[\]Filter]* +| + + + + +|`_implementation` + +string +| + + + + + +|=== + +[#_camel_apache_org_v1_PluginExtensions] +=== PluginExtensions + +*Appears on:* + +* <<#_camel_apache_org_v1_PluginConfiguration, PluginConfiguration>> + + + +[cols="2,2a",options="header"] +|=== +|Field +|Description + +|`pluginExtension` + +*xref:#_camel_apache_org_v1_PluginExtension[PluginExtension]* +| + + + + + |=== [#_camel_apache_org_v1_PluginProperties] @@ -4277,6 +4622,7 @@ the specification *Appears on:* * <<#_camel_apache_org_v1_BuildahTask, BuildahTask>> +* <<#_camel_apache_org_v1_JibTask, JibTask>> * <<#_camel_apache_org_v1_KanikoTask, KanikoTask>> * <<#_camel_apache_org_v1_SpectrumTask, SpectrumTask>> @@ -4836,6 +5182,13 @@ a SpectrumTask, for Spectrum strategy a S2iTask, for S2I strategy +|`jib` + +*xref:#_camel_apache_org_v1_JibTask[JibTask]* +| + + +a JibTask, for Jib strategy + |`custom` + *xref:#_camel_apache_org_v1_UserTask[UserTask]* | diff --git a/helm/camel-k/crds/crd-build.yaml b/helm/camel-k/crds/crd-build.yaml index d5fff8964e..bc1981b710 100644 --- a/helm/camel-k/crds/crd-build.yaml +++ b/helm/camel-k/crds/crd-build.yaml @@ -696,6 +696,43 @@ spec: description: name of the task type: string type: object + jib: + description: a JibTask, for Jib strategy + properties: + baseImage: + description: base image layer + type: string + contextDir: + description: can be useful to share info with other tasks + type: string + image: + description: final image name + type: string + name: + description: name of the task + type: string + registry: + description: where to publish the final image + properties: + address: + description: the URI to access + type: string + ca: + description: the configmap which stores the Certificate + Authority + type: string + insecure: + description: if the container registry is insecure (ie, + http only) + type: boolean + organization: + description: the registry organization + type: string + secret: + description: the secret where credentials are stored + type: string + type: object + type: object kaniko: description: a KanikoTask, for Kaniko strategy properties: diff --git a/pkg/apis/camel/v1/build_types.go b/pkg/apis/camel/v1/build_types.go index e928c66c9a..5c8dcb2d5c 100644 --- a/pkg/apis/camel/v1/build_types.go +++ b/pkg/apis/camel/v1/build_types.go @@ -67,6 +67,8 @@ type Task struct { Spectrum *SpectrumTask `json:"spectrum,omitempty"` // a S2iTask, for S2I strategy S2i *S2iTask `json:"s2i,omitempty"` + // a JibTask, for Jib strategy + Jib *JibTask `json:"jib,omitempty"` // User customizable task execution @@ -154,6 +156,12 @@ type KanikoTaskCache struct { PersistentVolumeClaim string `json:"persistentVolumeClaim,omitempty"` } +// JibTask is used to configure Jib +type JibTask struct { + BaseTask `json:",inline"` + PublishTask `json:",inline"` +} + // SpectrumTask is used to configure Spectrum type SpectrumTask struct { BaseTask `json:",inline"` diff --git a/pkg/apis/camel/v1/common_types.go b/pkg/apis/camel/v1/common_types.go index 0f3e2c8a3c..51b419305c 100644 --- a/pkg/apis/camel/v1/common_types.go +++ b/pkg/apis/camel/v1/common_types.go @@ -302,6 +302,17 @@ type ValueSource struct { SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` } +// String returns a string representation of ValueSource +func (o *ValueSource) String() string { + text := "" + if o.ConfigMapKeyRef != nil { + text = "configmap:" + o.ConfigMapKeyRef.Name + "/" + o.ConfigMapKeyRef.Key + } else if o.SecretKeyRef != nil { + text = "secret:" + o.SecretKeyRef.Name + "/" + o.SecretKeyRef.Key + } + return text +} + // RuntimeSpec represents the configuration for the Java runtime in charge to execute the Camel application type RuntimeSpec struct { // Camel K Runtime version diff --git a/pkg/apis/camel/v1/integrationkit_types_support.go b/pkg/apis/camel/v1/integrationkit_types_support.go index 87d7667be8..c4c10a6088 100644 --- a/pkg/apis/camel/v1/integrationkit_types_support.go +++ b/pkg/apis/camel/v1/integrationkit_types_support.go @@ -24,6 +24,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const IntegrationKitLabel = "camel.apache.org/integrationkit" + func NewIntegrationKit(namespace string, name string) *IntegrationKit { return &IntegrationKit{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/apis/camel/v1/integrationplatform_types.go b/pkg/apis/camel/v1/integrationplatform_types.go index d877373588..65dbbfdb87 100644 --- a/pkg/apis/camel/v1/integrationplatform_types.go +++ b/pkg/apis/camel/v1/integrationplatform_types.go @@ -165,6 +165,9 @@ const ( // IntegrationPlatformBuildPublishStrategySpectrum uses Spectrum project (https://github.com/container-tools/spectrum) // in order to push the incremental images to the image repository. It is the default choice on vanilla Kubernetes cluster IntegrationPlatformBuildPublishStrategySpectrum IntegrationPlatformBuildPublishStrategy = "Spectrum" + // IntegrationPlatformBuildPublishStrategyJib uses Jib maven plugin (https://github.com/GoogleContainerTools/jib) + // in order to push the incremental images to the image repository. + IntegrationPlatformBuildPublishStrategyJib IntegrationPlatformBuildPublishStrategy = "Jib" ) // IntegrationPlatformBuildPublishStrategies the list of all available publish strategies @@ -173,6 +176,7 @@ var IntegrationPlatformBuildPublishStrategies = []IntegrationPlatformBuildPublis IntegrationPlatformBuildPublishStrategyKaniko, IntegrationPlatformBuildPublishStrategyS2I, IntegrationPlatformBuildPublishStrategySpectrum, + IntegrationPlatformBuildPublishStrategyJib, } // IntegrationPlatformPhase is the phase of an IntegrationPlatform diff --git a/pkg/apis/camel/v1/maven_types.go b/pkg/apis/camel/v1/maven_types.go index 0650279e3e..f2d0bb7b6e 100644 --- a/pkg/apis/camel/v1/maven_types.go +++ b/pkg/apis/camel/v1/maven_types.go @@ -106,3 +106,54 @@ type StringOrProperties struct { type Properties map[string]string type PluginProperties map[string]StringOrProperties + +type PluginConfiguration struct { + Container Container `xml:"container" json:"container"` + AllowInsecureRegistries string `xml:"allowInsecureRegistries" json:"allowInsecureRegistries"` + ExtraDirectories ExtraDirectories `xml:"extraDirectories" json:"extraDirectories"` + PluginExtensions PluginExtensions `xml:"pluginExtensions" json:"pluginExtensions"` +} + +type Container struct { + Entrypoint string `xml:"entrypoint" json:"entrypoint"` + Args Args `xml:"args" json:"args"` +} + +type Args struct { + Arg string `xml:"arg" json:"arg"` +} + +type ExtraDirectories struct { + Paths []Path `xml:"paths>path" json:"paths>path"` + Permissions []Permission `xml:"permissions>permission,omitempty" json:"permissions>permission,omitempty"` +} + +type Path struct { + From string `xml:"from" json:"from"` + Into string `xml:"into" json:"into"` + Excludes []string `xml:"excludes>exclude,omitempty" json:"excludes>exclude,omitempty"` +} + +type Permission struct { + File string `xml:"file" json:"file"` + Mode string `xml:"mode" json:"mode"` +} + +type PluginExtensions struct { + PluginExtension PluginExtension `xml:"pluginExtension" json:"pluginExtension"` +} + +type PluginExtension struct { + Implementation string `xml:"implementation" json:"implementation"` + Configuration PluginExtensionConfiguration `xml:"configuration" json:"configuration"` +} + +type PluginExtensionConfiguration struct { + Filters []Filter `xml:"filters>Filter" json:"filters>Filter"` + Implementation string `xml:"implementation,attr" json:"_implementation"` +} + +type Filter struct { + Glob string `xml:"glob" json:"glob"` + ToLayer string `xml:"toLayer,omitempty" json:"toLayer,omitempty"` +} diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go b/pkg/apis/camel/v1/zz_generated.deepcopy.go index cc0853d904..533002897b 100644 --- a/pkg/apis/camel/v1/zz_generated.deepcopy.go +++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go @@ -33,6 +33,21 @@ func (in *AddonTrait) DeepCopy() *AddonTrait { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Args) DeepCopyInto(out *Args) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Args. +func (in *Args) DeepCopy() *Args { + if in == nil { + return nil + } + out := new(Args) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Artifact) DeepCopyInto(out *Artifact) { *out = *in @@ -608,6 +623,22 @@ func (in *ConfigurationSpec) DeepCopy() *ConfigurationSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Container) DeepCopyInto(out *Container) { + *out = *in + out.Args = in.Args +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container. +func (in *Container) DeepCopy() *Container { + if in == nil { + return nil + } + out := new(Container) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DataSpec) DeepCopyInto(out *DataSpec) { *out = *in @@ -894,6 +925,33 @@ func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtraDirectories) DeepCopyInto(out *ExtraDirectories) { + *out = *in + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]Path, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = make([]Permission, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraDirectories. +func (in *ExtraDirectories) DeepCopy() *ExtraDirectories { + if in == nil { + return nil + } + out := new(ExtraDirectories) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Failure) DeepCopyInto(out *Failure) { *out = *in @@ -927,6 +985,21 @@ func (in *FailureRecovery) DeepCopy() *FailureRecovery { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Filter) DeepCopyInto(out *Filter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Filter. +func (in *Filter) DeepCopy() *Filter { + if in == nil { + return nil + } + out := new(Filter) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Flow) DeepCopyInto(out *Flow) { *out = *in @@ -1735,6 +1808,23 @@ func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JibTask) DeepCopyInto(out *JibTask) { + *out = *in + out.BaseTask = in.BaseTask + out.PublishTask = in.PublishTask +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JibTask. +func (in *JibTask) DeepCopy() *JibTask { + if in == nil { + return nil + } + out := new(JibTask) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Kamelet) DeepCopyInto(out *Kamelet) { *out = *in @@ -2038,6 +2128,41 @@ func (in *MavenSpec) DeepCopy() *MavenSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Path) DeepCopyInto(out *Path) { + *out = *in + if in.Excludes != nil { + in, out := &in.Excludes, &out.Excludes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Path. +func (in *Path) DeepCopy() *Path { + if in == nil { + return nil + } + out := new(Path) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Permission) DeepCopyInto(out *Permission) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permission. +func (in *Permission) DeepCopy() *Permission { + if in == nil { + return nil + } + out := new(Permission) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pipe) DeepCopyInto(out *Pipe) { *out = *in @@ -2187,6 +2312,76 @@ func (in *PipeStatus) DeepCopy() *PipeStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PluginConfiguration) DeepCopyInto(out *PluginConfiguration) { + *out = *in + out.Container = in.Container + in.ExtraDirectories.DeepCopyInto(&out.ExtraDirectories) + in.PluginExtensions.DeepCopyInto(&out.PluginExtensions) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginConfiguration. +func (in *PluginConfiguration) DeepCopy() *PluginConfiguration { + if in == nil { + return nil + } + out := new(PluginConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PluginExtension) DeepCopyInto(out *PluginExtension) { + *out = *in + in.Configuration.DeepCopyInto(&out.Configuration) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginExtension. +func (in *PluginExtension) DeepCopy() *PluginExtension { + if in == nil { + return nil + } + out := new(PluginExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PluginExtensionConfiguration) DeepCopyInto(out *PluginExtensionConfiguration) { + *out = *in + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = make([]Filter, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginExtensionConfiguration. +func (in *PluginExtensionConfiguration) DeepCopy() *PluginExtensionConfiguration { + if in == nil { + return nil + } + out := new(PluginExtensionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PluginExtensions) DeepCopyInto(out *PluginExtensions) { + *out = *in + in.PluginExtension.DeepCopyInto(&out.PluginExtension) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginExtensions. +func (in *PluginExtensions) DeepCopy() *PluginExtensions { + if in == nil { + return nil + } + out := new(PluginExtensions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in PluginProperties) DeepCopyInto(out *PluginProperties) { { @@ -2584,6 +2779,11 @@ func (in *Task) DeepCopyInto(out *Task) { *out = new(S2iTask) **out = **in } + if in.Jib != nil { + in, out := &in.Jib, &out.Jib + *out = new(JibTask) + **out = **in + } if in.Custom != nil { in, out := &in.Custom, &out.Custom *out = new(UserTask) diff --git a/pkg/builder/jib.go b/pkg/builder/jib.go new file mode 100644 index 0000000000..6ae130d003 --- /dev/null +++ b/pkg/builder/jib.go @@ -0,0 +1,137 @@ +/* +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" + "os" + "os/exec" + "path/filepath" + "strings" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/client" + "github.com/apache/camel-k/v2/pkg/util" + "github.com/apache/camel-k/v2/pkg/util/jib" + "github.com/apache/camel-k/v2/pkg/util/log" + "github.com/apache/camel-k/v2/pkg/util/maven" +) + +type jibTask struct { + c client.Client + build *v1.Build + task *v1.JibTask +} + +var _ Task = &jibTask{} + +func (t *jibTask) Do(ctx context.Context) v1.BuildStatus { + status := v1.BuildStatus{} + + baseImage := t.build.Status.BaseImage + if baseImage == "" { + baseImage = t.task.BaseImage + status.BaseImage = baseImage + } + + contextDir := t.task.ContextDir + if contextDir == "" { + // Use the working directory. + // This is useful when the task is executed in-container, + // so that its WorkingDir can be used to share state and + // coordinate with other tasks. + pwd, err := os.Getwd() + if err != nil { + return status.Failed(err) + } + contextDir = filepath.Join(pwd, ContextDir) + } + + exists, err := util.DirectoryExists(contextDir) + if err != nil { + return status.Failed(err) + } + empty, err := util.DirectoryEmpty(contextDir) + if err != nil { + return status.Failed(err) + } + if !exists || empty { + // this can only indicate that there are no more resources to add to the base image, + // because transitive resolution is the same even if spec differs. + log.Infof("No new image to build, reusing existing image %s", baseImage) + status.Image = baseImage + return status + } + mavenDir := strings.ReplaceAll(contextDir, ContextDir, "maven") + + log.Debugf("Registry address: %s", t.task.Registry.Address) + log.Debugf("Base image: %s", baseImage) + + registryConfigDir := "" + if t.task.Registry.Secret != "" { + registryConfigDir, err = MountSecret(ctx, t.c, t.build.Namespace, t.task.Registry.Secret) + if err != nil { + return status.Failed(err) + } + } + + if registryConfigDir != "" { + if err := os.RemoveAll(registryConfigDir); err != nil { + return status.Failed(err) + } + } + + // TODO refactor maven code to avoid creating a file to pass command args + mavenCommand, err := util.ReadFile(filepath.Join(mavenDir, "MAVEN_CONTEXT")) + if err != nil { + return status.Failed(err) + } + + mavenArgs := make([]string, 0) + mavenArgs = append(mavenArgs, jib.JibMavenGoal) + mavenArgs = append(mavenArgs, strings.Split(string(mavenCommand), " ")...) + mavenArgs = append(mavenArgs, "-P", "jib") + mavenArgs = append(mavenArgs, jib.JibMavenToImageParam+t.task.Image) + mavenArgs = append(mavenArgs, jib.JibMavenFromImageParam+baseImage) + if t.task.Registry.Insecure { + mavenArgs = append(mavenArgs, jib.JibMavenInsecureRegistries+"true") + } + + mvnCmd := "./mvnw" + if c, ok := os.LookupEnv("MAVEN_CMD"); ok { + mvnCmd = c + } + cmd := exec.CommandContext(ctx, mvnCmd, mavenArgs...) + cmd.Dir = mavenDir + + myerror := util.RunAndLog(ctx, cmd, maven.MavenLogHandler, maven.MavenLogHandler) + if myerror != nil { + log.Errorf(myerror, "jib integration image containerization did not run successfully") + return status.Failed(myerror) + } else { + log.Debug("jib integration image containerization did run successfully") + status.Image = t.task.Image + + // retrieve image digest + mavenDigest, errDigest := util.ReadFile(filepath.Join(mavenDir, jib.JibDigestFile)) + if errDigest != nil { + return status.Failed(errDigest) + } + status.Digest = string(mavenDigest) + } + + return status +} diff --git a/pkg/builder/project.go b/pkg/builder/project.go index e31e25a56b..41e44263ea 100644 --- a/pkg/builder/project.go +++ b/pkg/builder/project.go @@ -20,6 +20,7 @@ package builder import ( "bytes" "encoding/xml" + "fmt" "os" "regexp" "strings" @@ -198,10 +199,10 @@ func sanitizeDependencies(ctx *builderContext) error { func injectProfiles(ctx *builderContext) error { if ctx.Build.Maven.Profiles != nil { profiles := "" - for _, profile := range ctx.Build.Maven.Profiles { - val, err := kubernetes.ResolveValueSource(ctx.C, ctx.Client, ctx.Namespace, &profile) + for i := range ctx.Build.Maven.Profiles { + val, err := kubernetes.ResolveValueSource(ctx.C, ctx.Client, ctx.Namespace, &ctx.Build.Maven.Profiles[i]) if err != nil { - return err + return fmt.Errorf("could not load profile : %s: %w. ", ctx.Build.Maven.Profiles[i].String(), err) } if val != "" { profiles += val diff --git a/pkg/builder/tasks.go b/pkg/builder/tasks.go index 00e575e0ea..054fe35521 100644 --- a/pkg/builder/tasks.go +++ b/pkg/builder/tasks.go @@ -62,6 +62,12 @@ func (b *Build) Task(task v1.Task) Task { build: b.build, task: task.S2i, } + case task.Jib != nil: + return &jibTask{ + c: b.builder.client, + build: b.build, + task: task.Jib, + } } return &emptyTask{ @@ -138,6 +144,12 @@ func (b *Build) TaskByName(name string) Task { build: b.build, task: task.S2i, } + case task.Jib != nil && task.Jib.Name == name: + return &jibTask{ + c: b.builder.client, + build: b.build, + task: task.Jib, + } } } return &missingTask{ diff --git a/pkg/client/camel/applyconfiguration/camel/v1/jibtask.go b/pkg/client/camel/applyconfiguration/camel/v1/jibtask.go new file mode 100644 index 0000000000..1e87d42814 --- /dev/null +++ b/pkg/client/camel/applyconfiguration/camel/v1/jibtask.go @@ -0,0 +1,73 @@ +/* +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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// JibTaskApplyConfiguration represents an declarative configuration of the JibTask type for use +// with apply. +type JibTaskApplyConfiguration struct { + BaseTaskApplyConfiguration `json:",inline"` + PublishTaskApplyConfiguration `json:",inline"` +} + +// JibTaskApplyConfiguration constructs an declarative configuration of the JibTask type for use with +// apply. +func JibTask() *JibTaskApplyConfiguration { + return &JibTaskApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *JibTaskApplyConfiguration) WithName(value string) *JibTaskApplyConfiguration { + b.Name = &value + return b +} + +// WithContextDir sets the ContextDir field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ContextDir field is set to the value of the last call. +func (b *JibTaskApplyConfiguration) WithContextDir(value string) *JibTaskApplyConfiguration { + b.ContextDir = &value + return b +} + +// WithBaseImage sets the BaseImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the BaseImage field is set to the value of the last call. +func (b *JibTaskApplyConfiguration) WithBaseImage(value string) *JibTaskApplyConfiguration { + b.BaseImage = &value + return b +} + +// WithImage sets the Image field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Image field is set to the value of the last call. +func (b *JibTaskApplyConfiguration) WithImage(value string) *JibTaskApplyConfiguration { + b.Image = &value + return b +} + +// WithRegistry sets the Registry field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Registry field is set to the value of the last call. +func (b *JibTaskApplyConfiguration) WithRegistry(value *RegistrySpecApplyConfiguration) *JibTaskApplyConfiguration { + b.Registry = value + return b +} diff --git a/pkg/client/camel/applyconfiguration/camel/v1/task.go b/pkg/client/camel/applyconfiguration/camel/v1/task.go index 01fd1aa5a2..a4b7912c2e 100644 --- a/pkg/client/camel/applyconfiguration/camel/v1/task.go +++ b/pkg/client/camel/applyconfiguration/camel/v1/task.go @@ -27,6 +27,7 @@ type TaskApplyConfiguration struct { Kaniko *KanikoTaskApplyConfiguration `json:"kaniko,omitempty"` Spectrum *SpectrumTaskApplyConfiguration `json:"spectrum,omitempty"` S2i *S2iTaskApplyConfiguration `json:"s2i,omitempty"` + Jib *JibTaskApplyConfiguration `json:"jib,omitempty"` Custom *UserTaskApplyConfiguration `json:"custom,omitempty"` } @@ -76,6 +77,14 @@ func (b *TaskApplyConfiguration) WithS2i(value *S2iTaskApplyConfiguration) *Task return b } +// WithJib sets the Jib field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Jib field is set to the value of the last call. +func (b *TaskApplyConfiguration) WithJib(value *JibTaskApplyConfiguration) *TaskApplyConfiguration { + b.Jib = value + return b +} + // WithCustom sets the Custom field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Custom field is set to the value of the last call. diff --git a/pkg/client/camel/applyconfiguration/utils.go b/pkg/client/camel/applyconfiguration/utils.go index 1e0497534b..3ce316c0eb 100644 --- a/pkg/client/camel/applyconfiguration/utils.go +++ b/pkg/client/camel/applyconfiguration/utils.go @@ -136,6 +136,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &camelv1.IntegrationSpecApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("IntegrationStatus"): return &camelv1.IntegrationStatusApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("JibTask"): + return &camelv1.JibTaskApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("JSON"): return &camelv1.JSONApplyConfiguration{} case v1.SchemeGroupVersion.WithKind("JSONSchemaProp"): diff --git a/pkg/controller/build/build_pod.go b/pkg/controller/build/build_pod.go index bb5fe31a7a..686152ab3b 100644 --- a/pkg/controller/build/build_pod.go +++ b/pkg/controller/build/build_pod.go @@ -170,6 +170,8 @@ func newBuildPod(ctx context.Context, c ctrl.Reader, client client.Client, build addBuildTaskToPod(ctx, client, build, task.S2i.Name, pod) case task.Spectrum != nil: addBuildTaskToPod(ctx, client, build, task.Spectrum.Name, pod) + case task.Jib != nil: + addBuildTaskToPod(ctx, client, build, task.Jib.Name, pod) case task.Custom != nil: addCustomTaskToPod(build, task.Custom, pod) } diff --git a/pkg/controller/build/monitor_routine.go b/pkg/controller/build/monitor_routine.go index 1d1486a386..adc566d042 100644 --- a/pkg/controller/build/monitor_routine.go +++ b/pkg/controller/build/monitor_routine.go @@ -148,6 +148,13 @@ tasks: break tasks } t.ContextDir = filepath.Join(buildDir, builder.ContextDir) + + } else if t := task.Jib; t != nil && t.ContextDir == "" { + if buildDir == "" { + status.Failed(fmt.Errorf("cannot determine context directory for task %s", t.Name)) + break tasks + } + t.ContextDir = filepath.Join(buildDir, builder.ContextDir) } // Execute the task diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go index 16bc17cd54..9d878661f2 100644 --- a/pkg/trait/builder.go +++ b/pkg/trait/builder.go @@ -28,6 +28,7 @@ import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" "github.com/apache/camel-k/v2/pkg/builder" + "github.com/apache/camel-k/v2/pkg/util/jib" mvn "github.com/apache/camel-k/v2/pkg/util/maven" "github.com/apache/camel-k/v2/pkg/util/property" ) @@ -120,6 +121,18 @@ func (t *builderTrait) Apply(e *Environment) error { }, }}) + case v1.IntegrationPlatformBuildPublishStrategyJib: + pipelineTasks = append(pipelineTasks, v1.Task{Jib: &v1.JibTask{ + BaseTask: v1.BaseTask{ + Name: "jib", + }, + PublishTask: v1.PublishTask{ + BaseImage: e.Platform.Status.Build.BaseImage, + Image: getImageName(e), + Registry: e.Platform.Status.Build.Registry, + }, + }}) + case v1.IntegrationPlatformBuildPublishStrategyS2I: pipelineTasks = append(pipelineTasks, v1.Task{S2i: &v1.S2iTask{ BaseTask: v1.BaseTask{ @@ -181,6 +194,7 @@ func (t *builderTrait) Apply(e *Environment) error { ExecutorImage: executorImage, }}) } + // add local pipeline tasks to env pipeline e.Pipeline = append(e.Pipeline, pipelineTasks...) return nil @@ -284,12 +298,18 @@ func (t *builderTrait) builderTask(e *Environment) (*v1.BuilderTask, error) { } } + if e.Platform.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyJib { + if err := jib.CreateProfileConfigmap(e.Ctx, e.Client, e.IntegrationKit); err != nil { + return nil, fmt.Errorf("could not create default maven jib profile: %w. ", err) + } + t.MavenProfiles = append(t.MavenProfiles, "configmap:"+e.IntegrationKit.Name+"-publish-jib-profile/profile.xml") + } + // User provides a maven profile if t.MavenProfiles != nil { mavenProfiles := make([]v1.ValueSource, 0) for _, v := range t.MavenProfiles { if v != "" { - // TODO parametrize message with input mavenProfile, err := v1.DecodeValueSource(v, "profile.xml", "illegal profile definition, syntax: configmap|secret:resource-name[/profile path]") if err != nil { @@ -300,10 +320,6 @@ func (t *builderTrait) builderTask(e *Environment) (*v1.BuilderTask, error) { } task.Maven.Profiles = mavenProfiles } - // add jib profile - /*if e.Platform.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyJib { - t.L.Info("GFO - You should add jib profile") - }*/ steps := make([]builder.Step, 0) steps = append(steps, builder.Project.CommonSteps...) diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go index 4ccd434d68..54151db293 100644 --- a/pkg/trait/builder_test.go +++ b/pkg/trait/builder_test.go @@ -126,6 +126,9 @@ func createBuilderTestEnv(cluster v1.IntegrationPlatformCluster, strategy v1.Int Status: v1.IntegrationKitStatus{ Phase: v1.IntegrationKitPhaseBuildSubmitted, }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-kit", + }, }, Platform: &v1.IntegrationPlatform{ Spec: v1.IntegrationPlatformSpec{ @@ -290,5 +293,25 @@ func TestInvalidMavenProfilesBuilderTrait(t *testing.T) { err := builderTrait.Apply(env) assert.NotNil(t, err) + assert.Equal(t, env.IntegrationKit.Status.Phase, v1.IntegrationKitPhaseError) + assert.Equal(t, env.IntegrationKit.Status.Conditions[0].Status, corev1.ConditionFalse) + assert.Contains(t, env.IntegrationKit.Status.Conditions[0].Message, "fakeprofile") +} + +func TestMavenBuilderTraitJib(t *testing.T) { + env := createBuilderTestEnv(v1.IntegrationPlatformClusterKubernetes, v1.IntegrationPlatformBuildPublishStrategyJib, v1.BuildStrategyRoutine) + builderTrait := createNominalBuilderTraitTest() + + err := builderTrait.Apply(env) + assert.Nil(t, err) + + assert.Equal(t, v1.ValueSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-kit-publish-jib-profile", + }, + Key: "profile.xml", + }, + }, env.Pipeline[0].Builder.Maven.MavenSpec.Profiles[0]) } diff --git a/pkg/trait/quarkus.go b/pkg/trait/quarkus.go index ae182e740b..11c4fdfafe 100644 --- a/pkg/trait/quarkus.go +++ b/pkg/trait/quarkus.go @@ -319,14 +319,14 @@ func (t *quarkusTrait) applyWhenBuildSubmitted(e *Environment) error { } steps = append(steps, builder.Image.NativeImageContext) // Spectrum does not rely on Dockerfile to assemble the image - if e.Platform.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategySpectrum { + if e.Platform.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategySpectrum && e.Platform.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategyJib { steps = append(steps, builder.Image.ExecutableDockerfile) } } else { build.Maven.Properties["quarkus.package.type"] = string(traitv1.FastJarPackageType) steps = append(steps, builder.Quarkus.ComputeQuarkusDependencies, builder.Image.IncrementalImageContext) // Spectrum does not rely on Dockerfile to assemble the image - if e.Platform.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategySpectrum { + if e.Platform.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategySpectrum && e.Platform.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategyJib { steps = append(steps, builder.Image.JvmDockerfile) } } diff --git a/pkg/util/jib/configuration.go b/pkg/util/jib/configuration.go new file mode 100644 index 0000000000..4497f9c8d4 --- /dev/null +++ b/pkg/util/jib/configuration.go @@ -0,0 +1,159 @@ +/* +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 jib contains utilities for jib strategy builds. +package jib + +import ( + "context" + "encoding/xml" + "fmt" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/client" + "github.com/apache/camel-k/v2/pkg/util" + "github.com/apache/camel-k/v2/pkg/util/maven" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const JibMavenGoal = "jib:build" +const JibMavenToImageParam = "-Djib.to.image=" +const JibMavenFromImageParam = "-Djib.from.image=" +const JibMavenInsecureRegistries = "-Djib.allowInsecureRegistries=" +const JibDigestFile = "target/jib-image.digest" + +type JibBuild struct { + Plugins []maven.Plugin `xml:"plugins>plugin,omitempty"` +} + +type JibProfile struct { + XMLName xml.Name + ID string `xml:"id"` + Build JibBuild `xml:"build,omitempty"` +} + +// Create a Configmap containing the default jib profile. +func CreateProfileConfigmap(ctx context.Context, c client.Client, kit *v1.IntegrationKit) error { + profile, err := jibMavenProfile() + if err != nil { + return fmt.Errorf("error generating default maven jib profile: %w. ", err) + } + + annotations := util.CopyMap(kit.Annotations) + controller := true + blockOwnerDeletion := true + jibProfileConfigMap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: kit.Name + "-publish-jib-profile", + Namespace: kit.Namespace, + Annotations: annotations, + Labels: map[string]string{ + v1.IntegrationKitLabel: kit.Name, + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: kit.APIVersion, + Kind: kit.Kind, + Name: kit.Name, + UID: kit.UID, + Controller: &controller, + BlockOwnerDeletion: &blockOwnerDeletion, + }}, + }, + Data: map[string]string{ + "profile.xml": profile, + }, + } + + err = c.Create(ctx, jibProfileConfigMap) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return fmt.Errorf("error creating the configmap containing the default maven jib profile: %s: %w. ", kit.Name+"-publish-jib-profile", err) + } + return nil +} + +func jibMavenProfile() (string, error) { + jibPlugin := maven.Plugin{ + GroupID: "com.google.cloud.tools", + ArtifactID: "jib-maven-plugin", + Version: "3.3.2", + Dependencies: []maven.Dependency{ + { + GroupID: "com.google.cloud.tools", + ArtifactID: "jib-layer-filter-extension-maven", + Version: "0.3.0", + }, + }, + Configuration: v1.PluginConfiguration{ + Container: v1.Container{ + Entrypoint: "INHERIT", + Args: v1.Args{ + Arg: "jshell", + }, + }, + AllowInsecureRegistries: "true", + ExtraDirectories: v1.ExtraDirectories{ + Paths: []v1.Path{ + { + From: "../context", + Into: "/deployments", + }, + }, + Permissions: []v1.Permission{ + { + File: "/deployments/*", + Mode: "544", + }, + }, + }, + PluginExtensions: v1.PluginExtensions{ + PluginExtension: v1.PluginExtension{ + Implementation: "com.google.cloud.tools.jib.maven.extension.layerfilter.JibLayerFilterExtension", + Configuration: v1.PluginExtensionConfiguration{ + Implementation: "com.google.cloud.tools.jib.maven.extension.layerfilter.Configuration", + Filters: []v1.Filter{ + { + Glob: "/app/**", + }, + }, + }, + }, + }, + }, + } + + jibMavenPluginProfile := JibProfile{ + XMLName: xml.Name{Local: "profile"}, + ID: "jib", + Build: JibBuild{ + Plugins: []maven.Plugin{jibPlugin}, + }, + } + content, err := util.EncodeXMLWithoutHeader(jibMavenPluginProfile) + if err != nil { + return "", err + } + return string(content), nil + +} diff --git a/pkg/util/jib/configuration_test.go b/pkg/util/jib/configuration_test.go new file mode 100644 index 0000000000..1593822372 --- /dev/null +++ b/pkg/util/jib/configuration_test.go @@ -0,0 +1,74 @@ +/* +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 jib + +import ( + "context" + "strings" + "testing" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/util/test" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestJibMavenProfile(t *testing.T) { + profile, err := jibMavenProfile() + + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(profile, "")) + assert.True(t, strings.HasSuffix(profile, "")) + +} + +func TestJibConfigMap(t *testing.T) { + ctx := context.TODO() + c, _ := test.NewFakeClient() + kit := &v1.IntegrationKit{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: v1.IntegrationKitKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + UID: types.UID("8dc44a2b-063c-490e-ae02-1fab285ac70a"), + }, + Status: v1.IntegrationKitStatus{ + Phase: v1.IntegrationKitPhaseBuildSubmitted, + }, + } + + err := CreateProfileConfigmap(ctx, c, kit) + assert.NoError(t, err) + + key := ctrl.ObjectKey{ + Namespace: "ns", + Name: "test-publish-jib-profile", + } + cm := &corev1.ConfigMap{} + err = c.Get(ctx, key, cm) + assert.NoError(t, err) + assert.Equal(t, cm.OwnerReferences[0].Name, "test") + assert.Equal(t, cm.OwnerReferences[0].UID, types.UID("8dc44a2b-063c-490e-ae02-1fab285ac70a")) + assert.NotNil(t, cm.Data["profile.xml"]) + +} diff --git a/pkg/util/maven/maven_command.go b/pkg/util/maven/maven_command.go index 9ba23b1b74..5c01a36fd5 100644 --- a/pkg/util/maven/maven_command.go +++ b/pkg/util/maven/maven_command.go @@ -142,7 +142,12 @@ func (c *Command) Do(ctx context.Context) error { Log.WithValues("MAVEN_OPTS", mavenOptions).Infof("executing: %s", strings.Join(cmd.Args, " ")) - return util.RunAndLog(ctx, cmd, mavenLogHandler, mavenLogHandler) + // generate maven file + if err := generateMavenContext(c.context.Path, args); err != nil { + return err + } + + return util.RunAndLog(ctx, cmd, MavenLogHandler, MavenLogHandler) } func NewContext(buildDir string) Context { @@ -243,7 +248,7 @@ func generateProjectStructure(context Context, project Project) error { func (c *Command) prepareMavenWrapper(ctx context.Context) error { cmd := exec.CommandContext(ctx, "cp", "--recursive", "/usr/share/maven/mvnw/.", ".") cmd.Dir = c.context.Path - return util.RunAndLog(ctx, cmd, mavenLogHandler, mavenLogHandler) + return util.RunAndLog(ctx, cmd, MavenLogHandler, MavenLogHandler) } // ParseGAV decodes the provided Maven GAV into the corresponding Dependency. @@ -279,3 +284,16 @@ func ParseGAV(gav string) (Dependency, error) { return dep, nil } + +// Create a MAVEN_CONTEXT file containing all arguments for a maven command. +func generateMavenContext(path string, args []string) error { + // TODO refactor maven code to avoid creating a file to pass command args + commandArgs := make([]string, 0) + for _, arg := range args { + if arg != "package" && len(strings.TrimSpace(arg)) != 0 { + commandArgs = append(commandArgs, strings.TrimSpace(arg)) + } + } + + return util.WriteToFile(filepath.Join(path, "MAVEN_CONTEXT"), strings.Join(commandArgs, " ")) +} diff --git a/pkg/util/maven/maven_log.go b/pkg/util/maven/maven_log.go index 277c267d9f..e47b15cd89 100644 --- a/pkg/util/maven/maven_log.go +++ b/pkg/util/maven/maven_log.go @@ -48,7 +48,7 @@ const ( var mavenLogger = log.WithName("maven.build") -func mavenLogHandler(s string) string { +func MavenLogHandler(s string) string { mavenLog, parseError := parseLog(s) if parseError == nil { normalizeLog(mavenLog) diff --git a/pkg/util/maven/maven_log_test.go b/pkg/util/maven/maven_log_test.go index 0cce7c61c3..d31ba257a7 100644 --- a/pkg/util/maven/maven_log_test.go +++ b/pkg/util/maven/maven_log_test.go @@ -34,7 +34,7 @@ func TestRunAndLogErrorMvn(t *testing.T) { } cmd := exec.CommandContext(context.Background(), mavenCmd, "package", "-B") - err := util.RunAndLog(context.Background(), cmd, mavenLogHandler, mavenLogHandler) + err := util.RunAndLog(context.Background(), cmd, MavenLogHandler, MavenLogHandler) assert.NotNil(t, err) assert.ErrorContains(t, err, "[ERROR] The goal you specified requires a project to execute but there is no POM in this directory") diff --git a/pkg/util/maven/maven_types.go b/pkg/util/maven/maven_types.go index 4a655a3ec9..039dab1b27 100644 --- a/pkg/util/maven/maven_types.go +++ b/pkg/util/maven/maven_types.go @@ -37,11 +37,12 @@ type Build struct { } type Plugin struct { - GroupID string `xml:"groupId"` - ArtifactID string `xml:"artifactId"` - Version string `xml:"version,omitempty"` - Executions []Execution `xml:"executions>execution,omitempty"` - Dependencies []Dependency `xml:"dependencies>dependency,omitempty"` + GroupID string `xml:"groupId"` + ArtifactID string `xml:"artifactId"` + Version string `xml:"version,omitempty"` + Executions []Execution `xml:"executions>execution,omitempty"` + Dependencies []Dependency `xml:"dependencies>dependency,omitempty"` + Configuration v1.PluginConfiguration `xml:"configuration,omitempty"` } type Execution struct { diff --git a/pkg/util/util.go b/pkg/util/util.go index 5d122dee1b..c70c71e702 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -236,9 +236,18 @@ func RandomInt63() int64 { return randomSourceUTC.Int63() } +func EncodeXMLWithoutHeader(content interface{}) ([]byte, error) { + return encodeXML(content, "") +} + func EncodeXML(content interface{}) ([]byte, error) { + + return encodeXML(content, xml.Header) +} + +func encodeXML(content interface{}, xmlHeader string) ([]byte, error) { w := &bytes.Buffer{} - w.WriteString(xml.Header) + w.WriteString(xmlHeader) e := xml.NewEncoder(w) e.Indent("", " ")