mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Make environment-to-ini support loading key value from file (#24832)
Replace #19857 Close #19856 Close #10311 Close #10123 Major changes: 1. Move a lot of code from `environment-to-ini.go` to `config_env.go` to make them testable. 2. Add `__FILE` support 3. Update documents 4. Add tests
This commit is contained in:
		@@ -5,8 +5,6 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
@@ -14,7 +12,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
	ini "gopkg.in/ini.v1"
 | 
			
		||||
	"gopkg.in/ini.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EnvironmentPrefix environment variables prefixed with this represent ini values to write
 | 
			
		||||
@@ -32,6 +30,10 @@ func main() {
 | 
			
		||||
	will be mapped to the ini section "[section_name]" and the key
 | 
			
		||||
	"KEY_NAME" with the value as provided.
 | 
			
		||||
 | 
			
		||||
	Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME__FILE"
 | 
			
		||||
	will be mapped to the ini section "[section_name]" and the key
 | 
			
		||||
	"KEY_NAME" with the value loaded from the specified file.
 | 
			
		||||
 | 
			
		||||
	Environment variables are usually restricted to a reduced character
 | 
			
		||||
	set "0-9A-Z_" - in order to allow the setting of sections with
 | 
			
		||||
	characters outside of that set, they should be escaped as following:
 | 
			
		||||
@@ -96,11 +98,11 @@ func runEnvironmentToIni(c *cli.Context) error {
 | 
			
		||||
	setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
 | 
			
		||||
 | 
			
		||||
	cfg := ini.Empty()
 | 
			
		||||
	isFile, err := util.IsFile(setting.CustomConf)
 | 
			
		||||
	confFileExists, err := util.IsFile(setting.CustomConf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("Unable to check if %s is a file. Error: %v", setting.CustomConf, err)
 | 
			
		||||
	}
 | 
			
		||||
	if isFile {
 | 
			
		||||
	if confFileExists {
 | 
			
		||||
		if err := cfg.Append(setting.CustomConf); err != nil {
 | 
			
		||||
			log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -109,47 +111,11 @@ func runEnvironmentToIni(c *cli.Context) error {
 | 
			
		||||
	}
 | 
			
		||||
	cfg.NameMapper = ini.SnackCase
 | 
			
		||||
 | 
			
		||||
	changed := false
 | 
			
		||||
	prefixGitea := c.String("prefix") + "__"
 | 
			
		||||
	suffixFile := "__FILE"
 | 
			
		||||
	changed := setting.EnvironmentToConfig(cfg, prefixGitea, suffixFile, os.Environ())
 | 
			
		||||
 | 
			
		||||
	prefix := c.String("prefix") + "__"
 | 
			
		||||
 | 
			
		||||
	for _, kv := range os.Environ() {
 | 
			
		||||
		idx := strings.IndexByte(kv, '=')
 | 
			
		||||
		if idx < 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		eKey := kv[:idx]
 | 
			
		||||
		value := kv[idx+1:]
 | 
			
		||||
		if !strings.HasPrefix(eKey, prefix) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		eKey = eKey[len(prefix):]
 | 
			
		||||
		sectionName, keyName := DecodeSectionKey(eKey)
 | 
			
		||||
		if len(keyName) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		section, err := cfg.GetSection(sectionName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			section, err = cfg.NewSection(sectionName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("Error creating section: %s : %v", sectionName, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		key := section.Key(keyName)
 | 
			
		||||
		if key == nil {
 | 
			
		||||
			key, err = section.NewKey(keyName, value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, value, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		oldValue := key.Value()
 | 
			
		||||
		if !changed && oldValue != value {
 | 
			
		||||
			changed = true
 | 
			
		||||
		}
 | 
			
		||||
		key.SetValue(value)
 | 
			
		||||
	}
 | 
			
		||||
	// try to save the config file
 | 
			
		||||
	destination := c.String("out")
 | 
			
		||||
	if len(destination) == 0 {
 | 
			
		||||
		destination = setting.CustomConf
 | 
			
		||||
@@ -161,6 +127,8 @@ func runEnvironmentToIni(c *cli.Context) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// clear Gitea's specific environment variables if requested
 | 
			
		||||
	if c.Bool("clear") {
 | 
			
		||||
		for _, kv := range os.Environ() {
 | 
			
		||||
			idx := strings.IndexByte(kv, '=')
 | 
			
		||||
@@ -168,69 +136,11 @@ func runEnvironmentToIni(c *cli.Context) error {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			eKey := kv[:idx]
 | 
			
		||||
			if strings.HasPrefix(eKey, prefix) {
 | 
			
		||||
			if strings.HasPrefix(eKey, prefixGitea) {
 | 
			
		||||
				_ = os.Unsetenv(eKey)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
 | 
			
		||||
 | 
			
		||||
var escapeRegex = regexp.MustCompile(escapeRegexpString)
 | 
			
		||||
 | 
			
		||||
// DecodeSectionKey will decode a portable string encoded Section__Key pair
 | 
			
		||||
// Portable strings are considered to be of the form [A-Z0-9_]*
 | 
			
		||||
// We will encode a disallowed value as the UTF8 byte string preceded by _0X and
 | 
			
		||||
// followed by _. E.g. _0X2C_ for a '-' and _0X2E_ for '.'
 | 
			
		||||
// Section and Key are separated by a plain '__'.
 | 
			
		||||
// The entire section can be encoded as a UTF8 byte string
 | 
			
		||||
func DecodeSectionKey(encoded string) (string, string) {
 | 
			
		||||
	section := ""
 | 
			
		||||
	key := ""
 | 
			
		||||
 | 
			
		||||
	inKey := false
 | 
			
		||||
	last := 0
 | 
			
		||||
	escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1)
 | 
			
		||||
	for _, unescapeIdx := range escapeStringIndices {
 | 
			
		||||
		preceding := encoded[last:unescapeIdx[0]]
 | 
			
		||||
		if !inKey {
 | 
			
		||||
			if splitter := strings.Index(preceding, "__"); splitter > -1 {
 | 
			
		||||
				section += preceding[:splitter]
 | 
			
		||||
				inKey = true
 | 
			
		||||
				key += preceding[splitter+2:]
 | 
			
		||||
			} else {
 | 
			
		||||
				section += preceding
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			key += preceding
 | 
			
		||||
		}
 | 
			
		||||
		toDecode := encoded[unescapeIdx[0]+3 : unescapeIdx[1]-1]
 | 
			
		||||
		decodedBytes := make([]byte, len(toDecode)/2)
 | 
			
		||||
		for i := 0; i < len(toDecode)/2; i++ {
 | 
			
		||||
			// Can ignore error here as we know these should be hexadecimal from the regexp
 | 
			
		||||
			byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 0)
 | 
			
		||||
			decodedBytes[i] = byte(byteInt)
 | 
			
		||||
		}
 | 
			
		||||
		if inKey {
 | 
			
		||||
			key += string(decodedBytes)
 | 
			
		||||
		} else {
 | 
			
		||||
			section += string(decodedBytes)
 | 
			
		||||
		}
 | 
			
		||||
		last = unescapeIdx[1]
 | 
			
		||||
	}
 | 
			
		||||
	remaining := encoded[last:]
 | 
			
		||||
	if !inKey {
 | 
			
		||||
		if splitter := strings.Index(remaining, "__"); splitter > -1 {
 | 
			
		||||
			section += remaining[:splitter]
 | 
			
		||||
			key += remaining[splitter+2:]
 | 
			
		||||
		} else {
 | 
			
		||||
			section += remaining
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		key += remaining
 | 
			
		||||
	}
 | 
			
		||||
	section = strings.ToLower(section)
 | 
			
		||||
	return section, key
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user