mirror of
https://github.com/dokku/dokku.git
synced 2026-07-11 04:51:57 +02:00
Chart property names containing `/` failed because per-key flat-file storage interpreted the slash as a filesystem path separator, and any move to line-based storage would silently truncate multi-line values. A new `PropertyMap*` helper family in `common` persists each map as a single JSON file so both `/` in keys and `\n` in values round-trip losslessly. Chart overrides move to `chart-overrides.<chart>` and an idempotent `TriggerInstall` migration rewrites any legacy `chart.<chart>.<key>` files into the new layout. The deprecated `scheduler-k3s:set --global chart.*` form is rerouted through the same map storage so it does not silently orphan writes. Closes #8717.
790 lines
22 KiB
Go
790 lines
22 KiB
Go
package common
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// CommandPropertySet is a generic function that will set a property for a given plugin/app combination
|
|
func CommandPropertySet(pluginName, appName, property, value string, validProperties map[string]string, validGlobalProperties map[string]bool) {
|
|
if appName != "--global" {
|
|
if err := VerifyAppName(appName); err != nil {
|
|
LogFailWithError(err)
|
|
}
|
|
}
|
|
if appName == "--global" && !validGlobalProperties[property] {
|
|
LogFail("Property cannot be specified globally")
|
|
}
|
|
if property == "" {
|
|
LogFail("No property specified")
|
|
}
|
|
|
|
for k := range validGlobalProperties {
|
|
if _, ok := validProperties[k]; !ok {
|
|
validProperties[k] = ""
|
|
}
|
|
}
|
|
|
|
if _, ok := validProperties[property]; !ok {
|
|
properties := reflect.ValueOf(validProperties).MapKeys()
|
|
validPropertyList := make([]string, len(properties))
|
|
for i := 0; i < len(properties); i++ {
|
|
validPropertyList[i] = properties[i].String()
|
|
}
|
|
|
|
sort.Strings(validPropertyList)
|
|
LogFail(fmt.Sprintf("Invalid property specified, valid properties include: %s", strings.Join(validPropertyList, ", ")))
|
|
}
|
|
|
|
if value != "" {
|
|
LogInfo2Quiet(fmt.Sprintf("Setting %s to %s", property, value))
|
|
if err := PropertyWrite(pluginName, appName, property, value); err != nil {
|
|
LogFailWithError(err)
|
|
}
|
|
} else {
|
|
LogInfo2Quiet(fmt.Sprintf("Unsetting %s", property))
|
|
if err := PropertyDelete(pluginName, appName, property); err != nil {
|
|
LogFailWithError(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// PropertyClone clones a set of properties from one app to another
|
|
func PropertyClone(pluginName string, oldAppName string, newAppName string) error {
|
|
properties, err := PropertyGetAll(pluginName, oldAppName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
for property, value := range properties {
|
|
if err := PropertyWrite(pluginName, newAppName, property, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyDelete deletes a property from the plugin properties for an app
|
|
func PropertyDelete(pluginName string, appName string, property string) error {
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
if err := os.Remove(propertyPath); err != nil {
|
|
if !PropertyExists(pluginName, appName, property) {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("Unable to remove %s property %s.%s", pluginName, appName, property)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyDestroy destroys the plugin properties for an app
|
|
func PropertyDestroy(pluginName string, appName string) error {
|
|
if appName == "_all_" {
|
|
pluginConfigPath := getPluginConfigPath(pluginName)
|
|
return os.RemoveAll(pluginConfigPath)
|
|
}
|
|
|
|
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
|
return os.RemoveAll(pluginAppConfigRoot)
|
|
}
|
|
|
|
// PropertyExists returns whether a property exists or not
|
|
func PropertyExists(pluginName string, appName string, property string) bool {
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
_, err := os.Stat(propertyPath)
|
|
return !os.IsNotExist(err)
|
|
}
|
|
|
|
// PropertyGet returns the value for a given property
|
|
func PropertyGet(pluginName string, appName string, property string) string {
|
|
return PropertyGetDefault(pluginName, appName, property, "")
|
|
}
|
|
|
|
// PropertyGetAll returns a map of all properties for a given app
|
|
func PropertyGetAll(pluginName string, appName string) (map[string]string, error) {
|
|
properties := make(map[string]string)
|
|
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
|
|
|
fi, err := os.Stat(pluginAppConfigRoot)
|
|
if err != nil {
|
|
return properties, nil
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
return properties, errors.New("Specified property path is not a directory")
|
|
}
|
|
|
|
files, err := os.ReadDir(pluginAppConfigRoot)
|
|
if err != nil {
|
|
return properties, err
|
|
}
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
property := file.Name()
|
|
properties[property] = PropertyGet(pluginName, appName, property)
|
|
}
|
|
|
|
return properties, nil
|
|
}
|
|
|
|
// PropertyGetAllByPrefix returns a map of all properties for a given app with a specified prefix
|
|
func PropertyGetAllByPrefix(pluginName string, appName string, prefix string) (map[string]string, error) {
|
|
properties := make(map[string]string)
|
|
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
|
|
|
fi, err := os.Stat(pluginAppConfigRoot)
|
|
if err != nil {
|
|
return properties, nil
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
return properties, errors.New("Specified property path is not a directory")
|
|
}
|
|
|
|
files, err := os.ReadDir(pluginAppConfigRoot)
|
|
if err != nil {
|
|
return properties, err
|
|
}
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
property := file.Name()
|
|
if !strings.HasPrefix(property, prefix) {
|
|
continue
|
|
}
|
|
|
|
properties[property] = PropertyGet(pluginName, appName, property)
|
|
}
|
|
|
|
return properties, nil
|
|
}
|
|
|
|
// PropertyGetDefault returns the value for a given property with a specified default value
|
|
func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val string) {
|
|
if !PropertyExists(pluginName, appName, property) {
|
|
val = defaultValue
|
|
return
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
b, err := os.ReadFile(propertyPath)
|
|
if err != nil {
|
|
LogWarn(fmt.Sprintf("Unable to read %s property %s.%s", pluginName, appName, property))
|
|
return
|
|
}
|
|
val = string(b)
|
|
return
|
|
}
|
|
|
|
// PropertyListAdd adds a property to a list at an optionally specified index
|
|
func PropertyListAdd(pluginName string, appName string, property string, value string, index int) error {
|
|
if err := propertyTouch(pluginName, appName, property); err != nil {
|
|
return err
|
|
}
|
|
|
|
scannedLines, err := PropertyListGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value = strings.TrimSpace(value)
|
|
|
|
var lines []string
|
|
for i, line := range scannedLines {
|
|
if index != 0 && i == (index-1) {
|
|
lines = append(lines, value)
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
|
|
if index == 0 || index > len(scannedLines) {
|
|
lines = append(lines, value)
|
|
}
|
|
|
|
return PropertyListWrite(pluginName, appName, property, lines)
|
|
}
|
|
|
|
// PropertyListWrite completely rewrites a list property
|
|
func PropertyListWrite(pluginName string, appName string, property string, values []string) error {
|
|
if err := propertyTouch(pluginName, appName, property); err != nil {
|
|
return err
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w := bufio.NewWriter(file)
|
|
for _, line := range values {
|
|
fmt.Fprintln(w, line)
|
|
}
|
|
if err = w.Flush(); err != nil {
|
|
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := file.Close(); err != nil {
|
|
return fmt.Errorf("Unable to close %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := SetPermissions(SetPermissionInput{
|
|
Filename: propertyPath,
|
|
Mode: os.FileMode(0600),
|
|
}); err != nil {
|
|
return fmt.Errorf("Unable to set permissions for %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyListGet returns a property list
|
|
func PropertyListGet(pluginName string, appName string, property string) (lines []string, err error) {
|
|
if !PropertyExists(pluginName, appName, property) {
|
|
return lines, nil
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
file, err := os.Open(propertyPath)
|
|
if err != nil {
|
|
return lines, err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text())
|
|
}
|
|
|
|
if err = scanner.Err(); err != nil {
|
|
return lines, fmt.Errorf("Unable to read %s config value for %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
return lines, nil
|
|
}
|
|
|
|
// PropertyListLength returns the length of a property list
|
|
func PropertyListLength(pluginName string, appName string, property string) (length int, err error) {
|
|
if !PropertyExists(pluginName, appName, property) {
|
|
return length, nil
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
file, err := os.Open(propertyPath)
|
|
if err != nil {
|
|
return length, err
|
|
}
|
|
defer file.Close()
|
|
|
|
var lines []string
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text())
|
|
}
|
|
|
|
if err = scanner.Err(); err != nil {
|
|
return length, fmt.Errorf("Unable to read %s config value for %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
length = len(lines)
|
|
return length, nil
|
|
}
|
|
|
|
// PropertyListGetByIndex returns an entry within property list by index
|
|
func PropertyListGetByIndex(pluginName string, appName string, property string, index int) (propertyValue string, err error) {
|
|
lines, err := PropertyListGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
found := false
|
|
for i, line := range lines {
|
|
if i == index {
|
|
propertyValue = line
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
err = errors.New("Index not found")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// PropertyListGetByValue returns an entry within property list by value
|
|
func PropertyListGetByValue(pluginName string, appName string, property string, value string) (propertyValue string, err error) {
|
|
lines, err := PropertyListGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
found := false
|
|
for _, line := range lines {
|
|
if line == value {
|
|
propertyValue = line
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
err = errors.New("Value not found")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// PropertyListRemove removes a value from a property list
|
|
func PropertyListRemove(pluginName string, appName string, property string, value string) error {
|
|
lines, err := PropertyListGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
found := false
|
|
w := bufio.NewWriter(file)
|
|
for _, line := range lines {
|
|
if line == value {
|
|
found = true
|
|
continue
|
|
}
|
|
fmt.Fprintln(w, line)
|
|
}
|
|
if err = w.Flush(); err != nil {
|
|
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := file.Close(); err != nil {
|
|
return fmt.Errorf("Unable to close %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := SetPermissions(SetPermissionInput{
|
|
Filename: propertyPath,
|
|
Mode: os.FileMode(0600),
|
|
}); err != nil {
|
|
return fmt.Errorf("Unable to set permissions for %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if !found {
|
|
return errors.New("Property not found, nothing was removed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyListRemoveByPrefix removes a value by prefix from a property list
|
|
func PropertyListRemoveByPrefix(pluginName string, appName string, property string, prefix string) error {
|
|
lines, err := PropertyListGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
found := false
|
|
w := bufio.NewWriter(file)
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, prefix) {
|
|
found = true
|
|
continue
|
|
}
|
|
fmt.Fprintln(w, line)
|
|
}
|
|
if err = w.Flush(); err != nil {
|
|
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := file.Close(); err != nil {
|
|
return fmt.Errorf("Unable to close %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := SetPermissions(SetPermissionInput{
|
|
Filename: propertyPath,
|
|
Mode: os.FileMode(0600),
|
|
}); err != nil {
|
|
return fmt.Errorf("Unable to set permissions for %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if !found {
|
|
return errors.New("Property not found, nothing was removed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyListSet sets a value within a property list at a specified index
|
|
func PropertyListSet(pluginName string, appName string, property string, value string, index int) error {
|
|
if err := propertyTouch(pluginName, appName, property); err != nil {
|
|
return err
|
|
}
|
|
|
|
scannedLines, err := PropertyListGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value = strings.TrimSpace(value)
|
|
|
|
var lines []string
|
|
if index >= len(scannedLines) {
|
|
lines = append(lines, scannedLines...)
|
|
lines = append(lines, value)
|
|
} else {
|
|
for i, line := range scannedLines {
|
|
if i == index {
|
|
lines = append(lines, value)
|
|
} else {
|
|
lines = append(lines, line)
|
|
}
|
|
}
|
|
}
|
|
|
|
return PropertyListWrite(pluginName, appName, property, lines)
|
|
}
|
|
|
|
// propertyTouch ensures a given application property file exists
|
|
func propertyTouch(pluginName string, appName string, property string) error {
|
|
if err := makePluginAppPropertyPath(pluginName, appName); err != nil {
|
|
return fmt.Errorf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error())
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
if PropertyExists(pluginName, appName, property) {
|
|
return nil
|
|
}
|
|
|
|
file, err := os.Create(propertyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
defer file.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyWrite writes a value for a given application property
|
|
func PropertyWrite(pluginName string, appName string, property string, value string) error {
|
|
if err := propertyTouch(pluginName, appName, property); err != nil {
|
|
return err
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
file, err := os.Create(propertyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
defer file.Close()
|
|
|
|
fmt.Fprint(file, value)
|
|
|
|
if err := file.Close(); err != nil {
|
|
return fmt.Errorf("Unable to close %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
if err := SetPermissions(SetPermissionInput{
|
|
Filename: propertyPath,
|
|
Mode: os.FileMode(0600),
|
|
}); err != nil {
|
|
return fmt.Errorf("Unable to set permissions for %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyMapWrite persists a string→string map for a given property as a single
|
|
// JSON file. Unlike PropertyWrite/PropertyListWrite, map *keys* may contain any
|
|
// bytes (including '/' or '\n') because they live inside file content rather
|
|
// than in the on-disk filename. A nil map is persisted as "{}".
|
|
func PropertyMapWrite(pluginName string, appName string, property string, m map[string]string) error {
|
|
if err := propertyTouch(pluginName, appName, property); err != nil {
|
|
return err
|
|
}
|
|
|
|
if m == nil {
|
|
m = map[string]string{}
|
|
}
|
|
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to marshal %s map property %s.%s: %w", pluginName, appName, property, err)
|
|
}
|
|
|
|
propertyPath := getPropertyPath(pluginName, appName, property)
|
|
if err := os.WriteFile(propertyPath, b, 0600); err != nil {
|
|
return fmt.Errorf("Unable to write %s map property %s.%s: %w", pluginName, appName, property, err)
|
|
}
|
|
|
|
if err := SetPermissions(SetPermissionInput{
|
|
Filename: propertyPath,
|
|
Mode: os.FileMode(0600),
|
|
}); err != nil {
|
|
return fmt.Errorf("Unable to set permissions for %s map property %s.%s: %w", pluginName, appName, property, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PropertyMapGet returns the string→string map persisted for a property. A
|
|
// missing or empty-file property is reported as an empty map without error.
|
|
func PropertyMapGet(pluginName string, appName string, property string) (map[string]string, error) {
|
|
m := map[string]string{}
|
|
if !PropertyExists(pluginName, appName, property) {
|
|
return m, nil
|
|
}
|
|
|
|
b, err := os.ReadFile(getPropertyPath(pluginName, appName, property))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to read %s map property %s.%s: %w", pluginName, appName, property, err)
|
|
}
|
|
|
|
if len(b) == 0 {
|
|
return m, nil
|
|
}
|
|
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return nil, fmt.Errorf("Unable to parse %s map property %s.%s as JSON: %w", pluginName, appName, property, err)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// PropertyMapSet reads the current map, sets one key, and writes the map back.
|
|
func PropertyMapSet(pluginName string, appName string, property string, key string, value string) error {
|
|
m, err := PropertyMapGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m[key] = value
|
|
return PropertyMapWrite(pluginName, appName, property, m)
|
|
}
|
|
|
|
// PropertyMapDelete reads the current map, removes one key, and writes the map
|
|
// back. Removing a key that is not present is a no-op.
|
|
func PropertyMapDelete(pluginName string, appName string, property string, key string) error {
|
|
if !PropertyExists(pluginName, appName, property) {
|
|
return nil
|
|
}
|
|
m, err := PropertyMapGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, ok := m[key]; !ok {
|
|
return nil
|
|
}
|
|
delete(m, key)
|
|
return PropertyMapWrite(pluginName, appName, property, m)
|
|
}
|
|
|
|
// PropertyMapLength returns the number of entries in the map persisted for a
|
|
// property. A missing property is reported as length 0 without error.
|
|
func PropertyMapLength(pluginName string, appName string, property string) (int, error) {
|
|
m, err := PropertyMapGet(pluginName, appName, property)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(m), nil
|
|
}
|
|
|
|
// PropertySetup creates the plugin config root
|
|
func PropertySetup(pluginName string) error {
|
|
configRoot := filepath.Join(MustGetEnv("DOKKU_LIB_ROOT"), "config")
|
|
pluginConfigRoot := getPluginConfigPath(pluginName)
|
|
if err := os.MkdirAll(pluginConfigRoot, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if configRoot is a symlink
|
|
if !IsSymlink(configRoot) {
|
|
input := SetPermissionInput{
|
|
Filename: configRoot,
|
|
Mode: os.FileMode(0755),
|
|
}
|
|
|
|
if err := SetPermissions(input); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return SetPermissions(SetPermissionInput{
|
|
Filename: pluginConfigRoot,
|
|
Mode: os.FileMode(0755),
|
|
})
|
|
}
|
|
|
|
func PropertySetupApp(pluginName string, appName string) error {
|
|
if err := PropertySetup(pluginName); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := makePluginAppPropertyPath(pluginName, appName); err != nil {
|
|
return fmt.Errorf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MigrateConfigEntry describes a single config-to-property migration
|
|
type MigrateConfigEntry struct {
|
|
// ConfigVar is the per-app environment variable name (e.g. "DOKKU_APP_PROXY_TYPE")
|
|
ConfigVar string
|
|
|
|
// GlobalConfigVar is the global environment variable name (e.g. "DOKKU_PROXY_TYPE"), empty if none
|
|
GlobalConfigVar string
|
|
|
|
// Property is the target property name (e.g. "type")
|
|
Property string
|
|
|
|
// Transform is an optional value transformation function applied before writing
|
|
Transform func(value string) string
|
|
|
|
// ListProperty indicates the value should be written as a list property via PropertyListWrite
|
|
ListProperty bool
|
|
}
|
|
|
|
// MigrateConfigToProperties migrates config variables to properties for a given plugin
|
|
// across all apps and optionally globally. It is idempotent: if the property already
|
|
// exists, the migration is skipped for that app/entry.
|
|
func MigrateConfigToProperties(pluginName string, entries []MigrateConfigEntry) error {
|
|
apps, err := UnfilteredDokkuApps()
|
|
if err != nil && !errors.Is(err, NoAppsExist) {
|
|
return nil
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.GlobalConfigVar != "" {
|
|
if err := migrateConfigEntry(pluginName, "--global", entry.GlobalConfigVar, entry); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, appName := range apps {
|
|
if entry.ConfigVar == "" {
|
|
continue
|
|
}
|
|
|
|
if err := migrateConfigEntry(pluginName, appName, entry.ConfigVar, entry); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateConfigEntry migrates a single config variable to a property for a given app
|
|
func migrateConfigEntry(pluginName string, appName string, configVar string, entry MigrateConfigEntry) error {
|
|
if entry.ListProperty {
|
|
if exists, _ := PropertyListLength(pluginName, appName, entry.Property); exists > 0 {
|
|
return nil
|
|
}
|
|
} else if PropertyExists(pluginName, appName, entry.Property) {
|
|
return nil
|
|
}
|
|
|
|
triggerName := "config-get"
|
|
triggerArgs := []string{appName, configVar}
|
|
if appName == "--global" {
|
|
triggerName = "config-get-global"
|
|
triggerArgs = []string{configVar}
|
|
}
|
|
|
|
results, _ := CallPlugnTrigger(PlugnTriggerInput{
|
|
Trigger: triggerName,
|
|
Args: triggerArgs,
|
|
})
|
|
value := results.StdoutContents()
|
|
if value == "" {
|
|
return nil
|
|
}
|
|
|
|
if entry.Transform != nil {
|
|
value = entry.Transform(value)
|
|
}
|
|
|
|
if appName == "--global" {
|
|
LogInfo1(fmt.Sprintf("Migrating deprecated global %s to %s %s property. Use 'dokku %s:set --global %s <value>' to manage this going forward.", configVar, pluginName, entry.Property, pluginName, entry.Property))
|
|
} else {
|
|
LogInfo1(fmt.Sprintf("Migrating deprecated %s to %s %s property for %s. Use 'dokku %s:set %s %s <value>' to manage this going forward.", configVar, pluginName, entry.Property, appName, pluginName, appName, entry.Property))
|
|
}
|
|
|
|
if entry.ListProperty {
|
|
values := strings.Split(value, " ")
|
|
if err := PropertyListWrite(pluginName, appName, entry.Property, values); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := PropertyWrite(pluginName, appName, entry.Property, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
unsetTrigger := "config-unset"
|
|
unsetArgs := []string{appName, configVar}
|
|
if appName == "--global" {
|
|
unsetArgs = []string{"--global", configVar}
|
|
}
|
|
|
|
_, err := CallPlugnTrigger(PlugnTriggerInput{
|
|
Trigger: unsetTrigger,
|
|
Args: unsetArgs,
|
|
Env: map[string]string{
|
|
"DOKKU_QUIET_OUTPUT": "1",
|
|
},
|
|
})
|
|
if err != nil {
|
|
LogWarn(err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getPropertyPath(pluginName string, appName string, property string) string {
|
|
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
|
return filepath.Join(pluginAppConfigRoot, property)
|
|
}
|
|
|
|
// getPluginAppPropertyPath returns the plugin property path for a given plugin/app combination
|
|
func getPluginAppPropertyPath(pluginName string, appName string) string {
|
|
return filepath.Join(getPluginConfigPath(pluginName), appName)
|
|
}
|
|
|
|
// getPluginConfigPath returns the plugin property path for a given plugin
|
|
func getPluginConfigPath(pluginName string) string {
|
|
return filepath.Join(MustGetEnv("DOKKU_LIB_ROOT"), "config", pluginName)
|
|
}
|
|
|
|
// makePluginAppPropertyPath ensures that a property path exists
|
|
func makePluginAppPropertyPath(pluginName string, appName string) error {
|
|
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
|
if err := os.MkdirAll(pluginAppConfigRoot, 0755); err != nil {
|
|
return err
|
|
}
|
|
return SetPermissions(SetPermissionInput{
|
|
Filename: pluginAppConfigRoot,
|
|
Mode: os.FileMode(0755),
|
|
})
|
|
}
|