mirror of https://github.com/go-gitea/gitea.git
Browse Source
Ref: #32669. Helps addressing https://gitea.com/gitea/helm-chart/issues/356.pull/35745/head
10 changed files with 260 additions and 175 deletions
@ -0,0 +1,156 @@ |
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting" |
||||||
|
|
||||||
|
"github.com/urfave/cli/v3" |
||||||
|
) |
||||||
|
|
||||||
|
func cmdConfig() *cli.Command { |
||||||
|
subcmdConfigEditIni := &cli.Command{ |
||||||
|
Name: "edit-ini", |
||||||
|
Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.", |
||||||
|
Description: ` |
||||||
|
Help users to edit the Gitea configuration INI file. |
||||||
|
|
||||||
|
# Keep Specified Keys |
||||||
|
|
||||||
|
If you need to re-create the configuration file with only a subset of keys, |
||||||
|
you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag. |
||||||
|
For example, if a helm chart needs to reset the settings and only keep SECRET_KEY, |
||||||
|
it can use a template file (only keys take effect, values are ignored): |
||||||
|
|
||||||
|
[security] |
||||||
|
SECRET_KEY= |
||||||
|
|
||||||
|
$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini |
||||||
|
|
||||||
|
# Map Environment Variables to INI Configuration |
||||||
|
|
||||||
|
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 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 variable keys can only contain characters "0-9A-Z_", |
||||||
|
if a section or key name contains dot ".", it needs to be escaped as _0x2E_. |
||||||
|
For example, to apply this config: |
||||||
|
|
||||||
|
[git.config] |
||||||
|
foo.bar=val |
||||||
|
|
||||||
|
$ export GITEA__git_0x2E_config__foo_0x2E_bar=val |
||||||
|
|
||||||
|
# Put All Together |
||||||
|
|
||||||
|
$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini} |
||||||
|
`, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper
|
||||||
|
// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file
|
||||||
|
&cli.BoolFlag{ |
||||||
|
Name: "in-place", |
||||||
|
Usage: "Output to the same config file as input. This flag will be ignored if --out is set.", |
||||||
|
}, |
||||||
|
&cli.StringFlag{ |
||||||
|
Name: "config-keep-keys", |
||||||
|
Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.", |
||||||
|
}, |
||||||
|
&cli.BoolFlag{ |
||||||
|
Name: "apply-env", |
||||||
|
Usage: "Apply all GITEA__* variables from the environment to the config.", |
||||||
|
}, |
||||||
|
&cli.StringFlag{ |
||||||
|
Name: "out", |
||||||
|
Usage: "Destination config file to write to.", |
||||||
|
}, |
||||||
|
}, |
||||||
|
Action: runConfigEditIni, |
||||||
|
} |
||||||
|
|
||||||
|
return &cli.Command{ |
||||||
|
Name: "config", |
||||||
|
Usage: "Manage Gitea configuration", |
||||||
|
Commands: []*cli.Command{ |
||||||
|
subcmdConfigEditIni, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func runConfigEditIni(_ context.Context, c *cli.Command) error { |
||||||
|
// the config system may change the environment variables, so get a copy first, to be used later
|
||||||
|
env := append([]string{}, os.Environ()...) |
||||||
|
|
||||||
|
// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly
|
||||||
|
if !c.IsSet("config") { |
||||||
|
return errors.New("flag is required but not set: --config") |
||||||
|
} |
||||||
|
configFileIn := c.String("config") |
||||||
|
|
||||||
|
cfgIn, err := setting.NewConfigProviderFromFile(configFileIn) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("failed to load config file %q: %v", configFileIn, err) |
||||||
|
} |
||||||
|
|
||||||
|
// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file
|
||||||
|
inPlace := c.Bool("in-place") |
||||||
|
configFileOut := c.String("out") |
||||||
|
if configFileOut == "" { |
||||||
|
if !inPlace { |
||||||
|
return errors.New("either --in-place or --out must be specified") |
||||||
|
} |
||||||
|
configFileOut = configFileIn // in-place edit
|
||||||
|
} |
||||||
|
|
||||||
|
needWriteOut := configFileOut != configFileIn |
||||||
|
|
||||||
|
cfgOut := cfgIn |
||||||
|
configKeepKeys := c.String("config-keep-keys") |
||||||
|
if configKeepKeys != "" { |
||||||
|
needWriteOut = true |
||||||
|
cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err) |
||||||
|
} |
||||||
|
|
||||||
|
for _, secOut := range cfgOut.Sections() { |
||||||
|
for _, keyOut := range secOut.Keys() { |
||||||
|
secIn := cfgIn.Section(secOut.Name()) |
||||||
|
keyIn := setting.ConfigSectionKey(secIn, keyOut.Name()) |
||||||
|
if keyIn != nil { |
||||||
|
keyOut.SetValue(keyIn.String()) |
||||||
|
} else { |
||||||
|
secOut.DeleteKey(keyOut.Name()) |
||||||
|
} |
||||||
|
} |
||||||
|
if len(secOut.Keys()) == 0 { |
||||||
|
cfgOut.DeleteSection(secOut.Name()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if c.Bool("apply-env") { |
||||||
|
if setting.EnvironmentToConfig(cfgOut, env) { |
||||||
|
needWriteOut = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if needWriteOut { |
||||||
|
err = cfgOut.SaveTo(configFileOut) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
@ -0,0 +1,85 @@ |
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
func TestConfigEdit(t *testing.T) { |
||||||
|
tmpDir := t.TempDir() |
||||||
|
configOld := tmpDir + "/app-old.ini" |
||||||
|
configTemplate := tmpDir + "/app-template.ini" |
||||||
|
_ = os.WriteFile(configOld, []byte(` |
||||||
|
[sec] |
||||||
|
k1=v1 |
||||||
|
k2=v2 |
||||||
|
`), os.ModePerm) |
||||||
|
|
||||||
|
_ = os.WriteFile(configTemplate, []byte(` |
||||||
|
[sec] |
||||||
|
k1=in-template |
||||||
|
|
||||||
|
[sec2] |
||||||
|
k3=v3 |
||||||
|
`), os.ModePerm) |
||||||
|
|
||||||
|
t.Setenv("GITEA__EnV__KeY", "val") |
||||||
|
|
||||||
|
t.Run("OutputToNewWithEnv", func(t *testing.T) { |
||||||
|
configNew := tmpDir + "/app-new.ini" |
||||||
|
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ |
||||||
|
"./gitea", "--config", configOld, |
||||||
|
"config", "edit-ini", |
||||||
|
"--apply-env", |
||||||
|
"--config-keep-keys", configTemplate, |
||||||
|
"--out", configNew, |
||||||
|
}) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
// "k1" old value is kept because its key is in the template
|
||||||
|
// "k2" is removed because it isn't in the template
|
||||||
|
// "k3" isn't in new config because it isn't in the old config
|
||||||
|
// [env] is applied from environment variable
|
||||||
|
data, _ := os.ReadFile(configNew) |
||||||
|
require.Equal(t, `[sec] |
||||||
|
k1 = v1 |
||||||
|
|
||||||
|
[env] |
||||||
|
KeY = val |
||||||
|
`, string(data)) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) { |
||||||
|
// the legacy "environment-to-ini" (now a wrapper script) behavior:
|
||||||
|
// if no "--out", then "--in-place" must be used to overwrite the existing "--config" file
|
||||||
|
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ |
||||||
|
"./gitea", "config", "edit-ini", |
||||||
|
"--apply-env", |
||||||
|
"--config", configOld, |
||||||
|
}) |
||||||
|
require.ErrorContains(t, err, "either --in-place or --out must be specified") |
||||||
|
|
||||||
|
// simulate the "environment-to-ini" behavior with "--in-place"
|
||||||
|
err = NewMainApp(AppVersion{}).Run(t.Context(), []string{ |
||||||
|
"./gitea", "config", "edit-ini", |
||||||
|
"--in-place", |
||||||
|
"--apply-env", |
||||||
|
"--config", configOld, |
||||||
|
}) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
data, _ := os.ReadFile(configOld) |
||||||
|
require.Equal(t, `[sec] |
||||||
|
k1 = v1 |
||||||
|
k2 = v2 |
||||||
|
|
||||||
|
[env] |
||||||
|
KeY = val |
||||||
|
`, string(data)) |
||||||
|
}) |
||||||
|
} |
||||||
@ -1,47 +0,0 @@ |
|||||||
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 |
|
||||||
|
|
||||||
Note, SECTION_NAME in the notation above is case-insensitive. |
|
||||||
|
|
||||||
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 "." and "_0X2D_" 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 build locally, run: |
|
||||||
|
|
||||||
go build contrib/environment-to-ini/environment-to-ini.go |
|
||||||
@ -1,112 +0,0 @@ |
|||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"os" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log" |
|
||||||
"code.gitea.io/gitea/modules/setting" |
|
||||||
|
|
||||||
"github.com/urfave/cli/v3" |
|
||||||
) |
|
||||||
|
|
||||||
func main() { |
|
||||||
app := cli.Command{} |
|
||||||
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 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: |
|
||||||
"_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", |
|
||||||
Aliases: []string{"C"}, |
|
||||||
Value: setting.CustomPath, |
|
||||||
Usage: "Custom path file path", |
|
||||||
}, |
|
||||||
&cli.StringFlag{ |
|
||||||
Name: "config", |
|
||||||
Aliases: []string{"c"}, |
|
||||||
Value: setting.CustomConf, |
|
||||||
Usage: "Custom configuration file path", |
|
||||||
}, |
|
||||||
&cli.StringFlag{ |
|
||||||
Name: "work-path", |
|
||||||
Aliases: []string{"w"}, |
|
||||||
Value: setting.AppWorkPath, |
|
||||||
Usage: "Set the gitea working path", |
|
||||||
}, |
|
||||||
&cli.StringFlag{ |
|
||||||
Name: "out", |
|
||||||
Aliases: []string{"o"}, |
|
||||||
Value: "", |
|
||||||
Usage: "Destination file to write to", |
|
||||||
}, |
|
||||||
} |
|
||||||
app.Action = runEnvironmentToIni |
|
||||||
err := app.Run(context.Background(), os.Args) |
|
||||||
if err != nil { |
|
||||||
log.Fatal("Failed to run app with %s: %v", os.Args, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func runEnvironmentToIni(_ context.Context, c *cli.Command) error { |
|
||||||
// the config system may change the environment variables, so get a copy first, to be used later
|
|
||||||
env := append([]string{}, os.Environ()...) |
|
||||||
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ |
|
||||||
WorkPath: c.String("work-path"), |
|
||||||
CustomPath: c.String("custom-path"), |
|
||||||
CustomConf: c.String("config"), |
|
||||||
}) |
|
||||||
|
|
||||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) |
|
||||||
if err != nil { |
|
||||||
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) |
|
||||||
} |
|
||||||
|
|
||||||
changed := setting.EnvironmentToConfig(cfg, env) |
|
||||||
|
|
||||||
// try to save the config file
|
|
||||||
destination := c.String("out") |
|
||||||
if len(destination) == 0 { |
|
||||||
destination = setting.CustomConf |
|
||||||
} |
|
||||||
if destination != setting.CustomConf || changed { |
|
||||||
log.Info("Settings saved to: %q", destination) |
|
||||||
err = cfg.SaveTo(destination) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
@ -0,0 +1,2 @@ |
|||||||
|
#!/bin/bash |
||||||
|
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@" |
||||||
@ -0,0 +1,2 @@ |
|||||||
|
#!/bin/bash |
||||||
|
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@" |
||||||
Loading…
Reference in new issue