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:
authorJacob Vosmaer <jacob@gitlab.com>2019-10-01 19:16:38 +0300
committerJacob Vosmaer <jacob@gitlab.com>2019-10-01 19:16:38 +0300
commit950b34432b20a1059d29b46801b0c9233f9769dd (patch)
tree03196ba934737cd39204c1a3ced9b250fac0618a
parent210c94447d0d8d4613a4ab9a86d606bbc46d3f2f (diff)
parentb676876889ce332b26efb93ddd8d5d9649da2776 (diff)
Merge branch 'jc-hook-binary' into 'master'
Create go binary to execute hooks Closes #1859 See merge request gitlab-org/gitaly!1328
-rw-r--r--.gitignore1
-rw-r--r--NOTICE253
-rw-r--r--changelogs/unreleased/jc-hook-binary.yml5
-rw-r--r--cmd/gitaly-hooks/.gitignore2
-rw-r--r--cmd/gitaly-hooks/hooks.go61
-rw-r--r--cmd/gitaly-hooks/hooks_test.go300
-rwxr-xr-xcmd/gitaly-hooks/testdata/update4
-rw-r--r--go.mod4
-rw-r--r--go.sum7
-rw-r--r--internal/git/receivepack.go2
-rw-r--r--internal/gitlabshell/env.go2
-rw-r--r--internal/log/hook.go42
-rw-r--r--internal/rubyserver/rubyserver.go1
-rwxr-xr-xruby/git-hooks/gitlab-shell-hook9
-rw-r--r--ruby/lib/gitlab/config.rb8
-rw-r--r--ruby/lib/gitlab/git/hook.rb2
-rw-r--r--ruby/lib/gitlab/git/remote_repository.rb2
-rw-r--r--ruby/spec/spec_helper.rb2
18 files changed, 688 insertions, 19 deletions
diff --git a/.gitignore b/.gitignore
index cde28605c..dfa56ee86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
/_build
/gitaly
+/gitaly-hooks
cmd/gitaly-ssh/gitaly-ssh
/gitaly-ssh
cmd/gitaly-wrapper/gitaly-wrapper
diff --git a/NOTICE b/NOTICE
index 848d69da0..ad10493ef 100644
--- a/NOTICE
+++ b/NOTICE
@@ -3555,4 +3555,255 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LICENSE-3rdparty.csv - gitlab.com/gitlab-org/gitaly/vendor/gopkg.in/DataDog/dd-trace-go.v1
Component,Origin,License,Copyright
-import,io.opentracing,Apache-2.0,Copyright 2016-2017 The OpenTracing Authors
+import,io.opentracing,Apache-2.0,Copyright 2016-2017 The OpenTracing Authors~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+LICENSE - gitlab.com/gitlab-org/gitaly/vendor/gopkg.in/yaml.v2
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+LICENSE.libyaml - gitlab.com/gitlab-org/gitaly/vendor/gopkg.in/yaml.v2
+The following files were ported to Go from C files of libyaml, and thus
+are still covered by their original copyright and license:
+
+ apic.go
+ emitterc.go
+ parserc.go
+ readerc.go
+ scannerc.go
+ writerc.go
+ yamlh.go
+ yamlprivateh.go
+
+Copyright (c) 2006 Kirill Simonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+NOTICE - gitlab.com/gitlab-org/gitaly/vendor/gopkg.in/yaml.v2
+Copyright 2011-2016 Canonical Ltd.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/changelogs/unreleased/jc-hook-binary.yml b/changelogs/unreleased/jc-hook-binary.yml
new file mode 100644
index 000000000..fa8a22077
--- /dev/null
+++ b/changelogs/unreleased/jc-hook-binary.yml
@@ -0,0 +1,5 @@
+---
+title: Create go binary to execute hooks
+merge_request: 1328
+author:
+type: other
diff --git a/cmd/gitaly-hooks/.gitignore b/cmd/gitaly-hooks/.gitignore
new file mode 100644
index 000000000..084737248
--- /dev/null
+++ b/cmd/gitaly-hooks/.gitignore
@@ -0,0 +1,2 @@
+testdata/gitaly-libexec
+testdata/tempfile \ No newline at end of file
diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go
new file mode 100644
index 000000000..c699d0c6d
--- /dev/null
+++ b/cmd/gitaly-hooks/hooks.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "gitlab.com/gitlab-org/gitaly/internal/command"
+ "gitlab.com/gitlab-org/gitaly/internal/log"
+)
+
+func main() {
+ var logger = log.NewHookLogger()
+
+ if len(os.Args) < 2 {
+ logger.Fatal(errors.New("requires hook name"))
+ }
+
+ gitlabRubyDir := os.Getenv("GITALY_RUBY_DIR")
+ if gitlabRubyDir == "" {
+ logger.Fatal(errors.New("GITALY_RUBY_DIR not set"))
+ }
+
+ hookName := os.Args[1]
+ rubyHookPath := filepath.Join(gitlabRubyDir, "gitlab-shell", "hooks", hookName)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ var hookCmd *exec.Cmd
+
+ switch hookName {
+ case "update":
+ args := os.Args[2:]
+ if len(args) != 3 {
+ logger.Fatal(errors.New("update hook missing required arguments"))
+ }
+
+ hookCmd = exec.Command(rubyHookPath, args...)
+ case "pre-receive", "post-receive":
+ hookCmd = exec.Command(rubyHookPath)
+ default:
+ logger.Fatal(errors.New("hook name invalid"))
+ }
+
+ var stderr bytes.Buffer
+ mw := io.MultiWriter(&stderr, os.Stderr)
+
+ cmd, err := command.New(ctx, hookCmd, os.Stdin, os.Stdout, mw, os.Environ()...)
+ if err != nil {
+ logger.Fatalf("error when starting command for %v: %v", rubyHookPath, err)
+ }
+
+ if err = cmd.Wait(); err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go
new file mode 100644
index 000000000..cf8990c20
--- /dev/null
+++ b/cmd/gitaly-hooks/hooks_test.go
@@ -0,0 +1,300 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/command"
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+ "gopkg.in/yaml.v2"
+)
+
+func TestMain(m *testing.M) {
+ os.Exit(testMain(m))
+}
+
+func testMain(m *testing.M) int {
+ defer testhelper.MustHaveNoChildProcess()
+
+ configureGitalyHooksBinary()
+
+ return m.Run()
+}
+
+func TestHooksPrePostReceive(t *testing.T) {
+ secretToken := "secret token"
+ key := 1234
+ glRepository := "some_repo"
+
+ tempGitlabShellDir, cleanup := createTempGitlabShellDir(t)
+ defer cleanup()
+
+ changes := "abc"
+
+ ts := gitlabTestServer(t, secretToken, key, glRepository, changes, true)
+ defer ts.Close()
+
+ writeTemporaryConfigFile(t, tempGitlabShellDir, ts.URL)
+ writeShellSecretFile(t, tempGitlabShellDir, secretToken)
+
+ for _, hook := range []string{"pre-receive", "post-receive"} {
+ for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} {
+ t.Run(hook+"."+envName, func(t *testing.T) {
+ var stderr, stdout bytes.Buffer
+ stdin := bytes.NewBuffer([]byte(changes))
+ cmd := exec.Command(fmt.Sprintf("../../ruby/git-hooks/%s", hook))
+ cmd.Stderr = &stderr
+ cmd.Stdout = &stdout
+ cmd.Stdin = stdin
+ cmd.Env = env
+
+ require.NoError(t, cmd.Run())
+ require.Empty(t, stderr.String())
+ require.Empty(t, stdout.String())
+ })
+ }
+ }
+}
+
+func TestHooksUpdate(t *testing.T) {
+ key := 1234
+ glRepository := "some_repo"
+
+ tempGitlabShellDir, cleanup := createTempGitlabShellDir(t)
+ defer cleanup()
+
+ writeTemporaryConfigFile(t, tempGitlabShellDir, "http://www.example.com")
+ writeShellSecretFile(t, tempGitlabShellDir, "the wrong token")
+
+ require.NoError(t, os.MkdirAll(filepath.Join(tempGitlabShellDir, "hooks", "update.d"), 0755))
+ testhelper.MustRunCommand(t, nil, "cp", "testdata/update", filepath.Join(tempGitlabShellDir, "hooks", "update.d", "update"))
+
+ for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} {
+ t.Run(envName, func(t *testing.T) {
+ refval, oldval, newval := "refval", "oldval", "newval"
+ var stdout, stderr bytes.Buffer
+
+ cmd := exec.Command(fmt.Sprintf("../../ruby/git-hooks/%s", "update"), refval, oldval, newval)
+ cmd.Env = env
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ require.NoError(t, cmd.Run())
+ require.FileExists(t, "testdata/tempfile")
+ require.Empty(t, stdout.String())
+ require.Empty(t, stderr.String())
+
+ var inputs []string
+
+ f, err := os.Open("testdata/tempfile")
+ require.NoError(t, err)
+ require.NoError(t, json.NewDecoder(f).Decode(&inputs))
+ require.Equal(t, []string{refval, oldval, newval}, inputs)
+ require.NoError(t, os.Remove("testdata/tempfile"))
+ })
+ }
+}
+
+func TestHooksPostReceiveFailed(t *testing.T) {
+ secretToken := "secret token"
+ key := 1234
+ glRepository := "some_repo"
+
+ tempGitlabShellDir, cleanup := createTempGitlabShellDir(t)
+ defer cleanup()
+
+ // By setting the last parameter to false, the post-receive API call will
+ // send back {"reference_counter_increased": false}, indicating something went wrong
+ // with the call
+
+ ts := gitlabTestServer(t, secretToken, key, glRepository, "", false)
+ defer ts.Close()
+
+ writeTemporaryConfigFile(t, tempGitlabShellDir, ts.URL)
+ writeShellSecretFile(t, tempGitlabShellDir, secretToken)
+
+ for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} {
+ t.Run(envName, func(t *testing.T) {
+ var stdout, stderr bytes.Buffer
+
+ cmd := exec.Command(fmt.Sprintf("../../ruby/git-hooks/%s", "post-receive"))
+ cmd.Env = env
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ err := cmd.Run()
+ code, ok := command.ExitStatus(err)
+
+ require.True(t, ok, "expect exit status in %v", err)
+ require.Equal(t, 1, code, "exit status")
+ require.Empty(t, stdout.String())
+ require.Empty(t, stderr.String())
+ })
+ }
+}
+
+func TestHooksNotAllowed(t *testing.T) {
+ secretToken := "secret token"
+ key := 1234
+ glRepository := "some_repo"
+
+ tempGitlabShellDir, cleanup := createTempGitlabShellDir(t)
+ defer cleanup()
+
+ ts := gitlabTestServer(t, secretToken, key, glRepository, "", true)
+ defer ts.Close()
+
+ writeTemporaryConfigFile(t, tempGitlabShellDir, ts.URL)
+ writeShellSecretFile(t, tempGitlabShellDir, "the wrong token")
+
+ for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} {
+ t.Run(envName, func(t *testing.T) {
+ var stderr, stdout bytes.Buffer
+
+ cmd := exec.Command(fmt.Sprintf("../../ruby/git-hooks/%s", "pre-receive"))
+ cmd.Stderr = &stderr
+ cmd.Stdout = &stdout
+ cmd.Env = env
+
+ require.Error(t, cmd.Run())
+ require.Equal(t, "GitLab: 401 Unauthorized\n", stderr.String())
+ require.Equal(t, "", stdout.String())
+ })
+ }
+}
+
+type GitlabShellConfig struct {
+ GitlabURL string `yaml:"gitlab_url"`
+}
+
+func handleAllowed(t *testing.T, secretToken string, key int, glRepository, changes string) func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ require.NoError(t, r.ParseForm())
+ require.Equal(t, http.MethodPost, r.Method)
+ require.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type"))
+ require.Equal(t, strconv.Itoa(key), r.Form.Get("key_id"))
+ require.Equal(t, glRepository, r.Form.Get("gl_repository"))
+ require.Equal(t, "ssh", r.Form.Get("protocol"))
+ require.Equal(t, changes, r.Form.Get("changes"))
+
+ w.Header().Set("Content-Type", "application/json")
+ if r.Form.Get("secret_token") == secretToken {
+ w.Write([]byte(`{"status":true}`))
+ return
+ }
+ w.WriteHeader(http.StatusUnauthorized)
+ w.Write([]byte(`{"message":"401 Unauthorized"}`))
+ }
+}
+
+func handlePreReceive(t *testing.T, secretToken, glRepository string) func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ require.NoError(t, r.ParseForm())
+ require.Equal(t, http.MethodPost, r.Method)
+ require.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type"))
+ require.Equal(t, glRepository, r.Form.Get("gl_repository"))
+ require.Equal(t, secretToken, r.Form.Get("secret_token"))
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"reference_counter_increased": true}`))
+ }
+}
+
+func handlePostReceive(t *testing.T, secretToken string, key int, glRepository, changes string, counterDecreased bool) func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ require.NoError(t, r.ParseForm())
+ require.Equal(t, http.MethodPost, r.Method)
+ require.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type"))
+ require.Equal(t, glRepository, r.Form.Get("gl_repository"))
+ require.Equal(t, secretToken, r.Form.Get("secret_token"))
+ require.Equal(t, fmt.Sprintf("key-%d", key), r.Form.Get("identifier"))
+ require.Equal(t, changes, r.Form.Get("changes"))
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(fmt.Sprintf(`{"reference_counter_decreased": %v}`, counterDecreased)))
+ }
+}
+
+func gitlabTestServer(t *testing.T, secretToken string, key int, glRepository, changes string, postReceiveCounterDecreased bool) *httptest.Server {
+ mux := http.NewServeMux()
+ mux.Handle("/api/v4/internal/allowed", http.HandlerFunc(handleAllowed(t, secretToken, key, glRepository, changes)))
+ mux.Handle("/api/v4/internal/pre_receive", http.HandlerFunc(handlePreReceive(t, secretToken, glRepository)))
+ mux.Handle("/api/v4/internal/post_receive", http.HandlerFunc(handlePostReceive(t, secretToken, key, glRepository, changes, postReceiveCounterDecreased)))
+
+ return httptest.NewServer(mux)
+}
+
+func createTempGitlabShellDir(t *testing.T) (string, func()) {
+ tempDir, err := ioutil.TempDir("", "gitlab-shell")
+ require.NoError(t, err)
+ return tempDir, func() {
+ require.NoError(t, os.RemoveAll(tempDir))
+ }
+}
+
+func writeTemporaryConfigFile(t *testing.T, dir, testServerURL string) {
+ cfg := GitlabShellConfig{GitlabURL: testServerURL}
+ out, err := yaml.Marshal(cfg)
+ require.NoError(t, err)
+ require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "config.yml"), out, 0644))
+}
+
+func env(t *testing.T, glRepo, gitlabShellDir string, key int) []string {
+ rubyDir, err := filepath.Abs("../../ruby")
+ require.NoError(t, err)
+
+ return append(oldEnv(t, glRepo, gitlabShellDir, key), []string{
+ "GITALY_BIN_DIR=testdata/gitaly-libexec",
+ fmt.Sprintf("GITALY_RUBY_DIR=%s", rubyDir),
+ }...)
+}
+
+func oldEnv(t *testing.T, glRepo, gitlabShellDir string, key int) []string {
+ return append([]string{
+ fmt.Sprintf("GL_ID=key-%d", key),
+ fmt.Sprintf("GL_REPOSITORY=%s", glRepo),
+ "GL_PROTOCOL=ssh",
+ fmt.Sprintf("GITALY_GITLAB_SHELL_DIR=%s", gitlabShellDir),
+ fmt.Sprintf("GITALY_LOG_DIR=%s", gitlabShellDir),
+ "GITALY_LOG_LEVEL=info",
+ "GITALY_LOG_FORMAT=json",
+ fmt.Sprintf("GITALY_LOG_DIR=%s", gitlabShellDir),
+ }, os.Environ()...)
+}
+
+func writeShellSecretFile(t *testing.T, dir, secretToken string) {
+ require.NoError(t, ioutil.WriteFile(filepath.Join(dir, ".gitlab_shell_secret"), []byte(secretToken), 0644))
+}
+
+// configureGitalyHooksBinary builds gitaly-hooks command for tests
+func configureGitalyHooksBinary() {
+ var err error
+
+ config.Config.BinDir, err = filepath.Abs("testdata/gitaly-libexec")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ goBuildArgs := []string{
+ "build",
+ "-o",
+ path.Join(config.Config.BinDir, "gitaly-hooks"),
+ "gitlab.com/gitlab-org/gitaly/cmd/gitaly-hooks",
+ }
+ testhelper.MustRunCommand(nil, nil, "go", goBuildArgs...)
+}
diff --git a/cmd/gitaly-hooks/testdata/update b/cmd/gitaly-hooks/testdata/update
new file mode 100755
index 000000000..a4076ec24
--- /dev/null
+++ b/cmd/gitaly-hooks/testdata/update
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+require 'json'
+
+open('testdata/tempfile', 'w') { |f| f.puts(JSON.dump(ARGV)) }
diff --git a/go.mod b/go.mod
index 6cd6efcec..3302cc021 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,6 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/kelseyhightower/envconfig v1.3.0
- github.com/kr/pretty v0.1.0 // indirect
github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47
github.com/prometheus/client_golang v1.0.0
github.com/sirupsen/logrus v1.2.0
@@ -22,6 +21,5 @@ require (
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898 // indirect
google.golang.org/grpc v1.16.0
- gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
- gopkg.in/yaml.v2 v2.2.2 // indirect
+ gopkg.in/yaml.v2 v2.2.2
)
diff --git a/go.sum b/go.sum
index 9bce6f095..c8095c6b6 100644
--- a/go.sum
+++ b/go.sum
@@ -51,11 +51,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47 h1:HDt7WT3kpXSHq4mlOuLzgXH9LeOK1qlhyFdKIAzxxeM=
github.com/libgit2/git2go v0.0.0-20190104134018-ecaeb7a21d47/go.mod h1:4bKN42efkbNYMZlvDfxGDxzl066GhpvIircZDsm8Y+Y=
github.com/lightstep/lightstep-tracer-go v0.15.6 h1:D0GGa7afJ7GcQvu5as6ssLEEKYXvRgKI5d5cevtz8r4=
@@ -156,8 +151,6 @@ gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzw
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
diff --git a/internal/git/receivepack.go b/internal/git/receivepack.go
index c9670612f..f4dbdba8c 100644
--- a/internal/git/receivepack.go
+++ b/internal/git/receivepack.go
@@ -22,8 +22,8 @@ func HookEnv(req ReceivePackRequest) []string {
return append([]string{
fmt.Sprintf("GL_ID=%s", req.GetGlId()),
fmt.Sprintf("GL_USERNAME=%s", req.GetGlUsername()),
- fmt.Sprintf("GITLAB_SHELL_DIR=%s", config.Config.GitlabShell.Dir),
fmt.Sprintf("GL_REPOSITORY=%s", req.GetGlRepository()),
+ fmt.Sprintf("GITLAB_SHELL_DIR=%s", config.Config.GitlabShell.Dir),
}, gitlabshell.Env()...)
}
diff --git a/internal/gitlabshell/env.go b/internal/gitlabshell/env.go
index f3ff7bf73..70e391ece 100644
--- a/internal/gitlabshell/env.go
+++ b/internal/gitlabshell/env.go
@@ -12,5 +12,7 @@ func Env() []string {
"GITALY_LOG_FORMAT=" + cfg.Logging.Format,
"GITALY_LOG_LEVEL=" + cfg.Logging.Level,
"GITLAB_SHELL_DIR=" + cfg.GitlabShell.Dir, //GITLAB_SHELL_DIR has been deprecated
+ "GITALY_BIN_DIR=" + config.Config.BinDir,
+ "GITALY_RUBY_DIR=" + config.Config.Ruby.Dir,
}
}
diff --git a/internal/log/hook.go b/internal/log/hook.go
new file mode 100644
index 000000000..54789ab3d
--- /dev/null
+++ b/internal/log/hook.go
@@ -0,0 +1,42 @@
+package log
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/sirupsen/logrus"
+)
+
+// HookLogger is a wrapper around *logrus.Logger
+type HookLogger struct {
+ logger *logrus.Logger
+}
+
+// NewHookLogger creates a file logger, since both stderr and stdout will be displayed in git output
+func NewHookLogger() *HookLogger {
+ logger := logrus.New()
+
+ filepath := filepath.Join(os.Getenv("GITALY_LOG_DIR"), "gitaly_hooks.log")
+
+ logFile, err := os.OpenFile(filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ logger.SetOutput(ioutil.Discard)
+ } else {
+ logger.SetOutput(logFile)
+ }
+
+ return &HookLogger{logger: logger}
+}
+
+// Fatal logs an error at the Fatal level and writes a generic message to stderr
+func (h *HookLogger) Fatal(err error) {
+ h.Fatalf("%v", err)
+}
+
+// Fatalf logs a formatted error at the Fatal level
+func (h *HookLogger) Fatalf(format string, a ...interface{}) {
+ fmt.Fprintf(os.Stderr, "error executing git hook")
+ h.logger.Fatalf(format, a...)
+}
diff --git a/internal/rubyserver/rubyserver.go b/internal/rubyserver/rubyserver.go
index 92196f79b..82f806c5c 100644
--- a/internal/rubyserver/rubyserver.go
+++ b/internal/rubyserver/rubyserver.go
@@ -117,6 +117,7 @@ func (s *Server) start() error {
fmt.Sprintf("GITALY_RUBY_WRITE_BUFFER_SIZE=%d", streamio.WriteBufferSize),
fmt.Sprintf("GITALY_RUBY_MAX_COMMIT_OR_TAG_MESSAGE_SIZE=%d", helper.MaxCommitOrTagMessageSize),
"GITALY_RUBY_GITALY_BIN_DIR="+cfg.BinDir,
+ "GITALY_RUBY_DIR="+cfg.Ruby.Dir,
"GITALY_VERSION="+version.GetVersion(),
"GITALY_GIT_HOOKS_DIR="+hooks.Path())
env = append(env, gitlabshell.Env()...)
diff --git a/ruby/git-hooks/gitlab-shell-hook b/ruby/git-hooks/gitlab-shell-hook
index 7ad435b18..550f31d83 100755
--- a/ruby/git-hooks/gitlab-shell-hook
+++ b/ruby/git-hooks/gitlab-shell-hook
@@ -1,6 +1,9 @@
#!/bin/sh
# This is the single source of truth for where Gitaly's embedded Git hooks are.
-hooks_dir="$(dirname $0)/../gitlab-shell/hooks"
-
-exec "$hooks_dir/$(basename $0)" "$@"
+if [ -n "$GITALY_BIN_DIR" ]; then
+ exec "$GITALY_BIN_DIR/gitaly-hooks" "$(basename $0)" "$@"
+else
+ hooks_dir="$(dirname $0)/../gitlab-shell/hooks"
+ exec "$hooks_dir/$(basename $0)" "$@"
+fi
diff --git a/ruby/lib/gitlab/config.rb b/ruby/lib/gitlab/config.rb
index 91ab1d741..2763e55f2 100644
--- a/ruby/lib/gitlab/config.rb
+++ b/ruby/lib/gitlab/config.rb
@@ -52,8 +52,12 @@ module Gitlab
class Gitaly
include TestSetup
- def client_path
- @client_path ||= ENV['GITALY_RUBY_GITALY_BIN_DIR']
+ def bin_dir
+ @bin_dir ||= ENV['GITALY_RUBY_GITALY_BIN_DIR']
+ end
+
+ def ruby_dir
+ @ruby_dir ||= ENV['GITALY_RUBY_DIR']
end
def rbtrace_enabled?
diff --git a/ruby/lib/gitlab/git/hook.rb b/ruby/lib/gitlab/git/hook.rb
index 3691f8639..61af0999a 100644
--- a/ruby/lib/gitlab/git/hook.rb
+++ b/ruby/lib/gitlab/git/hook.rb
@@ -107,6 +107,8 @@ module Gitlab
'GITALY_LOG_DIR' => Gitlab.config.logging.dir,
'GITALY_LOG_LEVEL' => Gitlab.config.logging.level,
'GITALY_LOG_FORMAT' => Gitlab.config.logging.format,
+ 'GITALY_RUBY_DIR' => Gitlab.config.gitaly.ruby_dir,
+ 'GITALY_BIN_DIR' => Gitlab.config.gitaly.bin_dir,
'GL_ID' => gl_id,
'GL_USERNAME' => gl_username,
'GL_REPOSITORY' => repository.gl_repository,
diff --git a/ruby/lib/gitlab/git/remote_repository.rb b/ruby/lib/gitlab/git/remote_repository.rb
index df857176d..721753399 100644
--- a/ruby/lib/gitlab/git/remote_repository.rb
+++ b/ruby/lib/gitlab/git/remote_repository.rb
@@ -36,7 +36,7 @@ module Gitlab
end
def fetch_env(git_config_options: [])
- gitaly_ssh = File.absolute_path(File.join(Gitlab.config.gitaly.client_path, 'gitaly-ssh'))
+ gitaly_ssh = File.absolute_path(File.join(Gitlab.config.gitaly.bin_dir, 'gitaly-ssh'))
gitaly_address = gitaly_client.address(storage)
gitaly_token = gitaly_client.token(storage)
diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb
index 9011b96f3..49e956f92 100644
--- a/ruby/spec/spec_helper.rb
+++ b/ruby/spec/spec_helper.rb
@@ -14,7 +14,7 @@ Dir[File.join(__dir__, 'support/helpers/*.rb')].each { |f| require f }
Gitlab.config.git.test_global_ivar_override(:bin_path, 'git')
Gitlab.config.git.test_global_ivar_override(:hooks_directory, File.join(Gitlab.config.gitlab_shell.path.to_s, "hooks"))
-Gitlab.config.gitaly.test_global_ivar_override(:client_path, __dir__)
+Gitlab.config.gitaly.test_global_ivar_override(:bin_dir, __dir__)
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods