| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- /*
- Copyright 2026 LocoStack.
- Licensed 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 reconciler
- import (
- "context"
- "fmt"
- "strings"
- "github.com/LocoStack/loco-operator/api/v1alpha1"
- corev1 "k8s.io/api/core/v1"
- "sigs.k8s.io/controller-runtime/pkg/client"
- )
- type ComponentReconciler interface {
- // create all resources for the component, and return the list of dependencies
- ReconcileComponent(ctx context.Context, tmpl *v1alpha1.Template, variables map[string]string) ([]client.Object, error)
- ComponentAvailable(ctx context.Context) bool
- ResourceName(name string) string
- }
- func ResourceLabels(stack, component, name, namespace string) map[string]string {
- labels := map[string]string{
- "app.kubernetes.io/managed-by": "loco-operator",
- "locostack.com/stack": stack,
- "locostack.com/component": component,
- "locostack.com/name": name,
- }
- if namespace != "" {
- labels["locostack.com/namespace"] = namespace
- }
- return labels
- }
- // substituteVariables replaces placeholders in the input string with actual values from the variables map.
- // Placeholders are in the format $(placeholder).
- func substituteVariables(variables map[string]string, input string) (bool, string) {
- for placeholder, value := range variables {
- if !strings.Contains(input, "$(") {
- return true, input
- }
- if strings.Contains(input, fmt.Sprintf("$(%s)", placeholder)) {
- input = strings.ReplaceAll(input, fmt.Sprintf("$(%s)", placeholder), value)
- }
- }
- return false, input
- }
- func mustSubstituteVariables(variables map[string]string, input string) string {
- _, substituted := substituteVariables(variables, input)
- return substituted
- }
- func buildVolumes(variables map[string]string, tmpl *v1alpha1.Template) []corev1.Volume {
- volumes := make([]corev1.Volume, len(tmpl.Spec.Volumes))
- if len(volumes) == 0 {
- volumes = nil
- } else {
- for i, vol := range tmpl.Spec.Volumes {
- volumes[i] = vol
- if vol.VolumeSource.ConfigMap != nil {
- volumes[i].VolumeSource.ConfigMap.LocalObjectReference.Name = mustSubstituteVariables(variables, vol.VolumeSource.ConfigMap.LocalObjectReference.Name)
- }
- if vol.VolumeSource.Secret != nil {
- volumes[i].VolumeSource.Secret.SecretName = mustSubstituteVariables(variables, vol.VolumeSource.Secret.SecretName)
- }
- if vol.VolumeSource.PersistentVolumeClaim != nil {
- volumes[i].VolumeSource.PersistentVolumeClaim.ClaimName = mustSubstituteVariables(variables, vol.VolumeSource.PersistentVolumeClaim.ClaimName)
- }
- }
- }
- return volumes
- }
- func buildContainerSpec(variables map[string]string, tmpl *v1alpha1.Template) corev1.Container {
- command := make([]string, len(tmpl.Spec.Runtime.Command))
- if len(command) == 0 {
- command = nil
- } else {
- for i, cmd := range tmpl.Spec.Runtime.Command {
- command[i] = mustSubstituteVariables(variables, cmd)
- }
- }
- args := make([]string, len(tmpl.Spec.Runtime.Args))
- if len(args) == 0 {
- args = nil
- } else {
- for i, arg := range tmpl.Spec.Runtime.Args {
- args[i] = mustSubstituteVariables(variables, arg)
- }
- for _, arg := range tmpl.Spec.Runtime.ExtraArgs {
- args = append(args, mustSubstituteVariables(variables, arg))
- }
- for _, arg := range tmpl.Spec.Runtime.ConditionalArgs {
- complete, substituted := substituteVariables(variables, arg.When)
- if complete && substituted != "" {
- for _, a := range arg.Args {
- args = append(args, mustSubstituteVariables(variables, a))
- }
- }
- }
- }
- env := make([]corev1.EnvVar, len(tmpl.Spec.Runtime.Env))
- if len(env) == 0 {
- env = nil
- } else {
- for i, envVar := range tmpl.Spec.Runtime.Env {
- env[i] = envVar
- env[i].Value = mustSubstituteVariables(variables, envVar.Value)
- if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
- env[i].ValueFrom.SecretKeyRef.LocalObjectReference.Name = mustSubstituteVariables(variables, envVar.ValueFrom.SecretKeyRef.LocalObjectReference.Name)
- env[i].ValueFrom.SecretKeyRef.Key = mustSubstituteVariables(variables, envVar.ValueFrom.SecretKeyRef.Key)
- }
- }
- }
- envFrom := make([]corev1.EnvFromSource, len(tmpl.Spec.Runtime.EnvFrom))
- if len(envFrom) == 0 {
- envFrom = nil
- } else {
- for i, envFromSource := range tmpl.Spec.Runtime.EnvFrom {
- envFrom[i] = envFromSource
- if envFromSource.ConfigMapRef != nil {
- envFrom[i].ConfigMapRef.LocalObjectReference.Name = mustSubstituteVariables(variables, envFromSource.ConfigMapRef.LocalObjectReference.Name)
- }
- if envFromSource.SecretRef != nil {
- envFrom[i].SecretRef.LocalObjectReference.Name = mustSubstituteVariables(variables, envFromSource.SecretRef.LocalObjectReference.Name)
- }
- }
- }
- ports := make([]corev1.ContainerPort, 0)
- if tmpl.Spec.Runtime.Port != 0 {
- ports = append(ports, corev1.ContainerPort{
- ContainerPort: tmpl.Spec.Runtime.Port,
- Protocol: corev1.ProtocolTCP,
- })
- }
- return corev1.Container{
- Name: "main",
- Image: tmpl.Spec.Runtime.Image,
- ImagePullPolicy: corev1.PullIfNotPresent,
- Command: command,
- Args: args,
- Env: env,
- EnvFrom: envFrom,
- VolumeMounts: tmpl.Spec.Runtime.VolumeMounts,
- Ports: ports,
- Resources: tmpl.Spec.Resources,
- }
- }
- func buildPodSpec(variables map[string]string, tmpl *v1alpha1.Template) (corev1.PodSpec, error) {
- return corev1.PodSpec{
- NodeSelector: tmpl.Spec.NodeSelector,
- Affinity: tmpl.Spec.Affinity,
- Tolerations: tmpl.Spec.Tolerations,
- TopologySpreadConstraints: tmpl.Spec.TopologySpreadConstraints,
- Containers: []corev1.Container{buildContainerSpec(variables, tmpl)},
- SecurityContext: tmpl.Spec.SecurityContext,
- Volumes: buildVolumes(variables, tmpl),
- }, nil
- }
|