Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/modules/traits/pages/init-containers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@ The following configuration options are available:

| init-containers.initTasks
| []string
| A list of init tasks to be executed with format `<name>;<container-image>;<container-command>`.
| A list of init tasks to be executed.
Each task accepts the format `<name>;<container-image>;<container-command>` or key=value format
`name=<name>;image=<image>;command=<command>;request-cpu=<quantity>;limit-cpu=<quantity>;request-memory=<quantity>;limit-memory=<quantity>`.
Resource keys (request-cpu, limit-cpu, request-memory, limit-memory) are optional and accept Kubernetes resource quantities.

| init-containers.sideCarTasks
| []string
| A list of sidecar tasks to be executed with format `<name>;<container-image>;<container-command>`.
| A list of sidecar tasks to be executed.
Each task accepts the format `<name>;<container-image>;<container-command>` or key=value format
`name=<name>;image=<image>;command=<command>;request-cpu=<quantity>;limit-cpu=<quantity>;request-memory=<quantity>;limit-memory=<quantity>`.
Resource keys (request-cpu, limit-cpu, request-memory, limit-memory) are optional and accept Kubernetes resource quantities.

|===

Expand Down
10 changes: 8 additions & 2 deletions pkg/apis/camel/v1/trait/init_containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ package trait
type InitContainersTrait struct {
Trait `json:",inline" property:",squash"`

// A list of init tasks to be executed with format `<name>;<container-image>;<container-command>`.
// A list of init tasks to be executed.
// Each task accepts the format `<name>;<container-image>;<container-command>` or key=value format
// `name=<name>;image=<image>;command=<command>;request-cpu=<quantity>;limit-cpu=<quantity>;request-memory=<quantity>;limit-memory=<quantity>`.
// Resource keys (request-cpu, limit-cpu, request-memory, limit-memory) are optional and accept Kubernetes resource quantities.
InitTasks []string `json:"initTasks,omitempty" property:"init-tasks"`
// A list of sidecar tasks to be executed with format `<name>;<container-image>;<container-command>`.
// A list of sidecar tasks to be executed.
// Each task accepts the format `<name>;<container-image>;<container-command>` or key=value format
// `name=<name>;image=<image>;command=<command>;request-cpu=<quantity>;limit-cpu=<quantity>;request-memory=<quantity>;limit-memory=<quantity>`.
// Resource keys (request-cpu, limit-cpu, request-memory, limit-memory) are optional and accept Kubernetes resource quantities.
SidecarTasks []string `json:"sideCarTasks,omitempty" property:"sidecar-tasks"`
}
128 changes: 105 additions & 23 deletions pkg/trait/init_containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
"github.com/apache/camel-k/v2/pkg/util/defaults"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/ptr"
)

Expand All @@ -38,11 +39,15 @@ const (
)

type containerTask struct {
name string
image string
command string
isSidecar bool
env []corev1.EnvVar
name string
image string
command string
isSidecar bool
requestCPU string
requestMemory string
limitCPU string
limitMemory string
env []corev1.EnvVar
}

type initContainersTrait struct {
Expand Down Expand Up @@ -183,6 +188,24 @@ func (t *initContainersTrait) configureContainers(containers *[]corev1.Container
if task.isSidecar {
initCont.RestartPolicy = ptr.To(corev1.ContainerRestartPolicyAlways)
}
if task.requestCPU != "" || task.requestMemory != "" || task.limitCPU != "" || task.limitMemory != "" {
initCont.Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{},
Limits: corev1.ResourceList{},
}
if task.requestCPU != "" {
initCont.Resources.Requests[corev1.ResourceCPU] = resource.MustParse(task.requestCPU)
}
if task.requestMemory != "" {
initCont.Resources.Requests[corev1.ResourceMemory] = resource.MustParse(task.requestMemory)
}
if task.limitCPU != "" {
initCont.Resources.Limits[corev1.ResourceCPU] = resource.MustParse(task.limitCPU)
}
if task.limitMemory != "" {
initCont.Resources.Limits[corev1.ResourceMemory] = resource.MustParse(task.limitMemory)
}
}
*containers = append(*containers, initCont)
}
}
Expand All @@ -194,31 +217,90 @@ func (t *initContainersTrait) parseTasks() (bool, error) {
t.tasks = make([]containerTask, len(t.InitTasks)+len(t.SidecarTasks))
i := 0
for _, task := range t.InitTasks {
split := strings.SplitN(task, ";", 3)
if len(split) != 3 {
return false, fmt.Errorf(`could not parse init container task "%s": format expected "name;container-image;command"`, task)
}
t.tasks[i] = containerTask{
name: split[0],
image: split[1],
command: split[2],
isSidecar: false,
parsed, err := parseSingleTask(task, false)
if err != nil {
return false, fmt.Errorf("could not parse init container task %q: %w", task, err)
}
t.tasks[i] = parsed
i++
}
for _, task := range t.SidecarTasks {
split := strings.SplitN(task, ";", 3)
if len(split) != 3 {
return false, fmt.Errorf(`could not parse sidecar container task "%s": format expected "name;container-image;command"`, task)
}
t.tasks[i] = containerTask{
name: split[0],
image: split[1],
command: split[2],
isSidecar: true,
parsed, err := parseSingleTask(task, true)
if err != nil {
return false, fmt.Errorf("could not parse sidecar container task %q: %w", task, err)
}
t.tasks[i] = parsed
i++
}

return true, nil
}

func parseSingleTask(task string, isSidecar bool) (containerTask, error) {
segments := strings.Split(task, ";")

var result containerTask
result.isSidecar = isSidecar

var commandParts []string

for _, seg := range segments {
if len(strings.TrimSpace(seg)) == 0 {
continue
}
if strings.Contains(seg, "=") {
kv := strings.SplitN(seg, "=", 2)
key, value := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])
switch key {
case "name":
result.name = value
case "image":
result.image = value
case "command":
commandParts = append(commandParts, value)
case "request-cpu":
if _, err := resource.ParseQuantity(value); err != nil {
return containerTask{}, fmt.Errorf("invalid request-cpu value %q: %w", value, err)
}
result.requestCPU = value
case "request-memory":
if _, err := resource.ParseQuantity(value); err != nil {
return containerTask{}, fmt.Errorf("invalid request-memory value %q: %w", value, err)
}
result.requestMemory = value
case "limit-cpu":
if _, err := resource.ParseQuantity(value); err != nil {
return containerTask{}, fmt.Errorf("invalid limit-cpu value %q: %w", value, err)
}
result.limitCPU = value
case "limit-memory":
if _, err := resource.ParseQuantity(value); err != nil {
return containerTask{}, fmt.Errorf("invalid limit-memory value %q: %w", value, err)
}
result.limitMemory = value
default:
// Forward compatibility: unknown keys are appended to command
commandParts = append(commandParts, seg)
}
} else {
// Positional segment: fill first unset field
if result.name == "" {
result.name = seg
} else if result.image == "" {
result.image = seg
} else {
commandParts = append(commandParts, seg)
}
}
}

if len(commandParts) > 0 {
result.command = strings.Join(commandParts, ";")
}

if result.name == "" || result.image == "" {
return containerTask{}, fmt.Errorf("name and image are required (format: %q)", "name;image;command or name=...;image=...")
}

return result, nil
}
Loading