Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavlo Strokov <pstrokov@gitlab.com>2023-02-06 19:07:27 +0300
committerPavlo Strokov <pstrokov@gitlab.com>2023-02-13 14:39:43 +0300
commit4a527b5dbe7865f5573fe19897a41464d1a25c25 (patch)
tree4e4f26d73f18410ac8d5ba6697928d541501e979
parente4c21598faf9af715c8a38c2bb3b8e132495e564 (diff)
Gitaly: validate-configuration sub-commandps-config-validation
In order to improve developers experience in using Gitaly the new 'validate-configuration' is added. The purpose of it to validate provided configuration before starting the service. The output lists all the problems of the configuration in JSON format into STDOUT. The structure of object includes 'key' which is a path to the field where the problem detected and the 'message' with an explanation of the problem. Changelog: added Part of: https://gitlab.com/gitlab-org/gitaly/-/issues/4650
-rw-r--r--cmd/gitaly/main.go18
-rw-r--r--cmd/gitaly/validate.go73
-rw-r--r--cmd/gitaly/validate_test.go106
-rw-r--r--internal/gitaly/config/config.go2
4 files changed, 194 insertions, 5 deletions
diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go
index df49c8b44..4b8a0b3a7 100644
--- a/cmd/gitaly/main.go
+++ b/cmd/gitaly/main.go
@@ -76,13 +76,23 @@ func flagUsage() {
fmt.Println(version.GetVersionString("Gitaly"))
fmt.Printf("Usage: %v [command] [options] <configfile>\n", os.Args[0])
flag.PrintDefaults()
- fmt.Printf("\nThe commands are:\n\n\tcheck\tchecks accessability of internal Rails API\n")
+ fmt.Printf(`
+The commands are:
+
+ check checks accessability of internal Rails API
+ validate-configuration validates provided configuration
+`)
}
func main() {
- // If invoked with subcommand check
- if len(os.Args) > 1 && os.Args[1] == "check" {
- execCheck()
+ // If invoked with subcommand
+ if len(os.Args) > 1 {
+ switch os.Args[1] {
+ case "check":
+ execCheck()
+ case "validate-configuration":
+ execValidateConfiguration()
+ }
}
flag.Usage = flagUsage
diff --git a/cmd/gitaly/validate.go b/cmd/gitaly/validate.go
new file mode 100644
index 000000000..4e5a3f5be
--- /dev/null
+++ b/cmd/gitaly/validate.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/pelletier/go-toml/v2"
+ "github.com/sirupsen/logrus"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config"
+)
+
+type validationOutputError struct {
+ Key []string `json:"key,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+type validationOutput struct {
+ Errors []validationOutputError `json:"errors,omitempty"`
+}
+
+func execValidateConfiguration() {
+ logrus.SetLevel(logrus.ErrorLevel)
+
+ command := flag.NewFlagSet("validate-configuration", flag.ExitOnError)
+ command.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage: %v validate-configuration < <configfile>\n", os.Args[0])
+ command.PrintDefaults()
+ }
+
+ cfg, err := config.Load(os.Stdin)
+ if err != nil {
+ terr := &toml.DecodeError{}
+ if errors.As(err, &terr) {
+ row, column := terr.Position()
+ jsonEncoded(os.Stdout, os.Stderr, " ", validationOutput{Errors: []validationOutputError{{
+ Key: terr.Key(),
+ Message: fmt.Sprintf("line %d column %d: %v", row, column, terr.Error()),
+ }}})
+ os.Exit(1)
+ }
+ fmt.Fprintf(os.Stderr, "processing input data: %v\n", err)
+ os.Exit(1)
+ }
+
+ if err := cfg.Validate(); err != nil {
+ var terr config.ValidationErrors
+ if errors.As(err, &terr) {
+ out := validationOutput{}
+ for _, err := range terr {
+ out.Errors = append(out.Errors, validationOutputError{
+ Key: err.Key,
+ Message: err.Message,
+ })
+ }
+ jsonEncoded(os.Stdout, os.Stderr, " ", out)
+ os.Exit(1)
+ }
+ }
+
+ os.Exit(0)
+}
+
+func jsonEncoded(outStream io.Writer, errStream io.Writer, indent string, val any) {
+ encoder := json.NewEncoder(outStream)
+ encoder.SetIndent("", indent)
+ if err := encoder.Encode(val); err != nil {
+ fmt.Fprintf(errStream, "writing results: %v\n", err)
+ }
+}
diff --git a/cmd/gitaly/validate_test.go b/cmd/gitaly/validate_test.go
new file mode 100644
index 000000000..5725f4022
--- /dev/null
+++ b/cmd/gitaly/validate_test.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+ "bytes"
+ "io"
+ "os/exec"
+ "strings"
+ "testing"
+
+ "github.com/pelletier/go-toml/v2"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/command"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper/testcfg"
+)
+
+func TestValidateConfiguration(t *testing.T) {
+ t.Parallel()
+ cfg := testcfg.Build(t)
+ testcfg.BuildGitaly(t, cfg)
+
+ for _, tc := range []struct {
+ name string
+ exitCode int
+ stdin func(t *testing.T) io.Reader
+ stderr string
+ stdout string
+ }{
+ {
+ name: "ok",
+ exitCode: 0,
+ stdin: func(*testing.T) io.Reader {
+ t.Helper()
+ var stdin bytes.Buffer
+ require.NoError(t, toml.NewEncoder(&stdin).Encode(cfg))
+ return &stdin
+ },
+ },
+ {
+ name: "bad toml format",
+ exitCode: 1,
+ stdin: func(*testing.T) io.Reader {
+ return strings.NewReader(`graceful_restart_timeout = "bad value"`)
+ },
+ stdout: `{
+ "errors": [
+ {
+ "message": "line 1 column 28: toml: time: invalid duration \"bad value\""
+ }
+ ]
+}
+`,
+ },
+ {
+ name: "validation failures",
+ exitCode: 1,
+ stdin: func(t *testing.T) io.Reader {
+ cfg := cfg
+ cfg.Git.Config = []config.GitConfig{{Key: "bad"}}
+ cfg.Storages = []config.Storage{{Name: " ", Path: cfg.Storages[0].Path}}
+ var stdin bytes.Buffer
+ require.NoError(t, toml.NewEncoder(&stdin).Encode(cfg))
+ return &stdin
+ },
+ stdout: `{
+ "errors": [
+ {
+ "key": [
+ "storage",
+ "name"
+ ],
+ "message": "empty value at declaration 1"
+ },
+ {
+ "key": [
+ "git",
+ "config"
+ ],
+ "message": "invalid configuration key 'bad': key must contain at least one section"
+ }
+ ]
+}
+`,
+ },
+ } {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command(cfg.BinaryPath("gitaly"), "validate-configuration")
+ var stderr, stdout bytes.Buffer
+ cmd.Stderr = &stderr
+ cmd.Stdout = &stdout
+ cmd.Stdin = tc.stdin(t)
+
+ err := cmd.Run()
+ if tc.exitCode != 0 {
+ status, ok := command.ExitStatus(err)
+ require.Truef(t, ok, "%T: %v", err, err)
+ assert.Equal(t, tc.exitCode, status)
+ }
+ assert.Equal(t, tc.stderr, stderr.String())
+ assert.Equal(t, tc.stdout, stdout.String())
+ })
+ }
+}
diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go
index f36e484f7..632b65c5e 100644
--- a/internal/gitaly/config/config.go
+++ b/internal/gitaly/config/config.go
@@ -300,7 +300,7 @@ func Load(file io.Reader) (Cfg, error) {
}
if err := toml.NewDecoder(file).Decode(&cfg); err != nil {
- return Cfg{}, fmt.Errorf("load toml: %v", err)
+ return Cfg{}, fmt.Errorf("load toml: %w", err)
}
if err := cfg.setDefaults(); err != nil {