-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add contrib/environment-to-ini (#9519)
* Add contrib/environment-to-ini This contrib command provides a mechanism to allow arbitrary setting of ini values using the environment variable in a more docker standard fashion. Environment variable keys should be structured as: "GITEA__SECTION_NAME__KEY_NAME" Use of the command is explained in the README. Partial fix for #350 Closes #7287 * Update contrib/environment-to-ini/environment-to-ini.go Co-Authored-By: 6543 <6543@obermui.de> Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
- Loading branch information
Showing
2 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
Environment To Ini | ||
================== | ||
|
||
Multiple docker users have requested that the Gitea docker is changed | ||
to permit arbitrary configuration via environment variables. | ||
|
||
Gitea needs to use an ini file for configuration because the running | ||
environment that starts the docker may not be the same as that used | ||
by the hooks. An ini file also gives a good default and means that | ||
users do not have to completely provide a full environment. | ||
|
||
With those caveats above, this command provides a generic way of | ||
converting suitably structured environment variables into any ini | ||
value. | ||
|
||
To use the command is very simple just run it and the default gitea | ||
app.ini will be rewritten to take account of the variables provided, | ||
however there are various options to give slightly different | ||
behavior and these can be interrogated with the `-h` option. | ||
|
||
The environment variables should be of the form: | ||
|
||
GITEA__SECTION_NAME__KEY_NAME | ||
|
||
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: | ||
"_0X2E_" for ".". The entire section and key names can be escaped as | ||
a UTF8 byte string if necessary. E.g. to configure: | ||
|
||
""" | ||
... | ||
[log.console] | ||
COLORIZE=false | ||
STDERR=true | ||
... | ||
""" | ||
|
||
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false" | ||
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found | ||
on the configuration cheat sheet. | ||
|
||
To plug this command in to the docker, you simply compile the provided go file using: | ||
|
||
go build environment-to-ini.go | ||
|
||
And copy the resulting `environment-to-ini` command to /app/gitea in the docker. | ||
|
||
Apply the below patch to /etc/s6/gitea.setup to wire this in. | ||
|
||
If you find this useful please comment on #7287 | ||
|
||
|
||
diff --git a/docker/root/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup | ||
index f87ce9115..565bfcba9 100755 | ||
--- a/docker/root/etc/s6/gitea/setup | ||
+++ b/docker/root/etc/s6/gitea/setup | ||
@@ -44,6 +44,8 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then | ||
SECRET_KEY=${SECRET_KEY:-""} \ | ||
envsubst < /etc/templates/app.ini > ${GITEA_CUSTOM}/conf/app.ini | ||
|
||
+ /app/gitea/environment-to-ini -c ${GITEA_CUSTOM}/conf/app.ini | ||
+ | ||
chown ${USER}:git ${GITEA_CUSTOM}/conf/app.ini | ||
fi | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
// Copyright 2019 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package main | ||
|
||
import ( | ||
"os" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"code.gitea.io/gitea/modules/log" | ||
"code.gitea.io/gitea/modules/setting" | ||
|
||
"github.com/unknwon/com" | ||
"github.com/urfave/cli" | ||
ini "gopkg.in/ini.v1" | ||
) | ||
|
||
// EnvironmentPrefix environment variables prefixed with this represent ini values to write | ||
const EnvironmentPrefix = "GITEA" | ||
|
||
func main() { | ||
app := cli.NewApp() | ||
app.Name = "environment-to-ini" | ||
app.Usage = "Use provided environment to update configuration ini" | ||
app.Description = `As a helper to allow docker users to update the gitea configuration | ||
through the environment, this command allows environment variables to | ||
be mapped to values in the ini. | ||
Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME" | ||
will be mapped to the ini section "[section_name]" and the key | ||
"KEY_NAME" with the value as provided. | ||
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: | ||
"_0X2E_" for ".". The entire section and key names can be escaped as | ||
a UTF8 byte string if necessary. E.g. to configure: | ||
""" | ||
... | ||
[log.console] | ||
COLORIZE=false | ||
STDERR=true | ||
... | ||
""" | ||
You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false" | ||
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found | ||
on the configuration cheat sheet.` | ||
app.Flags = []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "custom-path, C", | ||
Value: setting.CustomPath, | ||
Usage: "Custom path file path", | ||
}, | ||
cli.StringFlag{ | ||
Name: "config, c", | ||
Value: setting.CustomConf, | ||
Usage: "Custom configuration file path", | ||
}, | ||
cli.StringFlag{ | ||
Name: "work-path, w", | ||
Value: setting.AppWorkPath, | ||
Usage: "Set the gitea working path", | ||
}, | ||
cli.StringFlag{ | ||
Name: "out, o", | ||
Value: "", | ||
Usage: "Destination file to write to", | ||
}, | ||
cli.BoolFlag{ | ||
Name: "clear", | ||
Usage: "Clears the matched variables from the environment", | ||
}, | ||
cli.StringFlag{ | ||
Name: "prefix, p", | ||
Value: EnvironmentPrefix, | ||
Usage: "Environment prefix to look for - will be suffixed by __ (2 underscores)", | ||
}, | ||
} | ||
app.Action = runEnvironmentToIni | ||
setting.SetCustomPathAndConf("", "", "") | ||
|
||
err := app.Run(os.Args) | ||
if err != nil { | ||
log.Fatal("Failed to run app with %s: %v", os.Args, err) | ||
} | ||
} | ||
|
||
func runEnvironmentToIni(c *cli.Context) error { | ||
providedCustom := c.String("custom-path") | ||
providedConf := c.String("config") | ||
providedWorkPath := c.String("work-path") | ||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath) | ||
|
||
cfg := ini.Empty() | ||
if com.IsFile(setting.CustomConf) { | ||
if err := cfg.Append(setting.CustomConf); err != nil { | ||
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) | ||
} | ||
} else { | ||
log.Warn("Custom config '%s' not found, ignore this if you're running first time", setting.CustomConf) | ||
} | ||
cfg.NameMapper = ini.AllCapsUnderscore | ||
|
||
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 | ||
} | ||
} | ||
key.SetValue(value) | ||
} | ||
destination := c.String("out") | ||
if len(destination) == 0 { | ||
destination = setting.CustomConf | ||
} | ||
err := cfg.SaveTo(destination) | ||
if err != nil { | ||
return err | ||
} | ||
if c.Bool("clear") { | ||
for _, kv := range os.Environ() { | ||
idx := strings.IndexByte(kv, '=') | ||
if idx < 0 { | ||
continue | ||
} | ||
eKey := kv[:idx] | ||
if strings.HasPrefix(eKey, prefix) { | ||
_ = 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] | ||
inKey = true | ||
key += remaining[splitter+2:] | ||
} else { | ||
section += remaining | ||
} | ||
} else { | ||
key += remaining | ||
} | ||
return section, key | ||
} |