124 lines
4.3 KiB
Go
124 lines
4.3 KiB
Go
package instance
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.zaynet.fr/DoGaMa/DoGaMa-serv/internal/catalog"
|
|
)
|
|
|
|
// ResolvedConfiguration is the safe, non-secret part of a template's runtime
|
|
// configuration. It is persisted with the preview so a recreated container is
|
|
// built identically. Secret mutations retain only their field identifier.
|
|
type ResolvedConfiguration struct {
|
|
Environment map[string]string `json:"environment,omitempty"`
|
|
RuntimeEnvironment map[string]string `json:"runtime_environment,omitempty"`
|
|
Arguments []string `json:"arguments,omitempty"`
|
|
INI []INIMutation `json:"ini,omitempty"`
|
|
SecretEnvironment map[string]string `json:"secret_environment,omitempty"`
|
|
SecretArguments []SecretArgument `json:"secret_arguments,omitempty"`
|
|
}
|
|
|
|
type SecretArgument struct {
|
|
Name string `json:"name"`
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type INIMutation struct {
|
|
Mount string `json:"mount"`
|
|
File string `json:"file"`
|
|
Section string `json:"section"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value,omitempty"`
|
|
SecretID string `json:"secret_id,omitempty"`
|
|
}
|
|
|
|
var protectedEnvironment = map[string]bool{"PATH": true, "HOME": true, "HOSTNAME": true, "DOCKER_HOST": true, "DOGAMA_INSTANCE_ID": true}
|
|
|
|
func ResolveConfiguration(template catalog.Template, values map[string]string) (ResolvedConfiguration, error) {
|
|
result := ResolvedConfiguration{Environment: make(map[string]string), RuntimeEnvironment: make(map[string]string), SecretEnvironment: make(map[string]string)}
|
|
for key, value := range template.Container.Environment {
|
|
result.Environment[key] = value
|
|
}
|
|
for _, field := range template.Configuration.Fields {
|
|
value, configured := values[field.ID]
|
|
secret := field.Type == "secret"
|
|
if !configured {
|
|
continue
|
|
}
|
|
target := field.Target
|
|
switch target.Kind {
|
|
case "environment":
|
|
if value == "" && !field.Required {
|
|
continue
|
|
}
|
|
if protectedEnvironment[target.Name] || strings.HasPrefix(target.Name, "DOGAMA_") {
|
|
return ResolvedConfiguration{}, fmt.Errorf("%s targets a protected environment variable", field.ID)
|
|
}
|
|
if secret {
|
|
result.SecretEnvironment[target.Name] = field.ID
|
|
continue
|
|
}
|
|
// A field may replace only the explicitly named template variable.
|
|
result.Environment[target.Name] = value
|
|
case "argument":
|
|
if secret {
|
|
result.SecretArguments = append(result.SecretArguments, SecretArgument{Name: target.Name, ID: field.ID, Type: field.Type})
|
|
continue
|
|
}
|
|
argument, include, err := resolveArgument(target.Name, field.Type, value)
|
|
if err != nil {
|
|
return ResolvedConfiguration{}, fmt.Errorf("%s has an invalid argument target", field.ID)
|
|
}
|
|
if include {
|
|
result.Arguments = append(result.Arguments, argument)
|
|
}
|
|
case "ini":
|
|
mutation := INIMutation{Mount: target.Mount, File: target.File, Section: target.Section, Key: target.Key, Value: value}
|
|
if secret {
|
|
mutation.SecretID = field.ID
|
|
mutation.Value = ""
|
|
}
|
|
if err := validateINIMutation(mutation); err != nil {
|
|
return ResolvedConfiguration{}, fmt.Errorf("%s has an invalid INI target", field.ID)
|
|
}
|
|
result.INI = append(result.INI, mutation)
|
|
default:
|
|
return ResolvedConfiguration{}, fmt.Errorf("%s has an unknown configuration target", field.ID)
|
|
}
|
|
}
|
|
sort.Slice(result.INI, func(i, j int) bool {
|
|
return result.INI[i].Mount+result.INI[i].File+result.INI[i].Section+result.INI[i].Key < result.INI[j].Mount+result.INI[j].File+result.INI[j].Section+result.INI[j].Key
|
|
})
|
|
return result, nil
|
|
}
|
|
|
|
func resolveArgument(name, typ, value string) (string, bool, error) {
|
|
if strings.TrimSpace(name) == "" || strings.ContainsAny(name, "\x00\n\r") {
|
|
return "", false, errors.New("invalid")
|
|
}
|
|
if typ == "boolean" {
|
|
if value == "false" && !strings.Contains(name, "{{value}}") {
|
|
return "", false, nil
|
|
}
|
|
}
|
|
if strings.Contains(name, "{{value}}") {
|
|
return strings.ReplaceAll(name, "{{value}}", value), true, nil
|
|
}
|
|
if typ == "boolean" {
|
|
return name, true, nil
|
|
}
|
|
return name + "=" + value, true, nil
|
|
}
|
|
|
|
func validateINIMutation(m INIMutation) error {
|
|
if m.Mount == "" || m.File == "" || m.Key == "" || filepath.IsAbs(m.File) || filepath.Clean(m.File) != m.File || strings.HasPrefix(m.File, ".."+string(filepath.Separator)) || strings.ContainsAny(m.File+m.Section+m.Key, "\x00\r\n") {
|
|
return errors.New("invalid ini path")
|
|
}
|
|
return nil
|
|
}
|