forked from LaconicNetwork/kompose
Fix PVC handling for StatefulSets
This commit is contained in:
parent
e6b323eedb
commit
f2688ca2cf
@ -606,7 +606,13 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos
|
|||||||
komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig
|
komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
handleVolume(&komposeObject, &composeObject.Volumes)
|
// Normalize volume names in the volumes map so lookups work with normalized names
|
||||||
|
normalizedVolumes := make(types.Volumes)
|
||||||
|
for volName, volConfig := range composeObject.Volumes {
|
||||||
|
normalizedVolumes[normalizeVolumes(volName)] = volConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVolume(&komposeObject, &normalizedVolumes)
|
||||||
return komposeObject, nil
|
return komposeObject, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,7 +89,8 @@ type ServiceValues struct {
|
|||||||
Tag string
|
Tag string
|
||||||
PullPolicy string
|
PullPolicy string
|
||||||
}
|
}
|
||||||
Env map[string]string
|
Env map[string]string
|
||||||
|
Replicas int `yaml:"replicas,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PersistenceValues struct {
|
type PersistenceValues struct {
|
||||||
@ -134,62 +135,79 @@ func unquoteHelmTemplates(yamlBytes []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// templatePVCStorage replaces PVC storage values with Helm templates
|
// templatePVCStorage replaces PVC storage values with Helm templates
|
||||||
// TODO: This post-processes YAML because resource.Quantity validates input and rejects template strings
|
// This post-processes YAML because resource.Quantity validates input and rejects template strings
|
||||||
func templatePVCStorage(yamlBytes []byte, persistenceValues map[string]PersistenceValues) []byte {
|
func templatePVCStorage(yamlBytes []byte, persistenceValues map[string]PersistenceValues) []byte {
|
||||||
yamlStr := string(yamlBytes)
|
yamlStr := string(yamlBytes)
|
||||||
|
|
||||||
// Check if this is a PersistentVolumeClaim
|
// Only process PersistentVolumeClaim or StatefulSet with volumeClaimTemplates
|
||||||
if !strings.Contains(yamlStr, "kind: PersistentVolumeClaim") {
|
if !strings.Contains(yamlStr, "kind: PersistentVolumeClaim") &&
|
||||||
|
!(strings.Contains(yamlStr, "kind: StatefulSet") && strings.Contains(yamlStr, "volumeClaimTemplates:")) {
|
||||||
return yamlBytes
|
return yamlBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract PVC name from metadata
|
|
||||||
var pvcName string
|
|
||||||
lines := strings.Split(yamlStr, "\n")
|
lines := strings.Split(yamlStr, "\n")
|
||||||
|
var pvcName string
|
||||||
|
inVolumeClaimTemplates := false
|
||||||
inMetadata := false
|
inMetadata := false
|
||||||
|
|
||||||
|
// Extract PVC name from metadata
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
if trimmed == "metadata:" {
|
|
||||||
|
if strings.Contains(trimmed, "volumeClaimTemplates:") {
|
||||||
|
inVolumeClaimTemplates = true
|
||||||
|
} else if (trimmed == "metadata:" || trimmed == "- metadata:") && (inVolumeClaimTemplates || !strings.Contains(yamlStr, "volumeClaimTemplates:")) {
|
||||||
inMetadata = true
|
inMetadata = true
|
||||||
continue
|
} else if inMetadata && strings.HasPrefix(trimmed, "name:") {
|
||||||
}
|
if parts := strings.SplitN(trimmed, ":", 2); len(parts) == 2 {
|
||||||
if inMetadata && strings.HasPrefix(trimmed, "name:") {
|
|
||||||
parts := strings.SplitN(trimmed, ":", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
pvcName = strings.TrimSpace(parts[1])
|
pvcName = strings.TrimSpace(parts[1])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Exit metadata section if we hit another top-level key
|
|
||||||
if inMetadata && len(line) > 0 && line[0] != ' ' && line[0] != '\t' && !strings.HasPrefix(trimmed, "name:") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found the PVC name and it's in our persistence values, template it
|
// Template the storage value if PVC name is in persistence values
|
||||||
if pvcName != "" {
|
if pvcName != "" {
|
||||||
if _, exists := persistenceValues[pvcName]; exists {
|
if _, exists := persistenceValues[pvcName]; exists {
|
||||||
templateStr := "{{ index .Values \"persistence\" \"" + pvcName + "\" \"size\" }}"
|
templateStr := "{{ index .Values \"persistence\" \"" + pvcName + "\" \"size\" }}"
|
||||||
|
|
||||||
// Replace storage value with template
|
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
if strings.Contains(line, "storage:") {
|
if strings.Contains(line, "storage:") {
|
||||||
log.Debugf("Found storage line: %q", line)
|
|
||||||
prefix := line[:strings.Index(line, ":")+2]
|
prefix := line[:strings.Index(line, ":")+2]
|
||||||
log.Debugf("Prefix: %q, Template: %q", prefix, templateStr)
|
|
||||||
lines[i] = prefix + templateStr
|
lines[i] = prefix + templateStr
|
||||||
log.Debugf("New line: %q", lines[i])
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yamlStr = strings.Join(lines, "\n")
|
yamlStr = strings.Join(lines, "\n")
|
||||||
log.Debugf("Final templated YAML:\n%s", yamlStr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []byte(yamlStr)
|
return []byte(yamlStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// templateReplicas replaces hardcoded replica values with Helm templates
|
||||||
|
// This post-processes YAML because Replicas is *int32 and rejects template strings
|
||||||
|
func templateReplicas(yamlBytes []byte, serviceName string) []byte {
|
||||||
|
yamlStr := string(yamlBytes)
|
||||||
|
|
||||||
|
// Only process Deployment or StatefulSet (DaemonSet doesn't use replicas)
|
||||||
|
if !strings.Contains(yamlStr, "kind: Deployment") && !strings.Contains(yamlStr, "kind: StatefulSet") {
|
||||||
|
return yamlBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(yamlStr, "\n")
|
||||||
|
templateStr := "{{ " + helmValuesPath(serviceName, "replicas") + " | default 1 }}"
|
||||||
|
|
||||||
|
for i, line := range lines {
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(line), "replicas:") {
|
||||||
|
indentEnd := strings.Index(line, "replicas:")
|
||||||
|
lines[i] = line[:indentEnd] + "replicas: " + templateStr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
// extractValuesFromKomposeObject extracts service values from KomposeObject for values.yaml
|
// extractValuesFromKomposeObject extracts service values from KomposeObject for values.yaml
|
||||||
// Note: PVC values are extracted during transformation and stored in Kubernetes.PersistenceValues
|
// Note: PVC values are extracted during transformation and stored in Kubernetes.PersistenceValues
|
||||||
func extractValuesFromKomposeObject(komposeObject kobject.KomposeObject) (map[string]ServiceValues, map[string]PersistenceValues) {
|
func extractValuesFromKomposeObject(komposeObject kobject.KomposeObject) (map[string]ServiceValues, map[string]PersistenceValues) {
|
||||||
@ -216,6 +234,13 @@ func extractValuesFromKomposeObject(komposeObject kobject.KomposeObject) (map[st
|
|||||||
svcValues.Env[envVar.Name] = envVar.Value
|
svcValues.Env[envVar.Name] = envVar.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract replicas (default to 1 if not set)
|
||||||
|
if service.Replicas > 0 {
|
||||||
|
svcValues.Replicas = service.Replicas
|
||||||
|
} else {
|
||||||
|
svcValues.Replicas = 1
|
||||||
|
}
|
||||||
|
|
||||||
serviceValues[serviceName] = svcValues
|
serviceValues[serviceName] = svcValues
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,6 +349,9 @@ func generateValuesYAML(serviceValues map[string]ServiceValues, persistenceValue
|
|||||||
serviceMap["env"] = svcValues.Env
|
serviceMap["env"] = svcValues.Env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add replicas
|
||||||
|
serviceMap["replicas"] = svcValues.Replicas
|
||||||
|
|
||||||
valuesMap[serviceName] = serviceMap
|
valuesMap[serviceName] = serviceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,6 +545,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObje
|
|||||||
if opt.CreateChart {
|
if opt.CreateChart {
|
||||||
data = unquoteHelmTemplates(data)
|
data = unquoteHelmTemplates(data)
|
||||||
data = templatePVCStorage(data, persistenceValues)
|
data = templatePVCStorage(data, persistenceValues)
|
||||||
|
data = templateReplicas(data, objectMeta.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
|
file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
|
||||||
@ -828,7 +857,15 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
|||||||
volumesMount = append(volumesMount, TmpVolumesMount...)
|
volumesMount = append(volumesMount, TmpVolumesMount...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pvc != nil && opt.Controller != StatefulStateController {
|
// Check if service uses StatefulSet controller (either from opt or from label)
|
||||||
|
isStatefulSet := opt.Controller == StatefulStateController
|
||||||
|
if !isStatefulSet {
|
||||||
|
if controllerType, ok := service.Labels[compose.LabelControllerType]; ok {
|
||||||
|
isStatefulSet = (controllerType == StatefulStateController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pvc != nil && !isStatefulSet {
|
||||||
// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
|
// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
|
||||||
// because the type of objects and pvc is different, but when doing append
|
// because the type of objects and pvc is different, but when doing append
|
||||||
// one element at a time it gets converted to runtime.Object for objects slice
|
// one element at a time it gets converted to runtime.Object for objects slice
|
||||||
@ -904,7 +941,8 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if initPvc != nil {
|
// Skip PVCs for init containers if parent service is StatefulSet
|
||||||
|
if initPvc != nil && !isStatefulSet {
|
||||||
for _, p := range initPvc {
|
for _, p := range initPvc {
|
||||||
if !existingObjectNames[p.Name] {
|
if !existingObjectNames[p.Name] {
|
||||||
*objects = append(*objects, p)
|
*objects = append(*objects, p)
|
||||||
@ -1115,8 +1153,26 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
|
|||||||
persistentVolumeClaims[i] = *persistentVolumeClaim
|
persistentVolumeClaims[i] = *persistentVolumeClaim
|
||||||
persistentVolumeClaims[i].APIVersion = ""
|
persistentVolumeClaims[i].APIVersion = ""
|
||||||
persistentVolumeClaims[i].Kind = ""
|
persistentVolumeClaims[i].Kind = ""
|
||||||
|
|
||||||
|
// Store PVC size for helm chart values (for volumeClaimTemplates)
|
||||||
|
if opt.CreateChart {
|
||||||
|
pvcName := persistentVolumeClaim.Name
|
||||||
|
if storageQty, ok := persistentVolumeClaim.Spec.Resources.Requests[api.ResourceStorage]; ok {
|
||||||
|
k.PersistenceValues[pvcName] = storageQty.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
objType.Spec.VolumeClaimTemplates = persistentVolumeClaims
|
objType.Spec.VolumeClaimTemplates = persistentVolumeClaims
|
||||||
|
|
||||||
|
// Remove PVC volumes from pod spec since they come from volumeClaimTemplates
|
||||||
|
// Keep only non-PVC volumes (ConfigMaps, Secrets, EmptyDir, HostPath, etc.)
|
||||||
|
var filteredVolumes []api.Volume
|
||||||
|
for _, vol := range objType.Spec.Template.Spec.Volumes {
|
||||||
|
if vol.PersistentVolumeClaim == nil {
|
||||||
|
filteredVolumes = append(filteredVolumes, vol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objType.Spec.Template.Spec.Volumes = filteredVolumes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user