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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash |
||||
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@" |
||||
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash |
||||
exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@" |
||||
Loading…
Reference in new issue