2023-12-06 15:24:02 +01:00
|
|
|
package generator
|
|
|
|
|
|
|
|
import (
|
2024-12-03 14:37:13 +01:00
|
|
|
"fmt"
|
2023-12-06 15:24:02 +01:00
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
2024-12-03 13:50:58 +01:00
|
|
|
"unicode/utf8"
|
2023-12-06 15:24:02 +01:00
|
|
|
|
2025-08-19 23:58:51 +02:00
|
|
|
"katenary.io/internal/generator/labels"
|
|
|
|
"katenary.io/internal/generator/labels/labelstructs"
|
|
|
|
"katenary.io/internal/utils"
|
2025-08-19 23:09:50 +02:00
|
|
|
|
2023-12-06 15:24:02 +01:00
|
|
|
"github.com/compose-spec/compose-go/types"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
)
|
|
|
|
|
2024-10-17 17:08:42 +02:00
|
|
|
// FileMapUsage is the usage of the filemap.
|
|
|
|
type FileMapUsage uint8
|
|
|
|
|
|
|
|
// FileMapUsage constants.
|
|
|
|
const (
|
|
|
|
FileMapUsageConfigMap FileMapUsage = iota // pure configmap for key:values.
|
|
|
|
FileMapUsageFiles // files in a configmap.
|
2023-12-06 15:24:02 +01:00
|
|
|
)
|
|
|
|
|
2024-10-17 17:08:42 +02:00
|
|
|
// only used to check interface implementation
|
|
|
|
var (
|
|
|
|
_ DataMap = (*ConfigMap)(nil)
|
|
|
|
_ Yaml = (*ConfigMap)(nil)
|
2023-12-06 15:24:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// ConfigMap is a kubernetes ConfigMap.
|
|
|
|
// Implements the DataMap interface.
|
|
|
|
type ConfigMap struct {
|
|
|
|
*corev1.ConfigMap
|
|
|
|
service *types.ServiceConfig
|
|
|
|
path string
|
2024-05-06 21:11:36 +02:00
|
|
|
usage FileMapUsage
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewConfigMap creates a new ConfigMap from a compose service. The appName is the name of the application taken from the project name.
|
|
|
|
// The ConfigMap is filled by environment variables and labels "map-env".
|
2024-10-17 17:08:42 +02:00
|
|
|
func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *ConfigMap {
|
2023-12-06 15:24:02 +01:00
|
|
|
done := map[string]bool{}
|
|
|
|
drop := map[string]bool{}
|
|
|
|
labelValues := []string{}
|
|
|
|
|
|
|
|
cm := &ConfigMap{
|
|
|
|
service: &service,
|
|
|
|
ConfigMap: &corev1.ConfigMap{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
Kind: "ConfigMap",
|
|
|
|
APIVersion: "v1",
|
|
|
|
},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: utils.TplName(service.Name, appName),
|
|
|
|
Labels: GetLabels(service.Name, appName),
|
|
|
|
Annotations: Annotations,
|
|
|
|
},
|
|
|
|
Data: make(map[string]string),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the secrets from the labels
|
2025-07-06 14:34:16 +02:00
|
|
|
secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
|
2024-05-06 21:11:36 +02:00
|
|
|
if err != nil {
|
2024-04-24 23:06:45 +02:00
|
|
|
log.Fatal(err)
|
2024-05-06 21:11:36 +02:00
|
|
|
}
|
|
|
|
// drop the secrets from the environment
|
|
|
|
for _, secret := range secrets {
|
|
|
|
drop[secret] = true
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
// get the label values from the labels
|
2024-11-18 17:12:12 +01:00
|
|
|
varDescriptons := utils.GetValuesFromLabel(service, labels.LabelValues)
|
2023-12-06 15:24:02 +01:00
|
|
|
for value := range varDescriptons {
|
|
|
|
labelValues = append(labelValues, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// change the environment variables to the values defined in the values.yaml
|
|
|
|
for _, value := range labelValues {
|
|
|
|
if _, ok := service.Environment[value]; !ok {
|
|
|
|
done[value] = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-23 16:17:01 +02:00
|
|
|
if !forFile {
|
2024-10-17 17:08:42 +02:00
|
|
|
// do not bind env variables to the configmap
|
2024-10-23 16:17:01 +02:00
|
|
|
// remove the variables that are already defined in the environment
|
2024-11-18 17:12:12 +01:00
|
|
|
if l, ok := service.Labels[labels.LabelMapEnv]; ok {
|
2025-07-06 14:34:16 +02:00
|
|
|
envmap, err := labelstructs.MapEnvFrom(l)
|
2024-10-23 16:17:01 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Error parsing map-env", err)
|
|
|
|
}
|
|
|
|
for key, value := range envmap {
|
|
|
|
cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName))
|
|
|
|
done[key] = true
|
|
|
|
}
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
2024-11-22 14:55:42 +01:00
|
|
|
for key, env := range service.Environment {
|
|
|
|
_, isDropped := drop[key]
|
|
|
|
_, isDone := done[key]
|
|
|
|
if isDropped || isDone {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cm.AddData(key, *env)
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cm
|
|
|
|
}
|
|
|
|
|
2024-04-19 11:13:24 +02:00
|
|
|
// NewConfigMapFromDirectory creates a new ConfigMap from a compose service. This path is the path to the
|
2023-12-06 15:24:02 +01:00
|
|
|
// file or directory. If the path is a directory, all files in the directory are added to the ConfigMap.
|
|
|
|
// Each subdirectory are ignored. Note that the Generate() function will create the subdirectories ConfigMaps.
|
2024-05-06 21:11:36 +02:00
|
|
|
func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string) *ConfigMap {
|
2023-12-06 15:24:02 +01:00
|
|
|
normalized := path
|
|
|
|
normalized = strings.TrimLeft(normalized, ".")
|
|
|
|
normalized = strings.TrimLeft(normalized, "/")
|
|
|
|
normalized = regexp.MustCompile(`[^a-zA-Z0-9-]+`).ReplaceAllString(normalized, "-")
|
|
|
|
|
|
|
|
cm := &ConfigMap{
|
|
|
|
path: path,
|
|
|
|
service: &service,
|
|
|
|
usage: FileMapUsageFiles,
|
|
|
|
ConfigMap: &corev1.ConfigMap{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
Kind: "ConfigMap",
|
|
|
|
APIVersion: "v1",
|
|
|
|
},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: utils.TplName(service.Name, appName) + "-" + normalized,
|
|
|
|
Labels: GetLabels(service.Name, appName),
|
|
|
|
Annotations: Annotations,
|
|
|
|
},
|
|
|
|
Data: make(map[string]string),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
// cumulate the path to the WorkingDir
|
|
|
|
path = filepath.Join(service.WorkingDir, path)
|
|
|
|
path = filepath.Clean(path)
|
2025-06-04 14:29:13 +02:00
|
|
|
if err := cm.AppendDir(path); err != nil {
|
|
|
|
log.Fatal("Error adding files to configmap:", err)
|
|
|
|
}
|
2023-12-06 15:24:02 +01:00
|
|
|
return cm
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddData adds a key value pair to the configmap. Append or overwrite the value if the key already exists.
|
2024-05-06 21:11:36 +02:00
|
|
|
func (c *ConfigMap) AddData(key, value string) {
|
2023-12-06 15:24:02 +01:00
|
|
|
c.Data[key] = value
|
|
|
|
}
|
|
|
|
|
2024-12-03 13:50:58 +01:00
|
|
|
// AddBinaryData adds binary data to the configmap. Append or overwrite the value if the key already exists.
|
|
|
|
func (c *ConfigMap) AddBinaryData(key string, value []byte) {
|
|
|
|
if c.BinaryData == nil {
|
|
|
|
c.BinaryData = make(map[string][]byte)
|
|
|
|
}
|
|
|
|
c.BinaryData[key] = value
|
|
|
|
}
|
|
|
|
|
2025-06-26 23:18:11 +02:00
|
|
|
// AppendDir adds files from given path to the configmap. It is not recursive, to add all files in a directory,
|
2023-12-06 15:24:02 +01:00
|
|
|
// you need to call this function for each subdirectory.
|
2024-12-03 14:37:13 +01:00
|
|
|
func (c *ConfigMap) AppendDir(path string) error {
|
2023-12-06 15:24:02 +01:00
|
|
|
// read all files in the path and add them to the configmap
|
|
|
|
stat, err := os.Stat(path)
|
|
|
|
if err != nil {
|
2025-06-04 14:29:13 +02:00
|
|
|
return fmt.Errorf("path %s does not exist, %w", path, err)
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
// recursively read all files in the path and add them to the configmap
|
|
|
|
if stat.IsDir() {
|
|
|
|
files, err := os.ReadDir(path)
|
|
|
|
if err != nil {
|
2024-12-03 14:37:13 +01:00
|
|
|
return err
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
for _, file := range files {
|
|
|
|
if file.IsDir() {
|
2024-12-03 13:50:58 +01:00
|
|
|
utils.Warn("Subdirectories are ignored for the moment, skipping", filepath.Join(path, file.Name()))
|
2023-12-06 15:24:02 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
path := filepath.Join(path, file.Name())
|
|
|
|
content, err := os.ReadFile(path)
|
|
|
|
if err != nil {
|
2024-12-03 14:37:13 +01:00
|
|
|
return err
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
// remove the path from the file
|
|
|
|
filename := filepath.Base(path)
|
2024-12-03 13:50:58 +01:00
|
|
|
if utf8.Valid(content) {
|
|
|
|
c.AddData(filename, string(content))
|
|
|
|
} else {
|
|
|
|
c.AddBinaryData(filename, content)
|
|
|
|
}
|
|
|
|
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// add the file to the configmap
|
|
|
|
content, err := os.ReadFile(path)
|
|
|
|
if err != nil {
|
2024-12-03 14:37:13 +01:00
|
|
|
return err
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
2024-12-03 13:50:58 +01:00
|
|
|
filename := filepath.Base(path)
|
|
|
|
if utf8.Valid(content) {
|
|
|
|
c.AddData(filename, string(content))
|
|
|
|
} else {
|
|
|
|
c.AddBinaryData(filename, content)
|
|
|
|
}
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
2024-12-03 14:37:13 +01:00
|
|
|
return nil
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|
|
|
|
|
2024-12-03 14:37:13 +01:00
|
|
|
func (c *ConfigMap) AppendFile(path string) error {
|
2024-04-19 11:13:24 +02:00
|
|
|
// read all files in the path and add them to the configmap
|
|
|
|
stat, err := os.Stat(path)
|
|
|
|
if err != nil {
|
2025-06-04 14:29:13 +02:00
|
|
|
return fmt.Errorf("path %s doesn not exists, %w", path, err)
|
2024-04-19 11:13:24 +02:00
|
|
|
}
|
|
|
|
// recursively read all files in the path and add them to the configmap
|
|
|
|
if !stat.IsDir() {
|
|
|
|
// add the file to the configmap
|
|
|
|
content, err := os.ReadFile(path)
|
|
|
|
if err != nil {
|
2024-12-03 14:37:13 +01:00
|
|
|
return err
|
2024-04-19 11:13:24 +02:00
|
|
|
}
|
2024-12-03 13:50:58 +01:00
|
|
|
if utf8.Valid(content) {
|
|
|
|
c.AddData(filepath.Base(path), string(content))
|
|
|
|
} else {
|
|
|
|
c.AddBinaryData(filepath.Base(path), content)
|
|
|
|
}
|
|
|
|
|
2024-04-19 11:13:24 +02:00
|
|
|
}
|
2024-12-03 14:37:13 +01:00
|
|
|
return nil
|
2024-04-19 11:13:24 +02:00
|
|
|
}
|
|
|
|
|
2023-12-06 15:24:02 +01:00
|
|
|
// Filename returns the filename of the configmap. If the configmap is used for files, the filename contains the path.
|
|
|
|
func (c *ConfigMap) Filename() string {
|
|
|
|
switch c.usage {
|
|
|
|
case FileMapUsageFiles:
|
|
|
|
return filepath.Join(c.service.Name, "statics", c.path, "configmap.yaml")
|
|
|
|
default:
|
|
|
|
return c.service.Name + ".configmap.yaml"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-17 17:08:42 +02:00
|
|
|
// SetData sets the data of the configmap. It replaces the entire data.
|
|
|
|
func (c *ConfigMap) SetData(data map[string]string) {
|
|
|
|
c.Data = data
|
|
|
|
}
|
|
|
|
|
2023-12-06 15:24:02 +01:00
|
|
|
// Yaml returns the yaml representation of the configmap
|
|
|
|
func (c *ConfigMap) Yaml() ([]byte, error) {
|
2024-11-09 14:18:27 +01:00
|
|
|
return ToK8SYaml(c)
|
2023-12-06 15:24:02 +01:00
|
|
|
}
|