forked from LaconicNetwork/kompose
Generate template placeholders for env vars and images in charts
This commit is contained in:
parent
fb0539e64c
commit
3074b5451d
@ -261,7 +261,7 @@ func Convert(opt kobject.ConvertOptions) ([]runtime.Object, error) {
|
||||
}
|
||||
|
||||
// Print output
|
||||
err = kubernetes.PrintList(objects, opt)
|
||||
err = kubernetes.PrintList(objects, opt, komposeObject)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
@ -82,10 +82,157 @@ type DeploymentMapping struct {
|
||||
TargetDeploymentName string
|
||||
}
|
||||
|
||||
// ServiceValues holds the extracted values for templating
|
||||
type ServiceValues struct {
|
||||
Image struct {
|
||||
Repository string
|
||||
Tag string
|
||||
PullPolicy string
|
||||
}
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// splitImage splits "repo:tag" or "repo" into repository and tag
|
||||
func splitImage(image string) (string, string) {
|
||||
parts := strings.Split(image, ":")
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
// No tag specified, use "latest"
|
||||
return image, "latest"
|
||||
}
|
||||
|
||||
// extractImageValues extracts image information from Deployment and StatefulSet objects
|
||||
func extractImageValues(objects []runtime.Object) map[string]ServiceValues {
|
||||
values := make(map[string]ServiceValues)
|
||||
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *appsv1.Deployment:
|
||||
serviceName := o.ObjectMeta.Name
|
||||
if len(o.Spec.Template.Spec.Containers) > 0 {
|
||||
container := o.Spec.Template.Spec.Containers[0]
|
||||
|
||||
repo, tag := splitImage(container.Image)
|
||||
pullPolicy := string(container.ImagePullPolicy)
|
||||
if pullPolicy == "" {
|
||||
pullPolicy = "IfNotPresent"
|
||||
}
|
||||
|
||||
svcValues := ServiceValues{}
|
||||
svcValues.Image.Repository = repo
|
||||
svcValues.Image.Tag = tag
|
||||
svcValues.Image.PullPolicy = pullPolicy
|
||||
|
||||
// Extract env vars
|
||||
svcValues.Env = make(map[string]string)
|
||||
for _, envVar := range container.Env {
|
||||
svcValues.Env[envVar.Name] = envVar.Value
|
||||
}
|
||||
|
||||
values[serviceName] = svcValues
|
||||
}
|
||||
|
||||
case *appsv1.StatefulSet:
|
||||
serviceName := o.ObjectMeta.Name
|
||||
if len(o.Spec.Template.Spec.Containers) > 0 {
|
||||
container := o.Spec.Template.Spec.Containers[0]
|
||||
|
||||
repo, tag := splitImage(container.Image)
|
||||
pullPolicy := string(container.ImagePullPolicy)
|
||||
if pullPolicy == "" {
|
||||
pullPolicy = "IfNotPresent"
|
||||
}
|
||||
|
||||
svcValues := ServiceValues{}
|
||||
svcValues.Image.Repository = repo
|
||||
svcValues.Image.Tag = tag
|
||||
svcValues.Image.PullPolicy = pullPolicy
|
||||
|
||||
// Extract env vars
|
||||
svcValues.Env = make(map[string]string)
|
||||
for _, envVar := range container.Env {
|
||||
svcValues.Env[envVar.Name] = envVar.Value
|
||||
}
|
||||
|
||||
values[serviceName] = svcValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// templatizeImage replaces image and imagePullPolicy with Helm template syntax
|
||||
func templatizeImage(yamlBytes []byte, serviceName string, values map[string]ServiceValues) []byte {
|
||||
if svcValues, ok := values[serviceName]; ok {
|
||||
yamlStr := string(yamlBytes)
|
||||
|
||||
// Replace image line
|
||||
originalImage := svcValues.Image.Repository + ":" + svcValues.Image.Tag
|
||||
templatedImage := "{{ .Values." + serviceName + ".image.repository }}:{{ .Values." + serviceName + ".image.tag }}"
|
||||
yamlStr = strings.Replace(yamlStr, "image: "+originalImage, "image: "+templatedImage, 1)
|
||||
|
||||
// Replace imagePullPolicy line
|
||||
if svcValues.Image.PullPolicy != "" {
|
||||
originalPolicy := "imagePullPolicy: " + svcValues.Image.PullPolicy
|
||||
templatedPolicy := "imagePullPolicy: {{ .Values." + serviceName + ".image.pullPolicy }}"
|
||||
yamlStr = strings.Replace(yamlStr, originalPolicy, templatedPolicy, 1)
|
||||
}
|
||||
|
||||
return []byte(yamlStr)
|
||||
}
|
||||
return yamlBytes
|
||||
}
|
||||
|
||||
// templatizeEnv replaces env var values with Helm template syntax
|
||||
func templatizeEnv(yamlBytes []byte, serviceName string, values map[string]ServiceValues) []byte {
|
||||
if svcValues, ok := values[serviceName]; ok {
|
||||
yamlStr := string(yamlBytes)
|
||||
|
||||
for envName, envValue := range svcValues.Env {
|
||||
template := "{{ .Values." + serviceName + ".env." + envName + " | quote }}"
|
||||
|
||||
if envValue != "" {
|
||||
// Match: name: ENVNAME\n<spaces>value: "VALUE" or value: VALUE
|
||||
// Replace with: name: ENVNAME\n<spaces>value: TEMPLATE
|
||||
pattern := `(name: ` + regexp.QuoteMeta(envName) + `)\n(\s+)value: (?:"` + regexp.QuoteMeta(envValue) + `"|` + regexp.QuoteMeta(envValue) + `)`
|
||||
replacement := `${1}` + "\n" + `${2}value: ` + template
|
||||
re := regexp.MustCompile(pattern)
|
||||
yamlStr = re.ReplaceAllString(yamlStr, replacement)
|
||||
} else {
|
||||
// For empty values, handle two cases:
|
||||
// 1. name: ENVNAME\n<spaces>value: ""
|
||||
// 2. name: ENVNAME\n - name: (no value field)
|
||||
|
||||
// Try to replace existing empty value first
|
||||
pattern1 := `(name: ` + regexp.QuoteMeta(envName) + `)\n(\s+)value: ""`
|
||||
replacement1 := `${1}` + "\n" + `${2}value: ` + template
|
||||
re1 := regexp.MustCompile(pattern1)
|
||||
newYamlStr := re1.ReplaceAllString(yamlStr, replacement1)
|
||||
|
||||
// If nothing was replaced, insert value field after name
|
||||
if newYamlStr == yamlStr {
|
||||
// Match the current list item with its indentation
|
||||
pattern2 := `(\s+)- (name: ` + regexp.QuoteMeta(envName) + `)\n`
|
||||
replacement2 := "${1}- ${2}${1} value: " + template + "\n"
|
||||
re2 := regexp.MustCompile(pattern2)
|
||||
yamlStr = re2.ReplaceAllString(yamlStr, replacement2)
|
||||
} else {
|
||||
yamlStr = newYamlStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(yamlStr)
|
||||
}
|
||||
return yamlBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Helm Chart configuration
|
||||
*/
|
||||
func generateHelm(dirName string) error {
|
||||
func generateHelm(dirName string, values map[string]ServiceValues) error {
|
||||
type ChartDetails struct {
|
||||
Name string
|
||||
}
|
||||
@ -141,10 +288,61 @@ home:
|
||||
return err
|
||||
}
|
||||
|
||||
/* Create the values.yaml file */
|
||||
if len(values) > 0 {
|
||||
valuesYAML, err := generateValuesYAML(values)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to generate values.yaml")
|
||||
}
|
||||
err = os.WriteFile(dirName+string(os.PathSeparator)+"values.yaml", valuesYAML, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("chart created in %q\n", dirName+string(os.PathSeparator))
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateValuesYAML creates values.yaml content from extracted values
|
||||
func generateValuesYAML(values map[string]ServiceValues) ([]byte, error) {
|
||||
// Build hierarchical structure: serviceName -> image -> {repository, tag, pullPolicy}
|
||||
valuesMap := make(map[string]interface{})
|
||||
|
||||
// Sort service names for consistent output
|
||||
serviceNames := make([]string, 0, len(values))
|
||||
for name := range values {
|
||||
serviceNames = append(serviceNames, name)
|
||||
}
|
||||
sort.Strings(serviceNames)
|
||||
|
||||
for _, serviceName := range serviceNames {
|
||||
svcValues := values[serviceName]
|
||||
serviceMap := map[string]interface{}{
|
||||
"image": map[string]string{
|
||||
"repository": svcValues.Image.Repository,
|
||||
"tag": svcValues.Image.Tag,
|
||||
"pullPolicy": svcValues.Image.PullPolicy,
|
||||
},
|
||||
}
|
||||
|
||||
// Add env vars if present
|
||||
if len(svcValues.Env) > 0 {
|
||||
serviceMap["env"] = svcValues.Env
|
||||
}
|
||||
|
||||
valuesMap[serviceName] = serviceMap
|
||||
}
|
||||
|
||||
// Use marshalWithIndent for consistent 2-space indentation
|
||||
yamlBytes, err := marshalWithIndent(valuesMap, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return yamlBytes, nil
|
||||
}
|
||||
|
||||
// Check if given path is a directory
|
||||
func isDir(name string) (bool, error) {
|
||||
// Open file to get stat later
|
||||
@ -183,7 +381,7 @@ func getDirName(opt kobject.ConvertOptions) string {
|
||||
}
|
||||
|
||||
// PrintList will take the data converted and decide on the commandline attributes given
|
||||
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
||||
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions, komposeObject kobject.KomposeObject) error {
|
||||
var f *os.File
|
||||
dirName := getDirName(opt)
|
||||
log.Debugf("Target Dir: %s", dirName)
|
||||
@ -215,6 +413,13 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
||||
}
|
||||
|
||||
var files []string
|
||||
var imageValues map[string]ServiceValues
|
||||
|
||||
// Extract image values for chart templating (before processing)
|
||||
if opt.CreateChart {
|
||||
imageValues = extractImageValues(objects)
|
||||
}
|
||||
|
||||
// if asked to print to stdout or to put in single file
|
||||
// we will create a list
|
||||
if opt.ToStdout || f != nil {
|
||||
@ -265,6 +470,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
||||
var typeMeta metav1.TypeMeta
|
||||
var objectMeta metav1.ObjectMeta
|
||||
|
||||
// Get object metadata first for templating
|
||||
if us, ok := v.(*unstructured.Unstructured); ok {
|
||||
typeMeta = metav1.TypeMeta{
|
||||
Kind: us.GetKind(),
|
||||
@ -275,15 +481,16 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
||||
}
|
||||
} else {
|
||||
val := reflect.ValueOf(v).Elem()
|
||||
// Use reflect to access TypeMeta struct inside runtime.Object.
|
||||
// cast it to correct type - metav1.TypeMeta
|
||||
typeMeta = val.FieldByName("TypeMeta").Interface().(metav1.TypeMeta)
|
||||
|
||||
// Use reflect to access ObjectMeta struct inside runtime.Object.
|
||||
// cast it to correct type - api.ObjectMeta
|
||||
objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
|
||||
}
|
||||
|
||||
// Templatize YAML if generating chart
|
||||
if opt.CreateChart && imageValues != nil {
|
||||
data = templatizeEnv(data, objectMeta.Name, imageValues)
|
||||
data = templatizeImage(data, objectMeta.Name, imageValues)
|
||||
}
|
||||
|
||||
file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "transformer.Print failed")
|
||||
@ -293,7 +500,7 @@ func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
|
||||
}
|
||||
}
|
||||
if opt.CreateChart {
|
||||
err = generateHelm(dirName)
|
||||
err = generateHelm(dirName, imageValues)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generateHelm failed")
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user