From 189eca1d2e9a1469974052bb3456711861dfe1bf Mon Sep 17 00:00:00 2001 From: James Liu Date: Wed, 3 Jul 2024 15:29:49 +1000 Subject: [PATCH 1/7] test: Use testcfg.Build instead of literal instantiation Modifies a few tests which were manually declaring the config.Cfg struct so they use the testcfg.Build() constructor instead. This removes some redundant code and ensures we're always running the logic within the constructor. --- internal/git/command_options_test.go | 25 ++++++--------- internal/git/gittest/testhelper_test.go | 31 ++----------------- .../service/smarthttp/upload_pack_test.go | 1 - .../gitaly/service/ssh/upload_pack_test.go | 8 +++-- 4 files changed, 17 insertions(+), 48 deletions(-) diff --git a/internal/git/command_options_test.go b/internal/git/command_options_test.go index e5e19b090b..fa4354c31e 100644 --- a/internal/git/command_options_test.go +++ b/internal/git/command_options_test.go @@ -11,6 +11,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" "gitlab.com/gitlab-org/labkit/correlation" "google.golang.org/grpc/metadata" @@ -192,10 +193,7 @@ func TestGlobalOption(t *testing.T) { } func TestWithConfig(t *testing.T) { - cfg := config.Cfg{ - BinDir: testhelper.TempDir(t), - } - + cfg := testcfg.Build(t) ctx := testhelper.Context(t) gitCmdFactory := newCommandFactory(t, cfg, WithSkipHooks()) @@ -265,14 +263,14 @@ func TestWithConfig(t *testing.T) { } func TestExecCommandFactoryGitalyConfigOverrides(t *testing.T) { - cfg := config.Cfg{ - BinDir: testhelper.TempDir(t), - Git: config.Git{ - Config: []config.GitConfig{ - {Key: "foo.bar", Value: "from-gitaly-config"}, + cfg := testcfg.Build(t, testcfg.WithBase( + config.Cfg{ + Git: config.Git{ + Config: []config.GitConfig{ + {Key: "foo.bar", Value: "from-gitaly-config"}, + }, }, - }, - } + })) ctx := testhelper.Context(t) @@ -294,10 +292,7 @@ func TestExecCommandFactoryGitalyConfigOverrides(t *testing.T) { } func TestWithConfigEnv(t *testing.T) { - cfg := config.Cfg{ - BinDir: testhelper.TempDir(t), - } - + cfg := testcfg.Build(t) ctx := testhelper.Context(t) gitCmdFactory := newCommandFactory(t, cfg, WithSkipHooks()) diff --git a/internal/git/gittest/testhelper_test.go b/internal/git/gittest/testhelper_test.go index 6bc92c9dcc..029fbbdeca 100644 --- a/internal/git/gittest/testhelper_test.go +++ b/internal/git/gittest/testhelper_test.go @@ -1,14 +1,11 @@ package gittest import ( - "os" - "path/filepath" "testing" - "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config" - "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage/mode" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" ) @@ -21,32 +18,8 @@ func TestMain(m *testing.M) { func setup(tb testing.TB) (config.Cfg, *gitalypb.Repository, string) { tb.Helper() - rootDir := testhelper.TempDir(tb) - ctx := testhelper.Context(tb) - var cfg config.Cfg - - cfg.SocketPath = "it is a stub to bypass Validate method" - - cfg.Storages = []config.Storage{ - { - Name: "default", - Path: filepath.Join(rootDir, "storage.d"), - }, - } - require.NoError(tb, os.Mkdir(cfg.Storages[0].Path, mode.Directory)) - - cfg.GitlabShell.Dir = filepath.Join(rootDir, "shell.d") - require.NoError(tb, os.Mkdir(cfg.GitlabShell.Dir, mode.Directory)) - - cfg.BinDir = filepath.Join(rootDir, "bin.d") - require.NoError(tb, os.Mkdir(cfg.BinDir, mode.Directory)) - - cfg.RuntimeDir = filepath.Join(rootDir, "run.d") - require.NoError(tb, os.Mkdir(cfg.RuntimeDir, mode.Directory)) - require.NoError(tb, os.Mkdir(cfg.InternalSocketDir(), mode.Directory)) - - require.NoError(tb, cfg.Validate()) + cfg := testcfg.Build(tb) repo, repoPath := CreateRepository(tb, ctx, cfg, CreateRepositoryConfig{ SkipCreationViaService: true, diff --git a/internal/gitaly/service/smarthttp/upload_pack_test.go b/internal/gitaly/service/smarthttp/upload_pack_test.go index 944268aee5..a6359d08d0 100644 --- a/internal/gitaly/service/smarthttp/upload_pack_test.go +++ b/internal/gitaly/service/smarthttp/upload_pack_test.go @@ -277,7 +277,6 @@ func testServerPostUploadPackWithSidechannelUsesPackObjectsHook(t *testing.T, ct func testServerPostUploadPackUsesPackObjectsHook(t *testing.T, ctx context.Context, makeRequest requestMaker, opts ...testcfg.Option) { cfg := testcfg.Build(t, append(opts, testcfg.WithPackObjectsCacheEnabled())...) - cfg.BinDir = testhelper.TempDir(t) outputPath := filepath.Join(cfg.BinDir, "output") //nolint:gitaly-linters diff --git a/internal/gitaly/service/ssh/upload_pack_test.go b/internal/gitaly/service/ssh/upload_pack_test.go index 949bcad1cd..26c1aab6fd 100644 --- a/internal/gitaly/service/ssh/upload_pack_test.go +++ b/internal/gitaly/service/ssh/upload_pack_test.go @@ -693,11 +693,13 @@ func TestUploadPack_packObjectsHook(t *testing.T) { ctx := testhelper.Context(t) - cfg := testcfg.Build(t, testcfg.WithPackObjectsCacheEnabled()) - filterDir := testhelper.TempDir(t) outputPath := filepath.Join(filterDir, "output") - cfg.BinDir = filterDir + cfg := testcfg.Build(t, testcfg.WithPackObjectsCacheEnabled(), testcfg.WithBase( + config.Cfg{ + BinDir: filterDir, + }, + )) testcfg.BuildGitalySSH(t, cfg) -- GitLab From 846ab63710b2990fe764c64b32ab00f287bb1e3a Mon Sep 17 00:00:00 2001 From: James Liu Date: Thu, 20 Jun 2024 18:30:43 +1000 Subject: [PATCH 2/7] git: Embed bundled Git binaries When Omnibus conducts a zero-downtime upgrade of GitLab, it swaps Git binaries on the system with new versions as required. This can cause problems if Gitaly is actively invoking `git`, as symlinks are now broken. https://gitlab.com/gitlab-org/gitaly/-/issues/5927 describes the issue in more detail. To address this, we should embed Git binaries within the `gitaly` binary so updates are atomic. Modify packedBinariesFS to include the Git binaries we build as part of the `build-bundled-git` Makefile target. This target is executed when Gitaly is configured to use a "bundled" Git installation. This is the default for production, and subsequent commits will make this the default for testing too. Modify the Makefile to declare the newly packed binaries, and ensure that Git is compiled before Gitaly. Retain the `install-bundled-git` commands as they are required downstream, but will essentially become a no-op. For more information on Git execution environments, see the documentation at doc/git-execution-environments.md. Changelog: added --- Makefile | 9 ++++++++- internal/gitaly/config/config.go | 3 ++- packed_binaries.go | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bbf400327f..f3a41608b9 100644 --- a/Makefile +++ b/Makefile @@ -204,10 +204,16 @@ BUILD_GEM_OPTIONS ?= ## Options to override the name of Gitaly gem BUILD_GEM_NAME ?= gitaly +# Git binaries that are eventually embedded into the Gitaly binary. +GIT_PACKED_EXECUTABLES = $(addprefix ${BUILD_DIR}/bin/gitaly-, \ + $(addsuffix -v2.45, ${GIT_EXECUTABLES})) + # All executables provided by Gitaly. GITALY_EXECUTABLES = $(addprefix ${BUILD_DIR}/bin/,$(notdir $(shell find ${SOURCE_DIR}/cmd -mindepth 1 -maxdepth 1 -type d -print))) # All executables packed inside the Gitaly binary. -GITALY_PACKED_EXECUTABLES = $(filter %gitaly-hooks %gitaly-gpg %gitaly-ssh %gitaly-lfs-smudge, ${GITALY_EXECUTABLES}) +GITALY_PACKED_EXECUTABLES = $(filter %gitaly-hooks %gitaly-gpg %gitaly-ssh %gitaly-lfs-smudge, ${GITALY_EXECUTABLES}) \ + ${GIT_PACKED_EXECUTABLES} + # All executables that should be installed. GITALY_INSTALLED_EXECUTABLES = $(filter-out ${GITALY_PACKED_EXECUTABLES}, ${GITALY_EXECUTABLES}) # Find all Go source files. @@ -583,6 +589,7 @@ ${BUILD_DIR}/bin/%: ${BUILD_DIR}/intermediate/% | ${BUILD_DIR}/bin clear-go-build-cache-if-needed: ${Q}if [ -d ${GOCACHE} ] && [ $$(du -sk ${GOCACHE} | cut -f 1) -gt ${GOCACHE_MAX_SIZE_KB} ]; then go clean --cache; fi +${BUILD_DIR}/intermediate/gitaly: build-bundled-git ${BUILD_DIR}/intermediate/gitaly: GO_BUILD_TAGS = ${SERVER_BUILD_TAGS} ${BUILD_DIR}/intermediate/gitaly: ${GITALY_PACKED_EXECUTABLES} ${BUILD_DIR}/intermediate/praefect: GO_BUILD_TAGS = ${SERVER_BUILD_TAGS} diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go index ae7e1f282c..72526a14a4 100644 --- a/internal/gitaly/config/config.go +++ b/internal/gitaly/config/config.go @@ -945,7 +945,8 @@ func validateIsDirectory(path, name string) error { } // packedBinaries are the binaries that are packed in the main Gitaly binary. This should always match -// the actual list in /packed_binaries.go so the binaries are correctly located. +// the actual list in /packed_binaries.go so the binaries are correctly located. Git binaries are +// excepted, as they are wired up using a separate mechanism. // // Resolving the names automatically from the packed binaries is not possible at the moment due to how // the packed binaries themselves depend on this config package. If this config package inspected the diff --git a/packed_binaries.go b/packed_binaries.go index cb9dc3a7ae..28f2785ba9 100644 --- a/packed_binaries.go +++ b/packed_binaries.go @@ -14,10 +14,11 @@ import ( // buildDir is the directory path where our build target places the built binaries. const buildDir = "_build/bin" -// packedBinariesFS contains embedded binaries. If you modify the above embeddings, you must also update +// packedBinariesFS contains embedded binaries. If you modify the below embeddings, you must also update // GITALY_PACKED_EXECUTABLES in Makefile and packedBinaries in internal/gitaly/config/config.go. // //go:embed _build/bin/gitaly-hooks _build/bin/gitaly-ssh _build/bin/gitaly-lfs-smudge _build/bin/gitaly-gpg +//go:embed _build/bin/gitaly-git-* var packedBinariesFS embed.FS // UnpackAuxiliaryBinaries unpacks the packed auxiliary binaries of Gitaly into destination directory. -- GitLab From 3ce97836e7e78417ae6feb71bdab9dea10807150 Mon Sep 17 00:00:00 2001 From: James Liu Date: Tue, 2 Jul 2024 10:49:29 +1000 Subject: [PATCH 3/7] test: Default to using bundled Git Modify testcfg.Build() to ensure we use the bundled Git environment by default. Add a warning if the bundled Git environment was not compiled with `make build-bundled-git` prior to running tests. This can occur if the user runs tests via an IDE instead of `make test ...`, as that will bypass the dependency to build Git. Compiling Git adds some overhead to the initial test being run (about 20 seconds on my machine), but the compiled binaries remain in the _build/bin directory for subsequent tests to use. This is true for CI as well. --- .gitlab-ci.yml | 2 +- Makefile | 2 +- _support/benchmarking/roles/gitaly/tasks/setup_gitaly.yml | 3 --- internal/cli/gitaly/subcmd_check.go | 7 +++++++ internal/git/gittest/command_factory.go | 6 ++++++ internal/testhelper/testcfg/gitaly.go | 4 ++++ 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7958632f0a..21537348aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -136,7 +136,7 @@ workflow: # But the actual tests should run unprivileged. This assures that we pay # proper attention to permission bits and that we don't modify the source # directory. - - setpriv --reuid=${TEST_UID} --regid=${TEST_UID} --clear-groups --no-new-privs make ${TEST_TARGET} $(test "${GIT_VERSION}" = default && echo WITH_BUNDLED_GIT=YesPlease) + - setpriv --reuid=${TEST_UID} --regid=${TEST_UID} --clear-groups --no-new-privs make ${TEST_TARGET} coverage: /^total:\t+\(statements\)\t+\d+\.\d+%$/ after_script: &test_after_script - go run "${CI_PROJECT_DIR}/tools/panic-parser" diff --git a/Makefile b/Makefile index f3a41608b9..163dee1ffa 100644 --- a/Makefile +++ b/Makefile @@ -140,6 +140,7 @@ OVERRIDE_GIT_VERSION ?= ${GIT_VERSION} # if it is either set to the empty string or "default". ifeq (${GIT_VERSION:default=},) override GIT_VERSION := ${GIT_VERSION_2_45} + export WITH_BUNDLED_GIT = YesPlease else # Support both vX.Y.Z and X.Y.Z version patterns, since callers across GitLab # use both. @@ -310,7 +311,6 @@ build: build-bundled-git prepare-tests: build-bundled-git install: install-bundled-git -export GITALY_TESTING_BUNDLED_GIT_PATH ?= ${BUILD_DIR}/bin else prepare-tests: ${DEPENDENCY_DIR}/git-distribution/git diff --git a/_support/benchmarking/roles/gitaly/tasks/setup_gitaly.yml b/_support/benchmarking/roles/gitaly/tasks/setup_gitaly.yml index 42f9a5e67f..a4a8d39f3b 100644 --- a/_support/benchmarking/roles/gitaly/tasks/setup_gitaly.yml +++ b/_support/benchmarking/roles/gitaly/tasks/setup_gitaly.yml @@ -66,8 +66,6 @@ - name: Build Gitaly make: target: build - params: - WITH_BUNDLED_GIT: YesPlease jobs: "{{ ansible_processor_nproc }}" chdir: /src/gitaly environment: @@ -78,7 +76,6 @@ make: target: install params: - WITH_BUNDLED_GIT: YesPlease PREFIX: /opt/gitaly jobs: "{{ ansible_processor_nproc }}" chdir: /src/gitaly diff --git a/internal/cli/gitaly/subcmd_check.go b/internal/cli/gitaly/subcmd_check.go index 9be414ad80..31ab8adc92 100644 --- a/internal/cli/gitaly/subcmd_check.go +++ b/internal/cli/gitaly/subcmd_check.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/urfave/cli/v2" + "gitlab.com/gitlab-org/gitaly/v16" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config/prometheus" @@ -45,6 +46,12 @@ func checkAction(ctx *cli.Context) error { return fmt.Errorf("loading configuration %q: %w", configPath, err) } + // Since this subcommand invokes a Git command, we need to unpack the bundled Git binaries + // from the Gitaly binary. + if err := gitaly.UnpackAuxiliaryBinaries(cfg.RuntimeDir); err != nil { + return fmt.Errorf("unpack auxiliary binaries: %w", err) + } + fmt.Fprint(ctx.App.Writer, "Checking GitLab API access: ") info, err := checkAPI(cfg, logger) if err != nil { diff --git a/internal/git/gittest/command_factory.go b/internal/git/gittest/command_factory.go index 8c4af980f8..2cfb48901f 100644 --- a/internal/git/gittest/command_factory.go +++ b/internal/git/gittest/command_factory.go @@ -1,6 +1,7 @@ package gittest import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -13,6 +14,11 @@ import ( func NewCommandFactory(tb testing.TB, cfg config.Cfg, opts ...git.ExecCommandFactoryOption) *git.ExecCommandFactory { tb.Helper() factory, cleanup, err := git.NewExecCommandFactory(cfg, testhelper.SharedLogger(tb), opts...) + if err != nil { + if strings.Contains(err.Error(), "no such file or directory") { + require.FailNowf(tb, "Gitaly tests execute against the bundled Git environment. Have you run `make build-bundled-git`? Error: %s", err.Error()) + } + } require.NoError(tb, err) tb.Cleanup(cleanup) return factory diff --git a/internal/testhelper/testcfg/gitaly.go b/internal/testhelper/testcfg/gitaly.go index 088ec82449..89305b3ca9 100644 --- a/internal/testhelper/testcfg/gitaly.go +++ b/internal/testhelper/testcfg/gitaly.go @@ -149,6 +149,10 @@ func (gc *GitalyCfgBuilder) Build(tb testing.TB) config.Cfg { cfg.Transactions.Enabled = testhelper.IsWALEnabled() + // We force the use of bundled (embedded) binaries unless we're specifically executing tests + // against a custom version of Git. + cfg.Git.UseBundledBinaries = os.Getenv("GITALY_TESTING_GIT_BINARY") == "" + require.NoError(tb, cfg.Sanitize()) require.NoError(tb, cfg.ValidateV2()) -- GitLab From 2b0ecf7f773ba9fd8fad6bcf24947dfb9040817d Mon Sep 17 00:00:00 2001 From: Sami Hiltunen Date: Mon, 15 Jul 2024 09:34:27 +1000 Subject: [PATCH 4/7] gitaly-ssh: Restore original working directory A subsequent commit will execute `go env GOMOD` within tests to determine the root directory of the Gitaly repository. This command relies on the current working directly being within the Gitaly source tree. The gitaly-ssh command gets compiled and invoked by a test, and it internally will change the working directory. Modify the gitaly-ssh command so that it restores the previous working directory after executing. --- cmd/gitaly-ssh/main.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cmd/gitaly-ssh/main.go b/cmd/gitaly-ssh/main.go index e8a86997f2..6ce3b72942 100644 --- a/cmd/gitaly-ssh/main.go +++ b/cmd/gitaly-ssh/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" "os" "strconv" @@ -89,7 +90,7 @@ func main() { os.Exit(code) } -func (cmd gitalySSHCommand) run(logger log.Logger) (int, error) { +func (cmd gitalySSHCommand) run(logger log.Logger) (_ int, returnedErr error) { // Configure distributed tracing closer := tracing.Initialize(tracing.WithServiceName("gitaly-ssh")) defer closer.Close() @@ -114,6 +115,17 @@ func (cmd gitalySSHCommand) run(logger log.Logger) (int, error) { } if cmd.workingDir != "" { + originalWD, err := os.Getwd() + if err != nil { + return 0, fmt.Errorf("getwd: %w", err) + } + + defer func() { + if err := os.Chdir(originalWD); err != nil { + returnedErr = errors.Join(err, fmt.Errorf("restore working directory: %w", err)) + } + }() + if err := os.Chdir(cmd.workingDir); err != nil { return 1, fmt.Errorf("unable to chdir to %v", cmd.workingDir) } -- GitLab From bd7ad8b8a4064be267e70f67e32b0b332871acfc Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 5 Jul 2024 14:00:15 +1000 Subject: [PATCH 5/7] test: Wire up bundled Git binaries When configuring the Git execution environment, we source compiled Git binaries from cfg.BinDir or GITALY_TESTING_BUNDLED_GIT_PATH if it's set. In production (on Omnibus for example), cfg.BinDir will point to /opt/gitlab/embedded/bin which contains shared binaries across the entire GitLab installation such as `gitlab-workhorse` and `gitaly-backup`. The Git binaries end up there because Omnibus executes our `build-bundled-git` Makefile target during installation. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/config/software/git.rb for more information. During testing, the Makefile sets GITALY_TESTING_BUNDLED_GIT_PATH to _build/bin in an effort to make the tests use the bundled Git environment. Unfortunately, if the tests are run without using the `make test...` target (such as in an IDE), the env var is not set and tests will fall-back to whatever Git is on the PATH. Simplify this logic by using cfg.RuntimeDir for both testing and production: - In testing, remove the GITALY_TESTING_BUNDLED_GIT_PATH env var. Instead, we can symlink the various Git binaries in _build/bin to cfg.RuntimeDir and issue a warning that fails the test if the Git binaries were not present in _build/bin. - In production, use cfg.RuntimeDir directly to source the Git binaries. We unpack the embedded Git binaries in packed_binaries.go into that directory on startup. Since Gitaly's RuntimeDir is independent of how Gitaly was packaged, we retain full control over the Git binary that we execute in production. --- cmd/gitaly/check_test.go | 4 +- internal/git/command_factory_test.go | 178 +++------------------ internal/git/execution_environment.go | 68 +++----- internal/git/execution_environment_test.go | 142 +--------------- internal/testhelper/testcfg/gitaly.go | 38 ++++- internal/testhelper/testhelper.go | 7 + 6 files changed, 93 insertions(+), 344 deletions(-) diff --git a/cmd/gitaly/check_test.go b/cmd/gitaly/check_test.go index 5963670517..f1fbdf2887 100644 --- a/cmd/gitaly/check_test.go +++ b/cmd/gitaly/check_test.go @@ -36,7 +36,7 @@ func TestCheckOK(t *testing.T) { }, SecretFile: gitlab.WriteShellSecretFile(t, testhelper.TempDir(t), "the secret"), }, - })) + }), testcfg.WithDisableBundledGitSymlinking()) testcfg.BuildGitaly(t, cfg) configPath := testcfg.WriteTemporaryGitalyConfigFile(t, cfg) @@ -81,7 +81,7 @@ func TestCheckBadCreds(t *testing.T) { }, SecretFile: gitlab.WriteShellSecretFile(t, testhelper.TempDir(t), "the secret"), }, - })) + }), testcfg.WithDisableBundledGitSymlinking()) testcfg.BuildGitaly(t, cfg) configPath := testcfg.WriteTemporaryGitalyConfigFile(t, cfg) diff --git a/internal/git/command_factory_test.go b/internal/git/command_factory_test.go index 9942a3b246..45a3659d87 100644 --- a/internal/git/command_factory_test.go +++ b/internal/git/command_factory_test.go @@ -3,9 +3,7 @@ package git_test import ( "bytes" "context" - "errors" "fmt" - "io/fs" "math/rand" "net/http" "net/http/httptest" @@ -13,7 +11,6 @@ import ( "os/exec" "path/filepath" "strings" - "syscall" "testing" "time" @@ -411,7 +408,6 @@ func TestExecCommandFactory_addAttrTree(t *testing.T) { func TestCommandFactory_ExecutionEnvironment(t *testing.T) { testhelper.Unsetenv(t, "GITALY_TESTING_GIT_BINARY") - testhelper.Unsetenv(t, "GITALY_TESTING_BUNDLED_GIT_PATH") ctx := testhelper.Context(t) @@ -461,161 +457,33 @@ func TestCommandFactory_ExecutionEnvironment(t *testing.T) { }) }) - t.Run("set using GITALY_TESTING_BUNDLED_GIT_PATH", func(t *testing.T) { - writeExecutables := func(t *testing.T, dir string, executables ...string) { - for _, executable := range executables { - testhelper.WriteExecutable(t, filepath.Join(dir, executable), nil) - } - } - - missingBinaryError := func(dir, binary string) error { - return fmt.Errorf("setting up Git execution environment: %w", - fmt.Errorf("constructing Git environment: %w", - fmt.Errorf("statting %q: %w", binary, - &fs.PathError{ - Op: "stat", - Path: filepath.Join(dir, binary), - Err: syscall.ENOENT, - }, - ), - ), - ) - } + t.Run("set with the default bundled Git environment", func(t *testing.T) { + suffix := git.BundledGitConstructors[0].Suffix - type setupData struct { - binaryDir string - bundledGitDir string - expectedErr error - } + cfg := testcfg.Build(t) - for _, tc := range []struct { - desc string - setup func(t *testing.T) setupData - }{ - { - desc: "unset binary directory", - setup: func(t *testing.T) setupData { - return setupData{ - binaryDir: "", - bundledGitDir: "/does/not/exist", - expectedErr: fmt.Errorf("setting up Git execution environment: %w", - fmt.Errorf("constructing Git environment: %w", - errors.New("cannot use bundled binaries without bin path being set"), - ), - ), - } - }, - }, - { - desc: "missing directory", - setup: func(t *testing.T) setupData { - return setupData{ - binaryDir: testhelper.TempDir(t), - bundledGitDir: "/does/not/exist", - expectedErr: missingBinaryError("/does/not/exist", "gitaly-git-v2.38"), - } - }, - }, - { - desc: "missing gitaly-git", - setup: func(t *testing.T) setupData { - bundledGitDir := testhelper.TempDir(t) - - return setupData{ - binaryDir: testhelper.TempDir(t), - bundledGitDir: bundledGitDir, - expectedErr: missingBinaryError(bundledGitDir, "gitaly-git-v2.38"), - } - }, - }, - { - desc: "missing gitaly-git-remote-http", - setup: func(t *testing.T) setupData { - bundledGitDir := testhelper.TempDir(t) - - writeExecutables(t, bundledGitDir, "gitaly-git-v2.38") - - return setupData{ - binaryDir: testhelper.TempDir(t), - bundledGitDir: bundledGitDir, - expectedErr: missingBinaryError(bundledGitDir, "gitaly-git-remote-http-v2.38"), - } - }, - }, - { - desc: "missing gitaly-git-http-backend", - setup: func(t *testing.T) setupData { - bundledGitDir := testhelper.TempDir(t) - - writeExecutables(t, bundledGitDir, - "gitaly-git-v2.38", - "gitaly-git-remote-http-v2.38", - ) - - return setupData{ - binaryDir: testhelper.TempDir(t), - bundledGitDir: bundledGitDir, - expectedErr: missingBinaryError(bundledGitDir, "gitaly-git-http-backend-v2.38"), - } + gitCmdFactory, cleanup, err := git.NewExecCommandFactory( + cfg, + testhelper.SharedLogger(t), + git.WithSkipHooks(), + git.WithExecutionEnvironmentConstructors( + git.BundledGitEnvironmentConstructor{ + Suffix: suffix, }, - }, - { - desc: "successful", - setup: func(t *testing.T) setupData { - bundledGitDir := testhelper.TempDir(t) - writeExecutables(t, bundledGitDir, "gitaly-git-v2.38", "gitaly-git-remote-http-v2.38", "gitaly-git-http-backend-v2.38") - - return setupData{ - binaryDir: testhelper.TempDir(t), - bundledGitDir: bundledGitDir, - } - }, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - setupData := tc.setup(t) - t.Setenv("GITALY_TESTING_BUNDLED_GIT_PATH", setupData.bundledGitDir) - - gitCmdFactory, cleanup, err := git.NewExecCommandFactory( - config.Cfg{ - BinDir: setupData.binaryDir, - }, - testhelper.SharedLogger(t), - git.WithSkipHooks(), - // Override the constructors so that we don't have to change - // tests every time we're upgrading our bundled Git version. - git.WithExecutionEnvironmentConstructors( - git.BundledGitEnvironmentConstructor{ - Suffix: "-v2.38", - }, - ), - ) - require.Equal(t, setupData.expectedErr, err) - if err != nil { - return - } - defer cleanup() + ), + ) + require.NoError(t, err) + defer cleanup() - // When the command factory has been successfully created then we - // verify that the symlink that it has created match what we expect. - gitBinPath := gitCmdFactory.GetExecutionEnvironment(ctx).BinaryPath - for _, executable := range []string{"git", "git-remote-http", "git-http-backend"} { - symlinkPath := filepath.Join(filepath.Dir(gitBinPath), executable) - - // The symlink in Git's temporary BinPath points to the Git - // executable in Gitaly's BinDir. - target, err := os.Readlink(symlinkPath) - require.NoError(t, err) - require.Equal(t, filepath.Join(setupData.binaryDir, "gitaly-"+executable+"-v2.38"), target) - - // And in a test setup, the symlink in Gitaly's BinDir must point to - // the Git binary pointed to by the GITALY_TESTING_BUNDLED_GIT_PATH - // environment variable. - target, err = os.Readlink(target) - require.NoError(t, err) - require.Equal(t, filepath.Join(setupData.bundledGitDir, "gitaly-"+executable+"-v2.38"), target) - } - }) + // When the command factory has been successfully created then we + // verify that the symlink that it has created match what we expect. + gitBinPath := gitCmdFactory.GetExecutionEnvironment(ctx).BinaryPath + for _, binary := range []string{"git", "git-remote-http", "git-http-backend"} { + symlinkPath := filepath.Join(filepath.Dir(gitBinPath), binary) + + target, err := os.Readlink(symlinkPath) + require.NoError(t, err) + require.Equal(t, filepath.Join(cfg.RuntimeDir, fmt.Sprintf("gitaly-%s%s", binary, suffix)), target) } }) diff --git a/internal/git/execution_environment.go b/internal/git/execution_environment.go index a44755d5cb..65ba714d54 100644 --- a/internal/git/execution_environment.go +++ b/internal/git/execution_environment.go @@ -18,17 +18,28 @@ var ( // environment was not configured. ErrNotConfigured = errors.New("execution environment is not configured") + // BundledGitConstructors defines the versions of Git that we embed into the Gitaly + // binary. + BundledGitConstructors = []BundledGitEnvironmentConstructor{ + { + Suffix: "-v2.45", + }, + } + // defaultExecutionEnvironmentConstructors is the list of Git environments supported by the // Git command factory. The order is important and signifies the priority in which the // environments will be used: the environment created by the first constructor is the one // that will be preferred when executing Git commands. Later environments may be used in // case `IsEnabled()` returns `false` though. - defaultExecutionEnvironmentConstructors = []ExecutionEnvironmentConstructor{ - BundledGitEnvironmentConstructor{ - Suffix: "-v2.45", - }, - DistributedGitEnvironmentConstructor{}, - } + defaultExecutionEnvironmentConstructors = func() (constructors []ExecutionEnvironmentConstructor) { + for _, c := range BundledGitConstructors { + constructors = append(constructors, c) + } + + constructors = append(constructors, DistributedGitEnvironmentConstructor{}) + + return constructors + }() ) // ExecutionEnvironmentConstructor is an interface for constructors of Git execution environments. @@ -126,52 +137,11 @@ type BundledGitEnvironmentConstructor struct { // binaries with their usual names as expected by Git. Furthermore, we configure the GIT_EXEC_PATH // environment variable to point to that directory such that Git is able to locate its auxiliary // binaries. -// -// For testing purposes, this function will automatically enable use of bundled Git in case the -// `GITALY_TESTING_BUNDLED_GIT_PATH` environment variable is set. func (c BundledGitEnvironmentConstructor) Construct(cfg config.Cfg) (_ ExecutionEnvironment, returnedErr error) { - useBundledBinaries := cfg.Git.UseBundledBinaries - - if bundledGitPath := os.Getenv("GITALY_TESTING_BUNDLED_GIT_PATH"); bundledGitPath != "" { - if cfg.BinDir == "" { - return ExecutionEnvironment{}, errors.New("cannot use bundled binaries without bin path being set") - } - - // We need to symlink pre-built Git binaries into Gitaly's binary directory. - // Normally they would of course already exist there, but in tests we create a new - // binary directory for each server and thus need to populate it first. - for _, binary := range []string{"gitaly-git", "gitaly-git-remote-http", "gitaly-git-http-backend"} { - binary := binary + c.Suffix - - bundledGitBinary := filepath.Join(bundledGitPath, binary) - if _, err := os.Stat(bundledGitBinary); err != nil { - return ExecutionEnvironment{}, fmt.Errorf("statting %q: %w", binary, err) - } - - if err := os.Symlink(bundledGitBinary, filepath.Join(cfg.BinDir, binary)); err != nil { - // Multiple Git command factories might be created for the same configuration. - // Each of them will create the execution environment every time, - // therefore these symlinks might already exist. - // It would be nice if we could fix this, but gracefully handling the error is a - // more boring solution. - if errors.Is(err, os.ErrExist) { - continue - } - return ExecutionEnvironment{}, fmt.Errorf("symlinking bundled %q: %w", binary, err) - } - } - - useBundledBinaries = true - } - - if !useBundledBinaries { + if !cfg.Git.UseBundledBinaries { return ExecutionEnvironment{}, ErrNotConfigured } - if cfg.BinDir == "" { - return ExecutionEnvironment{}, errors.New("cannot use bundled binaries without bin path being set") - } - // In order to support having a single Git binary only as compared to a complete Git // installation, we create our own GIT_EXEC_PATH which contains symlinks to the Git // binary for executables which Git expects to be present. @@ -205,7 +175,7 @@ func (c BundledGitEnvironmentConstructor) Construct(cfg config.Cfg) (_ Execution "git-remote-ftps": "gitaly-git-remote-http", } { target := target + c.Suffix - targetPath := filepath.Join(cfg.BinDir, target) + targetPath := filepath.Join(cfg.RuntimeDir, target) if err := unix.Access(targetPath, unix.X_OK); err != nil { return ExecutionEnvironment{}, fmt.Errorf("checking bundled Git binary %q: %w", target, err) diff --git a/internal/git/execution_environment_test.go b/internal/git/execution_environment_test.go index 8dbf5b8de2..773df99885 100644 --- a/internal/git/execution_environment_test.go +++ b/internal/git/execution_environment_test.go @@ -1,7 +1,6 @@ package git_test import ( - "errors" "fmt" "os" "path/filepath" @@ -12,6 +11,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage/mode" "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg" ) func TestDistributedGitEnvironmentConstructor(t *testing.T) { @@ -67,52 +67,18 @@ func TestDistributedGitEnvironmentConstructor(t *testing.T) { } func TestBundledGitEnvironmentConstructor(t *testing.T) { - testhelper.Unsetenv(t, "GITALY_TESTING_BUNDLED_GIT_PATH") - - constructor := git.BundledGitEnvironmentConstructor{} + testhelper.Unsetenv(t, "GITALY_TESTING_GIT_BINARY") - seedDirWithExecutables := func(t *testing.T, executableNames ...string) string { - dir := testhelper.TempDir(t) - for _, executableName := range executableNames { - require.NoError(t, os.WriteFile(filepath.Join(dir, executableName), nil, mode.Executable)) - } - return dir - } + constructor := git.BundledGitConstructors[0] t.Run("disabled bundled Git fails", func(t *testing.T) { _, err := constructor.Construct(config.Cfg{}) require.Equal(t, git.ErrNotConfigured, err) }) - t.Run("bundled Git without binary directory fails", func(t *testing.T) { - _, err := constructor.Construct(config.Cfg{ - Git: config.Git{ - UseBundledBinaries: true, - }, - }) - require.Equal(t, errors.New("cannot use bundled binaries without bin path being set"), err) - }) - - t.Run("incomplete binary directory succeeds", func(t *testing.T) { - _, err := constructor.Construct(config.Cfg{ - BinDir: seedDirWithExecutables(t, "gitaly-git", "gitaly-git-remote-http"), - Git: config.Git{ - UseBundledBinaries: true, - }, - }) - require.Error(t, err) - require.Equal(t, "checking bundled Git binary \"gitaly-git-http-backend\": no such file or directory", err.Error()) - }) - t.Run("complete binary directory succeeds", func(t *testing.T) { - binDir := seedDirWithExecutables(t, "gitaly-git", "gitaly-git-remote-http", "gitaly-git-http-backend") - - execEnv, err := constructor.Construct(config.Cfg{ - BinDir: binDir, - Git: config.Git{ - UseBundledBinaries: true, - }, - }) + cfg := testcfg.Build(t) + execEnv, err := constructor.Construct(cfg) require.NoError(t, err) defer func() { require.NoError(t, execEnv.Cleanup()) }() @@ -128,103 +94,7 @@ func TestBundledGitEnvironmentConstructor(t *testing.T) { for _, binary := range []string{"git", "git-remote-http", "git-http-backend"} { target, err := filepath.EvalSymlinks(filepath.Join(execPrefix, binary)) require.NoError(t, err) - require.Equal(t, filepath.Join(binDir, "gitaly-"+binary), target) - } - }) - - t.Run("cleanup removes temporary directory", func(t *testing.T) { - execEnv, err := constructor.Construct(config.Cfg{ - BinDir: seedDirWithExecutables(t, "gitaly-git", "gitaly-git-remote-http", "gitaly-git-http-backend"), - Git: config.Git{ - UseBundledBinaries: true, - }, - }) - require.NoError(t, err) - - execPrefix := filepath.Dir(execEnv.BinaryPath) - require.DirExists(t, execPrefix) - - require.NoError(t, execEnv.Cleanup()) - - require.NoDirExists(t, execPrefix) - }) - - t.Run("bundled Git path without binary directory fails", func(t *testing.T) { - t.Setenv("GITALY_TESTING_BUNDLED_GIT_PATH", "/does/not/exist") - _, err := constructor.Construct(config.Cfg{}) - require.Equal(t, errors.New("cannot use bundled binaries without bin path being set"), err) - }) - - t.Run("nonexistent bundled Git path via environment fails", func(t *testing.T) { - t.Setenv("GITALY_TESTING_BUNDLED_GIT_PATH", "/does/not/exist") - _, err := constructor.Construct(config.Cfg{ - BinDir: testhelper.TempDir(t), - }) - require.Error(t, err) - require.Equal(t, err.Error(), "statting \"gitaly-git\": stat /does/not/exist/gitaly-git: no such file or directory") - }) - - t.Run("incomplete bundled Git environment fails", func(t *testing.T) { - bundledGitPath := seedDirWithExecutables(t, "gitaly-git", "gitaly-git-remote-http") - t.Setenv("GITALY_TESTING_BUNDLED_GIT_PATH", bundledGitPath) - - _, err := constructor.Construct(config.Cfg{ - BinDir: testhelper.TempDir(t), - }) - require.Error(t, err) - require.Contains(t, err.Error(), "statting \"gitaly-git-http-backend\": ") - }) - - t.Run("complete bundled Git environment populates binary directory", func(t *testing.T) { - bundledGitPath := seedDirWithExecutables(t, "gitaly-git", "gitaly-git-remote-http", "gitaly-git-http-backend") - t.Setenv("GITALY_TESTING_BUNDLED_GIT_PATH", bundledGitPath) - - execEnv, err := constructor.Construct(config.Cfg{ - BinDir: testhelper.TempDir(t), - }) - require.NoError(t, err) - defer func() { require.NoError(t, execEnv.Cleanup()) }() - - require.Equal(t, "git", filepath.Base(execEnv.BinaryPath)) - execPrefix := filepath.Dir(execEnv.BinaryPath) - - require.Equal(t, []string{ - "GIT_EXEC_PATH=" + execPrefix, - }, execEnv.EnvironmentVariables) - - for _, binary := range []string{"git", "git-remote-http", "git-http-backend"} { - target, err := filepath.EvalSymlinks(filepath.Join(execPrefix, binary)) - require.NoError(t, err) - require.Equal(t, filepath.Join(bundledGitPath, "gitaly-"+binary), target) - } - }) - - t.Run("with version suffix", func(t *testing.T) { - constructor := git.BundledGitEnvironmentConstructor{ - Suffix: "-v2.35.1", - } - - binDir := seedDirWithExecutables(t, "gitaly-git-v2.35.1", "gitaly-git-remote-http-v2.35.1", "gitaly-git-http-backend-v2.35.1") - - execEnv, err := constructor.Construct(config.Cfg{ - BinDir: binDir, - Git: config.Git{ - UseBundledBinaries: true, - }, - }) - require.NoError(t, err) - defer func() { require.NoError(t, execEnv.Cleanup()) }() - - require.Equal(t, "git", filepath.Base(execEnv.BinaryPath)) - execPrefix := filepath.Dir(execEnv.BinaryPath) - require.Equal(t, []string{ - "GIT_EXEC_PATH=" + execPrefix, - }, execEnv.EnvironmentVariables) - - for _, binary := range []string{"git", "git-remote-http", "git-http-backend"} { - target, err := filepath.EvalSymlinks(filepath.Join(execPrefix, binary)) - require.NoError(t, err) - require.Equal(t, filepath.Join(binDir, "gitaly-"+binary+"-v2.35.1"), target) + require.Equal(t, filepath.Join(testhelper.SourceRoot(t), "_build", "bin", fmt.Sprintf("gitaly-%s%s", binary, constructor.Suffix)), target) } }) } diff --git a/internal/testhelper/testcfg/gitaly.go b/internal/testhelper/testcfg/gitaly.go index 89305b3ca9..0e1a910c18 100644 --- a/internal/testhelper/testcfg/gitaly.go +++ b/internal/testhelper/testcfg/gitaly.go @@ -48,6 +48,19 @@ func WithPackObjectsCacheEnabled() Option { } } +// WithDisableBundledGitSymlinking stops the builder from symlinking the +// compiled bundled Git binaries in _build/bin to cfg.RuntimeDir. This is +// useful for tests like cmd/gitaly/check_test.go which executes a test +// against a compiled Gitaly binary. If this option is not supplied, both +// the test and the Gitaly process it spawns will try to populate RuntimeDir +// with the same binaries, which causes a "file exists" exception in +// packed_binaries.go +func WithDisableBundledGitSymlinking() Option { + return func(builder *GitalyCfgBuilder) { + builder.disableBundledGitSymlinking = true + } +} + // NewGitalyCfgBuilder returns gitaly configuration builder with configured set of options. func NewGitalyCfgBuilder(opts ...Option) GitalyCfgBuilder { cfgBuilder := GitalyCfgBuilder{} @@ -63,8 +76,9 @@ func NewGitalyCfgBuilder(opts ...Option) GitalyCfgBuilder { type GitalyCfgBuilder struct { cfg config.Cfg - storages []string - packObjectsCacheEnabled bool + storages []string + packObjectsCacheEnabled bool + disableBundledGitSymlinking bool } // Build setups required filesystem structure, creates and returns configuration of the gitaly service. @@ -102,6 +116,26 @@ func (gc *GitalyCfgBuilder) Build(tb testing.TB) config.Cfg { cfg.RuntimeDir = filepath.Join(root, "runtime.d") require.NoError(tb, os.Mkdir(cfg.RuntimeDir, mode.Directory)) require.NoError(tb, os.Mkdir(cfg.InternalSocketDir(), mode.Directory)) + + if !gc.disableBundledGitSymlinking { + // We do this symlinking to emulate what happens with the Git binaries in production: + // - In production, the Git binaries get unpacked by packed_binaries.go into Gitaly's RuntimeDir, which for + // Omnibus is something like /var/opt/gitlab/gitaly/run/gitaly-. + // - In testing, the binaries remain in the _build/bin directory of the repository root. + bundledGitBins, err := filepath.Glob(filepath.Join(testhelper.SourceRoot(tb), "_build", "bin", "gitaly-git-*")) + require.NoError(tb, err) + + for _, targetPath := range bundledGitBins { + destinationPath := filepath.Join(cfg.RuntimeDir, filepath.Base(targetPath)) + + // It's possible that the same RuntimeDir is used across multiple invocations of + // testcfg.Build(), so we simply remove any existing symlinks. + if _, err := os.Lstat(destinationPath); err == nil { + require.NoError(tb, os.Remove(destinationPath)) + } + require.NoError(tb, os.Symlink(targetPath, destinationPath)) + } + } } if len(cfg.Storages) != 0 && len(gc.storages) != 0 { diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go index b95d3b3260..e9f29b8a52 100644 --- a/internal/testhelper/testhelper.go +++ b/internal/testhelper/testhelper.go @@ -457,3 +457,10 @@ func TestdataAbsolutePath(t *testing.T) string { return filepath.Join(filepath.Dir(currentFile), "testdata") } + +// SourceRoot returns the root directory of the Gitaly repository. +func SourceRoot(tb testing.TB) string { + output, err := exec.Command("go", "env", "GOMOD").CombinedOutput() + require.NoError(tb, err, output) + return filepath.Dir(string(output)) +} -- GitLab From c6c45e17cebf38c746a4bd6102245b33c96332f9 Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 8 Jul 2024 11:38:35 +1000 Subject: [PATCH 6/7] doc: Update Git versioning documentation --- .../issue_templates/Git Version Upgrade.md | 11 +++-- doc/PROCESS.md | 35 ++++------------ doc/git-execution-environments.md | 42 +++++++++---------- 3 files changed, 31 insertions(+), 57 deletions(-) diff --git a/.gitlab/issue_templates/Git Version Upgrade.md b/.gitlab/issue_templates/Git Version Upgrade.md index d179aebc64..e569ffc515 100644 --- a/.gitlab/issue_templates/Git Version Upgrade.md +++ b/.gitlab/issue_templates/Git Version Upgrade.md @@ -11,20 +11,19 @@ Would be really nice to point out contributions made by the Gitaly team, if any. - [ ] Introduce the new Git version behind a feature flag ([Reference](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5587)). - [ ] Introduce the new bundled Git version in the [Makefile](/Makefile). - - [ ] Introduce the new bundled Git execution environment in the [Git package](/internal/git/version.go) behind a feature flag. -- [ ] Roll out the feature flag. + - [ ] Introduce the new bundled Git execution environment in the [Git package](/internal/git/execution_environment.go) behind a feature flag. - [ ] Create an issue for the rollout of the feature flag ([Reference](https://gitlab.com/gitlab-org/gitaly/-/issues/5030)). - [ ] Optional: Create a [change request](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#change-request-workflows) in case the new Git version contains changes that may cause issues. - [ ] Roll out the feature flag. - - [ ] After a release containing feature flag, remove the feature flag. -- [ ] Update the default Git version. This must happen in a release after the feature flag has been removed to avoid issues with zero-downtime upgrades. + - [ ] Once the upgrade is deemed stable, remove the feature flag. You can do this in any release, including the release that adds the feature flag. +- [ ] Update the default Git version. - [ ] Remove the old bundled Git execution environment. - [ ] Remove the old bundled Git version in the [Makefile](/Makefile). - [ ] Update the default Git distribution by updating `GIT_VERSION` to the new Git version in the [Makefile](/Makefile). - [ ] Optional: Upgrade the minimum required Git version. This is only needed when we want to start using features that have been introduced with the new Git version. - [ ] Update the minimum required Git version in the [Git package](/internal/git/version.go). ([Reference](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5705)) - [ ] Update the minimum required Git version in the [README.md](/README.md). - - [ ] Update the GitLab release notes to reflect the new minimum required Git version. ([Reference](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107565). - + - [ ] Update the GitLab release notes to reflect the new minimum required Git version. ([Reference](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107565). + /label ~"Category:Gitaly" ~"group::gitaly" ~"group::gitaly::git" ~"section::enablement" ~"devops::systems" ~"type::maintenance" ~"maintenance::dependency" /label ~"workflow::ready for development" diff --git a/doc/PROCESS.md b/doc/PROCESS.md index 505759b329..87add5fdc0 100644 --- a/doc/PROCESS.md +++ b/doc/PROCESS.md @@ -513,39 +513,18 @@ that we have no such issues with zero-downtime upgrades: 1. We remove the old bundled Git binaries. -Note that because we cannot remove the old Git binaries at the same time when we -add the new ones. We must ensure that both sets exist in parallel for at least -one release. +Note that it is no longer required to wait one release before removing the old +bundled Git binaries. This is because the binaries are now embedded into the +Gitaly binary, thus an upgrade can be performed atomically. A Gitaly process +is in complete control of its Git execution environment when using bundled Git. [bundled-git]: git-execution-environments.md#bundled-git-recommended ### Detailed Process -The following detailed steps need to be done to upgrade to a new Git version: - -1. Add the new bundled Git distribution to the `Makefile`. See - c0d05650be681c2accb4cec5aac74a6dd77a2fa6. - -1. Add a new bundled Git execution environment with a feature flag. See - b547b368c8f584e9aabe8eef9342f99440b0c248. Please note that execution - environments are ordered by decreasing priority: the first environment - whose feature flags are all turned on will be picked. You thus have to - add your new environment to the top. - -1. Roll out the feature flag by following our feature flag process. You may - decide to remove the feature flag before the feature flag is removed in - case it is a low-risk upgrade of the Git version (e.g. when you perform a - patch-release upgrade, only). - -1. Remove the feature flag. See 888e6233fd85691f0852ae6c4a3656da9bf3d8e4. - -1. Remove the execution environment of the old bundled Git version. See - af1a3fe7b536d22a6db9ba6591d222b23d01d83f. - -1. Remove the old set of bundled Git binaries from the `Makefile`. See - 9c700ea473d781eea50eab685d643d95e9c4ffee. Note that this must only happen - _after_ both old and new bundled Git binaries have been installed in - parallel in a release already. +The process is detailed in the +[Git Version Upgrade](https://gitlab.com/gitlab-org/gitaly/-/blob/master/.gitlab/issue_templates/Git%20Version%20Upgrade.md) +issue template. ## Gitaly Releases diff --git a/doc/git-execution-environments.md b/doc/git-execution-environments.md index 76f68053aa..7be7dbe023 100644 --- a/doc/git-execution-environments.md +++ b/doc/git-execution-environments.md @@ -5,7 +5,8 @@ must have access to Git. Gitaly supports three ways of accessing Git: - Bundled Git (recommended): Gitaly accesses bundled Git binaries. Unlike the other two ways of accessing Git, this is not a complete Git installation but - only contains a subset of binaries that are required at runtime. + only contains a subset of binaries that are required at runtime. Bundled Git + binaries are directly embedded into the Gitaly binary during compilation. - External Git distribution (not recommended): Gitaly accesses Git provided by the operating system or manually installed by the administrator. - Gitaly Git distribution (not recommended): Gitaly accesses it's own version of @@ -19,30 +20,29 @@ The bundled Git execution environment is the recommended way to set up Gitaly. Instead of using a full Git distribution, you can use a bundled Git execution environment. -To install bundled Git binaries, run `make install-bundled-git`. This Make -target installs the bundled Git binaries in the `bin` directory of a location -set with the `PREFIX` environment variable. For example: +Bundled Git binaries are compiled whenever Gitaly is compiled. There's no +need to explicitly build and install them. If you're running a test without +using the `make test...` Makefile target, you must run `make build-bundled-git` +to ensure the binaries are present in the `_build/bin` directory. -```shell -make install-bundled-git PREFIX=/usr/local -``` +When Gitaly is compiled for production, the bundled Git binaries are embedded +into the Gitaly binary using the go:embed feature. See `packed_binaries.go`. Bundled Git binaries all have a `gitaly-` prefix so they do not clash with normal Git executables. Furthermore, bundled Git binaries may have a version -suffix which allows Gitaly to install multiple different versions of Git at the -same time. For example, `make install-bundled-git PREFIX=/usr/local` may install -`gitaly-git-v2.35.1` and `gitaly-git-v2.36.0` into `/usr/local/bin`. +suffix which allows Gitaly to use multiple different versions of Git at the +same time. + +Bundled Git solves two important problems: -Being able to install multiple Git versions in parallel solves two important -issues: +- Being able to use multiple Git versions in parallel allows us roll out new + Git versions with the help of feature flags. This means we can roll back to + old versions of Git if a new version is exhibiting faulty behavior. -- When doing zero-downtime upgrades, a new Gitaly package can avoid replacing - Git binaries that are used by the currently running version of Gitaly. Gitaly - doesn't need to run in an intermediate state where the old version of Gitaly - is unexpectedly using newer Git binaries. -- Gitaly can roll out new Git versions with the help of feature flags. This - means we can roll back to old versions of Git if a new version is exhibiting - faulty behavior. +- Embedding the Git binaries allows seamless zero-downtime upgrades, as Gitaly + and Git can be upgraded as an atomic unit. Gitaly doesn't need to run in an + intermediate state where the old version of Gitaly is unexpectedly using newer + Git binaries. Git binaries expect to be able to locate helper binaries at runtime. However, because the bundled Git binaries all have a `gitaly-` prefix, the mechanisms Git @@ -60,10 +60,6 @@ environment. Gitaly can be configured to use bundled binaries by setting the following keys: ```toml -# The binary directory where Gitaly's own binaries are installed. This directory -# will also contain the bundled Git binaries. -bin_dir = "/usr/local/bin" - [git] use_bundled_binaries = true ``` -- GitLab From 660b830224647997fee81378a2f70206d93b8ef3 Mon Sep 17 00:00:00 2001 From: James Liu Date: Tue, 30 Jul 2024 19:34:56 +1000 Subject: [PATCH 7/7] git: Log configured Git version and binary path --- internal/cli/gitaly/serve.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/cli/gitaly/serve.go b/internal/cli/gitaly/serve.go index fb2bc9422f..696eb41e08 100644 --- a/internal/cli/gitaly/serve.go +++ b/internal/cli/gitaly/serve.go @@ -243,6 +243,8 @@ func run(appCtx *cli.Context, cfg config.Cfg, logger log.Logger) error { defer cleanup() logger.WithField("duration_ms", time.Since(began).Milliseconds()).Info("finished initializing command factory") + logger.WithField("binary_path", gitCmdFactory.GetExecutionEnvironment(ctx).BinaryPath).Info("using Git binary") + began = time.Now() gitVersion, err := gitCmdFactory.GitVersion(ctx) if err != nil { @@ -254,6 +256,8 @@ func run(appCtx *cli.Context, cfg config.Cfg, logger log.Logger) error { return fmt.Errorf("unsupported Git version: %q", gitVersion) } + logger.WithField("version", gitVersion.String()).Info("using Git version") + registry := backchannel.NewRegistry() transactionManager := transaction.NewManager(cfg, logger, registry) prometheus.MustRegister(transactionManager) -- GitLab