diff options
author | Justin Tobler <jtobler@gitlab.com> | 2022-07-26 22:16:15 +0300 |
---|---|---|
committer | Justin Tobler <jtobler@gitlab.com> | 2022-08-01 18:22:10 +0300 |
commit | 86a6db566f4075fd83aa3139351d0964bc7f7df7 (patch) | |
tree | 492806e5cb3dc767f735aad7ffc900718acb994f | |
parent | 31ea786fbf0a7874b44efcb1f8b43bc4536d9b7e (diff) |
cli: Relocate check subcommand to Gitaly binary
The gitaly-hooks check subcommand is used by admins to check whether
the internal API of Rails is accessible. Since in a running system
gitaly-hooks itself does not need to call the internal API this check
makes more sense to reside in the Gitaly binary and has been moved
there accordingly. This will validate whether or not Gitaly can
actually call the internal API.
-rw-r--r-- | cmd/gitaly-hooks/hooks.go | 55 | ||||
-rw-r--r-- | cmd/gitaly-hooks/hooks_test.go | 102 | ||||
-rw-r--r-- | cmd/gitaly/check.go | 72 | ||||
-rw-r--r-- | cmd/gitaly/check_test.go | 119 | ||||
-rw-r--r-- | cmd/gitaly/main.go | 8 | ||||
-rw-r--r-- | internal/testhelper/testcfg/build.go | 7 |
6 files changed, 205 insertions, 158 deletions
diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index 95bca501e..39d0e4ba4 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -9,14 +9,10 @@ import ( "os" "path/filepath" - "github.com/sirupsen/logrus" gitalyauth "gitlab.com/gitlab-org/gitaly/v15/auth" "gitlab.com/gitlab-org/gitaly/v15/client" "gitlab.com/gitlab-org/gitaly/v15/internal/git" - "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" - "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config/prometheus" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/hook" - "gitlab.com/gitlab-org/gitaly/v15/internal/gitlab" "gitlab.com/gitlab-org/gitaly/v15/internal/helper/env" gitalylog "gitlab.com/gitlab-org/gitaly/v15/internal/log" "gitlab.com/gitlab-org/gitaly/v15/internal/metadata/featureflag" @@ -88,31 +84,6 @@ func run(args []string) error { subCmd := args[1] switch subCmd { - case "check": - logrus.SetLevel(logrus.ErrorLevel) - if len(args) != 3 { - fmt.Fprint(os.Stderr, "no configuration file path provided invoke with: gitaly-hooks check <config_path>") - os.Exit(1) - } - - configPath := args[2] - fmt.Print("Checking GitLab API access: ") - - info, err := check(configPath) - if err != nil { - fmt.Print("FAIL\n") - fmt.Fprint(os.Stderr, err) - os.Exit(1) - } - - fmt.Print("OK\n") - fmt.Printf("GitLab version: %s\n", info.Version) - fmt.Printf("GitLab revision: %s\n", info.Revision) - fmt.Printf("GitLab Api version: %s\n", info.APIVersion) - fmt.Printf("Redis reachable for GitLab: %t\n", info.RedisReachable) - fmt.Println("OK") - - return nil case "git": return executeHook(hookCommand{ exec: packObjectsHook, @@ -215,32 +186,6 @@ func sendFunc(reqWriter io.Writer, stream grpc.ClientStream, stdin io.Reader) fu } } -func check(configPath string) (*gitlab.CheckInfo, error) { - cfgFile, err := os.Open(configPath) - if err != nil { - return nil, fmt.Errorf("failed to open config file: %w", err) - } - defer cfgFile.Close() - - cfg, err := config.Load(cfgFile) - if err != nil { - return nil, err - } - - gitlabAPI, err := gitlab.NewHTTPClient(logrus.New(), cfg.Gitlab, cfg.TLS, prometheus.Config{}) - if err != nil { - return nil, err - } - - gitCmdFactory, cleanup, err := git.NewExecCommandFactory(cfg) - if err != nil { - return nil, err - } - defer cleanup() - - return hook.NewManager(cfg, config.NewLocator(cfg), gitCmdFactory, nil, gitlabAPI).Check(context.TODO()) -} - func updateHook(ctx context.Context, payload git.HooksPayload, hookClient gitalypb.HookServiceClient, args []string) error { if len(args) != 3 { return fmt.Errorf("update hook expects exactly three arguments, got %q", args) diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go index 54e4fd026..798772597 100644 --- a/cmd/gitaly-hooks/hooks_test.go +++ b/cmd/gitaly-hooks/hooks_test.go @@ -14,7 +14,6 @@ import ( "strings" "testing" - "github.com/pelletier/go-toml" "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -492,93 +491,6 @@ func TestHooksNotAllowed(t *testing.T) { require.NoFileExists(t, customHookOutputPath) } -func TestCheckOK(t *testing.T) { - user, password := "user123", "password321" - - c := gitlab.TestServerOptions{ - User: user, - Password: password, - SecretToken: "", - GLRepository: "", - Changes: "", - PostReceiveCounterDecreased: false, - Protocol: "ssh", - } - serverURL, cleanup := gitlab.NewTestServer(t, c) - defer cleanup() - - cfg := testcfg.Build(t, testcfg.WithBase(config.Cfg{ - Gitlab: config.Gitlab{ - URL: serverURL, - HTTPSettings: config.HTTPSettings{ - User: user, - Password: password, - }, - SecretFile: gitlab.WriteShellSecretFile(t, testhelper.TempDir(t), "the secret"), - }, - })) - testcfg.BuildGitalyHooks(t, cfg) - testcfg.BuildGitalySSH(t, cfg) - - configPath := writeTemporaryGitalyConfigFile(t, cfg) - - cmd := exec.Command(cfg.BinaryPath("gitaly-hooks"), "check", configPath) - - var stderr, stdout bytes.Buffer - cmd.Stderr = &stderr - cmd.Stdout = &stdout - - err := cmd.Run() - require.NoError(t, err) - require.Empty(t, stderr.String()) - - output := stdout.String() - require.Contains(t, output, "Checking GitLab API access: OK") - require.Contains(t, output, "Redis reachable for GitLab: true") -} - -func TestCheckBadCreds(t *testing.T) { - user, password := "user123", "password321" - - c := gitlab.TestServerOptions{ - User: user, - Password: password, - SecretToken: "", - GLRepository: "", - Changes: "", - PostReceiveCounterDecreased: false, - Protocol: "ssh", - GitPushOptions: nil, - } - serverURL, cleanup := gitlab.NewTestServer(t, c) - defer cleanup() - - cfg := testcfg.Build(t, testcfg.WithBase(config.Cfg{ - Gitlab: config.Gitlab{ - URL: serverURL, - HTTPSettings: config.HTTPSettings{ - User: "wrong", - Password: password, - }, - SecretFile: gitlab.WriteShellSecretFile(t, testhelper.TempDir(t), "the secret"), - }, - })) - testcfg.BuildGitalyHooks(t, cfg) - testcfg.BuildGitalySSH(t, cfg) - - configPath := writeTemporaryGitalyConfigFile(t, cfg) - - cmd := exec.Command(cfg.BinaryPath("gitaly-hooks"), "check", configPath) - - var stderr, stdout bytes.Buffer - cmd.Stderr = &stderr - cmd.Stdout = &stdout - - require.Error(t, cmd.Run()) - require.Contains(t, stderr.String(), "HTTP GET to GitLab endpoint /check failed: authorization failed") - require.Regexp(t, `Checking GitLab API access: .* level=error msg="Internal API error" .* error="authorization failed" method=GET status=401 url="http://127.0.0.1:[0-9]+/api/v4/internal/check"\nFAIL`, stdout.String()) -} - func runHookServiceServer(t *testing.T, cfg config.Cfg, serverOpts ...testserver.GitalyServerOpt) { runHookServiceWithGitlabClient(t, cfg, gitlab.NewMockClient( t, gitlab.MockAllowed, gitlab.MockPreReceive, gitlab.MockPostReceive, @@ -729,17 +641,3 @@ func TestRequestedHooks(t *testing.T) { }) } } - -// writeTemporaryGitalyConfigFile writes the given Gitaly configuration into a temporary file and -// returns its path. -func writeTemporaryGitalyConfigFile(t testing.TB, cfg config.Cfg) string { - t.Helper() - - path := filepath.Join(testhelper.TempDir(t), "config.toml") - - contents, err := toml.Marshal(cfg) - require.NoError(t, err) - require.NoError(t, os.WriteFile(path, contents, 0o644)) - - return path -} diff --git a/cmd/gitaly/check.go b/cmd/gitaly/check.go new file mode 100644 index 000000000..00dcb7603 --- /dev/null +++ b/cmd/gitaly/check.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/v15/internal/git" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config/prometheus" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/hook" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitlab" +) + +func execCheck() { + logrus.SetLevel(logrus.ErrorLevel) + + checkCmd := flag.NewFlagSet("check", flag.ExitOnError) + checkCmd.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %v check <configfile>\n", os.Args[0]) + checkCmd.PrintDefaults() + } + + _ = checkCmd.Parse(os.Args[2:]) + + if checkCmd.NArg() != 1 || checkCmd.Arg(0) == "" { + fmt.Fprintf(os.Stderr, "error: invalid argument(s)") + checkCmd.Usage() + os.Exit(2) + } + + configPath := checkCmd.Arg(0) + + fmt.Print("Checking GitLab API access: ") + info, err := checkAPI(configPath) + if err != nil { + fmt.Println("FAILED") + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + fmt.Println("OK") + fmt.Printf("GitLab version: %s\n", info.Version) + fmt.Printf("GitLab revision: %s\n", info.Revision) + fmt.Printf("GitLab Api version: %s\n", info.APIVersion) + fmt.Printf("Redis reachable for GitLab: %t\n", info.RedisReachable) + fmt.Println("OK") + + os.Exit(0) +} + +func checkAPI(configPath string) (*gitlab.CheckInfo, error) { + cfg, err := loadConfig(configPath) + if err != nil { + return nil, fmt.Errorf("load config: config_path %q: %w", configPath, err) + } + + gitlabAPI, err := gitlab.NewHTTPClient(logrus.New(), cfg.Gitlab, cfg.TLS, prometheus.Config{}) + if err != nil { + return nil, err + } + + gitCmdFactory, cleanup, err := git.NewExecCommandFactory(cfg) + if err != nil { + return nil, err + } + defer cleanup() + + return hook.NewManager(cfg, config.NewLocator(cfg), gitCmdFactory, nil, gitlabAPI).Check(context.Background()) +} diff --git a/cmd/gitaly/check_test.go b/cmd/gitaly/check_test.go new file mode 100644 index 000000000..01df6c00e --- /dev/null +++ b/cmd/gitaly/check_test.go @@ -0,0 +1,119 @@ +package main + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/pelletier/go-toml" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitlab" + "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper/testcfg" +) + +func TestMain(m *testing.M) { + testhelper.Run(m) +} + +func TestCheckOK(t *testing.T) { + user, password := "user123", "password321" + + c := gitlab.TestServerOptions{ + User: user, + Password: password, + SecretToken: "", + GLRepository: "", + Changes: "", + PostReceiveCounterDecreased: false, + Protocol: "ssh", + } + serverURL, cleanup := gitlab.NewTestServer(t, c) + defer cleanup() + + cfg := testcfg.Build(t, testcfg.WithBase(config.Cfg{ + Gitlab: config.Gitlab{ + URL: serverURL, + HTTPSettings: config.HTTPSettings{ + User: user, + Password: password, + }, + SecretFile: gitlab.WriteShellSecretFile(t, testhelper.TempDir(t), "the secret"), + }, + })) + testcfg.BuildGitaly(t, cfg) + + configPath := writeTemporaryGitalyConfigFile(t, cfg) + + cmd := exec.Command(cfg.BinaryPath("gitaly"), "check", configPath) + + var stderr, stdout bytes.Buffer + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + err := cmd.Run() + require.NoError(t, err) + require.Empty(t, stderr.String()) + + output := stdout.String() + require.Contains(t, output, "Checking GitLab API access: OK") + require.Contains(t, output, "Redis reachable for GitLab: true") +} + +func TestCheckBadCreds(t *testing.T) { + user, password := "user123", "password321" + + c := gitlab.TestServerOptions{ + User: user, + Password: password, + SecretToken: "", + GLRepository: "", + Changes: "", + PostReceiveCounterDecreased: false, + Protocol: "ssh", + GitPushOptions: nil, + } + serverURL, cleanup := gitlab.NewTestServer(t, c) + defer cleanup() + + cfg := testcfg.Build(t, testcfg.WithBase(config.Cfg{ + Gitlab: config.Gitlab{ + URL: serverURL, + HTTPSettings: config.HTTPSettings{ + User: "wrong", + Password: password, + }, + SecretFile: gitlab.WriteShellSecretFile(t, testhelper.TempDir(t), "the secret"), + }, + })) + testcfg.BuildGitaly(t, cfg) + + configPath := writeTemporaryGitalyConfigFile(t, cfg) + + cmd := exec.Command(cfg.BinaryPath("gitaly"), "check", configPath) + + var stderr, stdout bytes.Buffer + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + require.Error(t, cmd.Run()) + require.Contains(t, stderr.String(), "HTTP GET to GitLab endpoint /check failed: authorization failed") + require.Regexp(t, `Checking GitLab API access: .* level=error msg="Internal API error" .* error="authorization failed" method=GET status=401 url="http://127.0.0.1:[0-9]+/api/v4/internal/check"\nFAIL`, stdout.String()) +} + +// writeTemporaryGitalyConfigFile writes the given Gitaly configuration into a temporary file and +// returns its path. +func writeTemporaryGitalyConfigFile(t testing.TB, cfg config.Cfg) string { + t.Helper() + + path := filepath.Join(testhelper.TempDir(t), "config.toml") + + contents, err := toml.Marshal(cfg) + require.NoError(t, err) + require.NoError(t, os.WriteFile(path, contents, 0o644)) + + return path +} diff --git a/cmd/gitaly/main.go b/cmd/gitaly/main.go index 793bb015e..0f04a2b5d 100644 --- a/cmd/gitaly/main.go +++ b/cmd/gitaly/main.go @@ -73,11 +73,17 @@ func loadConfig(configPath string) (config.Cfg, error) { func flagUsage() { fmt.Println(version.GetVersionString()) - fmt.Printf("Usage: %v [OPTIONS] configfile\n", os.Args[0]) + 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") } func main() { + // If invoked with subcommand check + if len(os.Args) > 1 && os.Args[1] == "check" { + execCheck() + } + flag.Usage = flagUsage flag.Parse() diff --git a/internal/testhelper/testcfg/build.go b/internal/testhelper/testcfg/build.go index d8a88f0df..ffacd17cb 100644 --- a/internal/testhelper/testcfg/build.go +++ b/internal/testhelper/testcfg/build.go @@ -50,6 +50,13 @@ func BuildPraefect(t testing.TB, cfg config.Cfg) string { return buildGitalyCommand(t, cfg, "praefect") } +// BuildGitaly builds the gitaly binary and installs it into the binary directory. The gitaly binary +// embeds other binaries it needs to use when servicing requests. The packed binaries are not built +// prior to building this gitaly binary and thus cannot be guaranteed to be from the same build. +func BuildGitaly(t testing.TB, cfg config.Cfg) string { + return buildGitalyCommand(t, cfg, "gitaly") +} + // buildGitalyCommand builds an executable and places it in the correct directory depending // whether it is packed in the production build or not. func buildGitalyCommand(t testing.TB, cfg config.Cfg, executableName string) string { |